commit 2d133e56d7c55cedeef50512512df6128f60d700 Author: gem <> Date: Tue Feb 18 15:21:31 2025 +0800 no message diff --git a/CMakeLists.header.txt b/CMakeLists.header.txt new file mode 100644 index 0000000..7dd60b3 --- /dev/null +++ b/CMakeLists.header.txt @@ -0,0 +1,6 @@ + +cmake_minimum_required(VERSION 3.8) +project(engine_native CXX) +enable_language(C ASM) + +include(${CMAKE_CURRENT_LIST_DIR}/../CMakeLists.txt) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..947dcbc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,3395 @@ +if(NOT USE_XR AND NOT USE_AR_MODULE) + include(${CMAKE_CURRENT_LIST_DIR}/cmake/predefine.cmake) +endif() + +################################# engine source code ################################## +set(CWD ${CMAKE_CURRENT_LIST_DIR}) +set(COCOS_SOURCE_LIST) +set(ENGINE_NAME cocos_engine) + +# Should be enable someday in the future +# set(CMAKE_CXX_FLAGS "${WERROR_FLAGS}") + +################################# options ############################################ +# default fallback options +cc_set_if_undefined(CC_DEBUG_FORCE OFF) +cc_set_if_undefined(USE_SE_V8 ON) +cc_set_if_undefined(USE_SE_SM OFF) +cc_set_if_undefined(USE_SE_NAPI OFF) +cc_set_if_undefined(USE_V8_DEBUGGER ON) +cc_set_if_undefined(USE_V8_DEBUGGER_FORCE OFF) +cc_set_if_undefined(USE_SOCKET ON) +cc_set_if_undefined(USE_AUDIO ON) +cc_set_if_undefined(USE_EDIT_BOX ON) +cc_set_if_undefined(USE_VIDEO ON) +cc_set_if_undefined(USE_WEBVIEW ON) +cc_set_if_undefined(USE_MIDDLEWARE ON) +cc_set_if_undefined(USE_DRAGONBONES ON) +cc_set_if_undefined(USE_SPINE ON) +cc_set_if_undefined(USE_WEBSOCKET_SERVER OFF) +cc_set_if_undefined(USE_JOB_SYSTEM_TASKFLOW OFF) +cc_set_if_undefined(USE_JOB_SYSTEM_TBB OFF) +cc_set_if_undefined(USE_PHYSICS_PHYSX OFF) +cc_set_if_undefined(USE_MODULES OFF) +cc_set_if_undefined(USE_XR OFF) +cc_set_if_undefined(USE_SERVER_MODE OFF) +cc_set_if_undefined(USE_AR_MODULE OFF) +cc_set_if_undefined(USE_AR_AUTO OFF) +cc_set_if_undefined(USE_AR_CORE OFF) +cc_set_if_undefined(USE_AR_ENGINE OFF) +cc_set_if_undefined(USE_PLUGINS ON) +cc_set_if_undefined(USE_OCCLUSION_QUERY ON) +cc_set_if_undefined(USE_DEBUG_RENDERER ON) +cc_set_if_undefined(USE_GEOMETRY_RENDERER ON) +cc_set_if_undefined(USE_WEBP ON) +cc_set_if_undefined(NET_MODE 0) # 0 is client +cc_set_if_undefined(USE_REMOTE_LOG OFF) + + +if(ANDROID AND NOT DEFINED USE_CCACHE) + if("$ENV{COCOS_USE_CCACHE}" STREQUAL "1") + message(STATUS "Enable ccache by env CC_USE_CACHE=1") + set(USE_CCACHE ON) + else() + set(USE_CCACHE OFF) + endif() +endif() + +if(NX) + cc_set_if_undefined(CC_USE_VULKAN ON) + cc_set_if_undefined(CC_USE_GLES3 OFF) + cc_set_if_undefined(CC_USE_GLES2 OFF) +elseif(ANDROID OR WINDOWS OR OHOS) + cc_set_if_undefined(CC_USE_GLES3 ON) + cc_set_if_undefined(CC_USE_VULKAN OFF) + cc_set_if_undefined(CC_USE_GLES2 OFF) +elseif(MACOSX OR IOS) + cc_set_if_undefined(CC_USE_METAL ON) + cc_set_if_undefined(CC_USE_VULKAN OFF) + cc_set_if_undefined(CC_USE_GLES3 OFF) + cc_set_if_undefined(CC_USE_GLES2 OFF) +endif() + +set(CC_USE_GLES3 ${CC_USE_GLES3} CACHE INTERNAL "") +set(CC_USE_GLES2 ${CC_USE_GLES2} CACHE INTERNAL "") +set(CC_USE_VULKAN ${CC_USE_VULKAN} CACHE INTERNAL "") +set(CC_USE_METAL ${CC_USE_METAL} CACHE INTERNAL "") + +if(USE_SERVER_MODE) + set(CC_USE_VULKAN OFF) + set(CC_USE_METAL OFF) + set(CC_USE_GLES3 OFF) + set(CC_USE_GLES2 OFF) + add_definitions(-DCC_SERVER_MODE) +endif() + +# Fix the issue: https://github.com/cocos/cocos-engine/issues/16190 +# std::unary_function was removed since clang 15 +# The macro has already been defined in boost/config/stdlib/dinkumware.hpp(258,1), +# so no need to define it again to avoid 'macro redefinition' error. +if(NOT WINDOWS) + add_definitions(-DBOOST_NO_CXX98_FUNCTION_BASE) +endif() + +add_definitions(-DCC_NETMODE_CLIENT=0) +add_definitions(-DCC_NETMODE_LISTEN_SERVER=1) +add_definitions(-DCC_NETMODE_HOST_SERVER=2) +if(${NET_MODE} EQUAL "0" OR ${NET_MODE} EQUAL "1" OR ${NET_MODE} EQUAL "2") + add_definitions(-DCC_NETMODE=${NET_MODE}) +else() + message(FATAL_ERROR "NET_MODE only support setting to 1 2 or 3.") +endif() + +if(USE_PHYSICS_PHYSX) + if (ANDROID AND CMAKE_ANDROID_ARCH_ABI MATCHES x86) + set(USE_PHYSICS_PHYSX OFF) + message(AUTHOR_WARNING "Native PhysX does not support Google Android X86") + endif() +endif() + +if(NOT USE_SOCKET) + set(USE_WEBSOCKET_SERVER OFF) +endif() + +if(OPENHARMONY AND OHOS) + set(CC_USE_GLES2 OFF) +endif() + +## disable videoplay on non-mobile platforms +if(NOT ANDROID AND NOT IOS AND NOT OHOS AND NOT OPENHARMONY) + set(USE_VIDEO OFF) + set(USE_WEBVIEW OFF) +endif() + +## disable middleware when dragonbones and spine are disabled +if(NOT USE_DRAGONBONES AND NOT USE_SPINE) + set(USE_MIDDLEWARE OFF) +endif() + +if(USE_DRAGONBONES OR USE_SPINE) + set(USE_MIDDLEWARE ON) +endif() + +if(USE_JOB_SYSTEM_TASKFLOW AND USE_JOB_SYSTEM_TBB) + set(USE_JOB_SYSTEM_TASKFLOW ON) + set(USE_JOB_SYSTEM_TBB OFF) +endif() + +if(OHOS AND USE_JOB_SYSTEM_TBB) + message(WARNING "JobSystem tbb is not supported by HarmonyOS") + set(USE_JOB_SYSTEM_TBB OFF) +endif() + +if(USE_JOB_SYSTEM_TASKFLOW) + set(CMAKE_CXX_STANDARD 17) + if(IOS AND "${TARGET_IOS_VERSION}" VERSION_LESS "12.0") + message(FATAL_ERROR "Target iOS version requires 12.0+ when enable taskflow") + endif() +endif() + +if(ANDROID) + if(USE_CCACHE AND NOT CCACHE_EXECUTABLE) + message(AUTHOR_WARNING "ccache executable not found!") + endif() + if(CCACHE_EXECUTABLE AND USE_CCACHE) + set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_EXECUTABLE}") + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_EXECUTABLE}") + endif() +endif() + +if(CI_FORCE_MINIMAL_FEATURES) + set(USE_V8_DEBUGGER_FORCE OFF) + set(USE_V8_DEBUGGER OFF) + set(USE_AUDIO OFF) + set(USE_EDIT_BOX OFF) + set(USE_VIDEO OFF) + set(USE_WEBVIEW OFF) + set(USE_MIDDLEWARE OFF) + set(USE_DRAGONBONES OFF) + set(USE_SPINE OFF) + set(USE_SOCKET OFF) + set(USE_WEBSOCKET_SERVER OFF) + set(USE_PHYSICS_PHYSX OFF) + set(USE_JOB_SYSTEM_TBB OFF) + set(USE_JOB_SYSTEM_TASKFLOW OFF) + set(USE_PLUGINS OFF) + set(USE_OCCLUSION_QUERY OFF) + set(USE_DEBUG_RENDERER OFF) + set(USE_GEOMETRY_RENDERER OFF) + set(USE_WEBP OFF) +endif() + +################################# external source code ################################ +set(EXTERNAL_ROOT ${CMAKE_CURRENT_LIST_DIR}/external) +set(USE_BUILTIN_EXTERNAL OFF) +if(NOT EXISTS ${EXTERNAL_ROOT}/CMakeLists.txt) + if(DEFINED BUILTIN_COCOS_X_PATH) + set(EXTERNAL_ROOT ${BUILTIN_COCOS_X_PATH}/external) + set(USE_BUILTIN_EXTERNAL ON) + message(AUTHOR_WARNING "Directory 'external' not found in '${CMAKE_CURRENT_LIST_DIR}', use builtin directory '${BUILTIN_COCOS_X_PATH}' instead!") + else() + message(FATAL_ERROR "Please download external libraries! File ${CMAKE_CURRENT_LIST_DIR}/external/CMakeLists.txt not exist!") + endif() +endif() + +include_directories(${EXTERNAL_ROOT}/sources) + +################################# list all option values ############################## + +cc_inspect_values( + BUILTIN_COCOS_X_PATH + USE_BUILTIN_EXTERNAL + USE_MODULES + CC_USE_METAL + CC_USE_GLES3 + CC_USE_GLES2 + CC_USE_VULKAN + CC_DEBUG_FORCE + USE_SE_V8 + USE_V8_DEBUGGER + USE_V8_DEBUGGER_FORCE + USE_SE_SM + USE_SOCKET + USE_AUDIO + USE_EDIT_BOX + USE_VIDEO + USE_WEBVIEW + USE_MIDDLEWARE + USE_DRAGONBONES + USE_SPINE + USE_WEBSOCKET_SERVER + USE_PHYSICS_PHYSX + USE_JOB_SYSTEM_TBB + USE_JOB_SYSTEM_TASKFLOW + USE_XR + USE_SERVER_MODE + USE_AR_MODULE + USE_AR_AUTO + USE_AR_CORE + USE_AR_ENGINE + USE_CCACHE + CCACHE_EXECUTABLE + NODE_EXECUTABLE + NET_MODE + USE_REMOTE_LOG +) + +if(USE_XR) + if(USE_XR_REMOTE_PREVIEW) + set(USE_WEBSOCKET_SERVER ON) + endif() +endif() + +include(${EXTERNAL_ROOT}/CMakeLists.txt) + +##### audio +if(USE_AUDIO) + cocos_source_files( + cocos/audio/AudioEngine.cpp + cocos/audio/include/AudioEngine.h + cocos/audio/include/AudioDef.h + cocos/audio/include/Export.h + ) + if(WINDOWS) + cocos_source_files( + cocos/audio/common/utils/tinysndfile.cpp +NO_WERROR cocos/audio/common/utils/primitives.cpp + cocos/audio/common/utils/include/tinysndfile.h + cocos/audio/common/utils/include/primitives.h + cocos/audio/common/utils/private/private.h + cocos/audio/common/decoder/AudioDecoder.cpp + cocos/audio/common/decoder/AudioDecoder.h + cocos/audio/common/decoder/AudioDecoderManager.cpp + cocos/audio/common/decoder/AudioDecoderManager.h + cocos/audio/common/decoder/AudioDecoderMp3.cpp + cocos/audio/common/decoder/AudioDecoderMp3.h + cocos/audio/common/decoder/AudioDecoderOgg.cpp + cocos/audio/common/decoder/AudioDecoderOgg.h + cocos/audio/common/decoder/AudioDecoderWav.cpp + cocos/audio/common/decoder/AudioDecoderWav.h + cocos/audio/oalsoft/AudioCache.cpp + cocos/audio/oalsoft/AudioCache.h + cocos/audio/oalsoft/AudioEngine-soft.cpp + cocos/audio/oalsoft/AudioEngine-soft.h + cocos/audio/include/AudioMacros.h + cocos/audio/oalsoft/AudioPlayer.cpp + cocos/audio/oalsoft/AudioPlayer.h + ) + elseif(LINUX OR QNX) + cocos_source_files( + cocos/audio/common/decoder/AudioDecoder.cpp + cocos/audio/common/decoder/AudioDecoder.h + cocos/audio/common/decoder/AudioDecoderManager.cpp + cocos/audio/common/decoder/AudioDecoderManager.h + cocos/audio/common/decoder/AudioDecoderMp3.cpp + cocos/audio/common/decoder/AudioDecoderMp3.h + cocos/audio/common/decoder/AudioDecoderOgg.cpp + cocos/audio/common/decoder/AudioDecoderOgg.h + cocos/audio/oalsoft/AudioCache.cpp + cocos/audio/oalsoft/AudioCache.h + cocos/audio/oalsoft/AudioEngine-soft.cpp + cocos/audio/oalsoft/AudioEngine-soft.h + cocos/audio/include/AudioMacros.h + cocos/audio/oalsoft/AudioPlayer.cpp + cocos/audio/oalsoft/AudioPlayer.h + ) + elseif(ANDROID OR OPENHARMONY) + cocos_source_files( + cocos/audio/common/utils/include/primitives.h + cocos/audio/common/utils/include/format.h + cocos/audio/common/utils/include/minifloat.h + cocos/audio/common/utils/include/tinysndfile.h + cocos/audio/common/utils/private/private.h + cocos/audio/common/utils/minifloat.cpp + cocos/audio/common/utils/primitives.cpp + cocos/audio/common/utils/tinysndfile.cpp + cocos/audio/common/utils/format.cpp + cocos/audio/android/AssetFd.cpp + cocos/audio/android/AssetFd.h + cocos/audio/android/audio.h + cocos/audio/android/AudioBufferProvider.h + cocos/audio/android/AudioDecoder.cpp + cocos/audio/android/AudioDecoder.h + cocos/audio/android/AudioDecoderMp3.cpp + cocos/audio/android/AudioDecoderMp3.h + cocos/audio/android/AudioDecoderOgg.cpp + cocos/audio/android/AudioDecoderOgg.h + cocos/audio/android/AudioDecoderProvider.cpp + cocos/audio/android/AudioDecoderProvider.h + cocos/audio/android/AudioDecoderSLES.cpp + cocos/audio/android/AudioDecoderSLES.h + cocos/audio/android/AudioDecoderWav.cpp + cocos/audio/android/AudioDecoderWav.h + cocos/audio/android/AudioEngine-inl.cpp + cocos/audio/android/AudioEngine-inl.h + cocos/audio/android/AudioMixer.cpp + cocos/audio/android/AudioMixer.h + cocos/audio/android/AudioMixerController.cpp + cocos/audio/android/AudioMixerController.h + cocos/audio/android/AudioMixerOps.h + cocos/audio/android/AudioPlayerProvider.cpp + cocos/audio/android/AudioPlayerProvider.h + cocos/audio/android/AudioResampler.cpp + cocos/audio/android/AudioResampler.h + cocos/audio/android/AudioResamplerCubic.cpp + cocos/audio/android/AudioResamplerCubic.h + cocos/audio/android/AudioResamplerPublic.h + cocos/audio/android/cutils/bitops.h + cocos/audio/android/cutils/log.h + cocos/audio/android/IAudioPlayer.h + cocos/audio/android/ICallerThreadUtils.h + cocos/audio/android/IVolumeProvider.h + cocos/audio/android/mp3reader.cpp + cocos/audio/android/mp3reader.h + cocos/audio/android/OpenSLHelper.h + cocos/audio/android/PcmAudioPlayer.cpp + cocos/audio/android/PcmAudioPlayer.h + cocos/audio/android/PcmAudioService.cpp + cocos/audio/android/PcmAudioService.h + cocos/audio/android/PcmBufferProvider.cpp + cocos/audio/android/PcmBufferProvider.h + cocos/audio/android/PcmData.cpp + cocos/audio/android/PcmData.h + cocos/audio/android/Track.cpp + cocos/audio/android/Track.h + cocos/audio/android/UrlAudioPlayer.cpp + cocos/audio/android/UrlAudioPlayer.h + cocos/audio/android/utils/Compat.h + cocos/audio/android/utils/Errors.h + cocos/audio/android/utils/Utils.cpp + cocos/audio/android/utils/Utils.h + ) + elseif(OHOS) + cocos_source_files( + cocos/audio/common/utils/tinysndfile.cpp + cocos/audio/common/utils/primitives.cpp + cocos/audio/common/utils/include/tinysndfile.h + cocos/audio/common/utils/include/primitives.h + cocos/audio/common/decoder/AudioDecoder.cpp + cocos/audio/common/decoder/AudioDecoder.h + cocos/audio/common/decoder/AudioDecoderManager.cpp + cocos/audio/common/decoder/AudioDecoderManager.h + cocos/audio/common/decoder/AudioDecoderMp3.cpp + cocos/audio/common/decoder/AudioDecoderMp3.h + cocos/audio/common/decoder/AudioDecoderOgg.cpp + cocos/audio/common/decoder/AudioDecoderOgg.h + cocos/audio/common/decoder/AudioDecoderWav.cpp + cocos/audio/common/decoder/AudioDecoderWav.h + cocos/audio/oalsoft/AudioCache.cpp + cocos/audio/oalsoft/AudioCache.h + cocos/audio/oalsoft/AudioEngine-soft.cpp + cocos/audio/oalsoft/AudioEngine-soft.h + cocos/audio/include/AudioMacros.h + cocos/audio/oalsoft/AudioPlayer.cpp + cocos/audio/oalsoft/AudioPlayer.h + cocos/audio/ohos/FsCallback.h + cocos/audio/ohos/FsCallback.cpp + ) + elseif(APPLE) + cocos_source_files( + cocos/audio/apple/AudioDecoder.h + NO_WERROR NO_UBUILD cocos/audio/apple/AudioPlayer.mm + NO_WERROR NO_UBUILD cocos/audio/apple/AudioDecoder.mm + cocos/audio/apple/AudioPlayer.h + NO_WERROR NO_UBUILD cocos/audio/apple/AudioEngine-inl.mm + cocos/audio/apple/AudioMacros.h + NO_WERROR NO_UBUILD cocos/audio/apple/AudioCache.mm + cocos/audio/apple/AudioCache.h + cocos/audio/apple/AudioEngine-inl.h + ) + endif() + +endif() + +##### plugins +cocos_source_files( + cocos/plugins/Plugins.h + cocos/plugins/Plugins.cpp + cocos/plugins/bus/BusTypes.h + cocos/plugins/bus/EventBus.h + cocos/plugins/bus/EventBus.cpp +) + +cocos_source_files( + cocos/core/event/Event.h + cocos/core/event/EventBus.h + cocos/core/event/EventBus.cpp + cocos/core/event/EventTarget.h + cocos/core/event/intl/EventIntl.h + cocos/core/event/intl/List.h + cocos/core/event/intl/EventTargetMacros.h + cocos/core/event/intl/EventBusMacros.h +) + +##### base +cocos_source_files( + cocos/base/Agent.h + cocos/base/astc.cpp + cocos/base/astc.h + cocos/base/Compressed.h + cocos/base/Compressed.cpp + cocos/base/base64.cpp + cocos/base/base64.h + cocos/base/Config.h + cocos/base/csscolorparser.cpp + cocos/base/csscolorparser.h + cocos/base/DeferredReleasePool.cpp + cocos/base/DeferredReleasePool.h + cocos/base/etc1.cpp + cocos/base/etc1.h + cocos/base/etc2.cpp + cocos/base/etc2.h + cocos/base/HasMemberFunction.h + cocos/base/IndexHandle.h + cocos/base/Locked.h + cocos/base/Macros.h + cocos/base/Assertf.h + cocos/base/Object.h + cocos/base/Ptr.h + cocos/base/Random.h + cocos/base/RefCounted.cpp + cocos/base/RefCounted.h + cocos/base/RefMap.h + cocos/base/RefVector.h + cocos/base/Scheduler.cpp + cocos/base/Scheduler.h + cocos/base/StringHandle.cpp + cocos/base/StringHandle.h + cocos/base/StringPool.h + cocos/base/StringUtil.cpp + cocos/base/StringUtil.h + cocos/base/TemplateUtils.h + cocos/base/ThreadPool.cpp + cocos/base/ThreadPool.h + cocos/base/TypeDef.h + cocos/base/BinaryArchive.cpp + cocos/base/BinaryArchive.h + cocos/base/std/any.h + cocos/base/std/optional.h + cocos/base/std/variant.h + cocos/base/std/container/array.h + cocos/base/std/container/deque.h + cocos/base/std/container/list.h + cocos/base/std/container/map.h + cocos/base/std/container/queue.h + cocos/base/std/container/set.h + cocos/base/std/container/string.h + cocos/base/std/container/unordered_map.h + cocos/base/std/container/unordered_set.h + cocos/base/std/container/vector.h + cocos/base/std/hash/detail/float_functions.hpp + cocos/base/std/hash/detail/hash_float.hpp + cocos/base/std/hash/detail/limits.hpp + cocos/base/std/hash/extensions.hpp + cocos/base/std/hash/hash_fwd.hpp + cocos/base/std/hash/hash.h +) + +############ application +cocos_source_files( + cocos/application/BaseApplication.h + cocos/application/CocosApplication.h + cocos/application/CocosApplication.cpp + cocos/application/ApplicationManager.h + cocos/application/ApplicationManager.cpp + cocos/application/BaseGame.h + cocos/application/BaseGame.cpp +) + +############ engine +cocos_source_files( + cocos/engine/BaseEngine.cpp + cocos/engine/BaseEngine.h + cocos/engine/Engine.cpp + cocos/engine/Engine.h +) + +############ gi +cocos_source_files( + cocos/gi/light-probe/AutoPlacement.cpp + cocos/gi/light-probe/AutoPlacement.h + cocos/gi/light-probe/Delaunay.cpp + cocos/gi/light-probe/Delaunay.h + cocos/gi/light-probe/LightProbe.cpp + cocos/gi/light-probe/LightProbe.h + cocos/gi/light-probe/PolynomialSolver.cpp + cocos/gi/light-probe/PolynomialSolver.h + cocos/gi/light-probe/SH.cpp + cocos/gi/light-probe/SH.h + cocos/gi/light-probe/tetgen.cpp + cocos/gi/light-probe/tetgen.h + cocos/gi/light-probe/predicates.cpp +) + +############ main +if(NOT USE_SERVER_MODE AND (WINDOWS OR LINUX OR MACOSX)) +cocos_source_files( + cocos/platform/SDLHelper.h + cocos/platform/SDLHelper.cpp +) +endif() + +############ platform +cocos_source_files( + cocos/platform/BasePlatform.cpp + cocos/platform/BasePlatform.h + cocos/platform/UniversalPlatform.cpp + cocos/platform/UniversalPlatform.h +) +if(WINDOWS) + cocos_source_files( + cocos/platform/win32/WindowsPlatform.cpp + cocos/platform/win32/WindowsPlatform.h + ) +elseif(LINUX) + cocos_source_files( + cocos/platform/linux/LinuxPlatform.cpp + cocos/platform/linux/LinuxPlatform.h + ) +elseif(ANDROID) + cocos_source_files( + cocos/platform/android/AndroidPlatform.cpp + cocos/platform/android/AndroidPlatform.h + cocos/platform/android/AndroidKeyCodes.cpp + cocos/platform/android/adpf_manager.h + cocos/platform/android/adpf_manager.cpp + ) +elseif(OPENHARMONY) + cocos_source_files( + cocos/platform/openharmony/napi/NapiHelper.cpp + cocos/platform/openharmony/napi/NapiHelper.h + + cocos/platform/openharmony/WorkerMessageQueue.cpp + cocos/platform/openharmony/WorkerMessageQueue.h + cocos/platform/openharmony/OpenHarmonyPlatform.cpp + cocos/platform/openharmony/OpenHarmonyPlatform.h + ) +elseif(OHOS) + cocos_source_files( + cocos/platform/ohos/OhosPlatform.cpp + cocos/platform/ohos/OhosPlatform.h + ) +elseif(MACOSX) + cocos_source_files( + cocos/platform/mac/MacPlatform.mm + cocos/platform/mac/MacPlatform.h + cocos/platform/mac/AppDelegate.h + cocos/platform/mac/AppDelegate.mm + ) +elseif(IOS) + cocos_source_files( + cocos/platform/ios/IOSPlatform.mm + cocos/platform/ios/IOSPlatform.h + cocos/platform/ios/AppDelegateBridge.mm + cocos/platform/ios/AppDelegateBridge.h + ) +endif() + +############ platform : Abstract interface +cocos_source_files( + cocos/platform/interfaces/OSInterface.h +) + +############ platform : Interface definition +if(NOT OPENHARMONY) +cocos_source_files( + cocos/platform/interfaces/modules/Device.cpp + cocos/platform/interfaces/modules/Device.h + cocos/platform/interfaces/modules/IAccelerometer.h + cocos/platform/interfaces/modules/IBattery.h + cocos/platform/interfaces/modules/IScreen.h + cocos/platform/interfaces/modules/INetwork.h + cocos/platform/interfaces/modules/ISystem.cpp + cocos/platform/interfaces/modules/ISystem.h + cocos/platform/interfaces/modules/ISystemWindow.h + cocos/platform/interfaces/modules/ISystemWindowManager.h + cocos/platform/interfaces/modules/IVibrator.h + cocos/platform/interfaces/modules/XRCommon.h + cocos/platform/interfaces/modules/IXRInterface.h +) +elseif(OPENHARMONY) + cocos_source_files( + cocos/platform/interfaces/modules/ISystem.h + cocos/platform/interfaces/modules/ISystem.cpp + cocos/platform/interfaces/modules/ISystemWindow.h + cocos/platform/interfaces/modules/Device.cpp + cocos/platform/interfaces/modules/Device.h + cocos/platform/interfaces/modules/IScreen.h + ) +endif() + +cocos_source_files( + cocos/platform/interfaces/modules/canvas/CanvasRenderingContext2D.cpp + cocos/platform/interfaces/modules/canvas/CanvasRenderingContext2D.h + cocos/platform/interfaces/modules/canvas/ICanvasRenderingContext2D.h +) +if(USE_SERVER_MODE) + cocos_source_files( + cocos/platform/empty/modules/CanvasRenderingContext2DDelegate.cpp + cocos/platform/empty/modules/CanvasRenderingContext2DDelegate.h + ) +elseif(WINDOWS) + cocos_source_files( + cocos/platform/win32/modules/CanvasRenderingContext2DDelegate.cpp + cocos/platform/win32/modules/CanvasRenderingContext2DDelegate.h + ) +elseif(OPENHARMONY) + cocos_source_files( + cocos/platform/openharmony/modules/CanvasRenderingContext2DDelegate.cpp + cocos/platform/openharmony/modules/CanvasRenderingContext2DDelegate.h + ) +elseif(ANDROID OR OHOS) + cocos_source_files( + cocos/platform/java/modules/CanvasRenderingContext2DDelegate.cpp + cocos/platform/java/modules/CanvasRenderingContext2DDelegate.h + ) +elseif(MACOSX OR IOS) + cocos_source_files( + cocos/platform/apple/modules/CanvasRenderingContext2DDelegate.mm + cocos/platform/apple/modules/CanvasRenderingContext2DDelegate.h + ) +elseif(LINUX) + cocos_source_files( + cocos/platform/linux/modules/CanvasRenderingContext2DDelegate.cpp + cocos/platform/linux/modules/CanvasRenderingContext2DDelegate.h + ) +endif() + +############ platform : Interface implementation of windows + +if(WINDOWS) + cocos_source_files( + cocos/platform/win32/modules/Accelerometer.cpp + cocos/platform/win32/modules/Accelerometer.h + cocos/platform/win32/modules/Battery.cpp + cocos/platform/win32/modules/Battery.h + cocos/platform/win32/modules/Network.cpp + cocos/platform/win32/modules/Network.h + cocos/platform/win32/modules/Vibrator.cpp + cocos/platform/win32/modules/Vibrator.h + cocos/platform/win32/modules/System.cpp + cocos/platform/win32/modules/System.h + ) + if(USE_SERVER_MODE) + cocos_source_files( + cocos/platform/empty/modules/Screen.cpp + cocos/platform/empty/modules/Screen.h + cocos/platform/empty/modules/SystemWindow.cpp + cocos/platform/empty/modules/SystemWindow.h + cocos/platform/empty/modules/SystemWindowManager.cpp + cocos/platform/empty/modules/SystemWindowManager.h + ) + else() + cocos_source_files( + cocos/platform/win32/modules/Screen.cpp + cocos/platform/win32/modules/Screen.h + cocos/platform/win32/modules/SystemWindow.cpp + cocos/platform/win32/modules/SystemWindow.h + cocos/platform/win32/modules/SystemWindowManager.cpp + cocos/platform/win32/modules/SystemWindowManager.h + ) + endif() +elseif(OPENHARMONY) + cocos_source_files( + cocos/platform/openharmony/modules/System.cpp + cocos/platform/openharmony/modules/System.h + cocos/platform/openharmony/modules/SystemWindow.cpp + cocos/platform/openharmony/modules/SystemWindow.h + cocos/platform/openharmony/modules/Screen.cpp + cocos/platform/openharmony/modules/Screen.h + cocos/platform/openharmony/modules/Accelerometer.cpp + cocos/platform/openharmony/modules/Accelerometer.h + cocos/platform/openharmony/modules/Battery.cpp + cocos/platform/openharmony/modules/Battery.h + cocos/platform/openharmony/modules/System.cpp + cocos/platform/openharmony/modules/SystemWindow.cpp + cocos/platform/openharmony/modules/SystemWindowManager.h + cocos/platform/openharmony/modules/SystemWindowManager.cpp + cocos/platform/empty/modules/Network.cpp + cocos/platform/empty/modules/Network.h + cocos/platform/empty/modules/Vibrator.cpp + cocos/platform/empty/modules/Vibrator.h + ) +elseif(ANDROID OR OHOS) + cocos_source_files( + cocos/platform/java/modules/Accelerometer.cpp + cocos/platform/java/modules/Accelerometer.h + cocos/platform/java/modules/Battery.cpp + cocos/platform/java/modules/Battery.h + cocos/platform/java/modules/CommonScreen.cpp + cocos/platform/java/modules/CommonScreen.h + cocos/platform/java/modules/Network.cpp + cocos/platform/java/modules/Network.h + cocos/platform/java/modules/Vibrator.cpp + cocos/platform/java/modules/Vibrator.h + cocos/platform/java/modules/CommonSystem.cpp + cocos/platform/java/modules/CommonSystem.h + cocos/platform/java/modules/SystemWindow.cpp + cocos/platform/java/modules/SystemWindow.h + cocos/platform/java/modules/SystemWindowManager.cpp + cocos/platform/java/modules/SystemWindowManager.h + ) + + if(USE_XR) + cocos_source_files( + cocos/platform/java/modules/XRInterface.cpp + cocos/platform/java/modules/XRInterface.h + ) + endif() +endif() + +if(ANDROID) + cocos_source_files( + cocos/platform/android/modules/Screen.cpp + cocos/platform/android/modules/Screen.h + cocos/platform/android/modules/System.cpp + cocos/platform/android/modules/System.h + ) +elseif(OPENHARMONY) + # Because there will be a definition of OHOS in harmonyos, it is necessary to define the macro of OPENHARMONY. +elseif(OHOS) + cocos_source_files( + cocos/platform/ohos/modules/Screen.cpp + cocos/platform/ohos/modules/Screen.h + cocos/platform/ohos/modules/System.cpp + cocos/platform/ohos/modules/System.h + ) +elseif(MACOSX) + cocos_source_files( + NO_WERROR cocos/platform/mac/modules/Accelerometer.mm + cocos/platform/mac/modules/Accelerometer.h + cocos/platform/mac/modules/Battery.mm + cocos/platform/mac/modules/Battery.h + cocos/platform/mac/modules/Network.mm + cocos/platform/mac/modules/Network.h + cocos/platform/mac/modules/Vibrator.mm + cocos/platform/mac/modules/Vibrator.h + cocos/platform/mac/modules/System.mm + cocos/platform/mac/modules/System.h + ) + if(USE_SERVER_MODE) + cocos_source_files( + cocos/platform/empty/modules/Screen.cpp + cocos/platform/empty/modules/Screen.h + cocos/platform/empty/modules/SystemWindow.cpp + cocos/platform/empty/modules/SystemWindow.h + cocos/platform/empty/modules/SystemWindowManager.cpp + cocos/platform/empty/modules/SystemWindowManager.h + ) + else() + cocos_source_files( + NO_WERROR NO_UBUILD cocos/platform/mac/modules/Screen.mm + cocos/platform/mac/modules/Screen.h + cocos/platform/mac/modules/SystemWindow.mm + cocos/platform/mac/modules/SystemWindow.h + cocos/platform/mac/modules/SystemWindowManager.mm + cocos/platform/mac/modules/SystemWindowManager.h + ) + endif() +elseif(IOS) + cocos_source_files( + NO_WERROR NO_UBUILD cocos/platform/ios/modules/Accelerometer.mm + cocos/platform/ios/modules/Accelerometer.h + cocos/platform/ios/modules/Battery.mm + cocos/platform/ios/modules/Battery.h + NO_WERROR NO_UBUILD cocos/platform/ios/modules/Screen.mm + cocos/platform/ios/modules/Screen.h + cocos/platform/ios/modules/Network.mm + cocos/platform/ios/modules/Network.h + cocos/platform/ios/modules/Vibrator.mm + cocos/platform/ios/modules/Vibrator.h + cocos/platform/ios/modules/System.mm + cocos/platform/ios/modules/System.h + cocos/platform/ios/modules/SystemWindow.mm + cocos/platform/ios/modules/SystemWindow.h + cocos/platform/ios/modules/SystemWindowManager.mm + cocos/platform/ios/modules/SystemWindowManager.h + ) +elseif(LINUX) + cocos_source_files( + cocos/platform/linux/modules/Accelerometer.cpp + cocos/platform/linux/modules/Accelerometer.h + cocos/platform/linux/modules/Battery.cpp + cocos/platform/linux/modules/Battery.h + cocos/platform/linux/modules/Network.cpp + cocos/platform/linux/modules/Network.h + cocos/platform/linux/modules/Vibrator.cpp + cocos/platform/linux/modules/Vibrator.h + cocos/platform/linux/modules/System.cpp + cocos/platform/linux/modules/System.h + ) + if(USE_SERVER_MODE) + cocos_source_files( + cocos/platform/empty/modules/Screen.cpp + cocos/platform/empty/modules/Screen.h + cocos/platform/empty/modules/SystemWindow.cpp + cocos/platform/empty/modules/SystemWindow.h + cocos/platform/empty/modules/SystemWindowManager.cpp + cocos/platform/empty/modules/SystemWindowManager.h + ) + else() + cocos_source_files( + cocos/platform/linux/modules/Screen.cpp + cocos/platform/linux/modules/Screen.h + cocos/platform/linux/modules/SystemWindow.cpp + cocos/platform/linux/modules/SystemWindow.h + cocos/platform/linux/modules/SystemWindowManager.cpp + cocos/platform/linux/modules/SystemWindowManager.h + ) + endif() +endif() + +############ module log +cocos_source_files(MODULE cclog + cocos/base/Log.cpp + cocos/base/Log.h +) + +cocos_source_files(MODULE cclog + cocos/base/LogRemote.cpp +) + +############ module log +cocos_source_files(MODULE ccunzip + cocos/base/ZipUtils.cpp + cocos/base/ZipUtils.h +) + +##### memory +cocos_source_files( + cocos/base/memory/Memory.h + cocos/base/memory/MemoryHook.cpp + cocos/base/memory/MemoryHook.h + cocos/base/memory/CallStack.cpp + cocos/base/memory/CallStack.h +) + +##### threading +cocos_source_files( + cocos/base/threading/ConditionVariable.h + cocos/base/threading/ConditionVariable.cpp + cocos/base/threading/Event.h + cocos/base/threading/MessageQueue.h + cocos/base/threading/MessageQueue.cpp + cocos/base/threading/Semaphore.h + cocos/base/threading/Semaphore.cpp + cocos/base/threading/ThreadPool.h + cocos/base/threading/ThreadPool.cpp + cocos/base/threading/ThreadSafeCounter.h + cocos/base/threading/ThreadSafeLinearAllocator.h + cocos/base/threading/ThreadSafeLinearAllocator.cpp +) +if(APPLE) +cocos_source_files( + cocos/base/threading/AutoReleasePool.h + cocos/base/threading/AutoReleasePool-apple.mm +) +else() +cocos_source_files( + cocos/base/threading/AutoReleasePool.h + cocos/base/threading/AutoReleasePool.cpp +) +endif() + +##### job system +cocos_source_files( + cocos/base/job-system/JobSystem.h +) + +if(USE_JOB_SYSTEM_TASKFLOW) + cocos_source_files( + cocos/base/job-system/job-system-taskflow/TFJobGraph.h + cocos/base/job-system/job-system-taskflow/TFJobGraph.cpp + cocos/base/job-system/job-system-taskflow/TFJobSystem.h + cocos/base/job-system/job-system-taskflow/TFJobSystem.cpp + ) +elseif(USE_JOB_SYSTEM_TBB) + cocos_source_files( + cocos/base/job-system/job-system-tbb/TBBJobGraph.h + cocos/base/job-system/job-system-tbb/TBBJobGraph.cpp + cocos/base/job-system/job-system-tbb/TBBJobSystem.h + cocos/base/job-system/job-system-tbb/TBBJobSystem.cpp + ) +else() + cocos_source_files( + cocos/base/job-system/job-system-dummy/DummyJobGraph.h + cocos/base/job-system/job-system-dummy/DummyJobGraph.cpp + cocos/base/job-system/job-system-dummy/DummyJobSystem.h + cocos/base/job-system/job-system-dummy/DummyJobSystem.cpp + ) +endif() + +######### module math +cocos_source_files(MODULE ccmath + cocos/math/Utils.h + cocos/math/Utils.cpp + cocos/math/Geometry.cpp + cocos/math/Geometry.h + cocos/math/Color.cpp + cocos/math/Color.h + cocos/math/Math.h + cocos/math/Math.cpp + cocos/math/MathBase.h + cocos/math/Mat3.cpp + cocos/math/Mat3.h + cocos/math/Mat4.cpp + cocos/math/Mat4.h + cocos/math/Mat4.inl + cocos/math/MathUtil.cpp + cocos/math/MathUtil.h + cocos/math/MathUtil.inl + cocos/math/MathUtilNeon.inl + cocos/math/MathUtilNeon64.inl + cocos/math/MathUtilSSE.inl + cocos/math/Quaternion.cpp + cocos/math/Quaternion.h + cocos/math/Quaternion.inl + cocos/math/Vec2.cpp + cocos/math/Vec2.h + cocos/math/Vec2.inl + cocos/math/Vec3.cpp + cocos/math/Vec3.h + cocos/math/Vec3.inl + cocos/math/Vec4.cpp + cocos/math/Vec4.h + cocos/math/Vec4.inl +) + +##### network +cocos_source_files( + cocos/network/Downloader.cpp + cocos/network/Downloader.h + cocos/network/DownloaderImpl.h +) +# The xmlhttprequest module of OpenHarmony is implemented in the ts layer +cocos_source_files( + cocos/network/HttpClient.h + cocos/network/HttpCookie.cpp + cocos/network/HttpCookie.h + cocos/network/HttpRequest.h + cocos/network/HttpResponse.h + cocos/network/Uri.cpp + cocos/network/Uri.h + ) + +if(USE_SOCKET) + cocos_source_files( + cocos/network/WebSocket.h + cocos/network/SocketIO.cpp + cocos/network/SocketIO.h + ) +endif() + +if(USE_WEBSOCKET_SERVER) + cocos_source_files( + cocos/network/WebSocketServer.h + cocos/network/WebSocketServer.cpp + ) +endif() + +if(APPLE) + cocos_source_files( + cocos/network/DownloaderImpl-apple.h + cocos/network/DownloaderImpl-apple.mm + cocos/network/HttpAsynConnection-apple.h + NO_WERROR NO_UBUILD cocos/network/HttpAsynConnection-apple.m + NO_UBUILD cocos/network/HttpClient-apple.mm + ) + if(USE_SOCKET) + cocos_source_files( + cocos/network/WebSocket-apple.mm + ) + set_source_files_properties(${CWD}/cocos/network/WebSocket-apple.mm PROPERTIES + COMPILE_FLAGS -fobjc-arc + ) + endif() +endif() + +if(ANDROID OR (NOT OPENHARMONY AND OHOS)) + cocos_source_files( + cocos/network/Downloader-java.cpp + cocos/network/Downloader-java.h + cocos/network/HttpClient-java.cpp + ) +endif() + +if(NOT APPLE AND USE_SOCKET) + if(ANDROID) + cocos_source_files( + NO_WERROR cocos/network/WebSocket-okhttp.cpp + ) + else() + cocos_source_files( + NO_WERROR cocos/network/WebSocket-libwebsockets.cpp + ) + endif() +endif() + +if(WINDOWS OR LINUX OR QNX OR OPENHARMONY) + cocos_source_files( + NO_WERROR cocos/network/Downloader-curl.cpp + cocos/network/Downloader-curl.h + ) +endif() + +if(WINDOWS OR MACOSX OR LINUX OR QNX OR OPENHARMONY) + cocos_source_files( + NO_WERROR NO_UBUILD cocos/network/HttpClient.cpp + ) +endif() + +set(SWIG_OUTPUT_ROOT ${CMAKE_CURRENT_BINARY_DIR}/generated) +set(SWIG_OUTPUT ${SWIG_OUTPUT_ROOT}/cocos/bindings/auto) +cc_gen_swig_files(${CMAKE_CURRENT_LIST_DIR}/tools/swig-config ${SWIG_OUTPUT}) + + +##### platform +cocos_source_files( + cocos/platform/Image.cpp + cocos/platform/Image.h + cocos/platform/StdC.h +) + +########## module utils +cocos_source_files(MODULE ccutils + cocos/base/Data.cpp + cocos/base/Data.h + cocos/base/Value.cpp + cocos/base/Value.h + cocos/base/UTF8.cpp + cocos/base/UTF8.h + cocos/platform/SAXParser.cpp + cocos/platform/SAXParser.h + cocos/base/Utils.cpp + cocos/base/Utils.h + cocos/base/Timer.cpp + cocos/base/Timer.h +) + +########## module ccfilesystem +cocos_source_files(MODULE ccfilesystem + cocos/platform/FileUtils.cpp + cocos/platform/FileUtils.h +) + +if(WINDOWS) + cocos_source_files(MODULE ccutils + cocos/platform/win32/Utils-win32.cpp + cocos/platform/win32/Utils-win32.h + ) +endif() + +if(WINDOWS) + cocos_source_files( + cocos/platform/win32/compat + cocos/platform/win32/compat/stdint.h + ) + cocos_source_files(MODULE ccfilesystem + cocos/platform/win32/FileUtils-win32.cpp + cocos/platform/win32/FileUtils-win32.h + ) +elseif(ANDROID) + set(CC_JNI_SRC_FILES + ${CWD}/cocos/platform/android/jni/JniCocosSurfaceView.cpp + ${CWD}/cocos/platform/android/jni/JniCocosEntry.cpp + ${CWD}/cocos/platform/java/jni/JniCocosOrientationHelper.cpp + ${CWD}/cocos/platform/java/jni/JniHelper.cpp + ${CWD}/cocos/platform/java/jni/JniHelper.h + ${CWD}/cocos/platform/java/jni/JniImp.cpp + ${CWD}/cocos/platform/java/jni/JniImp.h + ${CWD}/cocos/platform/java/jni/glue/MessagePipe.cpp + ${CWD}/cocos/platform/java/jni/glue/MessagePipe.h + ) + + cocos_source_files(MODULE ccfilesystem + cocos/platform/android/FileUtils-android.cpp + cocos/platform/android/FileUtils-android.h + ) +elseif(OPENHARMONY) + # Because there will be a definition of OHOS in harmonyos, it is necessary to define the macro of OPENHARMONY. + cocos_source_files(MODULE ccfilesystem + cocos/platform/openharmony/FileUtils-OpenHarmony.cpp + cocos/platform/openharmony/FileUtils-OpenHarmony.h + ) +elseif(OHOS) + set(CC_JNI_SRC_FILES + ${CWD}/cocos/platform/ohos/jni/JniCocosAbility.cpp + ${CWD}/cocos/platform/ohos/jni/JniCocosAbility.h + ${CWD}/cocos/platform/java/jni/JniHelper.cpp + ${CWD}/cocos/platform/java/jni/JniHelper.h + ${CWD}/cocos/platform/java/jni/JniImp.cpp + ${CWD}/cocos/platform/java/jni/JniImp.h + ${CWD}/cocos/platform/java/jni/JniCocosTouchHandler.cpp + ${CWD}/cocos/platform/java/jni/JniCocosKeyCodeHandler.cpp + ${CWD}/cocos/platform/java/jni/JniCocosOrientationHelper.cpp + ${CWD}/cocos/platform/ohos/jni/AbilityConsts.h + ${CWD}/cocos/platform/java/jni/glue/JniNativeGlue.cpp + ${CWD}/cocos/platform/java/jni/glue/JniNativeGlue.h + ${CWD}/cocos/platform/java/jni/glue/MessagePipe.cpp + ${CWD}/cocos/platform/java/jni/glue/MessagePipe.h + ) + cocos_source_files(MODULE ccfilesystem + cocos/platform/ohos/FileUtils-ohos.cpp + cocos/platform/ohos/FileUtils-ohos.h + ) +elseif(APPLE) + cocos_source_files( + cocos/platform/apple/Reachability.h + cocos/platform/apple/Reachability.cpp + ) + + cocos_source_files(MODULE ccfilesystem + cocos/platform/apple/FileUtils-apple.h + cocos/platform/apple/FileUtils-apple.mm + ) + if(MACOSX) + cocos_source_files( + cocos/platform/mac/View.h + cocos/platform/mac/View.mm + cocos/platform/mac/KeyCodeHelper.h + cocos/platform/mac/KeyCodeHelper.cpp + ) + elseif(IOS) + cocos_source_files( + cocos/platform/ios/View.h + NO_WERROR NO_UBUILD cocos/platform/ios/View.mm # CAMetalLayer bug for apple + ) + endif() +elseif(LINUX) + cocos_source_files(MODULE ccfilesystem + cocos/platform/linux/FileUtils-linux.cpp + cocos/platform/linux/FileUtils-linux.h + ) +endif() + +##### renderer +cocos_source_files( + cocos/renderer/core/PassUtils.h + cocos/renderer/core/PassUtils.cpp + cocos/renderer/core/ProgramLib.h + cocos/renderer/core/ProgramLib.cpp + cocos/renderer/core/ProgramUtils.h + cocos/renderer/core/ProgramUtils.cpp + cocos/renderer/core/MaterialInstance.h + cocos/renderer/core/MaterialInstance.cpp + cocos/renderer/core/PassInstance.h + cocos/renderer/core/PassInstance.cpp + cocos/renderer/core/TextureBufferPool.h + cocos/renderer/core/TextureBufferPool.cpp + + cocos/renderer/GFXDeviceManager.h + + cocos/renderer/gfx-base/SPIRVUtils.h + cocos/renderer/gfx-base/SPIRVUtils.cpp + cocos/renderer/gfx-base/GFXObject.h + cocos/renderer/gfx-base/GFXObject.cpp + cocos/renderer/gfx-base/GFXBarrier.cpp + cocos/renderer/gfx-base/GFXBarrier.h + cocos/renderer/gfx-base/GFXBuffer.cpp + cocos/renderer/gfx-base/GFXBuffer.h + cocos/renderer/gfx-base/GFXCommandBuffer.cpp + cocos/renderer/gfx-base/GFXCommandBuffer.h + cocos/renderer/gfx-base/GFXDef.cpp + cocos/renderer/gfx-base/GFXDef.h + cocos/renderer/gfx-base/GFXDef-common.h + cocos/renderer/gfx-base/GFXDevice.cpp + cocos/renderer/gfx-base/GFXDevice.h + cocos/renderer/gfx-base/GFXFramebuffer.cpp + cocos/renderer/gfx-base/GFXFramebuffer.h + cocos/renderer/gfx-base/GFXInputAssembler.cpp + cocos/renderer/gfx-base/GFXInputAssembler.h + cocos/renderer/gfx-base/GFXDescriptorSet.cpp + cocos/renderer/gfx-base/GFXDescriptorSet.h + cocos/renderer/gfx-base/GFXDescriptorSetLayout.cpp + cocos/renderer/gfx-base/GFXDescriptorSetLayout.h + cocos/renderer/gfx-base/GFXPipelineLayout.cpp + cocos/renderer/gfx-base/GFXPipelineLayout.h + cocos/renderer/gfx-base/GFXPipelineState.cpp + cocos/renderer/gfx-base/GFXPipelineState.h + cocos/renderer/gfx-base/GFXQueue.cpp + cocos/renderer/gfx-base/GFXQueue.h + cocos/renderer/gfx-base/GFXQueryPool.cpp + cocos/renderer/gfx-base/GFXQueryPool.h + cocos/renderer/gfx-base/GFXRenderPass.cpp + cocos/renderer/gfx-base/GFXRenderPass.h + cocos/renderer/gfx-base/GFXShader.cpp + cocos/renderer/gfx-base/GFXShader.h + cocos/renderer/gfx-base/GFXSwapchain.cpp + cocos/renderer/gfx-base/GFXSwapchain.h + cocos/renderer/gfx-base/GFXTexture.cpp + cocos/renderer/gfx-base/GFXTexture.h + cocos/renderer/gfx-base/GFXDeviceObject.h + cocos/renderer/gfx-base/GFXUtil.cpp + cocos/renderer/gfx-base/GFXUtil.h + cocos/renderer/gfx-base/states/GFXGeneralBarrier.cpp + cocos/renderer/gfx-base/states/GFXGeneralBarrier.h + cocos/renderer/gfx-base/states/GFXSampler.cpp + cocos/renderer/gfx-base/states/GFXSampler.h + cocos/renderer/gfx-base/states/GFXTextureBarrier.cpp + cocos/renderer/gfx-base/states/GFXTextureBarrier.h + cocos/renderer/gfx-base/states/GFXBufferBarrier.h + cocos/renderer/gfx-base/states/GFXBufferBarrier.cpp + + cocos/renderer/gfx-agent/BufferAgent.h + cocos/renderer/gfx-agent/BufferAgent.cpp + cocos/renderer/gfx-agent/CommandBufferAgent.h + cocos/renderer/gfx-agent/CommandBufferAgent.cpp + cocos/renderer/gfx-agent/DescriptorSetAgent.h + cocos/renderer/gfx-agent/DescriptorSetAgent.cpp + cocos/renderer/gfx-agent/DescriptorSetLayoutAgent.h + cocos/renderer/gfx-agent/DescriptorSetLayoutAgent.cpp + cocos/renderer/gfx-agent/DeviceAgent.h + cocos/renderer/gfx-agent/DeviceAgent.cpp + cocos/renderer/gfx-agent/FramebufferAgent.h + cocos/renderer/gfx-agent/FramebufferAgent.cpp + cocos/renderer/gfx-agent/InputAssemblerAgent.h + cocos/renderer/gfx-agent/InputAssemblerAgent.cpp + cocos/renderer/gfx-agent/PipelineLayoutAgent.h + cocos/renderer/gfx-agent/PipelineLayoutAgent.cpp + cocos/renderer/gfx-agent/PipelineStateAgent.h + cocos/renderer/gfx-agent/PipelineStateAgent.cpp + cocos/renderer/gfx-agent/QueueAgent.h + cocos/renderer/gfx-agent/QueueAgent.cpp + cocos/renderer/gfx-agent/QueryPoolAgent.h + cocos/renderer/gfx-agent/QueryPoolAgent.cpp + cocos/renderer/gfx-agent/RenderPassAgent.h + cocos/renderer/gfx-agent/RenderPassAgent.cpp + cocos/renderer/gfx-agent/SwapchainAgent.h + cocos/renderer/gfx-agent/SwapchainAgent.cpp + cocos/renderer/gfx-agent/ShaderAgent.h + cocos/renderer/gfx-agent/ShaderAgent.cpp + cocos/renderer/gfx-agent/TextureAgent.h + cocos/renderer/gfx-agent/TextureAgent.cpp + + cocos/renderer/gfx-validator/BufferValidator.h + cocos/renderer/gfx-validator/BufferValidator.cpp + cocos/renderer/gfx-validator/CommandBufferValidator.h + cocos/renderer/gfx-validator/CommandBufferValidator.cpp + cocos/renderer/gfx-validator/DescriptorSetValidator.h + cocos/renderer/gfx-validator/DescriptorSetValidator.cpp + cocos/renderer/gfx-validator/DescriptorSetLayoutValidator.h + cocos/renderer/gfx-validator/DescriptorSetLayoutValidator.cpp + cocos/renderer/gfx-validator/DeviceValidator.h + cocos/renderer/gfx-validator/DeviceValidator.cpp + cocos/renderer/gfx-validator/FramebufferValidator.h + cocos/renderer/gfx-validator/FramebufferValidator.cpp + cocos/renderer/gfx-validator/InputAssemblerValidator.h + cocos/renderer/gfx-validator/InputAssemblerValidator.cpp + cocos/renderer/gfx-validator/PipelineLayoutValidator.h + cocos/renderer/gfx-validator/PipelineLayoutValidator.cpp + cocos/renderer/gfx-validator/PipelineStateValidator.h + cocos/renderer/gfx-validator/PipelineStateValidator.cpp + cocos/renderer/gfx-validator/QueueValidator.h + cocos/renderer/gfx-validator/QueueValidator.cpp + cocos/renderer/gfx-validator/QueryPoolValidator.h + cocos/renderer/gfx-validator/QueryPoolValidator.cpp + cocos/renderer/gfx-validator/RenderPassValidator.h + cocos/renderer/gfx-validator/RenderPassValidator.cpp + cocos/renderer/gfx-validator/ShaderValidator.h + cocos/renderer/gfx-validator/ShaderValidator.cpp + cocos/renderer/gfx-validator/SwapchainValidator.h + cocos/renderer/gfx-validator/SwapchainValidator.cpp + cocos/renderer/gfx-validator/TextureValidator.h + cocos/renderer/gfx-validator/TextureValidator.cpp + cocos/renderer/gfx-validator/ValidationUtils.h + cocos/renderer/gfx-validator/ValidationUtils.cpp + + cocos/renderer/gfx-empty/EmptyBuffer.h + cocos/renderer/gfx-empty/EmptyBuffer.cpp + cocos/renderer/gfx-empty/EmptyCommandBuffer.h + cocos/renderer/gfx-empty/EmptyCommandBuffer.cpp + cocos/renderer/gfx-empty/EmptyDescriptorSet.h + cocos/renderer/gfx-empty/EmptyDescriptorSet.cpp + cocos/renderer/gfx-empty/EmptyDescriptorSetLayout.h + cocos/renderer/gfx-empty/EmptyDescriptorSetLayout.cpp + cocos/renderer/gfx-empty/EmptyDevice.h + cocos/renderer/gfx-empty/EmptyDevice.cpp + cocos/renderer/gfx-empty/EmptyFramebuffer.h + cocos/renderer/gfx-empty/EmptyFramebuffer.cpp + cocos/renderer/gfx-empty/EmptyInputAssembler.h + cocos/renderer/gfx-empty/EmptyInputAssembler.cpp + cocos/renderer/gfx-empty/EmptyPipelineLayout.h + cocos/renderer/gfx-empty/EmptyPipelineLayout.cpp + cocos/renderer/gfx-empty/EmptyPipelineState.h + cocos/renderer/gfx-empty/EmptyPipelineState.cpp + cocos/renderer/gfx-empty/EmptyQueue.h + cocos/renderer/gfx-empty/EmptyQueue.cpp + cocos/renderer/gfx-empty/EmptyQueryPool.h + cocos/renderer/gfx-empty/EmptyQueryPool.cpp + cocos/renderer/gfx-empty/EmptyRenderPass.h + cocos/renderer/gfx-empty/EmptyRenderPass.cpp + cocos/renderer/gfx-empty/EmptyShader.h + cocos/renderer/gfx-empty/EmptyShader.cpp + cocos/renderer/gfx-empty/EmptySwapchain.h + cocos/renderer/gfx-empty/EmptySwapchain.cpp + cocos/renderer/gfx-empty/EmptyTexture.h + cocos/renderer/gfx-empty/EmptyTexture.cpp + + cocos/renderer/pipeline/ClusterLightCulling.cpp + cocos/renderer/pipeline/ClusterLightCulling.h + cocos/renderer/pipeline/Define.h + cocos/renderer/pipeline/Define.cpp + cocos/renderer/pipeline/GlobalDescriptorSetManager.h + cocos/renderer/pipeline/GlobalDescriptorSetManager.cpp + cocos/renderer/pipeline/InstancedBuffer.cpp + cocos/renderer/pipeline/InstancedBuffer.h + cocos/renderer/pipeline/PipelineStateManager.cpp + cocos/renderer/pipeline/PipelineStateManager.h + cocos/renderer/pipeline/RenderAdditiveLightQueue.cpp + cocos/renderer/pipeline/RenderAdditiveLightQueue.h + cocos/renderer/pipeline/RenderFlow.cpp + cocos/renderer/pipeline/RenderFlow.h + cocos/renderer/pipeline/RenderInstancedQueue.cpp + cocos/renderer/pipeline/RenderInstancedQueue.h + cocos/renderer/pipeline/RenderPipeline.cpp + cocos/renderer/pipeline/RenderPipeline.h + cocos/renderer/pipeline/RenderQueue.cpp + cocos/renderer/pipeline/RenderQueue.h + cocos/renderer/pipeline/RenderStage.cpp + cocos/renderer/pipeline/RenderStage.h + cocos/renderer/pipeline/PlanarShadowQueue.cpp + cocos/renderer/pipeline/PlanarShadowQueue.h + cocos/renderer/pipeline/ShadowMapBatchedQueue.cpp + cocos/renderer/pipeline/ShadowMapBatchedQueue.h + cocos/renderer/pipeline/PipelineUBO.cpp + cocos/renderer/pipeline/PipelineUBO.h + cocos/renderer/pipeline/PipelineSceneData.cpp + cocos/renderer/pipeline/PipelineSceneData.h + cocos/renderer/pipeline/forward/ForwardFlow.cpp + cocos/renderer/pipeline/forward/ForwardFlow.h + cocos/renderer/pipeline/forward/ForwardPipeline.cpp + cocos/renderer/pipeline/forward/ForwardPipeline.h + cocos/renderer/pipeline/forward/ForwardStage.cpp + cocos/renderer/pipeline/forward/ForwardStage.h + cocos/renderer/pipeline/SceneCulling.cpp + cocos/renderer/pipeline/SceneCulling.h + cocos/renderer/pipeline/deferred/DeferredPipeline.cpp + cocos/renderer/pipeline/deferred/DeferredPipeline.h + cocos/renderer/pipeline/deferred/MainFlow.cpp + cocos/renderer/pipeline/deferred/MainFlow.h + cocos/renderer/pipeline/deferred/DeferredPipelineSceneData.cpp + cocos/renderer/pipeline/deferred/DeferredPipelineSceneData.h + cocos/renderer/pipeline/deferred/GbufferStage.cpp + cocos/renderer/pipeline/deferred/GbufferStage.h + cocos/renderer/pipeline/deferred/LightingStage.cpp + cocos/renderer/pipeline/deferred/LightingStage.h + cocos/renderer/pipeline/deferred/ReflectionComp.cpp + cocos/renderer/pipeline/deferred/ReflectionComp.h + cocos/renderer/pipeline/shadow/ShadowFlow.cpp + cocos/renderer/pipeline/shadow/ShadowFlow.h + cocos/renderer/pipeline/shadow/ShadowStage.cpp + cocos/renderer/pipeline/shadow/ShadowStage.h + cocos/renderer/pipeline/shadow/CSMLayers.cpp + cocos/renderer/pipeline/shadow/CSMLayers.h + cocos/renderer/pipeline/Enum.h + cocos/renderer/pipeline/deferred/BloomStage.cpp + cocos/renderer/pipeline/deferred/BloomStage.h + cocos/renderer/pipeline/deferred/PostProcessStage.cpp + cocos/renderer/pipeline/deferred/PostProcessStage.h + cocos/renderer/pipeline/UIPhase.cpp + cocos/renderer/pipeline/UIPhase.h + cocos/renderer/pipeline/DebugView.cpp + cocos/renderer/pipeline/DebugView.h + cocos/renderer/pipeline/helper/Utils.h + cocos/renderer/pipeline/helper/Utils.cpp + cocos/renderer/pipeline/custom/test/test.h + cocos/renderer/pipeline/reflection-probe/ReflectionProbeFlow.cpp + cocos/renderer/pipeline/reflection-probe/ReflectionProbeFlow.h + cocos/renderer/pipeline/reflection-probe/ReflectionProbeStage.cpp + cocos/renderer/pipeline/reflection-probe/ReflectionProbeStage.h + cocos/renderer/pipeline/ReflectionProbeBatchedQueue.cpp + cocos/renderer/pipeline/ReflectionProbeBatchedQueue.h + cocos/renderer/pipeline/custom/ArchiveFwd.h + cocos/renderer/pipeline/custom/ArchiveTypes.cpp + cocos/renderer/pipeline/custom/ArchiveTypes.h + cocos/renderer/pipeline/custom/BinaryArchive.h + cocos/renderer/pipeline/custom/CustomFwd.h + cocos/renderer/pipeline/custom/CustomTypes.cpp + cocos/renderer/pipeline/custom/CustomTypes.h + cocos/renderer/pipeline/custom/FGDispatcherGraphs.h + cocos/renderer/pipeline/custom/FGDispatcherTypes.cpp + cocos/renderer/pipeline/custom/FGDispatcherTypes.h + cocos/renderer/pipeline/custom/FrameGraphDispatcher.cpp + cocos/renderer/pipeline/custom/LayoutGraphFwd.h + cocos/renderer/pipeline/custom/LayoutGraphGraphs.h + cocos/renderer/pipeline/custom/LayoutGraphNames.h + cocos/renderer/pipeline/custom/LayoutGraphSerialization.h + cocos/renderer/pipeline/custom/LayoutGraphTypes.cpp + cocos/renderer/pipeline/custom/LayoutGraphTypes.h + cocos/renderer/pipeline/custom/LayoutGraphUtils.cpp + cocos/renderer/pipeline/custom/LayoutGraphUtils.h + cocos/renderer/pipeline/custom/NativeBuiltinUtils.cpp + cocos/renderer/pipeline/custom/NativeBuiltinUtils.h + cocos/renderer/pipeline/custom/NativeExecutor.cpp + cocos/renderer/pipeline/custom/NativeFactory.cpp + cocos/renderer/pipeline/custom/NativeFwd.h + cocos/renderer/pipeline/custom/NativePipeline.cpp + cocos/renderer/pipeline/custom/NativePipelineFwd.h + cocos/renderer/pipeline/custom/NativePipelineGraphs.h + cocos/renderer/pipeline/custom/NativePipelineTypes.cpp + cocos/renderer/pipeline/custom/NativePipelineTypes.h + cocos/renderer/pipeline/custom/NativePools.cpp + cocos/renderer/pipeline/custom/NativeProgramLibrary.cpp + cocos/renderer/pipeline/custom/NativeRenderGraph.cpp + cocos/renderer/pipeline/custom/NativeRenderGraphUtils.cpp + cocos/renderer/pipeline/custom/NativeRenderGraphUtils.h + cocos/renderer/pipeline/custom/NativeRenderQueue.cpp + cocos/renderer/pipeline/custom/NativeRenderingModule.cpp + cocos/renderer/pipeline/custom/NativeResourceGraph.cpp + cocos/renderer/pipeline/custom/NativeSceneCulling.cpp + cocos/renderer/pipeline/custom/NativeSetter.cpp + cocos/renderer/pipeline/custom/NativeTypes.cpp + cocos/renderer/pipeline/custom/NativeTypes.h + cocos/renderer/pipeline/custom/NativeUtils.cpp + cocos/renderer/pipeline/custom/NativeUtils.h + cocos/renderer/pipeline/custom/PrivateFwd.h + cocos/renderer/pipeline/custom/PrivateTypes.cpp + cocos/renderer/pipeline/custom/PrivateTypes.h + cocos/renderer/pipeline/custom/RenderCommonFwd.h + cocos/renderer/pipeline/custom/RenderCommonJsb.cpp + cocos/renderer/pipeline/custom/RenderCommonJsb.h + cocos/renderer/pipeline/custom/RenderCommonNames.h + cocos/renderer/pipeline/custom/RenderCommonSerialization.h + cocos/renderer/pipeline/custom/RenderCommonTypes.cpp + cocos/renderer/pipeline/custom/RenderCommonTypes.h + cocos/renderer/pipeline/custom/RenderGraphFwd.h + cocos/renderer/pipeline/custom/RenderGraphGraphs.h + cocos/renderer/pipeline/custom/RenderGraphTypes.cpp + cocos/renderer/pipeline/custom/RenderGraphTypes.h + cocos/renderer/pipeline/custom/RenderInterfaceFwd.h + cocos/renderer/pipeline/custom/RenderInterfaceTypes.cpp + cocos/renderer/pipeline/custom/RenderInterfaceTypes.h + cocos/renderer/pipeline/custom/RenderingModule.h + cocos/renderer/pipeline/custom/details/DebugUtils.h + cocos/renderer/pipeline/custom/details/GraphImpl.h + cocos/renderer/pipeline/custom/details/GraphTypes.h + cocos/renderer/pipeline/custom/details/GraphView.h + cocos/renderer/pipeline/custom/details/GslUtils.h + cocos/renderer/pipeline/custom/details/JsbConversion.h + cocos/renderer/pipeline/custom/details/Map.h + cocos/renderer/pipeline/custom/details/Overload.h + cocos/renderer/pipeline/custom/details/PathUtils.h + cocos/renderer/pipeline/custom/details/Pmr.h + cocos/renderer/pipeline/custom/details/Range.h + cocos/renderer/pipeline/custom/details/SerializationUtils.h + cocos/renderer/pipeline/custom/details/Set.h + cocos/renderer/pipeline/custom/details/Utility.h +) + +if (USE_GEOMETRY_RENDERER) + cocos_source_files( + cocos/renderer/pipeline/GeometryRenderer.h + cocos/renderer/pipeline/GeometryRenderer.cpp + ) +endif() + +##### terrain +# +# cocos_source_files( +# cocos/terrain/HeightField.cpp +# cocos/terrain/HeightField.h +# cocos/terrain/Terrain.cpp +# cocos/terrain/Terrain.h +# cocos/terrain/TerrainAsset.cpp +# cocos/terrain/TerrainAsset.h +# cocos/terrain/TerrainLod.cpp +# cocos/terrain/TerrainLod.h +# ) + + +##### scene +cocos_source_files( + cocos/scene/Ambient.h + cocos/scene/Ambient.cpp + cocos/scene/Camera.h + cocos/scene/Camera.cpp + cocos/scene/Define.h + cocos/scene/DirectionalLight.h + cocos/scene/DirectionalLight.cpp + cocos/scene/DrawBatch2D.h + cocos/scene/DrawBatch2D.cpp + cocos/scene/Fog.h + cocos/scene/Fog.cpp + cocos/scene/Light.h + cocos/scene/Light.cpp + cocos/scene/LODGroup.h + cocos/scene/LODGroup.cpp + cocos/scene/Model.h + cocos/scene/Model.cpp + cocos/scene/Pass.h + cocos/scene/Pass.cpp + cocos/scene/RenderScene.h + cocos/scene/RenderScene.cpp + cocos/scene/RenderWindow.cpp + cocos/scene/RenderWindow.h + cocos/scene/Skybox.h + cocos/scene/Skybox.cpp + cocos/scene/SphereLight.h + cocos/scene/SphereLight.cpp + cocos/scene/SpotLight.h + cocos/scene/SpotLight.cpp + cocos/scene/PointLight.h + cocos/scene/PointLight.cpp + cocos/scene/RangedDirectionalLight.h + cocos/scene/RangedDirectionalLight.cpp + cocos/scene/SubModel.h + cocos/scene/SubModel.cpp + cocos/scene/Octree.h + cocos/scene/Octree.cpp + cocos/scene/Shadow.h + cocos/scene/Shadow.cpp + cocos/scene/ReflectionProbe.h + cocos/scene/ReflectionProbe.cpp + cocos/scene/ReflectionProbeManager.cpp + cocos/scene/ReflectionProbeManager.h + cocos/scene/Skin.h + cocos/scene/Skin.cpp + cocos/scene/PostSettings.h + cocos/scene/PostSettings.cpp +) + +##### primitive +cocos_source_files( + cocos/primitive/Box.cpp + cocos/primitive/Box.h + cocos/primitive/Capsule.cpp + cocos/primitive/Capsule.h + cocos/primitive/Circle.cpp + cocos/primitive/Circle.h + cocos/primitive/Cone.cpp + cocos/primitive/Cone.h + cocos/primitive/Cylinder.cpp + cocos/primitive/Cylinder.h + cocos/primitive/Plane.cpp + cocos/primitive/Plane.h + cocos/primitive/Primitive.cpp + cocos/primitive/Primitive.h + cocos/primitive/PrimitiveDefine.h + cocos/primitive/PrimitiveUtils.cpp + cocos/primitive/PrimitiveUtils.h + cocos/primitive/Quad.cpp + cocos/primitive/Quad.h + cocos/primitive/Sphere.cpp + cocos/primitive/Sphere.h + cocos/primitive/Torus.cpp + cocos/primitive/Torus.h + cocos/primitive/Transform.cpp + cocos/primitive/Transform.h +) + +##### profiler +if (USE_DEBUG_RENDERER) +cocos_source_files( + cocos/profiler/DebugRenderer.h + cocos/profiler/DebugRenderer.cpp +) +endif() + +cocos_source_files( + cocos/profiler/Profiler.h + cocos/profiler/Profiler.cpp + cocos/profiler/GameStats.h +) + +##### components +cocos_source_files( + cocos/3d/models/BakedSkinningModel.h + cocos/3d/models/BakedSkinningModel.cpp + cocos/3d/models/MorphModel.h + cocos/3d/models/MorphModel.cpp + cocos/3d/models/SkinningModel.h + cocos/3d/models/SkinningModel.cpp +) + +##### animations +cocos_source_files( + cocos/core/animation/SkeletalAnimationUtils.h + cocos/core/animation/SkeletalAnimationUtils.cpp +) + +##### lights +# cocos_source_files( +# cocos/3d/lights/LightComponent.h +# cocos/3d/lights/LightComponent.cpp +# cocos/3d/lights/DirectionalLightComponent.h +# cocos/3d/lights/DirectionalLightComponent.cpp +# cocos/3d/lights/SpotLightComponent.h +# cocos/3d/lights/SpotLightComponent.cpp +# cocos/3d/lights/SphereLightComponent.h +# cocos/3d/lights/SphereLightComponent.cpp +# ) + +if(CC_USE_GLES2 OR CC_USE_GLES3) + cocos_source_files( + cocos/renderer/gfx-gles-common/GLESCommandPool.h + cocos/renderer/gfx-gles-common/eglw.cpp + cocos/renderer/gfx-gles-common/gles2w.cpp + ) + if(CC_USE_GLES3) + cocos_source_files( + cocos/renderer/gfx-gles-common/gles3w.cpp + ) + endif() +endif() + +if(CC_USE_GLES2) + cocos_source_files( + cocos/renderer/gfx-gles2/GLES2Buffer.cpp + cocos/renderer/gfx-gles2/GLES2Buffer.h + cocos/renderer/gfx-gles2/GLES2CommandBuffer.cpp + cocos/renderer/gfx-gles2/GLES2CommandBuffer.h + cocos/renderer/gfx-gles2/GLES2Commands.cpp + cocos/renderer/gfx-gles2/GLES2Commands.h + cocos/renderer/gfx-gles2/GLES2Device.cpp + cocos/renderer/gfx-gles2/GLES2Device.h + cocos/renderer/gfx-gles2/GLES2Framebuffer.cpp + cocos/renderer/gfx-gles2/GLES2Framebuffer.h + cocos/renderer/gfx-gles2/GLES2GPUContext.cpp + cocos/renderer/gfx-gles2/GLES2GPUObjects.h + cocos/renderer/gfx-gles2/GLES2InputAssembler.cpp + cocos/renderer/gfx-gles2/GLES2InputAssembler.h + cocos/renderer/gfx-gles2/GLES2DescriptorSet.cpp + cocos/renderer/gfx-gles2/GLES2DescriptorSet.h + cocos/renderer/gfx-gles2/GLES2DescriptorSetLayout.cpp + cocos/renderer/gfx-gles2/GLES2DescriptorSetLayout.h + cocos/renderer/gfx-gles2/GLES2PipelineLayout.cpp + cocos/renderer/gfx-gles2/GLES2PipelineLayout.h + cocos/renderer/gfx-gles2/GLES2PipelineState.cpp + cocos/renderer/gfx-gles2/GLES2PipelineState.h + cocos/renderer/gfx-gles2/GLES2PrimaryCommandBuffer.cpp + cocos/renderer/gfx-gles2/GLES2PrimaryCommandBuffer.h + cocos/renderer/gfx-gles2/GLES2Queue.cpp + cocos/renderer/gfx-gles2/GLES2Queue.h + cocos/renderer/gfx-gles2/GLES2QueryPool.cpp + cocos/renderer/gfx-gles2/GLES2QueryPool.h + cocos/renderer/gfx-gles2/GLES2RenderPass.cpp + cocos/renderer/gfx-gles2/GLES2RenderPass.h + cocos/renderer/gfx-gles2/GLES2Shader.cpp + cocos/renderer/gfx-gles2/GLES2Shader.h + cocos/renderer/gfx-gles2/GLES2Swapchain.cpp + cocos/renderer/gfx-gles2/GLES2Swapchain.h + cocos/renderer/gfx-gles2/GLES2Std.h + cocos/renderer/gfx-gles2/GLES2Texture.cpp + cocos/renderer/gfx-gles2/GLES2Texture.h + cocos/renderer/gfx-gles2/GLES2Wrangler.cpp + cocos/renderer/gfx-gles2/GLES2Wrangler.h + cocos/renderer/gfx-gles2/states/GLES2Sampler.cpp + cocos/renderer/gfx-gles2/states/GLES2Sampler.h + ) +endif() + +if(CC_USE_GLES3) + cocos_source_files( + cocos/renderer/gfx-gles3/GLES3Buffer.cpp + cocos/renderer/gfx-gles3/GLES3Buffer.h + cocos/renderer/gfx-gles3/GLES3CommandBuffer.cpp + cocos/renderer/gfx-gles3/GLES3CommandBuffer.h + cocos/renderer/gfx-gles3/GLES3Commands.cpp + cocos/renderer/gfx-gles3/GLES3Commands.h + cocos/renderer/gfx-gles3/GLES3Device.cpp + cocos/renderer/gfx-gles3/GLES3Device.h + cocos/renderer/gfx-gles3/GLES3Framebuffer.cpp + cocos/renderer/gfx-gles3/GLES3Framebuffer.h + cocos/renderer/gfx-gles3/GLES3GPUContext.cpp + cocos/renderer/gfx-gles3/GLES3GPUObjects.h + cocos/renderer/gfx-gles3/GLES3InputAssembler.cpp + cocos/renderer/gfx-gles3/GLES3InputAssembler.h + cocos/renderer/gfx-gles3/GLES3DescriptorSet.cpp + cocos/renderer/gfx-gles3/GLES3DescriptorSet.h + cocos/renderer/gfx-gles3/GLES3DescriptorSetLayout.cpp + cocos/renderer/gfx-gles3/GLES3DescriptorSetLayout.h + cocos/renderer/gfx-gles3/GLES3PipelineLayout.cpp + cocos/renderer/gfx-gles3/GLES3PipelineLayout.h + cocos/renderer/gfx-gles3/GLES3PipelineState.cpp + cocos/renderer/gfx-gles3/GLES3PipelineState.h + cocos/renderer/gfx-gles3/GLES3PrimaryCommandBuffer.cpp + cocos/renderer/gfx-gles3/GLES3PrimaryCommandBuffer.h + cocos/renderer/gfx-gles3/GLES3Queue.cpp + cocos/renderer/gfx-gles3/GLES3Queue.h + cocos/renderer/gfx-gles3/GLES3QueryPool.cpp + cocos/renderer/gfx-gles3/GLES3QueryPool.h + cocos/renderer/gfx-gles3/GLES3RenderPass.cpp + cocos/renderer/gfx-gles3/GLES3RenderPass.h + cocos/renderer/gfx-gles3/GLES3Shader.cpp + cocos/renderer/gfx-gles3/GLES3Shader.h + cocos/renderer/gfx-gles3/GLES3Swapchain.cpp + cocos/renderer/gfx-gles3/GLES3Swapchain.h + cocos/renderer/gfx-gles3/GLES3Std.h + cocos/renderer/gfx-gles3/GLES3Texture.cpp + cocos/renderer/gfx-gles3/GLES3Texture.h + cocos/renderer/gfx-gles3/GLES3Wrangler.cpp + cocos/renderer/gfx-gles3/GLES3Wrangler.h + cocos/renderer/gfx-gles3/GLES3PipelineCache.cpp + cocos/renderer/gfx-gles3/GLES3PipelineCache.h + cocos/renderer/gfx-gles3/states/GLES3GeneralBarrier.cpp + cocos/renderer/gfx-gles3/states/GLES3GeneralBarrier.h + cocos/renderer/gfx-gles3/states/GLES3Sampler.cpp + cocos/renderer/gfx-gles3/states/GLES3Sampler.h + ) +endif() + +if(CC_USE_METAL) + cocos_source_files( + cocos/renderer/gfx-metal/MTLBuffer.h + cocos/renderer/gfx-metal/MTLBuffer.mm + cocos/renderer/gfx-metal/MTLCommandBuffer.h + cocos/renderer/gfx-metal/MTLCommandBuffer.mm + cocos/renderer/gfx-metal/MTLDevice.h + NO_WERROR NO_UBUILD cocos/renderer/gfx-metal/MTLDevice.mm # CAMetalLayer bug for apple + cocos/renderer/gfx-metal/MTLFramebuffer.mm + cocos/renderer/gfx-metal/MTLFramebuffer.h + cocos/renderer/gfx-metal/MTLGPUObjects.h + cocos/renderer/gfx-metal/MTLInputAssembler.h + cocos/renderer/gfx-metal/MTLInputAssembler.mm + cocos/renderer/gfx-metal/MTLDescriptorSetLayout.h + cocos/renderer/gfx-metal/MTLDescriptorSetLayout.mm + cocos/renderer/gfx-metal/MTLPipelineLayout.h + cocos/renderer/gfx-metal/MTLPipelineLayout.mm + cocos/renderer/gfx-metal/MTLPipelineState.h + cocos/renderer/gfx-metal/MTLPipelineState.mm + cocos/renderer/gfx-metal/MTLDescriptorSet.h + cocos/renderer/gfx-metal/MTLDescriptorSet.mm + cocos/renderer/gfx-metal/MTLQueue.h + cocos/renderer/gfx-metal/MTLQueue.mm + cocos/renderer/gfx-metal/MTLQueryPool.h + cocos/renderer/gfx-metal/MTLQueryPool.mm + cocos/renderer/gfx-metal/MTLRenderPass.h + cocos/renderer/gfx-metal/MTLRenderPass.mm + cocos/renderer/gfx-metal/MTLSampler.h + cocos/renderer/gfx-metal/MTLSampler.mm + cocos/renderer/gfx-metal/MTLShader.h + cocos/renderer/gfx-metal/MTLShader.mm + cocos/renderer/gfx-metal/MTLTexture.h + cocos/renderer/gfx-metal/MTLTexture.mm + cocos/renderer/gfx-metal/MTLUtils.h + cocos/renderer/gfx-metal/MTLUtils.mm + cocos/renderer/gfx-metal/MTLRenderCommandEncoder.h + cocos/renderer/gfx-metal/MTLComputeCommandEncoder.h + cocos/renderer/gfx-metal/MTLCommandEncoder.h + cocos/renderer/gfx-metal/MTLConfig.h + cocos/renderer/gfx-metal/MTLSemaphore.h + cocos/renderer/gfx-metal/MTLSwapchain.h + cocos/renderer/gfx-metal/MTLSwapchain.mm + ) +endif() + +if(CC_USE_VULKAN) + + cocos_source_files( + cocos/renderer/gfx-vulkan/vk_mem_alloc.h + cocos/renderer/gfx-vulkan/VKBuffer.cpp + cocos/renderer/gfx-vulkan/VKBuffer.h + cocos/renderer/gfx-vulkan/VKCommandBuffer.cpp + cocos/renderer/gfx-vulkan/VKCommandBuffer.h + cocos/renderer/gfx-vulkan/VKCommands.cpp + cocos/renderer/gfx-vulkan/VKCommands.h + cocos/renderer/gfx-vulkan/VKDevice.cpp + cocos/renderer/gfx-vulkan/VKDevice.h + cocos/renderer/gfx-vulkan/VKFramebuffer.cpp + cocos/renderer/gfx-vulkan/VKFramebuffer.h + cocos/renderer/gfx-vulkan/VKGPUContext.cpp + cocos/renderer/gfx-vulkan/VKGPUObjects.h + cocos/renderer/gfx-vulkan/VKInputAssembler.cpp + cocos/renderer/gfx-vulkan/VKInputAssembler.h + cocos/renderer/gfx-vulkan/VKDescriptorSet.cpp + cocos/renderer/gfx-vulkan/VKDescriptorSet.h + cocos/renderer/gfx-vulkan/VKDescriptorSetLayout.cpp + cocos/renderer/gfx-vulkan/VKDescriptorSetLayout.h + cocos/renderer/gfx-vulkan/VKPipelineLayout.cpp + cocos/renderer/gfx-vulkan/VKPipelineLayout.h + cocos/renderer/gfx-vulkan/VKPipelineState.cpp + cocos/renderer/gfx-vulkan/VKPipelineState.h + cocos/renderer/gfx-vulkan/VKQueue.cpp + cocos/renderer/gfx-vulkan/VKQueue.h + cocos/renderer/gfx-vulkan/VKQueryPool.cpp + cocos/renderer/gfx-vulkan/VKQueryPool.h + cocos/renderer/gfx-vulkan/VKRenderPass.cpp + cocos/renderer/gfx-vulkan/VKRenderPass.h + cocos/renderer/gfx-vulkan/VKShader.cpp + cocos/renderer/gfx-vulkan/VKShader.h + cocos/renderer/gfx-vulkan/VKStd.cpp + cocos/renderer/gfx-vulkan/VKStd.h + cocos/renderer/gfx-vulkan/VKSwapchain.cpp + cocos/renderer/gfx-vulkan/VKSwapchain.h + cocos/renderer/gfx-vulkan/VKTexture.cpp + cocos/renderer/gfx-vulkan/VKTexture.h + cocos/renderer/gfx-vulkan/VKUtils.cpp + cocos/renderer/gfx-vulkan/VKUtils.h + cocos/renderer/gfx-vulkan/volk.c + cocos/renderer/gfx-vulkan/volk.h + cocos/renderer/gfx-vulkan/VKGPURecycleBin.cpp + cocos/renderer/gfx-vulkan/VKPipelineCache.cpp + cocos/renderer/gfx-vulkan/VKPipelineCache.h + cocos/renderer/gfx-vulkan/states/VKGeneralBarrier.cpp + cocos/renderer/gfx-vulkan/states/VKGeneralBarrier.h + cocos/renderer/gfx-vulkan/states/VKSampler.cpp + cocos/renderer/gfx-vulkan/states/VKSampler.h + cocos/renderer/gfx-vulkan/states/VKTextureBarrier.cpp + cocos/renderer/gfx-vulkan/states/VKTextureBarrier.h + cocos/renderer/gfx-vulkan/states/VKBufferBarrier.cpp + cocos/renderer/gfx-vulkan/states/VKBufferBarrier.h + ) + + if(ANDROID) + set_source_files_properties( + ${CWD}/cocos/renderer/gfx-vulkan/VKDevice.cpp + PROPERTIES + # supress warning on Android + COMPILE_FLAGS -Wno-nullability-completeness + ) + endif() +endif() + +if(NX) + include(${CMAKE_CURRENT_LIST_DIR}/platform-nx/source.cmake) +endif() + +cocos_source_files( + cocos/renderer/frame-graph/Blackboard.h + cocos/renderer/frame-graph/CallbackPass.h + cocos/renderer/frame-graph/DevicePass.cpp + cocos/renderer/frame-graph/DevicePass.h + cocos/renderer/frame-graph/DevicePassResourceTable.cpp + cocos/renderer/frame-graph/DevicePassResourceTable.h + cocos/renderer/frame-graph/FrameGraph.cpp + cocos/renderer/frame-graph/FrameGraph.h + cocos/renderer/frame-graph/Handle.h + cocos/renderer/frame-graph/PassInsertPointManager.cpp + cocos/renderer/frame-graph/PassInsertPointManager.h + cocos/renderer/frame-graph/PassNode.cpp + cocos/renderer/frame-graph/PassNode.h + cocos/renderer/frame-graph/PassNodeBuilder.cpp + cocos/renderer/frame-graph/PassNodeBuilder.h + cocos/renderer/frame-graph/RenderTargetAttachment.h + cocos/renderer/frame-graph/Resource.h + cocos/renderer/frame-graph/ResourceAllocator.h + cocos/renderer/frame-graph/ResourceEntry.h + cocos/renderer/frame-graph/ResourceNode.h + cocos/renderer/frame-graph/VirtualResource.cpp + cocos/renderer/frame-graph/VirtualResource.h + cocos/renderer/frame-graph/ImmutableState.h + cocos/renderer/frame-graph/ImmutableState.cpp +) + +##### physics + +if(USE_PHYSICS_PHYSX) + cocos_source_files( + cocos/physics/PhysicsSDK.h + cocos/physics/PhysicsSelector.h + cocos/physics/sdk/World.h + cocos/physics/sdk/World.cpp + cocos/physics/sdk/Shape.h + cocos/physics/sdk/Shape.cpp + cocos/physics/sdk/RigidBody.h + cocos/physics/sdk/RigidBody.cpp + cocos/physics/sdk/Joint.h + cocos/physics/sdk/Joint.cpp + cocos/physics/sdk/CharacterController.h + cocos/physics/sdk/CharacterController.cpp + cocos/physics/spec/IBody.h + cocos/physics/spec/IJoint.h + cocos/physics/spec/ILifecycle.h + cocos/physics/spec/IShape.h + cocos/physics/spec/IWorld.h + cocos/physics/spec/ICharacterController.h + cocos/physics/physx/PhysX.h + cocos/physics/physx/PhysXInc.h + cocos/physics/physx/PhysXUtils.h + cocos/physics/physx/PhysXUtils.cpp + cocos/physics/physx/PhysXWorld.h + cocos/physics/physx/PhysXWorld.cpp + cocos/physics/physx/PhysXFilterShader.h + cocos/physics/physx/PhysXFilterShader.cpp + cocos/physics/physx/PhysXEventManager.h + cocos/physics/physx/PhysXEventManager.cpp + cocos/physics/physx/PhysXSharedBody.h + cocos/physics/physx/PhysXSharedBody.cpp + cocos/physics/physx/PhysXRigidBody.h + cocos/physics/physx/PhysXRigidBody.cpp + cocos/physics/physx/shapes/PhysXShape.h + cocos/physics/physx/shapes/PhysXShape.cpp + cocos/physics/physx/shapes/PhysXSphere.h + cocos/physics/physx/shapes/PhysXSphere.cpp + cocos/physics/physx/shapes/PhysXBox.h + cocos/physics/physx/shapes/PhysXBox.cpp + cocos/physics/physx/shapes/PhysXPlane.h + cocos/physics/physx/shapes/PhysXPlane.cpp + cocos/physics/physx/shapes/PhysXCapsule.h + cocos/physics/physx/shapes/PhysXCapsule.cpp + cocos/physics/physx/shapes/PhysXCone.h + cocos/physics/physx/shapes/PhysXCone.cpp + cocos/physics/physx/shapes/PhysXCylinder.h + cocos/physics/physx/shapes/PhysXCylinder.cpp + cocos/physics/physx/shapes/PhysXTerrain.h + cocos/physics/physx/shapes/PhysXTerrain.cpp + cocos/physics/physx/shapes/PhysXTrimesh.h + cocos/physics/physx/shapes/PhysXTrimesh.cpp + cocos/physics/physx/joints/PhysXJoint.h + cocos/physics/physx/joints/PhysXJoint.cpp + cocos/physics/physx/joints/PhysXRevolute.h + cocos/physics/physx/joints/PhysXRevolute.cpp + cocos/physics/physx/joints/PhysXFixedJoint.h + cocos/physics/physx/joints/PhysXFixedJoint.cpp + cocos/physics/physx/joints/PhysXSpherical.h + cocos/physics/physx/joints/PhysXSpherical.cpp + cocos/physics/physx/joints/PhysXGenericJoint.h + cocos/physics/physx/joints/PhysXGenericJoint.cpp + cocos/physics/physx/character-controllers/PhysXCharacterController.h + cocos/physics/physx/character-controllers/PhysXCharacterController.cpp + cocos/physics/physx/character-controllers/PhysXCapsuleCharacterController.h + cocos/physics/physx/character-controllers/PhysXCapsuleCharacterController.cpp + cocos/physics/physx/character-controllers/PhysXBoxCharacterController.h + cocos/physics/physx/character-controllers/PhysXBoxCharacterController.cpp + ) + cocos_source_files( + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_physics_auto.cpp + ${SWIG_OUTPUT}/jsb_physics_auto.h + ) +endif() + +##### 2d +cocos_source_files( + cocos/2d/renderer/Batcher2d.h + cocos/2d/renderer/Batcher2d.cpp + cocos/2d/renderer/UIModelProxy.h + cocos/2d/renderer/UIModelProxy.cpp + cocos/2d/renderer/RenderDrawInfo.h + cocos/2d/renderer/RenderDrawInfo.cpp + cocos/2d/renderer/UIMeshBuffer.h + cocos/2d/renderer/UIMeshBuffer.cpp + cocos/2d/renderer/RenderEntity.h + cocos/2d/renderer/RenderEntity.cpp + cocos/2d/renderer/StencilManager.h + cocos/2d/renderer/StencilManager.cpp +) + +##### 3d +cocos_source_files( + cocos/3d/assets/Types.h + cocos/3d/assets/Mesh.h + cocos/3d/assets/Mesh.cpp + cocos/3d/assets/Morph.h + cocos/3d/assets/MorphRendering.h + cocos/3d/assets/MorphRendering.cpp + cocos/3d/assets/Skeleton.h + cocos/3d/assets/Skeleton.cpp + + cocos/3d/misc/CreateMesh.h + cocos/3d/misc/CreateMesh.cpp + cocos/3d/misc/BufferBlob.h + cocos/3d/misc/BufferBlob.cpp + cocos/3d/misc/Buffer.h + cocos/3d/misc/Buffer.cpp + + cocos/3d/skeletal-animation/SkeletalAnimationUtils.h + cocos/3d/skeletal-animation/SkeletalAnimationUtils.cpp +) + +##### core + +cocos_source_files( + cocos/core/Any.h + cocos/core/ArrayBuffer.h + + cocos/core/DataView.h + cocos/core/DataView.cpp + cocos/core/Root.cpp + cocos/core/Root.h + + cocos/core/System.h + + cocos/core/Types.h + cocos/core/TypedArray.h + cocos/core/TypedArray.cpp + + # memop + cocos/core/memop/CachedArray.h + cocos/core/memop/Pool.h + cocos/core/memop/RecyclePool.h + + + cocos/core/assets/Asset.cpp + cocos/core/assets/Asset.h + cocos/core/assets/AssetEnum.h + cocos/core/assets/AssetsModuleHeader.h + cocos/core/assets/BufferAsset.cpp + cocos/core/assets/BufferAsset.h + cocos/core/assets/EffectAsset.cpp + cocos/core/assets/EffectAsset.h + cocos/core/assets/ImageAsset.cpp + cocos/core/assets/ImageAsset.h + cocos/core/assets/Material.cpp + cocos/core/assets/Material.h + cocos/core/assets/RenderTexture.cpp + cocos/core/assets/RenderingSubMesh.cpp + cocos/core/assets/RenderingSubMesh.h + cocos/core/assets/SceneAsset.cpp + cocos/core/assets/SceneAsset.h + cocos/core/assets/SimpleTexture.cpp + cocos/core/assets/SimpleTexture.h + cocos/core/assets/TextAsset.h + cocos/core/assets/Texture2D.cpp + cocos/core/assets/Texture2D.h + cocos/core/assets/TextureBase.cpp + cocos/core/assets/TextureBase.h + cocos/core/assets/TextureCube.cpp + cocos/core/assets/TextureCube.h + cocos/core/assets/BitmapFont.h + cocos/core/assets/BitmapFont.cpp + cocos/core/assets/Font.h + cocos/core/assets/Font.cpp + + # builtin + cocos/core/builtin/BuiltinResMgr.cpp + cocos/core/builtin/BuiltinResMgr.h + + cocos/core/data/Object.cpp + cocos/core/data/Object.h + cocos/core/data/JSBNativeDataHolder.h + cocos/core/scene-graph/Layers.h + cocos/core/scene-graph/Node.cpp + cocos/core/scene-graph/Node.h + cocos/core/scene-graph/NodeEnum.h + cocos/core/scene-graph/Scene.cpp + cocos/core/scene-graph/Scene.h + cocos/core/scene-graph/SceneGlobals.cpp + cocos/core/scene-graph/SceneGlobals.h + cocos/core/scene-graph/SceneGraphModuleHeader.h + + cocos/core/utils/IDGenerator.cpp + cocos/core/utils/IDGenerator.h + cocos/core/utils/Path.cpp + cocos/core/utils/Path.h + cocos/core/utils/Pool.h + cocos/core/utils/ImageUtils.h + cocos/core/utils/ImageUtils.cpp + + # cocos/core/platform/native/SystemInfo.h + # cocos/core/platform/native/SystemInfo.cpp + + cocos/core/platform/Debug.h + cocos/core/platform/Debug.cpp + cocos/core/platform/Macro.h + cocos/core/platform/Macro.cpp + # cocos/core/platform/Screen.h + # cocos/core/platform/Screen.cpp + # cocos/core/platform/View.h + # cocos/core/platform/View.cpp + # cocos/core/platform/VisibleRect.h + # cocos/core/platform/VisibleRect.cpp + +) + +# FreeTypeFont is used in DebugRenderer currently +if (USE_DEBUG_RENDERER) +cocos_source_files( + cocos/core/assets/FreeTypeFont.h + cocos/core/assets/FreeTypeFont.cpp +) +endif() + +cocos_source_files(MODULE ccgeometry + cocos/core/geometry/AABB.cpp + cocos/core/geometry/AABB.h + cocos/core/geometry/Capsule.cpp + cocos/core/geometry/Capsule.h + # cocos/core/geometry/Curve.cpp + # cocos/core/geometry/Curve.h + cocos/core/geometry/Distance.cpp + cocos/core/geometry/Distance.h + cocos/core/geometry/Enums.h + cocos/core/geometry/Frustum.cpp + cocos/core/geometry/Frustum.h + cocos/core/geometry/Intersect.cpp + cocos/core/geometry/Intersect.h + cocos/core/geometry/Line.cpp + cocos/core/geometry/Line.h + cocos/core/geometry/Obb.cpp + cocos/core/geometry/Obb.h + cocos/core/geometry/Plane.cpp + cocos/core/geometry/Plane.h + cocos/core/geometry/Ray.cpp + cocos/core/geometry/Ray.h + cocos/core/geometry/Spec.cpp + cocos/core/geometry/Spec.h + cocos/core/geometry/Sphere.cpp + cocos/core/geometry/Sphere.h + cocos/core/geometry/Spline.cpp + cocos/core/geometry/Spline.h + cocos/core/geometry/Triangle.cpp + cocos/core/geometry/Triangle.h +) + +##### script bindings + +######## utils +cocos_source_files( + cocos/bindings/utils/BindingUtils.cpp + cocos/bindings/utils/BindingUtils.h +) + +######## dop +cocos_source_files( + NO_UBUILD cocos/bindings/dop/BufferPool.cpp + cocos/bindings/dop/BufferPool.h + NO_UBUILD cocos/bindings/dop/jsb_dop.cpp + cocos/bindings/dop/jsb_dop.h + cocos/bindings/dop/PoolType.h + cocos/bindings/dop/BufferAllocator.h + NO_UBUILD cocos/bindings/dop/BufferAllocator.cpp +) + +######## auto +cocos_source_files( + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_network_auto.cpp + ${SWIG_OUTPUT}/jsb_network_auto.h + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_cocos_auto.cpp + ${SWIG_OUTPUT}/jsb_cocos_auto.h + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_extension_auto.cpp + ${SWIG_OUTPUT}/jsb_extension_auto.h + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_gfx_auto.cpp + ${SWIG_OUTPUT}/jsb_gfx_auto.h + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_pipeline_auto.cpp + ${SWIG_OUTPUT}/jsb_pipeline_auto.h + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_scene_auto.cpp + ${SWIG_OUTPUT}/jsb_scene_auto.h + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_gi_auto.cpp + ${SWIG_OUTPUT}/jsb_gi_auto.h + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_assets_auto.cpp + ${SWIG_OUTPUT}/jsb_assets_auto.h + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_geometry_auto.cpp + ${SWIG_OUTPUT}/jsb_geometry_auto.h + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_render_auto.cpp + ${SWIG_OUTPUT}/jsb_render_auto.h + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_2d_auto.cpp + ${SWIG_OUTPUT}/jsb_2d_auto.h +) + + +if(USE_AUDIO) + cocos_source_files( + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_audio_auto.cpp + ${SWIG_OUTPUT}/jsb_audio_auto.h + ) +endif() + +if(USE_VIDEO) + cocos_source_files( + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_video_auto.cpp + ${SWIG_OUTPUT}/jsb_video_auto.h + ) +endif() + +if(USE_WEBVIEW) + cocos_source_files( + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_webview_auto.cpp + ${SWIG_OUTPUT}/jsb_webview_auto.h + ) +endif() + +if(USE_XR_REMOTE_PREVIEW) + cocos_source_files( + NO_WERROR cocos/xr/XRRemotePreviewManager.h + cocos/xr/XRRemotePreviewManager.cpp + ) +endif() + +if(USE_MIDDLEWARE) + cocos_source_files( + cocos/editor-support/IOBuffer.cpp + cocos/editor-support/IOBuffer.h + cocos/editor-support/IOTypedArray.cpp + cocos/editor-support/IOTypedArray.h + cocos/editor-support/MeshBuffer.cpp + cocos/editor-support/MeshBuffer.h + cocos/editor-support/middleware-adapter.cpp + cocos/editor-support/middleware-adapter.h + cocos/editor-support/MiddlewareMacro.h + cocos/editor-support/MiddlewareManager.cpp + cocos/editor-support/MiddlewareManager.h + cocos/editor-support/SharedBufferManager.cpp + cocos/editor-support/SharedBufferManager.h + cocos/editor-support/TypedArrayPool.cpp + cocos/editor-support/TypedArrayPool.h + ) + + cocos_source_files( + NO_WERROR NO_UBUILD ${SWIG_OUTPUT}/jsb_editor_support_auto.cpp + ${SWIG_OUTPUT}/jsb_editor_support_auto.h + ) + + if(USE_SPINE) + cocos_source_files( + NO_WERROR NO_UBUILD cocos/editor-support/spine/Animation.cpp + cocos/editor-support/spine/Animation.h + NO_WERROR NO_UBUILD cocos/editor-support/spine/AnimationState.cpp + cocos/editor-support/spine/AnimationState.h + NO_WERROR cocos/editor-support/spine/AnimationStateData.cpp + cocos/editor-support/spine/AnimationStateData.h + NO_WERROR cocos/editor-support/spine/Atlas.cpp + cocos/editor-support/spine/Atlas.h + NO_WERROR cocos/editor-support/spine/AtlasAttachmentLoader.cpp + cocos/editor-support/spine/AtlasAttachmentLoader.h + NO_WERROR cocos/editor-support/spine/Attachment.cpp + cocos/editor-support/spine/Attachment.h + NO_WERROR cocos/editor-support/spine/AttachmentLoader.cpp + cocos/editor-support/spine/AttachmentLoader.h + NO_WERROR cocos/editor-support/spine/AttachmentTimeline.cpp + cocos/editor-support/spine/AttachmentTimeline.h + cocos/editor-support/spine/AttachmentType.h + cocos/editor-support/spine/BlendMode.h + NO_WERROR cocos/editor-support/spine/Bone.cpp + cocos/editor-support/spine/Bone.h + NO_WERROR cocos/editor-support/spine/BoneData.cpp + cocos/editor-support/spine/BoneData.h + NO_WERROR cocos/editor-support/spine/BoundingBoxAttachment.cpp + cocos/editor-support/spine/BoundingBoxAttachment.h + NO_WERROR cocos/editor-support/spine/ClippingAttachment.cpp + cocos/editor-support/spine/ClippingAttachment.h + cocos/editor-support/spine/Color.h + NO_WERROR cocos/editor-support/spine/ColorTimeline.cpp + cocos/editor-support/spine/ColorTimeline.h + NO_WERROR cocos/editor-support/spine/Constraint.cpp + cocos/editor-support/spine/Constraint.h + NO_WERROR NO_UBUILD cocos/editor-support/spine/ConstraintData.cpp + cocos/editor-support/spine/ConstraintData.h + cocos/editor-support/spine/ContainerUtil.h + NO_WERROR cocos/editor-support/spine/CurveTimeline.cpp + cocos/editor-support/spine/CurveTimeline.h + cocos/editor-support/spine/Debug.h + NO_WERROR cocos/editor-support/spine/DeformTimeline.cpp + cocos/editor-support/spine/DeformTimeline.h + cocos/editor-support/spine/dll.h + NO_WERROR cocos/editor-support/spine/DrawOrderTimeline.cpp + cocos/editor-support/spine/DrawOrderTimeline.h + NO_WERROR cocos/editor-support/spine/Event.cpp + cocos/editor-support/spine/Event.h + NO_WERROR cocos/editor-support/spine/EventData.cpp + cocos/editor-support/spine/EventData.h + NO_WERROR cocos/editor-support/spine/EventTimeline.cpp + cocos/editor-support/spine/EventTimeline.h + NO_WERROR cocos/editor-support/spine/Extension.cpp + cocos/editor-support/spine/Extension.h + cocos/editor-support/spine/HashMap.h + cocos/editor-support/spine/HasRendererObject.h + NO_WERROR cocos/editor-support/spine/IkConstraint.cpp + cocos/editor-support/spine/IkConstraint.h + NO_WERROR cocos/editor-support/spine/IkConstraintData.cpp + cocos/editor-support/spine/IkConstraintData.h + NO_WERROR cocos/editor-support/spine/IkConstraintTimeline.cpp + cocos/editor-support/spine/IkConstraintTimeline.h + NO_WERROR cocos/editor-support/spine/Json.cpp + cocos/editor-support/spine/Json.h + NO_WERROR cocos/editor-support/spine/LinkedMesh.cpp + cocos/editor-support/spine/LinkedMesh.h + NO_WERROR cocos/editor-support/spine/MathUtil.cpp + cocos/editor-support/spine/MathUtil.h + NO_WERROR cocos/editor-support/spine/MeshAttachment.cpp + cocos/editor-support/spine/MeshAttachment.h + cocos/editor-support/spine/MixBlend.h + cocos/editor-support/spine/MixDirection.h + NO_WERROR cocos/editor-support/spine/PathAttachment.cpp + cocos/editor-support/spine/PathAttachment.h + NO_WERROR cocos/editor-support/spine/PathConstraint.cpp + cocos/editor-support/spine/PathConstraint.h + NO_WERROR cocos/editor-support/spine/PathConstraintData.cpp + cocos/editor-support/spine/PathConstraintData.h + NO_WERROR cocos/editor-support/spine/PathConstraintMixTimeline.cpp + cocos/editor-support/spine/PathConstraintMixTimeline.h + NO_WERROR cocos/editor-support/spine/PathConstraintPositionTimeline.cpp + cocos/editor-support/spine/PathConstraintPositionTimeline.h + NO_WERROR cocos/editor-support/spine/PathConstraintSpacingTimeline.cpp + cocos/editor-support/spine/PathConstraintSpacingTimeline.h + NO_WERROR cocos/editor-support/spine/PointAttachment.cpp + cocos/editor-support/spine/PointAttachment.h + cocos/editor-support/spine/Pool.h + cocos/editor-support/spine/PositionMode.h + NO_WERROR cocos/editor-support/spine/RegionAttachment.cpp + cocos/editor-support/spine/RegionAttachment.h + cocos/editor-support/spine/RotateMode.h + NO_WERROR cocos/editor-support/spine/RotateTimeline.cpp + cocos/editor-support/spine/RotateTimeline.h + NO_WERROR cocos/editor-support/spine/RTTI.cpp + cocos/editor-support/spine/RTTI.h + NO_WERROR cocos/editor-support/spine/ScaleTimeline.cpp + cocos/editor-support/spine/ScaleTimeline.h + NO_WERROR cocos/editor-support/spine/ShearTimeline.cpp + cocos/editor-support/spine/ShearTimeline.h + NO_WERROR cocos/editor-support/spine/Skeleton.cpp + cocos/editor-support/spine/Skeleton.h + NO_WERROR cocos/editor-support/spine/SkeletonBinary.cpp + cocos/editor-support/spine/SkeletonBinary.h + NO_WERROR cocos/editor-support/spine/SkeletonBounds.cpp + cocos/editor-support/spine/SkeletonBounds.h + NO_WERROR cocos/editor-support/spine/SkeletonClipping.cpp + cocos/editor-support/spine/SkeletonClipping.h + NO_WERROR cocos/editor-support/spine/SkeletonData.cpp + cocos/editor-support/spine/SkeletonData.h + NO_WERROR cocos/editor-support/spine/SkeletonJson.cpp + cocos/editor-support/spine/SkeletonJson.h + NO_WERROR cocos/editor-support/spine/Skin.cpp + cocos/editor-support/spine/Skin.h + NO_WERROR cocos/editor-support/spine/Slot.cpp + cocos/editor-support/spine/Slot.h + NO_WERROR cocos/editor-support/spine/SlotData.cpp + cocos/editor-support/spine/SlotData.h + cocos/editor-support/spine/SpacingMode.h + cocos/editor-support/spine/spine.h + NO_WERROR cocos/editor-support/spine/SpineObject.cpp + cocos/editor-support/spine/SpineObject.h + cocos/editor-support/spine/SpineString.h + NO_WERROR cocos/editor-support/spine/TextureLoader.cpp + cocos/editor-support/spine/TextureLoader.h + NO_WERROR cocos/editor-support/spine/Timeline.cpp + cocos/editor-support/spine/Timeline.h + cocos/editor-support/spine/TimelineType.h + NO_WERROR cocos/editor-support/spine/TransformConstraint.cpp + cocos/editor-support/spine/TransformConstraint.h + NO_WERROR cocos/editor-support/spine/TransformConstraintData.cpp + cocos/editor-support/spine/TransformConstraintData.h + NO_WERROR cocos/editor-support/spine/TransformConstraintTimeline.cpp + cocos/editor-support/spine/TransformConstraintTimeline.h + cocos/editor-support/spine/TransformMode.h + NO_WERROR cocos/editor-support/spine/TranslateTimeline.cpp + cocos/editor-support/spine/TranslateTimeline.h + NO_WERROR cocos/editor-support/spine/Triangulator.cpp + cocos/editor-support/spine/Triangulator.h + NO_WERROR cocos/editor-support/spine/TwoColorTimeline.cpp + cocos/editor-support/spine/TwoColorTimeline.h + NO_WERROR cocos/editor-support/spine/Updatable.cpp + cocos/editor-support/spine/Updatable.h + cocos/editor-support/spine/Vector.h + NO_WERROR cocos/editor-support/spine/VertexAttachment.cpp + cocos/editor-support/spine/VertexAttachment.h + NO_WERROR cocos/editor-support/spine/VertexEffect.cpp + cocos/editor-support/spine/VertexEffect.h + cocos/editor-support/spine/Vertices.h + NO_WERROR cocos/editor-support/spine-creator-support/AttachmentVertices.cpp + cocos/editor-support/spine-creator-support/AttachmentVertices.h + NO_WERROR cocos/editor-support/spine-creator-support/SkeletonAnimation.cpp + cocos/editor-support/spine-creator-support/SkeletonAnimation.h + NO_WERROR cocos/editor-support/spine-creator-support/SkeletonCache.cpp + cocos/editor-support/spine-creator-support/SkeletonCache.h + NO_WERROR cocos/editor-support/spine-creator-support/SkeletonCacheAnimation.cpp + cocos/editor-support/spine-creator-support/SkeletonCacheAnimation.h + NO_WERROR cocos/editor-support/spine-creator-support/SkeletonCacheMgr.cpp + cocos/editor-support/spine-creator-support/SkeletonCacheMgr.h + NO_WERROR cocos/editor-support/spine-creator-support/SkeletonDataMgr.cpp + cocos/editor-support/spine-creator-support/SkeletonDataMgr.h + NO_WERROR NO_UBUILD cocos/editor-support/spine-creator-support/SkeletonRenderer.cpp + cocos/editor-support/spine-creator-support/SkeletonRenderer.h + NO_WERROR cocos/editor-support/spine-creator-support/spine-cocos2dx.cpp + cocos/editor-support/spine-creator-support/spine-cocos2dx.h + NO_WERROR cocos/editor-support/spine-creator-support/VertexEffectDelegate.cpp + cocos/editor-support/spine-creator-support/VertexEffectDelegate.h + cocos/editor-support/spine-creator-support/VertexEffectDelegate.h + NO_WERROR cocos/editor-support/spine-creator-support/Vector2.cpp + cocos/editor-support/spine-creator-support/Vector2.h + + ) + cocos_source_files( + NO_WERROR ${SWIG_OUTPUT}/jsb_spine_auto.cpp + ${SWIG_OUTPUT}/jsb_spine_auto.h + NO_WERROR cocos/bindings/manual/jsb_spine_manual.cpp + cocos/bindings/manual/jsb_spine_manual.h + ) + endif() + + if(USE_DRAGONBONES) + cocos_source_files( + cocos/editor-support/dragonbones/DragonBonesHeaders.h + NO_WERROR cocos/editor-support/dragonbones/animation/Animation.cpp + cocos/editor-support/dragonbones/animation/Animation.h + NO_WERROR cocos/editor-support/dragonbones/animation/AnimationState.cpp + cocos/editor-support/dragonbones/animation/AnimationState.h + NO_WERROR cocos/editor-support/dragonbones/animation/BaseTimelineState.cpp + cocos/editor-support/dragonbones/animation/BaseTimelineState.h + cocos/editor-support/dragonbones/animation/IAnimatable.h + NO_WERROR cocos/editor-support/dragonbones/animation/TimelineState.cpp + cocos/editor-support/dragonbones/animation/TimelineState.h + NO_WERROR cocos/editor-support/dragonbones/animation/WorldClock.cpp + cocos/editor-support/dragonbones/animation/WorldClock.h + NO_WERROR cocos/editor-support/dragonbones/armature/Armature.cpp + cocos/editor-support/dragonbones/armature/Armature.h + NO_WERROR cocos/editor-support/dragonbones/armature/Bone.cpp + cocos/editor-support/dragonbones/armature/Bone.h + NO_WERROR cocos/editor-support/dragonbones/armature/Constraint.cpp + cocos/editor-support/dragonbones/armature/Constraint.h + NO_WERROR cocos/editor-support/dragonbones/armature/DeformVertices.cpp + cocos/editor-support/dragonbones/armature/DeformVertices.h + cocos/editor-support/dragonbones/armature/IArmatureProxy.h + NO_WERROR cocos/editor-support/dragonbones/armature/Slot.cpp + cocos/editor-support/dragonbones/armature/Slot.h + NO_WERROR cocos/editor-support/dragonbones/armature/TransformObject.cpp + cocos/editor-support/dragonbones/armature/TransformObject.h + NO_WERROR cocos/editor-support/dragonbones/core/BaseObject.cpp + cocos/editor-support/dragonbones/core/BaseObject.h + NO_WERROR cocos/editor-support/dragonbones/core/DragonBones.cpp + cocos/editor-support/dragonbones/core/DragonBones.h + NO_WERROR cocos/editor-support/dragonbones/event/EventObject.cpp + cocos/editor-support/dragonbones/event/EventObject.h + cocos/editor-support/dragonbones/event/IEventDispatcher.h + NO_WERROR cocos/editor-support/dragonbones/factory/BaseFactory.cpp + cocos/editor-support/dragonbones/factory/BaseFactory.h + cocos/editor-support/dragonbones/geom/ColorTransform.h + cocos/editor-support/dragonbones/geom/Matrix.h + NO_WERROR cocos/editor-support/dragonbones/geom/Point.cpp + cocos/editor-support/dragonbones/geom/Point.h + cocos/editor-support/dragonbones/geom/Rectangle.h + NO_WERROR cocos/editor-support/dragonbones/geom/Transform.cpp + cocos/editor-support/dragonbones/geom/Transform.h + NO_WERROR cocos/editor-support/dragonbones/model/AnimationConfig.cpp + cocos/editor-support/dragonbones/model/AnimationConfig.h + NO_WERROR cocos/editor-support/dragonbones/model/AnimationData.cpp + cocos/editor-support/dragonbones/model/AnimationData.h + NO_WERROR cocos/editor-support/dragonbones/model/ArmatureData.cpp + cocos/editor-support/dragonbones/model/ArmatureData.h + NO_WERROR cocos/editor-support/dragonbones/model/BoundingBoxData.cpp + cocos/editor-support/dragonbones/model/BoundingBoxData.h + NO_WERROR cocos/editor-support/dragonbones/model/CanvasData.cpp + cocos/editor-support/dragonbones/model/CanvasData.h + NO_WERROR cocos/editor-support/dragonbones/model/ConstraintData.cpp + cocos/editor-support/dragonbones/model/ConstraintData.h + NO_WERROR cocos/editor-support/dragonbones/model/DisplayData.cpp + cocos/editor-support/dragonbones/model/DisplayData.h + NO_WERROR cocos/editor-support/dragonbones/model/DragonBonesData.cpp + cocos/editor-support/dragonbones/model/DragonBonesData.h + NO_WERROR cocos/editor-support/dragonbones/model/SkinData.cpp + cocos/editor-support/dragonbones/model/SkinData.h + NO_WERROR cocos/editor-support/dragonbones/model/TextureAtlasData.cpp + cocos/editor-support/dragonbones/model/TextureAtlasData.h + NO_WERROR cocos/editor-support/dragonbones/model/UserData.cpp + cocos/editor-support/dragonbones/model/UserData.h + NO_WERROR cocos/editor-support/dragonbones/parser/BinaryDataParser.cpp + cocos/editor-support/dragonbones/parser/BinaryDataParser.h + NO_WERROR cocos/editor-support/dragonbones/parser/DataParser.cpp + cocos/editor-support/dragonbones/parser/DataParser.h + NO_WERROR NO_UBUILD cocos/editor-support/dragonbones/parser/JSONDataParser.cpp + cocos/editor-support/dragonbones/parser/JSONDataParser.h + NO_WERROR cocos/editor-support/dragonbones-creator-support/ArmatureCache.cpp + cocos/editor-support/dragonbones-creator-support/ArmatureCache.h + NO_WERROR cocos/editor-support/dragonbones-creator-support/ArmatureCacheMgr.cpp + cocos/editor-support/dragonbones-creator-support/ArmatureCacheMgr.h + NO_WERROR cocos/editor-support/dragonbones-creator-support/CCArmatureCacheDisplay.cpp + cocos/editor-support/dragonbones-creator-support/CCArmatureCacheDisplay.h + NO_WERROR NO_UBUILD cocos/editor-support/dragonbones-creator-support/CCArmatureDisplay.cpp + cocos/editor-support/dragonbones-creator-support/CCArmatureDisplay.h + cocos/editor-support/dragonbones-creator-support/CCDragonBonesHeaders.h + NO_WERROR cocos/editor-support/dragonbones-creator-support/CCFactory.cpp + cocos/editor-support/dragonbones-creator-support/CCFactory.h + NO_WERROR cocos/editor-support/dragonbones-creator-support/CCSlot.cpp + cocos/editor-support/dragonbones-creator-support/CCSlot.h + NO_WERROR cocos/editor-support/dragonbones-creator-support/CCTextureAtlasData.cpp + cocos/editor-support/dragonbones-creator-support/CCTextureAtlasData.h + + ) + cocos_source_files( + NO_WERROR ${SWIG_OUTPUT}/jsb_dragonbones_auto.cpp + ${SWIG_OUTPUT}/jsb_dragonbones_auto.h + NO_WERROR cocos/bindings/manual/jsb_dragonbones_manual.cpp + cocos/bindings/manual/jsb_dragonbones_manual.h + ) + endif() +endif() + +######## manual +cocos_source_files( + NO_WERROR cocos/bindings/manual/jsb_network_manual.cpp + cocos/bindings/manual/jsb_network_manual.h + NO_WERROR cocos/bindings/manual/jsb_xmlhttprequest.cpp + cocos/bindings/manual/jsb_xmlhttprequest.h + +) + +cocos_source_files( + cocos/bindings/manual/jsb_classtype.cpp + cocos/bindings/manual/jsb_classtype.h + cocos/bindings/manual/jsb_cocos_manual.cpp + cocos/bindings/manual/jsb_cocos_manual.h + cocos/bindings/manual/jsb_geometry_manual.cpp + cocos/bindings/manual/jsb_geometry_manual.h + cocos/bindings/manual/jsb_conversions.h + cocos/bindings/manual/jsb_conversions_spec.cpp + cocos/bindings/manual/jsb_conversions_spec.h + cocos/bindings/manual/jsb_gfx_manual.cpp + cocos/bindings/manual/jsb_gfx_manual.h + cocos/bindings/manual/jsb_global.cpp + cocos/bindings/manual/jsb_global.h + cocos/bindings/manual/jsb_helper.cpp + cocos/bindings/manual/jsb_helper.h + cocos/bindings/manual/jsb_module_register.h + cocos/bindings/manual/jsb_module_register.cpp + cocos/bindings/manual/jsb_platform.h + cocos/bindings/manual/jsb_scene_manual.cpp + cocos/bindings/manual/jsb_scene_manual.h + cocos/bindings/manual/jsb_assets_manual.cpp + cocos/bindings/manual/jsb_assets_manual.h + cocos/bindings/manual/jsb_network_manual.cpp + cocos/bindings/manual/jsb_network_manual.h + cocos/bindings/manual/jsb_xmlhttprequest.cpp + cocos/bindings/manual/jsb_xmlhttprequest.h + cocos/bindings/manual/jsb_pipeline_manual.h + cocos/bindings/manual/jsb_pipeline_manual.cpp + cocos/bindings/manual/jsb_adpf.cpp +) +if(USE_AUDIO) + cocos_source_files( + cocos/bindings/manual/jsb_audio_manual.cpp + cocos/bindings/manual/jsb_audio_manual.h + ) +endif() +if(USE_SOCKET) + cocos_source_files( + cocos/bindings/manual/jsb_socketio.cpp + cocos/bindings/manual/jsb_socketio.h + cocos/bindings/manual/jsb_websocket.cpp + cocos/bindings/manual/jsb_websocket.h + ) +endif() + +if(USE_WEBSOCKET_SERVER) + cocos_source_files( + cocos/bindings/manual/jsb_websocket_server.cpp + cocos/bindings/manual/jsb_websocket_server.h + ) +endif() + +if(ANDROID) + cocos_source_files( + cocos/bindings/manual/jsb_platform_android.cpp + cocos/bindings/manual/JavaScriptJavaBridge.cpp + cocos/bindings/manual/JavaScriptJavaBridge.h + ) +elseif(OPENHARMONY) + cocos_source_files( + cocos/bindings/manual/jsb_platform_openharmony.cpp + ) +elseif(OHOS) + cocos_source_files( + cocos/bindings/manual/jsb_platform_ohos.cpp + cocos/bindings/manual/JavaScriptJavaBridge.cpp + cocos/bindings/manual/JavaScriptJavaBridge.h + ) +elseif(APPLE) + cocos_source_files( + cocos/bindings/manual/jsb_platform_apple.mm + cocos/bindings/manual/JavaScriptObjCBridge.h + cocos/bindings/manual/JavaScriptObjCBridge.mm + cocos/platform/apple/JsbBridge.h + cocos/platform/apple/JsbBridge.mm + cocos/platform/apple/JsbBridgeWrapper.h + cocos/platform/apple/JsbBridgeWrapper.mm + ) +elseif(WIN32) + cocos_source_files( + NO_WERROR cocos/bindings/manual/jsb_platform_win32.cpp + ) +elseif(LINUX) + cocos_source_files( + NO_WERROR cocos/bindings/manual/jsb_platform_linux.cpp + ) +endif() + +############# module bindings +cocos_source_files(MODULE ccbindings + cocos/bindings/sebind/intl/common.h + cocos/bindings/sebind/intl/common.cpp + cocos/bindings/sebind/class.inl + cocos/bindings/sebind/sebind.h +) + +if(USE_SE_V8) + cocos_source_files(MODULE ccbindings + cocos/bindings/sebind/class_v8.h + cocos/bindings/sebind/class_v8.cpp + cocos/bindings/jswrapper/v8/Base.h + cocos/bindings/jswrapper/v8/Class.cpp + cocos/bindings/jswrapper/v8/Class.h + cocos/bindings/jswrapper/v8/HelperMacros.h + cocos/bindings/jswrapper/v8/HelperMacros.cpp + cocos/bindings/jswrapper/v8/Object.cpp + cocos/bindings/jswrapper/v8/Object.h + cocos/bindings/jswrapper/v8/ObjectWrap.cpp + cocos/bindings/jswrapper/v8/ObjectWrap.h + cocos/bindings/jswrapper/v8/ScriptEngine.cpp + cocos/bindings/jswrapper/v8/ScriptEngine.h + cocos/bindings/jswrapper/v8/SeApi.h + cocos/bindings/jswrapper/v8/Utils.cpp + cocos/bindings/jswrapper/v8/Utils.h + cocos/bindings/jswrapper/v8/MissingSymbols.cpp + cocos/bindings/jswrapper/v8/MissingSymbols.h + ) + if(USE_V8_DEBUGGER) + cocos_source_files(MODULE ccbindings + cocos/bindings/jswrapper/v8/debugger/base64.h + NO_WERROR cocos/bindings/jswrapper/v8/debugger/env.cpp + cocos/bindings/jswrapper/v8/debugger/env.h + NO_WERROR NO_UBUILD cocos/bindings/jswrapper/v8/debugger/http_parser.cpp + cocos/bindings/jswrapper/v8/debugger/http_parser.h + NO_WERROR cocos/bindings/jswrapper/v8/debugger/inspector_agent.cpp + cocos/bindings/jswrapper/v8/debugger/inspector_agent.h + NO_WERROR cocos/bindings/jswrapper/v8/debugger/inspector_io.cpp + cocos/bindings/jswrapper/v8/debugger/inspector_io.h + NO_WERROR cocos/bindings/jswrapper/v8/debugger/inspector_socket.cpp + cocos/bindings/jswrapper/v8/debugger/inspector_socket.h + NO_WERROR cocos/bindings/jswrapper/v8/debugger/inspector_socket_server.cpp + cocos/bindings/jswrapper/v8/debugger/inspector_socket_server.h + NO_WERROR cocos/bindings/jswrapper/v8/debugger/node.cpp + cocos/bindings/jswrapper/v8/debugger/node.h + NO_WERROR cocos/bindings/jswrapper/v8/debugger/node_debug_options.cpp + cocos/bindings/jswrapper/v8/debugger/node_debug_options.h + cocos/bindings/jswrapper/v8/debugger/node_mutex.h + NO_WERROR cocos/bindings/jswrapper/v8/debugger/SHA1.cpp + cocos/bindings/jswrapper/v8/debugger/SHA1.h + cocos/bindings/jswrapper/v8/debugger/util-inl.h + NO_WERROR cocos/bindings/jswrapper/v8/debugger/util.cpp + cocos/bindings/jswrapper/v8/debugger/util.h + cocos/bindings/jswrapper/v8/debugger/v8_inspector_protocol_json.h + ) + endif() +endif() + +if(USE_SE_SM) + cocos_source_files(MODULE ccbindings + cocos/bindings/jswrapper/sm/Base.h + NO_WERROR cocos/bindings/jswrapper/sm/Class.cpp + cocos/bindings/jswrapper/sm/Class.h + cocos/bindings/jswrapper/sm/HelperMacros.h + NO_WERROR cocos/bindings/jswrapper/sm/HelperMacros.cpp + NO_WERROR cocos/bindings/jswrapper/sm/Object.cpp + cocos/bindings/jswrapper/sm/Object.h + NO_WERROR cocos/bindings/jswrapper/sm/ScriptEngine.cpp + cocos/bindings/jswrapper/sm/ScriptEngine.h + cocos/bindings/jswrapper/sm/SeApi.h + NO_WERROR cocos/bindings/jswrapper/sm/Utils.cpp + cocos/bindings/jswrapper/sm/Utils.h + ) +endif() + +if(USE_SE_NAPI) + cocos_source_files(MODULE ccbindings + cocos/bindings/jswrapper/napi/Class.cpp + cocos/bindings/jswrapper/napi/Class.h + cocos/bindings/jswrapper/napi/CommonHeader.h + cocos/bindings/jswrapper/napi/HelperMacros.h + cocos/bindings/jswrapper/napi/HelperMacros.cpp + cocos/bindings/jswrapper/napi/Object.cpp + cocos/bindings/jswrapper/napi/Object.h + NO_WERROR cocos/bindings/jswrapper/napi/ScriptEngine.cpp + cocos/bindings/jswrapper/napi/ScriptEngine.h + cocos/bindings/jswrapper/napi/SeApi.h + NO_WERROR cocos/bindings/jswrapper/napi/Utils.cpp + cocos/bindings/jswrapper/napi/Utils.h + ) +endif() + +cocos_source_files(MODULE ccbindings + cocos/bindings/jswrapper/config.h + cocos/bindings/jswrapper/config.cpp + cocos/bindings/jswrapper/HandleObject.cpp + cocos/bindings/jswrapper/HandleObject.h + cocos/bindings/jswrapper/MappingUtils.cpp + cocos/bindings/jswrapper/MappingUtils.h + cocos/bindings/jswrapper/Object.h + cocos/bindings/jswrapper/RefCounter.cpp + cocos/bindings/jswrapper/RefCounter.h + cocos/bindings/jswrapper/SeApi.h + cocos/bindings/jswrapper/State.h + cocos/bindings/jswrapper/PrivateObject.h + cocos/bindings/jswrapper/Value.cpp + cocos/bindings/jswrapper/Value.h + cocos/bindings/jswrapper/ValueArrayPool.cpp + cocos/bindings/jswrapper/ValueArrayPool.h +) + +cocos_source_files( + cocos/bindings/manual/jsb_global_init.cpp + cocos/bindings/manual/jsb_global_init.h +) + +cocos_source_files( + cocos/bindings/event/EventDispatcher.cpp + cocos/bindings/event/EventDispatcher.h +) + +#### storage +if(NOT ANDROID) + cocos_source_files( + cocos/storage/local-storage/LocalStorage.cpp + cocos/storage/local-storage/LocalStorage.h + ) +else() + cocos_source_files( + cocos/storage/local-storage/LocalStorage-android.cpp + ) +endif() + +#### ui +if(USE_EDIT_BOX) + cocos_source_files( + cocos/ui/edit-box/EditBox.h + ) +endif() + +if(NOT OPENHARMONY AND (ANDROID OR IOS OR OHOS)) + if(USE_VIDEO) + cocos_source_files( + cocos/ui/videoplayer/VideoPlayer.h + ) + endif() + if(USE_WEBVIEW) + cocos_source_files( + cocos/ui/webview/WebView-inl.h + cocos/ui/webview/WebView.h + ) + endif() +endif() + +if(ANDROID) + if(USE_EDIT_BOX) + cocos_source_files( + cocos/ui/edit-box/EditBox-android.cpp + ) + endif() + if(USE_VIDEO) + cocos_source_files( + cocos/ui/videoplayer/VideoPlayer-java.cpp + ) + endif() + if(USE_WEBVIEW) + cocos_source_files( + cocos/ui/webview/WebViewImpl-java.h + cocos/ui/webview/WebViewImpl-android.cpp + ) + endif() +elseif(OPENHARMONY) + if(USE_EDIT_BOX) + cocos_source_files( + cocos/ui/edit-box/EditBox-openharmony.h + cocos/ui/edit-box/EditBox-openharmony.cpp + ) + endif() + if(USE_WEBVIEW) + cocos_source_files( + cocos/ui/webview/WebViewImpl-java.h + cocos/ui/webview/WebViewImpl-openharmony.h + cocos/ui/webview/WebViewImpl-openharmony.cpp + ) + endif() +elseif(OHOS) + if(USE_EDIT_BOX) + cocos_source_files( + cocos/ui/edit-box/EditBox-ohos.cpp + ) + endif() + if(USE_VIDEO) + cocos_source_files( + cocos/ui/videoplayer/VideoPlayer-java.cpp + ) + endif() + if(USE_WEBVIEW) + cocos_source_files( + cocos/ui/webview/WebViewImpl-java.h + cocos/ui/webview/WebViewImpl-ohos.cpp + ) + endif() +elseif(MACOSX) + if(USE_EDIT_BOX) + cocos_source_files( + cocos/ui/edit-box/EditBox-mac.mm + ) + endif() +elseif(IOS) + if(USE_EDIT_BOX) + cocos_source_files( + NO_WERROR NO_UBUILD cocos/ui/edit-box/EditBox-ios.mm + ) + endif() + if(USE_VIDEO) + cocos_source_files( + cocos/ui/videoplayer/VideoPlayer-ios.mm + ) + endif() + if(USE_WEBVIEW) + cocos_source_files( + cocos/ui/webview/WebViewImpl-ios.h + NO_WERROR NO_UBUILD cocos/ui/webview/WebViewImpl-ios.mm + + ) + endif() +elseif(WIN32) + if(USE_EDIT_BOX) + cocos_source_files( + NO_WERROR cocos/ui/edit-box/EditBox-win32.cpp + ) + endif() +elseif(LINUX OR QNX) + if(USE_EDIT_BOX) + cocos_source_files( + NO_WERROR cocos/ui/edit-box/EditBox-linux.cpp + ) + endif() +endif() + +##### ar module +if(USE_AR_MODULE) + cocos_source_files( + cocos/renderer/pipeline/xr/ar/ARStage.cpp + cocos/renderer/pipeline/xr/ar/ARStage.h + cocos/renderer/pipeline/xr/ar/ARBackground.cpp + cocos/renderer/pipeline/xr/ar/ARBackground.h + ) +endif() + +if(USE_XR OR USE_AR_MODULE) + list(APPEND COCOS_SOURCE_LIST ${XR_COMMON_SOURCES}) + list(APPEND CC_EXTERNAL_LIBS ${XR_LIBS}) +endif() + +##### extensions + +cocos_source_files( + extensions/assets-manager/AssetsManagerEx.cpp + extensions/assets-manager/AssetsManagerEx.h + extensions/assets-manager/AsyncTaskPool.cpp + extensions/assets-manager/AsyncTaskPool.h + extensions/assets-manager/EventAssetsManagerEx.cpp + extensions/assets-manager/EventAssetsManagerEx.h + extensions/assets-manager/Manifest.cpp + extensions/assets-manager/Manifest.h + extensions/cocos-ext.h + extensions/ExtensionExport.h + extensions/ExtensionMacros.h +) + +list(APPEND COCOS_SOURCE_LIST ${CC_EXTERNAL_SOURCES}) + +### generate source files + +set(COCOS_DEBUGINFO_SRC + ${CWD}/cocos/core/builtin/DebugInfos.cpp +) + +add_custom_target(builtin-res + COMMAND ${CMAKE_COMMAND} -E echo "Generate builtin resources ..." + COMMAND ${NODE_EXECUTABLE} ${CWD}/cmake/scripts/gen_debugInfos.js + ${CWD}/../EngineErrorMap.md + ${CWD}/cocos/core/builtin/DebugInfos.cpp.in + ${CWD}/cocos/core/builtin/DebugInfos.cpp + DEPENDS + ${CWD}/cocos/core/builtin/DebugInfos.cpp.in + VERBATIM USES_TERMINAL +) +set_target_properties(builtin-res PROPERTIES FOLDER Utils) + + +list(APPEND COCOS_SOURCE_LIST ${COCOS_DEBUGINFO_SRC}) + +if(USE_MODULES) + add_library(ccmath ${ccmath_SOURCE_LIST}) + add_library(cclog ${cclog_SOURCE_LIST}) + add_library(ccfilesystem ${ccfilesystem_SOURCE_LIST} ${CC_TINYDIR_SOURCES}) + add_library(ccutils ${ccutils_SOURCE_LIST} ${CC_UTILS_SOURCES}) + add_library(ccunzip ${ccunzip_SOURCE_LIST} ${CC_UNZIP_SOURCES}) + add_library(ccbindings ${ccbindings_SOURCE_LIST}) + add_library(ccgeometry ${ccgeometry_SOURCE_LIST}) + + target_include_directories(ccmath PRIVATE + ${CWD}/cocos + ) + target_include_directories(ccgeometry PRIVATE + ${CWD}/cocos + ) + target_include_directories(ccunzip PRIVATE + ${CWD}/external/sources/unzip + ${CWD}/external/sources + ${CWD}/cocos + ) + target_include_directories(cclog PRIVATE + ${CWD}/cocos + ) + target_include_directories(ccfilesystem PRIVATE + ${CWD} + ${CWD}/cocos + ${CWD}/external/sources + ) + target_include_directories(ccutils PRIVATE + ${CWD}/cocos + ${CWD}/external/sources + ) + target_include_directories(ccbindings PRIVATE + ${CWD}/cocos + ${CWD}/cocos/bindings/jswrapper + ${CWD}/cocos/editor-support # TODO: refactor include path + ${CWD} + ${CC_EXTERNAL_INCLUDES} + ) + target_link_libraries(ccunzip PUBLIC + ${ZLIB} + ) + target_link_libraries(ccfilesystem PUBLIC + ccunzip + ccutils + cclog + ) + target_link_libraries(ccbindings PUBLIC + ccfilesystem + cclog + ${se_libs_name} + uv + ) + target_link_libraries(ccgeometry PUBLIC + ccmath + ) + if(ANDROID) + target_link_libraries(ccmath PUBLIC + android_platform + ) + target_link_libraries(cclog PUBLIC + log + ) + target_link_libraries(ccfilesystem PUBLIC + cocos_jni + ) + elseif(OPENHARMONY) + + elseif(OHOS) + target_link_libraries(cclog PUBLIC + hilog_ndk.z + ) + target_link_libraries(ccfilesystem PUBLIC + cocos_jni + rawfile.z + ) + elseif(APPLE) + target_link_libraries(ccfilesystem PUBLIC + "-framework Foundation" + "-framework SystemConfiguration" + ) + endif() +else() + list(APPEND COCOS_SOURCE_LIST + ${ccmath_SOURCE_LIST} + ${ccgeometry_SOURCE_LIST} + ${cclog_SOURCE_LIST} + ${ccfilesystem_SOURCE_LIST} + ${ccunzip_SOURCE_LIST} + ${ccbindings_SOURCE_LIST} + ${ccutils_SOURCE_LIST} + ${CC_UNZIP_SOURCES} + ${CC_TINYDIR_SOURCES} + ) +endif() + + +if(QNX) + include(${QNX_PATH}/source.cmake) + qnx_source_list(ccqnx_SOURCE_LIST ${QNX_PATH}) + source_group(TREE ${QNX_PATH} PREFIX "Source Files" FILES ${ccqnx_SOURCE_LIST}}) + list(APPEND COCOS_SOURCE_LIST ${ccqnx_SOURCE_LIST}) +endif() + +add_library(${ENGINE_NAME} ${COCOS_SOURCE_LIST}) + +if(CMAKE_GENERATOR STREQUAL "Xcode") + add_dependencies(${ENGINE_NAME} genbindings) +endif() + + +if(USE_MODULES) + target_link_libraries(${ENGINE_NAME} PUBLIC + ccmath + cclog + ccfilesystem + ccunzip + ccbindings + ccutils + ) +endif() + + +if(ANDROID OR (NOT OPENHARMONY AND OHOS)) + cc_enable_werror("${CC_JNI_SRC_FILES}") + add_library(cocos_jni STATIC ${CC_JNI_SRC_FILES}) + target_include_directories(cocos_jni PUBLIC + ${CWD} + ${CWD}/cocos + ${CC_EXTERNAL_INCLUDES} + ) + if(ANDROID) + target_link_libraries(cocos_jni PUBLIC + android_platform + android + log + ) + # Export GameActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u GameActivity_onCreate") + endif() +endif() + +################################# cc_apply_definations ################################### +function(cc_apply_definations target) + target_compile_definitions(${target} PUBLIC + $,CC_USE_VIDEO=1,CC_USE_VIDEO=0> + $,CC_USE_WEBVIEW=1,CC_USE_WEBVIEW=0> + $,CC_USE_AUDIO=1,CC_USE_AUDIO=0> + $,CC_USE_XR=1,CC_USE_XR=0> + $,CC_USE_XR_REMOTE_PREVIEW=1,CC_USE_XR_REMOTE_PREVIEW=0> + $,CC_USE_SOCKET=1,CC_USE_SOCKET=0> + $,CC_USE_WEBSOCKET_SERVER=1,CC_USE_WEBSOCKET_SERVER=0> + $,CC_USE_EDITBOX=1,CC_USE_EDITBOX=0> + # USE_V8_DEBUGGER = USE_V8_DEBUGGER && (in_debug_mode OR USE_V8_DEBUGGER_FORCEON) + $,$>,$>,USE_V8_DEBUGGER=1,USE_V8_DEBUGGER=0> + $,CC_USE_MIDDLEWARE=1,CC_USE_MIDDLEWARE=0> + $,CC_USE_SPINE=1,CC_USE_SPINE=0> + $,CC_USE_DRAGONBONES=1,CC_USE_DRAGONBONES=0> + $,CC_USE_JOB_SYSTEM_TBB=1,CC_USE_JOB_SYSTEM_TBB=0> + $,CC_USE_JOB_SYSTEM_TASKFLOW=1,CC_USE_JOB_SYSTEM_TASKFLOW=0> + $,CC_USE_PHYSICS_PHYSX=1,CC_USE_PHYSICS_PHYSX=0> + $,CC_USE_AR_MODULE=1,CC_USE_AR_MODULE=0> + $,CC_USE_AR_AUTO=1,CC_USE_AR_AUTO=0> + $,CC_USE_AR_CORE=1,CC_USE_AR_CORE=0> + $,CC_USE_AR_ENGINE=1,CC_USE_AR_ENGINE=0> + $,CC_USE_OCCLUSION_QUERY=1,CC_USE_OCCLUSION_QUERY=0> + $,CC_USE_DEBUG_RENDERER=1,CC_USE_DEBUG_RENDERER=0> + $,CC_USE_GEOMETRY_RENDERER=1,CC_USE_GEOMETRY_RENDERER=0> + $,CC_USE_WEBP=1,CC_USE_WEBP=0> + $,CC_EDITOR=1,CC_EDITOR=0> + $,ENABLE_FLOAT_OUTPUT=1,ENABLE_FLOAT_OUTPUT=0> + $<$:CC_REMOTE_LOG=1> + $<$:SCRIPT_ENGINE_TYPE=1> + $<$:SCRIPT_ENGINE_TYPE=2> + $<$:SCRIPT_ENGINE_TYPE=5> + $<$,$>:CC_DEBUG=1> + ) +endfunction() + +# setup default flags +cc_apply_definations(${ENGINE_NAME}) + +if(USE_MODULES) + cc_apply_definations(ccmath) + cc_apply_definations(cclog) + cc_apply_definations(ccfilesystem) + cc_apply_definations(ccunzip) + cc_apply_definations(ccbindings) + cc_apply_definations(ccutils) +endif() + +if(USE_XR) + cc_xr_apply_definations(${ENGINE_NAME}) + if(USE_MODULES) + cc_xr_apply_definations(ccmath) + cc_xr_apply_definations(cclog) + cc_xr_apply_definations(ccfilesystem) + cc_xr_apply_definations(ccunzip) + cc_xr_apply_definations(ccbindings) + cc_xr_apply_definations(ccutils) + endif() +endif() + +if(MSVC) + set(V8_DIR + ${CWD}/external/win64/libs/v8 + ) + file(GLOB V8_DLLS + ${CWD}/external/win64/libs/v8/Release/*.dll + ) + + file(GLOB WINDOWS_DLLS + ${CWD}/external/win64/libs/*.dll + ${CWD}/external/win64/libs/msvcr/*.dll + ) + target_compile_options(${ENGINE_NAME} PUBLIC /MP + /bigobj + /wd4018 # signed/unsigned mismatch + /wd4221 # no public symbols + /wd4244 # type conversion data lost + /wd4267 # conversion from 'size_t' to 'type', possible loss of data + /wd4305 # truncation from 'type1' to 'type2' + /wd4309 # truncation of constant value + /wd4819 # file contains a character that cannot be represented in the current code page + /wd4996 # deprecated + /wd4098 # defaultlib conflicts + /wd4099 # PDB 'filename' was not found with 'object/library' or at 'path'; linking object as if no debug info + ) + target_link_options(${ENGINE_NAME} PUBLIC + /ignore:4099 # PDB 'filename' was not found with 'object/library' or at 'path'; linking object as if no debug info + /ignore:4098 # defaultlib 'library' conflicts with use of other libs; use /NODEFAULTLIB:library + /ignore:4204 # 'filename' is missing debugging information for referencing module; linking object as if no debug info + ) +endif() + +if(CC_USE_VULKAN) + + target_compile_definitions(${ENGINE_NAME} PUBLIC VK_NO_PROTOTYPES) + target_compile_definitions(${ENGINE_NAME} PUBLIC CC_USE_VULKAN) + + if(WIN32) + target_compile_definitions(${ENGINE_NAME} PUBLIC VK_USE_PLATFORM_WIN32_KHR) + elseif(NX) + target_compile_definitions(${ENGINE_NAME} PUBLIC VK_USE_PLATFORM_VI_NN) + elseif(ANDROID) + target_compile_definitions(${ENGINE_NAME} PUBLIC VK_USE_PLATFORM_ANDROID_KHR) + elseif(IOS) + target_compile_definitions(${ENGINE_NAME} PUBLIC VK_USE_PLATFORM_IOS_MVK) + elseif(MACOSX) + target_compile_definitions(${ENGINE_NAME} PUBLIC VK_USE_PLATFORM_MACOS_MVK) + else() + target_compile_definitions(${ENGINE_NAME} PUBLIC VK_USE_PLATFORM_XCB_KHR) + endif() +endif() + +if(CC_USE_METAL) + target_compile_definitions(${ENGINE_NAME} PUBLIC CC_USE_METAL) +endif() + +if(CC_USE_GLES3) + target_compile_definitions(${ENGINE_NAME} PUBLIC CC_USE_GLES3) +endif() + +if(CC_USE_GLES2) + target_compile_definitions(${ENGINE_NAME} PUBLIC CC_USE_GLES2) +endif() + +target_include_directories(${ENGINE_NAME} + PUBLIC + ${CC_EXTERNAL_INCLUDES} + ${CWD} + ${CWD}/cocos + ${CWD}/cocos/renderer + ${CWD}/cocos/platform + ${CWD}/cocos/renderer/core + ${CWD}/cocos/editor-support + ${SWIG_OUTPUT_ROOT} + ${SWIG_OUTPUT_ROOT}/cocos + $<$:${CWD}/cocos/bindings/jswrapper> + PRIVATE + ${CC_EXTERNAL_PRIVATE_INCLUDES} +) + +target_compile_definitions(${ENGINE_NAME} + PRIVATE + ${CC_EXTERNAL_PRIVATE_DEFINITIONS} +) + +if(NOT APPLE) + target_include_directories(${ENGINE_NAME} PUBLIC + ${CWD}/external/sources/EGL + ) +endif() + +target_include_directories(${ENGINE_NAME} PUBLIC + ${CWD}/external/sources/khronos +) + +if(NX) + include(${CMAKE_CURRENT_LIST_DIR}/platform-nx/setup.cmake) +elseif(WINDOWS) + target_link_libraries(${ENGINE_NAME} PUBLIC + ws2_32 userenv psapi winmm Iphlpapi + ${CC_EXTERNAL_LIBS} + ) + cc_win32_definations(${ENGINE_NAME}) + + if(USE_MODULES) + cc_win32_definations(ccmath) + cc_win32_definations(cclog) + cc_win32_definations(ccunzip) + cc_win32_definations(ccfilesystem) + cc_win32_definations(ccutils) + cc_win32_definations(ccbindings) + + target_link_libraries(ccutils PUBLIC + ws2_32 psapi userenv Iphlpapi + ) + target_link_libraries(ccbindings PUBLIC + ws2_32 + ) + endif() +endif() + +if(ANDROID) + target_compile_options(${ENGINE_NAME} PRIVATE + -Wno-comment # nested block comments are actually useful sometimes + ) + + find_library(LIB_EGL NAMES EGL) + target_link_libraries(${ENGINE_NAME} PUBLIC + android + paddleboat_static + OpenSLES + ${LIB_EGL} + jnigraphics + ${CC_EXTERNAL_LIBS} + ) +endif() + +if(NOT OPENHARMONY AND OHOS) + target_link_libraries(${ENGINE_NAME} PUBLIC + EGL + GLESv3 + zgraphic.z + image_pixelmap.z + rawfile.z + z + OpenSLES + hilog_ndk.z + ${CC_EXTERNAL_LIBS} + ) +endif() + +if(OPENHARMONY) + find_library( # Sets the name of the path variable. + EGL-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + EGL ) + find_library( # Sets the name of the path variable. + GLESv3-lib + # Specifies the name of the NDK library that + # you want CMake to locate. + GLESv3) + + find_library( hilog-lib + hilog_ndk.z ) + + find_library( libace-lib + libace_ndk.z ) + + target_link_libraries(${ENGINE_NAME} PUBLIC + ${EGL-lib} ${GLESv3-lib} ${hilog-lib} libace_napi.z.so libace_ndk.z.so libz.so libuv.so libnative_drawing.so librawfile.z.so libOpenSLES.so + ${CC_EXTERNAL_LIBS} + ) +endif() + +if(APPLE) + + target_compile_options(${ENGINE_NAME} PRIVATE + -Wno-objc-method-access + ) + + target_include_directories(${ENGINE_NAME} PUBLIC + ${CWD}/cocos/platform/ios + ) + target_link_libraries(${ENGINE_NAME} PUBLIC + "-liconv" + "-framework AudioToolbox" + "-framework Foundation" + "-framework OpenAL" + "-framework GameController" + "-framework Metal" + "-framework MetalKit" + "-framework QuartzCore" + "-framework MetalPerformanceShaders" + "-lsqlite3" + "-framework Security" + "-framework SystemConfiguration" + "$(inherited)" + ${CC_EXTERNAL_LIBS} + ) + set_target_properties(${ENGINE_NAME} PROPERTIES + XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH "YES" + OSX_ARCHITECTURES "arm64;x86_64" + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/archives + ) + if(MACOSX) + target_link_libraries(${ENGINE_NAME} PUBLIC + "-framework OpenGL" + "-framework AppKit" + ) + target_compile_definitions(${ENGINE_NAME} PUBLIC + CC_KEYBOARD_SUPPORT + ) + elseif(IOS) + + if("${XCODE_VERSION}" VERSION_GREATER_EQUAL "15.0") + # For Xcode 15 and newer, add extra link flags to fix crash in __cxx_global_var_init. + message(STATUS "Using Xcode 15 or newer, adding extra link flags: -Wl,-ld_classic.") + target_link_options(${ENGINE_NAME} PUBLIC -Wl,-ld_classic) + endif() + + target_link_libraries(${ENGINE_NAME} PUBLIC + "-framework QuartzCore" + "-framework MetalPerformanceShaders" + "-framework UIKit" + "-framework AVKit" + "-framework WebKit" + "-framework CoreVideo" + "-framework CoreMotion" + "-framework CFNetwork" + "-framework CoreMedia" + "-framework CoreText" + "-framework CoreGraphics" + "-framework AVFoundation" + "-lz" + "-framework OpenGLES" + "-framework JavaScriptCore" + ) + set_property(TARGET ${ENGINE_NAME} PROPERTY XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${TARGET_IOS_VERSION}) + set_property(TARGET ${ENGINE_NAME} PROPERTY XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") + endif() + +endif() + +if(QNX) + include(${QNX_PATH}/setup.cmake) +elseif(LINUX) + set(LINUX_GRAPHIC_LIBS "") + if(NOT USE_SERVER_MODE) + list(APPEND LINUX_GRAPHIC_LIBS X11) + endif() + target_link_libraries(${ENGINE_NAME} PUBLIC + ${LINUX_GRAPHIC_LIBS} + ${CC_EXTERNAL_LIBS} + ) +endif() + +### set source_group for generated files +set(COCOS_SOURCE_LIST_EXCLUDE_GENRATED ${COCOS_SOURCE_LIST}) +set(COCOS_GENERATED_LIST) +foreach(src IN LISTS COCOS_SOURCE_LIST_EXCLUDE_GENRATED) + get_source_file_property(IS_GENERATED ${src} GENERATED) + if(IS_GENERATED) + list(REMOVE_ITEM COCOS_SOURCE_LIST_EXCLUDE_GENRATED ${src}) + list(APPEND COCOS_GENERATED_LIST ${src}) + endif() +endforeach() + +source_group(TREE ${SWIG_OUTPUT_ROOT} PREFIX "Source Files/generated" FILES ${COCOS_GENERATED_LIST}) + +add_custom_target(genbindings + DEPENDS ${COCOS_GENERATED_LIST} +) +set_target_properties(genbindings PROPERTIES FOLDER Utils) + +if(USE_BUILTIN_EXTERNAL) + set(EXCLUDE_EXTERANL_LIST ${COCOS_SOURCE_LIST_EXCLUDE_GENRATED}) + foreach(x IN LISTS CC_EXTERNAL_SOURCES) + list(REMOVE_ITEM EXCLUDE_EXTERANL_LIST ${x}) + endforeach() + if(USE_XR OR USE_AR_MODULE) + foreach(x IN LISTS XR_COMMON_SOURCES) + list(REMOVE_ITEM EXCLUDE_EXTERANL_LIST ${x}) + endforeach() + endif() + source_group(TREE ${CWD} PREFIX "Source Files" FILES ${EXCLUDE_EXTERANL_LIST}) + source_group(TREE ${EXTERNAL_ROOT} PREFIX "Source Files/external" FILES ${CC_EXTERNAL_SOURCES}) + if(USE_XR OR USE_AR_MODULE) + source_group(TREE ${XR_COMMON_PATH} PREFIX "Source Files/common" FILES ${XR_COMMON_SOURCES}) + endif() +else() + if(USE_XR OR USE_AR_MODULE) + set(EXCLUDE_XR_LIST ${COCOS_SOURCE_LIST_EXCLUDE_GENRATED}) + foreach(x IN LISTS XR_COMMON_SOURCES) + list(REMOVE_ITEM EXCLUDE_XR_LIST ${x}) + endforeach() + source_group(TREE ${CWD} PREFIX "Source Files" FILES ${EXCLUDE_XR_LIST}) + source_group(TREE ${XR_COMMON_PATH} PREFIX "Source Files/common" FILES ${XR_COMMON_SOURCES}) + else() + source_group(TREE ${CWD} PREFIX "Source Files" FILES ${COCOS_SOURCE_LIST_EXCLUDE_GENRATED}) + endif() +endif() diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c9d682 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +cocos-engine native part +========================== + +It is the native backend of [Cocos Creator](https://www.cocos.com/en/creator). It works on `iOS`, `Android`, `macOS` and `Windows`. + +Coding format and coding style +--------------------------------- + +The coding format file is `.clang-format`, and the coding style format file is `.clang-tidy`. Please use [clang-format](https://clang.llvm.org/docs/ClangFormat.html) to format the codes and use [clang-tidy](http://clang.llvm.org/extra/index.html) to fix the coding style before committing codes. See the [linter auto-fix guide](docs/LINTER_AUTOFIX_GUIDE.md) for more information. + + +Build Requirements +-------------------------------- +- Xcode 11.5+ to build mac games +- or Visual Studio 2017 15.7+ / Visual Studio 2019 to build win64 games +- NDK 21-22 is required to build Android games (23+ is not supported) +- Cmake 3.8+ is required + +System Requirements +-------------------------------- +- macOS 10.14+ +- iOS 11.0+ +- iOS Simulator 13.0+ +- 64-bit Windows 7+ + - with vulkan 1.0 to 1.2 if want to run with vulkan +- Android 4.4+ + - Android 7+ if want to run with vulkan + +C++ related +-------------------------------- +Use C++17. + +These C++17 features are tested and supported by all platforms + - `std::string_view` + - [`constexpr if`](https://www.codingame.com/playgrounds/2205/7-features-of-c17-that-will-simplify-your-code/constexpr-if) + +The following features are not supported + - `std::optional` is not supported by iOS 11. + +Other C++17 features are not tested. \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..a9b1cfc --- /dev/null +++ b/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +mkdir -p build +cp CMakeLists.header.txt build/CMakeLists.txt +cmake -Sbuild -Bbuild -DCC_USE_GLES2=OFF -DCC_USE_VULKAN=OFF -DCC_USE_GLES3=OFF -DCC_USE_METAL=ON \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DUSE_PHYSICS_PHYSX=ON \ + -DCMAKE_OSX_SYSROOT=iphoneos \ + -DCMAKE_SYSTEM_NAME=iOS \ + -GXcode +if [[ -f build/compile_commands.json ]]; then + cp build/compile_commands.json . +fi diff --git a/cmake/predefine.cmake b/cmake/predefine.cmake new file mode 100644 index 0000000..86a15c5 --- /dev/null +++ b/cmake/predefine.cmake @@ -0,0 +1,392 @@ + +set(CC_PLATFORM_IOS 1) +set(CC_PLATFORM_WINDOWS 2) +set(CC_PLATFORM_ANDROID 3) +set(CC_PLATFORM_MACOS 4) +set(CC_PLATFORM_OHOS 5) +set(CC_PLATFORM_LINUX 6) +set(CC_PLATFORM_QNX 7) +set(CC_PLATFORM_NX 8) +set(CC_PLATFORM_EMSCRIPTEN 9) +set(CC_PLATFORM_OPENHARMONY 10) +set(CC_PLATFORM 1) + +if(NX) + if(NOT DEFINED ENV{NINTENDO_SDK_ROOT}) + message(FATAL_ERROR "Nintendo SDK not found") + return() + endif() + if(NOT IS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/../platform-nx) + message(FATAL_ERROR "platform adaptation package not found") + return() + endif() + if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Windows") + message(FATAL_ERROR "Only windows environment is supported") + return() + endif() + + if (CC_NX_WINDOWS) # windows reference + set(WINDOWS TRUE) + set(CC_PLATFORM ${CC_PLATFORM_WINDOWS}) + else() + set(CC_PLATFORM ${CC_PLATFORM_NX}) + endif() +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set(WINDOWS TRUE) + set(PLATFORM_FOLDER windows) + set(CC_PLATFORM ${CC_PLATFORM_WINDOWS}) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Android") + set(PLATFORM_FOLDER android) + set(ANDROID TRUE) + set(CC_PLATFORM ${CC_PLATFORM_ANDROID}) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(APPLE TRUE) + set(MACOSX TRUE) + set(PLATFORM_FOLDER mac) + set(CC_PLATFORM ${CC_PLATFORM_MACOS}) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(LINUX TRUE) + set(PLATFORM_FOLDER linux) + set(CC_PLATFORM ${CC_PLATFORM_LINUX}) + add_definitions(-D__LINUX__=1) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "iOS") + set(APPLE TRUE) + set(IOS TRUE) + set(PLATFORM_FOLDER ios) + set(CC_PLATFORM ${CC_PLATFORM_IOS}) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "QNX") + if(NOT IS_DIRECTORY ${QNX_PATH}) + message(FATAL_ERROR "platform adaptation package not found") + return() + endif() + set(QNX TRUE) + set(PLATFORM_FOLDER qnx) + set(CC_PLATFORM ${CC_PLATFORM_QNX}) + add_definitions(-D__QNX__=1) +elseif(OPENHARMONY) + set(OPENHARMONY TRUE) + set(CC_PLATFORM ${CC_PLATFORM_OPENHARMONY}) + add_definitions(-D__OPENHARMONY__=1) + set(PLATFORM_FOLDER openharmony) + set(CMAKE_CXX_FLAGS "-fvisibility=hidden -fvisibility-inlines-hidden ${CMAKE_CXX_FLAGS}") + if("${OHOS_ARCH}" STREQUAL "armeabi-v7a") + set(CMAKE_CXX_FLAGS "-march=armv7a ${CMAKE_CXX_FLAGS}") + endif() +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") + set(CC_WGPU_WASM TRUE) + set(CC_PLATFORM ${CC_PLATFORM_EMSCRIPTEN}) + set(EMSCRIPTEN TRUE) + add_definitions(-DCC_WGPU_WASM=1) +elseif(OHOS) + set(OHOS TRUE) + set(CC_PLATFORM ${CC_PLATFORM_OHOS}) + add_definitions(-D__OHOS__=1) + set(PLATFORM_FOLDER ohos) +else() + message(FATAL_ERROR "Unsupported platform '${CMAKE_SYSTEM_NAME}', CMake will exit!") + return() +endif() + +MESSAGE(STATUS "platform: ${CMAKE_SYSTEM_NAME}") + +# platform macros +add_definitions(-DCC_PLATFORM_WINDOWS=${CC_PLATFORM_WINDOWS}) +add_definitions(-DCC_PLATFORM_MACOS=${CC_PLATFORM_MACOS}) +add_definitions(-DCC_PLATFORM_IOS=${CC_PLATFORM_IOS}) +add_definitions(-DCC_PLATFORM_MAC_OSX=${CC_PLATFORM_MACOS}) # keep compatible +add_definitions(-DCC_PLATFORM_MAC_IOS=${CC_PLATFORM_IOS}) # keep compatible +add_definitions(-DCC_PLATFORM_ANDROID=${CC_PLATFORM_ANDROID}) +add_definitions(-DCC_PLATFORM_OHOS=${CC_PLATFORM_OHOS}) +add_definitions(-DCC_PLATFORM_LINUX=${CC_PLATFORM_LINUX}) +add_definitions(-DCC_PLATFORM_QNX=${CC_PLATFORM_QNX}) +add_definitions(-DCC_PLATFORM_NX=${CC_PLATFORM_NX}) +add_definitions(-DCC_PLATFORM_OPENHARMONY=${CC_PLATFORM_OPENHARMONY}) +add_definitions(-DCC_PLATFORM_EMSCRIPTEN=${CC_PLATFORM_EMSCRIPTEN}) +add_definitions(-DCC_PLATFORM=${CC_PLATFORM}) + + +# simplify generator condition, please use them everywhere +if(CMAKE_GENERATOR STREQUAL Xcode) + set(XCODE TRUE) +elseif(CMAKE_GENERATOR MATCHES Visual) + set(VS TRUE) +endif() + +# generators that are capable of organizing into a hierarchy of folders +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# set c++ standard +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if("$ENV{COCOS_ENGINE_DEV}" EQUAL "1") + set(WERROR_FLAGS "-Werror -Werror=return-type") # -Wshorten-64-to-32 -Werror=return-type + + if(APPLE) + set(WERROR_FLAGS " ${WERROR_FLAGS} -Wno-deprecated-declarations") + elseif(LINUX) + set(WERROR_FLAGS " ${WERROR_FLAGS} -Wno-nullability-completeness -Wno-deprecated-declarations") + elseif(ANDROID) + set(WERROR_FLAGS " ${WERROR_FLAGS} -Wno-deprecated-declarations -Wno-unknown-warning-option -Wno-deprecated-builtins") + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(WERROR_FLAGS " ${WERROR_FLAGS} -Wno-invalid-offsetof") + endif() + + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + set(WERROR_FLAGS "/WX") + endif() + message(STATUS "Enable NO_WERROR") +else() + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + set(WERROR_FLAGS "") + else() + set(WERROR_FLAGS "-Werror=return-type") + endif() + message(STATUS "Ignore NO_WERROR") +endif() + +if(ANDROID) + if("${ANDROID_ABI}" STREQUAL "armeabi-v7a") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfpu=neon-fp16") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpu=neon-fp16") + endif() + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsigned-char -ffunction-sections -fdata-sections -fstrict-aliasing") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsigned-char -ffunction-sections -fdata-sections -fstrict-aliasing -frtti -fexceptions") + + + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=default -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=default -fno-omit-frame-pointer") + else() + if(NOT DEFINED HIDE_SYMBOLS OR HIDE_SYMBOLS) # hidden by default + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden") + endif() + endif() +endif() + +function(cc_enable_werror source_list) + foreach(src IN LISTS source_list) + if("${src}" MATCHES "\\.(cpp|mm|c|m)\$") + set_source_files_properties("${src}" PROPERTIES + COMPILE_FLAGS "${WERROR_FLAGS}" + ) + endif() + endforeach() +endfunction() + +################################# cc_set_if_undefined ################################### +macro(cc_set_if_undefined varname value) + if(NOT DEFINED ${varname}) + set(${varname} ${value}) + endif() +endmacro() + + +################################# cocos_source_files ################################### +macro(cocos_source_files) + set(list_var "${ARGN}") + set(TWAE ON) + set(ACCEPT_MN OFF) + set(MODULE_NAME "COCOS") + set(NO_UBUILD OFF) + foreach(src IN LISTS list_var) + if(ACCEPT_MN) + set(MODULE_NAME "${src}") + set(ACCEPT_MN OFF) + elseif("NO_WERROR" STREQUAL "${src}") + set(TWAE OFF) + elseif("MODULE" STREQUAL "${src}") + set(ACCEPT_MN ON) + elseif("NO_UBUILD" STREQUAL "${src}") + set(NO_UBUILD ON) + else() + if(IS_ABSOLUTE "${src}") + set(fp "${src}") + else() + set(fp "${CWD}/${src}") + endif() + get_source_file_property(IS_GENERATED ${fp} GENERATED) + if(EXISTS ${fp} OR ${IS_GENERATED}) + if("${fp}" MATCHES "\\.(cpp|mm|c|m)\$" AND TWAE) + set_source_files_properties("${fp}" PROPERTIES + COMPILE_FLAGS "${WERROR_FLAGS}" + ) + endif() + if("${fp}" MATCHES "\\.(cpp|mm|c|m)\$" AND NO_UBUILD) + set_source_files_properties("${fp}" PROPERTIES + SKIP_UNITY_BUILD_INCLUSION ON + ) + endif() + list(APPEND ${MODULE_NAME}_SOURCE_LIST "${fp}") + else() + message(FATAL_ERROR "Cocos souce file not exists: \"${src}\", is generated ${IS_GENERATED}") + endif() + set(TWAE ON) + set(NO_UBUILD OFF) + endif() + endforeach() +endmacro() + +################################# inspect_values ################################### +function(cc_inspect_values) + set(list_var "${ARGN}") + foreach(src IN LISTS list_var) + set(opv ${${src}}) + message(STATUS "OPTION ${src}:\t${opv}") + endforeach() +endfunction() + +function(cc_parse_cfg_include_files cfg_file output_var) + file(STRINGS ${cfg_file} my_lines) + set(include_pattern "^%include *\"([^\"]*)\" *$") + set(include_files "") + foreach(line ${my_lines}) + if(line MATCHES ${include_pattern}) + # keep syncing with SWIG_ARGS + set(include_file ${CMAKE_CURRENT_LIST_DIR}/cocos/${CMAKE_MATCH_1}) + set(include_file2 ${CMAKE_CURRENT_LIST_DIR}/${CMAKE_MATCH_1}) + if(EXISTS ${include_file}) + list(APPEND include_files ${include_file}) + elseif(EXISTS ${include_file2}) + list(APPEND include_files ${include_file2}) + else() + message(FATAL_ERROR "%include ${include_file}: file not found") + endif() + endif() + endforeach() + set(${output_var} ${include_files} PARENT_SCOPE) +endfunction() + +function(cc_gen_swig_files cfg_directory output_dir) + file(MAKE_DIRECTORY "${output_dir}") + if(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Windows") + set(SWIG_DIR ${EXTERNAL_ROOT}/win64/bin/swig) + set(SWIG_EXEC ${SWIG_DIR}/bin/swig.exe) + elseif(${CMAKE_HOST_SYSTEM_NAME} MATCHES "Darwin") + set(SWIG_DIR ${EXTERNAL_ROOT}/mac/bin/swig) + set(SWIG_EXEC ${SWIG_DIR}/bin/swig) + elseif(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Linux") + set(SWIG_DIR ${EXTERNAL_ROOT}/linux/bin/swig) + set(SWIG_EXEC ${SWIG_DIR}/bin/swig) + else() + message(FATAL_ERROR "swig is not supported on current platform!") + endif() + + set(SWIG_ARGS + -c++ + -cocos + -fvirtual + -noexcept + -cpperraswarn + -D__clang__ + -Dfinal= + -DCC_PLATFORM=3 + -Dconstexpr=const + -DCC_PLATFORM_ANDROID=3 + -I${SWIG_DIR}/share/swig/4.1.0/javascript/cocos + -I${SWIG_DIR}/share/swig/4.1.0 + -I${CMAKE_CURRENT_LIST_DIR}/ + -I${CMAKE_CURRENT_LIST_DIR}/cocos + -o + ) + file(GLOB cfg_files ${cfg_directory}/*.i) + + list(FILTER cfg_files EXCLUDE REGEX ".*template.*") + foreach(cfg ${cfg_files}) + + set(dep_files) + get_filename_component(mod_name ${cfg} NAME_WE) + set(output_file_tmp ${output_dir}/temp/jsb_${mod_name}_auto.cpp) + set(output_file ${output_dir}/jsb_${mod_name}_auto.cpp) + + set(output_hfile_tmp ${output_dir}/temp/jsb_${mod_name}_auto.h) + set(output_hfile ${output_dir}/jsb_${mod_name}_auto.h) + + cc_parse_cfg_include_files(${cfg} dep_files) + + add_custom_command( + OUTPUT + ${output_hfile} + ${output_file} + COMMAND ${CMAKE_COMMAND} -E echo "Running swig with config file ${cfg} ..." + COMMAND ${CMAKE_COMMAND} -E make_directory ${output_dir}/temp + COMMAND ${SWIG_EXEC} ${SWIG_ARGS} + ${output_file_tmp} + ${cfg} + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${output_file_tmp} ${output_file} + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${output_hfile_tmp} ${output_hfile} + DEPENDS ${cfg} ${dep_files} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/.. + ) + set_source_files_properties(${output_file} + PROPERTIES + GENERATED TRUE + LOCATION ${output_file} + ) + get_source_file_property(IS_GENERATED ${output_file} GENERATED) + + endforeach() +endfunction(cc_gen_swig_files) + +function(cc_set_target_property target_name property value) + set_target_properties(${target_name} PROPERTIES CC_${property} ${value}) +endfunction() + +function(cc_get_target_property output target_name property) + get_target_property(output ${target_name} ${property}) +endfunction() + +function(cc_redirect_property target from_property to_property) + cc_get_target_property(output ${target} ${from_property}) + if(output) + set_target_properties(${target_name} PROPERTIES + ${to_property} ${output} + ) + endif() +endfunction() + +if(NOT DEFINED NODE_EXECUTABLE) + if(DEFINED EDITOR_NODEJS) + set(NODE_EXECUTABLE ${EDITOR_NODEJS}) + elseif(DEFINED ENV{NODE_EXECUTABLE}) + set(NODE_EXECUTABLE $ENV{NODE_EXECUTABLE}) + message(STATUS "set NODE_EXECUTABLE by env") + else() + find_program(NODE_EXECUTABLE NAMES node) + endif() +endif() +if(NOT DEFINED TSC_EXECUTABLE) + find_program(TSC_EXECUTABLE NAMES tsc) +endif() +if(NOT DEFINED CCACHE_EXECUTABLE) + find_program(CCACHE_EXECUTABLE NAMES ccache) +endif() + +## predefined configurations for game applications +include(${CMAKE_CURRENT_LIST_DIR}/../templates/cmake/common.cmake) +if(APPLE) + include(${CMAKE_CURRENT_LIST_DIR}/../templates/cmake/apple.cmake) +elseif(WINDOWS) + include(${CMAKE_CURRENT_LIST_DIR}/../emplates/cmake/windows.cmake) +elseif(LINUX) + include(${CMAKE_CURRENT_LIST_DIR}/../templates/cmake/linux.cmake) +elseif(ANDROID) + include(${CMAKE_CURRENT_LIST_DIR}/../templates/cmake/android.cmake) +elseif(OPENHARMONY) + include(${CMAKE_CURRENT_LIST_DIR}/../templates/cmake/openharmony.cmake) +elseif(OHOS) + include(${CMAKE_CURRENT_LIST_DIR}/../templates/cmake/ohos.cmake) +elseif(QNX) +elseif(EMSCRIPTEN) +else() + message(FATAL_ERROR "Unhandled platform specified cmake utils!") +endif() diff --git a/cmake/scripts/engine-version.js b/cmake/scripts/engine-version.js new file mode 100644 index 0000000..5095e0f --- /dev/null +++ b/cmake/scripts/engine-version.js @@ -0,0 +1,96 @@ +const fs = require('fs'); +const path = require('path'); + + +const pkgCfg = path.join(__dirname, '../../../package.json'); + +if (!fs.existsSync(pkgCfg)) { + console.error(`Can not find package.json: ${pkgCfg}`); + process.exit(1); +} + +let pkgJson = {}; +try { + pkgJson = JSON.parse(fs.readFileSync(pkgCfg, 'utf8')); +} catch (err) { + console.error(`Error parsing ${pkgCfg}`); + console.error(err); + process.exit(1); +} + +if (!pkgJson.version) { + console.error(`Can not find field 'version' in file ${pkgCfg}`); + process.exit(1); +} + +const version_regex = /(\d+)\.(\d+)(\.(\d+))?(-(\w+))?/; +const version_str = pkgJson.version; +const version_result = version_str.match(version_regex); + +if (!version_result) { + console.error(`Failed to parse version string: '${version_str}'`); + process.exit(1); +} +const majorVersion = parseInt(version_result[1]); +const minorVersion = parseInt(version_result[2]); +const patchVersion = version_result[4] ? parseInt(version_result[4]) : 0; +const preRelease = version_result[6] ? version_result[6] : 'release'; +const versionIntValue = majorVersion * 10000 + minorVersion * 100 + patchVersion; +const fullYear = (new Date).getFullYear(); +const version_header = `/**************************************************************************** +Copyright (c) ${fullYear <= 2022 ? "2022" : "2022-"+fullYear} Xiamen Yaji Software Co., Ltd. + +http://www.cocos.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +#pragma once + +#define COCOS_MAJOR_VERSION ${majorVersion} +#define COCOS_MINJOR_VERSION ${minorVersion} +#define COCOS_PATCH_VERSION ${patchVersion} +#define COCOS_VERSION_STRING "${majorVersion}.${minorVersion}.${patchVersion}" +#define COCOS_VERSION_DEFINED 1 +#define COCOS_VERSION ${versionIntValue} + +// #define COCOS_PRE_RELEASE "${preRelease}" +`; + +const outputVersionFile = path.join(__dirname, '../../cocos/cocos-version.h'); + +if(!fs.existsSync(outputVersionFile) || !compareFileContent(outputVersionFile, version_header)) { + console.log(`Update cocos-version.h to ${version_str}`); + fs.writeFileSync(outputVersionFile, version_header); +} else { + console.log(`cocos-version.h is up to date`); +} + +function compareFileContent(file, content) { + const srcLines = fs.readFileSync(file, 'utf8').split('\n').map(x=>x.trim()); + const dstLines = content.split('\n').map(x => x.trim()); + if(srcLines.length !== dstLines.length) { + return false; + } + for(let i = 0, l = srcLines.length; i < l; i++) { + if(srcLines[i] != dstLines[i]) { + return false; + } + } + return true; +} diff --git a/cmake/scripts/gen_debugInfos.js b/cmake/scripts/gen_debugInfos.js new file mode 100644 index 0000000..9963fda --- /dev/null +++ b/cmake/scripts/gen_debugInfos.js @@ -0,0 +1,47 @@ +const Fs = require('fs'); +const process = require('process'); +const writeIfDifferent = require('./utils').writeIfDifferent; + +if (process.argv.length !== 5) { + console.error('bad argument'); + console.error(' - input file'); + console.error(' - template file'); + console.error(' - output file'); + process.exit(-1); +} + +const inputFile = process.argv[2]; +const template = process.argv[3]; +const outputFile = process.argv[4]; + +let buildDebugInfos = function() { + let readContent = Fs.readFileSync(inputFile, 'utf-8'); + let titleRegExp = /### \d+/g; + let debugInfos = ""; + + let result1 = titleRegExp.exec(readContent); + while (result1) { + let result2 = titleRegExp.exec(readContent); + let errInfoHead = result1.index + result1[0].length; + let errInfoTail = result2? result2.index: readContent.length; + + let errCode = /\d+/.exec(result1[0])[0]; + let errInfo = readContent.slice(errInfoHead, errInfoTail); + errInfo = errInfo.replace(/```/g, ' '); + errInfo = errInfo.trim(); + errInfo = errInfo.replace(/\r\n/g, '\n'); + + if (!errInfo.includes('')) { + errInfo = errInfo.replace(/\n/g, "\\n").replace(/\"/g, "'").replace(/\\`/g, "`"); + debugInfos = debugInfos.concat("{ ", errCode, ", \"", errInfo, "\" },\n"); + } + + result1 = result2; + } + + let replaceData = Fs.readFileSync(template).toString('utf-8').replace("${PLACE_HOLDER}", debugInfos); + writeIfDifferent(outputFile, replaceData, { encoding: 'utf-8' }); +}; + +buildDebugInfos(); +process.exit(0); diff --git a/cmake/scripts/plugin_support/plugin_cfg.js b/cmake/scripts/plugin_support/plugin_cfg.js new file mode 100644 index 0000000..e1b3a86 --- /dev/null +++ b/cmake/scripts/plugin_support/plugin_cfg.js @@ -0,0 +1,846 @@ +/* + * Generated by PEG.js 0.10.0. + * + * http://pegjs.org/ + */ + +"use strict"; + +function peg$subclass(child, parent) { + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); +} + +function peg$SyntaxError(message, expected, found, location) { + this.message = message; + this.expected = expected; + this.found = found; + this.location = location; + this.name = "SyntaxError"; + + if (typeof Error.captureStackTrace === "function") { + Error.captureStackTrace(this, peg$SyntaxError); + } +} + +peg$subclass(peg$SyntaxError, Error); + +peg$SyntaxError.buildMessage = function(expected, found) { + var DESCRIBE_EXPECTATION_FNS = { + literal: function(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + "class": function(expectation) { + var escapedParts = "", + i; + + for (i = 0; i < expectation.parts.length; i++) { + escapedParts += expectation.parts[i] instanceof Array + ? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1]) + : classEscape(expectation.parts[i]); + } + + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + }, + + any: function(expectation) { + return "any character"; + }, + + end: function(expectation) { + return "end of input"; + }, + + other: function(expectation) { + return expectation.description; + } + }; + + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + + function literalEscape(s) { + return s + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\0/g, '\\0') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); + } + + function classEscape(s) { + return s + .replace(/\\/g, '\\\\') + .replace(/\]/g, '\\]') + .replace(/\^/g, '\\^') + .replace(/-/g, '\\-') + .replace(/\0/g, '\\0') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); + } + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + var descriptions = new Array(expected.length), + i, j; + + for (i = 0; i < expected.length; i++) { + descriptions[i] = describeExpectation(expected[i]); + } + + descriptions.sort(); + + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; +}; + +function peg$parse(input, options) { + options = options !== void 0 ? options : {}; + + var peg$FAILED = {}, + + peg$startRuleIndices = { Expression: 0 }, + peg$startRuleIndex = 0, + + peg$consts = [ + "||", + peg$literalExpectation("||", false), + function(head, tail) { + let s = [head]; + s = s.concat(tail.map(x=>x[1])); + return new VersionSet(s); + }, + peg$otherExpectation("Condition Items"), + function(head, tail) { + let s = [head]; + s = s.concat(tail.map(x=> x[1])); + return s; + }, + function(head) { + return [head]; + }, + ">=", + peg$literalExpectation(">=", false), + function(v) { + v.op = OP_GE; + return v; + }, + "<=", + peg$literalExpectation("<=", false), + function(v) { + v.op = OP_LE; + return v; + }, + "!=", + peg$literalExpectation("!=", false), + function(v) { + v.op = OP_NOT; + return v; + }, + "!", + peg$literalExpectation("!", false), + "=", + peg$literalExpectation("=", false), + function(v) { + v.op = OP_EQ; + return v; + }, + "<", + peg$literalExpectation("<", false), + function(v) { + v.op = OP_LT; + return v; + }, + ">", + peg$literalExpectation(">", false), + function(v) { + v.op = OP_GT; + return v; + }, + function(head) { + head.op = OP_IS; + return head; + }, + peg$otherExpectation("version"), + peg$otherExpectation("major.minor.patch"), + ".", + peg$literalExpectation(".", false), + function(v, n) { v.patch = n; return v}, + peg$otherExpectation("major.minor"), + function(v, n) {v.minor = n; return v}, + peg$otherExpectation("major"), + function(n) {let v = new Version; v.major = n; return v}, + "*", + peg$literalExpectation("*", false), + "x", + peg$literalExpectation("x", false), + "X", + peg$literalExpectation("X", false), + function() {return '*';}, + peg$otherExpectation("integer"), + /^[0-9]/, + peg$classExpectation([["0", "9"]], false, false), + function() { return parseInt(text(), 10); }, + peg$otherExpectation("whitespace"), + /^[ \t\n\r]/, + peg$classExpectation([" ", "\t", "\n", "\r"], false, false) + ], + + peg$bytecode = [ + peg$decode("%;!/k#$%2 \"\"6 7!/,#;!/#$+\")(\"'#&'#0<*%2 \"\"6 7!/,#;!/#$+\")(\"'#&'#&/)$8\":\"\"\"! )(\"'#&'#"), + peg$decode("<%;*/w#;\"/n$$%;)/,#;\"/#$+\")(\"'#&'#/9#06*%;)/,#;\"/#$+\")(\"'#&'#&&&#/2$;*/)$8$:$$\"\"!)($'#(#'#(\"'#&'#.D &%;*/:#;\"/1$;*/($8#:%#!!)(#'#(\"'#&'#=.\" 7#"), + peg$decode("%2&\"\"6&7'/:#;*/1$;#/($8#:(#! )(#'#(\"'#&'#.\u013D &%2)\"\"6)7*/:#;*/1$;#/($8#:+#! )(#'#(\"'#&'#.\u0110 &%2,\"\"6,7-/:#;*/1$;#/($8#:.#! )(#'#(\"'#&'#.\xE3 &%2/\"\"6/70/:#;*/1$;#/($8#:.#! )(#'#(\"'#&'#.\xB6 &%21\"\"6172/:#;*/1$;#/($8#:3#! )(#'#(\"'#&'#.\x89 &%24\"\"6475/:#;*/1$;#/($8#:6#! )(#'#(\"'#&'#.\\ &%27\"\"6778/:#;*/1$;#/($8#:9#! )(#'#(\"'#&'#./ &%;#/' 8!::!! )"), + peg$decode("<;$.) &;%.# &;&=.\" 7;"), + peg$decode("<%;%/A#2=\"\"6=7>/2$;'/)$8#:?#\"\" )(#'#(\"'#&'#=.\" 7<"), + peg$decode("<%;&/A#2=\"\"6=7>/2$;'/)$8#:A#\"\" )(#'#(\"'#&'#=.\" 7@"), + peg$decode("<%;'/' 8!:C!! )=.\" 7B"), + peg$decode("%2D\"\"6D7E.5 &2F\"\"6F7G.) &2H\"\"6H7I/& 8!:J! ).# &;("), + peg$decode("<%$4L\"\"5!7M/,#0)*4L\"\"5!7M&&&#/& 8!:N! )=.\" 7K"), + peg$decode("<$4P\"\"5!7Q/,#0)*4P\"\"5!7Q&&&#=.\" 7O"), + peg$decode("<$4P\"\"5!7Q0)*4P\"\"5!7Q&=.\" 7O") + ], + + peg$currPos = 0, + peg$savedPos = 0, + peg$posDetailsCache = [{ line: 1, column: 1 }], + peg$maxFailPos = 0, + peg$maxFailExpected = [], + peg$silentFails = 0, + + peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleIndices)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleIndex = peg$startRuleIndices[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) + + throw peg$buildSimpleError(message, location); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text: text, ignoreCase: ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase) { + return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description: description }; + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos], p; + + if (details) { + return details; + } else { + p = pos - 1; + while (!peg$posDetailsCache[p]) { + p--; + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + return details; + } + } + + function peg$computeLocation(startPos, endPos) { + var startPosDetails = peg$computePosDetails(startPos), + endPosDetails = peg$computePosDetails(endPos); + + return { + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$decode(s) { + var bc = new Array(s.length), i; + + for (i = 0; i < s.length; i++) { + bc[i] = s.charCodeAt(i) - 32; + } + + return bc; + } + + function peg$parseRule(index) { + var bc = peg$bytecode[index], + ip = 0, + ips = [], + end = bc.length, + ends = [], + stack = [], + params, i; + + while (true) { + while (ip < end) { + switch (bc[ip]) { + case 0: + stack.push(peg$consts[bc[ip + 1]]); + ip += 2; + break; + + case 1: + stack.push(void 0); + ip++; + break; + + case 2: + stack.push(null); + ip++; + break; + + case 3: + stack.push(peg$FAILED); + ip++; + break; + + case 4: + stack.push([]); + ip++; + break; + + case 5: + stack.push(peg$currPos); + ip++; + break; + + case 6: + stack.pop(); + ip++; + break; + + case 7: + peg$currPos = stack.pop(); + ip++; + break; + + case 8: + stack.length -= bc[ip + 1]; + ip += 2; + break; + + case 9: + stack.splice(-2, 1); + ip++; + break; + + case 10: + stack[stack.length - 2].push(stack.pop()); + ip++; + break; + + case 11: + stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1])); + ip += 2; + break; + + case 12: + stack.push(input.substring(stack.pop(), peg$currPos)); + ip++; + break; + + case 13: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + + if (stack[stack.length - 1]) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + + break; + + case 14: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + + if (stack[stack.length - 1] === peg$FAILED) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + + break; + + case 15: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + + if (stack[stack.length - 1] !== peg$FAILED) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + + break; + + case 16: + if (stack[stack.length - 1] !== peg$FAILED) { + ends.push(end); + ips.push(ip); + + end = ip + 2 + bc[ip + 1]; + ip += 2; + } else { + ip += 2 + bc[ip + 1]; + } + + break; + + case 17: + ends.push(end); + ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]); + + if (input.length > peg$currPos) { + end = ip + 3 + bc[ip + 1]; + ip += 3; + } else { + end = ip + 3 + bc[ip + 1] + bc[ip + 2]; + ip += 3 + bc[ip + 1]; + } + + break; + + case 18: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + + if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + + break; + + case 19: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + + if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + + break; + + case 20: + ends.push(end); + ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]); + + if (peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))) { + end = ip + 4 + bc[ip + 2]; + ip += 4; + } else { + end = ip + 4 + bc[ip + 2] + bc[ip + 3]; + ip += 4 + bc[ip + 2]; + } + + break; + + case 21: + stack.push(input.substr(peg$currPos, bc[ip + 1])); + peg$currPos += bc[ip + 1]; + ip += 2; + break; + + case 22: + stack.push(peg$consts[bc[ip + 1]]); + peg$currPos += peg$consts[bc[ip + 1]].length; + ip += 2; + break; + + case 23: + stack.push(peg$FAILED); + if (peg$silentFails === 0) { + peg$fail(peg$consts[bc[ip + 1]]); + } + ip += 2; + break; + + case 24: + peg$savedPos = stack[stack.length - 1 - bc[ip + 1]]; + ip += 2; + break; + + case 25: + peg$savedPos = peg$currPos; + ip++; + break; + + case 26: + params = bc.slice(ip + 4, ip + 4 + bc[ip + 3]); + for (i = 0; i < bc[ip + 3]; i++) { + params[i] = stack[stack.length - 1 - params[i]]; + } + + stack.splice( + stack.length - bc[ip + 2], + bc[ip + 2], + peg$consts[bc[ip + 1]].apply(null, params) + ); + + ip += 4 + bc[ip + 3]; + break; + + case 27: + stack.push(peg$parseRule(bc[ip + 1])); + ip += 2; + break; + + case 28: + peg$silentFails++; + ip++; + break; + + case 29: + peg$silentFails--; + ip++; + break; + + default: + throw new Error("Invalid opcode: " + bc[ip] + "."); + } + } + + if (ends.length > 0) { + end = ends.pop(); + ip = ips.pop(); + } else { + break; + } + } + + return stack[0]; + } + + + + const OP_EQ = "equal"; + const OP_IS = OP_EQ; + const OP_GT = "greater"; + const OP_GE= "greaterequal"; + const OP_LT= "less"; + const OP_LE= "lessequal"; + const OP_NOT = "not"; + + const VERBOSE = false; + + function Version(){ + this.major = null; + this.minor = null; + this.patch = null; + this.op = null; + } + + const P = Version.prototype; + + P.toString = function() { + return `[${this.op ? this.op : "?"}]/v${this.major}.${this.minor}.${this.patch}`; + } + + Object.defineProperty(Version.prototype, 'wildcard', { + get: function() { + return this.major === '*' || this.minor === '*' || this.patch === '*'; + } + }); + Object.defineProperty(Version.prototype, 'anyMajor', { + get: function() { return this.major === '*';} + }); + Object.defineProperty(Version.prototype, 'anyMinor', { + get: function() { return this.minor === '*';} + }); + Object.defineProperty(Version.prototype, 'anyPatch', { + get: function() { return this.patch === '*';} + }); + P.toArray = function() { + const r = []; + if(this.major !== null) { + r.push(this.major); + } + if(this.minor !== null) { + r.push(this.minor); + } + if(this.patch !== null) { + r.push(this.patch); + } + return r; + }; + P.assertNotWildcard = function() { + if(this.wildcard) { + throw new Error("Source version should not be wildcard: "+this.toString()); + } + } + + P.compareTo = function(o) { + this.assertNotWildcard(); + if(o.wildcard) { + throw new Error("Reference version should not be wildcard when comparing!"); + } + const va = this.toArray(); + const vb = o.toArray(); + const l = Math.min(va.length, vb.length); + let fillZeros = (a, l, ml) =>{ for(let i = l; i < ml; i++) a[i] = 0;} + fillZeros(va, l, 3); + fillZeros(vb, l, 3); + let toFactor = (v) => {return (v[0] << 20) + (v[1] << 10) + (v[2]);} + return toFactor(va) - toFactor(vb); + }; + P.match = function(o) { + if(VERBOSE) { + console.log(`try match ${o}`); + } + this.assertNotWildcard(); + if(!o.wildcard) { + throw new Error("Reference version should be wildcard when matching!"); + } + + if(VERBOSE) { + console.log(` match major ${o.major}, any ${o.anyMajor}, ${this.major} <> ${o.major}`); + } + if(o.anyMajor) return true; + if(this.major !== o.major) return false; + + if(VERBOSE) { + console.log(` match minor ${o.minor}, any ${o.anyMinor}, ${this.minor} <> ${o.minor}`); + } + if(o.anyMinor) return true; + if(this.minor !== o.minor) return false; + + if(VERBOSE) { + console.log(` match patch ${o.patch}, any ${o.anyPatch}`); + } + if(o.anyPatch) return true; + return this.patch == o.patch; + }; + + P.test = function(o) { + let op = o.op; + if(op === OP_EQ) { + return o.wildcard ? this.match(o) : this.compareTo(o) === 0; + } + if(op === OP_NOT) { + return o.wildcard ? !this.match(o) : this.compareTo(o) !== 0; + } + if(o.wildcard){ + throw new Error("Can not compare to wildcard version"); + } + const R = this.compareTo(o); + if(op === OP_GE) { + return R >= 0; + } + if(op === OP_LE) { + return R <= 0; + } + if(op === OP_GT) { + return R > 0; + } + if(op === OP_LT) { + return R < 0; + } + throw new Error("invalidate operation " + op); + }; + + function VersionSet(data) { + this.conds = data; + } + + function allMatch(v, versions) { + let t; + let r = true; + for(let c of versions) { + t = v.test(c); + if(VERBOSE) { + console.log(`test ${v} with ${c} => ${t}`); + } + if(!t){ + r = false; + if(!VERBOSE) { + return false; + } + } + } + return r; + } + + function arrElmOr(arr, idx, dft){ + if(arr[idx] === undefined) return dft; + return arr[idx]; + } + + VersionSet.prototype.match = function(o) { + let ps = o.split('.'); + let v = new Version; + v.major = parseInt(arrElmOr(ps, 0, 0), 10); + v.minor = parseInt(arrElmOr(ps, 1, 0), 10); + v.patch = parseInt(arrElmOr(ps, 2, 0), 10); + if(VERBOSE) { + console.log(`match version ${v}`); + } + for(let c of this.conds){ + if(allMatch(v, c)) { + return true; + } + } + return false; + }; + + + peg$result = peg$parseRule(peg$startRuleIndex); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + + throw peg$buildStructuredError( + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } +} + +module.exports = { + SyntaxError: peg$SyntaxError, + parse: peg$parse +}; diff --git a/cmake/scripts/plugin_support/plugin_cfg.pegjs b/cmake/scripts/plugin_support/plugin_cfg.pegjs new file mode 100644 index 0000000..3a814bb --- /dev/null +++ b/cmake/scripts/plugin_support/plugin_cfg.pegjs @@ -0,0 +1,251 @@ +// Simple Version Grammar + +{ + + const OP_EQ = "equal"; + const OP_IS = OP_EQ; + const OP_GT = "greater"; + const OP_GE= "greaterequal"; + const OP_LT= "less"; + const OP_LE= "lessequal"; + const OP_NOT = "not"; + + const VERBOSE = false; + + function Version(){ + this.major = null; + this.minor = null; + this.patch = null; + this.op = null; + } + + const P = Version.prototype; + + P.toString = function() { + return `[${this.op ? this.op : "?"}]/v${this.major}.${this.minor}.${this.patch}`; + } + + Object.defineProperty(Version.prototype, 'wildcard', { + get: function() { + return this.major === '*' || this.minor === '*' || this.patch === '*'; + } + }); + Object.defineProperty(Version.prototype, 'anyMajor', { + get: function() { return this.major === '*';} + }); + Object.defineProperty(Version.prototype, 'anyMinor', { + get: function() { return this.minor === '*';} + }); + Object.defineProperty(Version.prototype, 'anyPatch', { + get: function() { return this.patch === '*';} + }); + P.toArray = function() { + const r = []; + if(this.major !== null) { + r.push(this.major); + } + if(this.minor !== null) { + r.push(this.minor); + } + if(this.patch !== null) { + r.push(this.patch); + } + return r; + }; + P.assertNotWildcard = function() { + if(this.wildcard) { + throw new Error("Source version should not be wildcard: "+this.toString()); + } + } + + P.compareTo = function(o) { + this.assertNotWildcard(); + if(o.wildcard) { + throw new Error("Reference version should not be wildcard when comparing!"); + } + const va = this.toArray(); + const vb = o.toArray(); + const l = Math.min(va.length, vb.length); + let fillZeros = (a, l, ml) =>{ for(let i = l; i < ml; i++) a[i] = 0;} + fillZeros(va, l, 3); + fillZeros(vb, l, 3); + let toFactor = (v) => {return (v[0] << 20) + (v[1] << 10) + (v[2]);} + return toFactor(va) - toFactor(vb); + }; + P.match = function(o) { + if(VERBOSE) { + console.log(`try match ${o}`); + } + this.assertNotWildcard(); + if(!o.wildcard) { + throw new Error("Reference version should be wildcard when matching!"); + } + + if(VERBOSE) { + console.log(` match major ${o.major}, any ${o.anyMajor}, ${this.major} <> ${o.major}`); + } + if(o.anyMajor) return true; + if(this.major !== o.major) return false; + + if(VERBOSE) { + console.log(` match minor ${o.minor}, any ${o.anyMinor}, ${this.minor} <> ${o.minor}`); + } + if(o.anyMinor) return true; + if(this.minor !== o.minor) return false; + + if(VERBOSE) { + console.log(` match patch ${o.patch}, any ${o.anyPatch}`); + } + if(o.anyPatch) return true; + return this.patch == o.patch; + }; + + P.test = function(o) { + let op = o.op; + if(op === OP_EQ) { + return o.wildcard ? this.match(o) : this.compareTo(o) === 0; + } + if(op === OP_NOT) { + return o.wildcard ? !this.match(o) : this.compareTo(o) !== 0; + } + if(o.wildcard){ + throw new Error("Can not compare to wildcard version"); + } + const R = this.compareTo(o); + if(op === OP_GE) { + return R >= 0; + } + if(op === OP_LE) { + return R <= 0; + } + if(op === OP_GT) { + return R > 0; + } + if(op === OP_LT) { + return R < 0; + } + throw new Error("invalidate operation " + op); + }; + + function VersionSet(data) { + this.conds = data; + } + + function allMatch(v, versions) { + let t; + let r = true; + for(let c of versions) { + t = v.test(c); + if(VERBOSE) { + console.log(`test ${v} with ${c} => ${t}`); + } + if(!t){ + r = false; + if(!VERBOSE) { + return false; + } + } + } + return r; + } + + function arrElmOr(arr, idx, dft){ + if(arr[idx] === undefined) return dft; + return arr[idx]; + } + + VersionSet.prototype.match = function(o) { + let ps = o.split('.'); + let v = new Version; + v.major = parseInt(arrElmOr(ps, 0, 0), 10); + v.minor = parseInt(arrElmOr(ps, 1, 0), 10); + v.patch = parseInt(arrElmOr(ps, 2, 0), 10); + if(VERBOSE) { + console.log(`match version ${v}`); + } + for(let c of this.conds){ + if(allMatch(v, c)) { + return true; + } + } + return false; + }; +} + + +Expression + = head:Conds tail:('||' Conds)* { + let s = [head]; + s = s.concat(tail.map(x=>x[1])); + return new VersionSet(s); + } + +Conds "Condition Items" + = _ head:Cond tail:(S Cond)+ _ { + let s = [head]; + s = s.concat(tail.map(x=> x[1])); + return s; + } + / _ head: Cond _ { + return [head]; + } + +Cond + = '>=' _ v:Version { + v.op = OP_GE; + return v; + } + / '<=' _ v:Version { + v.op = OP_LE; + return v; + } + / '!=' _ v:Version { + v.op = OP_NOT; + return v; + } + / '!' _ v:Version { + v.op = OP_NOT; + return v; + } + / '=' _ v:Version { + v.op = OP_EQ; + return v; + } + / '<' _ v:Version { + v.op = OP_LT; + return v; + } + / '>'_ v:Version { + v.op = OP_GT; + return v; + } + / head:Version { + head.op = OP_IS; + return head; + } + +Version "version" + = V3 / V2 / V1 + +V3 "major.minor.patch" + = v:V2 '.' n:Factor { v.patch = n; return v} + +V2 "major.minor" + = v:V1 '.' n:Factor {v.minor = n; return v} + +V1 "major" + = n:Factor {let v = new Version; v.major = n; return v} + + +Factor + = ('*' / 'x' / 'X') {return '*';} + / Integer + +Integer "integer" + = [0-9]+ { return parseInt(text(), 10); } + +S "whitespace" + = [ \t\n\r]+ + +_ "whitespace" + = [ \t\n\r]* \ No newline at end of file diff --git a/cmake/scripts/plugin_support/test_parse.js b/cmake/scripts/plugin_support/test_parse.js new file mode 100644 index 0000000..b41543e --- /dev/null +++ b/cmake/scripts/plugin_support/test_parse.js @@ -0,0 +1,133 @@ + + +const parser = require('./plugin_cfg.js'); +const input_text = `3.4.x 3.5 3 3.* * 6.2.* || > 3.5 <= 3.2 > 2.4 <= 33.2 >=133.222.2 !333 != 22 || 4`; + +// let r = parser.parse(input_text); + + + +function assert(label, blk) { + console.log(`run ${label}`); + let r = blk(); + if (!r) { + console.error(` failed!`); + } +} + +// r.match("3.2"); +// r.match("3.4"); +{ + let vs = ["3.4", "3.4.*", "3.4.x", "3.4.X"]; + for (let v of vs) { + let s = parser.parse(v); + let label = `Simple match ${v}`; + assert(`${label} 1`, () => { + return s.match("3.4"); + }); + assert(`${label} 2`, () => { + return !s.match("3.2"); + }); + assert(`${label} 2`, () => { + return s.match("3.4.3"); + }); + assert(`${label} 3`, () => { + return s.match("3.4.6"); + }); + assert(`${label} 4`, () => { + return !s.match("3.444"); + }); + assert("dont match", () => { + let cases = [ + "2.4", + "2.4.2", + "6.4", + "3" + ] + return !cases.reduce((p, cs) => p || s.match(cs), false); + }); + } +} +{ + let s = parser.parse("*"); + assert("match all", () => { + let cases = [ + "2.4", + "2.4.2", + "6.4", + "3" + ] + return cases.reduce((p, cs) => p && s.match(cs), true); + }); +} +{ + function assert_match(target, versions, bad_versions) { + let s = parser.parse(target); + for (let v of versions) { + assert(`test ${v}`, () => { + return s.match(v); + }); + } + for (let v of bad_versions) { + assert(`test not match ${v}`, () => { + return !s.match(v); + }); + } + } + assert_match(">3.3 <3.6", [ + '3.4', + '3.4.1', + '3.4.2', + '3.5', + '3.5.0', + '3.5.1', + '3.5.2', + ], + [ + '3.3', + '3.3.2', + '2.3', + '3.6.0', + '3.6', + ] + ); + assert_match(">=3.3 <= 3.6.0", [ + '3.3.0', + '3.3.1', + '3.4', + '3.4.1', + '3.4.2', + '3.5', + '3.5.0', + '3.5.1', + '3.5.2', + '3.6.0' + ], + [ + '3.2.9', + '2.3', + '3.6.1', + ] + ); + assert_match(">=3.3 <= 3.6.0 !3.5.2|| 4.x", [ + '3.3.0', + '3.3.1', + '3.4', + '3.4.1', + '3.4.2', + '3.5', + '3.5.0', + '4.0', + '4.1', + '4.2', + '3.5.1', + '3.6.0' + ], + [ + '3.2.9', + '2.3', + '3.6.1', + '3.5.2' + ] + ); +} \ No newline at end of file diff --git a/cmake/scripts/plugins_parser.js b/cmake/scripts/plugins_parser.js new file mode 100644 index 0000000..8edbfc3 --- /dev/null +++ b/cmake/scripts/plugins_parser.js @@ -0,0 +1,271 @@ +const fs = require('fs'); +const path = require('path'); +const version_parser = require('./plugin_support/plugin_cfg'); + +const MAX_SEARCH_LEVEL = 7; +const CC_PLUGIN_JSON_STR = 'cc_plugin.json'; + +const node_path = process.argv.shift(); +const script_path = process.argv.shift(); +const search_path_input_file = process.argv.shift(); +const plugin_cmake_output_file = process.argv.shift(); +const PLATFORM_NAME_FROM_CMAKE = process.argv.shift(); + +const PROJ_SEARCH_PATHS = fs.readFileSync(search_path_input_file, 'utf8'). + split('\n'). + map(x => x.trim()). + filter(x => x.length > 0 && !x.startsWith("#")); + +const cc_config_json_list = []; + +function search_cc_config_json_levels(dir, depth) { + // console.log(`[searching plugins] search dir ${dir}`); + if (depth > MAX_SEARCH_LEVEL) return; + const st = fs.statSync(dir); + if (!st.isDirectory()) return; + + let subfiles = fs.readdirSync(dir); + let subdirs = []; + for (let item of subfiles) { + if (item === "." || item === "..") continue; + let fp = path.join(dir, item); + const subst = fs.statSync(fp); + if (subst.isFile() && item === CC_PLUGIN_JSON_STR) { + cc_config_json_list.push(dir); + return; + } + if (subst.isDirectory()) { + subdirs.push(fp); + } + } + for (let sd of subdirs) { + search_cc_config_json_levels(sd, depth + 1); + } +} + +for (let searchPath of PROJ_SEARCH_PATHS) { + if (!fs.existsSync(searchPath)) { + console.log(`[searching plugins] directory ${searchPath} does not exist`); + continue; + } + search_cc_config_json_levels(searchPath, 1); +} + + +if (cc_config_json_list.length === 0) { + console.log("[searching plugins] no plugins found!"); + process.exit(0) +} + +for (let dir of cc_config_json_list) { + console.log(`[searching plugins] plugin dir found: ${dir}`) +} + +function read_engine_version() { + const pkg = path.join(__dirname, '../../../package.json'); + return require(pkg).version; +} + +function parse_package_dependency(info) { + let pkgs = []; + for (let m of info.modules) { + if (m.platforms && m.platforms.indexOf(PLATFORM_NAME_FROM_CMAKE) < 0) { + continue; + } + pkgs.push({ target: m.target, depends: typeof m.depends === 'string' ? [m.depends] : (m.depends || []) }); + } + return pkgs; +} + +function get_property_variants(obj, ...names) { + for (let n of names) { + if (obj.hasOwnProperty(n)) { + return obj[n]; + } + if (n.indexOf('_') >= 0) { + const k = n.replace(/_/g, '-'); + if (obj.hasOwnProperty(k)) { + return obj[k]; + } + } + if (n.indexOf('-') >= 0) { + const k = n.replace(/-/g, '_'); + if (obj.hasOwnProperty(k)) { + return obj[k]; + } + } + } + return undefined; +} + + +function test_enable_by_configurations(config) { + const support_platforms = get_property_variants(config, "platforms") || []; + const enabled_default = get_property_variants(config, "enable", "enabled"); + const enable_all = enabled_default === undefined ? true : enabled_default; + const disable_all = (get_property_variants(config, "disable", "disabled") || false) || !enable_all; + const disabled_platforms = get_property_variants(config, "disable-by-platforms", "disabled-by-platforms") || []; + const engine_version_value = get_property_variants(config, "engine-version"); + if (disable_all) { + // all disabled + console.log(` plugin is disabled.`); + return false; + } + if (support_platforms.length > 0 && support_platforms.indexOf(PLATFORM_NAME_FROM_CMAKE) < 0) { + // unsupported platform + console.log(` plugin is not supported by current platform ${PLATFORM_NAME_FROM_CMAKE}.`); + return false; + } + if (disabled_platforms.indexOf(PLATFORM_NAME_FROM_CMAKE) > -1) { + // disable by user settings + console.log(` plugin is disabled by setting.`); + return false; + } + + const ENGINE_VERSION = read_engine_version().replace(/^(v|V)/, ''); + try { + const version_filter = version_parser.parse(engine_version_value); + const version_valid = version_filter.match(ENGINE_VERSION); + if (!version_valid) { + console.warn(` Engine version '${ENGINE_VERSION}' mismatch '${engine_version_value}'`); + } + } catch (e) { + console.error(` Failed to parse 'engine-version', value: '${engine_version_value}'`); + console.error(e); + return false; + } + return true; +} + +function validate_cc_plugin_json_format(tag, content) { + const field_required = (obj, field_name) => { + if (Object.hasOwnProperty(obj, field_name)) { + console.warn(`${tag} field '${field_name}' is not set`); + return false; + } + return true; + } + const required_fields = ["name", "version", "engine-version", "author", "description", "modules", "platforms"]; + for (const f of required_fields) { + if (!field_required(content, f)) { + return false; + } + } + const modules = content["modules"]; + if (modules.length == 0) { + console.warn(`${tag} modules field is empty`); + return false; + } + + for (let m of modules) { + const mod_fields = ["target"]; + for (const f of mod_fields) { + if (!field_required(m, f)) { + console.warn(`${tag} module field ${f} is not set`); + return false; + } + } + } + return true; +} + + +function add_search_path_suffix(dir, platform) { + if (platform.match(/^android/i)) { + return [`${dir}/android/\${ANDROID_ABI}`, `${dir}/android`]; + } else if (platform.match(/^win/i)) { + return [`${dir}/windows/x86_64`, `${dir}/windows`]; + } else if (platform.match(/^iphonesimulator/i)) { + return [`${dir}/iphonesimulator`, `${dir}/ios`]; + } else if (platform.match(/^ios/i)) { + return [`${dir}/ios`]; + } else if (platform.match(/^mac/i) || platform.match(/^darwin/i)) { + return [`${dir}/mac/\${CMAKE_SYSTEM_PROCESSOR}`, `${dir}/mac`]; + } else { + console.warn(`Don't knowm suffix for '${platform}`) + return []; + } +} + +console.log(`Engine version: ${read_engine_version()}`); + +/// Generate Pre-AutoLoadPlugins.cmake + +let output_lines = ["# plugins found & enabled in search path", + "# To disable automatic update of this file, set SKIP_SCAN_PLUGINS to ON.", + ""]; +for (let plugin_dir of cc_config_json_list) { + let load_plugins = []; + try { + let maybe_plugin_name = path.basename(plugin_dir); + console.log(`Parsing plugin directory ${maybe_plugin_name}`); + let cc_plugin_file = path.join(plugin_dir, CC_PLUGIN_JSON_STR); + let cc_plugin_content = fs.readFileSync(cc_plugin_file, { encoding: 'utf8' }); + let cc_plugin_json = JSON.parse(cc_plugin_content); + if (!validate_cc_plugin_json_format(`Parsing module ${maybe_plugin_name}:`, cc_plugin_json)) { + continue; + } + if (!test_enable_by_configurations(cc_plugin_json)) { + console.log(` ${maybe_plugin_name} disabled by configuration`); + continue; + } + const plugin_name = cc_plugin_json.name; + const module_type = get_property_variants(cc_plugin_json, "module_type") + if (module_type !== undefined && module_type !== 'release') { + console.log(` plugin ${plugin_name} is not a release, should be include or add_subdirectory in dev env.`); + continue; + } + const packages = parse_package_dependency(cc_plugin_json); + const cc_project_dir = path.dirname(plugin_cmake_output_file); + let project_to_plugin_dir = path.relative(cc_project_dir, plugin_dir).replace(/\\/g, '/'); + project_to_plugin_dir = `\${CC_PROJECT_DIR}/${project_to_plugin_dir}`; + const plugin_root_path_for_platform = add_search_path_suffix(project_to_plugin_dir, PLATFORM_NAME_FROM_CMAKE); + for (let pkg of packages) { + const [target_name, target_version] = pkg.target.split('@'); + output_lines.push(`set(${target_name}_ROOT\n${plugin_root_path_for_platform.map(x => ` "${x}"`).join("\n")}\n)`, ""); + output_lines.push(`list(APPEND CMAKE_FIND_ROOT_PATH \${${target_name}_ROOT})`) + load_plugins = load_plugins.concat([...pkg.depends, target_name + (target_version !== undefined ? '@' + target_version : '')]); + output_lines.push(`list(APPEND CC_REGISTERED_PLUGINS`); + output_lines = output_lines.concat(` ${target_name}`); + output_lines.push(`)`); + } + let plugin_names = load_plugins.map(x => x.split(/@/)); + for (let plg of plugin_names) { + output_lines.push(""); + if (plg[1] && plg.length > 0) { + output_lines.push(`find_package(${plg[0]} ${plg[1]}`); + } else { + output_lines.push(`find_package(${plg[0]}`); + } + output_lines.push(` REQUIRED`); + output_lines.push(` NAMES "${plg[0]}"`); + output_lines.push(`# NO_DEFAULT_PATH`); + output_lines.push(`)`); + } + if (packages.length > 0) { + console.log(` record plugin ${plugin_name}`); + } else { + console.log(` no sub module found`); + } + } catch (e) { + console.error(`Parsing plugin directory: ${plugin_dir}`) + console.error(e); + } +} + +if (cc_config_json_list.length == 0) { + console.log(`Try unlink file ${out_file}`) + if (fs.existsSync(out_file)) { + fs.unlinkSync(out_file); + } +} else { + let old_content = null; + let new_content = output_lines.join("\n") + "\n"; + if (fs.existsSync(plugin_cmake_output_file)) { + old_content = fs.readFileSync(plugin_cmake_output_file); + } + if (old_content !== new_content) { + fs.writeFileSync(plugin_cmake_output_file, output_lines.join("\n") + "\n", { encoding: 'utf8' }); + } +} +process.exit(0); diff --git a/cmake/scripts/utils.js b/cmake/scripts/utils.js new file mode 100644 index 0000000..fdf1530 --- /dev/null +++ b/cmake/scripts/utils.js @@ -0,0 +1,38 @@ + + +const fs = require('fs'); + + +function all_eql(a, b) { + if (a.length !== b.length) { + return false; + } + const l = a.length; + for (let i = 0; i < l; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +function equals_ignore_spaces(a, b) { + const lines_a = a.split('\n').map(x => x.trim()); + const lines_b = b.split('\n').map(x => x.trim()); + return all_eql(lines_a, lines_b); +} + + +function writeIfDifferent(file, data, args) { + if (fs.existsSync(file)) { + let content = fs.readFileSync(file, args).toString(args.encoding); + if (equals_ignore_spaces(content, data)) { + console.log(`Skip update ${file}`); + return; + } + } + fs.writeFileSync(file, data, args); + console.log(` write to file ${file}`); +} + +exports.writeIfDifferent = writeIfDifferent; \ No newline at end of file diff --git a/cocos/2d/renderer/Batcher2d.cpp b/cocos/2d/renderer/Batcher2d.cpp new file mode 100644 index 0000000..8e05a43 --- /dev/null +++ b/cocos/2d/renderer/Batcher2d.cpp @@ -0,0 +1,614 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "2d/renderer/Batcher2d.h" +#include "application/ApplicationManager.h" +#include "base/TypeDef.h" +#include "core/Root.h" +#include "core/scene-graph/Scene.h" +#include "editor-support/MiddlewareManager.h" +#include "renderer/pipeline/Define.h" +#include "scene/Pass.h" + +namespace cc { + +Batcher2d::Batcher2d() : Batcher2d(nullptr) { +} + +Batcher2d::Batcher2d(Root* root) +: _drawBatchPool([]() { return ccnew scene::DrawBatch2D(); }, [](auto* obj) { delete obj; }, 10U) { + if (root == nullptr) { + root = Root::getInstance(); + } + _root = root; + _device = _root->getDevice(); + _stencilManager = StencilManager::getInstance(); +} + +Batcher2d::~Batcher2d() { // NOLINT + _drawBatchPool.destroy(); + + for (auto iter : _descriptorSetCache) { + delete iter.second; + } + + for (auto* drawBatch : _batches) { + delete drawBatch; + } + _attributes.clear(); + + if (_maskClearModel != nullptr) { + Root::getInstance()->destroyModel(_maskClearModel); + _maskClearModel = nullptr; + } + if (_maskModelMesh != nullptr) { + _maskModelMesh->destroy(); + _maskModelMesh = nullptr; + } + _maskClearMtl = nullptr; + _maskAttributes.clear(); +} + +void Batcher2d::syncMeshBuffersToNative(uint16_t accId, ccstd::vector&& buffers) { + _meshBuffersMap[accId] = std::move(buffers); +} + +UIMeshBuffer* Batcher2d::getMeshBuffer(uint16_t accId, uint16_t bufferId) { // NOLINT(bugprone-easily-swappable-parameters) + const auto& map = _meshBuffersMap[accId]; + return map[bufferId]; +} + +gfx::Device* Batcher2d::getDevice() { + if (_device == nullptr) { + _device = Root::getInstance()->getDevice(); + } + return _device; +} + +void Batcher2d::updateDescriptorSet() { +} + +void Batcher2d::syncRootNodesToNative(ccstd::vector&& rootNodes) { + _rootNodeArr = std::move(rootNodes); +} + +void Batcher2d::fillBuffersAndMergeBatches() { + size_t index = 0; + for (auto* rootNode : _rootNodeArr) { + // _batches will add by generateBatch + walk(rootNode, 1); + generateBatch(_currEntity, _currDrawInfo); + + auto* scene = rootNode->getScene()->getRenderScene(); + size_t const count = _batches.size(); + for (size_t i = index; i < count; i++) { + scene->addBatch(_batches.at(i)); + } + index = count; + } +} + +void Batcher2d::walk(Node* node, float parentOpacity) { // NOLINT(misc-no-recursion) + if (!node->isActiveInHierarchy()) { + return; + } + bool breakWalk = false; + auto* entity = static_cast(node->getUserData()); + if (entity) { + if (entity->getColorDirty()) { + float localOpacity = entity->getLocalOpacity(); + float localColorAlpha = entity->getColorAlpha(); + entity->setOpacity(parentOpacity * localOpacity * localColorAlpha); + entity->setColorDirty(false); + entity->setVBColorDirty(true); + } + if (math::isEqualF(entity->getOpacity(), 0)) { + breakWalk = true; + } else if (entity->isEnabled()) { + uint32_t size = entity->getRenderDrawInfosSize(); + for (uint32_t i = 0; i < size; i++) { + auto* drawInfo = entity->getRenderDrawInfoAt(i); + handleDrawInfo(entity, drawInfo, node); + } + entity->setVBColorDirty(false); + } + if (entity->getRenderEntityType() == RenderEntityType::CROSSED) { + breakWalk = true; + } + } + + if (!breakWalk) { + const auto& children = node->getChildren(); + float thisOpacity = entity ? entity->getOpacity() : parentOpacity; + for (const auto& child : children) { + // we should find parent opacity recursively upwards if it doesn't have an entity. + walk(child, thisOpacity); + } + } + + // post assembler + if (_stencilManager->getMaskStackSize() > 0 && entity && entity->isEnabled()) { + handlePostRender(entity); + } +} + +void Batcher2d::handlePostRender(RenderEntity* entity) { + bool isMask = entity->getIsMask(); + if (isMask) { + generateBatch(_currEntity, _currDrawInfo); + resetRenderStates(); + _stencilManager->exitMask(); + } +} +CC_FORCE_INLINE void Batcher2d::handleComponentDraw(RenderEntity* entity, RenderDrawInfo* drawInfo, Node* node) { + ccstd::hash_t dataHash = drawInfo->getDataHash(); + if (drawInfo->getIsMeshBuffer()) { + dataHash = 0; + } + + // may slow + bool isMask = entity->getIsMask(); + if (isMask) { + // Mask subComp + insertMaskBatch(entity); + } else { + entity->setEnumStencilStage(_stencilManager->getStencilStage()); + } + auto tempStage = static_cast(entity->getStencilStage()); + + if (_currHash != dataHash || dataHash == 0 || _currMaterial != drawInfo->getMaterial() || _currStencilStage != tempStage) { + // Generate a batch if not batching + generateBatch(_currEntity, _currDrawInfo); + + if (!drawInfo->getIsMeshBuffer()) { + UIMeshBuffer* buffer = drawInfo->getMeshBuffer(); + if (_currMeshBuffer != buffer) { + _currMeshBuffer = buffer; + _indexStart = _currMeshBuffer->getIndexOffset(); + } + } + + _currHash = dataHash; + _currMaterial = drawInfo->getMaterial(); + _currStencilStage = tempStage; + _currLayer = entity->getNode()->getLayer(); + _currEntity = entity; + _currDrawInfo = drawInfo; + + _currTexture = drawInfo->getTexture(); + _currSampler = drawInfo->getSampler(); + if (_currSampler == nullptr) { + _currSamplerHash = 0; + } else { + _currSamplerHash = _currSampler->getHash(); + } + } + + if (!drawInfo->getIsMeshBuffer()) { + if (node->getChangedFlags() || drawInfo->getVertDirty()) { + fillVertexBuffers(entity, drawInfo); + drawInfo->setVertDirty(false); + } + if (entity->getVBColorDirty()) { + fillColors(entity, drawInfo); + } + + fillIndexBuffers(drawInfo); + } + + if (isMask) { + _stencilManager->enableMask(); + } +} + +CC_FORCE_INLINE void Batcher2d::handleModelDraw(RenderEntity* entity, RenderDrawInfo* drawInfo) { + generateBatch(_currEntity, _currDrawInfo); + resetRenderStates(); + + // stencil stage + gfx::DepthStencilState* depthStencil = nullptr; + ccstd::hash_t dssHash = 0; + Material* renderMat = drawInfo->getMaterial(); + + bool isMask = entity->getIsMask(); + if (isMask) { + // Mask Comp + insertMaskBatch(entity); + } else { + entity->setEnumStencilStage(_stencilManager->getStencilStage()); + } + + StencilStage entityStage = entity->getEnumStencilStage(); + depthStencil = _stencilManager->getDepthStencilState(entityStage, renderMat); + dssHash = _stencilManager->getStencilHash(entityStage); + + // Model + auto* model = drawInfo->getModel(); + if (model == nullptr) return; + auto stamp = CC_CURRENT_ENGINE()->getTotalFrames(); + model->updateTransform(stamp); + model->updateUBOs(stamp); + + const auto& subModelList = model->getSubModels(); + for (const auto& submodel : subModelList) { + auto* curdrawBatch = _drawBatchPool.alloc(); + curdrawBatch->setVisFlags(entity->getNode()->getLayer()); + curdrawBatch->setModel(model); + curdrawBatch->setInputAssembler(submodel->getInputAssembler()); + curdrawBatch->setDescriptorSet(submodel->getDescriptorSet()); + + curdrawBatch->fillPass(renderMat, depthStencil, dssHash, &(submodel->getPatches())); + _batches.push_back(curdrawBatch); + } + + if (isMask) { + _stencilManager->enableMask(); + } +} + +CC_FORCE_INLINE void Batcher2d::handleMiddlewareDraw(RenderEntity* entity, RenderDrawInfo* drawInfo) { + auto layer = entity->getNode()->getLayer(); + Material* material = drawInfo->getMaterial(); + auto* texture = drawInfo->getTexture(); + auto* sampler = drawInfo->getSampler(); + auto* meshBuffer = drawInfo->getMeshBuffer(); + + // check for merge draw + auto enableBatch = !entity->getUseLocal(); + if (enableBatch && _currTexture == texture && _currMeshBuffer == meshBuffer && !_currEntity->getUseLocal() && material->getHash() == _currMaterial->getHash() && drawInfo->getIndexOffset() == _currDrawInfo->getIndexOffset() + _currDrawInfo->getIbCount() && layer == _currLayer) { + auto ibCount = _currDrawInfo->getIbCount(); + _currDrawInfo->setIbCount(ibCount + drawInfo->getIbCount()); + } else { + generateBatch(_currEntity, _currDrawInfo); + _currLayer = layer; + _currMaterial = material; + _currTexture = texture; + _currMeshBuffer = meshBuffer; + _currEntity = entity; + _currDrawInfo = drawInfo; + _currHash = 0; + } +} + +CC_FORCE_INLINE void Batcher2d::handleSubNode(RenderEntity* entity, RenderDrawInfo* drawInfo) { // NOLINT + if (drawInfo->getSubNode()) { + walk(drawInfo->getSubNode(), entity->getOpacity()); + } +} + +CC_FORCE_INLINE void Batcher2d::handleDrawInfo(RenderEntity* entity, RenderDrawInfo* drawInfo, Node* node) { // NOLINT(misc-no-recursion) + CC_ASSERT(entity); + CC_ASSERT(drawInfo); + RenderDrawInfoType drawInfoType = drawInfo->getEnumDrawInfoType(); + + switch (drawInfoType) { + case RenderDrawInfoType::COMP: + handleComponentDraw(entity, drawInfo, node); + break; + case RenderDrawInfoType::MODEL: + handleModelDraw(entity, drawInfo); + break; + case RenderDrawInfoType::MIDDLEWARE: + handleMiddlewareDraw(entity, drawInfo); + break; + case RenderDrawInfoType::SUB_NODE: + handleSubNode(entity, drawInfo); + break; + default: + break; + } +} + +void Batcher2d::generateBatch(RenderEntity* entity, RenderDrawInfo* drawInfo) { + if (drawInfo == nullptr) { + return; + } + if (drawInfo->getEnumDrawInfoType() == RenderDrawInfoType::MIDDLEWARE) { + generateBatchForMiddleware(entity, drawInfo); + return; + } + if (_currMaterial == nullptr) { + return; + } + gfx::InputAssembler* ia = nullptr; + + uint32_t indexOffset = 0; + uint32_t indexCount = 0; + if (drawInfo->getIsMeshBuffer()) { + // Todo MeshBuffer RenderData + ia = drawInfo->requestIA(getDevice()); + indexOffset = drawInfo->getIndexOffset(); + indexCount = drawInfo->getIbCount(); + _meshRenderDrawInfo.emplace_back(drawInfo); + } else { + UIMeshBuffer* currMeshBuffer = drawInfo->getMeshBuffer(); + + currMeshBuffer->setDirty(true); + + ia = currMeshBuffer->requireFreeIA(getDevice()); + indexCount = currMeshBuffer->getIndexOffset() - _indexStart; + if (ia == nullptr) { + return; + } + indexOffset = _indexStart; + _indexStart = currMeshBuffer->getIndexOffset(); + } + + _currMeshBuffer = nullptr; + + // stencilStage + gfx::DepthStencilState* depthStencil = nullptr; + ccstd::hash_t dssHash = 0; + StencilStage entityStage = entity->getEnumStencilStage(); + depthStencil = _stencilManager->getDepthStencilState(entityStage, _currMaterial); + dssHash = _stencilManager->getStencilHash(entityStage); + + auto* curdrawBatch = _drawBatchPool.alloc(); + curdrawBatch->setVisFlags(_currLayer); + curdrawBatch->setInputAssembler(ia); + curdrawBatch->setFirstIndex(indexOffset); + curdrawBatch->setIndexCount(indexCount); + curdrawBatch->fillPass(_currMaterial, depthStencil, dssHash); + const auto& pass = curdrawBatch->getPasses().at(0); + + if (entity->getUseLocal()) { + drawInfo->updateLocalDescriptorSet(entity->getRenderTransform(), pass->getLocalSetLayout()); + curdrawBatch->setDescriptorSet(drawInfo->getLocalDes()); + } else { + curdrawBatch->setDescriptorSet(getDescriptorSet(_currTexture, _currSampler, pass->getLocalSetLayout())); + } + _batches.push_back(curdrawBatch); +} + +void Batcher2d::generateBatchForMiddleware(RenderEntity* entity, RenderDrawInfo* drawInfo) { + auto layer = entity->getNode()->getLayer(); + auto* material = drawInfo->getMaterial(); + auto* texture = drawInfo->getTexture(); + auto* sampler = drawInfo->getSampler(); + auto* meshBuffer = drawInfo->getMeshBuffer(); + // set meshbuffer offset + auto indexOffset = drawInfo->getIndexOffset(); + auto indexCount = drawInfo->getIbCount(); + indexOffset += indexCount; + if (meshBuffer->getIndexOffset() < indexOffset) { + meshBuffer->setIndexOffset(indexOffset); + } + + meshBuffer->setDirty(true); + gfx::InputAssembler* ia = meshBuffer->requireFreeIA(getDevice()); + + // stencilstage + auto stencilStage = _stencilManager->getStencilStage(); + gfx::DepthStencilState* depthStencil = _stencilManager->getDepthStencilState(stencilStage, material); + ccstd::hash_t dssHash = _stencilManager->getStencilHash(stencilStage); + + auto* curdrawBatch = _drawBatchPool.alloc(); + curdrawBatch->setVisFlags(_currLayer); + curdrawBatch->setInputAssembler(ia); + curdrawBatch->setFirstIndex(drawInfo->getIndexOffset()); + curdrawBatch->setIndexCount(drawInfo->getIbCount()); + curdrawBatch->fillPass(material, depthStencil, dssHash); + const auto& pass = curdrawBatch->getPasses().at(0); + if (entity->getUseLocal()) { + drawInfo->updateLocalDescriptorSet(entity->getNode(), pass->getLocalSetLayout()); + curdrawBatch->setDescriptorSet(drawInfo->getLocalDes()); + } else { + curdrawBatch->setDescriptorSet(getDescriptorSet(texture, sampler, pass->getLocalSetLayout())); + } + _batches.push_back(curdrawBatch); + // make sure next generateBatch return. + resetRenderStates(); + _currMeshBuffer = nullptr; +} + +void Batcher2d::resetRenderStates() { + _currMaterial = nullptr; + _currTexture = nullptr; + _currSampler = nullptr; + _currSamplerHash = 0; + _currLayer = 0; + _currEntity = nullptr; + _currDrawInfo = nullptr; +} + +gfx::DescriptorSet* Batcher2d::getDescriptorSet(gfx::Texture* texture, gfx::Sampler* sampler, const gfx::DescriptorSetLayout* dsLayout) { + ccstd::hash_t hash = 2; + size_t textureHash; + if (texture != nullptr) { + textureHash = boost::hash_value(texture); + ccstd::hash_combine(hash, textureHash); + } + if (sampler != nullptr) { + ccstd::hash_combine(hash, sampler->getHash()); + } + auto iter = _descriptorSetCache.find(hash); + if (iter != _descriptorSetCache.end()) { + if (texture != nullptr && sampler != nullptr) { + iter->second->bindTexture(static_cast(pipeline::ModelLocalBindings::SAMPLER_SPRITE), texture); + iter->second->bindSampler(static_cast(pipeline::ModelLocalBindings::SAMPLER_SPRITE), sampler); + } + iter->second->forceUpdate(); + return iter->second; + } + _dsInfo.layout = dsLayout; + auto* ds = getDevice()->createDescriptorSet(_dsInfo); + + if (texture != nullptr && sampler != nullptr) { + ds->bindTexture(static_cast(pipeline::ModelLocalBindings::SAMPLER_SPRITE), texture); + ds->bindSampler(static_cast(pipeline::ModelLocalBindings::SAMPLER_SPRITE), sampler); + } + ds->update(); + _descriptorSetCache.emplace(hash, ds); + + return ds; +} + +void Batcher2d::releaseDescriptorSetCache(gfx::Texture* texture, gfx::Sampler* sampler) { + ccstd::hash_t hash = 2; + size_t textureHash; + if (texture != nullptr) { + textureHash = boost::hash_value(texture); + ccstd::hash_combine(hash, textureHash); + } + if (sampler != nullptr) { + ccstd::hash_combine(hash, sampler->getHash()); + } + auto iter = _descriptorSetCache.find(hash); + if (iter != _descriptorSetCache.end()) { + delete iter->second; + _descriptorSetCache.erase(hash); + } +} + +bool Batcher2d::initialize() { + _isInit = true; + return _isInit; +} + +void Batcher2d::update() { + fillBuffersAndMergeBatches(); + resetRenderStates(); +} + +void Batcher2d::uploadBuffers() { + if (_batches.empty()) { + return; + } + + for (auto& meshRenderData : _meshRenderDrawInfo) { + meshRenderData->uploadBuffers(); + } + + for (auto& map : _meshBuffersMap) { + for (auto& buffer : map.second) { + buffer->uploadBuffers(); + buffer->reset(); + } + } + updateDescriptorSet(); +} + +void Batcher2d::reset() { + for (auto& batch : _batches) { + batch->clear(); + _drawBatchPool.free(batch); + } + _batches.clear(); + + for (auto& meshRenderData : _meshRenderDrawInfo) { + meshRenderData->resetMeshIA(); + } + _meshRenderDrawInfo.clear(); + + // meshDataArray + for (auto& map : _meshBuffersMap) { + for (auto& buffer : map.second) { + if (buffer) { + buffer->resetIA(); + } + } + } + // meshBuffer cannot clear because it is not transported at every frame. + + _currMeshBuffer = nullptr; + _indexStart = 0; + _currHash = 0; + _currLayer = 0; + _currMaterial = nullptr; + _currTexture = nullptr; + _currSampler = nullptr; + + // stencilManager +} + +void Batcher2d::insertMaskBatch(RenderEntity* entity) { + generateBatch(_currEntity, _currDrawInfo); + resetRenderStates(); + createClearModel(); + _maskClearModel->setNode(entity->getNode()); + _maskClearModel->setTransform(entity->getNode()); + _stencilManager->pushMask(); + auto stage = _stencilManager->clear(entity); + + gfx::DepthStencilState* depthStencil = nullptr; + ccstd::hash_t dssHash = 0; + if (_maskClearMtl != nullptr) { + depthStencil = _stencilManager->getDepthStencilState(stage, _maskClearMtl); + dssHash = _stencilManager->getStencilHash(stage); + } + + // Model + if (_maskClearModel == nullptr) return; + auto stamp = CC_CURRENT_ENGINE()->getTotalFrames(); + _maskClearModel->updateTransform(stamp); + _maskClearModel->updateUBOs(stamp); + + const auto& subModelList = _maskClearModel->getSubModels(); + for (const auto& submodel : subModelList) { + auto* curdrawBatch = _drawBatchPool.alloc(); + curdrawBatch->setVisFlags(entity->getNode()->getLayer()); + curdrawBatch->setModel(_maskClearModel); + curdrawBatch->setInputAssembler(submodel->getInputAssembler()); + curdrawBatch->setDescriptorSet(submodel->getDescriptorSet()); + + curdrawBatch->fillPass(_maskClearMtl, depthStencil, dssHash, &(submodel->getPatches())); + _batches.push_back(curdrawBatch); + } + + _stencilManager->enterLevel(entity); +} + +void Batcher2d::createClearModel() { + if (_maskClearModel == nullptr) { + _maskClearMtl = BuiltinResMgr::getInstance()->get(ccstd::string("default-clear-stencil")); + + _maskClearModel = Root::getInstance()->createModel(); + uint32_t stride = 12; // vfmt + + auto* vertexBuffer = _device->createBuffer({ + gfx::BufferUsageBit::VERTEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE, + 4 * stride, + stride, + }); + const float vertices[] = {-1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0}; + vertexBuffer->update(vertices); + auto* indexBuffer = _device->createBuffer({ + gfx::BufferUsageBit::INDEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE, + 6 * sizeof(uint16_t), + sizeof(uint16_t), + }); + const uint16_t indices[] = {0, 2, 1, 2, 1, 3}; + indexBuffer->update(indices); + + gfx::BufferList vbReference; + vbReference.emplace_back(vertexBuffer); + _maskModelMesh = ccnew RenderingSubMesh(vbReference, _maskAttributes, _primitiveMode, indexBuffer); + _maskModelMesh->setSubMeshIdx(0); + + _maskClearModel->initSubModel(0, _maskModelMesh, _maskClearMtl); + } +} +} // namespace cc diff --git a/cocos/2d/renderer/Batcher2d.h b/cocos/2d/renderer/Batcher2d.h new file mode 100644 index 0000000..18acf6f --- /dev/null +++ b/cocos/2d/renderer/Batcher2d.h @@ -0,0 +1,202 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "2d/renderer/RenderDrawInfo.h" +#include "2d/renderer/RenderEntity.h" +#include "2d/renderer/UIMeshBuffer.h" +#include "base/Macros.h" +#include "base/Ptr.h" +#include "base/TypeDef.h" +#include "core/assets/Material.h" +#include "core/memop/Pool.h" +#include "renderer/gfx-base/GFXTexture.h" +#include "renderer/gfx-base/states/GFXSampler.h" +#include "scene/DrawBatch2D.h" + +namespace cc { +class Root; +using UIMeshBufferArray = ccstd::vector; +using UIMeshBufferMap = ccstd::unordered_map; + +class Batcher2d final { +public: + Batcher2d(); + explicit Batcher2d(Root* root); + ~Batcher2d(); + + void syncMeshBuffersToNative(uint16_t accId, ccstd::vector&& buffers); + + bool initialize(); + void update(); + void uploadBuffers(); + void reset(); + + void syncRootNodesToNative(ccstd::vector&& rootNodes); + void releaseDescriptorSetCache(gfx::Texture* texture, gfx::Sampler* sampler); + + UIMeshBuffer* getMeshBuffer(uint16_t accId, uint16_t bufferId); + gfx::Device* getDevice(); + inline ccstd::vector* getDefaultAttribute() { return &_attributes; } + + void updateDescriptorSet(); + + void fillBuffersAndMergeBatches(); + void walk(Node* node, float parentOpacity); + void handlePostRender(RenderEntity* entity); + void handleDrawInfo(RenderEntity* entity, RenderDrawInfo* drawInfo, Node* node); + void handleComponentDraw(RenderEntity* entity, RenderDrawInfo* drawInfo, Node* node); + void handleModelDraw(RenderEntity* entity, RenderDrawInfo* drawInfo); + void handleMiddlewareDraw(RenderEntity* entity, RenderDrawInfo* drawInfo); + void handleSubNode(RenderEntity* entity, RenderDrawInfo* drawInfo); + void generateBatch(RenderEntity* entity, RenderDrawInfo* drawInfo); + void generateBatchForMiddleware(RenderEntity* entity, RenderDrawInfo* drawInfo); + void resetRenderStates(); + +private: + bool _isInit = false; + + inline void fillIndexBuffers(RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static) + uint16_t* ib = drawInfo->getIDataBuffer(); + + UIMeshBuffer* buffer = drawInfo->getMeshBuffer(); + uint32_t indexOffset = buffer->getIndexOffset(); + + uint16_t* indexb = drawInfo->getIbBuffer(); + uint32_t indexCount = drawInfo->getIbCount(); + + memcpy(&ib[indexOffset], indexb, indexCount * sizeof(uint16_t)); + indexOffset += indexCount; + + buffer->setIndexOffset(indexOffset); + } + + inline void fillVertexBuffers(RenderEntity* entity, RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static) + Node* node = entity->getNode(); + const Mat4& matrix = node->getWorldMatrix(); + uint8_t stride = drawInfo->getStride(); + uint32_t size = drawInfo->getVbCount() * stride; + float* vbBuffer = drawInfo->getVbBuffer(); + for (int i = 0; i < size; i += stride) { + Render2dLayout* curLayout = drawInfo->getRender2dLayout(i); + // make sure that the layout of Vec3 is three consecutive floats + static_assert(sizeof(Vec3) == 3 * sizeof(float)); + // cast to reduce value copy instructions + reinterpret_cast(vbBuffer + i)->transformMat4(curLayout->position, matrix); + } + } + + inline void setIndexRange(RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static) + UIMeshBuffer* buffer = drawInfo->getMeshBuffer(); + uint32_t indexOffset = drawInfo->getIndexOffset(); + uint32_t indexCount = drawInfo->getIbCount(); + indexOffset += indexCount; + if (buffer->getIndexOffset() < indexOffset) { + buffer->setIndexOffset(indexOffset); + } + } + + inline void fillColors(RenderEntity* entity, RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static) + Color temp = entity->getColor(); + + uint8_t stride = drawInfo->getStride(); + uint32_t size = drawInfo->getVbCount() * stride; + float* vbBuffer = drawInfo->getVbBuffer(); + + uint32_t offset = 0; + for (int i = 0; i < size; i += stride) { + offset = i + 5; + vbBuffer[offset++] = static_cast(temp.r) / 255.0F; + vbBuffer[offset++] = static_cast(temp.g) / 255.0F; + vbBuffer[offset++] = static_cast(temp.b) / 255.0F; + vbBuffer[offset++] = entity->getOpacity(); + } + } + + void insertMaskBatch(RenderEntity* entity); + void createClearModel(); + + gfx::DescriptorSet* getDescriptorSet(gfx::Texture* texture, gfx::Sampler* sampler, const gfx::DescriptorSetLayout* dsLayout); + + StencilManager* _stencilManager{nullptr}; + + // weak reference + Root* _root{nullptr}; + // weak reference + ccstd::vector _rootNodeArr; + + // manage memory manually + ccstd::vector _batches; + memop::Pool _drawBatchPool; + + // weak reference + gfx::Device* _device{nullptr}; // use getDevice() + + // weak reference + RenderEntity* _currEntity{nullptr}; + // weak reference + RenderDrawInfo* _currDrawInfo{nullptr}; + // weak reference + UIMeshBuffer* _currMeshBuffer{nullptr}; + uint32_t _indexStart{0}; + ccstd::hash_t _currHash{0}; + uint32_t _currLayer{0}; + StencilStage _currStencilStage{StencilStage::DISABLED}; + + // weak reference + Material* _currMaterial{nullptr}; + // weak reference + gfx::Texture* _currTexture{nullptr}; + // weak reference + gfx::Sampler* _currSampler{nullptr}; + ccstd::hash_t _currSamplerHash{0}; + + // weak reference + ccstd::vector _meshRenderDrawInfo; + + // manage memory manually + ccstd::unordered_map _descriptorSetCache; + gfx::DescriptorSetInfo _dsInfo; + + UIMeshBufferMap _meshBuffersMap; + + // DefaultAttribute + ccstd::vector _attributes{ + gfx::Attribute{gfx::ATTR_NAME_POSITION, gfx::Format::RGB32F}, + gfx::Attribute{gfx::ATTR_NAME_TEX_COORD, gfx::Format::RG32F}, + gfx::Attribute{gfx::ATTR_NAME_COLOR, gfx::Format::RGBA32F}, + }; + + // Mask use + IntrusivePtr _maskClearModel; + IntrusivePtr _maskClearMtl; + IntrusivePtr _maskModelMesh; + ccstd::vector _maskAttributes{ + gfx::Attribute{gfx::ATTR_NAME_POSITION, gfx::Format::RGB32F}, + }; + gfx::PrimitiveMode _primitiveMode{gfx::PrimitiveMode::TRIANGLE_LIST}; + + CC_DISALLOW_COPY_MOVE_ASSIGN(Batcher2d); +}; +} // namespace cc diff --git a/cocos/2d/renderer/RenderDrawInfo.cpp b/cocos/2d/renderer/RenderDrawInfo.cpp new file mode 100644 index 0000000..94397e3 --- /dev/null +++ b/cocos/2d/renderer/RenderDrawInfo.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "2d/renderer/RenderDrawInfo.h" +#include "2d/renderer/Batcher2d.h" +#include "base/TypeDef.h" +#include "core/Root.h" +#include "renderer/gfx-base/GFXDevice.h" + +namespace cc { + +static gfx::DescriptorSetInfo gDsInfo; +static float matrixData[pipeline::UBOLocal::COUNT] = {0.F}; +void mat4ToFloatArray(const cc::Mat4& mat, float* out, index_t ofs = 0) { + memcpy(out + ofs, mat.m, 16 * sizeof(float)); +} + +RenderDrawInfo::RenderDrawInfo() { + _attrSharedBufferActor.initialize(&_drawInfoAttrs, sizeof(_drawInfoAttrs)); +} + +RenderDrawInfo::~RenderDrawInfo() { + destroy(); +} + +void RenderDrawInfo::changeMeshBuffer() { + CC_ASSERT(Root::getInstance()->getBatcher2D()); + _meshBuffer = Root::getInstance()->getBatcher2D()->getMeshBuffer(_drawInfoAttrs._accId, _drawInfoAttrs._bufferId); +} + +gfx::InputAssembler* RenderDrawInfo::requestIA(gfx::Device* device) { + CC_ASSERT(_drawInfoAttrs._isMeshBuffer && _drawInfoAttrs._drawInfoType == RenderDrawInfoType::COMP); + return initIAInfo(device); +} + +void RenderDrawInfo::uploadBuffers() { + CC_ASSERT(_drawInfoAttrs._isMeshBuffer && _drawInfoAttrs._drawInfoType == RenderDrawInfoType::COMP); + if (_drawInfoAttrs._vbCount == 0 || _drawInfoAttrs._ibCount == 0) return; + uint32_t size = _drawInfoAttrs._vbCount * 9 * sizeof(float); // magic Number + gfx::Buffer* vBuffer = _ia->getVertexBuffers()[0]; + vBuffer->resize(size); + vBuffer->update(_vDataBuffer); + gfx::Buffer* iBuffer = _ia->getIndexBuffer(); + uint32_t iSize = _drawInfoAttrs._ibCount * 2; + iBuffer->resize(iSize); + iBuffer->update(_iDataBuffer); +} + +void RenderDrawInfo::resetMeshIA() { // NOLINT(readability-make-member-function-const) + CC_ASSERT(_drawInfoAttrs._isMeshBuffer && _drawInfoAttrs._drawInfoType == RenderDrawInfoType::COMP); +} + +void RenderDrawInfo::destroy() { + _vb = nullptr; + _ib = nullptr; + _ia = nullptr; + if (_localDSBF) { + CC_SAFE_DELETE(_localDSBF->ds); + CC_SAFE_DELETE(_localDSBF->uboBuf); + CC_SAFE_DELETE(_localDSBF); + } +} + +gfx::InputAssembler* RenderDrawInfo::initIAInfo(gfx::Device* device) { + if (!_ia) { + gfx::InputAssemblerInfo iaInfo = {}; + uint32_t vbStride = 9 * sizeof(float); // magic Number + uint32_t ibStride = sizeof(uint16_t); + _vb = device->createBuffer({ + gfx::BufferUsageBit::VERTEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE | gfx::MemoryUsageBit::HOST, + vbStride * 3, + vbStride, + }); + _ib = device->createBuffer({ + gfx::BufferUsageBit::INDEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE | gfx::MemoryUsageBit::HOST, + ibStride * 3, + ibStride, + }); + + iaInfo.attributes = *(Root::getInstance()->getBatcher2D()->getDefaultAttribute()); + iaInfo.vertexBuffers.emplace_back(_vb); + iaInfo.indexBuffer = _ib; + + _ia = device->createInputAssembler(iaInfo); + } + return _ia; +} + +void RenderDrawInfo::updateLocalDescriptorSet(Node* transform, const gfx::DescriptorSetLayout* dsLayout) { + if (_localDSBF == nullptr) { + _localDSBF = new LocalDSBF(); + auto* device = Root::getInstance()->getDevice(); + gDsInfo.layout = dsLayout; + _localDSBF->ds = device->createDescriptorSet(gDsInfo); + _localDSBF->uboBuf = device->createBuffer({ + gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::HOST | gfx::MemoryUsageBit::DEVICE, + pipeline::UBOLocal::SIZE, + pipeline::UBOLocal::SIZE, + }); + } + if (_texture != nullptr && _sampler != nullptr) { + _localDSBF->ds->bindTexture(static_cast(pipeline::ModelLocalBindings::SAMPLER_SPRITE), _texture); + _localDSBF->ds->bindSampler(static_cast(pipeline::ModelLocalBindings::SAMPLER_SPRITE), _sampler); + } + _localDSBF->ds->bindBuffer(pipeline::UBOLocal::BINDING, _localDSBF->uboBuf); + _localDSBF->ds->update(); + const auto& worldMatrix = transform->getWorldMatrix(); + mat4ToFloatArray(worldMatrix, matrixData, pipeline::UBOLocal::MAT_WORLD_OFFSET); + _localDSBF->uboBuf->update(matrixData); +} + +} // namespace cc diff --git a/cocos/2d/renderer/RenderDrawInfo.h b/cocos/2d/renderer/RenderDrawInfo.h new file mode 100644 index 0000000..a06266b --- /dev/null +++ b/cocos/2d/renderer/RenderDrawInfo.h @@ -0,0 +1,300 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "2d/renderer/UIMeshBuffer.h" +#include "base/Ptr.h" +#include "base/Macros.h" +#include "base/TypeDef.h" +#include "bindings/utils/BindingUtils.h" +#include "core/ArrayBuffer.h" +#include "core/assets/Material.h" +#include "core/scene-graph/Node.h" +#include "math/Color.h" +#include "math/Vec2.h" +#include "math/Vec3.h" +#include "math/Vec4.h" +#include "renderer/gfx-base/states/GFXSampler.h" +#include "scene/Model.h" + +namespace cc { +struct Render2dLayout { + Vec3 position; + Vec2 uv; + Vec4 color; +}; + +enum class RenderDrawInfoType : uint8_t { + COMP, + MODEL, + MIDDLEWARE, + SUB_NODE, +}; + +struct LocalDSBF { + gfx::DescriptorSet* ds; + gfx::Buffer* uboBuf; +}; + +class Batcher2d; + +class RenderDrawInfo final { +public: + RenderDrawInfo(); + ~RenderDrawInfo(); + + inline uint32_t getDrawInfoType() const { return static_cast(_drawInfoAttrs._drawInfoType); } + inline void setDrawInfoType(uint32_t type) { + _drawInfoAttrs._drawInfoType = static_cast(type); + } + + inline uint16_t getAccId() const { return _drawInfoAttrs._accId; } + inline void setAccId(uint16_t id) { + _drawInfoAttrs._accId = id; + } + + inline uint16_t getBufferId() const { return _drawInfoAttrs._bufferId; } + inline void setBufferId(uint16_t bufferId) { + _drawInfoAttrs._bufferId = bufferId; + } + + inline uint32_t getVertexOffset() const { return _drawInfoAttrs._vertexOffset; } + inline void setVertexOffset(uint32_t vertexOffset) { + _drawInfoAttrs._vertexOffset = vertexOffset; + } + + inline uint32_t getIndexOffset() const { return _drawInfoAttrs._indexOffset; } + inline void setIndexOffset(uint32_t indexOffset) { + _drawInfoAttrs._indexOffset = indexOffset; + } + + inline uint32_t getVbCount() const { return _drawInfoAttrs._vbCount; } + inline void setVbCount(uint32_t vbCount) { + _drawInfoAttrs._vbCount = vbCount; + } + + inline uint32_t getIbCount() const { return _drawInfoAttrs._ibCount; } + inline void setIbCount(uint32_t ibCount) { + _drawInfoAttrs._ibCount = ibCount; + } + + inline bool getVertDirty() const { return _drawInfoAttrs._vertDirty; } + inline void setVertDirty(bool val) { + _drawInfoAttrs._vertDirty = val; + } + + inline ccstd::hash_t getDataHash() const { return _drawInfoAttrs._dataHash; } + inline void setDataHash(ccstd::hash_t dataHash) { + _drawInfoAttrs._dataHash = dataHash; + } + + inline bool getIsMeshBuffer() const { return _drawInfoAttrs._isMeshBuffer; } + inline void setIsMeshBuffer(bool isMeshBuffer) { + _drawInfoAttrs._isMeshBuffer = isMeshBuffer; + } + + inline uint8_t getStride() const { return _drawInfoAttrs._stride; } + inline void setStride(uint8_t stride) { + _drawInfoAttrs._stride = stride; + } + + inline Material* getMaterial() const { return _material; } + inline void setMaterial(Material* material) { + _material = material; + } + + inline void setMeshBuffer(UIMeshBuffer* meshBuffer) { + _meshBuffer = meshBuffer; + } + inline UIMeshBuffer* getMeshBuffer() const { + return _meshBuffer; + } + + inline float* getVDataBuffer() const { + return _vDataBuffer; + } + inline void setVDataBuffer(float* vDataBuffer) { + _vDataBuffer = vDataBuffer; + } + inline uint16_t* getIDataBuffer() const { + return _iDataBuffer; + } + + inline void setIDataBuffer(uint16_t* iDataBuffer) { + _iDataBuffer = iDataBuffer; + } + + inline gfx::Texture* getTexture() const { + return _texture; + } + + inline void setTexture(gfx::Texture* texture) { + _texture = texture; + } + + inline gfx::Sampler* getSampler() const { + return _sampler; + } + + inline void setSampler(gfx::Sampler* sampler) { + _sampler = sampler; + } + + inline float* getVbBuffer() const { + return _vbBuffer; + } + + inline void setVbBuffer(float* vbBuffer) { + _vbBuffer = vbBuffer; + } + + inline uint16_t* getIbBuffer() const { + return _ibBuffer; + } + + inline void setIbBuffer(uint16_t* ibBuffer) { + _ibBuffer = ibBuffer; + } + + inline scene::Model* getModel() const { + CC_ASSERT_EQ(_drawInfoAttrs._drawInfoType, RenderDrawInfoType::MODEL); + return _model; + } + + inline void setModel(scene::Model* model) { + CC_ASSERT_EQ(_drawInfoAttrs._drawInfoType, RenderDrawInfoType::MODEL); + if (_drawInfoAttrs._drawInfoType == RenderDrawInfoType::MODEL) { + _model = model; + } + } + + inline Node* getSubNode() const { + CC_ASSERT_EQ(_drawInfoAttrs._drawInfoType, RenderDrawInfoType::SUB_NODE); + return _subNode; + } + inline void setSubNode(Node* node) { + CC_ASSERT_EQ(_drawInfoAttrs._drawInfoType, RenderDrawInfoType::SUB_NODE); + _subNode = node; + } + + void changeMeshBuffer(); + + inline RenderDrawInfoType getEnumDrawInfoType() const { return _drawInfoAttrs._drawInfoType; } + + inline void setRender2dBufferToNative(uint8_t* buffer) { // NOLINT(bugprone-easily-swappable-parameters) + CC_ASSERT(_drawInfoAttrs._drawInfoType == RenderDrawInfoType::COMP && !_drawInfoAttrs._isMeshBuffer); + _sharedBuffer = buffer; + } + + inline Render2dLayout* getRender2dLayout(uint32_t dataOffset) const { + CC_ASSERT(_drawInfoAttrs._drawInfoType == RenderDrawInfoType::COMP && !_drawInfoAttrs._isMeshBuffer); + return reinterpret_cast(_sharedBuffer + dataOffset * sizeof(float)); + } + + inline se::Object* getAttrSharedBufferForJS() const { return _attrSharedBufferActor.getSharedArrayBufferObject(); } + + gfx::InputAssembler* requestIA(gfx::Device* device); + void uploadBuffers(); + void resetMeshIA(); + + inline gfx::DescriptorSet* getLocalDes() { return _localDSBF->ds; } + void updateLocalDescriptorSet(Node* transform, const gfx::DescriptorSetLayout* dsLayout); + + inline void resetDrawInfo() { + destroy(); + + _drawInfoAttrs._bufferId = 0; + _drawInfoAttrs._accId = 0; + _drawInfoAttrs._vertexOffset = 0; + _drawInfoAttrs._indexOffset = 0; + _drawInfoAttrs._vbCount = 0; + _drawInfoAttrs._ibCount = 0; + _drawInfoAttrs._stride = 0; + _drawInfoAttrs._dataHash = 0; + _drawInfoAttrs._vertDirty = false; + _drawInfoAttrs._isMeshBuffer = false; + + _vbBuffer = nullptr; + _ibBuffer = nullptr; + _vDataBuffer = nullptr; + _iDataBuffer = nullptr; + _material = nullptr; + _texture = nullptr; + _sampler = nullptr; + _subNode = nullptr; + _model = nullptr; + _sharedBuffer = nullptr; + } + +private: + CC_DISALLOW_COPY_MOVE_ASSIGN(RenderDrawInfo); + void destroy(); + + gfx::InputAssembler* initIAInfo(gfx::Device* device); + + struct DrawInfoAttrs { + RenderDrawInfoType _drawInfoType{RenderDrawInfoType::COMP}; + bool _vertDirty{false}; + bool _isMeshBuffer{false}; + uint8_t _stride{0}; + uint16_t _bufferId{0}; + uint16_t _accId{0}; + uint32_t _vertexOffset{0}; + uint32_t _indexOffset{0}; + uint32_t _vbCount{0}; + uint32_t _ibCount{0}; + ccstd::hash_t _dataHash{0}; + } _drawInfoAttrs{}; + + bindings::NativeMemorySharedToScriptActor _attrSharedBufferActor; + // weak reference + Material* _material{nullptr}; + // weak reference + float* _vDataBuffer{nullptr}; + // weak reference + uint16_t* _iDataBuffer{nullptr}; + // weak reference + UIMeshBuffer* _meshBuffer{nullptr}; + // weak reference + gfx::Texture* _texture{nullptr}; + // weak reference + gfx::Sampler* _sampler{nullptr}; + // weak reference + float* _vbBuffer{nullptr}; + // weak reference + uint16_t* _ibBuffer{nullptr}; + + union { + Node* _subNode{nullptr}; + scene::Model* _model; + uint8_t* _sharedBuffer; + }; + LocalDSBF* _localDSBF{nullptr}; + + // ia + IntrusivePtr _ia; + IntrusivePtr _vb; + IntrusivePtr _ib; +}; +} // namespace cc diff --git a/cocos/2d/renderer/RenderEntity.cpp b/cocos/2d/renderer/RenderEntity.cpp new file mode 100644 index 0000000..3010e7e --- /dev/null +++ b/cocos/2d/renderer/RenderEntity.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "2d/renderer/RenderEntity.h" +#include "2d/renderer/Batcher2d.h" +#include "bindings/utils/BindingUtils.h" + +namespace cc { +RenderEntity::RenderEntity(RenderEntityType type) : _renderEntityType(type) { + if (type == RenderEntityType::STATIC) { + ccnew_placement(&_staticDrawInfos) std::array(); + } else { + ccnew_placement(&_dynamicDrawInfos) ccstd::vector(); + } + _entitySharedBufferActor.initialize(&_entityAttrLayout, sizeof(EntityAttrLayout)); +} + +RenderEntity::~RenderEntity() { + if (_renderEntityType == RenderEntityType::STATIC) { + _staticDrawInfos.~array(); + } else { + _dynamicDrawInfos.~vector(); + } +}; + +void RenderEntity::addDynamicRenderDrawInfo(RenderDrawInfo* drawInfo) { + CC_ASSERT_NE(_renderEntityType, RenderEntityType::STATIC); + _dynamicDrawInfos.push_back(drawInfo); +} +void RenderEntity::setDynamicRenderDrawInfo(RenderDrawInfo* drawInfo, uint32_t index) { + CC_ASSERT_NE(_renderEntityType, RenderEntityType::STATIC); + if (index < _dynamicDrawInfos.size()) { + _dynamicDrawInfos[index] = drawInfo; + } +} +void RenderEntity::removeDynamicRenderDrawInfo() { + CC_ASSERT_NE(_renderEntityType, RenderEntityType::STATIC); + if (_dynamicDrawInfos.empty()) return; + _dynamicDrawInfos.pop_back(); // warning: memory leaking & crash +} + +void RenderEntity::clearDynamicRenderDrawInfos() { + CC_ASSERT_NE(_renderEntityType, RenderEntityType::STATIC); + _dynamicDrawInfos.clear(); +} + +void RenderEntity::clearStaticRenderDrawInfos() { + CC_ASSERT_EQ(_renderEntityType, RenderEntityType::STATIC); + + for (uint32_t i = 0; i < _staticDrawInfoSize; i++) { + RenderDrawInfo& drawInfo = _staticDrawInfos[i]; + drawInfo.resetDrawInfo(); + } + _staticDrawInfoSize = 0; +} + +void RenderEntity::setNode(Node* node) { + if (_node) { + _node->setUserData(nullptr); + } + _node = node; + if (_node) { + _node->setUserData(this); + } +} + +void RenderEntity::setRenderTransform(Node* renderTransform) { + _renderTransform = renderTransform; +} + +RenderDrawInfo* RenderEntity::getDynamicRenderDrawInfo(uint32_t index) { + CC_ASSERT_NE(_renderEntityType, RenderEntityType::STATIC); + if (index >= _dynamicDrawInfos.size()) { + return nullptr; + } + return _dynamicDrawInfos[index]; +} +ccstd::vector& RenderEntity::getDynamicRenderDrawInfos() { + CC_ASSERT_NE(_renderEntityType, RenderEntityType::STATIC); + return _dynamicDrawInfos; +} +void RenderEntity::setStaticDrawInfoSize(uint32_t size) { + CC_ASSERT(_renderEntityType == RenderEntityType::STATIC && size <= RenderEntity::STATIC_DRAW_INFO_CAPACITY); + _staticDrawInfoSize = size; +} +RenderDrawInfo* RenderEntity::getStaticRenderDrawInfo(uint32_t index) { + CC_ASSERT(_renderEntityType == RenderEntityType::STATIC && index < _staticDrawInfoSize); + return &(_staticDrawInfos[index]); +} +std::array& RenderEntity::getStaticRenderDrawInfos() { + CC_ASSERT_EQ(_renderEntityType, RenderEntityType::STATIC); + return _staticDrawInfos; +} +} // namespace cc diff --git a/cocos/2d/renderer/RenderEntity.h b/cocos/2d/renderer/RenderEntity.h new file mode 100644 index 0000000..c5e4700 --- /dev/null +++ b/cocos/2d/renderer/RenderEntity.h @@ -0,0 +1,158 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include +#include "2d/renderer/RenderDrawInfo.h" +#include "2d/renderer/StencilManager.h" +#include "base/Macros.h" +#include "base/TypeDef.h" +#include "bindings/utils/BindingUtils.h" +#include "core/ArrayBuffer.h" +#include "core/scene-graph/Node.h" + +namespace cc { +class Batcher2d; + +enum class RenderEntityType : uint8_t { + STATIC, + DYNAMIC, + CROSSED, +}; + +enum class MaskMode : uint8_t { + NONE, + MASK, + MASK_INVERTED, + MASK_NODE, + MASK_NODE_INVERTED +}; + +struct EntityAttrLayout { + float localOpacity{1.0F}; + uint8_t colorR{255}; + uint8_t colorG{255}; + uint8_t colorB{255}; + uint8_t colorA{255}; + uint8_t maskMode{0}; + uint8_t colorDirtyBit{1}; + uint8_t enabledIndex{0}; + uint8_t useLocal{0}; +}; + +class RenderEntity final : public Node::UserData { +public: + static constexpr uint32_t STATIC_DRAW_INFO_CAPACITY = 4; + + explicit RenderEntity(RenderEntityType type); + ~RenderEntity() override; + + void addDynamicRenderDrawInfo(RenderDrawInfo* drawInfo); + void setDynamicRenderDrawInfo(RenderDrawInfo* drawInfo, uint32_t index); + void removeDynamicRenderDrawInfo(); + void clearDynamicRenderDrawInfos(); + void clearStaticRenderDrawInfos(); + + inline bool getIsMask() const { + return static_cast(_entityAttrLayout.maskMode) == MaskMode::MASK || static_cast(_entityAttrLayout.maskMode) == MaskMode::MASK_INVERTED; + } + + inline bool getIsSubMask() const { + return static_cast(_entityAttrLayout.maskMode) == MaskMode::MASK_NODE || static_cast(_entityAttrLayout.maskMode) == MaskMode::MASK_NODE_INVERTED; + } + + inline bool getIsMaskInverted() const { + return static_cast(_entityAttrLayout.maskMode) == MaskMode::MASK_INVERTED || static_cast(_entityAttrLayout.maskMode) == MaskMode::MASK_NODE_INVERTED; + } + + inline bool getUseLocal() const { return _entityAttrLayout.useLocal; } + inline void setUseLocal(bool useLocal) { + _entityAttrLayout.useLocal = useLocal; + } + + inline Node* getNode() const { return _node; } + void setNode(Node* node); + + inline Node* getRenderTransform() const { return _renderTransform; } + void setRenderTransform(Node* renderTransform); + + inline uint32_t getStencilStage() const { return static_cast(_stencilStage); } + inline void setStencilStage(uint32_t stage) { + _stencilStage = static_cast(stage); + } + inline StencilStage getEnumStencilStage() const { return _stencilStage; } + inline void setEnumStencilStage(StencilStage stage) { + _stencilStage = stage; + } + + inline RenderEntityType getRenderEntityType() const { return _renderEntityType; }; + + inline uint32_t getStaticDrawInfoSize() const { return _staticDrawInfoSize; }; + void setStaticDrawInfoSize(uint32_t size); + + RenderDrawInfo* getStaticRenderDrawInfo(uint32_t index); + std::array& getStaticRenderDrawInfos(); + RenderDrawInfo* getDynamicRenderDrawInfo(uint32_t index); + ccstd::vector& getDynamicRenderDrawInfos(); + + inline se::Object* getEntitySharedBufferForJS() const { return _entitySharedBufferActor.getSharedArrayBufferObject(); } + inline bool getColorDirty() const { return _entityAttrLayout.colorDirtyBit != 0; } + inline void setColorDirty(bool dirty) { _entityAttrLayout.colorDirtyBit = dirty ? 1 : 0; } + inline bool getVBColorDirty() const { return _vbColorDirty; } + inline void setVBColorDirty(bool vbColorDirty) { _vbColorDirty = vbColorDirty; } + inline Color getColor() const { return Color(_entityAttrLayout.colorR, _entityAttrLayout.colorG, _entityAttrLayout.colorB, _entityAttrLayout.colorA); } + inline float getColorAlpha() const { return static_cast(_entityAttrLayout.colorA) / 255.F; } + inline float getLocalOpacity() const { return _entityAttrLayout.localOpacity; } + inline float getOpacity() const { return _opacity; } + inline void setOpacity(float opacity) { _opacity = opacity; } + inline bool isEnabled() const { return _entityAttrLayout.enabledIndex != 0; } + inline uint32_t getRenderDrawInfosSize() const { + return _renderEntityType == RenderEntityType::STATIC ? _staticDrawInfoSize : static_cast(_dynamicDrawInfos.size()); + } + inline RenderDrawInfo* getRenderDrawInfoAt(uint32_t index) { + return _renderEntityType == RenderEntityType::STATIC ? &(_staticDrawInfos[index]) : _dynamicDrawInfos[index]; + } + +private: + CC_DISALLOW_COPY_MOVE_ASSIGN(RenderEntity); + // weak reference + Node* _node{nullptr}; + + // weak reference + Node* _renderTransform{nullptr}; + + EntityAttrLayout _entityAttrLayout; + float _opacity{1.0F}; + + bindings::NativeMemorySharedToScriptActor _entitySharedBufferActor; + union { + std::array _staticDrawInfos; + ccstd::vector _dynamicDrawInfos; + }; + StencilStage _stencilStage{StencilStage::DISABLED}; + RenderEntityType _renderEntityType{RenderEntityType::STATIC}; + uint8_t _staticDrawInfoSize{0}; + bool _vbColorDirty{true}; +}; +} // namespace cc diff --git a/cocos/2d/renderer/StencilManager.cpp b/cocos/2d/renderer/StencilManager.cpp new file mode 100644 index 0000000..e9f0f4b --- /dev/null +++ b/cocos/2d/renderer/StencilManager.cpp @@ -0,0 +1,164 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "StencilManager.h" +#include "2d/renderer/RenderEntity.h" + +namespace cc { +namespace { +StencilManager* instance = nullptr; +} + +StencilManager* StencilManager::getInstance() { + if (instance == nullptr) { + instance = new StencilManager(); + } + return instance; +} + +StencilManager::~StencilManager() { + for (auto pair : _cacheStateMap) { + CC_SAFE_DELETE(pair.second); + } + + for (auto pair : _cacheStateMapWithDepth) { + CC_SAFE_DELETE(pair.second); + } +} + +StencilStage StencilManager::clear(RenderEntity* entity) { // NOLINT(readability-convert-member-functions-to-static) + bool inverted = entity->getIsMaskInverted(); + return inverted ? StencilStage::CLEAR_INVERTED : StencilStage::CLEAR; +} + +void StencilManager::enterLevel(RenderEntity* entity) { // NOLINT(readability-convert-member-functions-to-static) + bool inverted = entity->getIsMaskInverted(); + entity->setEnumStencilStage(inverted ? StencilStage::ENTER_LEVEL_INVERTED : StencilStage::ENTER_LEVEL); +} + +gfx::DepthStencilState* StencilManager::getDepthStencilState(StencilStage stage, Material* mat) { + uint32_t key = 0; + bool depthTest = false; + bool depthWrite = false; + gfx::ComparisonFunc depthFunc = gfx::ComparisonFunc::LESS; + auto* cacheMap = &_cacheStateMap; + + if (mat && !mat->getPasses()->empty()) { + IntrusivePtr& pass = mat->getPasses()->at(0); + const gfx::DepthStencilState* dss = pass->getDepthStencilState(); + uint32_t depthTestValue = 0; + uint32_t depthWriteValue = 0; + if (dss->depthTest) { + depthTestValue = 1; + } + if (dss->depthWrite) { + depthWriteValue = 1; + } + key = (depthTestValue) | (depthWriteValue << 1) | (static_cast(dss->depthFunc) << 2) | (static_cast(stage) << 6) | (_maskStackSize << 9); + + depthTest = dss->depthTest; + depthWrite = static_cast(dss->depthWrite); + depthFunc = dss->depthFunc; + cacheMap = &_cacheStateMapWithDepth; + + } else { + key = ((static_cast(stage)) << 16) | (_maskStackSize); + } + + auto iter = cacheMap->find(key); + if (iter != cacheMap->end()) { + return iter->second; + } + + setDepthStencilStateFromStage(stage); + + auto* depthStencilState = ccnew gfx::DepthStencilState(); + depthStencilState->depthTest = depthTest; + depthStencilState->depthWrite = depthWrite; + depthStencilState->depthFunc = depthFunc; + depthStencilState->stencilTestFront = _stencilPattern.stencilTest; + depthStencilState->stencilFuncFront = _stencilPattern.func; + depthStencilState->stencilReadMaskFront = _stencilPattern.stencilMask; + depthStencilState->stencilWriteMaskFront = _stencilPattern.writeMask; + depthStencilState->stencilFailOpFront = _stencilPattern.failOp; + depthStencilState->stencilZFailOpFront = _stencilPattern.zFailOp; + depthStencilState->stencilPassOpFront = _stencilPattern.passOp; + depthStencilState->stencilRefFront = _stencilPattern.ref; + depthStencilState->stencilTestBack = _stencilPattern.stencilTest; + depthStencilState->stencilFuncBack = _stencilPattern.func; + depthStencilState->stencilReadMaskBack = _stencilPattern.stencilMask; + depthStencilState->stencilWriteMaskBack = _stencilPattern.writeMask; + depthStencilState->stencilFailOpBack = _stencilPattern.failOp; + depthStencilState->stencilZFailOpBack = _stencilPattern.zFailOp; + depthStencilState->stencilPassOpBack = _stencilPattern.passOp; + depthStencilState->stencilRefBack = _stencilPattern.ref; + + const auto& pair = std::pair(key, depthStencilState); + cacheMap->insert(pair); + + return depthStencilState; +} + +void StencilManager::setDepthStencilStateFromStage(StencilStage stage) { + StencilEntity& pattern = _stencilPattern; + + if (stage == StencilStage::DISABLED) { + pattern.stencilTest = false; + pattern.func = gfx::ComparisonFunc::ALWAYS; + pattern.failOp = gfx::StencilOp::KEEP; + pattern.stencilMask = pattern.writeMask = 0xffff; + pattern.ref = 1; + } else { + pattern.stencilTest = true; + if (stage == StencilStage::ENABLED) { + pattern.func = gfx::ComparisonFunc::EQUAL; + pattern.failOp = gfx::StencilOp::KEEP; + pattern.stencilMask = pattern.ref = getStencilRef(); + pattern.writeMask = getWriteMask(); + } else if (stage == StencilStage::CLEAR) { + pattern.func = gfx::ComparisonFunc::NEVER; + pattern.failOp = gfx::StencilOp::ZERO; + pattern.writeMask = getWriteMask(); + pattern.stencilMask = getWriteMask(); + pattern.ref = getWriteMask(); + } else if (stage == StencilStage::CLEAR_INVERTED) { + pattern.func = gfx::ComparisonFunc::NEVER; + pattern.failOp = gfx::StencilOp::REPLACE; + pattern.writeMask = pattern.stencilMask = pattern.ref = getWriteMask(); + } else if (stage == StencilStage::ENTER_LEVEL) { + pattern.func = gfx::ComparisonFunc::NEVER; + pattern.failOp = gfx::StencilOp::REPLACE; + pattern.writeMask = pattern.stencilMask = pattern.ref = getWriteMask(); + } else if (stage == StencilStage::ENTER_LEVEL_INVERTED) { + pattern.func = gfx::ComparisonFunc::NEVER; + pattern.failOp = gfx::StencilOp::ZERO; + pattern.writeMask = pattern.stencilMask = pattern.ref = getWriteMask(); + } + } +} + +void StencilManager::setStencilStage(uint32_t stageIndex) { + _stage = static_cast(stageIndex); +} +} // namespace cc diff --git a/cocos/2d/renderer/StencilManager.h b/cocos/2d/renderer/StencilManager.h new file mode 100644 index 0000000..8853fbe --- /dev/null +++ b/cocos/2d/renderer/StencilManager.h @@ -0,0 +1,140 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" +#include "base/TypeDef.h" +#include "core/ArrayBuffer.h" +#include "core/assets/Material.h" +#include "renderer/gfx-base/GFXDef-common.h" +#include "scene/Pass.h" + +namespace cc { +class RenderEntity; +enum class StencilStage : uint8_t { + // Stencil disabled + DISABLED = 0, + // Clear stencil buffer + CLEAR = 1, + // Entering a new level, should handle new stencil + ENTER_LEVEL = 2, + // In content + ENABLED = 3, + // Exiting a level, should restore old stencil or disable + EXIT_LEVEL = 4, + // Clear stencil buffer & USE INVERTED + CLEAR_INVERTED = 5, + // Entering a new level & USE INVERTED + ENTER_LEVEL_INVERTED = 6 +}; + +struct StencilEntity { + uint32_t stencilTest{0}; + gfx::ComparisonFunc func{gfx::ComparisonFunc::ALWAYS}; + uint32_t stencilMask{0}; + uint32_t writeMask{0}; + gfx::StencilOp failOp{gfx::StencilOp::KEEP}; + gfx::StencilOp zFailOp{gfx::StencilOp::KEEP}; + gfx::StencilOp passOp{gfx::StencilOp::KEEP}; + uint32_t ref{0}; +}; + +class StencilManager final { +public: + static StencilManager* getInstance(); + StencilManager() = default; + ~StencilManager(); + + inline StencilStage getStencilStage() const { return _stage; } + + gfx::DepthStencilState* getDepthStencilState(StencilStage stage, Material* mat = nullptr); + void setDepthStencilStateFromStage(StencilStage stage); + + inline uint32_t getMaskStackSize() const { return _maskStackSize; } + inline void setMaskStackSize(uint32_t size) { + _maskStackSize = size; + } + + inline void pushMask() { + ++_maskStackSize; + } + + StencilStage clear(RenderEntity* entity); + void enterLevel(RenderEntity* entity); + + inline void enableMask() { + _stage = StencilStage::ENABLED; + } + + inline void exitMask() { + if (_maskStackSize == 0) { + return; + } + + --_maskStackSize; + if (_maskStackSize == 0) { + _stage = StencilStage::DISABLED; + } else { + _stage = StencilStage::ENABLED; + } + } + + inline uint32_t getWriteMask() const { + return 1 << (_maskStackSize - 1); + } + + inline uint32_t getExitWriteMask() const { + return 1 << _maskStackSize; + } + + inline uint32_t getStencilRef() const { + uint32_t result = 0; + for (uint32_t i = 0; i < _maskStackSize; i++) { + result += (1 << i); + } + return result; + } + + inline uint32_t getStencilHash(StencilStage stage) const { + return ((static_cast(stage)) << 8) | _maskStackSize; + } + + void setStencilStage(uint32_t stageIndex); + +private: + CC_DISALLOW_COPY_MOVE_ASSIGN(StencilManager); + + StencilEntity _stencilPattern; + ArrayBuffer::Ptr _stencilSharedBuffer; + + StencilStage _stage{StencilStage::DISABLED}; + + uint32_t _maskStackSize{0}; + + ccstd::unordered_map _cacheStateMap; + ccstd::unordered_map _cacheStateMapWithDepth; +}; +} // namespace cc diff --git a/cocos/2d/renderer/UIMeshBuffer.cpp b/cocos/2d/renderer/UIMeshBuffer.cpp new file mode 100644 index 0000000..bf8f307 --- /dev/null +++ b/cocos/2d/renderer/UIMeshBuffer.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "2d/renderer/UIMeshBuffer.h" +#include "renderer/gfx-base/GFXDevice.h" + +namespace cc { + +static uint32_t getAttributesStride(ccstd::vector& attrs) { + uint32_t stride = 0; + for (auto& attr : attrs) { + const auto& info = gfx::GFX_FORMAT_INFOS[static_cast(attr.format)]; + stride += info.size; + } + return stride; +} + +UIMeshBuffer::~UIMeshBuffer() { + destroy(); +} + +void UIMeshBuffer::setVData(float* vData) { + _vData = vData; +} + +void UIMeshBuffer::setIData(uint16_t* iData) { + _iData = iData; +} + +void UIMeshBuffer::initialize(ccstd::vector&& attrs, bool needCreateLayout) { + _attributes = attrs; + _vertexFormatBytes = getAttributesStride(attrs); + if (needCreateLayout) { + _meshBufferLayout = new MeshBufferLayout(); + } + _needDeleteLayout = needCreateLayout; +} + +void UIMeshBuffer::reset() { + setIndexOffset(0); + _dirty = false; +} + +void UIMeshBuffer::resetIA() { +} + +void UIMeshBuffer::destroy() { + reset(); + _attributes.clear(); + _vb = nullptr; + _ib = nullptr; + if (_needDeleteVData) { + delete _vData; + delete _iData; + } + _vData = nullptr; + _iData = nullptr; + // Destroy InputAssemblers + _ia = nullptr; + if (_needDeleteLayout) { + CC_SAFE_DELETE(_meshBufferLayout); + } +} + +void UIMeshBuffer::setDirty() { + _dirty = true; +} + +gfx::InputAssembler* UIMeshBuffer::requireFreeIA(gfx::Device* device) { + return createNewIA(device); +} + +void UIMeshBuffer::uploadBuffers() { + uint32_t byteOffset = getByteOffset(); + bool dirty = getDirty(); + if (_meshBufferLayout == nullptr || byteOffset == 0 || !dirty || !_ia) { + return; + } + + uint32_t indexCount = getIndexOffset(); + uint32_t byteCount = getByteOffset(); + + gfx::BufferList vBuffers = _ia->getVertexBuffers(); + if (!vBuffers.empty()) { + gfx::Buffer* vBuffer = vBuffers[0]; + if (byteCount > vBuffer->getSize()) { + vBuffer->resize(byteCount); + } + vBuffer->update(_vData); + } + gfx::Buffer* iBuffer = _ia->getIndexBuffer(); + if (indexCount * 2 > iBuffer->getSize()) { + iBuffer->resize(indexCount * 2); + } + iBuffer->update(_iData); + + setDirty(false); +} + +// use less +void UIMeshBuffer::recycleIA(gfx::InputAssembler* ia) { +} + +gfx::InputAssembler* UIMeshBuffer::createNewIA(gfx::Device* device) { + if (!_ia) { + uint32_t vbStride = _vertexFormatBytes; + uint32_t ibStride = sizeof(uint16_t); + + gfx::InputAssemblerInfo iaInfo = {}; + _vb = device->createBuffer({ + gfx::BufferUsageBit::VERTEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE | gfx::MemoryUsageBit::HOST, + vbStride * 3, + vbStride, + }); + _ib = device->createBuffer({ + gfx::BufferUsageBit::INDEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE | gfx::MemoryUsageBit::HOST, + ibStride * 3, + ibStride, + }); + + iaInfo.attributes = _attributes; + iaInfo.vertexBuffers.emplace_back(_vb); + iaInfo.indexBuffer = _ib; + _ia = device->createInputAssembler(iaInfo); + } + + return _ia; +} + +void UIMeshBuffer::syncSharedBufferToNative(uint32_t* buffer) { + _sharedBuffer = buffer; + parseLayout(); +} + +void UIMeshBuffer::parseLayout() { + _meshBufferLayout = reinterpret_cast(_sharedBuffer); +} + +void UIMeshBuffer::setByteOffset(uint32_t byteOffset) { + _meshBufferLayout->byteOffset = byteOffset; +} + +void UIMeshBuffer::setVertexOffset(uint32_t vertexOffset) { + _meshBufferLayout->vertexOffset = vertexOffset; +} + +void UIMeshBuffer::setIndexOffset(uint32_t indexOffset) { + _meshBufferLayout->indexOffset = indexOffset; +} + +void UIMeshBuffer::setDirty(bool dirty) const { + _meshBufferLayout->dirtyMark = dirty ? 1 : 0; +} + +} // namespace cc diff --git a/cocos/2d/renderer/UIMeshBuffer.h b/cocos/2d/renderer/UIMeshBuffer.h new file mode 100644 index 0000000..8e2bbef --- /dev/null +++ b/cocos/2d/renderer/UIMeshBuffer.h @@ -0,0 +1,100 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "base/Ptr.h" +#include "base/Macros.h" +#include "base/TypeDef.h" +#include "renderer/gfx-base/GFXInputAssembler.h" +#include "renderer/gfx-base/GFXDef-common.h" +#include "renderer/gfx-base/GFXBuffer.h" + +namespace cc { + +struct MeshBufferLayout { + uint32_t byteOffset; + uint32_t vertexOffset; + uint32_t indexOffset; + uint32_t dirtyMark; +}; + +class UIMeshBuffer final { +public: + UIMeshBuffer() = default; + ~UIMeshBuffer(); + + inline float* getVData() const { return _vData; } + void setVData(float* vData); + inline uint16_t* getIData() const { return _iData; } + void setIData(uint16_t* iData); + + void initialize(ccstd::vector&& attrs, bool needCreateLayout = false); + void reset(); + void destroy(); + void setDirty(); + void uploadBuffers(); + void syncSharedBufferToNative(uint32_t* buffer); + void resetIA(); + void recycleIA(gfx::InputAssembler* ia); + void parseLayout(); + + gfx::InputAssembler* requireFreeIA(gfx::Device* device); + gfx::InputAssembler* createNewIA(gfx::Device* device); + + inline uint32_t getByteOffset() const { return _meshBufferLayout->byteOffset; } + void setByteOffset(uint32_t byteOffset); + inline uint32_t getVertexOffset() const { return _meshBufferLayout->vertexOffset; } + void setVertexOffset(uint32_t vertexOffset); + inline uint32_t getIndexOffset() const { return _meshBufferLayout->indexOffset; } + void setIndexOffset(uint32_t indexOffset); + inline bool getDirty() const { return _meshBufferLayout->dirtyMark != 0; } + void setDirty(bool dirty) const; + inline const ccstd::vector& getAttributes() const { + return _attributes; + } + +protected: + CC_DISALLOW_COPY_MOVE_ASSIGN(UIMeshBuffer); + +private: + float* _vData{nullptr}; + uint16_t* _iData{nullptr}; + + MeshBufferLayout* _meshBufferLayout{nullptr}; + uint32_t* _sharedBuffer{nullptr}; + + uint32_t _vertexFormatBytes{0}; + uint32_t _initVDataCount{0}; + uint32_t _initIDataCount{0}; + + ccstd::vector _attributes; + IntrusivePtr _ia; + IntrusivePtr _vb; + IntrusivePtr _ib; + + bool _dirty{false}; + bool _needDeleteVData{false}; + bool _needDeleteLayout{false}; +}; +} // namespace cc diff --git a/cocos/2d/renderer/UIModelProxy.cpp b/cocos/2d/renderer/UIModelProxy.cpp new file mode 100644 index 0000000..11ae7af --- /dev/null +++ b/cocos/2d/renderer/UIModelProxy.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "UIModelProxy.h" +#include "2d/renderer/RenderEntity.h" +#include "core/assets/RenderingSubMesh.h" + +namespace cc { +UIModelProxy::UIModelProxy() { + _device = Root::getInstance()->getDevice(); +} + +UIModelProxy::~UIModelProxy() { + destroy(); +} + +void UIModelProxy::initModel(Node* node) { + _model = Root::getInstance()->createModel(); + _model->setNode(node); + _model->setTransform(node); + _node = node; +} + +void UIModelProxy::activeSubModels() { + if (_model == nullptr) return; + auto* entity = static_cast(_node->getUserData()); + auto drawInfoSize = entity->getDynamicRenderDrawInfos().size(); + auto subModelSize = _model->getSubModels().size(); + if (drawInfoSize > subModelSize) { + for (size_t i = subModelSize; i < drawInfoSize; i++) { + if (_model->getSubModels().size() <= i) { + RenderDrawInfo* drawInfo = entity->getDynamicRenderDrawInfo(static_cast(i)); + if (drawInfo == nullptr) { + return; + } + + auto* vertexBuffer = _device->createBuffer({ + gfx::BufferUsageBit::VERTEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE, + 65535 * _stride, + _stride, + }); + auto* indexBuffer = _device->createBuffer({ + gfx::BufferUsageBit::INDEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE, + 65535 * sizeof(uint16_t) * 2, + sizeof(uint16_t), + }); + gfx::BufferList vbReference; + vbReference.emplace_back(vertexBuffer); + + auto* renderMesh = ccnew RenderingSubMesh(vbReference, _attributes, _primitiveMode, indexBuffer); + renderMesh->setSubMeshIdx(0); + + _model->initSubModel(static_cast(i), renderMesh, drawInfo->getMaterial()); + _graphicsUseSubMeshes.emplace_back(renderMesh); + } + } + } +} + +void UIModelProxy::uploadData() { + auto* entity = static_cast(_node->getUserData()); + const auto& drawInfos = entity->getDynamicRenderDrawInfos(); + const auto& subModelList = _model->getSubModels(); + for (size_t i = 0; i < drawInfos.size(); i++) { + auto* drawInfo = drawInfos[i]; + auto* ia = subModelList.at(i)->getInputAssembler(); + if (drawInfo->getVertexOffset() <= 0 || drawInfo->getIndexOffset() <= 0) continue; + gfx::BufferList vBuffers = ia->getVertexBuffers(); + if (!vBuffers.empty()) { + auto size = drawInfo->getVertexOffset() * _stride; + // if (size > vBuffers[0]->getSize()) { + vBuffers[0]->resize(size); + // } + vBuffers[0]->update(drawInfo->getVDataBuffer()); // vdata + } + ia->setVertexCount(drawInfo->getVertexOffset()); // count + + gfx::Buffer* iBuffer = ia->getIndexBuffer(); + auto size = drawInfo->getIndexOffset() * 2; + // if (size > iBuffer->getSize()) { + iBuffer->resize(size); + // } + iBuffer->update(drawInfo->getIDataBuffer()); // idata + ia->setIndexCount(drawInfo->getIndexOffset()); // indexCount + // drawInfo->setModel(_model); // hack, render by model + } + + if (!drawInfos.empty()) { + drawInfos[0]->setModel(_model); + } +} + +void UIModelProxy::destroy() { + if (_model != nullptr) { + Root::getInstance()->destroyModel(_model); + _model = nullptr; + } + + for (auto& subMesh : _graphicsUseSubMeshes) { + subMesh->destroy(); + subMesh = nullptr; + } + _graphicsUseSubMeshes.clear(); + + _models.clear(); +} + +void UIModelProxy::clear() { +} + +// for ui model +void UIModelProxy::updateModels(scene::Model* model) { + _models.emplace_back(model); +} + +void UIModelProxy::attachDrawInfo() { + auto* entity = static_cast(_node->getUserData()); + auto& drawInfos = entity->getDynamicRenderDrawInfos(); + if (drawInfos.size() != _models.size()) return; + for (size_t i = 0; i < drawInfos.size(); i++) { + drawInfos[i]->setModel(_models[i]); + } +} + +void UIModelProxy::attachNode(Node* node) { + _node = node; +} + +void UIModelProxy::clearModels() { + _models.clear(); +} + +} // namespace cc diff --git a/cocos/2d/renderer/UIModelProxy.h b/cocos/2d/renderer/UIModelProxy.h new file mode 100644 index 0000000..c069980 --- /dev/null +++ b/cocos/2d/renderer/UIModelProxy.h @@ -0,0 +1,67 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "base/Macros.h" +#include "core/Root.h" +#include "scene/Model.h" + +namespace cc { +class UIModelProxy final { +public: + UIModelProxy(); + ~UIModelProxy(); + + void initModel(Node* node); + void activeSubModels(); + void uploadData(); + void destroy(); + void clear(); + inline scene::Model* getModel() const { return _model; } + // For UIModel + void updateModels(scene::Model* models); + void attachDrawInfo(); + void attachNode(Node* node); + void clearModels(); + +protected: + CC_DISALLOW_COPY_MOVE_ASSIGN(UIModelProxy); + +private: + Node* _node{nullptr}; + IntrusivePtr _model; + ccstd::vector> _graphicsUseSubMeshes{}; + // For UIModel + ccstd::vector _models{}; + + gfx::Device* _device{nullptr}; + uint32_t _stride{32}; + ccstd::vector _attributes{ + gfx::Attribute{gfx::ATTR_NAME_POSITION, gfx::Format::RGB32F}, + gfx::Attribute{gfx::ATTR_NAME_COLOR, gfx::Format::RGBA32F}, + gfx::Attribute{"a_dist", gfx::Format::R32F}, + }; + gfx::PrimitiveMode _primitiveMode{gfx::PrimitiveMode::TRIANGLE_LIST}; +}; +} // namespace cc diff --git a/cocos/3d/assets/Mesh.cpp b/cocos/3d/assets/Mesh.cpp new file mode 100644 index 0000000..08935f1 --- /dev/null +++ b/cocos/3d/assets/Mesh.cpp @@ -0,0 +1,1485 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "3d/assets/Mesh.h" +#include "3d/assets/Morph.h" +#include "3d/assets/Skeleton.h" +#include "3d/misc/BufferBlob.h" +#include "3d/misc/CreateMesh.h" +#include "base/std/hash/hash.h" +#include "core/DataView.h" +#include "core/assets/RenderingSubMesh.h" +#include "core/platform/Debug.h" +#include "math/Quaternion.h" +#include "math/Utils.h" +#include "renderer/gfx-base/GFXDevice.h" + +#include +#include + +#define CC_OPTIMIZE_MESH_DATA 0 + +namespace cc { + +namespace { + +uint32_t getOffset(const gfx::AttributeList &attributes, index_t attributeIndex) { + uint32_t result = 0; + for (index_t i = 0; i < attributeIndex; ++i) { + const auto &attribute = attributes[i]; + result += gfx::GFX_FORMAT_INFOS[static_cast(attribute.format)].size; + } + return result; +} + +// +uint32_t getComponentByteLength(gfx::Format format) { + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(format)]; + return info.size / info.count; +} + +using DataReaderCallback = std::function; + +DataReaderCallback getReader(const DataView &dataView, gfx::Format format) { + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(format)]; + const uint32_t stride = info.size / info.count; + + switch (info.type) { + case gfx::FormatType::UNORM: { + switch (stride) { + case 1: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getUint8(offset); }; + case 2: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getUint16(offset); }; + case 4: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getUint32(offset); }; + default: + break; + } + break; + } + case gfx::FormatType::SNORM: { + switch (stride) { + case 1: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getInt8(offset); }; + case 2: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getInt16(offset); }; + case 4: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getInt32(offset); }; + default: + break; + } + break; + } + case gfx::FormatType::INT: { + switch (stride) { + case 1: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getInt8(offset); }; + case 2: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getInt16(offset); }; + case 4: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getInt32(offset); }; + default: + break; + } + break; + } + case gfx::FormatType::UINT: { + switch (stride) { + case 1: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getUint8(offset); }; + case 2: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getUint16(offset); }; + case 4: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getUint32(offset); }; + default: + break; + } + break; + } + case gfx::FormatType::FLOAT: { + switch (stride) { + case 2: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getUint16(offset); }; + case 4: return [&](uint32_t offset) -> TypedArrayElementType { return dataView.getFloat32(offset); }; + default: + break; + } + break; + } + default: + break; + } + + return nullptr; +} + +using DataWritterCallback = std::function; + +DataWritterCallback getWriter(DataView &dataView, gfx::Format format) { + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(format)]; + const uint32_t stride = info.size / info.count; + + switch (info.type) { + case gfx::FormatType::UNORM: { + switch (stride) { + case 1: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setUint8(offset, ccstd::get(value)); }; + case 2: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setUint16(offset, ccstd::get(value)); }; + case 4: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setUint32(offset, ccstd::get(value)); }; + default: + break; + } + break; + } + case gfx::FormatType::SNORM: { + switch (stride) { + case 1: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setInt8(offset, ccstd::get(value)); }; + case 2: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setInt16(offset, ccstd::get(value)); }; + case 4: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setInt32(offset, ccstd::get(value)); }; + default: + break; + } + break; + } + case gfx::FormatType::INT: { + switch (stride) { + case 1: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setInt8(offset, ccstd::get(value)); }; + case 2: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setInt16(offset, ccstd::get(value)); }; + case 4: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setInt32(offset, ccstd::get(value)); }; + default: + break; + } + break; + } + case gfx::FormatType::UINT: { + switch (stride) { + case 1: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setUint8(offset, ccstd::get(value)); }; + case 2: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setUint16(offset, ccstd::get(value)); }; + case 4: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setUint32(offset, ccstd::get(value)); }; + default: + break; + } + break; + } + case gfx::FormatType::FLOAT: { + switch (stride) { + case 2: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setUint16(offset, ccstd::get(value)); }; + case 4: return [&](uint32_t offset, const TypedArrayElementType &value) { dataView.setFloat32(offset, ccstd::get(value)); }; + default: + break; + } + break; + } + default: + break; + } + + return nullptr; +} + +#if CC_OPTIMIZE_MESH_DATA +void checkAttributesNeedConvert(const gfx::AttributeList &orignalAttributes, // in + gfx::AttributeList &attributes, // in-out + ccstd::vector &attributeIndicsNeedConvert, // in-out + uint32_t &dstStride) { // in-out + attributeIndicsNeedConvert.clear(); + attributeIndicsNeedConvert.reserve(orignalAttributes.size()); + + uint32_t attributeIndex = 0; + for (auto &attribute : attributes) { + /* + NOTE: The size of RGB16F is 6 bytes, some Android devices may require 4 bytes alignment for attribute and Metal must require 4 bytes alignment. + Mesh will not be displayed correctly if setting non 4 bytes alignment (RGB16F is 6 bytes) on those devices. + And currently we depend on gfx::GFX_FORMAT_INFOS[format].size a lot, so we disable optimize NORMAL data temporarily before we find a better solution. + + if (attribute.name == gfx::ATTR_NAME_NORMAL) { + if (attribute.format == gfx::Format::RGB32F) { + attributeIndicsNeedConvert.emplace_back(attributeIndex); + attribute.format = gfx::Format::RGB16F; + #if (CC_PLATFORM == CC_PLATFORM_IOS) || (CC_PLATFORM == CC_PLATFORM_MACOS) + dstStride -= 4; // NOTE: Metal needs 4 bytes alignment + #else + dstStride -= 6; + #endif + } + } else */ + + if (attribute.name == gfx::ATTR_NAME_TEX_COORD || attribute.name == gfx::ATTR_NAME_TEX_COORD1 || attribute.name == gfx::ATTR_NAME_TEX_COORD2 || attribute.name == gfx::ATTR_NAME_TEX_COORD3 || attribute.name == gfx::ATTR_NAME_TEX_COORD4 || attribute.name == gfx::ATTR_NAME_TEX_COORD5 || attribute.name == gfx::ATTR_NAME_TEX_COORD6 || attribute.name == gfx::ATTR_NAME_TEX_COORD7 || attribute.name == gfx::ATTR_NAME_TEX_COORD8) { + if (attribute.format == gfx::Format::RG32F) { + attributeIndicsNeedConvert.emplace_back(attributeIndex); + attribute.format = gfx::Format::RG16F; + dstStride -= 4; + } + } else if (attribute.name == gfx::ATTR_NAME_TANGENT) { + if (attribute.format == gfx::Format::RGBA32F) { + attributeIndicsNeedConvert.emplace_back(attributeIndex); + attribute.format = gfx::Format::RGBA16F; + dstStride -= 8; + } + } + ++attributeIndex; + } +} + +void convertRGBA32FToRGBA16F(const float *src, uint16_t *dst) { + dst[0] = utils::rawHalfAsUint16(utils::floatToHalf(src[0])); + dst[1] = utils::rawHalfAsUint16(utils::floatToHalf(src[1])); + dst[2] = utils::rawHalfAsUint16(utils::floatToHalf(src[2])); + dst[3] = utils::rawHalfAsUint16(utils::floatToHalf(src[3])); +} + +void convertRGB32FToRGB16F(const float *src, uint16_t *dst) { + dst[0] = utils::rawHalfAsUint16(utils::floatToHalf(src[0])); + dst[1] = utils::rawHalfAsUint16(utils::floatToHalf(src[1])); + dst[2] = utils::rawHalfAsUint16(utils::floatToHalf(src[2])); +} + +void convertRG32FToRG16F(const float *src, uint16_t *dst) { + dst[0] = utils::rawHalfAsUint16(utils::floatToHalf(src[0])); + dst[1] = utils::rawHalfAsUint16(utils::floatToHalf(src[1])); +} + +#endif // #if CC_OPTIMIZE_MESH_DATA + +} // namespace + +void MeshUtils::dequantizeMesh(Mesh::IStruct &structInfo, Uint8Array &data) { + BufferBlob bufferBlob; + bufferBlob.setNextAlignment(0); + + using DataReaderCallback = std::function; + using DataWritterCallback = std::function; + + const auto transformVertex = + [](const DataReaderCallback &reader, + const DataWritterCallback &writer, + uint32_t count, + uint32_t components, + uint32_t componentSize, + uint32_t readerStride, + uint32_t writerStride) -> void { + for (uint32_t i = 0; i < count; ++i) { + for (uint32_t j = 0; j < components; ++j) { + const auto inputOffset = readerStride * i + componentSize * j; + const auto outputOffset = writerStride * i + componentSize * j; + writer(outputOffset, reader(inputOffset)); + } + } + }; + + const auto dequantizeHalf = + [](const DataReaderCallback &reader, + const DataWritterCallback &writer, + uint32_t count, + uint32_t components, + uint32_t readerStride, + uint32_t writerStride) -> void { + for (uint32_t i = 0; i < count; ++i) { + for (uint32_t j = 0; j < components; ++j) { + const auto inputOffset = readerStride * i + 2 * j; + const auto outputOffset = writerStride * i + 4 * j; + const auto val = mathutils::halfToFloat(ccstd::get(reader(inputOffset))); + writer(outputOffset, val); + } + } + }; + + for (auto &bundle : structInfo.vertexBundles) { + auto &view = bundle.view; + auto &attrs = bundle.attributes; + auto oldAttrs = attrs; + std::vector strides; + std::vector dequantizes; + std::vector readers; + strides.reserve(attrs.size()); + dequantizes.reserve(attrs.size()); + readers.reserve(attrs.size()); + for (uint32_t i = 0; i < attrs.size(); ++i) { + auto &attr = attrs[i]; + auto inputView = DataView(data.buffer(), view.offset + getOffset(oldAttrs, i)); + auto reader = getReader(inputView, attr.format); + auto dequantize = true; + switch (attr.format) { + case gfx::Format::R16F: + attr.format = gfx::Format::R32F; + break; + case gfx::Format::RG16F: + attr.format = gfx::Format::RG32F; + break; + case gfx::Format::RGB16F: + attr.format = gfx::Format::RGB32F; + break; + case gfx::Format::RGBA16F: + attr.format = gfx::Format::RGBA32F; + break; + default: + dequantize = false; + break; + } + strides.push_back(gfx::GFX_FORMAT_INFOS[static_cast(attr.format)].size); + dequantizes.push_back(dequantize); + readers.push_back(reader); + } + auto netStride = std::accumulate(strides.begin(), strides.end(), 0U); + auto vertData = Uint8Array(view.count * netStride); + for (uint32_t i = 0; i < attrs.size(); i++) { + const auto &attr = attrs[i]; + const auto &reader = readers[i]; + auto outputView = DataView(vertData.buffer(), getOffset(attrs, i)); + auto writer = getWriter(outputView, attr.format); + const auto &dequantize = dequantizes[i]; + const auto &formatInfo = gfx::GFX_FORMAT_INFOS[static_cast(attr.format)]; + if (dequantize) { + dequantizeHalf( + reader, + writer, + view.count, + formatInfo.count, + view.stride, + netStride); + } else { + transformVertex( + reader, + writer, + view.count, + formatInfo.count, + formatInfo.size / formatInfo.count, + view.stride, + netStride); + } + } + + bufferBlob.setNextAlignment(netStride); + Mesh::IBufferView vertexView; + vertexView.offset = bufferBlob.getLength(); + vertexView.length = view.count * netStride; + vertexView.count = view.count; + vertexView.stride = netStride; + bundle.view = vertexView; + bufferBlob.addBuffer(vertData.buffer()); + } + + for (auto &primitive : structInfo.primitives) { + if (!primitive.indexView.has_value()) { + continue; + } + auto &view = *primitive.indexView; + auto *buffer = ccnew ArrayBuffer(data.buffer()->getData() + view.offset, view.length); + bufferBlob.setNextAlignment(view.stride); + Mesh::IBufferView indexView; + indexView.offset = bufferBlob.getLength(); + indexView.length = view.length; + indexView.count = view.count; + indexView.stride = view.stride; + primitive.indexView = indexView; + bufferBlob.addBuffer(buffer); + } + + structInfo.quantized = false; + + data = Uint8Array(bufferBlob.getCombined()); +} + +Mesh::~Mesh() = default; + +ccstd::any Mesh::getNativeAsset() const { + return _data; //cjh FIXME: need copy? could be _data pointer? +} + +void Mesh::setNativeAsset(const ccstd::any &obj) { + auto *p = ccstd::any_cast(obj); + if (p != nullptr) { + _data = Uint8Array(p); + } +} + +uint32_t Mesh::getSubMeshCount() const { + return static_cast(_renderingSubMeshes.size()); +} + +const Vec3 *Mesh::getMinPosition() const { + return _struct.minPosition.has_value() ? &_struct.minPosition.value() : nullptr; +} + +const Vec3 *Mesh::getMaxPosition() const { + return _struct.maxPosition.has_value() ? &_struct.maxPosition.value() : nullptr; +} + +ccstd::hash_t Mesh::getHash() { + if (_hash == 0U) { + ccstd::hash_t seed = 666; + if (_data.buffer()) { + ccstd::hash_range(seed, _data.buffer()->getData(), _data.buffer()->getData() + _data.length()); + _hash = seed; + } else { + ccstd::hash_combine(_hash, seed); + } + } + + return _hash; +} + +const Mesh::JointBufferIndicesType &Mesh::getJointBufferIndices() { + if (!_jointBufferIndices.empty()) { + return _jointBufferIndices; + } + + _jointBufferIndices.reserve(_struct.primitives.size()); + for (auto &p : _struct.primitives) { + _jointBufferIndices.emplace_back(p.jointMapIndex.has_value() ? p.jointMapIndex.value() : 0); + } + + return _jointBufferIndices; +} + +void Mesh::initialize() { + if (_initialized) { + return; + } + + _initialized = true; + + if (_struct.compressed) { + // decompress + MeshUtils::inflateMesh(_struct, _data); + } + if (_struct.encoded) { + // decode + MeshUtils::decodeMesh(_struct, _data); + } + if (_struct.quantized && !hasFlag(gfx::Device::getInstance()->getFormatFeatures(gfx::Format::RG16F), gfx::FormatFeature::VERTEX_ATTRIBUTE)) { + MeshUtils::dequantizeMesh(_struct, _data); + } + + if (_struct.dynamic.has_value()) { + auto *device = gfx::Device::getInstance(); + gfx::BufferList vertexBuffers; + + for (const auto &vertexBundle : _struct.vertexBundles) { + auto *vertexBuffer = device->createBuffer({gfx::BufferUsageBit::VERTEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE, + vertexBundle.view.length, + vertexBundle.view.stride}); + vertexBuffers.emplace_back(vertexBuffer); + } + + for (auto i = 0U; i < _struct.primitives.size(); i++) { + const auto &primitive = _struct.primitives[i]; + const auto &indexView = primitive.indexView; + gfx::Buffer *indexBuffer = nullptr; + + if (indexView.has_value()) { + indexBuffer = device->createBuffer({gfx::BufferUsageBit::INDEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE, + indexView.value().length, + indexView.value().stride}); + } + + gfx::BufferList subVBs; + subVBs.reserve(primitive.vertexBundelIndices.size()); + for (const auto &idx : primitive.vertexBundelIndices) { + subVBs.emplace_back(vertexBuffers[idx]); + } + + gfx::AttributeList attributes; + for (const auto idx : primitive.vertexBundelIndices) { + const auto &vertexBundle = _struct.vertexBundles[idx]; + for (const auto &attr : vertexBundle.attributes) { + attributes.emplace_back(attr); + } + } + + auto *subMesh = ccnew RenderingSubMesh(subVBs, attributes, primitive.primitiveMode, indexBuffer); + subMesh->setDrawInfo(gfx::DrawInfo()); + subMesh->setMesh(this); + subMesh->setSubMeshIdx(static_cast(i)); + + _renderingSubMeshes.emplace_back(subMesh); + } + } else { + if (!_data.buffer()) { + return; + } + + auto &buffer = _data; + gfx::Device *gfxDevice = gfx::Device::getInstance(); + RefVector vertexBuffers{createVertexBuffers(gfxDevice, buffer.buffer())}; + RefVector indexBuffers; + ccstd::vector> subMeshes; + + for (size_t i = 0; i < _struct.primitives.size(); i++) { + auto &prim = _struct.primitives[i]; + if (prim.vertexBundelIndices.empty()) { + continue; + } + + gfx::Buffer *indexBuffer = nullptr; + if (prim.indexView.has_value()) { + const auto &idxView = prim.indexView.value(); + + uint32_t dstStride = idxView.stride; + uint32_t dstSize = idxView.length; + if (dstStride == 4) { + uint32_t vertexCount = _struct.vertexBundles[prim.vertexBundelIndices[0]].view.count; +#if CC_OPTIMIZE_MESH_DATA + if (vertexCount < 65536) { + dstStride >>= 1; // Reduce to short. + dstSize >>= 1; + } else if (!gfxDevice->hasFeature(gfx::Feature::ELEMENT_INDEX_UINT)) { + continue; // Ignore this primitive + } +#else + if (!gfxDevice->hasFeature(gfx::Feature::ELEMENT_INDEX_UINT)) { + if (vertexCount >= 65536) { + CC_LOG_WARNING("Device does not support UINT element index type and vertexCount (%u) is larger than ushort", vertexCount); + continue; + } + + dstStride >>= 1; // Reduce to short. + dstSize >>= 1; + } +#endif + } + + indexBuffer = gfxDevice->createBuffer(gfx::BufferInfo{ + gfx::BufferUsageBit::INDEX, + gfx::MemoryUsageBit::DEVICE, + dstSize, + dstStride, + }); + indexBuffers.pushBack(indexBuffer); + + const uint8_t *ib = buffer.buffer()->getData() + idxView.offset; + if (idxView.stride != dstStride) { + uint32_t ib16BitLength = idxView.length >> 1; + auto *ib16Bit = static_cast(CC_MALLOC(ib16BitLength)); + const auto *ib32Bit = reinterpret_cast(ib); + for (uint32_t j = 0, len = idxView.count; j < len; ++j) { + ib16Bit[j] = ib32Bit[j]; + } + + indexBuffer->update(ib16Bit, ib16BitLength); + CC_FREE(ib16Bit); + } else { + indexBuffer->update(ib); + } + } + + gfx::BufferList vbReference; + vbReference.reserve(prim.vertexBundelIndices.size()); + for (const auto &idx : prim.vertexBundelIndices) { + vbReference.emplace_back(vertexBuffers.at(idx)); + } + + gfx::AttributeList gfxAttributes; + if (!prim.vertexBundelIndices.empty()) { + uint32_t idx = prim.vertexBundelIndices[0]; + const IVertexBundle &vertexBundle = _struct.vertexBundles[idx]; + const gfx::AttributeList &attrs = vertexBundle.attributes; + gfxAttributes.resize(attrs.size()); + for (size_t j = 0; j < attrs.size(); ++j) { + const auto &attr = attrs[j]; + gfxAttributes[j] = gfx::Attribute{attr.name, attr.format, attr.isNormalized, attr.stream, attr.isInstanced, attr.location}; + } + } + + auto *subMesh = ccnew RenderingSubMesh(vbReference, gfxAttributes, prim.primitiveMode, indexBuffer); + subMesh->setMesh(this); + subMesh->setSubMeshIdx(static_cast(i)); + + subMeshes.emplace_back(subMesh); + } + + _renderingSubMeshes = subMeshes; + + if (_struct.morph.has_value()) { + morphRendering = createMorphRendering(this, gfxDevice); + } + + _isMeshDataUploaded = true; +#if !CC_EDITOR + if (!_allowDataAccess) { + releaseData(); + } +#endif + } +} + +void Mesh::destroyRenderingMesh() { + if (!_renderingSubMeshes.empty()) { + for (auto &submesh : _renderingSubMeshes) { + submesh->destroy(); + } + _renderingSubMeshes.clear(); + _initialized = false; + _isMeshDataUploaded = false; + } +} + +void Mesh::assign(const IStruct &structInfo, const Uint8Array &data) { + reset({structInfo, data}); +} + +void Mesh::reset(ICreateInfo &&info) { + destroyRenderingMesh(); + _struct = std::move(info.structInfo); + _data = std::move(info.data); + _hash = 0; +} + +Mesh::BoneSpaceBounds Mesh::getBoneSpaceBounds(Skeleton *skeleton) { + auto iter = _boneSpaceBounds.find(skeleton->getHash()); + if (iter != _boneSpaceBounds.end()) { + return iter->second; + } + Vec3 v32; + BoneSpaceBounds &bounds = _boneSpaceBounds[skeleton->getHash()]; + ccstd::vector valid; + const auto &bindposes = skeleton->getBindposes(); + valid.reserve(bindposes.size()); + for (size_t i = 0; i < bindposes.size(); i++) { + bounds.emplace_back(ccnew geometry::AABB{ + std::numeric_limits::infinity(), std::numeric_limits::infinity(), std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), -std::numeric_limits::infinity(), -std::numeric_limits::infinity()}); + valid.emplace_back(false); + } + const auto &primitives = _struct.primitives; + for (index_t p = 0; p < primitives.size(); p++) { + const auto joints = readAttribute(p, gfx::ATTR_NAME_JOINTS); + const auto weights = readAttribute(p, gfx::ATTR_NAME_WEIGHTS); + const auto positions = readAttribute(p, gfx::ATTR_NAME_POSITION); + if (joints.index() == 0 || weights.index() == 0 || positions.index() == 0) { + continue; + } + + uint32_t vertCount = std::min({getTypedArrayLength(joints) / 4, + getTypedArrayLength(weights) / 4, + getTypedArrayLength(positions) / 3}); + for (uint32_t i = 0; i < vertCount; i++) { + Vec3 v31{ + getTypedArrayValue(positions, 3 * i + 0), + getTypedArrayValue(positions, 3 * i + 1), + getTypedArrayValue(positions, 3 * i + 2)}; + + for (uint32_t j = 0; j < 4; ++j) { + const uint32_t idx = 4 * i + j; + const auto joint = getTypedArrayValue(joints, idx); + + if (std::fabs(getTypedArrayValue(weights, idx)) < FLT_EPSILON || joint >= bindposes.size()) { + continue; + } + + Vec3::transformMat4(v31, bindposes[joint], &v32); + valid[joint] = true; + auto &b = bounds[joint]; + Vec3::min(b->center, v32, &b->center); + Vec3::max(b->halfExtents, v32, &b->halfExtents); + } + } + } + for (size_t i = 0; i < bindposes.size(); i++) { + auto &b = bounds[i]; + if (!valid[i]) { + bounds[i] = {}; + } else { + geometry::AABB::fromPoints(b->center, b->halfExtents, b); + } + } + return bounds; +} + +bool Mesh::merge(Mesh *mesh, const Mat4 *worldMatrix /* = nullptr */, bool validate /* = false*/) { + if (validate) { + if (!validateMergingMesh(mesh)) { + return false; + } + } + + Vec3 vec3Temp; + Quaternion rotate; + geometry::AABB boundingBox; + if (worldMatrix != nullptr) { + worldMatrix->getRotation(&rotate); + } + if (!_initialized) { + auto structInfo = mesh->_struct; //NOTE: Need copy struct, so don't use referece + Uint8Array data{mesh->_data.slice()}; + if (worldMatrix != nullptr) { + if (structInfo.maxPosition.has_value() && structInfo.minPosition.has_value()) { + Vec3::add(structInfo.maxPosition.value(), structInfo.minPosition.value(), &boundingBox.center); + boundingBox.center.scale(0.5F); + Vec3::subtract(structInfo.maxPosition.value(), structInfo.minPosition.value(), &boundingBox.halfExtents); + boundingBox.halfExtents.scale(0.5F); + + boundingBox.transform(*worldMatrix, &boundingBox); + Vec3::add(boundingBox.center, boundingBox.halfExtents, &structInfo.maxPosition.value()); + Vec3::subtract(boundingBox.center, boundingBox.halfExtents, &structInfo.minPosition.value()); + } + for (auto &vtxBdl : structInfo.vertexBundles) { + for (int j = 0; j < vtxBdl.attributes.size(); j++) { + if (vtxBdl.attributes[j].name == gfx::ATTR_NAME_POSITION || vtxBdl.attributes[j].name == gfx::ATTR_NAME_NORMAL) { + const gfx::Format format = vtxBdl.attributes[j].format; + + DataView inputView(data.buffer(), vtxBdl.view.offset + getOffset(vtxBdl.attributes, j)); + + auto reader = getReader(inputView, format); + if (reader == nullptr) { + continue; + } + + auto writer = getWriter(inputView, format); + if (writer == nullptr) { + continue; + } + + const uint32_t vertexCount = vtxBdl.view.count; + + const uint32_t vertexStride = vtxBdl.view.stride; + const uint32_t attrComponentByteLength = getComponentByteLength(format); + for (uint32_t vtxIdx = 0; vtxIdx < vertexCount; vtxIdx++) { + const uint32_t xOffset = vtxIdx * vertexStride; + const uint32_t yOffset = xOffset + attrComponentByteLength; + const uint32_t zOffset = yOffset + attrComponentByteLength; + vec3Temp.set( + getTypedArrayElementValue(reader(xOffset)), + getTypedArrayElementValue(reader(yOffset)), + getTypedArrayElementValue(reader(zOffset))); + const auto &attrName = vtxBdl.attributes[j].name; + + if (attrName == gfx::ATTR_NAME_POSITION) { + vec3Temp.transformMat4(vec3Temp, *worldMatrix); + } else if (attrName == gfx::ATTR_NAME_NORMAL) { + vec3Temp.transformQuat(rotate); + } + + writer(xOffset, vec3Temp.x); + writer(yOffset, vec3Temp.y); + writer(zOffset, vec3Temp.z); + } + } + } + } + } + reset({structInfo, data}); + initialize(); + return true; + } + + // merge buffer + BufferBlob bufferBlob; + + // merge vertex buffer + uint32_t vertCount = 0; + uint32_t vertStride = 0; + + uint32_t srcAttrOffset = 0; + uint32_t srcVBOffset = 0; + uint32_t dstVBOffset = 0; + uint32_t attrSize = 0; + + bool hasAttr = false; + + ccstd::vector vertexBundles; + vertexBundles.resize(_struct.vertexBundles.size()); + + for (size_t i = 0; i < _struct.vertexBundles.size(); ++i) { + Uint8Array dstAttrView; + + const auto &bundle = _struct.vertexBundles[i]; + auto &dstBundle = mesh->_struct.vertexBundles[i]; + + vertStride = bundle.view.stride; + vertCount = bundle.view.count + dstBundle.view.count; + + auto *vb = ccnew ArrayBuffer(vertCount * vertStride); + Uint8Array vbView(vb); + + Uint8Array srcVBView = _data.subarray(bundle.view.offset, bundle.view.offset + bundle.view.length); + Uint8Array dstVBView = mesh->_data.subarray(dstBundle.view.offset, dstBundle.view.offset + dstBundle.view.length); + + vbView.set(srcVBView); + + srcAttrOffset = 0; + for (const auto &attr : bundle.attributes) { + dstVBOffset = 0; + hasAttr = false; + for (const auto &dstAttr : dstBundle.attributes) { + if (attr.name == dstAttr.name && attr.format == dstAttr.format) { + hasAttr = true; + break; + } + dstVBOffset += gfx::GFX_FORMAT_INFOS[static_cast(dstAttr.format)].size; + } + if (hasAttr) { + attrSize = gfx::GFX_FORMAT_INFOS[static_cast(attr.format)].size; + srcVBOffset = bundle.view.length + srcAttrOffset; + for (uint32_t v = 0; v < dstBundle.view.count; ++v) { + // Important note: the semantics of subarray are different in typescript and native + dstAttrView = dstVBView.subarray(dstBundle.view.offset + dstVBOffset, dstBundle.view.offset + dstVBOffset + attrSize); + vbView.set(dstAttrView, srcVBOffset); + if ((attr.name == gfx::ATTR_NAME_POSITION || attr.name == gfx::ATTR_NAME_NORMAL) && worldMatrix != nullptr) { + Float32Array f32Temp(vbView.buffer(), srcVBOffset, 3); + vec3Temp.set(f32Temp[0], f32Temp[1], f32Temp[2]); + if (attr.name == gfx::ATTR_NAME_POSITION) { + vec3Temp.transformMat4(vec3Temp, *worldMatrix); + } else if (attr.name == gfx::ATTR_NAME_NORMAL) { + vec3Temp.transformQuat(rotate); + } + + f32Temp[0] = vec3Temp.x; + f32Temp[1] = vec3Temp.y; + f32Temp[2] = vec3Temp.z; + } + srcVBOffset += bundle.view.stride; + dstVBOffset += dstBundle.view.stride; + } + } + srcAttrOffset += gfx::GFX_FORMAT_INFOS[static_cast(attr.format)].size; + } + + auto &vertexBundle = vertexBundles[i]; + vertexBundle.attributes = bundle.attributes, + vertexBundle.view.offset = bufferBlob.getLength(); + vertexBundle.view.length = vb->byteLength(); + vertexBundle.view.count = vertCount; + vertexBundle.view.stride = vertStride; + + bufferBlob.addBuffer(vb); + } + + // merge index buffer + uint32_t idxCount = 0; + uint32_t idxStride = 2; + + ccstd::vector primitives; + primitives.resize(_struct.primitives.size()); + + for (size_t i = 0; i < _struct.primitives.size(); ++i) { + const auto &prim = _struct.primitives[i]; + auto &dstPrim = mesh->_struct.primitives[i]; + + primitives[i].primitiveMode = prim.primitiveMode; + primitives[i].vertexBundelIndices = prim.vertexBundelIndices; + + uint32_t vertBatchCount = 0; + for (const uint32_t bundleIdx : prim.vertexBundelIndices) { + vertBatchCount = std::max(vertBatchCount, _struct.vertexBundles[bundleIdx].view.count); + } + + if (prim.indexView.has_value() && dstPrim.indexView.has_value()) { + idxCount = prim.indexView.value().count; + idxCount += dstPrim.indexView.value().count; + + if (idxCount < 256) { + idxStride = 1; + } else if (idxCount < 65536) { + idxStride = 2; + } else { + idxStride = 4; + } + + auto *ib = ccnew ArrayBuffer(idxCount * idxStride); + + TypedArray ibView; + TypedArray srcIBView; + TypedArray dstIBView; + + if (idxStride == 2) { + ibView = Uint16Array(ib); + } else if (idxStride == 1) { + ibView = Uint8Array(ib); + } else { // Uint32 + ibView = Uint32Array(ib); + } + + // merge src indices + if (prim.indexView.value().stride == 2) { + srcIBView = Uint16Array(_data.buffer(), prim.indexView.value().offset, prim.indexView.value().count); + } else if (prim.indexView.value().stride == 1) { + srcIBView = Uint8Array(_data.buffer(), prim.indexView.value().offset, prim.indexView.value().count); + } else { // Uint32 + srcIBView = Uint32Array(_data.buffer(), prim.indexView.value().offset, prim.indexView.value().count); + } + // + if (idxStride == prim.indexView.value().stride) { + switch (idxStride) { + case 2: + ccstd::get(ibView).set(ccstd::get(srcIBView)); + break; + case 1: + ccstd::get(ibView).set(ccstd::get(srcIBView)); + break; + default: + ccstd::get(ibView).set(ccstd::get(srcIBView)); + break; + } + } else { + for (uint32_t n = 0; n < prim.indexView.value().count; ++n) { + if (idxStride == 2) { + ccstd::get(ibView)[n] = static_cast(getTypedArrayValue(srcIBView, n)); + } else if (idxStride == 1) { + ccstd::get(ibView)[n] = static_cast(getTypedArrayValue(srcIBView, n)); + } else { + ccstd::get(ibView)[n] = getTypedArrayValue(srcIBView, n); + } + } + } + + // merge dst indices + uint32_t indexViewStride = dstPrim.indexView.value().stride; + if (indexViewStride == 2) { + dstIBView = Uint16Array(mesh->_data.buffer(), dstPrim.indexView.value().offset, dstPrim.indexView->count); + } else if (indexViewStride == 1) { + dstIBView = Uint8Array(mesh->_data.buffer(), dstPrim.indexView.value().offset, dstPrim.indexView->count); + } else { // Uint32 + dstIBView = Uint32Array(mesh->_data.buffer(), dstPrim.indexView.value().offset, dstPrim.indexView->count); + } + for (uint32_t n = 0; n < dstPrim.indexView.value().count; ++n) { + if (idxStride == 2) { + ccstd::get(ibView)[prim.indexView->count + n] = + vertBatchCount + static_cast(getTypedArrayValue(dstIBView, n)); + } else if (idxStride == 1) { + ccstd::get(ibView)[prim.indexView->count + n] = + vertBatchCount + static_cast(getTypedArrayValue(dstIBView, n)); + } else { + ccstd::get(ibView)[prim.indexView->count + n] = + vertBatchCount + getTypedArrayValue(dstIBView, n); + } + } + + IBufferView indexView; + indexView.offset = bufferBlob.getLength(); + indexView.length = ib->byteLength(); + indexView.count = idxCount; + indexView.stride = idxStride; + primitives[i].indexView = indexView; + + bufferBlob.setNextAlignment(idxStride); + bufferBlob.addBuffer(ib); + } + } + + // Create mesh struct. + Mesh::IStruct meshStruct; + meshStruct.vertexBundles = vertexBundles; + meshStruct.primitives = primitives; + meshStruct.minPosition = _struct.minPosition; + meshStruct.maxPosition = _struct.maxPosition; + + if (meshStruct.minPosition && mesh->_struct.minPosition && meshStruct.maxPosition && mesh->_struct.maxPosition) { + if (worldMatrix != nullptr) { + if (mesh->_struct.maxPosition.has_value() && mesh->_struct.minPosition.has_value()) { + Vec3::add(mesh->_struct.maxPosition.value(), mesh->_struct.minPosition.value(), &boundingBox.center); + } + boundingBox.center.scale(0.5F); + if (mesh->_struct.maxPosition.has_value() && mesh->_struct.minPosition.has_value()) { + Vec3::subtract(mesh->_struct.maxPosition.value(), mesh->_struct.minPosition.value(), &boundingBox.halfExtents); + } + boundingBox.halfExtents.scale(0.5F); + boundingBox.transform(*worldMatrix, &boundingBox); + + Vec3::add(boundingBox.center, boundingBox.halfExtents, &vec3Temp); + Vec3::max(meshStruct.maxPosition.value(), vec3Temp, &meshStruct.maxPosition.value()); + Vec3::subtract(boundingBox.center, boundingBox.halfExtents, &vec3Temp); + Vec3::min(meshStruct.minPosition.value(), vec3Temp, &meshStruct.minPosition.value()); + } else { + Vec3::min(meshStruct.minPosition.value(), mesh->_struct.minPosition.value(), &meshStruct.minPosition.value()); + Vec3::max(meshStruct.maxPosition.value(), mesh->_struct.maxPosition.value(), &meshStruct.maxPosition.value()); + } + } + + // Create mesh. + reset({std::move(meshStruct), + std::move(Uint8Array(bufferBlob.getCombined()))}); + initialize(); + return true; +} + +bool Mesh::validateMergingMesh(Mesh *mesh) { + // dynamic mesh is not allowed to merge. + if (_struct.dynamic.has_value() || mesh->_struct.dynamic.has_value()) { + return false; + } + + // validate vertex bundles + if (_struct.vertexBundles.size() != mesh->_struct.vertexBundles.size()) { + return false; + } + + for (size_t i = 0; i < _struct.vertexBundles.size(); ++i) { + const auto &bundle = _struct.vertexBundles[i]; + auto &dstBundle = mesh->_struct.vertexBundles[i]; + + if (bundle.attributes.size() != dstBundle.attributes.size()) { + return false; + } + for (size_t j = 0; j < bundle.attributes.size(); ++j) { + if (bundle.attributes[j].format != dstBundle.attributes[j].format) { + return false; + } + } + } + + // validate primitives + if (_struct.primitives.size() != mesh->_struct.primitives.size()) { + return false; + } + for (size_t i = 0; i < _struct.primitives.size(); ++i) { + const auto &prim = _struct.primitives[i]; + auto &dstPrim = mesh->_struct.primitives[i]; + if (prim.vertexBundelIndices.size() != dstPrim.vertexBundelIndices.size()) { + return false; + } + for (size_t j = 0; j < prim.vertexBundelIndices.size(); ++j) { + if (prim.vertexBundelIndices[j] != dstPrim.vertexBundelIndices[j]) { + return false; + } + } + if (prim.primitiveMode != dstPrim.primitiveMode) { + return false; + } + + if (prim.indexView.has_value()) { + if (!dstPrim.indexView.has_value()) { + return false; + } + } else if (dstPrim.indexView.has_value()) { + return false; + } + } + + return true; +} + +TypedArray Mesh::readAttribute(index_t primitiveIndex, const char *attributeName) { + TypedArray result; + accessAttribute(primitiveIndex, attributeName, [&](const IVertexBundle &vertexBundle, uint32_t iAttribute) { + const uint32_t vertexCount = vertexBundle.view.count; + const gfx::Format format = vertexBundle.attributes[iAttribute].format; + if (vertexCount == 0) { + return; + } + + DataView inputView(_data.buffer(), vertexBundle.view.offset + getOffset(vertexBundle.attributes, static_cast(iAttribute))); + + const auto &formatInfo = gfx::GFX_FORMAT_INFOS[static_cast(format)]; + + auto reader = getReader(inputView, format); + if (reader == nullptr) { + return; + } + + const uint32_t componentCount = formatInfo.count; + result = createTypedArrayWithGFXFormat(format, vertexCount * componentCount); + const uint32_t inputStride = vertexBundle.view.stride; + for (uint32_t iVertex = 0; iVertex < vertexCount; ++iVertex) { + for (uint32_t iComponent = 0; iComponent < componentCount; ++iComponent) { + TypedArrayElementType element = reader(inputStride * iVertex + getTypedArrayBytesPerElement(result) * iComponent); + setTypedArrayValue(result, componentCount * iVertex + iComponent, element); + } + } + }); + return result; +} + +bool Mesh::copyAttribute(index_t primitiveIndex, const char *attributeName, ArrayBuffer *buffer, uint32_t stride, uint32_t offset) { + bool written = false; + accessAttribute(primitiveIndex, attributeName, [&](const IVertexBundle &vertexBundle, uint32_t iAttribute) { + const uint32_t vertexCount = vertexBundle.view.count; + if (vertexCount == 0) { + written = true; + return; + } + const gfx::Format format = vertexBundle.attributes[iAttribute].format; + + DataView inputView(_data.buffer(), vertexBundle.view.offset + getOffset(vertexBundle.attributes, static_cast(iAttribute))); + + DataView outputView(buffer, offset); + + const auto &formatInfo = gfx::GFX_FORMAT_INFOS[static_cast(format)]; + + auto reader = getReader(inputView, format); + if (reader == nullptr) { + return; + } + + auto writer = getWriter(outputView, format); + if (writer == nullptr) { + return; + } + + const uint32_t componentCount = formatInfo.count; + + const uint32_t inputStride = vertexBundle.view.stride; + const uint32_t inputComponentByteLength = getComponentByteLength(format); + const uint32_t outputStride = stride; + const uint32_t outputComponentByteLength = inputComponentByteLength; + for (uint32_t iVertex = 0; iVertex < vertexCount; ++iVertex) { + for (uint32_t iComponent = 0; iComponent < componentCount; ++iComponent) { + const uint32_t inputOffset = inputStride * iVertex + inputComponentByteLength * iComponent; + const uint32_t outputOffset = outputStride * iVertex + outputComponentByteLength * iComponent; + writer(outputOffset, reader(inputOffset)); + } + } + written = true; + }); + return written; +} + +IBArray Mesh::readIndices(index_t primitiveIndex) { + if (primitiveIndex >= _struct.primitives.size()) { + return {}; + } + const auto &primitive = _struct.primitives[primitiveIndex]; + if (!primitive.indexView.has_value()) { + return {}; + } + const uint32_t stride = primitive.indexView.value().stride; + const uint32_t count = primitive.indexView.value().count; + const uint32_t byteOffset = primitive.indexView.value().offset; + IBArray ret; + if (stride == 1) { + ret = Uint8Array(_data.buffer(), byteOffset, count); + } else if (stride == 2) { + ret = Uint16Array(_data.buffer(), byteOffset, count); + } else { + ret = Uint32Array(_data.buffer(), byteOffset, count); + } + + return ret; +} + +bool Mesh::copyIndices(index_t primitiveIndex, TypedArray &outputArray) { + if (primitiveIndex >= _struct.primitives.size()) { + return false; + } + const auto &primitive = _struct.primitives[primitiveIndex]; + if (!primitive.indexView.has_value()) { + return false; + } + + const uint32_t indexCount = primitive.indexView.value().count; + const gfx::Format indexFormat = primitive.indexView.value().stride == 1 ? gfx::Format::R8UI + : (primitive.indexView.value().stride == 2 ? gfx::Format::R16UI + : gfx::Format::R32UI); + DataView view(_data.buffer()); + auto reader = getReader(view, indexFormat); + for (uint32_t i = 0; i < indexCount; ++i) { + TypedArrayElementType element = reader(primitive.indexView.value().offset + gfx::GFX_FORMAT_INFOS[static_cast(indexFormat)].size * i); + setTypedArrayValue(outputArray, i, element); + } + return true; +} + +const gfx::FormatInfo *Mesh::readAttributeFormat(index_t primitiveIndex, const char *attributeName) { + const gfx::FormatInfo *result = nullptr; + + accessAttribute(primitiveIndex, attributeName, [&](const IVertexBundle &vertexBundle, uint32_t iAttribute) { + const gfx::Format format = vertexBundle.attributes[iAttribute].format; + result = &gfx::GFX_FORMAT_INFOS[static_cast(format)]; + }); + + return result; +} + +void Mesh::updateSubMesh(index_t primitiveIndex, const IDynamicGeometry &geometry) { + if (!_struct.dynamic.has_value()) { + return; + } + + if (primitiveIndex >= _struct.primitives.size()) { + return; + } + + ccstd::vector buffers; + if (!geometry.positions.empty()) { + buffers.push_back(&geometry.positions); + } + + if (geometry.normals.has_value() && !geometry.normals.value().empty()) { + buffers.push_back(&geometry.normals.value()); + } + + if (geometry.uvs.has_value() && !geometry.uvs.value().empty()) { + buffers.push_back(&geometry.uvs.value()); + } + + if (geometry.tangents.has_value() && !geometry.tangents.value().empty()) { + buffers.push_back(&geometry.tangents.value()); + } + + if (geometry.colors.has_value() && !geometry.colors.value().empty()) { + buffers.push_back(&geometry.colors.value()); + } + + if (geometry.customAttributes.has_value()) { + for (const auto &ca : geometry.customAttributes.value()) { + buffers.push_back(&ca.values); + } + } + + auto &dynamic = _struct.dynamic.value(); + auto &info = dynamic.info; + auto &primitive = _struct.primitives[primitiveIndex]; + auto &subMesh = _renderingSubMeshes[primitiveIndex]; + auto &drawInfo = subMesh->getDrawInfo().value(); + + // update _data & buffer + for (auto index = 0U; index < buffers.size(); index++) { + const auto &vertices = *buffers[index]; + auto &bundle = _struct.vertexBundles[primitive.vertexBundelIndices[index]]; + const auto stride = bundle.view.stride; + const auto vertexCount = vertices.byteLength() / stride; + const auto updateSize = vertices.byteLength(); + auto *dstBuffer = _data.buffer()->getData() + bundle.view.offset; + const auto *srcBuffer = vertices.buffer()->getData() + vertices.byteOffset(); + auto *vertexBuffer = subMesh->getVertexBuffers()[index]; + CC_ASSERT_LE(vertexCount, info.maxSubMeshVertices); + + if (updateSize > 0U) { + std::memcpy(dstBuffer, srcBuffer, updateSize); + vertexBuffer->update(srcBuffer, updateSize); + } + + bundle.view.count = vertexCount; + drawInfo.vertexCount = vertexCount; + } + + if (primitive.indexView.has_value()) { + auto &indexView = primitive.indexView.value(); + const auto &stride = indexView.stride; + const auto indexCount = (stride == sizeof(uint16_t)) ? geometry.indices16.value().length() : geometry.indices32.value().length(); + const auto updateSize = indexCount * stride; + auto *dstBuffer = _data.buffer()->getData() + indexView.offset; + const auto *srcBuffer = (stride == sizeof(uint16_t)) ? geometry.indices16.value().buffer()->getData() + geometry.indices16.value().byteOffset() + : geometry.indices32.value().buffer()->getData() + geometry.indices32.value().byteOffset(); + auto *indexBuffer = subMesh->getIndexBuffer(); + CC_ASSERT_LE(indexCount, info.maxSubMeshIndices); + + if (updateSize > 0U) { + std::memcpy(dstBuffer, srcBuffer, updateSize); + indexBuffer->update(srcBuffer, updateSize); + } + + indexView.count = indexCount; + drawInfo.indexCount = indexCount; + } + + // update bound + if (geometry.minPos.has_value() && geometry.maxPos.has_value()) { + Vec3 minPos = geometry.minPos.value(); + Vec3 maxPos = geometry.maxPos.value(); + + geometry::AABB box; + geometry::AABB::fromPoints(minPos, maxPos, &box); + dynamic.bounds[primitiveIndex] = box; + + Vec3 subMin; + Vec3 subMax; + for (const auto &bound : dynamic.bounds) { + if (bound.isValid()) { + bound.getBoundary(&subMin, &subMax); + Vec3::min(minPos, subMin, &minPos); + Vec3::max(maxPos, subMax, &maxPos); + } + } + + _struct.minPosition = minPos; + _struct.maxPosition = maxPos; + } + + subMesh->invalidateGeometricInfo(); +} + +void Mesh::accessAttribute(index_t primitiveIndex, const char *attributeName, const AccessorType &accessor) { + if (primitiveIndex >= _struct.primitives.size()) { + return; + } + + auto &primitive = _struct.primitives[primitiveIndex]; + for (const auto &vertexBundleIndex : primitive.vertexBundelIndices) { + const auto &vertexBundle = _struct.vertexBundles[vertexBundleIndex]; + auto iter = std::find_if(vertexBundle.attributes.begin(), vertexBundle.attributes.end(), [&](const auto &a) -> bool { return a.name == attributeName; }); + if (iter == vertexBundle.attributes.end()) { + continue; + } + accessor(vertexBundle, static_cast(iter - vertexBundle.attributes.begin())); + break; + } +} + +void Mesh::tryConvertVertexData() { +#if CC_OPTIMIZE_MESH_DATA + if (!hasFlag(gfx::Device::getInstance()->getFormatFeatures(gfx::Format::RG16F), gfx::FormatFeature::VERTEX_ATTRIBUTE)) { + CC_LOG_DEBUG("Does not support half float vertex attribute!"); + return; + } + + uint8_t *data = _data.buffer()->getData(); + ccstd::vector attributeIndicsNeedConvert; + + for (auto &vertexBundle : _struct.vertexBundles) { + // NOTE: Don't use reference here since we need to copy attributes + const auto orignalAttributes = vertexBundle.attributes; + + auto &attributes = vertexBundle.attributes; + auto &view = vertexBundle.view; + uint32_t offset = view.offset; + uint32_t length = view.length; + uint32_t count = view.count; + const uint32_t stride = view.stride; + uint32_t dstStride = stride; + + CC_ASSERT_EQ(count * stride, length); + + checkAttributesNeedConvert(orignalAttributes, attributes, attributeIndicsNeedConvert, dstStride); + if (attributeIndicsNeedConvert.empty()) { + return; + } + + for (uint32_t i = 0; i < count; ++i) { + uint8_t *srcIndex = data + offset + i * stride; + uint8_t *dstIndex = data + offset + i * dstStride; + uint32_t wroteBytes = 0; + + for (size_t attributeIndex = 0, len = orignalAttributes.size(); attributeIndex < len; ++attributeIndex) { + const auto &attribute = orignalAttributes[attributeIndex]; + const auto &formatInfo = gfx::GFX_FORMAT_INFOS[static_cast(attribute.format)]; + const auto iter = std::find(attributeIndicsNeedConvert.cbegin(), attributeIndicsNeedConvert.cend(), attributeIndex); + + if (iter == attributeIndicsNeedConvert.end()) { + memmove(dstIndex, srcIndex, formatInfo.size); + + dstIndex += formatInfo.size; + wroteBytes += formatInfo.size; + srcIndex += formatInfo.size; + continue; + } + + const float *pValue = reinterpret_cast(srcIndex); + uint16_t *pDst = reinterpret_cast(dstIndex); + uint32_t advance = (formatInfo.size >> 1); + + switch (attribute.format) { + case gfx::Format::RGB32F: { + convertRGB32FToRGB16F(pValue, pDst); + #if (CC_PLATFORM == CC_PLATFORM_IOS) || (CC_PLATFORM == CC_PLATFORM_MACOS) + // NOTE: Metal needs 4 bytes alignment + pDst[3] = 0; + advance += (advance % 4); + #endif + + } break; + case gfx::Format::RG32F: { + convertRG32FToRG16F(pValue, pDst); + } break; + case gfx::Format::RGBA32F: { + convertRGBA32FToRGBA16F(pValue, pDst); + } break; + default: + CC_ABORT(); + break; + } + + dstIndex += advance; + wroteBytes += advance; + srcIndex += formatInfo.size; + } + + CC_ASSERT_EQ(wroteBytes, dstStride); + } + + // update stride & length + view.stride = dstStride; + view.length = view.stride * view.count; + } +#endif +} + +gfx::BufferList Mesh::createVertexBuffers(gfx::Device *gfxDevice, ArrayBuffer *data) { + tryConvertVertexData(); + + gfx::BufferList buffers; + buffers.reserve(_struct.vertexBundles.size()); + for (const auto &vertexBundle : _struct.vertexBundles) { + auto *vertexBuffer = gfxDevice->createBuffer({gfx::BufferUsageBit::VERTEX, + gfx::MemoryUsageBit::DEVICE, + vertexBundle.view.length, + vertexBundle.view.stride}); + + vertexBuffer->update(data->getData() + vertexBundle.view.offset, vertexBundle.view.length); + buffers.emplace_back(vertexBuffer); + } + return buffers; +} + +void Mesh::initDefault(const ccstd::optional &uuid) { + Super::initDefault(uuid); + reset({}); +} + +void Mesh::setAllowDataAccess(bool allowDataAccess) { + _allowDataAccess = allowDataAccess; +#if !CC_EDITOR + if (_isMeshDataUploaded && !_allowDataAccess) { + releaseData(); + } +#endif +} + +void Mesh::releaseData() { + _data.clear(); +} + +TypedArray Mesh::createTypedArrayWithGFXFormat(gfx::Format format, uint32_t count) { + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(format)]; + const uint32_t stride = info.size / info.count; + + switch (info.type) { + case gfx::FormatType::UNORM: + case gfx::FormatType::UINT: { + switch (stride) { + case 1: return Uint8Array(count); + case 2: return Uint16Array(count); + case 4: return Uint32Array(count); + default: + break; + } + break; + } + case gfx::FormatType::SNORM: + case gfx::FormatType::INT: { + switch (stride) { + case 1: return Int8Array(count); + case 2: return Int16Array(count); + case 4: return Int32Array(count); + default: + break; + } + break; + } + case gfx::FormatType::FLOAT: { + switch (stride) { + case 2: return Uint16Array(count); + case 4: return Float32Array(count); + default: + break; + } + break; + } + default: + break; + } + + return Float32Array(count); +} + +} // namespace cc diff --git a/cocos/3d/assets/Mesh.h b/cocos/3d/assets/Mesh.h new file mode 100644 index 0000000..8895059 --- /dev/null +++ b/cocos/3d/assets/Mesh.h @@ -0,0 +1,500 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "3d/assets/Morph.h" +#include "3d/assets/MorphRendering.h" +#include "base/std/optional.h" +#include "core/assets/Asset.h" +#include "core/geometry/AABB.h" +#include "math/Mat4.h" +#include "math/Vec3.h" +#include "primitive/PrimitiveDefine.h" +#include "renderer/gfx-base/GFXDef.h" + +namespace cc { + +class Skeleton; +class RenderingSubMesh; + +/** + * @en Mesh asset + * @zh 网格资源。 + */ +class Mesh : public Asset { +public: + using Super = Asset; + + using IBufferView = IMeshBufferView; + + /** + * @en Vertex bundle, it describes a set of interleaved vertex attributes and their values. + * @zh 顶点块。顶点块描述了一组**交错排列**(interleaved)的顶点属性并存储了顶点属性的实际数据。
+ * 交错排列是指在实际数据的缓冲区中,每个顶点的所有属性总是依次排列,并总是出现在下一个顶点的所有属性之前。 + */ + struct IVertexBundle { + ccstd::optional _padding; // NOTE: avoid jsb cache map + /** + * @en The actual value for all vertex attributes. + * You must use DataView to access the data. + * @zh 所有顶点属性的实际数据块。 + * 你必须使用 DataView 来读取数据。 + * 因为不能保证所有属性的起始偏移都按 TypedArray 要求的字节对齐。 + */ + IBufferView view; + + /** + * @en All attributes included in the bundle + * @zh 包含的所有顶点属性。 + */ + gfx::AttributeList attributes; + }; + + struct IMeshCluster { + IBufferView clusterView; + IBufferView triangleView; + IBufferView vertexView; + IBufferView coneView; + }; + + /** + * @en Sub mesh contains a list of primitives with the same type (Point, Line or Triangle) + * @zh 子网格。子网格由一系列相同类型的图元组成(例如点、线、面等)。 + */ + struct ISubMesh { + /** + * @en The vertex bundle references used by the sub mesh. + * @zh 此子网格引用的顶点块,索引至网格的顶点块数组。 + */ + ccstd::vector vertexBundelIndices; + + /** + * @en The primitive mode of the sub mesh + * @zh 此子网格的图元类型。 + */ + gfx::PrimitiveMode primitiveMode; + + /** + * @en The index data of the sub mesh + * @zh 此子网格使用的索引数据。 + */ + ccstd::optional indexView; + + /** + * @en The joint map index in [[IStruct.jointMaps]]. Could be absent + * @zh 此子网格使用的关节索引映射表在 [[IStruct.jointMaps]] 中的索引。 + * 如未定义或指向的映射表不存在,则默认 VB 内所有关节索引数据直接对应骨骼资源数据。 + */ + ccstd::optional jointMapIndex; + + ccstd::optional cluster; + }; + + /** + * @en The info use to create dynamic mesh. + * @zh 描述了创建动态网格需要的预分配信息。 + */ + struct IDynamicInfo { + /** + * @en max submesh count + * @zh 最大子模型个数。 + */ + uint32_t maxSubMeshes{0U}; + + /** + * @en max submesh vertex count + * @zh 子模型最大顶点个数。 + */ + uint32_t maxSubMeshVertices{0U}; + + /** + * @en max submesh index count + * @zh 子模型最大索引个数。 + */ + uint32_t maxSubMeshIndices{0U}; + }; + + /** + * @en The structure use to create dynamic mesh. + * @zh 描述了创建动态网格的结构。 + */ + struct IDynamicStruct { + /** + * @en dynamic mesh info + * @zh 动态模型信息。 + */ + IDynamicInfo info; + + /** + * @en dynamic submesh bounds + * @zh 动态子模型包围盒。 + */ + ccstd::vector bounds; + }; + + /** + * @en The structure of the mesh + * @zh 描述了网格的结构。 + */ + struct IStruct { + /** + * @en All vertex bundles of the mesh + * @zh 此网格所有的顶点块。 + */ + ccstd::vector vertexBundles; + + /** + * @en All sub meshes + * @zh 此网格的所有子网格。 + */ + ccstd::vector primitives; + + /** + * @en The minimum position of all vertices in the mesh + * @zh (各分量都)小于等于此网格任何顶点位置的最大位置。 + */ + ccstd::optional minPosition; + inline const ccstd::optional &getMinPosition() const { return minPosition; } // For JSB binding only + inline void setMinPosition(const ccstd::optional &v) { minPosition = v; } // For JSB binding only + + /** + * @en The maximum position of all vertices in the mesh + * @zh (各分量都)大于等于此网格任何顶点位置的最小位置。 + */ + ccstd::optional maxPosition; + inline const ccstd::optional &getMaxPosition() const { return maxPosition; } // For JSB binding only + inline void setMaxPosition(const ccstd::optional &v) { maxPosition = v; } // For JSB binding only + + /** + * @en The joint index map list. + * @zh 此网格使用的关节索引映射关系列表,数组长度应为子模型中实际使用到的所有关节, + * 每个元素都对应一个原骨骼资源里的索引,按子模型 VB 内的实际索引排列。 + */ + ccstd::optional>> jointMaps; + + /** + * @en The morph information of the mesh + * @zh 网格的形变数据 + */ + ccstd::optional morph; + + /** + * @en The specific data of the dynamic mesh + * @zh 动态网格特有数据 + */ + ccstd::optional dynamic; + + ccstd::optional encoded; + + ccstd::optional compressed; + + ccstd::optional quantized; + }; + + struct ICreateInfo { + /** + * @en Mesh structure + * @zh 网格结构。 + */ + IStruct structInfo; + + /** + * @en Mesh binary data + * @zh 网格二进制数据。 + */ + Uint8Array data; + }; + + Mesh() = default; + ~Mesh() override; + + ccstd::any getNativeAsset() const override; + void setNativeAsset(const ccstd::any &obj) override; + + void setAssetData(ArrayBuffer *data) { + _data = Uint8Array(data); + } + + ArrayBuffer *getAssetData() const { + return _data.buffer(); + } + + /** + * @en The sub meshes count of the mesh. + * @zh 此网格的子网格数量。 + * @deprecated Please use [[renderingSubMeshes.length]] instead + */ + uint32_t getSubMeshCount() const; + + /** + * @en The minimum position of all vertices in the mesh + * @zh (各分量都)小于等于此网格任何顶点位置的最大位置。 + * @deprecated Please use [[struct.minPosition]] instead + */ + const Vec3 *getMinPosition() const; + + /** + * @en The maximum position of all vertices in the mesh + * @zh (各分量都)大于等于此网格任何顶点位置的最大位置。 + * @deprecated Please use [[struct.maxPosition]] instead + */ + const Vec3 *getMaxPosition() const; + + /** + * @en The struct of the mesh + * @zh 此网格的结构。 + */ + inline const IStruct &getStruct() const { + return _struct; + } + + inline void setStruct(const IStruct &input) { + _struct = input; + } + + inline Uint8Array &getData() { + return _data; + } + + inline void setData(const Uint8Array &data) { + _data = data; + } + + /** + * @en The hash of the mesh + * @zh 此网格的哈希值。 + */ + ccstd::hash_t getHash(); + + /** + * @en Set the hash of the mesh + * @zh 设置此网格的哈希值。 + */ + void setHash(ccstd::hash_t hash) { _hash = hash; } + + using JointBufferIndicesType = ccstd::vector; + /** + * The index of the joint buffer of all sub meshes in the joint map buffers + */ + const JointBufferIndicesType &getJointBufferIndices(); + + using RenderingSubMeshList = ccstd::vector>; + /** + * @en The sub meshes for rendering. Mesh could be split into different sub meshes for rendering. + * @zh 此网格创建的渲染网格。 + */ + inline const RenderingSubMeshList &getRenderingSubMeshes() { + initialize(); + return _renderingSubMeshes; + } + + void onLoaded() override { + initialize(); + } + + void initialize(); + + /** + * @en Destroy the mesh and release all related GPU resources + * @zh 销毁此网格,并释放它占有的所有 GPU 资源。 + */ + bool destroy() override { + destroyRenderingMesh(); + return Super::destroy(); + } + + /** + * @en Release all related GPU resources + * @zh 释放此网格占有的所有 GPU 资源。 + */ + void destroyRenderingMesh(); + + /** + * @en Reset the struct and data of the mesh + * @zh 重置此网格的结构和数据。 + * @param struct The new struct + * @param data The new data + * @deprecated Will be removed in v3.0.0, please use [[reset]] instead + */ + void assign(const IStruct &structInfo, const Uint8Array &data); + + /** + * @en Reset the mesh with mesh creation information + * @zh 重置此网格。 + * @param info Mesh creation information including struct and data + */ + void reset(ICreateInfo &&info); + + using BoneSpaceBounds = ccstd::vector>; + /** + * @en Get [[AABB]] bounds in the skeleton's bone space + * @zh 获取骨骼变换空间内下的 [[AABB]] 包围盒 + * @param skeleton + */ + BoneSpaceBounds getBoneSpaceBounds(Skeleton *skeleton); + + /** + * @en Merge the given mesh into the current mesh + * @zh 合并指定的网格到此网格中。 + * @param mesh The mesh to be merged + * @param worldMatrix The world matrix of the given mesh + * @param [validate=false] Whether to validate the mesh + * @returns Check the mesh state and return the validation result. + */ + bool merge(Mesh *mesh, const Mat4 *worldMatrix = nullptr, bool validate = false); + + /** + * @en Validation for whether the given mesh can be merged into the current mesh. + * To pass the validation, it must satisfy either of these two requirements: + * - When the current mesh have no data + * - When the two mesh have the same vertex bundle count, the same sub meshes count, and the same sub mesh layout. + * + * Same mesh layout means: + * - They have the same primitive type and reference to the same amount vertex bundle with the same indices. + * - And they all have or don't have index view + * @zh 验证指定网格是否可以合并至当前网格。 + * + * 当满足以下条件之一时,指定网格可以合并至当前网格: + * - 当前网格无数据而待合并网格有数据; + * - 它们的顶点块数目相同且对应顶点块的布局一致,并且它们的子网格数目相同且对应子网格的布局一致。 + * + * 两个顶点块布局一致当且仅当: + * - 它们具有相同数量的顶点属性且对应的顶点属性具有相同的属性格式。 + * + * 两个子网格布局一致,当且仅当: + * - 它们具有相同的图元类型并且引用相同数量、相同索引的顶点块;并且, + * - 要么都需要索引绘制,要么都不需要索引绘制。 + * @param mesh The other mesh to be validated + */ + bool validateMergingMesh(Mesh *mesh); + + /** + * @en Read the requested attribute of the given sub mesh + * @zh 读取子网格的指定属性。 + * @param primitiveIndex Sub mesh index + * @param attributeName Attribute name + * @returns Return null if not found or can't read, otherwise, will create a large enough typed array to contain all data of the attribute, + * the array type will match the data type of the attribute. + */ + TypedArray readAttribute(index_t primitiveIndex, const char *attributeName); + + /** + * @en Read the requested attribute of the given sub mesh and fill into the given buffer. + * @zh 读取子网格的指定属性到目标缓冲区中。 + * @param primitiveIndex Sub mesh index + * @param attributeName Attribute name + * @param buffer The target array buffer + * @param stride Byte distance between two attributes in the target buffer + * @param offset The offset of the first attribute in the target buffer + * @returns Return false if failed to access attribute, return true otherwise. + */ + bool copyAttribute(index_t primitiveIndex, const char *attributeName, ArrayBuffer *buffer, uint32_t stride, uint32_t offset); + + /** + * @en Read the indices data of the given sub mesh + * @zh 读取子网格的索引数据。 + * @param primitiveIndex Sub mesh index + * @returns Return null if not found or can't read, otherwise, will create a large enough typed array to contain all indices data, + * the array type will use the corresponding stride size. + */ + IBArray readIndices(index_t primitiveIndex); + + /** + * @en Read the indices data of the given sub mesh and fill into the given array + * @zh 读取子网格的索引数据到目标数组中。 + * @param primitiveIndex Sub mesh index + * @param outputArray The target output array + * @returns Return false if failed to access the indices data, return true otherwise. + */ + bool copyIndices(index_t primitiveIndex, TypedArray &outputArray); + + /** + * @en Read the format by attributeName of submesh + * @zh 根据属性名读取子网格的属性信息。 + * @param primitiveIndex @en Sub mesh index @zh 子网格索引 + * @param attributeName @en Attribute name @zh 属性名称 + * @returns @en Return null if failed to read format, return the format otherwise. @zh 读取失败返回 null, 否则返回 format + */ + const gfx::FormatInfo *readAttributeFormat(index_t primitiveIndex, const char *attributeName); + + /** + * @en update dynamic sub mesh geometry + * @zh 更新动态子网格的几何数据 + * @param primitiveIndex: sub mesh index + * @param geometry: sub mesh geometry data + */ + void updateSubMesh(index_t primitiveIndex, const IDynamicGeometry &geometry); + + /** + * @en Set whether the data of this mesh could be accessed (read or wrote), it could be used only for static mesh + * @zh 设置此网格的数据是否可被存取,此接口只针对静态网格资源生效 + * @param allowDataAccess @en Indicate whether the data of this mesh could be accessed (read or wrote) @zh 是否允许存取网格数据 + */ + void setAllowDataAccess(bool allowDataAccess); + + /** + * @en Get whether the data of this mesh could be read or wrote + * @zh 获取此网格的数据是否可被存取 + * @return @en whether the data of this mesh could be accessed (read or wrote) @zh 此网格的数据是否可被存取 + */ + inline bool isAllowDataAccess() const { return _allowDataAccess; } + +private: + using AccessorType = std::function; + + void accessAttribute(index_t primitiveIndex, const char *attributeName, const AccessorType &accessor); + + gfx::BufferList createVertexBuffers(gfx::Device *gfxDevice, ArrayBuffer *data); + void tryConvertVertexData(); + + void initDefault(const ccstd::optional &uuid) override; + void releaseData(); + + static TypedArray createTypedArrayWithGFXFormat(gfx::Format format, uint32_t count); + +public: + IntrusivePtr morphRendering; + +private: + IStruct _struct; + ccstd::hash_t _hash{0U}; + Uint8Array _data; + + bool _initialized{false}; + bool _allowDataAccess{true}; + bool _isMeshDataUploaded{false}; + + RenderingSubMeshList _renderingSubMeshes; + + ccstd::unordered_map _boneSpaceBounds; + + JointBufferIndicesType _jointBufferIndices; + + friend class MeshDeserializer; + + CC_DISALLOW_COPY_MOVE_ASSIGN(Mesh); +}; + +} // namespace cc diff --git a/cocos/3d/assets/Morph.h b/cocos/3d/assets/Morph.h new file mode 100644 index 0000000..30c670a --- /dev/null +++ b/cocos/3d/assets/Morph.h @@ -0,0 +1,98 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "3d/assets/Types.h" + +namespace cc { + +struct IMeshBufferView { + uint32_t offset{0}; + uint32_t length{0}; + uint32_t count{0}; + uint32_t stride{0}; +}; + +/** + * @en Morph target contains all displacements data of each vertex attribute like position and normal. + * @zh 形变目标数据包含网格顶点属性在形变下的变化值,可能包含位移、法线等属性 + */ +struct MorphTarget { + /** + * Displacement of each target attribute. + */ + ccstd::vector displacements; +}; + +/** + * @en Sub mesh morph data describes all morph targets for one sub mesh, + * including attributes in each morph target, morph targets data and weights corresponding each targets. + * @zh 子网格形变数据描述一个子网格下所有的形变目标数据,包含顶点形变属性,形变目标数据和对应每个形变目标的权重。 + */ +struct SubMeshMorph { + /** + * Attributes to morph. + */ + ccstd::vector attributes; + + /** + * Targets. + */ + ccstd::vector targets; + + /** + * Initial weights of each target. + */ + ccstd::optional weights; +}; + +/** + * @en Mesh morph data structure to describe the sub meshes data of all sub meshes, + * it also contains all sub mesh morphs, global weights configuration and target names. + * Normally the global weights configuration should be identical to the sub mesh morph weights, + * but if not, the global weights in morph is less prioritized. + * @zh 网格的形变数据结构,包含所有子网格形变数据,全局的权重配置和所有形变目标名称。 + * 一般来说,全局权重配置和子网格形变数据中保持一致,但如果有差异,以子网格形变数据中的权重配置为准。 + */ + +struct Morph { + /** + * Morph data of each sub-mesh. + */ + ccstd::vector> subMeshMorphs; + + /** + * Common initial weights of each sub-mesh. + */ + ccstd::optional weights; + + /** + * Name of each target of each sub-mesh morph. + * This field is only meaningful if every sub-mesh has the same number of targets. + */ + ccstd::optional> targetNames; +}; + +} // namespace cc diff --git a/cocos/3d/assets/MorphRendering.cpp b/cocos/3d/assets/MorphRendering.cpp new file mode 100644 index 0000000..43bbdd7 --- /dev/null +++ b/cocos/3d/assets/MorphRendering.cpp @@ -0,0 +1,731 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "3d/assets/MorphRendering.h" + +#include +#include "3d/assets/Mesh.h" +#include "3d/assets/Morph.h" +#include "base/RefCounted.h" +#include "core/DataView.h" +#include "core/TypedArray.h" +#include "core/assets/ImageAsset.h" +#include "core/assets/RenderingSubMesh.h" +#include "core/assets/Texture2D.h" +#include "platform/Image.h" +#include "renderer/pipeline/Define.h" +#include "scene/Pass.h" + +namespace cc { + +MorphRendering *createMorphRendering(Mesh *mesh, gfx::Device *gfxDevice) { + return ccnew StdMorphRendering(mesh, gfxDevice); +} + +/** + * The instance of once sub-mesh morph rendering. + */ +class SubMeshMorphRenderingInstance : public RefCounted { +public: + ~SubMeshMorphRenderingInstance() override = default; + /** + * Set weights of each morph target. + * @param weights The weights. + */ + virtual void setWeights(const ccstd::vector &weights) = 0; + + /** + * Asks the define overrides needed to do the rendering. + */ + virtual ccstd::vector requiredPatches() = 0; + + /** + * Adapts the pipelineState to apply the rendering. + * @param pipelineState + */ + virtual void adaptPipelineState(gfx::DescriptorSet *descriptorSet) = 0; + + /** + * Destroy this instance. + */ + virtual void destroy() = 0; +}; + +/** + * Describes how to render a sub-mesh morph. + */ +class SubMeshMorphRendering : public RefCounted { +public: + ~SubMeshMorphRendering() override = default; + /** + * Creates a rendering instance. + */ + virtual SubMeshMorphRenderingInstance *createInstance() = 0; +}; + +namespace { +/** + * True if force to use cpu computing based sub-mesh rendering. + */ +const bool PREFER_CPU_COMPUTING = false; + +class MorphTexture final : public RefCounted { +public: + MorphTexture() = default; + + ~MorphTexture() override = default; + /** + * Gets the GFX texture. + */ + gfx::Texture *getTexture() { + return _textureAsset->getGFXTexture(); + } + + /** + * Gets the GFX sampler. + */ + gfx::Sampler *getSampler() { + return _sampler; + } + + /** + * Value view. + */ + Float32Array &getValueView() { + return _valueView; + } + + /** + * Destroy the texture. Release its GPU resources. + */ + void destroy() { + _textureAsset->destroy(); + // Samplers allocated from `samplerLib` are not required and + // should not be destroyed. + // _sampler.destroy(); + } + + /** + * Update the pixels content to `valueView`. + */ + void updatePixels() { + _textureAsset->uploadData(_arrayBuffer->getData()); + } + + void initialize(gfx::Device *gfxDevice, uint32_t width, uint32_t height, uint32_t pixelBytes, bool /*useFloat32Array*/, PixelFormat pixelFormat) { + _arrayBuffer = ccnew ArrayBuffer(width * height * pixelBytes); + _valueView = Float32Array(_arrayBuffer); + + auto *imageAsset = ccnew ImageAsset(); + IMemoryImageSource source{_arrayBuffer, false, width, height, pixelFormat}; + imageAsset->setNativeAsset(source); + + _textureAsset = ccnew Texture2D(); + _textureAsset->setFilters(Texture2D::Filter::NEAREST, Texture2D::Filter::NEAREST); + _textureAsset->setMipFilter(Texture2D::Filter::NONE); + _textureAsset->setWrapMode(Texture2D::WrapMode::CLAMP_TO_EDGE, Texture2D::WrapMode::CLAMP_TO_EDGE, Texture2D::WrapMode::CLAMP_TO_EDGE); + _textureAsset->setImage(imageAsset); + + if (nullptr == _textureAsset->getGFXTexture()) { + CC_LOG_WARNING("Unexpected: failed to create morph texture?"); + } + _sampler = gfxDevice->getSampler(_textureAsset->getSamplerInfo()); + } + +private: + IntrusivePtr _textureAsset; + gfx::Sampler *_sampler{nullptr}; + ArrayBuffer::Ptr _arrayBuffer; + Float32Array _valueView; + + CC_DISALLOW_COPY_MOVE_ASSIGN(MorphTexture); +}; + +struct GpuMorphAttribute { + ccstd::string attributeName; + IntrusivePtr morphTexture; +}; + +struct CpuMorphAttributeTarget { + Float32Array displacements; +}; + +using CpuMorphAttributeTargetList = ccstd::vector; + +struct CpuMorphAttribute { + ccstd::string name; + CpuMorphAttributeTargetList targets; +}; + +struct Vec4TextureFactory { + uint32_t width{0}; + uint32_t height{0}; + std::function create{nullptr}; +}; + +/** + * Decides a best texture size to have the specified pixel capacity at least. + * The decided width and height has the following characteristics: + * - the width and height are both power of 2; + * - if the width and height are different, the width would be set to the larger once; + * - the width is ensured to be multiple of 4. + * @param nPixels Least pixel capacity. + */ +bool bestSizeToHavePixels(uint32_t nPixels, uint32_t *pWidth, uint32_t *pHeight) { + if (pWidth == nullptr || pHeight == nullptr) { + if (pWidth != nullptr) { + *pWidth = 0; + } + + if (pHeight != nullptr) { + *pHeight = 0; + } + return false; + } + + if (nPixels < 5) { + nPixels = 5; + } + const uint32_t aligned = pipeline::nextPow2(nPixels); + const auto epxSum = static_cast(std::log2(aligned)); + const uint32_t h = epxSum >> 1; + const uint32_t w = (epxSum & 1) ? (h + 1) : h; + + *pWidth = 1 << w; + *pHeight = 1 << h; + + return true; +} + +/** + * When use vertex-texture-fetch technique, we do need + * `gl_vertexId` when we sample per-vertex data. + * WebGL 1.0 does not have `gl_vertexId`; WebGL 2.0, however, does. + * @param mesh + * @param subMeshIndex + * @param gfxDevice + */ +void enableVertexId(Mesh *mesh, uint32_t subMeshIndex, gfx::Device *gfxDevice) { + mesh->getRenderingSubMeshes()[subMeshIndex]->enableVertexIdChannel(gfxDevice); +} + +/** + * + * @param gfxDevice + * @param vec4Capacity Capacity of vec4. + */ +Vec4TextureFactory createVec4TextureFactory(gfx::Device *gfxDevice, uint32_t vec4Capacity) { + bool hasFeatureFloatTexture = static_cast(gfxDevice->getFormatFeatures(gfx::Format::RGBA32F) & gfx::FormatFeature::SAMPLED_TEXTURE) != 0; + + uint32_t pixelRequired = 0; + PixelFormat pixelFormat = PixelFormat::RGBA8888; + uint32_t pixelBytes = 4; + bool useFloat32Array = false; + if (hasFeatureFloatTexture) { + pixelRequired = vec4Capacity; + pixelBytes = 16; + pixelFormat = Texture2D::PixelFormat::RGBA32F; + useFloat32Array = true; + } else { + pixelRequired = 4 * vec4Capacity; + pixelBytes = 4; + pixelFormat = Texture2D::PixelFormat::RGBA8888; + useFloat32Array = false; + } + + uint32_t width = 0; + uint32_t height = 0; + bestSizeToHavePixels(pixelRequired, &width, &height); + CC_ASSERT_GE(width * height, pixelRequired); + + Vec4TextureFactory ret; + ret.width = width; + ret.height = height; + ret.create = [=]() -> MorphTexture * { + auto *texture = ccnew MorphTexture(); // texture will be held by IntrusivePtr in GpuMorphAttribute + texture->initialize(gfxDevice, width, height, pixelBytes, useFloat32Array, pixelFormat); + return texture; + }; + + return ret; +} + +/** + * Provides the access to morph related uniforms. + */ +class MorphUniforms final : public RefCounted { +public: + MorphUniforms(gfx::Device *gfxDevice, uint32_t targetCount) { + _targetCount = targetCount; + _localBuffer = ccnew DataView(ccnew ArrayBuffer(pipeline::UBOMorph::SIZE)); + + _remoteBuffer = gfxDevice->createBuffer(gfx::BufferInfo{ + gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::HOST | gfx::MemoryUsageBit::DEVICE, + pipeline::UBOMorph::SIZE, + pipeline::UBOMorph::SIZE, + }); + } + + ~MorphUniforms() override { + delete _localBuffer; + } + + void destroy() { + _remoteBuffer->destroy(); + } + + gfx::Buffer *getBuffer() const { + return _remoteBuffer; + } + + void setWeights(const ccstd::vector &weights) { + CC_ASSERT_EQ(weights.size(), _targetCount); + for (size_t iWeight = 0; iWeight < weights.size(); ++iWeight) { + _localBuffer->setFloat32(static_cast(pipeline::UBOMorph::OFFSET_OF_WEIGHTS + 4 * iWeight), weights[iWeight]); + } + } + + void setMorphTextureInfo(float width, float height) { + _localBuffer->setFloat32(pipeline::UBOMorph::OFFSET_OF_DISPLACEMENT_TEXTURE_WIDTH, width); + _localBuffer->setFloat32(pipeline::UBOMorph::OFFSET_OF_DISPLACEMENT_TEXTURE_HEIGHT, height); + } + + void setVerticesCount(uint32_t count) { + _localBuffer->setFloat32(pipeline::UBOMorph::OFFSET_OF_VERTICES_COUNT, static_cast(count)); + } + + void commit() { + ArrayBuffer *buffer = _localBuffer->buffer(); + _remoteBuffer->update(buffer->getData(), buffer->byteLength()); + } + +private: + uint32_t _targetCount{0}; + DataView *_localBuffer{nullptr}; + IntrusivePtr _remoteBuffer; +}; + +class CpuComputing final : public SubMeshMorphRendering { +public: + explicit CpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice); + + SubMeshMorphRenderingInstance *createInstance() override; + const ccstd::vector &getData() const; + +private: + ccstd::vector _attributes; + gfx::Device *_gfxDevice{nullptr}; +}; + +class GpuComputing final : public SubMeshMorphRendering { +public: + explicit GpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice); + SubMeshMorphRenderingInstance *createInstance() override; + + void destroy(); + +private: + gfx::Device *_gfxDevice{nullptr}; + const SubMeshMorph *_subMeshMorph{nullptr}; + uint32_t _textureWidth{0}; + uint32_t _textureHeight{0}; + ccstd::vector _attributes; + uint32_t _verticesCount{0}; + + friend class GpuComputingRenderingInstance; +}; + +class CpuComputingRenderingInstance final : public SubMeshMorphRenderingInstance { +public: + explicit CpuComputingRenderingInstance(CpuComputing *owner, uint32_t nVertices, gfx::Device *gfxDevice) { + _owner = owner; //NOTE: release by mesh`s destroy, it`ll call current instance`s destroy method + _morphUniforms = ccnew MorphUniforms(gfxDevice, 0 /* TODO? */); + + auto vec4TextureFactory = createVec4TextureFactory(gfxDevice, nVertices); + _morphUniforms->setMorphTextureInfo(static_cast(vec4TextureFactory.width), static_cast(vec4TextureFactory.height)); + _morphUniforms->commit(); + for (const auto &attributeMorph : _owner->getData()) { + auto *morphTexture = vec4TextureFactory.create(); + _attributes.emplace_back(GpuMorphAttribute{attributeMorph.name, morphTexture}); + } + } + + void setWeights(const ccstd::vector &weights) override { + for (size_t iAttribute = 0; iAttribute < _attributes.size(); ++iAttribute) { + const auto &myAttribute = _attributes[iAttribute]; + Float32Array &valueView = myAttribute.morphTexture->getValueView(); + const auto &attributeMorph = _owner->getData()[iAttribute]; + CC_ASSERT(weights.size() == attributeMorph.targets.size()); + for (size_t iTarget = 0; iTarget < attributeMorph.targets.size(); ++iTarget) { + const auto &targetDisplacements = attributeMorph.targets[iTarget].displacements; + const float weight = weights[iTarget]; + const uint32_t nVertices = targetDisplacements.length() / 3; + if (iTarget == 0) { + for (uint32_t iVertex = 0; iVertex < nVertices; ++iVertex) { + valueView[4 * iVertex + 0] = targetDisplacements[3 * iVertex + 0] * weight; + valueView[4 * iVertex + 1] = targetDisplacements[3 * iVertex + 1] * weight; + valueView[4 * iVertex + 2] = targetDisplacements[3 * iVertex + 2] * weight; + } + } else if (std::fabs(weight) >= std::numeric_limits::epsilon()) { + for (uint32_t iVertex = 0; iVertex < nVertices; ++iVertex) { + valueView[4 * iVertex + 0] += targetDisplacements[3 * iVertex + 0] * weight; + valueView[4 * iVertex + 1] += targetDisplacements[3 * iVertex + 1] * weight; + valueView[4 * iVertex + 2] += targetDisplacements[3 * iVertex + 2] * weight; + } + } + } + + myAttribute.morphTexture->updatePixels(); + } + } + + ccstd::vector requiredPatches() override { + return { + {"CC_MORPH_TARGET_USE_TEXTURE", true}, + {"CC_MORPH_PRECOMPUTED", true}, + }; + } + + void adaptPipelineState(gfx::DescriptorSet *descriptorSet) override { + for (const auto &attribute : _attributes) { + const auto &attributeName = attribute.attributeName; + ccstd::optional binding; + if (attributeName == gfx::ATTR_NAME_POSITION) { + binding = uint32_t{pipeline::POSITIONMORPH::BINDING}; + } else if (attributeName == gfx::ATTR_NAME_NORMAL) { + binding = uint32_t{pipeline::NORMALMORPH::BINDING}; + } else if (attributeName == gfx::ATTR_NAME_TANGENT) { + binding = uint32_t{pipeline::TANGENTMORPH::BINDING}; + } else { + CC_LOG_WARNING("Unexpected attribute!"); + } + + if (binding.has_value()) { + descriptorSet->bindSampler(binding.value(), attribute.morphTexture->getSampler()); + descriptorSet->bindTexture(binding.value(), attribute.morphTexture->getTexture()); + } + } + descriptorSet->bindBuffer(pipeline::UBOMorph::BINDING, _morphUniforms->getBuffer()); + descriptorSet->update(); + } + + void destroy() override { + CC_SAFE_DESTROY(_morphUniforms); + for (auto &myAttribute : _attributes) { + CC_SAFE_DESTROY(myAttribute.morphTexture); + } + } + +private: + ccstd::vector _attributes; + IntrusivePtr _owner; + IntrusivePtr _morphUniforms; +}; + +class GpuComputingRenderingInstance final : public SubMeshMorphRenderingInstance { +public: + explicit GpuComputingRenderingInstance(GpuComputing *owner, gfx::Device *gfxDevice) { + _owner = owner; + _morphUniforms = ccnew MorphUniforms(gfxDevice, static_cast(_owner->_subMeshMorph->targets.size())); + _morphUniforms->setMorphTextureInfo(static_cast(_owner->_textureWidth), static_cast(_owner->_textureHeight)); + _morphUniforms->setVerticesCount(_owner->_verticesCount); + _morphUniforms->commit(); + _attributes = &_owner->_attributes; + } + + void setWeights(const ccstd::vector &weights) override { + _morphUniforms->setWeights(weights); + _morphUniforms->commit(); + } + + ccstd::vector requiredPatches() override { + return { + {"CC_MORPH_TARGET_USE_TEXTURE", true}, + }; + } + + void adaptPipelineState(gfx::DescriptorSet *descriptorSet) override { + for (const auto &attribute : *_attributes) { + const auto &attributeName = attribute.attributeName; + ccstd::optional binding; + if (attributeName == gfx::ATTR_NAME_POSITION) { + binding = uint32_t{pipeline::POSITIONMORPH::BINDING}; + } else if (attributeName == gfx::ATTR_NAME_NORMAL) { + binding = uint32_t{pipeline::NORMALMORPH::BINDING}; + } else if (attributeName == gfx::ATTR_NAME_TANGENT) { + binding = uint32_t{pipeline::TANGENTMORPH::BINDING}; + } else { + CC_LOG_WARNING("Unexpected attribute!"); + } + + if (binding.has_value()) { + descriptorSet->bindSampler(binding.value(), attribute.morphTexture->getSampler()); + descriptorSet->bindTexture(binding.value(), attribute.morphTexture->getTexture()); + } + } + descriptorSet->bindBuffer(pipeline::UBOMorph::BINDING, _morphUniforms->getBuffer()); + descriptorSet->update(); + } + + void destroy() override { + } + +private: + ccstd::vector *_attributes{nullptr}; + IntrusivePtr _owner; + IntrusivePtr _morphUniforms; +}; + +CpuComputing::CpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice) { + _gfxDevice = gfxDevice; + const auto &subMeshMorph = morph->subMeshMorphs[subMeshIndex].value(); + enableVertexId(mesh, subMeshIndex, gfxDevice); + + for (size_t attributeIndex = 0, len = subMeshMorph.attributes.size(); attributeIndex < len; ++attributeIndex) { + const auto &attributeName = subMeshMorph.attributes[attributeIndex]; + + CpuMorphAttribute attr; + attr.name = attributeName; + attr.targets.resize(subMeshMorph.targets.size()); + + uint32_t i = 0; + for (const auto &attributeDisplacement : subMeshMorph.targets) { + const Mesh::IBufferView &displacementsView = attributeDisplacement.displacements[attributeIndex]; + attr.targets[i].displacements = Float32Array(mesh->getData().buffer(), + mesh->getData().byteOffset() + displacementsView.offset, + attributeDisplacement.displacements[attributeIndex].count); + + ++i; + } + + _attributes.emplace_back(attr); + } +} + +SubMeshMorphRenderingInstance *CpuComputing::createInstance() { + return ccnew CpuComputingRenderingInstance( + this, + _attributes[0].targets[0].displacements.length() / 3, + _gfxDevice); +} + +const ccstd::vector &CpuComputing::getData() const { + return _attributes; +} + +GpuComputing::GpuComputing(Mesh *mesh, uint32_t subMeshIndex, const Morph *morph, gfx::Device *gfxDevice) { + _gfxDevice = gfxDevice; + const auto &subMeshMorph = morph->subMeshMorphs[subMeshIndex].value(); + + _subMeshMorph = &subMeshMorph; + // assertIsNonNullable(subMeshMorph); + + enableVertexId(mesh, subMeshIndex, gfxDevice); + + uint32_t nVertices = mesh->getStruct().vertexBundles[mesh->getStruct().primitives[subMeshIndex].vertexBundelIndices[0]].view.count; + _verticesCount = nVertices; + auto nTargets = static_cast(subMeshMorph.targets.size()); + uint32_t vec4Required = nVertices * nTargets; + + auto vec4TextureFactory = createVec4TextureFactory(gfxDevice, vec4Required); + _textureWidth = vec4TextureFactory.width; + _textureHeight = vec4TextureFactory.height; + + // Creates texture for each attribute. + uint32_t attributeIndex = 0; + _attributes.reserve(subMeshMorph.attributes.size()); + for (const auto &attributeName : subMeshMorph.attributes) { + auto *vec4Tex = vec4TextureFactory.create(); + Float32Array &valueView = vec4Tex->getValueView(); + // if (DEV) { // Make it easy to view texture in profilers... + // for (let i = 0; i < valueView.length / 4; ++i) { + // valueView[i * 4 + 3] = 1.0; + // } + // } + + uint32_t morphTargetIndex = 0; + for (const auto &morphTarget : subMeshMorph.targets) { + const auto &displacementsView = morphTarget.displacements[attributeIndex]; + Float32Array displacements(mesh->getData().buffer(), + mesh->getData().byteOffset() + displacementsView.offset, + displacementsView.count); + const uint32_t displacementsOffset = (nVertices * morphTargetIndex) * 4; + for (uint32_t iVertex = 0; iVertex < nVertices; ++iVertex) { + valueView[displacementsOffset + 4 * iVertex + 0] = displacements[3 * iVertex + 0]; + valueView[displacementsOffset + 4 * iVertex + 1] = displacements[3 * iVertex + 1]; + valueView[displacementsOffset + 4 * iVertex + 2] = displacements[3 * iVertex + 2]; + } + + ++morphTargetIndex; + } + + vec4Tex->updatePixels(); + + _attributes.emplace_back(GpuMorphAttribute{attributeName, vec4Tex}); + + ++attributeIndex; + } +} + +SubMeshMorphRenderingInstance *GpuComputing::createInstance() { + return ccnew GpuComputingRenderingInstance(this, _gfxDevice); +} + +void GpuComputing::destroy() { + for (auto &attribute : _attributes) { + attribute.morphTexture->destroy(); + } +} + +} // namespace + +class StdMorphRenderingInstance : public MorphRenderingInstance { +public: + explicit StdMorphRenderingInstance(StdMorphRendering *owner) { + _owner = owner; + size_t nSubMeshes = _owner->_mesh->getStruct().primitives.size(); + _subMeshInstances.resize(nSubMeshes, nullptr); + + for (size_t iSubMesh = 0; iSubMesh < nSubMeshes; ++iSubMesh) { + if (_owner->_subMeshRenderings[iSubMesh] != nullptr) { + _subMeshInstances[iSubMesh] = _owner->_subMeshRenderings[iSubMesh]->createInstance(); + } + } + } + + ~StdMorphRenderingInstance() override = default; + + void setWeights(index_t subMeshIndex, const MeshWeightsType &weights) override { + if (_subMeshInstances[subMeshIndex]) { + _subMeshInstances[subMeshIndex]->setWeights(weights); + } + } + + void adaptPipelineState(index_t subMeshIndex, gfx::DescriptorSet *descriptorSet) override { + if (_subMeshInstances[subMeshIndex]) { + _subMeshInstances[subMeshIndex]->adaptPipelineState(descriptorSet); + } + } + + ccstd::vector requiredPatches(index_t subMeshIndex) override { + CC_ASSERT(_owner->_mesh->getStruct().morph.has_value()); + const auto &subMeshMorphOpt = _owner->_mesh->getStruct().morph.value().subMeshMorphs[subMeshIndex]; + auto *subMeshRenderingInstance = _subMeshInstances[subMeshIndex].get(); + if (subMeshRenderingInstance == nullptr || !subMeshMorphOpt.has_value()) { + return {}; + } + const auto &subMeshMorph = subMeshMorphOpt.value(); + + ccstd::vector patches{ + {"CC_USE_MORPH", true}, + {"CC_MORPH_TARGET_COUNT", static_cast(subMeshMorph.targets.size())}}; + + auto posIter = std::find(subMeshMorph.attributes.begin(), subMeshMorph.attributes.end(), gfx::ATTR_NAME_POSITION); + if (posIter != subMeshMorph.attributes.end()) { + patches.emplace_back(scene::IMacroPatch{ + "CC_MORPH_TARGET_HAS_POSITION", + true, + }); + } + + auto normalIter = std::find(subMeshMorph.attributes.begin(), subMeshMorph.attributes.end(), gfx::ATTR_NAME_NORMAL); + if (normalIter != subMeshMorph.attributes.end()) { + patches.emplace_back(scene::IMacroPatch{ + "CC_MORPH_TARGET_HAS_NORMAL", + true, + }); + } + + auto tangentIter = std::find(subMeshMorph.attributes.begin(), subMeshMorph.attributes.end(), gfx::ATTR_NAME_TANGENT); + if (tangentIter != subMeshMorph.attributes.end()) { + patches.emplace_back(scene::IMacroPatch{ + "CC_MORPH_TARGET_HAS_TANGENT", + true, + }); + } + + auto renderingInstancePatches = subMeshRenderingInstance->requiredPatches(); + for (auto &renderingInstancePatch : renderingInstancePatches) { + patches.emplace_back(renderingInstancePatch); + } + + return patches; + } + + void destroy() override { + for (auto &subMeshInstance : _subMeshInstances) { + if (subMeshInstance != nullptr) { + subMeshInstance->destroy(); + } + } + } + +private: + IntrusivePtr _owner; + ccstd::vector> _subMeshInstances; +}; + +StdMorphRendering::StdMorphRendering(Mesh *mesh, gfx::Device *gfxDevice) { + _mesh = mesh; + const auto &structInfo = _mesh->getStruct(); + if (!structInfo.morph.has_value()) { + return; + } + + const size_t nSubMeshes = structInfo.primitives.size(); + _subMeshRenderings.resize(nSubMeshes, nullptr); + const auto &morph = structInfo.morph.value(); + for (size_t iSubMesh = 0; iSubMesh < nSubMeshes; ++iSubMesh) { + const auto &subMeshMorphHolder = morph.subMeshMorphs[iSubMesh]; + if (!subMeshMorphHolder.has_value()) { + continue; + } + + const auto &subMeshMorph = subMeshMorphHolder.value(); + + if (PREFER_CPU_COMPUTING || subMeshMorph.targets.size() > pipeline::UBOMorph::MAX_MORPH_TARGET_COUNT) { + _subMeshRenderings[iSubMesh] = ccnew CpuComputing( + _mesh, + static_cast(iSubMesh), + &morph, + gfxDevice); + } else { + _subMeshRenderings[iSubMesh] = ccnew GpuComputing( + _mesh, + static_cast(iSubMesh), + &morph, + gfxDevice); + } + } +} + +StdMorphRendering::~StdMorphRendering() = default; + +MorphRenderingInstance *StdMorphRendering::createInstance() { + auto *ret = ccnew StdMorphRenderingInstance(this); + return ret; +} + +} // namespace cc diff --git a/cocos/3d/assets/MorphRendering.h b/cocos/3d/assets/MorphRendering.h new file mode 100644 index 0000000..36dc7aa --- /dev/null +++ b/cocos/3d/assets/MorphRendering.h @@ -0,0 +1,114 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "3d/assets/MorphRendering.h" +#include "3d/assets/Types.h" +#include "base/Ptr.h" +#include "scene/Define.h" + +namespace cc { + +class SubMeshMorphRendering; +class Mesh; + +namespace gfx { +class Device; +class DescriptorSet; +} // namespace gfx + +/** + * @en The instance of [[MorphRendering]] for dedicated control in the mesh renderer. + * The root [[MorphRendering]] is owned by [[Mesh]] asset, each [[MeshRenderer]] can have its own morph rendering instance. + * @zh 用于网格渲染器中独立控制 [[MorphRendering]] 的实例。原始 [[MorphRendering]] 被 [[Mesh]] 资源持有,每个 [[MeshRenderer]] 都持有自己的形变网格渲染实例。 + */ +class MorphRenderingInstance : public RefCounted { +public: + ~MorphRenderingInstance() override = default; + /** + * Sets weights of targets of specified sub mesh. + * @param subMeshIndex + * @param weights + */ + virtual void setWeights(index_t subMeshIndex, const MeshWeightsType &weights) = 0; + + /** + * Adapts pipeline state to do the rendering. + * @param subMeshIndex + * @param pipelineState + */ + virtual void adaptPipelineState(index_t subMeshIndex, gfx::DescriptorSet *descriptorSet) = 0; + + virtual ccstd::vector requiredPatches(index_t subMeshIndex) = 0; + + /** + * Destroy the rendering instance. + */ + virtual void destroy() = 0; +}; + +/** + * @en Interface for classes which control the rendering of morph resources. + * @zh 支持形变网格渲染的基类。 + */ +class MorphRendering : public RefCounted { +public: + ~MorphRendering() override = default; + virtual MorphRenderingInstance *createInstance() = 0; +}; + +/** + * @en Standard morph rendering class, it supports both GPU and CPU based morph blending. + * If sub mesh morph targets count is less than [[pipeline.UBOMorph.MAX_MORPH_TARGET_COUNT]], then GPU based blending is enabled. + * Each of the sub-mesh morph has its own [[MorphRenderingInstance]], + * its morph target weights, render pipeline state and strategy of morph blending are controlled separately. + * @zh 标准形变网格渲染类,它同时支持 CPU 和 GPU 的形变混合计算。 + * 如果子网格形变目标数量少于 [[pipeline.UBOMorph.MAX_MORPH_TARGET_COUNT]],那么就会使用基于 GPU 的形变混合计算。 + * 每个子网格形变都使用自己独立的 [[MorphRenderingInstance]],它的形变目标权重、渲染管线状态和形变混合计算策略都是独立控制的。 + */ +class StdMorphRendering final : public MorphRendering { +public: + explicit StdMorphRendering(Mesh *mesh, gfx::Device *gfxDevice); + ~StdMorphRendering() override; + MorphRenderingInstance *createInstance() override; + +private: + Mesh *_mesh{nullptr}; + ccstd::vector> _subMeshRenderings; + + CC_DISALLOW_COPY_MOVE_ASSIGN(StdMorphRendering); + + friend class StdMorphRenderingInstance; +}; + +/** + * @en Create morph rendering from mesh which contains morph targets data. + * @zh 从包含形变对象的网格资源中创建形变网格渲染对象。 + * @param mesh @en The mesh to create morph rendering from. @zh 用于创建形变网格渲染对象的原始网格资源。 + * @param gfxDevice @en The device instance acquired from [[Root]]. @zh 设备对象实例,可以从 [[Root]] 获取。 + */ +MorphRendering *createMorphRendering(Mesh *mesh, gfx::Device *gfxDevice); + +} // namespace cc diff --git a/cocos/3d/assets/Skeleton.cpp b/cocos/3d/assets/Skeleton.cpp new file mode 100644 index 0000000..5554194 --- /dev/null +++ b/cocos/3d/assets/Skeleton.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "3d/assets/Skeleton.h" + +#include +#include + +#include "base/std/hash/hash.h" + +namespace cc { + +const ccstd::vector &Skeleton::getInverseBindposes() { + if (!_invBindposes.has_value()) { + _invBindposes = ccstd::vector{}; + for (const auto &bindpose : _bindposes) { + _invBindposes.value().emplace_back(bindpose.getInversed()); + } + } + return *_invBindposes; +} + +ccstd::hash_t Skeleton::getHash() { + // hashes should already be computed offline, but if not, make one + if (!_hash) { + std::stringstream sstr; + for (const auto &ibm : _bindposes) { + sstr << std::fixed << std::setprecision(2) + << ibm.m[0] << " " << ibm.m[1] << " " << ibm.m[2] << " " << ibm.m[3] << " " + << ibm.m[4] << " " << ibm.m[5] << " " << ibm.m[6] << " " << ibm.m[7] << " " + << ibm.m[8] << " " << ibm.m[9] << " " << ibm.m[10] << " " << ibm.m[11] << " " + << ibm.m[12] << " " << ibm.m[13] << " " << ibm.m[14] << " " << ibm.m[15] << "\n"; + } + ccstd::string str{sstr.str()}; + ccstd::hash_t seed = 666; + ccstd::hash_range(seed, str.begin(), str.end()); + _hash = seed; + } + return _hash; +} + +bool Skeleton::destroy() { + //cjh TODO: (legacyCC.director.root?.dataPoolManager as DataPoolManager)?.releaseSkeleton(this); + return Super::destroy(); +} + +bool Skeleton::validate() const { + return !_joints.empty() && !_bindposes.empty(); +} + +} // namespace cc diff --git a/cocos/3d/assets/Skeleton.h b/cocos/3d/assets/Skeleton.h new file mode 100644 index 0000000..73e2184 --- /dev/null +++ b/cocos/3d/assets/Skeleton.h @@ -0,0 +1,87 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/assets/Asset.h" + +namespace cc { + +/** + * @en The skeleton asset. It stores the path related to [[SkinnedMeshRenderer.skinningRoot]] of all bones and its bind pose matrix. + * @zh 骨骼资源。骨骼资源记录了每个关节(相对于 [[SkinnedMeshRenderer.skinningRoot]])的路径以及它的绑定姿势矩阵。 + */ +class Skeleton final : public Asset { +public: + using Super = Asset; + Skeleton() = default; + ~Skeleton() override = default; + /** + * @en The path of all bones, the length always equals the length of [[bindposes]] + * @zh 所有关节的路径。该数组的长度始终与 [[bindposes]] 的长度相同。 + */ + inline const ccstd::vector &getJoints() const { + return _joints; + } + + inline void setJoints(const ccstd::vector &value) { + _joints = value; + } + + /** + * @en The bind poses matrix of all bones, the length always equals the length of [[joints]] + * @zh 所有关节的绑定姿势矩阵。该数组的长度始终与 [[joints]] 的长度相同。 + */ + const ccstd::vector &getBindposes() const { + return _bindposes; + } + + void setBindposes(const ccstd::vector &value) { + _bindposes = value; + } + + /** + * @en Gets the inverse bind poses matrix + * @zh 获取反向绑定姿势矩阵 + */ + const ccstd::vector &getInverseBindposes(); + + /** + * @en Gets the hash of the skeleton asset + * @zh 获取骨骼资源的哈希值 + */ + ccstd::hash_t getHash(); + void setHash(ccstd::hash_t hash) { _hash = hash; } + + bool destroy() override; + bool validate() const override; + +private: + ccstd::vector _joints; + ccstd::vector _bindposes; + ccstd::optional> _invBindposes; + ccstd::hash_t _hash{0U}; +}; + +} // namespace cc diff --git a/cocos/3d/assets/Types.h b/cocos/3d/assets/Types.h new file mode 100644 index 0000000..1e91935 --- /dev/null +++ b/cocos/3d/assets/Types.h @@ -0,0 +1,60 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/std/container/string.h" +#include "base/std/optional.h" +#include "core/TypedArray.h" +namespace cc { + +using MeshWeightsType = ccstd::vector; + +/** + * @en Array views for index buffer + * @zh 允许存储索引的数组视图 + */ +using IBArray = ccstd::variant; + +template +T getIBArrayValue(const IBArray &arr, uint32_t idx) { +#define IBARRAY_GET_VALUE(type) \ + do { \ + auto *p = ccstd::get_if(&arr); \ + if (p != nullptr) { \ + return static_cast((*p)[idx]); \ + } \ + } while (false) + + IBARRAY_GET_VALUE(Uint16Array); + IBARRAY_GET_VALUE(Uint32Array); + IBARRAY_GET_VALUE(Uint8Array); + +#undef IBARRAY_GET_VALUE + + return 0; +} + +} // namespace cc diff --git a/cocos/3d/misc/Buffer.cpp b/cocos/3d/misc/Buffer.cpp new file mode 100644 index 0000000..6fc9ad0 --- /dev/null +++ b/cocos/3d/misc/Buffer.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "3d/misc/Buffer.h" +#include "base/std/variant.h" + +namespace cc { + +namespace { +ccstd::unordered_map typeMap{ + {gfx::FormatType::UNORM, "Uint"}, + {gfx::FormatType::SNORM, "Int"}, + {gfx::FormatType::UINT, "Uint"}, + {gfx::FormatType::INT, "Int"}, + {gfx::FormatType::UFLOAT, "Float"}, + {gfx::FormatType::FLOAT, "Float"}, +}; + +ccstd::string getDataViewType(const gfx::FormatInfo &info) { + ccstd::string type; + auto iter = typeMap.find(info.type); + if (iter != typeMap.end()) { + type = iter->second; + } else { + type = "Uint"; + } + + const uint32_t bytes = info.size / info.count * 8; + return type + std::to_string(bytes); +} + +} // namespace + +using DataVariant = ccstd::variant; +using MapBufferCallback = std::function; + +DataView mapBuffer(DataView &target, + const MapBufferCallback &callback, + ccstd::optional aFormat, + ccstd::optional aOffset, + ccstd::optional aLength, + ccstd::optional aStride, + DataView *out) { + gfx::Format format = aFormat.has_value() ? aFormat.value() : gfx::Format::R32F; + uint32_t offset = aOffset.has_value() ? aOffset.value() : 0; + uint32_t length = aLength.has_value() ? aLength.value() : target.byteLength() - offset; + uint32_t stride = aStride.has_value() ? aStride.value() : 0; + + DataView dataView; + if (out == nullptr) { + out = &dataView; + dataView.assign(target.buffer()->slice(target.byteOffset(), target.byteOffset() + target.byteLength())); + } + + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(format)]; + if (stride == 0) { + stride = info.size; + } + + static const ccstd::string SET_PREFIX{"set"}; + static const ccstd::string GET_PREFIX{"get"}; + + bool isFloat = info.type == gfx::FormatType::FLOAT || info.type == gfx::FormatType::UFLOAT; + DataView::IntWritter intWritter = nullptr; + if (!isFloat) { + intWritter = DataView::intWritterMap[SET_PREFIX + getDataViewType(info)]; + } + + DataView::ReaderVariant intReader; + if (!isFloat) { + intReader = DataView::intReaderMap[GET_PREFIX + getDataViewType(info)]; + } + + const uint32_t componentBytesLength = info.size / info.count; + const uint32_t nSeg = floor(length / stride); + + for (uint32_t iSeg = 0; iSeg < nSeg; ++iSeg) { + const uint32_t x = offset + stride * iSeg; + for (uint32_t iComponent = 0; iComponent < info.count; ++iComponent) { + const uint32_t y = x + componentBytesLength * iComponent; + if (isFloat) { + float cur = target.getFloat32(y); + auto dataVariant = callback(cur, iComponent, target); + if (ccstd::holds_alternative(dataVariant)) { + out->setFloat32(y, ccstd::get(dataVariant)); + } else { + CC_LOG_ERROR("mapBuffer, wrong data type, expect float"); + } + } else { + int32_t cur = target.readInt(intReader, y); + // iComponent is usually more useful than y + auto dataVariant = callback(cur, iComponent, target); + if (ccstd::holds_alternative(dataVariant)) { + (target.*intWritter)(y, ccstd::get(dataVariant)); + } else { + CC_LOG_ERROR("mapBuffer, wrong data type, expect int32_t"); + } + } + } + } + return dataView; +} + +} // namespace cc diff --git a/cocos/3d/misc/Buffer.h b/cocos/3d/misc/Buffer.h new file mode 100644 index 0000000..e306101 --- /dev/null +++ b/cocos/3d/misc/Buffer.h @@ -0,0 +1,125 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include "base/std/optional.h" +#include "core/DataView.h" +#include "renderer/gfx-base/GFXDef.h" + +namespace cc { + +// default params behaviors just like on an plain, compact Float32Array +template +void writeBuffer(DataView &target, + const ccstd::vector &data, + const gfx::Format &format = gfx::Format::R32F, + uint32_t offset = 0, + uint32_t stride = 0) { + const gfx::FormatInfo &info = gfx::GFX_FORMAT_INFOS[static_cast(format)]; + if (stride == 0) { + stride = info.size; + } + const uint32_t componentBytesLength = info.size / info.count; + const auto nSeg = static_cast(floor(data.size() / info.count)); + + const uint32_t bytes = info.size / info.count * 8; + + for (uint32_t iSeg = 0; iSeg < nSeg; ++iSeg) { + uint32_t x = offset + stride * iSeg; + for (uint32_t iComponent = 0; iComponent < info.count; ++iComponent) { + const uint32_t y = x + componentBytesLength * iComponent; + // default Little-Endian + switch (info.type) { + case gfx::FormatType::UINT: + case gfx::FormatType::UNORM: + switch (bytes) { + case 8: + target.setUint8(y, static_cast(data[info.count * iSeg + iComponent])); + break; + case 16: + target.setUint16(y, static_cast(data[info.count * iSeg + iComponent])); + break; + case 32: + target.setUint32(y, static_cast(data[info.count * iSeg + iComponent])); + break; + default: + CC_ABORT(); + break; + } + break; + case gfx::FormatType::INT: + case gfx::FormatType::SNORM: + switch (bytes) { + case 8: + target.setInt8(y, static_cast(data[info.count * iSeg + iComponent])); + break; + case 16: + target.setInt16(y, static_cast(data[info.count * iSeg + iComponent])); + break; + case 32: + target.setInt32(y, static_cast(data[info.count * iSeg + iComponent])); + break; + default: + CC_ABORT(); + break; + } + break; + case gfx::FormatType::UFLOAT: + case gfx::FormatType::FLOAT: + switch (bytes) { + case 8: + target.setFloat32(y, static_cast(data[info.count * iSeg + iComponent])); + break; + case 16: + target.setFloat32(y, static_cast(data[info.count * iSeg + iComponent])); + break; + case 32: + target.setFloat32(y, static_cast(data[info.count * iSeg + iComponent])); + break; + default: + CC_ABORT(); + break; + } + break; + default: + CC_ABORT(); + break; + } + } + } +} + +using DataVariant = ccstd::variant; +using MapBufferCallback = std::function; + +DataView mapBuffer(DataView &target, + const MapBufferCallback &callback, + ccstd::optional aFormat, + ccstd::optional aOffset, + ccstd::optional aLength, + ccstd::optional aStride, + DataView *out); + +} // namespace cc diff --git a/cocos/3d/misc/BufferBlob.cpp b/cocos/3d/misc/BufferBlob.cpp new file mode 100644 index 0000000..9fe2e5c --- /dev/null +++ b/cocos/3d/misc/BufferBlob.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "3d/misc/BufferBlob.h" +#include "core/TypedArray.h" + +namespace cc { + +void BufferBlob::setNextAlignment(uint32_t align) { + if (align != 0) { + const uint32_t remainder = _length % align; + if (remainder != 0) { + const uint32_t padding = align - remainder; + _arrayBufferOrPaddings.emplace_back(padding); + _length += padding; + } + } +} + +uint32_t BufferBlob::addBuffer(ArrayBuffer *arrayBuffer) { + const uint32_t result = _length; + _arrayBufferOrPaddings.emplace_back(arrayBuffer); + _length += arrayBuffer->byteLength(); + return result; +} + +ArrayBuffer::Ptr BufferBlob::getCombined() { + Int8Array result(_length); + uint32_t counter = 0; + + for (const auto &arrayBufferOrPadding : _arrayBufferOrPaddings) { + if (const auto *p = ccstd::get_if(&arrayBufferOrPadding)) { + counter += *p; + } else if (const auto *p = ccstd::get_if(&arrayBufferOrPadding)) { + result.set(*p, counter); + counter += (*p)->byteLength(); + } + } + + return result.buffer(); +} + +} // namespace cc diff --git a/cocos/3d/misc/BufferBlob.h b/cocos/3d/misc/BufferBlob.h new file mode 100644 index 0000000..ce8f70f --- /dev/null +++ b/cocos/3d/misc/BufferBlob.h @@ -0,0 +1,47 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include "base/std/variant.h" +#include "cocos/base/std/container/vector.h" +#include "core/ArrayBuffer.h" + +namespace cc { + +class BufferBlob { +public: + void setNextAlignment(uint32_t align); + + uint32_t addBuffer(ArrayBuffer *arrayBuffer); + + inline uint32_t getLength() const { return _length; } + + ArrayBuffer::Ptr getCombined(); + +private: + ccstd::vector> _arrayBufferOrPaddings; + uint32_t _length{0}; +}; + +} // namespace cc diff --git a/cocos/3d/misc/CreateMesh.cpp b/cocos/3d/misc/CreateMesh.cpp new file mode 100644 index 0000000..ffed86a --- /dev/null +++ b/cocos/3d/misc/CreateMesh.cpp @@ -0,0 +1,483 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "3d/misc/CreateMesh.h" +#include +#include +#include "3d/misc/Buffer.h" +#include "3d/misc/BufferBlob.h" +#include "core/ArrayBuffer.h" +#include "core/DataView.h" +#include "core/assets/RenderingSubMesh.h" +#include "meshopt/meshoptimizer.h" +#include "renderer/gfx-base/GFXDef-common.h" + +namespace cc { +namespace { +gfx::AttributeList defAttrs = { + gfx::Attribute{gfx::ATTR_NAME_POSITION, gfx::Format::RGB32F}, + gfx::Attribute{gfx::ATTR_NAME_NORMAL, gfx::Format::RGB32F}, + gfx::Attribute{gfx::ATTR_NAME_TEX_COORD, gfx::Format::RG32F}, + gfx::Attribute{gfx::ATTR_NAME_TANGENT, gfx::Format::RGBA32F}, + gfx::Attribute{gfx::ATTR_NAME_COLOR, gfx::Format::RGBA32F}, +}; +} // namespace + +Mesh *MeshUtils::createMesh(const IGeometry &geometry, Mesh *out /*= nullptr*/, const ICreateMeshOptions &options /*= {}*/) { + if (!out) { + out = ccnew Mesh(); + } + + out->reset(createMeshInfo(geometry, options)); + return out; +} + +Mesh::ICreateInfo MeshUtils::createMeshInfo(const IGeometry &geometry, const ICreateMeshOptions &options /* = {}*/) { + // Collect attributes and calculate length of result vertex buffer. + gfx::AttributeList attributes; + uint32_t stride = 0; + struct Channel { + uint32_t offset{0}; + ccstd::vector data; // float? + gfx::Attribute attribute; + }; + ccstd::vector channels; + uint32_t vertCount = 0; + + const gfx::Attribute *attr = nullptr; + + ccstd::vector positions(geometry.positions); + + if (!positions.empty()) { + attr = nullptr; + if (geometry.attributes.has_value()) { + for (const auto &att : geometry.attributes.value()) { + if (att.name == gfx::ATTR_NAME_POSITION) { + attr = &att; + break; + } + } + } + + if (attr == nullptr) { + attr = &defAttrs[0]; + } + + attributes.emplace_back(*attr); + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(attr->format)]; + vertCount = std::max(vertCount, static_cast(std::floor(positions.size() / info.count))); + channels.emplace_back(Channel{stride, positions, *attr}); + stride += info.size; + } + + if (geometry.normals.has_value() && !geometry.normals.value().empty()) { + attr = nullptr; + if (geometry.attributes.has_value()) { + for (const auto &att : geometry.attributes.value()) { + if (att.name == gfx::ATTR_NAME_NORMAL) { + attr = &att; + break; + } + } + } + + if (attr == nullptr) { + attr = &defAttrs[1]; + } + + attributes.emplace_back(*attr); + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(attr->format)]; + vertCount = std::max(vertCount, static_cast(std::floor(geometry.normals->size() / info.count))); + channels.emplace_back(Channel{stride, geometry.normals.value(), *attr}); + stride += info.size; + } + + if (geometry.uvs.has_value() && !geometry.uvs.value().empty()) { + attr = nullptr; + if (geometry.attributes.has_value()) { + for (const auto &att : geometry.attributes.value()) { + if (att.name == gfx::ATTR_NAME_TEX_COORD) { + attr = &att; + break; + } + } + } + + if (attr == nullptr) { + attr = &defAttrs[2]; + } + + attributes.emplace_back(*attr); + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(attr->format)]; + vertCount = std::max(vertCount, static_cast(std::floor(geometry.uvs->size() / info.count))); + channels.emplace_back(Channel{stride, geometry.uvs.value(), *attr}); + stride += info.size; + } + + if (geometry.tangents.has_value() && !geometry.tangents.value().empty()) { + attr = nullptr; + if (geometry.attributes.has_value()) { + for (const auto &att : geometry.attributes.value()) { + if (att.name == gfx::ATTR_NAME_TANGENT) { + attr = &att; + break; + } + } + } + + if (attr == nullptr) { + attr = &defAttrs[3]; + } + + attributes.emplace_back(*attr); + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(attr->format)]; + vertCount = std::max(vertCount, static_cast(std::floor(geometry.tangents->size() / info.count))); + channels.emplace_back(Channel{stride, geometry.tangents.value(), *attr}); + stride += info.size; + } + + if (geometry.colors.has_value() && !geometry.colors.value().empty()) { + attr = nullptr; + if (geometry.attributes.has_value()) { + for (const auto &att : geometry.attributes.value()) { + if (att.name == gfx::ATTR_NAME_COLOR) { + attr = &att; + break; + } + } + } + + if (attr == nullptr) { + attr = &defAttrs[4]; + } + + attributes.emplace_back(*attr); + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(attr->format)]; + vertCount = std::max(vertCount, static_cast(std::floor(geometry.colors->size() / info.count))); + channels.emplace_back(Channel{stride, geometry.colors.value(), *attr}); + stride += info.size; + } + + if (geometry.customAttributes.has_value()) { + for (const auto &ca : geometry.customAttributes.value()) { + const auto &info = gfx::GFX_FORMAT_INFOS[static_cast(attr->format)]; + attributes.emplace_back(ca.attr); + vertCount = std::max(vertCount, static_cast(std::floor(ca.values.size() / info.count))); + channels.emplace_back(Channel{stride, ca.values, ca.attr}); + stride += info.size; + } + } + + // Use this to generate final merged buffer. + BufferBlob bufferBlob; + + // Fill vertex buffer. + auto *vertexBuffer = ccnew ArrayBuffer(vertCount * stride); + DataView vertexBufferView(vertexBuffer); + for (const auto &channel : channels) { + writeBuffer(vertexBufferView, channel.data, channel.attribute.format, channel.offset, stride); + } + bufferBlob.setNextAlignment(0); + Mesh::IVertexBundle vertexBundle; + Mesh::IBufferView buffferView; + + buffferView.offset = bufferBlob.getLength(); + buffferView.length = static_cast(vertexBuffer->byteLength()); + buffferView.count = vertCount; + buffferView.stride = stride; + vertexBundle.attributes = attributes; + vertexBundle.view = buffferView; + + bufferBlob.addBuffer(vertexBuffer); + + // Fill index buffer. + ArrayBuffer::Ptr indexBuffer; + uint32_t idxCount = 0; + const uint32_t idxStride = 2; + if (geometry.indices.has_value()) { + const ccstd::vector &indices = geometry.indices.value(); + idxCount = static_cast(indices.size()); + indexBuffer = ccnew ArrayBuffer(idxStride * idxCount); + DataView indexBufferView(indexBuffer); + writeBuffer(indexBufferView, indices, gfx::Format::R16UI); + } + + // Create primitive. + Mesh::ISubMesh primitive; + primitive.vertexBundelIndices = {0}; + primitive.primitiveMode = geometry.primitiveMode.has_value() ? geometry.primitiveMode.value() : gfx::PrimitiveMode::TRIANGLE_LIST; + + if (indexBuffer) { + bufferBlob.setNextAlignment(idxStride); + Mesh::IBufferView bufferView; + bufferView.offset = bufferBlob.getLength(); + bufferView.length = indexBuffer->byteLength(); + bufferView.count = idxCount; + bufferView.stride = idxStride; + primitive.indexView = bufferView; + bufferBlob.addBuffer(indexBuffer); + } + + ccstd::optional minPosition = geometry.minPos; + if (!minPosition.has_value() && options.calculateBounds.has_value() && options.calculateBounds.value()) { + minPosition = Vec3(std::numeric_limits::infinity(), std::numeric_limits::infinity(), std::numeric_limits::infinity()); + for (uint32_t iVertex = 0; iVertex < vertCount; ++iVertex) { + Vec3::min(minPosition.value(), Vec3(positions[iVertex * 3 + 0], positions[iVertex * 3 + 1], positions[iVertex * 3 + 2]), &minPosition.value()); + } + } + + ccstd::optional maxPosition = geometry.maxPos; + if (!maxPosition.has_value() && options.calculateBounds.has_value() && options.calculateBounds.value()) { + maxPosition = Vec3(-std::numeric_limits::infinity(), -std::numeric_limits::infinity(), -std::numeric_limits::infinity()); + for (uint32_t iVertex = 0; iVertex < vertCount; ++iVertex) { + Vec3::max(maxPosition.value(), Vec3(positions[iVertex * 3 + 0], positions[iVertex * 3 + 1], positions[iVertex * 3 + 2]), &maxPosition.value()); + } + } + + // Create mesh struct + Mesh::IStruct meshStruct; + meshStruct.vertexBundles = {vertexBundle}; + meshStruct.primitives = {primitive}; + + if (minPosition.has_value()) { + meshStruct.minPosition = minPosition.value(); + } + if (maxPosition.has_value()) { + meshStruct.maxPosition = maxPosition.value(); + } + + Mesh::ICreateInfo createInfo; + createInfo.structInfo = std::move(meshStruct); + createInfo.data = Uint8Array(bufferBlob.getCombined()); + return createInfo; +} + +static inline uint32_t getPadding(uint32_t length, uint32_t align) { + if (align > 0U) { + const uint32_t remainder = length % align; + if (remainder != 0U) { + const uint32_t padding = align - remainder; + return padding; + } + } + + return 0U; +} + +Mesh *MeshUtils::createDynamicMesh(index_t primitiveIndex, const IDynamicGeometry &geometry, Mesh *out /*= nullptr*/, const ICreateDynamicMeshOptions &options /*= {}*/) { + if (!out) { + out = ccnew Mesh(); + } + + out->reset(MeshUtils::createDynamicMeshInfo(geometry, options)); + out->initialize(); + out->updateSubMesh(primitiveIndex, geometry); + + return out; +} + +Mesh::ICreateInfo MeshUtils::createDynamicMeshInfo(const IDynamicGeometry &geometry, const ICreateDynamicMeshOptions &options /* = {}*/) { + gfx::AttributeList attributes; + uint32_t stream = 0U; + + if (!geometry.positions.empty()) { + attributes.push_back({gfx::ATTR_NAME_POSITION, gfx::Format::RGB32F, false, stream++, false, 0U}); + } + + if (geometry.normals.has_value() && !geometry.normals.value().empty()) { + attributes.push_back({gfx::ATTR_NAME_NORMAL, gfx::Format::RGB32F, false, stream++, false, 0U}); + } + + if (geometry.uvs.has_value() && !geometry.uvs.value().empty()) { + attributes.push_back({gfx::ATTR_NAME_TEX_COORD, gfx::Format::RG32F, false, stream++, false, 0U}); + } + + if (geometry.tangents.has_value() && !geometry.tangents.value().empty()) { + attributes.push_back({gfx::ATTR_NAME_TANGENT, gfx::Format::RGBA32F, false, stream++, false, 0U}); + } + + if (geometry.colors.has_value() && !geometry.colors.value().empty()) { + attributes.push_back({gfx::ATTR_NAME_COLOR, gfx::Format::RGBA32F, false, stream++, false, 0U}); + } + + if (geometry.customAttributes.has_value()) { + for (const auto &ca : geometry.customAttributes.value()) { + auto attr = ca.attr; + attr.stream = stream++; + attributes.emplace_back(attr); + } + } + + ccstd::vector vertexBundles; + ccstd::vector primitives; + uint32_t dataSize = 0U; + + for (auto i = 0U; i < options.maxSubMeshes; i++) { + Mesh::ISubMesh primitive; + primitive.primitiveMode = geometry.primitiveMode.has_value() ? geometry.primitiveMode.value() : gfx::PrimitiveMode::TRIANGLE_LIST; + + // add vertex buffers + for (const auto &attr : attributes) { + const auto &formatInfo = gfx::GFX_FORMAT_INFOS[static_cast(attr.format)]; + uint32_t vertexBufferSize = options.maxSubMeshVertices * formatInfo.size; + + Mesh::IBufferView vertexView = { + dataSize, + vertexBufferSize, + 0U, + formatInfo.size}; + + Mesh::IVertexBundle vertexBundle = { + 0U, + vertexView, + {attr}}; + + const auto vertexBundleIndex = static_cast(vertexBundles.size()); + primitive.vertexBundelIndices.emplace_back(vertexBundleIndex); + vertexBundles.emplace_back(vertexBundle); + dataSize += vertexBufferSize; + } + + // add index buffer + uint32_t stride = 0U; + if (geometry.indices16.has_value() && !geometry.indices16.value().empty()) { + stride = sizeof(uint16_t); + } else if (geometry.indices32.has_value() && !geometry.indices32.value().empty()) { + stride = sizeof(uint32_t); + } + + if (stride > 0U) { + dataSize += getPadding(dataSize, stride); + uint32_t indexBufferSize = options.maxSubMeshIndices * stride; + + Mesh::IBufferView indexView = { + dataSize, + indexBufferSize, + 0U, + stride}; + + primitive.indexView = indexView; + dataSize += indexBufferSize; + } + + primitives.emplace_back(primitive); + } + + Mesh::IDynamicInfo dynamicInfo = {options.maxSubMeshes, + options.maxSubMeshVertices, + options.maxSubMeshIndices}; + + Mesh::IDynamicStruct dynamicStruct; + dynamicStruct.info = dynamicInfo; + dynamicStruct.bounds.resize(options.maxSubMeshes); + for (auto &bound : dynamicStruct.bounds) { + bound.setValid(false); + } + + Mesh::IStruct meshStruct; + meshStruct.vertexBundles = vertexBundles; + meshStruct.primitives = primitives; + meshStruct.dynamic = std::move(dynamicStruct); + + Mesh::ICreateInfo createInfo; + createInfo.structInfo = std::move(meshStruct); + createInfo.data = Uint8Array(dataSize); + return createInfo; +} + +void MeshUtils::inflateMesh(const Mesh::IStruct &structInfo, Uint8Array &data) { + uLongf uncompressedSize = 0U; + for (const auto &prim : structInfo.primitives) { + if (prim.indexView.has_value()) { + uncompressedSize += prim.indexView->length + prim.indexView->stride; + } + if (prim.cluster.has_value()) { + uncompressedSize += prim.cluster->vertexView.length + prim.cluster->vertexView.stride; + uncompressedSize += prim.cluster->triangleView.length + prim.cluster->triangleView.stride; + uncompressedSize += prim.cluster->clusterView.length + prim.cluster->clusterView.stride; + uncompressedSize += prim.cluster->coneView.length + prim.cluster->coneView.stride; + } + } + for (const auto &vb : structInfo.vertexBundles) { + uncompressedSize += vb.view.length + vb.view.stride; + } + auto uncompressedData = Uint8Array(static_cast(uncompressedSize)); + auto res = uncompress(uncompressedData.buffer()->getData(), &uncompressedSize, data.buffer()->getData(), data.byteLength()); + data = Uint8Array(uncompressedData.buffer(), 0, static_cast(uncompressedSize)); +} + +void MeshUtils::decodeMesh(Mesh::IStruct &structInfo, Uint8Array &data) { + BufferBlob bufferBlob; + + for (auto &bundle : structInfo.vertexBundles) { + auto &view = bundle.view; + auto bound = view.count * view.stride; + auto *buffer = ccnew ArrayBuffer(bound); + auto vertex = Uint8Array(data.buffer(), view.offset, view.length); + int res = meshopt_decodeVertexBuffer(buffer->getData(), view.count, view.stride, vertex.buffer()->getData() + vertex.byteOffset(), view.length); + if (res < 0) { + assert(false && "failed to decode vertex buffer"); + } + + bufferBlob.setNextAlignment(view.stride); + Mesh::IVertexBundle vertexBundle; + Mesh::IBufferView buffferView; + buffferView.offset = bufferBlob.getLength(); + buffferView.length = bound; + buffferView.count = view.count; + buffferView.stride = view.stride; + bufferBlob.addBuffer(buffer); + + bundle.view = buffferView; + } + + for (auto &primitive : structInfo.primitives) { + if (!primitive.indexView.has_value()) { + continue; + } + + auto view = *primitive.indexView; + auto bound = view.count * view.stride; + auto *buffer = ccnew ArrayBuffer(bound); + auto index = DataView(data.buffer(), view.offset, view.length); + int res = meshopt_decodeIndexBuffer(buffer->getData(), view.count, view.stride, index.buffer()->getData() + index.byteOffset(), view.length); + if (res < 0) { + assert(false && "failed to decode index buffer"); + } + + bufferBlob.setNextAlignment(view.stride); + Mesh::IBufferView buffferView; + buffferView.offset = bufferBlob.getLength(); + buffferView.length = bound; + buffferView.count = view.count; + buffferView.stride = view.stride; + bufferBlob.addBuffer(buffer); + + primitive.indexView = buffferView; + } + + data = Uint8Array(bufferBlob.getCombined()); +} + +} // namespace cc diff --git a/cocos/3d/misc/CreateMesh.h b/cocos/3d/misc/CreateMesh.h new file mode 100644 index 0000000..4aba2ee --- /dev/null +++ b/cocos/3d/misc/CreateMesh.h @@ -0,0 +1,101 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include "base/std/optional.h" + +#include "3d/assets/Mesh.h" +#include "primitive/PrimitiveDefine.h" + +namespace cc { + +struct ICreateMeshOptions { + /** + * @en calculate mesh's aabb or not + * @zh 是否计算模型的包围盒。 + */ + ccstd::optional calculateBounds; +}; + +struct ICreateDynamicMeshOptions { + /** + * @en max submesh count + * @zh 最大子模型个数。 + */ + uint32_t maxSubMeshes{1U}; + + /** + * @en max submesh vertex count + * @zh 子模型最大顶点个数。 + */ + uint32_t maxSubMeshVertices{1024U}; + + /** + * @en max submesh index count + * @zh 子模型最大索引个数。 + */ + uint32_t maxSubMeshIndices{1024U}; +}; + +/** + * @en mesh utility class, use to create mesh. + * @zh 网格工具类,用于创建网格。 + */ +class MeshUtils { +public: + /** + * @en create a static mesh. + * @zh 创建一个静态网格。 + */ + static Mesh *createMesh(const IGeometry &geometry, Mesh *out = nullptr, const ICreateMeshOptions &options = {}); + + /** + * @en create a static mesh ICreateInfo. + * @zh 创建一个静态网格ICreateInfo。 + */ + static Mesh::ICreateInfo createMeshInfo(const IGeometry &geometry, const ICreateMeshOptions &options = {}); + + /** + * @en create a dynamic mesh. + * @zh 创建一个动态网格。 + */ + static Mesh *createDynamicMesh(index_t primitiveIndex, const IDynamicGeometry &geometry, Mesh *out = nullptr, const ICreateDynamicMeshOptions &options = {}); + + /** + * @en create a dynamic mesh ICreateInfo. + * @zh 创建一个动态网格ICreateInfo。 + */ + static Mesh::ICreateInfo createDynamicMeshInfo(const IDynamicGeometry &geometry, const ICreateDynamicMeshOptions &options = {}); + + /** + * + */ + static void inflateMesh(const Mesh::IStruct &structInfo, Uint8Array &data); + + static void decodeMesh(Mesh::IStruct &structInfo, Uint8Array &data); + + static void dequantizeMesh(Mesh::IStruct &structInfo, Uint8Array &data); +}; + +} // namespace cc diff --git a/cocos/3d/models/BakedSkinningModel.cpp b/cocos/3d/models/BakedSkinningModel.cpp new file mode 100644 index 0000000..4783185 --- /dev/null +++ b/cocos/3d/models/BakedSkinningModel.cpp @@ -0,0 +1,258 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "3d/models/BakedSkinningModel.h" +#include "3d/assets/Mesh.h" +//#include "3d/skeletal-animation/DataPoolManager.h" +#include "core/Root.h" +#include "scene/Model.h" +#include "scene/SubModel.h" + +namespace { +const cc::gfx::SamplerInfo JOINT_TEXTURE_SAMPLER_INFO{ + cc::gfx::Filter::POINT, + cc::gfx::Filter::POINT, + cc::gfx::Filter::NONE, + cc::gfx::Address::CLAMP, + cc::gfx::Address::CLAMP, + cc::gfx::Address::CLAMP, +}; + +ccstd::vector myPatches{ + {"CC_USE_SKINNING", true}, + {"CC_USE_BAKED_ANIMATION", true}}; + +const ccstd::string INST_JOINT_ANIM_INFO = "a_jointAnimInfo"; +} // namespace +namespace cc { + +BakedSkinningModel::BakedSkinningModel() +//, _dataPoolManager(Root::getInstance()->getDataPoolManager()) +{ + _type = Model::Type::BAKED_SKINNING; + _jointMedium.jointTextureInfo.reset(4); + // JSB uses _dataPoolManager in JS and the data is synchronized by syncDataForJS & syncAnimInfoForJS + // _jointMedium.animInfo = _dataPoolManager->jointAnimationInfo->getData(); +} + +void BakedSkinningModel::destroy() { + // CC_SAFE_DELETE(uploadedAnim); + _jointMedium.boundsInfo.clear(); + + if (_jointMedium.buffer != nullptr) { + CC_SAFE_DESTROY_NULL(_jointMedium.buffer); + } + if (_jointMedium.texture.has_value()) { + CC_SAFE_DELETE(_jointMedium.texture.value()); + } + applyJointTexture(ccstd::nullopt); + Super::destroy(); +} + +void BakedSkinningModel::bindSkeleton(Skeleton *skeleton, Node *skinningRoot, Mesh *mesh) { + _skeleton = skeleton; + _mesh = mesh; + if (skeleton == nullptr || skinningRoot == nullptr || mesh == nullptr) return; + setTransform(skinningRoot); + + // JSB uses _dataPoolManager in JS and the data is synchronized by syncDataForJS & syncAnimInfoForJS + // _jointMedium.animInfo = _dataPoolManager->jointAnimationInfo->getData(skinningRoot->getUuid()); + + if (_jointMedium.buffer == nullptr) { + _jointMedium.buffer = _device->createBuffer({ + gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE, + pipeline::UBOSkinning::size, + pipeline::UBOSkinning::size, + }); + } +} + +void BakedSkinningModel::updateTransform(uint32_t stamp) { + Super::updateTransform(stamp); + if (!_isUploadedAnim) { + return; + } + IAnimInfo &animInfo = _jointMedium.animInfo; + geometry::AABB *skelBound = nullptr; + const float *curFrame = animInfo.curFrame; + // float curFrame = info.data[0]; + auto index = static_cast(std::roundf(*curFrame)); + if (!_jointMedium.boundsInfo.empty() && index < _jointMedium.boundsInfo.size()) { + skelBound = &_jointMedium.boundsInfo[index].value(); + } + + if (_worldBounds && skelBound != nullptr) { + Node *node = getTransform(); + skelBound->transform(node->getWorldMatrix(), _worldBounds); + _worldBoundsDirty = true; + } +} + +void BakedSkinningModel::updateUBOs(uint32_t stamp) { + Super::updateUBOs(stamp); + + IAnimInfo &info = _jointMedium.animInfo; + const int idx = _instAnimInfoIdx; + const float *curFrame = info.curFrame; + bool hasNonInstancingPass = false; + for (const auto &subModel : _subModels) { + if (idx >= 0) { + auto &views = subModel->getInstancedAttributeBlock().views[idx]; + setTypedArrayValue(views, 0, *curFrame); + } else { + hasNonInstancingPass = true; + } + } + + const uint32_t frameDataBytes = info.frameDataBytes; + if (hasNonInstancingPass && *info.dirtyForJSB != 0) { + info.buffer->update(curFrame, frameDataBytes); + *info.dirtyForJSB = 0; + } +} + +void BakedSkinningModel::applyJointTexture(const ccstd::optional &texture) { + auto oldTex = _jointMedium.texture; + if (oldTex.has_value() && texture.has_value() && (&oldTex.value() != &texture.value())) { + // _dataPoolManager->jointTexturePool->releaseHandle(oldTex.value()); + } + _jointMedium.texture = texture; + if (!texture.has_value()) { + return; + } + auto *textureHandle = texture.value(); + auto *buffer = _jointMedium.buffer.get(); + auto &jointTextureInfo = _jointMedium.jointTextureInfo; + jointTextureInfo[0] = static_cast(textureHandle->handle.texture->getWidth()); + jointTextureInfo[1] = static_cast(_skeleton->getJoints().size()); + jointTextureInfo[2] = static_cast(textureHandle->pixelOffset) + 0.1F; // guard against floor() underflow + jointTextureInfo[3] = 1 / jointTextureInfo[0]; + updateInstancedJointTextureInfo(); + if (buffer != nullptr) { + buffer->update(&jointTextureInfo[0], jointTextureInfo.byteLength()); + } + auto *tex = textureHandle->handle.texture; + + for (const auto &subModel : _subModels) { + auto *descriptorSet = subModel->getDescriptorSet(); + descriptorSet->bindTexture(pipeline::JOINTTEXTURE::BINDING, tex); + } +} + +ccstd::vector BakedSkinningModel::getMacroPatches(index_t subModelIndex) { + auto patches = Super::getMacroPatches(subModelIndex); + patches.reserve(patches.size() + myPatches.size()); + patches.insert(std::end(patches), std::begin(myPatches), std::end(myPatches)); + return patches; +} + +void BakedSkinningModel::updateLocalDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) { + Super::updateLocalDescriptors(subModelIndex, descriptorSet); + gfx::Buffer *buffer = _jointMedium.buffer; + auto &texture = _jointMedium.texture; + const IAnimInfo &animInfo = _jointMedium.animInfo; + descriptorSet->bindBuffer(pipeline::UBOSkinningTexture::BINDING, buffer); + descriptorSet->bindBuffer(pipeline::UBOSkinningAnimation::BINDING, animInfo.buffer); + if (texture.has_value()) { + auto *sampler = _device->getSampler(JOINT_TEXTURE_SAMPLER_INFO); + descriptorSet->bindTexture(pipeline::JOINTTEXTURE::BINDING, texture.value()->handle.texture); + descriptorSet->bindSampler(pipeline::JOINTTEXTURE::BINDING, sampler); + } +} + +void BakedSkinningModel::updateInstancedAttributes(const ccstd::vector &attributes, scene::SubModel *subModel) { + Super::updateInstancedAttributes(attributes, subModel); + _instAnimInfoIdx = subModel->getInstancedAttributeIndex(INST_JOINT_ANIM_INFO); + updateInstancedJointTextureInfo(); +} + +void BakedSkinningModel::updateInstancedJointTextureInfo() { + const auto &jointTextureInfo = _jointMedium.jointTextureInfo; + const IAnimInfo &animInfo = _jointMedium.animInfo; + const index_t idx = _instAnimInfoIdx; + for (const auto &subModel : _subModels) { + auto &views = subModel->getInstancedAttributeBlock().views; + if (idx >= 0 && !views.empty()) { + auto &view = views[idx]; + setTypedArrayValue(view, 0, *animInfo.curFrame); //NOTE: curFrame is only used in JSB. + setTypedArrayValue(view, 1, jointTextureInfo[1]); + setTypedArrayValue(view, 2, jointTextureInfo[2]); + } + } +} + +void BakedSkinningModel::syncAnimInfoForJS(gfx::Buffer *buffer, const Float32Array &data, Uint8Array &dirty) { + _jointMedium.animInfo.buffer = buffer; + _jointMedium.animInfo.curFrame = &data[0]; + _jointMedium.animInfo.frameDataBytes = data.byteLength(); + _jointMedium.animInfo.dirtyForJSB = &dirty[0]; +} + +void BakedSkinningModel::syncDataForJS(const ccstd::vector> &boundsInfo, + const ccstd::optional &modelBound, + float jointTextureInfo0, + float jointTextureInfo1, + float jointTextureInfo2, + float jointTextureInfo3, + gfx::Texture *tex, + const Float32Array &animInfoData) { + _jointMedium.boundsInfo = boundsInfo; + + if (modelBound.has_value()) { + const geometry::AABB &modelBounldValue = modelBound.value(); + _modelBounds->set(modelBounldValue.center, modelBounldValue.halfExtents); + } else { + _modelBounds = nullptr; + } + + _jointMedium.jointTextureInfo[0] = jointTextureInfo0; + _jointMedium.jointTextureInfo[1] = jointTextureInfo1; + _jointMedium.jointTextureInfo[2] = jointTextureInfo2; + _jointMedium.jointTextureInfo[3] = jointTextureInfo3; + + _jointMedium.animInfo.curFrame = &animInfoData[0]; + _jointMedium.animInfo.frameDataBytes = animInfoData.byteLength(); + + if (_jointMedium.texture.has_value()) { + delete _jointMedium.texture.value(); + _jointMedium.texture = ccstd::nullopt; + } + IJointTextureHandle *textureInfo = IJointTextureHandle::createJoinTextureHandle(); + textureInfo->handle.texture = tex; + _jointMedium.texture = textureInfo; + + updateInstancedJointTextureInfo(); + + auto *buffer = _jointMedium.buffer.get(); + if (buffer != nullptr) { + buffer->update(&_jointMedium.jointTextureInfo[0], _jointMedium.jointTextureInfo.byteLength()); + } + + for (const auto &subModel : _subModels) { + auto *descriptorSet = subModel->getDescriptorSet(); + descriptorSet->bindTexture(pipeline::JOINTTEXTURE::BINDING, tex); + } +} + +} // namespace cc diff --git a/cocos/3d/models/BakedSkinningModel.h b/cocos/3d/models/BakedSkinningModel.h new file mode 100644 index 0000000..3226fbe --- /dev/null +++ b/cocos/3d/models/BakedSkinningModel.h @@ -0,0 +1,101 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +#include "3d/assets/Skeleton.h" +#include "3d/models/MorphModel.h" +#include "3d/skeletal-animation/SkeletalAnimationUtils.h" +#include "gfx-base/GFXDef-common.h" + +namespace cc { + +namespace gfx { +class Texture; +} + +class DataPoolManager; + +struct BakedJointInfo { + IntrusivePtr buffer; + Float32Array jointTextureInfo; + ccstd::optional texture; + IAnimInfo animInfo; + ccstd::vector> boundsInfo; +}; + +class BakedSkinningModel final : public MorphModel { +public: + using Super = MorphModel; + BakedSkinningModel(); + ~BakedSkinningModel() override = default; + void destroy() override; + ccstd::vector getMacroPatches(index_t subModelIndex) override; + void updateLocalDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) override; + void updateTransform(uint32_t stamp) override; + void updateUBOs(uint32_t stamp) override; + void updateInstancedAttributes(const ccstd::vector &attributes, scene::SubModel *subModel) override; + void updateInstancedJointTextureInfo(); + // void uploadAnimation(AnimationClip *anim); // TODO(xwx): AnimationClip not define + + void bindSkeleton(Skeleton *skeleton, Node *skinningRoot, Mesh *mesh); + + inline void updateModelBounds(geometry::AABB *modelBounds) { + if (modelBounds == nullptr) { + return; + } + _modelBounds->setValid(true); + _modelBounds->set(modelBounds->getCenter(), modelBounds->getHalfExtents()); + } + + void syncAnimInfoForJS(gfx::Buffer *buffer, const Float32Array &data, Uint8Array &dirty); + void syncDataForJS(const ccstd::vector> &boundsInfo, + const ccstd::optional &modelBound, + float jointTextureInfo0, + float jointTextureInfo1, + float jointTextureInfo2, + float jointTextureInfo3, + gfx::Texture *tex, + const Float32Array &animInfoData); + + void setUploadedAnimForJS(bool value) { _isUploadedAnim = value; } + +protected: + void applyJointTexture(const ccstd::optional &texture); + +private: + BakedJointInfo _jointMedium; + index_t _instAnimInfoIdx{CC_INVALID_INDEX}; + // IntrusivePtr _dataPoolManager; + IntrusivePtr _skeleton; + IntrusivePtr _mesh; + // AnimationClip* uploadedAnim; + bool _isUploadedAnim{false}; + + CC_DISALLOW_COPY_MOVE_ASSIGN(BakedSkinningModel); +}; + +} // namespace cc diff --git a/cocos/3d/models/MorphModel.cpp b/cocos/3d/models/MorphModel.cpp new file mode 100644 index 0000000..943ceab --- /dev/null +++ b/cocos/3d/models/MorphModel.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "3d/models/MorphModel.h" + +namespace cc { + +ccstd::vector MorphModel::getMacroPatches(index_t subModelIndex) { + ccstd::vector superMacroPatches = Super::getMacroPatches(subModelIndex); + if (_morphRenderingInstance) { + ccstd::vector morphInstanceMacroPatches = _morphRenderingInstance->requiredPatches(subModelIndex); + if (!morphInstanceMacroPatches.empty()) { + if (!superMacroPatches.empty()) { + morphInstanceMacroPatches.reserve(morphInstanceMacroPatches.size() + superMacroPatches.size()); + morphInstanceMacroPatches.insert(morphInstanceMacroPatches.end(), superMacroPatches.begin(), superMacroPatches.end()); + } + return morphInstanceMacroPatches; + } + } + return superMacroPatches; +} + +void MorphModel::initSubModel(index_t idx, RenderingSubMesh *subMeshData, Material *mat) { + Super::initSubModel(idx, subMeshData, launderMaterial(mat)); +} + +void MorphModel::destroy() { + Super::destroy(); + _morphRenderingInstance = nullptr; //minggo: should delete it? +} + +void MorphModel::setSubModelMaterial(index_t idx, Material *mat) { + Super::setSubModelMaterial(idx, launderMaterial(mat)); +} + +void MorphModel::updateLocalDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) { + Super::updateLocalDescriptors(subModelIndex, descriptorSet); + + if (_morphRenderingInstance) { + _morphRenderingInstance->adaptPipelineState(subModelIndex, descriptorSet); + } +} + +} // namespace cc diff --git a/cocos/3d/models/MorphModel.h b/cocos/3d/models/MorphModel.h new file mode 100644 index 0000000..1ba013d --- /dev/null +++ b/cocos/3d/models/MorphModel.h @@ -0,0 +1,57 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include "3d/assets/MorphRendering.h" +#include "scene/Define.h" +#include "scene/Model.h" + +namespace cc { + +class MorphModel : public scene::Model { +public: + using Super = scene::Model; + + MorphModel() = default; + ~MorphModel() override = default; + CC_DISALLOW_COPY_MOVE_ASSIGN(MorphModel); + + ccstd::vector getMacroPatches(index_t subModelIndex) override; + void initSubModel(index_t idx, RenderingSubMesh *subMeshData, Material *mat) override; + void destroy() override; + void setSubModelMaterial(index_t idx, Material *mat) override; + + inline void setMorphRendering(MorphRenderingInstance *morphRendering) { _morphRenderingInstance = morphRendering; } + +protected: + void updateLocalDescriptors(index_t subModelIndex, gfx::DescriptorSet *descriptorSet) override; + +private: + inline Material *launderMaterial(Material *material) { return material; } //NOLINT(readability-convert-member-functions-to-static) + + IntrusivePtr _morphRenderingInstance; +}; + +} // namespace cc diff --git a/cocos/3d/models/SkinningModel.cpp b/cocos/3d/models/SkinningModel.cpp new file mode 100644 index 0000000..28d1e5b --- /dev/null +++ b/cocos/3d/models/SkinningModel.cpp @@ -0,0 +1,359 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "3d/models/SkinningModel.h" + +#include + +#include "3d/assets/Mesh.h" +#include "3d/assets/Skeleton.h" +#include "core/platform/Debug.h" +#include "core/scene-graph/Node.h" +#include "renderer/gfx-base/GFXBuffer.h" +#include "scene/Pass.h" +#include "scene/RenderScene.h" + +const uint32_t REALTIME_JOINT_TEXTURE_WIDTH = 256; +const uint32_t REALTIME_JOINT_TEXTURE_HEIGHT = 3; + +namespace { +void getRelevantBuffers(ccstd::vector &outIndices, ccstd::vector &outBuffers, const ccstd::vector> &jointMaps, int32_t targetJoint) { + for (int32_t i = 0; i < jointMaps.size(); i++) { + index_t index = CC_INVALID_INDEX; + for (int32_t j = 0; j < jointMaps[i].size(); j++) { + if (jointMaps[i][j] == targetJoint) { + index = j; + break; + } + } + if (index >= 0) { + outBuffers.emplace_back(i); + outIndices.emplace_back(index); + } + } +} + +ccstd::vector uniformPatches{{"CC_USE_SKINNING", true}, {"CC_USE_REAL_TIME_JOINT_TEXTURE", false}}; +ccstd::vector texturePatches{{"CC_USE_SKINNING", true}, {"CC_USE_REAL_TIME_JOINT_TEXTURE", true}}; + +} // namespace +namespace cc { + +SkinningModel::SkinningModel() { + _type = Model::Type::SKINNING; +} +SkinningModel::~SkinningModel() { + releaseData(); +} + +void SkinningModel::destroy() { + bindSkeleton(nullptr, nullptr, nullptr); + releaseData(); + Super::destroy(); +} + +void SkinningModel::bindSkeleton(Skeleton *skeleton, Node *skinningRoot, Mesh *mesh) { + for (const JointInfo &joint : _joints) { + deleteTransform(joint.target); + } + _bufferIndices.clear(); + _joints.clear(); + + if (!skeleton || !skinningRoot || !mesh) return; + auto jointCount = static_cast(skeleton->getJoints().size()); + _realTimeTextureMode = pipeline::SkinningJointCapacity::jointUniformCapacity < jointCount; + setTransform(skinningRoot); + auto boneSpaceBounds = mesh->getBoneSpaceBounds(skeleton); + const auto &jointMaps = mesh->getStruct().jointMaps; + ensureEnoughBuffers((jointMaps.has_value() && !jointMaps->empty()) ? static_cast(jointMaps->size()) : 1); + _bufferIndices = mesh->getJointBufferIndices(); + initRealTimeJointTexture(); + for (index_t index = 0; index < skeleton->getJoints().size(); ++index) { + geometry::AABB *bound = boneSpaceBounds[index]; + auto *target = skinningRoot->getChildByPath(skeleton->getJoints()[index]); + if (!bound || !target) continue; + + auto *transform = cc::getTransform(target, skinningRoot); + const Mat4 &bindPose = skeleton->getBindposes()[index]; + ccstd::vector indices; + ccstd::vector buffers; + if (!jointMaps.has_value()) { + indices.emplace_back(index); + buffers.emplace_back(0); + } else { + getRelevantBuffers(indices, buffers, jointMaps.value(), index); + } + + JointInfo jointInfo; + jointInfo.bound = bound; + jointInfo.target = target; + jointInfo.bindpose = bindPose; + jointInfo.transform = transform; + jointInfo.buffers = std::move(buffers); + jointInfo.indices = std::move(indices); + _joints.emplace_back(std::move(jointInfo)); + } +} + +void SkinningModel::updateTransform(uint32_t stamp) { + auto *root = getTransform(); + if (root->getChangedFlags() || root->isTransformDirty()) { + root->updateWorldTransform(); + _localDataUpdated = true; + } + Vec3 v3Min{INFINITY, INFINITY, INFINITY}; + Vec3 v3Max{-INFINITY, -INFINITY, -INFINITY}; + geometry::AABB ab1; + Vec3 v31; + Vec3 v32; + for (JointInfo &jointInfo : _joints) { + auto &transform = jointInfo.transform; + Mat4 worldMatrix = cc::getWorldMatrix(transform, static_cast(stamp)); + jointInfo.bound->transform(worldMatrix, &ab1); + ab1.getBoundary(&v31, &v32); + Vec3::min(v3Min, v31, &v3Min); + Vec3::max(v3Max, v32, &v3Max); + } + if (_modelBounds && _modelBounds->isValid() && _worldBounds) { + geometry::AABB::fromPoints(v3Min, v3Max, _modelBounds); + _modelBounds->transform(root->getWorldMatrix(), _worldBounds); + _worldBoundsDirty = true; + } +} + +void SkinningModel::updateUBOs(uint32_t stamp) { + Super::updateUBOs(stamp); + uint32_t bIdx = 0; + Mat4 mat4; + for (const JointInfo &jointInfo : _joints) { + Mat4::multiply(jointInfo.transform->world, jointInfo.bindpose, &mat4); + for (uint32_t buffer : jointInfo.buffers) { + uploadJointData(jointInfo.indices[bIdx] * 12, mat4, _dataArray[buffer]); + bIdx++; + } + bIdx = 0; + } + if (_realTimeTextureMode) { + updateRealTimeJointTextureBuffer(); + } else { + bIdx = 0; + for (gfx::Buffer *buffer : _buffers) { + buffer->update(_dataArray[bIdx], buffer->getSize()); + bIdx++; + } + } +} + +void SkinningModel::initSubModel(index_t idx, RenderingSubMesh *subMeshData, Material *mat) { + const auto &original = subMeshData->getVertexBuffers(); + auto &iaInfo = subMeshData->getIaInfo(); + iaInfo.vertexBuffers = subMeshData->getJointMappedBuffers(); + Super::initSubModel(idx, subMeshData, mat); + iaInfo.vertexBuffers = original; +} + +ccstd::vector SkinningModel::getMacroPatches(index_t subModelIndex) { + auto patches = Super::getMacroPatches(subModelIndex); + auto myPatches = uniformPatches; + if (_realTimeTextureMode) { + myPatches = texturePatches; + } + if (!patches.empty()) { + patches.reserve(myPatches.size() + patches.size()); + patches.insert(std::begin(patches), std::begin(myPatches), std::end(myPatches)); + return patches; + } + + return myPatches; +} + +void SkinningModel::uploadJointData(uint32_t base, const Mat4 &mat, float *dst) { + memcpy(reinterpret_cast(dst + base), mat.m, sizeof(float) * 12); + dst[base + 3] = mat.m[12]; + dst[base + 7] = mat.m[13]; + dst[base + 11] = mat.m[14]; +} + +void SkinningModel::updateLocalDescriptors(index_t submodelIdx, gfx::DescriptorSet *descriptorset) { + Super::updateLocalDescriptors(submodelIdx, descriptorset); + uint32_t idx = _bufferIndices[submodelIdx]; + if (!_realTimeTextureMode) { + gfx::Buffer *buffer = _buffers[idx]; + if (buffer) { + descriptorset->bindBuffer(pipeline::UBOSkinning::BINDING, buffer); + } + } else { + bindRealTimeJointTexture(idx, descriptorset); + } +} + +void SkinningModel::updateInstancedAttributes(const ccstd::vector &attributes, scene::SubModel *subModel) { + auto *pass = subModel->getPass(0); + if (pass->getBatchingScheme() != scene::BatchingSchemes::NONE) { + // TODO(holycanvas): #9203 better to print the complete path instead of only the current node + debug::warnID(3936, getNode()->getName()); + CC_LOG_WARNING("pass batchingScheme is none, %s", getNode()->getName().c_str()); + } + Super::updateInstancedAttributes(attributes, subModel); +} + +void SkinningModel::ensureEnoughBuffers(uint32_t count) { + if (!_buffers.empty()) { + for (gfx::Buffer *buffer : _buffers) { + CC_SAFE_DESTROY(buffer); + } + _buffers.clear(); + } + if (!_dataArray.empty()) { + for (auto *data : _dataArray) { + CC_SAFE_DELETE_ARRAY(data); + } + _dataArray.clear(); + } + _dataArray.resize(count); + if (!_realTimeTextureMode) { + _buffers.resize(count); + uint32_t length = pipeline::UBOSkinning::count; + for (uint32_t i = 0; i < count; i++) { + _buffers[i] = _device->createBuffer({ + gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::HOST | gfx::MemoryUsageBit::DEVICE, + pipeline::UBOSkinning::size, + pipeline::UBOSkinning::size, + }); + _dataArray[i] = new float[length]; + memset(_dataArray[i], 0, sizeof(float) * length); + } + } else { + uint32_t length = 4 * REALTIME_JOINT_TEXTURE_WIDTH * REALTIME_JOINT_TEXTURE_HEIGHT; + for (uint32_t i = 0; i < count; i++) { + if (_dataArray[i] == nullptr) { + _dataArray[i] = new float[length]; + memset(_dataArray[i], 0, sizeof(float) * length); + } + } + } +} + +void SkinningModel::initRealTimeJointTexture() { + CC_SAFE_DELETE(_realTimeJointTexture); + if (!_realTimeTextureMode) return; + _realTimeJointTexture = ccnew RealTimeJointTexture; + auto *device = gfx::Device::getInstance(); + uint32_t texWidth = REALTIME_JOINT_TEXTURE_WIDTH; + uint32_t texHeight = REALTIME_JOINT_TEXTURE_HEIGHT; + gfx::Format textureFormat = gfx::Format::RGBA32F; + + gfx::FormatFeature formatFeature = device->getFormatFeatures(gfx::Format::RGBA32F); + if (!(formatFeature & gfx::FormatFeature::SAMPLED_TEXTURE)) { + textureFormat = gfx::Format::RGBA8; + texWidth = texWidth * 4; + } + uint32_t length = 4 * REALTIME_JOINT_TEXTURE_WIDTH * REALTIME_JOINT_TEXTURE_HEIGHT; + const size_t count = _dataArray.size(); + for (size_t i = 0; i < count; i++) { + gfx::TextureInfo textureInfo; + textureInfo.width = texWidth; + textureInfo.height = texHeight; + textureInfo.usage = gfx::TextureUsageBit::STORAGE | gfx::TextureUsageBit::SAMPLED | gfx::TextureUsageBit::TRANSFER_SRC | gfx::TextureUsageBit::TRANSFER_DST; + textureInfo.format = textureFormat; + IntrusivePtr texture = device->createTexture(textureInfo); + _realTimeJointTexture->textures.push_back(texture); + } + _realTimeJointTexture->buffer = new float[length]; +} + +void SkinningModel::bindRealTimeJointTexture(uint32_t idx, gfx::DescriptorSet *descriptorset) { + if (_realTimeJointTexture->textures.size() < idx + 1) return; + gfx::Texture *texture = _realTimeJointTexture->textures[idx]; + if (texture) { + gfx::SamplerInfo info{ + gfx::Filter::POINT, + gfx::Filter::POINT, + gfx::Filter::NONE, + gfx::Address::CLAMP, + gfx::Address::CLAMP, + gfx::Address::CLAMP, + }; + auto *device = gfx::Device::getInstance(); + auto *sampler = device->getSampler(info); + descriptorset->bindTexture(pipeline::REALTIMEJOINTTEXTURE::BINDING, texture); + descriptorset->bindSampler(pipeline::REALTIMEJOINTTEXTURE::BINDING, sampler); + } +} + +void SkinningModel::updateRealTimeJointTextureBuffer() { + uint32_t bIdx = 0; + uint32_t width = REALTIME_JOINT_TEXTURE_WIDTH; + uint32_t height = REALTIME_JOINT_TEXTURE_HEIGHT; + for (const auto &texture : _realTimeJointTexture->textures) { + auto *buffer = _realTimeJointTexture->buffer; + auto *dst = buffer; + auto *src = _dataArray[bIdx]; + uint32_t count = width; + for (uint32_t i = 0; i < count; i++) { + dst = buffer + (4 * i); + memcpy(dst, src, 16); + src = src + 4; + dst = buffer + (4 * (i + width)); + memcpy(dst, src, 16); + src = src + 4; + dst = buffer + 4 * (i + 2 * width); + memcpy(dst, src, 16); + src = src + 4; + } + uint32_t buffOffset = 0; + gfx::TextureSubresLayers layer; + gfx::Offset texOffset; + gfx::Extent extent{width, height, 1}; + gfx::BufferTextureCopy region{ + buffOffset, + width, + height, + texOffset, + extent, + layer}; + auto *device = gfx::Device::getInstance(); + + device->copyBuffersToTexture(reinterpret_cast(&buffer), texture, ®ion, 1); + bIdx++; + } +} + +void SkinningModel::releaseData() { + if (!_dataArray.empty()) { + for (auto *data : _dataArray) { + CC_SAFE_DELETE_ARRAY(data); + } + _dataArray.clear(); + } + CC_SAFE_DELETE(_realTimeJointTexture); + if (!_buffers.empty()) { + for (gfx::Buffer *buffer : _buffers) { + CC_SAFE_DESTROY(buffer); + } + _buffers.clear(); + } +} + +} // namespace cc diff --git a/cocos/3d/models/SkinningModel.h b/cocos/3d/models/SkinningModel.h new file mode 100644 index 0000000..486f392 --- /dev/null +++ b/cocos/3d/models/SkinningModel.h @@ -0,0 +1,85 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "3d/models/MorphModel.h" +#include "base/std/container/array.h" +#include "core/animation/SkeletalAnimationUtils.h" +#include "math/Mat4.h" +#include "renderer/gfx-base/GFXDef-common.h" +#include "renderer/pipeline/Define.h" + +namespace cc { +class Skeleton; +namespace geometry { +class AABB; +} + +struct JointInfo { + geometry::AABB *bound{nullptr}; + Node *target{nullptr}; + Mat4 bindpose; + IntrusivePtr transform; + ccstd::vector buffers; + ccstd::vector indices; +}; + +class SkinningModel final : public MorphModel { +public: + using Super = MorphModel; + SkinningModel(); + ~SkinningModel() override; + + void updateLocalDescriptors(index_t submodelIdx, gfx::DescriptorSet *descriptorset) override; + void updateTransform(uint32_t stamp) override; + void updateUBOs(uint32_t stamp) override; + void destroy() override; + + void initSubModel(index_t idx, RenderingSubMesh *subMeshData, Material *mat) override; + ccstd::vector getMacroPatches(index_t subModelIndex) override; + void updateInstancedAttributes(const ccstd::vector &attributes, scene::SubModel *subModel) override; + + void bindSkeleton(Skeleton *skeleton, Node *skinningRoot, Mesh *mesh); + +private: + static void uploadJointData(uint32_t base, const Mat4 &mat, float *dst); + void ensureEnoughBuffers(uint32_t count); + void updateRealTimeJointTextureBuffer(); + void initRealTimeJointTexture(); + void bindRealTimeJointTexture(uint32_t idx, gfx::DescriptorSet *descriptorset); + void releaseData(); + + ccstd::vector _bufferIndices; + ccstd::vector> _buffers; + ccstd::vector _joints; + ccstd::vector _dataArray; + bool _realTimeTextureMode = false; + RealTimeJointTexture *_realTimeJointTexture = nullptr; + + CC_DISALLOW_COPY_MOVE_ASSIGN(SkinningModel); +}; + +} // namespace cc diff --git a/cocos/3d/skeletal-animation/SkeletalAnimationUtils.cpp b/cocos/3d/skeletal-animation/SkeletalAnimationUtils.cpp new file mode 100644 index 0000000..474b985 --- /dev/null +++ b/cocos/3d/skeletal-animation/SkeletalAnimationUtils.cpp @@ -0,0 +1,513 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "3d/skeletal-animation/SkeletalAnimationUtils.h" +#include "3d/assets/Mesh.h" +#include "core/scene-graph/Node.h" +#include "renderer/pipeline/Define.h" + +namespace { +const float INF = std::numeric_limits::infinity(); + +cc::gfx::Format selectJointsMediumFormat(cc::gfx::Device *device) { + if (static_cast(device->getFormatFeatures(cc::gfx::Format::RGBA32F) & cc::gfx::FormatFeature::SAMPLED_TEXTURE)) { + return cc::gfx::Format::RGBA32F; + } + return cc::gfx::Format::RGBA8; +} + +// Linear Blending Skinning +void uploadJointDataLBS(cc::Float32Array out, uint32_t base, const cc::Mat4 &mat, bool /*firstBone*/) { + out[base + 0] = mat.m[0]; + out[base + 1] = mat.m[1]; + out[base + 2] = mat.m[2]; + out[base + 3] = mat.m[12]; + out[base + 4] = mat.m[4]; + out[base + 5] = mat.m[5]; + out[base + 6] = mat.m[6]; + out[base + 7] = mat.m[13]; + out[base + 8] = mat.m[8]; + out[base + 9] = mat.m[9]; + out[base + 10] = mat.m[10]; + out[base + 11] = mat.m[14]; +} + +cc::Quaternion dq0; +cc::Quaternion dq1; +cc::Vec3 v31; +cc::Quaternion qt1; +cc::Vec3 v32; + +float dot(const cc::Quaternion &a, const cc::Quaternion &b) { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; +} + +void multiplyScalar(const cc::Quaternion &a, float b, cc::Quaternion *out) { + out->x = a.x * b; + out->y = a.y * b; + out->z = a.z * b; + out->w = a.w * b; +} + +// Dual Quaternion Skinning +void uploadJointDataDQS(cc::Float32Array out, uint32_t base, cc::Mat4 &mat, bool firstBone) { + cc::Mat4::toRTS(mat, &qt1, &v31, &v32); + // // sign consistency + if (firstBone) { + dq0 = qt1; + } else if (dot(dq0, qt1) < 0) { + multiplyScalar(qt1, -1, &qt1); + } + // conversion + dq1.x = v31.x; + dq1.y = v31.y; + dq1.z = v31.z; + dq1.w = 0; + multiplyScalar(dq1 * qt1, 0.5, &dq1); + // upload + out[base + 0] = qt1.x; + out[base + 1] = qt1.y; + out[base + 2] = qt1.z; + out[base + 3] = qt1.w; + out[base + 4] = dq1.x; + out[base + 5] = dq1.y; + out[base + 6] = dq1.z; + out[base + 7] = dq1.w; + out[base + 8] = v32.x; + out[base + 9] = v32.y; + out[base + 10] = v32.z; +} + +// change here and cc-skinning.chunk to use other skinning algorithms +constexpr auto UPLOAD_JOINT_DATA = uploadJointDataLBS; +#if CC_EDITOR +const uint32_t MINIMUM_JOINT_TEXTURE_SIZE = 2040; +#else +const uint32_t MINIMUM_JOINT_TEXTURE_SIZE = 480; // have to be multiples of 12 +#endif + +uint32_t roundUpTextureSize(uint32_t targetLength, uint32_t formatSize) { + double formatScale = 4 / std::sqrt(formatSize); + return static_cast(std::ceil(std::max(MINIMUM_JOINT_TEXTURE_SIZE * formatScale, static_cast(targetLength)) / 12) * 12); +} + +const cc::gfx::SamplerInfo JOINT_TEXTURE_SAMPLER_INFO{ + cc::gfx::Filter::POINT, + cc::gfx::Filter::POINT, + cc::gfx::Filter::NONE, + cc::gfx::Address::CLAMP, + cc::gfx::Address::CLAMP, + cc::gfx::Address::CLAMP, +}; + +cc::Mat4 *getWorldTransformUntilRoot(cc::Node *target, cc::Node *root, cc::Mat4 *outMatrix) { + outMatrix->setIdentity(); + cc::Mat4 mat4; + while (target != root) { + cc::Mat4::fromRTS(target->getRotation(), target->getPosition(), target->getScale(), &mat4); + cc::Mat4::multiply(*outMatrix, mat4, outMatrix); + target = target->getParent(); + } + return outMatrix; +} + +} // namespace +namespace cc { +JointTexturePool::JointTexturePool(gfx::Device *device) { + _device = device; + const auto &format = selectJointsMediumFormat(_device); + _formatSize = gfx::GFX_FORMAT_INFOS[static_cast(format)].size; + _pixelsPerJoint = 48 / _formatSize; + _pool = ccnew TextureBufferPool(device); + ITextureBufferPoolInfo poolInfo; + poolInfo.format = format; + poolInfo.roundUpFn = roundUpType{roundUpTextureSize}; + _pool->initialize(poolInfo); + _customPool = ccnew TextureBufferPool(device); + ITextureBufferPoolInfo customPoolInfo; + customPoolInfo.format = format; + customPoolInfo.roundUpFn = roundUpType{roundUpTextureSize}; + _customPool->initialize(customPoolInfo); +} + +void JointTexturePool::clear() { + CC_SAFE_DESTROY(_pool); + _textureBuffers.clear(); +} + +void JointTexturePool::registerCustomTextureLayouts(const ccstd::vector &layouts) { + for (const auto &layout : layouts) { + auto textureLength = layout.textureLength; + if (!(static_cast(_device->getFormatFeatures(cc::gfx::Format::RGBA32F) & cc::gfx::FormatFeature::SAMPLED_TEXTURE))) { + textureLength *= 2; + } + uint32_t chunkIdx = _customPool->createChunk(textureLength); + for (const auto &content : layout.contents) { + auto skeleton = content.skeleton; + _chunkIdxMap[skeleton] = chunkIdx; // include default pose too + for (const auto &clip : content.clips) { + _chunkIdxMap[skeleton ^ clip] = chunkIdx; + } + } + } +} + +ccstd::optional JointTexturePool::getDefaultPoseTexture(Skeleton *skeleton, Mesh *mesh, Node *skinningRoot) { + ccstd::hash_t hash = skeleton->getHash() ^ 0; // may not equal to skeleton.hash + ccstd::optional texture; + if (_textureBuffers.find(hash) != _textureBuffers.end()) { + texture = _textureBuffers[hash]; + } + + const ccstd::vector &joints = skeleton->getJoints(); + const ccstd::vector &bindPoses = skeleton->getBindposes(); + Float32Array textureBuffer; + bool buildTexture = false; + auto jointCount = static_cast(joints.size()); + if (!texture.has_value()) { + uint32_t bufSize = jointCount * 12; + ITextureBufferHandle handle; + if (_chunkIdxMap.find(hash) != _chunkIdxMap.end()) { + handle = _customPool->alloc(bufSize * Float32Array::BYTES_PER_ELEMENT, _chunkIdxMap[hash]); + } else { + handle = _pool->alloc(bufSize * Float32Array::BYTES_PER_ELEMENT); + return texture; + } + IJointTextureHandle *textureHandle = IJointTextureHandle::createJoinTextureHandle(); + textureHandle->pixelOffset = handle.start / _formatSize; + textureHandle->refCount = 1; + textureHandle->clipHash = 0; + textureHandle->skeletonHash = skeleton->getHash(); + textureHandle->readyToBeDeleted = false; + textureHandle->handle = handle; + texture = textureHandle; + textureBuffer = Float32Array(bufSize); + buildTexture = true; + } else { + texture.value()->refCount++; + } + + geometry::AABB ab1; + Mat4 mat4; + Vec3 v34; + Vec3 v33; + Vec3 v3Min(-INF, -INF, -INF); + Vec3 v3Max(-INF, -INF, -INF); + auto boneSpaceBounds = mesh->getBoneSpaceBounds(skeleton); + for (uint32_t j = 0, offset = 0; j < jointCount; ++j, offset += 12) { + auto *node = skinningRoot->getChildByPath(joints[j]); + Mat4 mat = node ? *getWorldTransformUntilRoot(node, skinningRoot, &mat4) : skeleton->getInverseBindposes()[j]; + if (j < boneSpaceBounds.size()) { + auto *bound = boneSpaceBounds[j].get(); + bound->transform(mat, &ab1); + ab1.getBoundary(&v33, &v34); + Vec3::min(v3Min, v33, &v3Min); + Vec3::max(v3Max, v34, &v3Max); + } + + if (buildTexture) { + if (node != nullptr) { + Mat4::multiply(mat, bindPoses[j], &mat); + } + uploadJointDataLBS(textureBuffer, offset, node ? mat : Mat4::IDENTITY, j == 0); + } + } + + ccstd::vector bounds; + texture.value()->bounds[static_cast(mesh->getHash())] = bounds; + geometry::AABB::fromPoints(v3Min, v3Max, &bounds[0]); + if (buildTexture) { + _pool->update(texture.value()->handle, textureBuffer.buffer()); + _textureBuffers[hash] = texture.value(); + } + + return texture; +} + +// TODO(xwx): need to implement this function after define AnimationClip +// ccstd::optional JointTexturePool::getSequencePoseTexture(Skeleton *skeleton,AnimationClip *clip, Mesh *mesh, Node *skinningRoot) { +// uint64_t hash = skeleton->getHash() ^ clip->getHash(); +// ccstd::optional texture; +// if (_textureBuffers.find(hash) != _textureBuffers.end()) { +// texture = _textureBuffers[hash]; +// if (texture->bounds.find(mesh->getHash()) != texture->bounds.end()) { +// texture->refCount++; +// return texture; +// } +// } +// const ccstd::vector &joints = skeleton->getJoints(); +// const ccstd::vector & bindPoses = skeleton->getBindposes(); +// // const clipData = SkelAnimDataHub.getOrExtract(clip); +// // const { frames } = clipData; +// Float32Array textureBuffer; +// bool buildTexture = false; +// uint32_t jointCount = joints.size(); +// if (!texture.has_value()) { +// uint32_t bufSize = jointCount * 12; +// ITextureBufferHandle handle; +// if (_chunkIdxMap.find(hash) != _chunkIdxMap.end()) { +// handle = _customPool->alloc(bufSize * sizeof(Float32Array), _chunkIdxMap[hash]); // TODO(xwx): Float32Array.BYTES_PER_ELEMENT == sizeof(Float32Array) ? +// } else { +// handle = _pool->alloc(bufSize * sizeof(Float32Array)); +// return texture; +// } +// // auto animInfos = createAnimInfos(skeleton, clip, skinningRoot); // TODO(xwx): createAnimInfos not implement + +// texture = IJointTextureHandle{ +// .pixelOffset = handle.start / _formatSize, +// .refCount = 1, +// .clipHash = 0, +// .skeletonHash = skeleton->getHash(), +// .readyToBeDeleted = false, +// .handle = handle, +// // .animInfos = animInfos // TODO(xwx) +// }; +// textureBuffer.resize(bufSize); +// buildTexture = true; +// } else { +// texture->refCount++; +// } +// auto boneSpaceBounds = mesh->getBoneSpaceBounds(skeleton); +// ccstd::vector bounds; +// texture->bounds[mesh->getHash()] = bounds; + +// // for (uint32_t f = 0; f < frames; ++f) { // TODO(xwx): frames not define +// // bounds.emplace_back(geometry::AABB(INF, INF, INF, -INF, -INF, -INF)); +// // } + +// // TODO(xwx) : need to implement when define animInfos +// // for (uint32_t f = 0, offset = 0; f < frames; ++f) { +// // auto bound = bounds[f]; +// // for (uint32_t j = 0; j < jointCount; ++j, offset += 12) { +// // const { +// // curveData, +// // downstream, +// // bindposeIdx, +// // bindposeCorrection, +// // } = texture.animInfos ![j]; +// // let mat : Mat4; +// // let transformValid = true; +// // if (curveData && downstream) { // curve & static two-way combination +// // mat = Mat4.multiply(m4_1, curveData[f], downstream); +// // } else if (curveData) { // there is a curve directly controlling the joint +// // mat = curveData[f]; +// // } else if (downstream) { // fallback to default pose if no animation curve can be found upstream +// // mat = downstream; +// // } else { // bottom line: render the original mesh as-is +// // mat = skeleton.inverseBindposes[bindposeIdx]; +// // transformValid = false; +// // } +// // if (j < boneSpaceBounds.size()) { +// // auto bound = boneSpaceBounds[j]; +// // auto tarnsform = bindposeCorrection ? Mat4::multiply(mat, bindposeCorrection, &m42) : mat; // TODO(xwx): mat not define +// // ab1.getBoundary(&v33, &v34); +// // Vec3::min(bound.center, v33, &bound.center); +// // Vec3::max(bound.halfExtents, v34, &bound.halfExtents); +// // } + +// // if (buildTexture) { +// // if (transformValid) { +// // Mat4::multiply(mat, bindPoses[bindposIdx], &m41); +// // UPLOAD_JOINT_DATA(textureBuffer, offset, transformValid ? m41 : Mat4::IDENTITY, j == 0); +// // } +// // } +// // } +// // AABB::fromPoints(bound.center, bound.halfExtents, &bound); +// // } +// if (buildTexture) { +// // _pool->update(texture->handle, textureBuffer.buffer); // TODO(xwx): ArrayBuffer not implemented +// _textureBuffers[hash] = texture.value(); +// } +// return texture; +// } +// } + +void JointTexturePool::releaseHandle(IJointTextureHandle *handle) { + if (handle->refCount > 0) { + handle->refCount--; + } + if (!handle->refCount && handle->readyToBeDeleted) { + ccstd::hash_t hash = handle->skeletonHash ^ handle->clipHash; + if (_chunkIdxMap.find(hash) != _chunkIdxMap.end()) { + _customPool->free(handle->handle); + } else { + _pool->free(handle->handle); + } + if (_textureBuffers[hash] == handle) { + _textureBuffers.erase(hash); + CC_SAFE_DELETE(handle); + } + } +} + +void JointTexturePool::releaseSkeleton(Skeleton *skeleton) { + for (const auto &texture : _textureBuffers) { + auto *handle = texture.second; + if (handle->skeletonHash == skeleton->getHash()) { + handle->readyToBeDeleted = true; + if (handle->refCount > 0) { + // delete handle record immediately so new allocations with the same asset could work + _textureBuffers.erase(handle->skeletonHash ^ handle->clipHash); + } else { + releaseHandle(handle); + } + } + } +} + +// TODO(xwx): AnimationClip not define +// public releaseAnimationClip(clip: AnimationClip) { +// const it = this._textureBuffers.values(); +// let res = it.next(); +// while (!res.done) { +// const handle = res.value; +// if (handle.clipHash == = clip.hash) { +// handle.readyToBeDeleted = true; +// if (handle.refCount) { +// // delete handle record immediately so new allocations with the same asset could work +// this._textureBuffers.delete(handle.skeletonHash ^ handle.clipHash); +// } else { +// this.releaseHandle(handle); +// } +// } +// res = it.next(); +// } +// } + +// TODO(xwx): AnimationClip not define +// private _createAnimInfos (skeleton: Skeleton, clip: AnimationClip, skinningRoot: Node) { +// const animInfos: IInternalJointAnimInfo[] = []; +// const { joints, bindposes } = skeleton; +// const jointCount = joints.length; +// const clipData = SkelAnimDataHub.getOrExtract(clip); +// for (let j = 0; j < jointCount; j++) { +// let animPath = joints[j]; +// let source = clipData.joints[animPath]; +// let animNode = skinningRoot.getChildByPath(animPath); +// let downstream: Mat4 | undefined; +// let correctionPath: string | undefined; +// while (!source) { +// const idx = animPath.lastIndexOf('/'); +// animPath = animPath.substring(0, idx); +// source = clipData.joints[animPath]; +// if (animNode) { +// if (!downstream) { downstream = ccnew Mat4(); } +// Mat4.fromRTS(m4_1, animNode.rotation, animNode.position, animNode.scale); +// Mat4.multiply(downstream, m4_1, downstream); +// animNode = animNode.parent; +// } else { // record the nearest curve path if no downstream pose is present +// correctionPath = animPath; +// } +// if (idx < 0) { break; } +// } +// // the default behavior, just use the bindpose for current joint directly +// let bindposeIdx = j; +// let bindposeCorrection: Mat4 | undefined; +// /** +// * It is regularly observed that developers may choose to delete the whole +// * skeleton node tree for skinning models that only use baked animations +// * as an effective optimization strategy (substantial improvements on both +// * package size and runtime efficiency). +// * +// * This becomes troublesome in some cases during baking though, e.g. when a +// * skeleton joint node is not directly controlled by any animation curve, +// * but its parent nodes are. Due to lack of proper downstream default pose, +// * the joint transform can not be calculated accurately. +// * +// * We address this issue by employing some pragmatic approximation. +// * Specifically, by multiplying the bindpose of the joint corresponding to +// * the nearest curve, instead of the actual target joint. This effectively +// * merges the skinning influence of the 'incomplete' joint into its nearest +// * parent with accurate transform data. +// * It gives more visually-plausible results compared to the naive approach +// * for most cases we've covered. +// */ +// if (correctionPath !== undefined && source) { +// // just use the previous joint if the exact path is not found +// bindposeIdx = j - 1; +// for (let t = 0; t < jointCount; t++) { +// if (joints[t] === correctionPath) { +// bindposeIdx = t; +// bindposeCorrection = ccnew Mat4(); +// Mat4.multiply(bindposeCorrection, bindposes[t], skeleton.inverseBindposes[j]); +// break; +// } +// } +// } +// animInfos.push({ +// curveData: source && source.transforms, downstream, bindposeIdx, bindposeCorrection, +// }); +// } +// return animInfos; +// } + +JointAnimationInfo::JointAnimationInfo(gfx::Device *device) +: _device(device) { +} + +IAnimInfo JointAnimationInfo::getData(const ccstd::string &nodeID) { + if (_pool.find(nodeID) != _pool.end()) { + return _pool[nodeID]; + } + + auto *buffer = _device->createBuffer(gfx::BufferInfo{ + gfx::BufferUsageBit::UNIFORM | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::HOST | gfx::MemoryUsageBit::DEVICE, + pipeline::UBOSkinningAnimation::SIZE, + pipeline::UBOSkinningAnimation::SIZE, + }); + + Float32Array data; + buffer->update(data.buffer()->getData()); + IAnimInfo info; + info.buffer = buffer; + info.data = data; + info.dirty = false; + _pool[nodeID] = info; + + return info; +} + +void JointAnimationInfo::destroy(const ccstd::string &nodeID) { + if (_pool.find(nodeID) != _pool.end()) { + CC_SAFE_DESTROY_AND_DELETE(_pool[nodeID].buffer); + _pool.erase(nodeID); + } +} + +const IAnimInfo &JointAnimationInfo::switchClip(IAnimInfo &info /*, AnimationClip *clip */) { + // info.currentClip = clip; + info.data[0] = -1; + info.buffer->update(info.data.buffer()->getData()); + info.dirty = false; + return info; +} + +void JointAnimationInfo::clear() { + for (auto pool : _pool) { + CC_SAFE_DESTROY_AND_DELETE(pool.second.buffer); + } + _pool.clear(); +} +} // namespace cc diff --git a/cocos/3d/skeletal-animation/SkeletalAnimationUtils.h b/cocos/3d/skeletal-animation/SkeletalAnimationUtils.h new file mode 100644 index 0000000..ff78a94 --- /dev/null +++ b/cocos/3d/skeletal-animation/SkeletalAnimationUtils.h @@ -0,0 +1,154 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "base/std/container/unordered_map.h" +#include "base/std/optional.h" + +#include "3d/assets/Skeleton.h" +#include "core/TypedArray.h" +#include "core/geometry/AABB.h" +#include "gfx-base/GFXDef-common.h" +#include "renderer/core/TextureBufferPool.h" +#include "renderer/gfx-base/GFXBuffer.h" +#include "renderer/gfx-base/GFXDevice.h" + +namespace cc { + +class Node; +class Mesh; + +// _chunkIdxMap[key] = skeleton ^ clips[i] +struct IChunkContent { + uint32_t skeleton{0U}; + ccstd::vector clips; +}; + +struct ICustomJointTextureLayout { + uint32_t textureLength{0}; + ccstd::vector contents; +}; + +struct IInternalJointAnimInfo { + ccstd::optional downstream; // downstream default pose, if present + ccstd::optional> curveData; // the nearest animation curve, if present + index_t bindposeIdx{0}; // index of the actual bindpose to use + ccstd::optional bindposeCorrection; // correction factor from the original bindpose +}; + +class IJointTextureHandle { +public: + uint32_t pixelOffset{0}; + uint32_t refCount{0}; + ccstd::hash_t clipHash{0U}; + ccstd::hash_t skeletonHash{0U}; + bool readyToBeDeleted{false}; + ITextureBufferHandle handle; + ccstd::unordered_map> bounds; + ccstd::optional> animInfos; + + static IJointTextureHandle *createJoinTextureHandle() { + return ccnew IJointTextureHandle(); + } + +private: + IJointTextureHandle() = default; +}; + +class JointTexturePool : public RefCounted { +public: + JointTexturePool() = default; + explicit JointTexturePool(gfx::Device *device); + ~JointTexturePool() override = default; + + inline uint32_t getPixelsPerJoint() const { return _pixelsPerJoint; } + + void clear(); + + void registerCustomTextureLayouts(const ccstd::vector &layouts); + + /** + * @en + * Get joint texture for the default pose. + * @zh + * 获取默认姿势的骨骼贴图。 + */ + ccstd::optional getDefaultPoseTexture(Skeleton *skeleton, Mesh *mesh, Node *skinningRoot); + + /** + * @en + * Get joint texture for the specified animation clip. + * @zh + * 获取指定动画片段的骨骼贴图。 + */ + // ccstd::optional getSequencePoseTexture(Skeleton *skeleton, AnimationClip *clip, Mesh *mesh, Node *skinningRoot); + + void releaseHandle(IJointTextureHandle *handle); + + void releaseSkeleton(Skeleton *skeleton); + + // void releaseAnimationClip (AnimationClip* clip); // TODO(xwx): AnimationClip not define + +private: + // const IInternalJointAnimInfo &createAnimInfos(Skeleton *skeleton, AnimationClip *clip, Node *skinningRoot); // TODO(xwx): AnimationClip not define + + gfx::Device *_device{nullptr}; + IntrusivePtr _pool; + ccstd::unordered_map _textureBuffers; + uint32_t _formatSize{0}; + uint32_t _pixelsPerJoint{0}; + IntrusivePtr _customPool; + ccstd::unordered_map _chunkIdxMap; // hash -> chunkIdx + + CC_DISALLOW_COPY_MOVE_ASSIGN(JointTexturePool); +}; + +struct IAnimInfo { + gfx::Buffer *buffer{nullptr}; + Float32Array data; + const float *curFrame{nullptr}; // Only used in JSB + uint32_t frameDataBytes{0}; // Only used in JSB + uint8_t *dirtyForJSB{nullptr}; // Only used in JSB + bool dirty{false}; +}; + +class JointAnimationInfo : public RefCounted { +public: + JointAnimationInfo() = default; + explicit JointAnimationInfo(gfx::Device *device); + ~JointAnimationInfo() override = default; + + IAnimInfo getData(const ccstd::string &nodeID = "-1"); + void destroy(const ccstd::string &nodeID); + static const IAnimInfo &switchClip(IAnimInfo &info /*, AnimationClip *clip */); + void clear(); + +private: + ccstd::unordered_map _pool; // pre node + gfx::Device *_device{nullptr}; + + CC_DISALLOW_COPY_MOVE_ASSIGN(JointAnimationInfo); +}; + +} // namespace cc diff --git a/cocos/application/ApplicationManager.cpp b/cocos/application/ApplicationManager.cpp new file mode 100644 index 0000000..7d17ae3 --- /dev/null +++ b/cocos/application/ApplicationManager.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "application/ApplicationManager.h" +#include "base/Macros.h" + +namespace cc { +// static +ApplicationManager *ApplicationManager::getInstance() { + static ApplicationManager mgr; + return &mgr; +} + +void ApplicationManager::releseAllApplications() { + _apps.clear(); +} + +ApplicationManager::ApplicationPtr ApplicationManager::getCurrentApp() const { + if (_currentApp.expired()) { + return nullptr; + } + return _currentApp.lock(); +} + +ApplicationManager::ApplicationPtr ApplicationManager::getCurrentAppSafe() const { + CC_ASSERT(!_currentApp.expired()); + return _currentApp.lock(); +} +} // namespace cc + +// +void cocos_destory() { // NOLINT(readability-identifier-naming) + // Called in the platform layer, because the platform layer is isolated from the application layer + // It is the platform layer to drive applications and reclaim resources. + cc::ApplicationManager::getInstance()->releseAllApplications(); +} \ No newline at end of file diff --git a/cocos/application/ApplicationManager.h b/cocos/application/ApplicationManager.h new file mode 100644 index 0000000..b9a5307 --- /dev/null +++ b/cocos/application/ApplicationManager.h @@ -0,0 +1,96 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "application/BaseApplication.h" +#include "base/std/container/vector.h" + +namespace cc { +class CC_DLL ApplicationManager { +public: + static ApplicationManager* getInstance(); + + using ApplicationPtr = std::shared_ptr; + + /** + * @brief Generate application entry. + */ + template + std::enable_if_t::value, ApplicationPtr> + createApplication(int argc, const char* argv[]) { + ApplicationPtr app = std::make_shared(); + app->setArgumentsInternal(argc, argv); + _apps.push_back(app); + _currentApp = app; + return app; + } + + /** + * @brief Release all generated applications. + */ + void releseAllApplications(); + /** + * @brief Get the current application, may get empty. + */ + ApplicationPtr getCurrentApp() const; + /** + * @brief Get the current application, make sure it is not empty. + * Used to get the engine. + */ + ApplicationPtr getCurrentAppSafe() const; + +private: + std::weak_ptr _currentApp; + ccstd::vector _apps; +}; +} // namespace cc + +#define CC_APPLICATION_MANAGER() cc::ApplicationManager::getInstance() +#define CC_CURRENT_APPLICATION() CC_APPLICATION_MANAGER()->getCurrentApp() +#define CC_CURRENT_APPLICATION_SAFE() CC_APPLICATION_MANAGER()->getCurrentAppSafe() +#define CC_CURRENT_ENGINE() CC_CURRENT_APPLICATION_SAFE()->getEngine() +#define CC_GET_PLATFORM_INTERFACE(intf) CC_CURRENT_ENGINE()->getInterface() +#define CC_GET_SYSTEM_WINDOW(id) CC_GET_PLATFORM_INTERFACE(cc::ISystemWindowManager)->getWindow(id) +#define CC_GET_MAIN_SYSTEM_WINDOW() CC_GET_SYSTEM_WINDOW(cc::ISystemWindow::mainWindowId) // Assuming the 1st created window is the main system window for now! + +#define CC_GET_XR_INTERFACE() BasePlatform::getPlatform()->getInterface() + +/** + * @brief Called at the user-defined main entry + */ +#define CC_START_APPLICATION(className) \ + do { \ + auto app = CC_APPLICATION_MANAGER()->createApplication(argc, argv); \ + if (app->init()) { \ + return -1; \ + } \ + return app->run(argc, argv); \ + } while (0) + +#define CC_REGISTER_APPLICATION(className) \ + int cocos_main(int argc, const char** argv) { \ + CC_START_APPLICATION(className); \ + } diff --git a/cocos/application/BaseApplication.h b/cocos/application/BaseApplication.h new file mode 100644 index 0000000..e29583b --- /dev/null +++ b/cocos/application/BaseApplication.h @@ -0,0 +1,80 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "engine/BaseEngine.h" + +namespace cc { + +class CC_DLL BaseApplication { +public: + virtual ~BaseApplication() = default; + /** + * @brief Application initialization + */ + virtual int32_t init() = 0; + /** + * @brief Application main business logic. + */ + virtual int32_t run(int argc, + const char **argv) = 0; + /** + * @brief Pause the application. + */ + virtual void pause() = 0; + /** + * @brief Resume the application. + */ + virtual void resume() = 0; + /** + * @brief Restart the application. + */ + virtual void restart() = 0; + /** + * @brief Close the application. + */ + virtual void close() = 0; + /** + * @brief Get engine. + */ + virtual BaseEngine::Ptr getEngine() const = 0; + + /** + * @brief Get arguments passed to execution file + */ + virtual const std::vector &getArguments() const = 0; + +protected: + /** + * @brief Set arguments passed to execution file + * @note setArgumentsInternal needs to be protected since it should only be used internally. + */ + virtual void setArgumentsInternal(int argc, const char *argv[]) = 0; + + friend class ApplicationManager; +}; + +} // namespace cc diff --git a/cocos/application/BaseGame.cpp b/cocos/application/BaseGame.cpp new file mode 100644 index 0000000..ecad44b --- /dev/null +++ b/cocos/application/BaseGame.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "BaseGame.h" +#include +#include "ApplicationManager.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" +#include "renderer/pipeline/GlobalDescriptorSetManager.h" + +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include "platform/android/adpf_manager.h" +#endif +extern "C" void cc_load_all_plugins(); // NOLINT + +namespace cc { +int BaseGame::init() { + cc::pipeline::GlobalDSManager::setDescriptorSetLayout(); + + cc_load_all_plugins(); + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) && CC_SUPPORT_ADPF + ADPFManager::getInstance().Initialize(); +#endif + +#if CC_PLATFORM == CC_PLATFORM_WINDOWS || CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX || CC_PLATFORM == CC_PLATFORM_MACOS + // override default value + //_windowInfo.x = _windowInfo.x == -1 ? 0 : _windowInfo.x; + //_windowInfo.y = _windowInfo.y == -1 ? 0 : _windowInfo.y; + _windowInfo.width = _windowInfo.width == -1 ? 800 : _windowInfo.width; + _windowInfo.height = _windowInfo.height == -1 ? 600 : _windowInfo.height; + _windowInfo.flags = _windowInfo.flags == -1 ? cc::ISystemWindow::CC_WINDOW_SHOWN | + cc::ISystemWindow::CC_WINDOW_RESIZABLE | + cc::ISystemWindow::CC_WINDOW_INPUT_FOCUS + : _windowInfo.flags; + std::call_once(_windowCreateFlag, [&]() { + ISystemWindowInfo info; + info.title = _windowInfo.title; + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + info.x = _windowInfo.x == -1 ? 50 : _windowInfo.x; // 50 meams move window a little for now + info.y = _windowInfo.y == -1 ? 50 : _windowInfo.y; // same above + #else + info.x = _windowInfo.x == -1 ? 0 : _windowInfo.x; + info.y = _windowInfo.y == -1 ? 0 : _windowInfo.y; + #endif + info.width = _windowInfo.width; + info.height = _windowInfo.height; + info.flags = _windowInfo.flags; + + ISystemWindowManager* windowMgr = CC_GET_PLATFORM_INTERFACE(ISystemWindowManager); + windowMgr->createWindow(info); + }); + +#endif + + if (_debuggerInfo.enabled) { + setDebugIpAndPort(_debuggerInfo.address, _debuggerInfo.port, _debuggerInfo.pauseOnStart); + } + + int ret = cc::CocosApplication::init(); + if (ret != 0) { + return ret; + } + + setXXTeaKey(_xxteaKey); + runScript("jsb-adapter/web-adapter.js"); + runScript("main.js"); + return 0; +} +} // namespace cc diff --git a/cocos/application/BaseGame.h b/cocos/application/BaseGame.h new file mode 100644 index 0000000..308f293 --- /dev/null +++ b/cocos/application/BaseGame.h @@ -0,0 +1,55 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once +#include +#include "CocosApplication.h" + +namespace cc { +class BaseGame : public CocosApplication { +public: + struct DebuggerInfo { + bool enabled{true}; + int32_t port{6086}; + std::string address{"0.0.0.0"}; + bool pauseOnStart{false}; + }; + struct WindowInfo { + std::string title; + int32_t x{-1}; + int32_t y{-1}; + int32_t width{-1}; + int32_t height{-1}; + int32_t flags{-1}; + }; + + BaseGame() = default; + int init() override; + +protected: + std::string _xxteaKey; + DebuggerInfo _debuggerInfo; + WindowInfo _windowInfo; + std::once_flag _windowCreateFlag; +}; +} // namespace cc diff --git a/cocos/application/CocosApplication.cpp b/cocos/application/CocosApplication.cpp new file mode 100644 index 0000000..057c8d1 --- /dev/null +++ b/cocos/application/CocosApplication.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "application/CocosApplication.h" + +#include "base/Macros.h" + +#include "ApplicationManager.h" +#include "cocos/bindings/event/EventDispatcher.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_classtype.h" +#include "cocos/bindings/manual/jsb_global.h" +#include "cocos/bindings/manual/jsb_module_register.h" +#include "cocos/engine/BaseEngine.h" +#include "cocos/platform/interfaces/modules/IScreen.h" +#include "cocos/platform/interfaces/modules/ISystemWindowManager.h" + +namespace cc { + +CocosApplication::CocosApplication() { + _engine = BaseEngine::createEngine(); +} + +CocosApplication::~CocosApplication() { + unregisterAllEngineEvents(); +} + +void CocosApplication::unregisterAllEngineEvents() { + if (_engine != nullptr) { + _engine->off(_engineEvents); + } +} + +int CocosApplication::init() { + if (_engine->init()) { + return -1; + } + unregisterAllEngineEvents(); + + _systemWindow = CC_GET_MAIN_SYSTEM_WINDOW(); + + _engineEvents = _engine->on([this](BaseEngine * /*emitter*/, BaseEngine::EngineStatus status) { + switch (status) { + case BaseEngine::ON_START: + this->onStart(); + break; + case BaseEngine::ON_RESUME: + this->onResume(); + break; + case BaseEngine::ON_PAUSE: + this->onPause(); + break; + case BaseEngine::ON_CLOSE: + this->onClose(); + break; + default: + CC_ABORT(); + } + }); + + se::ScriptEngine *se = se::ScriptEngine::getInstance(); + + jsb_init_file_operation_delegate(); + + se->setExceptionCallback( + std::bind(&CocosApplication::handleException, this, // NOLINT(modernize-avoid-bind) + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + + jsb_register_all_modules(); +#if CC_EDITOR + auto isolate = v8::Isolate::GetCurrent(); + se->start(isolate); +#else + se->start(); +#endif + +#if (CC_PLATFORM == CC_PLATFORM_IOS) + auto logicSize = _systemWindow->getViewSize(); + IScreen *screen = _engine->getInterface(); + float pixelRatio = screen->getDevicePixelRatio(); + uint32_t windowId = _systemWindow->getWindowId(); + events::Resize::broadcast(logicSize.width * pixelRatio, logicSize.height * pixelRatio, windowId); +#endif + return 0; +} + +int32_t CocosApplication::run(int argc, const char **argv) { + CC_UNUSED_PARAM(argc); + CC_UNUSED_PARAM(argv); + return _engine->run(); +} + +void CocosApplication::pause() { + _engine->pause(); +} + +void CocosApplication::resume() { + _engine->resume(); +} + +void CocosApplication::restart() { + _engine->restart(); +} +// IMPORTANT!!The method `onClose` is a function to be listen close event, while `close` is a jsb binding method mean to close the whole application. +void CocosApplication::close() { + _systemWindow->closeWindow(); +} + +BaseEngine::Ptr CocosApplication::getEngine() const { + return _engine; +} + +const std::vector &CocosApplication::getArguments() const { + return _argv; +} + +void CocosApplication::setArgumentsInternal(int argc, const char *argv[]) { + _argv.clear(); + _argv.reserve(argc); + for (int i = 0; i < argc; ++i) { + _argv.emplace_back(argv[i]); + } +} + +void CocosApplication::onStart() { + // TODO(cc): Handling engine start events +} + +void CocosApplication::onPause() { + // TODO(cc): Handling pause events +} + +void CocosApplication::onResume() { + // TODO(cc): Handling resume events +} + +void CocosApplication::onClose() { + _engine->close(); +} + +void CocosApplication::setDebugIpAndPort(const ccstd::string &serverAddr, uint32_t port, bool isWaitForConnect) { + // Enable debugger here + jsb_enable_debugger(serverAddr, port, isWaitForConnect); +} + +void CocosApplication::runScript(const ccstd::string &filePath) { + jsb_run_script(filePath); +} + +void CocosApplication::handleException(const char *location, const char *message, const char *stack) { + // Send exception information to server like Tencent Bugly. + CC_LOG_ERROR("\nUncaught Exception:\n - location : %s\n - msg : %s\n - detail : \n %s\n", location, message, stack); +} + +void CocosApplication::setXXTeaKey(const ccstd::string &key) { + jsb_set_xxtea_key(key); +} +#if CC_PLATFORM == CC_PLATFORM_WINDOWS || CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX || CC_PLATFORM == CC_PLATFORM_MACOS +void CocosApplication::createWindow(const char *title, int32_t w, + int32_t h, int32_t flags) { + _systemWindow->createWindow(title, w, h, flags); +} + +void CocosApplication::createWindow(const char *title, + int32_t x, int32_t y, int32_t w, + int32_t h, int32_t flags) { + _systemWindow->createWindow(title, x, y, w, h, flags); +} +#endif + +} // namespace cc diff --git a/cocos/application/CocosApplication.h b/cocos/application/CocosApplication.h new file mode 100644 index 0000000..c46c4d8 --- /dev/null +++ b/cocos/application/CocosApplication.h @@ -0,0 +1,151 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "application/BaseApplication.h" +#include "cocos/platform/interfaces/modules/ISystemWindow.h" + +namespace cc { +class BaseEngine; + +class CC_DLL CocosApplication : public BaseApplication { +public: + CocosApplication(); + ~CocosApplication() override; + + /** + * @brief Application initialization. + */ + int32_t init() override; + /** + * @brief Application main business logic. + */ + int32_t run(int argc, const char **argv) override; + /** + * @brief Pause the application. + */ + void pause() override; + /** + * @brief Resume the application. + */ + void resume() override; + /** + * @brief Restart the application. + */ + void restart() override; + /** + * @brief Close the application. + */ + void close() override; + /** + * @brief Get engine. + */ + BaseEngine::Ptr getEngine() const override; + + /** + * @brief Get arguments passed to execution file + */ + const std::vector &getArguments() const override; + +protected: + /** + * @brief Set arguments passed to execution file + * @note setArgumentsInternal needs to be protected since it should only be used internally. + */ + void setArgumentsInternal(int argc, const char *argv[]) override; + +public: + /** + * @brief Processing engine start events. + */ + virtual void onStart(); + /** + * @brief Processing pause events.. + */ + virtual void onPause(); + /** + * @brief Processing recovery events. + */ + virtual void onResume(); + /** + * @brief Processing close events. + */ + virtual void onClose(); +#if CC_PLATFORM == CC_PLATFORM_WINDOWS || CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX || CC_PLATFORM == CC_PLATFORM_MACOS + /** + * @brief Create window. + * @param title: Window title + * @param x: x-axis coordinate + * @param y: y-axis coordinate + * @param w: Window width + * @param h: Window height + * @param flags: Window flag + */ + virtual void createWindow(const char *title, + int32_t x, int32_t y, int32_t w, + int32_t h, int32_t flags); + /** + * @brief Create a centered window. + * @param title: Window title + * @param w: Window width + * @param h: Window height + * @param flags: Window flag + */ + virtual void createWindow(const char *title, int32_t w, + int32_t h, int32_t flags); +#endif + /** + * @brief Set the debugging server Addr and port + * @param serverAddr:Server address. + * @param port:Server port. + * @param isWaitForConnect:Is Wait for connect. + */ + virtual void setDebugIpAndPort(const ccstd::string &serverAddr, uint32_t port, bool isWaitForConnect); + /** + * @brief Run the script file + * @param filePath:script path. + */ + virtual void runScript(const ccstd::string &filePath); + /** + * @brief Script exception handling + * @param location,Exception location + * @param message,Exception message + * @param stack,Exception stack + */ + virtual void handleException(const char *location, const char *message, const char *stack); + virtual void setXXTeaKey(const ccstd::string &key); + +private: + void unregisterAllEngineEvents(); + + ISystemWindow *_systemWindow{nullptr}; + BaseEngine::Ptr _engine{nullptr}; + + BaseEngine::EngineStatusChange::EventID _engineEvents; + + std::vector _argv; +}; +} // namespace cc diff --git a/cocos/audio/AudioEngine.cpp b/cocos/audio/AudioEngine.cpp new file mode 100644 index 0000000..45ec4f3 --- /dev/null +++ b/cocos/audio/AudioEngine.cpp @@ -0,0 +1,598 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "audio/include/AudioEngine.h" +#include +#include +#include +#include +#include "base/Log.h" +#include "base/Utils.h" +#include "base/memory/Memory.h" +#include "base/std/container/queue.h" +#include "platform/FileUtils.h" + +#if CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OPENHARMONY + // OpenHarmony and Android use the same audio playback module + #include "audio/android/AudioEngine-inl.h" +#elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + #include "audio/apple/AudioEngine-inl.h" +#elif CC_PLATFORM == CC_PLATFORM_WINDOWS || CC_PLATFORM == CC_PLATFORM_OHOS + #include "audio/oalsoft/AudioEngine-soft.h" +#elif CC_PLATFORM == CC_PLATFORM_WINRT + #include "audio/winrt/AudioEngine-winrt.h" +#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX + #include "audio/oalsoft/AudioEngine-soft.h" +#elif CC_PLATFORM == CC_PLATFORM_TIZEN + #include "audio/tizen/AudioEngine-tizen.h" +#endif + +#define TIME_DELAY_PRECISION 0.0001 + +#ifdef ERROR + #undef ERROR +#endif // ERROR + +namespace cc { + +const int AudioEngine::INVALID_AUDIO_ID = -1; +const float AudioEngine::TIME_UNKNOWN = -1.0F; + +//audio file path,audio IDs +ccstd::unordered_map> AudioEngine::sAudioPathIDMap; +//profileName,ProfileHelper +ccstd::unordered_map AudioEngine::sAudioPathProfileHelperMap; +unsigned int AudioEngine::sMaxInstances = MAX_AUDIOINSTANCES; +AudioEngine::ProfileHelper *AudioEngine::sDefaultProfileHelper = nullptr; +ccstd::unordered_map AudioEngine::sAudioIDInfoMap; +AudioEngineImpl *AudioEngine::sAudioEngineImpl = nullptr; + +float AudioEngine::sVolumeFactor = 1.0F; +events::EnterBackground::Listener AudioEngine::sOnPauseListenerID; +events::EnterForeground::Listener AudioEngine::sOnResumeListenerID; + +ccstd::vector AudioEngine::sBreakAudioID; + +AudioEngine::AudioEngineThreadPool *AudioEngine::sThreadPool = nullptr; +bool AudioEngine::sIsEnabled = true; + +AudioEngine::AudioInfo::AudioInfo() +: filePath(nullptr), + profileHelper(nullptr), + volume(1.0F), + loop(false), + duration(TIME_UNKNOWN), + state(AudioState::INITIALIZING) { +} + +class AudioEngine::AudioEngineThreadPool { +public: + explicit AudioEngineThreadPool(int threads = 4) { + for (int index = 0; index < threads; ++index) { + _workers.emplace_back(std::thread([this]() { + threadFunc(); + })); + } + } + + void addTask(const std::function &task) { + std::unique_lock lk(_queueMutex); + _taskQueue.emplace(task); + _taskCondition.notify_one(); + } + + ~AudioEngineThreadPool() { + { + std::unique_lock lk(_queueMutex); + _stop = true; + _taskCondition.notify_all(); + } + + for (auto &&worker : _workers) { + worker.join(); + } + } + +private: + void threadFunc() { + while (true) { + std::function task = nullptr; + { + std::unique_lock lk(_queueMutex); + if (_stop) { + break; + } + if (!_taskQueue.empty()) { + task = std::move(_taskQueue.front()); + _taskQueue.pop(); + } else { + _taskCondition.wait(lk); + continue; + } + } + + task(); + } + } + + ccstd::vector _workers; + ccstd::queue> _taskQueue; + + std::mutex _queueMutex; + std::condition_variable _taskCondition; + bool _stop{}; +}; + +void AudioEngine::end() { + stopAll(); + + if (sThreadPool) { + delete sThreadPool; + sThreadPool = nullptr; + } + + delete sAudioEngineImpl; + sAudioEngineImpl = nullptr; + + delete sDefaultProfileHelper; + sDefaultProfileHelper = nullptr; + + sOnPauseListenerID.reset(); + sOnResumeListenerID.reset(); +} + +bool AudioEngine::lazyInit() { + if (sAudioEngineImpl == nullptr) { + sAudioEngineImpl = ccnew AudioEngineImpl(); + if (!sAudioEngineImpl || !sAudioEngineImpl->init()) { + delete sAudioEngineImpl; + sAudioEngineImpl = nullptr; + return false; + } + sOnPauseListenerID.bind(&onEnterBackground); + sOnResumeListenerID.bind(&onEnterForeground); + } + +#if (CC_PLATFORM != CC_PLATFORM_ANDROID) + if (sAudioEngineImpl && sThreadPool == nullptr) { + sThreadPool = ccnew AudioEngineThreadPool(); + } +#endif + + return true; +} + +int AudioEngine::play2d(const ccstd::string &filePath, bool loop, float volume, const AudioProfile *profile) { + int ret = AudioEngine::INVALID_AUDIO_ID; + + do { + if (!isEnabled()) { + break; + } + + if (!lazyInit()) { + break; + } + + if (!FileUtils::getInstance()->isFileExist(filePath)) { + break; + } + + auto *profileHelper = sDefaultProfileHelper; + if (profile && profile != &profileHelper->profile) { + CC_ASSERT(!profile->name.empty()); + profileHelper = &sAudioPathProfileHelperMap[profile->name]; + profileHelper->profile = *profile; + } + + if (sAudioIDInfoMap.size() >= sMaxInstances) { + CC_LOG_INFO("Fail to play %s cause by limited max instance of AudioEngine", filePath.c_str()); + break; + } + if (profileHelper) { + if (profileHelper->profile.maxInstances != 0 && profileHelper->audioIDs.size() >= profileHelper->profile.maxInstances) { + CC_LOG_INFO("Fail to play %s cause by limited max instance of AudioProfile", filePath.c_str()); + break; + } + if (profileHelper->profile.minDelay > TIME_DELAY_PRECISION) { + auto currTime = std::chrono::high_resolution_clock::now(); + auto delay = static_cast(std::chrono::duration_cast(currTime - profileHelper->lastPlayTime).count()) / 1000000.0; + if (profileHelper->lastPlayTime.time_since_epoch().count() != 0 && delay <= profileHelper->profile.minDelay) { + CC_LOG_INFO("Fail to play %s cause by limited minimum delay", filePath.c_str()); + break; + } + } + } + + if (volume < 0.0F) { + volume = 0.0F; + } else if (volume > 1.0F) { + volume = 1.0F; + } + + ret = sAudioEngineImpl->play2d(filePath, loop, volume); + if (ret != INVALID_AUDIO_ID) { + sAudioPathIDMap[filePath].push_back(ret); + auto it = sAudioPathIDMap.find(filePath); + + auto &audioRef = sAudioIDInfoMap[ret]; + audioRef.volume = volume; + audioRef.loop = loop; + audioRef.filePath = &it->first; + audioRef.state = AudioState::PLAYING; + + if (profileHelper) { + profileHelper->lastPlayTime = std::chrono::high_resolution_clock::now(); + profileHelper->audioIDs.push_back(ret); + } + audioRef.profileHelper = profileHelper; + } + } while (false); + + return ret; +} + +void AudioEngine::setLoop(int audioID, bool loop) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end() && it->second.loop != loop) { + sAudioEngineImpl->setLoop(audioID, loop); + it->second.loop = loop; + } +} + +void AudioEngine::setVolume(int audioID, float volume) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end()) { + if (volume < 0.0F) { + volume = 0.0F; + } else if (volume > 1.0F) { + volume = 1.0F; + } + + if (it->second.volume != volume) { + sAudioEngineImpl->setVolume(audioID, volume * sVolumeFactor); + it->second.volume = volume; + } + } +} + +void AudioEngine::setVolumeFactor(float factor) { + if (factor > 1.0F) { + factor = 1.0F; + } + if (factor < 0) { + factor = 0.0F; + } + sVolumeFactor = factor; + for (auto &item : sAudioIDInfoMap) { + sAudioEngineImpl->setVolume(item.first, item.second.volume * sVolumeFactor); + } +} + +void AudioEngine::pause(int audioID) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end() && it->second.state == AudioState::PLAYING) { + sAudioEngineImpl->pause(audioID); + it->second.state = AudioState::PAUSED; + } +} + +void AudioEngine::pauseAll() { + auto itEnd = sAudioIDInfoMap.end(); + for (auto it = sAudioIDInfoMap.begin(); it != itEnd; ++it) { + if (it->second.state == AudioState::PLAYING) { + sAudioEngineImpl->pause(it->first); + it->second.state = AudioState::PAUSED; + } + } +} + +void AudioEngine::resume(int audioID) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end() && it->second.state == AudioState::PAUSED) { + sAudioEngineImpl->resume(audioID); + it->second.state = AudioState::PLAYING; + } +} + +void AudioEngine::resumeAll() { + auto itEnd = sAudioIDInfoMap.end(); + for (auto it = sAudioIDInfoMap.begin(); it != itEnd; ++it) { + if (it->second.state == AudioState::PAUSED) { + sAudioEngineImpl->resume(it->first); + it->second.state = AudioState::PLAYING; + } + } +} + +void AudioEngine::onEnterBackground() { + auto itEnd = sAudioIDInfoMap.end(); + for (auto it = sAudioIDInfoMap.begin(); it != itEnd; ++it) { + if (it->second.state == AudioState::PLAYING) { + sAudioEngineImpl->pause(it->first); + it->second.state = AudioState::PAUSED; + sBreakAudioID.push_back(it->first); + } + } + +#if CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OPENHARMONY + if (sAudioEngineImpl) { + sAudioEngineImpl->onPause(); + } +#endif +} + +void AudioEngine::onEnterForeground() { + auto itEnd = sBreakAudioID.end(); + for (auto it = sBreakAudioID.begin(); it != itEnd; ++it) { + auto iter = sAudioIDInfoMap.find(*it); + if (iter != sAudioIDInfoMap.end() && iter->second.state == AudioState::PAUSED) { + sAudioEngineImpl->resume(*it); + iter->second.state = AudioState::PLAYING; + } + } + sBreakAudioID.clear(); + +#if CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OPENHARMONY + if (sAudioEngineImpl) { + sAudioEngineImpl->onResume(); + } +#endif +} + +void AudioEngine::stop(int audioID) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end()) { + sAudioEngineImpl->stop(audioID); + + remove(audioID); + } +} + +void AudioEngine::remove(int audioID) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end()) { + if (it->second.profileHelper) { + it->second.profileHelper->audioIDs.remove(audioID); + } + sAudioPathIDMap[*it->second.filePath].remove(audioID); + sAudioIDInfoMap.erase(audioID); + } +} + +void AudioEngine::stopAll() { + if (!sAudioEngineImpl) { + return; + } + sAudioEngineImpl->stopAll(); + auto itEnd = sAudioIDInfoMap.end(); + for (auto it = sAudioIDInfoMap.begin(); it != itEnd; ++it) { + if (it->second.profileHelper) { + it->second.profileHelper->audioIDs.remove(it->first); + } + } + sAudioPathIDMap.clear(); + sAudioIDInfoMap.clear(); +} + +void AudioEngine::uncache(const ccstd::string &filePath) { + auto audioIDsIter = sAudioPathIDMap.find(filePath); + if (audioIDsIter != sAudioPathIDMap.end()) { + //@Note: For safely iterating elements from the audioID list, we need to copy the list + // since 'AudioEngine::remove' may be invoked in 'sAudioEngineImpl->stop' synchronously. + // If this happens, it will break the iteration, and crash will appear on some devices. + ccstd::list copiedIDs(audioIDsIter->second); + + for (int audioID : copiedIDs) { + sAudioEngineImpl->stop(audioID); + + auto itInfo = sAudioIDInfoMap.find(audioID); + if (itInfo != sAudioIDInfoMap.end()) { + if (itInfo->second.profileHelper) { + itInfo->second.profileHelper->audioIDs.remove(audioID); + } + sAudioIDInfoMap.erase(audioID); + } + } + sAudioPathIDMap.erase(filePath); + } + + if (sAudioEngineImpl) { + sAudioEngineImpl->uncache(filePath); + } +} + +void AudioEngine::uncacheAll() { + if (!sAudioEngineImpl) { + return; + } + stopAll(); + sAudioEngineImpl->uncacheAll(); +} + +float AudioEngine::getDuration(int audioID) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING) { + if (it->second.duration == TIME_UNKNOWN) { + it->second.duration = sAudioEngineImpl->getDuration(audioID); + } + return it->second.duration; + } + + return TIME_UNKNOWN; +} + +float AudioEngine::getDurationFromFile(const ccstd::string &filePath) { + lazyInit(); + + if (sAudioEngineImpl) { + return sAudioEngineImpl->getDurationFromFile(filePath); + } + + return TIME_UNKNOWN; +} + +bool AudioEngine::setCurrentTime(int audioID, float time) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING) { + return sAudioEngineImpl->setCurrentTime(audioID, time); + } + + return false; +} + +float AudioEngine::getCurrentTime(int audioID) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end() && it->second.state != AudioState::INITIALIZING) { + return sAudioEngineImpl->getCurrentTime(audioID); + } + return 0.0F; +} + +void AudioEngine::setFinishCallback(int audioID, const std::function &callback) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end()) { + sAudioEngineImpl->setFinishCallback(audioID, callback); + } +} + +bool AudioEngine::setMaxAudioInstance(int maxInstances) { + if (maxInstances > 0 && maxInstances <= MAX_AUDIOINSTANCES) { + sMaxInstances = maxInstances; + return true; + } + + return false; +} + +bool AudioEngine::isLoop(int audioID) { + auto tmpIterator = sAudioIDInfoMap.find(audioID); + if (tmpIterator != sAudioIDInfoMap.end()) { + return tmpIterator->second.loop; + } + + CC_LOG_INFO("AudioEngine::isLoop-->The audio instance %d is non-existent", audioID); + return false; +} + +float AudioEngine::getVolume(int audioID) { + auto tmpIterator = sAudioIDInfoMap.find(audioID); + if (tmpIterator != sAudioIDInfoMap.end()) { + return tmpIterator->second.volume; + } + + CC_LOG_INFO("AudioEngine::getVolume-->The audio instance %d is non-existent", audioID); + return 0.0F; +} + +AudioEngine::AudioState AudioEngine::getState(int audioID) { + auto tmpIterator = sAudioIDInfoMap.find(audioID); + if (tmpIterator != sAudioIDInfoMap.end()) { + return tmpIterator->second.state; + } + + return AudioState::ERROR; +} + +AudioProfile *AudioEngine::getProfile(int audioID) { + auto it = sAudioIDInfoMap.find(audioID); + if (it != sAudioIDInfoMap.end()) { + return &it->second.profileHelper->profile; + } + + return nullptr; +} + +AudioProfile *AudioEngine::getDefaultProfile() { + if (sDefaultProfileHelper == nullptr) { + sDefaultProfileHelper = ccnew ProfileHelper(); + } + + return &sDefaultProfileHelper->profile; +} + +AudioProfile *AudioEngine::getProfile(const ccstd::string &name) { + auto it = sAudioPathProfileHelperMap.find(name); + if (it != sAudioPathProfileHelperMap.end()) { + return &it->second.profile; + } + return nullptr; +} + +void AudioEngine::preload(const ccstd::string &filePath, const std::function &callback) { + if (!isEnabled()) { + callback(false); + return; + } + + lazyInit(); + + if (sAudioEngineImpl) { + if (!FileUtils::getInstance()->isFileExist(filePath)) { + if (callback) { + callback(false); + } + return; + } + + sAudioEngineImpl->preload(filePath, callback); + } +} + +void AudioEngine::addTask(const std::function &task) { + lazyInit(); + + if (sAudioEngineImpl && sThreadPool) { + sThreadPool->addTask(task); + } +} + +int AudioEngine::getPlayingAudioCount() { + return static_cast(sAudioIDInfoMap.size()); +} + +void AudioEngine::setEnabled(bool isEnabled) { + if (sIsEnabled != isEnabled) { + sIsEnabled = isEnabled; + + if (!sIsEnabled) { + stopAll(); + } + } +} + +bool AudioEngine::isEnabled() { + return sIsEnabled; +} + +PCMHeader AudioEngine::getPCMHeader(const char *url) { + lazyInit(); + return sAudioEngineImpl->getPCMHeader(url); +} +ccstd::vector AudioEngine::getOriginalPCMBuffer(const char *url, uint32_t channelID) { + lazyInit(); + return sAudioEngineImpl->getOriginalPCMBuffer(url, channelID); +} +} // namespace cc diff --git a/cocos/audio/android/AssetFd.cpp b/cocos/audio/android/AssetFd.cpp new file mode 100644 index 0000000..7011be2 --- /dev/null +++ b/cocos/audio/android/AssetFd.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AssetFd" + +#include "audio/android/AssetFd.h" +#include "audio/android/cutils/log.h" + +namespace cc { + +AssetFd::AssetFd(int assetFd) +: _assetFd(assetFd) { +} + +AssetFd::~AssetFd() { + ALOGV("~AssetFd: %d", _assetFd); + if (_assetFd > 0) { + ::close(_assetFd); + _assetFd = 0; + } +}; + +} // namespace cc diff --git a/cocos/audio/android/AssetFd.h b/cocos/audio/android/AssetFd.h new file mode 100644 index 0000000..e883a74 --- /dev/null +++ b/cocos/audio/android/AssetFd.h @@ -0,0 +1,42 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include + +namespace cc { + +class AssetFd { +public: + AssetFd(int assetFd); + ~AssetFd(); + + inline int getFd() const { return _assetFd; }; + +private: + int _assetFd; +}; + +} // namespace cc diff --git a/cocos/audio/android/AudioBufferProvider.h b/cocos/audio/android/AudioBufferProvider.h new file mode 100644 index 0000000..091f819 --- /dev/null +++ b/cocos/audio/android/AudioBufferProvider.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include "audio/android/utils/Errors.h" + +namespace cc { +// ---------------------------------------------------------------------------- + +class AudioBufferProvider { +public: + // IDEA: merge with AudioTrackShared::Buffer, AudioTrack::Buffer, and AudioRecord::Buffer + // and rename getNextBuffer() to obtainBuffer() + struct Buffer { + Buffer() : raw(NULL), frameCount(0) {} + union { + void *raw; + short *i16; + int8_t *i8; + }; + size_t frameCount; + }; + + virtual ~AudioBufferProvider() {} + + // value representing an invalid presentation timestamp + static const int64_t kInvalidPTS = 0x7FFFFFFFFFFFFFFFLL; // is too painful + + // pts is the local time when the next sample yielded by getNextBuffer + // will be rendered. + // Pass kInvalidPTS if the PTS is unknown or not applicable. + // On entry: + // buffer != NULL + // buffer->raw unused + // buffer->frameCount maximum number of desired frames + // On successful return: + // status NO_ERROR + // buffer->raw non-NULL pointer to buffer->frameCount contiguous available frames + // buffer->frameCount number of contiguous available frames at buffer->raw, + // 0 < buffer->frameCount <= entry value + // On error return: + // status != NO_ERROR + // buffer->raw NULL + // buffer->frameCount 0 + virtual status_t getNextBuffer(Buffer *buffer, int64_t pts = kInvalidPTS) = 0; + + // Release (a portion of) the buffer previously obtained by getNextBuffer(). + // It is permissible to call releaseBuffer() multiple times per getNextBuffer(). + // On entry: + // buffer->frameCount number of frames to release, must be <= number of frames + // obtained but not yet released + // buffer->raw unused + // On return: + // buffer->frameCount 0; implementation MUST set to zero + // buffer->raw undefined; implementation is PERMITTED to set to any value, + // so if caller needs to continue using this buffer it must + // keep track of the pointer itself + virtual void releaseBuffer(Buffer *buffer) = 0; +}; + +// ---------------------------------------------------------------------------- +} // namespace cc diff --git a/cocos/audio/android/AudioDecoder.cpp b/cocos/audio/android/AudioDecoder.cpp new file mode 100644 index 0000000..1a495db --- /dev/null +++ b/cocos/audio/android/AudioDecoder.cpp @@ -0,0 +1,260 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#define LOG_TAG "AudioDecoder" + +#include "audio/android/AudioDecoder.h" +#include "audio/android/AudioResampler.h" +#include "audio/android/PcmBufferProvider.h" + +#include +#include + +namespace cc { + +size_t AudioDecoder::fileRead(void *ptr, size_t size, size_t nmemb, void *datasource) { + AudioDecoder *thiz = (AudioDecoder *)datasource; + ssize_t toReadBytes = std::min((ssize_t)(thiz->_fileData.getSize() - thiz->_fileCurrPos), (ssize_t)(nmemb * size)); + if (toReadBytes > 0) { + memcpy(ptr, (unsigned char *)thiz->_fileData.getBytes() + thiz->_fileCurrPos, toReadBytes); + thiz->_fileCurrPos += toReadBytes; + } + // ALOGD("File size: %d, After fileRead _fileCurrPos %d", (int)thiz->_fileData.getSize(), thiz->_fileCurrPos); + return toReadBytes; +} + +int AudioDecoder::fileSeek(void *datasource, int64_t offset, int whence) { + AudioDecoder *thiz = (AudioDecoder *)datasource; + if (whence == SEEK_SET) + thiz->_fileCurrPos = static_cast(offset); + else if (whence == SEEK_CUR) + thiz->_fileCurrPos = static_cast(thiz->_fileCurrPos + offset); + else if (whence == SEEK_END) + thiz->_fileCurrPos = static_cast(thiz->_fileData.getSize()); + return 0; +} + +int AudioDecoder::fileClose(void *datasource) { + return 0; +} + +long AudioDecoder::fileTell(void *datasource) { + AudioDecoder *thiz = (AudioDecoder *)datasource; + return (long)thiz->_fileCurrPos; +} + +AudioDecoder::AudioDecoder() +: _fileCurrPos(0), _sampleRate(-1) { + auto pcmBuffer = std::make_shared>(); + pcmBuffer->reserve(4096); + _result.pcmBuffer = pcmBuffer; +} + +AudioDecoder::~AudioDecoder() { + ALOGV("~AudioDecoder() %p", this); +} + +bool AudioDecoder::init(const ccstd::string &url, int sampleRate) { + _url = url; + _sampleRate = sampleRate; + return true; +} + +bool AudioDecoder::start() { + auto oldTime = clockNow(); + auto nowTime = oldTime; + bool ret; + do { + ret = decodeToPcm(); + if (!ret) { + ALOGE("decodeToPcm (%s) failed!", _url.c_str()); + break; + } + + nowTime = clockNow(); + ALOGD("Decoding (%s) to pcm data wasted %fms", _url.c_str(), intervalInMS(oldTime, nowTime)); + oldTime = nowTime; + + ret = resample(); + if (!ret) { + ALOGE("resample (%s) failed!", _url.c_str()); + break; + } + + nowTime = clockNow(); + ALOGD("Resampling (%s) wasted %fms", _url.c_str(), intervalInMS(oldTime, nowTime)); + oldTime = nowTime; + + ret = interleave(); + if (!ret) { + ALOGE("interleave (%s) failed!", _url.c_str()); + break; + } + + nowTime = clockNow(); + ALOGD("Interleave (%s) wasted %fms", _url.c_str(), intervalInMS(oldTime, nowTime)); + + } while (false); + + ALOGV_IF(!ret, "%s returns false, decode (%s)", __FUNCTION__, _url.c_str()); + return ret; +} + +bool AudioDecoder::resample() { + if (_result.sampleRate == _sampleRate) { + ALOGI("No need to resample since the sample rate (%d) of the decoded pcm data is the same as the device output sample rate", + _sampleRate); + return true; + } + + ALOGV("Resample: %d --> %d", _result.sampleRate, _sampleRate); + + auto r = _result; + PcmBufferProvider provider; + provider.init(r.pcmBuffer->data(), r.numFrames, r.pcmBuffer->size() / r.numFrames); + + const int outFrameRate = _sampleRate; + int outputChannels = 2; + size_t outputFrameSize = outputChannels * sizeof(int32_t); + auto outputFrames = static_cast(((int64_t)r.numFrames * outFrameRate) / r.sampleRate); + size_t outputSize = outputFrames * outputFrameSize; + void *outputVAddr = malloc(outputSize); + + auto resampler = AudioResampler::create(AUDIO_FORMAT_PCM_16_BIT, r.numChannels, outFrameRate, + AudioResampler::MED_QUALITY); + resampler->setSampleRate(r.sampleRate); + resampler->setVolume(AudioResampler::UNITY_GAIN_FLOAT, AudioResampler::UNITY_GAIN_FLOAT); + + memset(outputVAddr, 0, outputSize); + + ALOGV("resample() %zu output frames", outputFrames); + + ccstd::vector Ovalues; + + if (Ovalues.empty()) { + Ovalues.push_back(static_cast(outputFrames)); + } + for (size_t i = 0, j = 0; i < outputFrames;) { + size_t thisFrames = Ovalues[j++]; + if (j >= Ovalues.size()) { + j = 0; + } + if (thisFrames == 0 || thisFrames > outputFrames - i) { + thisFrames = outputFrames - i; + } + int outFrames = static_cast(resampler->resample(static_cast(outputVAddr) + outputChannels * i, thisFrames, + &provider)); + ALOGV("outFrames: %d", outFrames); + i += thisFrames; + } + + ALOGV("resample() complete"); + + resampler->reset(); + + ALOGV("reset() complete"); + + delete resampler; + resampler = nullptr; + + // mono takes left channel only (out of stereo output pair) + // stereo and multichannel preserve all channels. + + int channels = r.numChannels; + int32_t *out = (int32_t *)outputVAddr; + int16_t *convert = (int16_t *)malloc(outputFrames * channels * sizeof(int16_t)); + + const int volumeShift = 12; // shift requirement for Q4.27 to Q.15 + // round to half towards zero and saturate at int16 (non-dithered) + const int roundVal = (1 << (volumeShift - 1)) - 1; // volumePrecision > 0 + + for (size_t i = 0; i < outputFrames; i++) { + for (int j = 0; j < channels; j++) { + int32_t s = out[i * outputChannels + j] + roundVal; // add offset here + if (s < 0) { + s = (s + 1) >> volumeShift; // round to 0 + if (s < -32768) { + s = -32768; + } + } else { + s = s >> volumeShift; + if (s > 32767) { + s = 32767; + } + } + convert[i * channels + j] = int16_t(s); + } + } + + // Reset result + _result.numFrames = static_cast(outputFrames); + _result.sampleRate = outFrameRate; + + auto buffer = std::make_shared>(); + buffer->reserve(_result.numFrames * _result.bitsPerSample / 8); + buffer->insert(buffer->end(), (char *)convert, + (char *)convert + outputFrames * channels * sizeof(int16_t)); + _result.pcmBuffer = buffer; + + ALOGV("pcm buffer size: %d", (int)_result.pcmBuffer->size()); + + free(convert); + free(outputVAddr); + return true; +} + +//----------------------------------------------------------------- +bool AudioDecoder::interleave() { + if (_result.numChannels == 2) { + ALOGI("Audio channel count is 2, no need to interleave"); + return true; + } else if (_result.numChannels == 1) { + // If it's a mono audio, try to compose a fake stereo buffer + size_t newBufferSize = _result.pcmBuffer->size() * 2; + auto newBuffer = std::make_shared>(); + newBuffer->reserve(newBufferSize); + size_t totalFrameSizeInBytes = (size_t)(_result.numFrames * _result.bitsPerSample / 8); + + for (size_t i = 0; i < totalFrameSizeInBytes; i += 2) { + // get one short value + char byte1 = _result.pcmBuffer->at(i); + char byte2 = _result.pcmBuffer->at(i + 1); + + // push two short value + for (int j = 0; j < 2; ++j) { + newBuffer->push_back(byte1); + newBuffer->push_back(byte2); + } + } + _result.numChannels = 2; + _result.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + _result.pcmBuffer = newBuffer; + return true; + } + + ALOGE("Audio channel count (%d) is wrong, interleave only supports converting mono to stereo!", _result.numChannels); + return false; +} + +} // namespace cc diff --git a/cocos/audio/android/AudioDecoder.h b/cocos/audio/android/AudioDecoder.h new file mode 100644 index 0000000..29f2bcc --- /dev/null +++ b/cocos/audio/android/AudioDecoder.h @@ -0,0 +1,61 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include "audio/android/OpenSLHelper.h" +#include "audio/android/PcmData.h" +#include "base/Data.h" + +namespace cc { + +class AudioDecoder { +public: + AudioDecoder(); + virtual ~AudioDecoder(); + + virtual bool init(const ccstd::string &url, int sampleRate); + + bool start(); + + inline PcmData getResult() { return _result; }; + +protected: + virtual bool decodeToPcm() = 0; + bool resample(); + bool interleave(); + + static size_t fileRead(void *ptr, size_t size, size_t nmemb, void *datasource); + static int fileSeek(void *datasource, int64_t offset, int whence); + static int fileClose(void *datasource); + static long fileTell(void *datasource); // NOLINT + + ccstd::string _url; + PcmData _result; + int _sampleRate; + Data _fileData; + size_t _fileCurrPos; +}; + +} // namespace cc diff --git a/cocos/audio/android/AudioDecoderMp3.cpp b/cocos/audio/android/AudioDecoderMp3.cpp new file mode 100644 index 0000000..9aa3164 --- /dev/null +++ b/cocos/audio/android/AudioDecoderMp3.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#define LOG_TAG "AudioDecoderMp3" + +#include "audio/android/AudioDecoderMp3.h" +#include "audio/android/mp3reader.h" +#include "platform/FileUtils.h" + +namespace cc { + +AudioDecoderMp3::AudioDecoderMp3() { + ALOGV("Create AudioDecoderMp3"); +} + +AudioDecoderMp3::~AudioDecoderMp3() { +} + +bool AudioDecoderMp3::decodeToPcm() { + _fileData = FileUtils::getInstance()->getDataFromFile(_url); + if (_fileData.isNull()) { + return false; + } + + mp3_callbacks callbacks; + callbacks.read = AudioDecoder::fileRead; + callbacks.seek = AudioDecoder::fileSeek; + callbacks.close = AudioDecoder::fileClose; + callbacks.tell = AudioDecoder::fileTell; + + int numChannels = 0; + int sampleRate = 0; + int numFrames = 0; + + if (EXIT_SUCCESS == decodeMP3(&callbacks, this, *_result.pcmBuffer, &numChannels, &sampleRate, &numFrames) && numChannels > 0 && sampleRate > 0 && numFrames > 0) { + _result.numChannels = numChannels; + _result.sampleRate = sampleRate; + _result.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + _result.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + _result.channelMask = numChannels == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT); + _result.endianness = SL_BYTEORDER_LITTLEENDIAN; + _result.numFrames = numFrames; + _result.duration = 1.0f * numFrames / sampleRate; + + ccstd::string info = _result.toString(); + ALOGI("Original audio info: %s, total size: %d", info.c_str(), (int)_result.pcmBuffer->size()); + return true; + } + + ALOGE("Decode MP3 (%s) failed, channels: %d, rate: %d, frames: %d", _url.c_str(), numChannels, sampleRate, numFrames); + return false; +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/android/AudioDecoderMp3.h b/cocos/audio/android/AudioDecoderMp3.h new file mode 100644 index 0000000..df42f31 --- /dev/null +++ b/cocos/audio/android/AudioDecoderMp3.h @@ -0,0 +1,41 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include "audio/android/AudioDecoder.h" + +namespace cc { + +class AudioDecoderMp3 : public AudioDecoder { +protected: + AudioDecoderMp3(); + virtual ~AudioDecoderMp3(); + + virtual bool decodeToPcm() override; + + friend class AudioDecoderProvider; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/android/AudioDecoderOgg.cpp b/cocos/audio/android/AudioDecoderOgg.cpp new file mode 100644 index 0000000..c49e16e --- /dev/null +++ b/cocos/audio/android/AudioDecoderOgg.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#define LOG_TAG "AudioDecoderOgg" + +#include "audio/android/AudioDecoderOgg.h" +#include "platform/FileUtils.h" + +namespace cc { + +AudioDecoderOgg::AudioDecoderOgg() { + ALOGV("Create AudioDecoderOgg"); +} + +AudioDecoderOgg::~AudioDecoderOgg() { +} + +int AudioDecoderOgg::fseek64Wrap(void *datasource, ogg_int64_t off, int whence) { + return AudioDecoder::fileSeek(datasource, (long)off, whence); +} + +bool AudioDecoderOgg::decodeToPcm() { + _fileData = FileUtils::getInstance()->getDataFromFile(_url); + if (_fileData.isNull()) { + return false; + } + + ov_callbacks callbacks; + callbacks.read_func = AudioDecoder::fileRead; + callbacks.seek_func = AudioDecoderOgg::fseek64Wrap; + callbacks.close_func = AudioDecoder::fileClose; + callbacks.tell_func = AudioDecoder::fileTell; + + _fileCurrPos = 0; + + OggVorbis_File vf; + int ret = ov_open_callbacks(this, &vf, NULL, 0, callbacks); + if (ret != 0) { + ALOGE("Open file error, file: %s, ov_open_callbacks return %d", _url.c_str(), ret); + return false; + } + // header + auto vi = ov_info(&vf, -1); + + uint32_t pcmSamples = (uint32_t)ov_pcm_total(&vf, -1); + + uint32_t bufferSize = pcmSamples * vi->channels * sizeof(short); + char *pcmBuffer = (char *)malloc(bufferSize); + memset(pcmBuffer, 0, bufferSize); + + int currentSection = 0; + long curPos = 0; + long readBytes = 0; + + do { + readBytes = ov_read(&vf, pcmBuffer + curPos, 4096, ¤tSection); + curPos += readBytes; + } while (readBytes > 0); + + if (curPos > 0) { + _result.pcmBuffer->insert(_result.pcmBuffer->end(), pcmBuffer, pcmBuffer + bufferSize); + _result.numChannels = vi->channels; + _result.sampleRate = vi->rate; + _result.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + _result.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + _result.channelMask = vi->channels == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT); + _result.endianness = SL_BYTEORDER_LITTLEENDIAN; + _result.numFrames = pcmSamples; + _result.duration = 1.0f * pcmSamples / vi->rate; + } else { + ALOGE("ov_read returns 0 byte!"); + } + + ov_clear(&vf); + free(pcmBuffer); + + return (curPos > 0); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/android/AudioDecoderOgg.h b/cocos/audio/android/AudioDecoderOgg.h new file mode 100644 index 0000000..943d3e8 --- /dev/null +++ b/cocos/audio/android/AudioDecoderOgg.h @@ -0,0 +1,44 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include "audio/android/AudioDecoder.h" + +#include "tremolo/Tremolo/ivorbisfile.h" + +namespace cc { + +class AudioDecoderOgg : public AudioDecoder { +protected: + AudioDecoderOgg(); + virtual ~AudioDecoderOgg(); + + static int fseek64Wrap(void *datasource, ogg_int64_t off, int whence); + virtual bool decodeToPcm() override; + + friend class AudioDecoderProvider; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/android/AudioDecoderProvider.cpp b/cocos/audio/android/AudioDecoderProvider.cpp new file mode 100644 index 0000000..ff71dce --- /dev/null +++ b/cocos/audio/android/AudioDecoderProvider.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioDecoderProvider" + +#include "audio/android/AudioDecoderProvider.h" +#include "audio/android/AudioDecoderMp3.h" +#include "audio/android/AudioDecoderOgg.h" +#include "audio/android/AudioDecoderSLES.h" +#include "audio/android/AudioDecoderWav.h" +#include "base/memory/Memory.h" +#include "platform/FileUtils.h" + +namespace cc { + +AudioDecoder *AudioDecoderProvider::createAudioDecoder(SLEngineItf engineItf, const ccstd::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback) { + AudioDecoder *decoder = nullptr; + ccstd::string extension = FileUtils::getInstance()->getFileExtension(url); + ALOGV("url:%s, extension:%s", url.c_str(), extension.c_str()); + if (extension == ".ogg") { + decoder = ccnew AudioDecoderOgg(); + if (!decoder->init(url, sampleRate)) { + delete decoder; + decoder = nullptr; + } + } else if (extension == ".mp3") { + decoder = ccnew AudioDecoderMp3(); + if (!decoder->init(url, sampleRate)) { + delete decoder; + decoder = nullptr; + } + } else if (extension == ".wav") { + decoder = ccnew AudioDecoderWav(); + if (!decoder->init(url, sampleRate)) { + delete decoder; + decoder = nullptr; + } + } else { + auto slesDecoder = ccnew AudioDecoderSLES(); + if (slesDecoder->init(engineItf, url, bufferSizeInFrames, sampleRate, fdGetterCallback)) { + decoder = slesDecoder; + } else { + delete slesDecoder; + } + } + + return decoder; +} + +void AudioDecoderProvider::destroyAudioDecoder(AudioDecoder **decoder) { + if (decoder != nullptr && *decoder != nullptr) { + delete (*decoder); + (*decoder) = nullptr; + } +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/android/AudioDecoderProvider.h b/cocos/audio/android/AudioDecoderProvider.h new file mode 100644 index 0000000..2a26baa --- /dev/null +++ b/cocos/audio/android/AudioDecoderProvider.h @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/android/OpenSLHelper.h" + +namespace cc { + +class AudioDecoder; + +class AudioDecoderProvider { +public: + static AudioDecoder *createAudioDecoder(SLEngineItf engineItf, const ccstd::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback); + static void destroyAudioDecoder(AudioDecoder **decoder); +}; + +} // namespace cc diff --git a/cocos/audio/android/AudioDecoderSLES.cpp b/cocos/audio/android/AudioDecoderSLES.cpp new file mode 100644 index 0000000..43b7a14 --- /dev/null +++ b/cocos/audio/android/AudioDecoderSLES.cpp @@ -0,0 +1,588 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioDecoderSLES" + +#include "base/Macros.h" +#include "audio/android/AudioDecoderSLES.h" +#include "platform/FileUtils.h" + +#include +#include + +namespace cc { + +/* Explicitly requesting SL_IID_ANDROIDSIMPLEBUFFERQUEUE and SL_IID_PREFETCHSTATUS +* on the UrlAudioPlayer object for decoding, SL_IID_METADATAEXTRACTION for retrieving the +* format of the decoded audio */ +#define NUM_EXPLICIT_INTERFACES_FOR_PLAYER 3 + +/* Size of the decode buffer queue */ +#define NB_BUFFERS_IN_QUEUE 4 + +/* size of the struct to retrieve the PCM format metadata values: the values we're interested in + * are SLuint32, but it is saved in the data field of a SLMetadataInfo, hence the larger size. + * Nate that this size is queried and displayed at l.452 for demonstration/test purposes. + * */ +#define PCM_METADATA_VALUE_SIZE 32 + +/* used to detect errors likely to have occurred when the OpenSL ES framework fails to open + * a resource, for instance because a file URI is invalid, or an HTTP server doesn't respond. + */ +#define PREFETCHEVENT_ERROR_CANDIDATE (SL_PREFETCHEVENT_STATUSCHANGE | SL_PREFETCHEVENT_FILLLEVELCHANGE) + +//----------------------------------------------------------------- + +static std::mutex __SLPlayerMutex; //NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + +static int toBufferSizeInBytes(int bufferSizeInFrames, int sampleSize, int channelCount) { + return bufferSizeInFrames * sampleSize * channelCount; +} + +static int BUFFER_SIZE_IN_BYTES = 0; // NOLINT(readability-identifier-naming) + +static void checkMetaData(int index, const char *key) { + if (index != -1) { + ALOGV("Key %s is at index %d", key, index); + } else { + ALOGE("Unable to find key %s", key); + } +} + +class SLAudioDecoderCallbackProxy { +public: + //----------------------------------------------------------------- + /* Callback for "prefetch" events, here used to detect audio resource opening errors */ + static void prefetchEventCallback(SLPrefetchStatusItf caller, void *context, SLuint32 event) { + auto *thiz = reinterpret_cast(context); + thiz->prefetchCallback(caller, event); + } + + static void decPlayCallback(CCSLBufferQueueItf queueItf, void *context) { + auto *thiz = reinterpret_cast(context); + thiz->decodeToPcmCallback(queueItf); + } + + static void decProgressCallback(SLPlayItf caller, void *context, SLuint32 event) { + auto *thiz = reinterpret_cast(context); + thiz->decodeProgressCallback(caller, event); + } +}; + +AudioDecoderSLES::AudioDecoderSLES() +: _engineItf(nullptr), _playObj(nullptr), _formatQueried(false), _prefetchError(false), _counter(0), _numChannelsKeyIndex(-1), _sampleRateKeyIndex(-1), _bitsPerSampleKeyIndex(-1), _containerSizeKeyIndex(-1), _channelMaskKeyIndex(-1), _endiannessKeyIndex(-1), _eos(false), _bufferSizeInFrames(-1), _assetFd(0), _fdGetterCallback(nullptr), _isDecodingCallbackInvoked(false) { + ALOGV("Create AudioDecoderSLES"); +} + +AudioDecoderSLES::~AudioDecoderSLES() { + { + std::lock_guard lk(__SLPlayerMutex); + SL_DESTROY_OBJ(_playObj); + } + ALOGV("After destroying SL play object"); + if (_assetFd > 0) { + ALOGV("Closing assetFd: %d", _assetFd); + ::close(_assetFd); + _assetFd = 0; + } + free(_pcmData); +} + +bool AudioDecoderSLES::init(SLEngineItf engineItf, const ccstd::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback) { + if (AudioDecoder::init(url, sampleRate)) { + _engineItf = engineItf; + _bufferSizeInFrames = bufferSizeInFrames; + _fdGetterCallback = fdGetterCallback; + + BUFFER_SIZE_IN_BYTES = toBufferSizeInBytes(bufferSizeInFrames, 2, 2); + _pcmData = static_cast(malloc(NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES)); + memset(_pcmData, 0x00, NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES); + return true; + } + + return false; +} + +bool AudioDecoderSLES::decodeToPcm() { +#if CC_PLATFORM == CC_PLATFORM_ANDROID + SLresult result; + + /* Objects this application uses: one audio player */ + SLObjectItf player; + + /* Interfaces for the audio player */ + CCSLBufferQueueItf decBuffQueueItf; + SLPrefetchStatusItf prefetchItf; + SLPlayItf playItf; + SLMetadataExtractionItf mdExtrItf; + + /* Source of audio data for the decoding */ + SLDataSource decSource; + + // decUri & locFd should be defined here + SLDataLocator_URI decUri; + SLDataLocator_AndroidFD locFd; + + /* Data sink for decoded audio */ + SLDataSink decDest; + SLDataLocator_AndroidSimpleBufferQueue decBuffQueue; + SLDataFormat_PCM pcm; + + SLboolean required[NUM_EXPLICIT_INTERFACES_FOR_PLAYER]; + SLInterfaceID iidArray[NUM_EXPLICIT_INTERFACES_FOR_PLAYER]; + + /* Initialize arrays required[] and iidArray[] */ + for (int i = 0; i < NUM_EXPLICIT_INTERFACES_FOR_PLAYER; i++) { + required[i] = SL_BOOLEAN_FALSE; + iidArray[i] = SL_IID_NULL; + } + + /* ------------------------------------------------------ */ + /* Configuration of the player */ + + /* Request the AndroidSimpleBufferQueue interface */ + required[0] = SL_BOOLEAN_TRUE; + iidArray[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE; + /* Request the PrefetchStatus interface */ + required[1] = SL_BOOLEAN_TRUE; + iidArray[1] = SL_IID_PREFETCHSTATUS; + /* Request the PrefetchStatus interface */ + required[2] = SL_BOOLEAN_TRUE; + iidArray[2] = SL_IID_METADATAEXTRACTION; + + SLDataFormat_MIME formatMime = {SL_DATAFORMAT_MIME, nullptr, SL_CONTAINERTYPE_UNSPECIFIED}; + decSource.pFormat = &formatMime; + + if (_url[0] != '/') { + off_t start = 0; + off_t length = 0; + ccstd::string relativePath; + size_t position = _url.find("@assets/"); + + if (0 == position) { + // "@assets/" is at the beginning of the path and we don't want it + relativePath = _url.substr(strlen("@assets/")); + } else { + relativePath = _url; + } + + _assetFd = _fdGetterCallback(relativePath, &start, &length); + + if (_assetFd <= 0) { + ALOGE("Failed to open file descriptor for '%s'", _url.c_str()); + return false; + } + + // configure audio source + locFd = {SL_DATALOCATOR_ANDROIDFD, _assetFd, start, length}; + + decSource.pLocator = &locFd; + } else { + decUri = {SL_DATALOCATOR_URI, (SLchar *)_url.c_str()}; // NOLINT(google-readability-casting) + decSource.pLocator = &decUri; + } + + /* Setup the data sink */ + decBuffQueue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + decBuffQueue.numBuffers = NB_BUFFERS_IN_QUEUE; + /* set up the format of the data in the buffer queue */ + pcm.formatType = SL_DATAFORMAT_PCM; + // IDEA: valid value required but currently ignored + pcm.numChannels = 2; + pcm.samplesPerSec = SL_SAMPLINGRATE_44_1; + pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + pcm.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; + + decDest.pLocator = reinterpret_cast(&decBuffQueue); + decDest.pFormat = reinterpret_cast(&pcm); + + { + std::lock_guard lk(__SLPlayerMutex); + /* Create the audio player */ + result = (*_engineItf)->CreateAudioPlayer(_engineItf, &player, &decSource, &decDest, NUM_EXPLICIT_INTERFACES_FOR_PLAYER, iidArray, required); + SL_RETURN_VAL_IF_FAILED(result, false, "CreateAudioPlayer failed"); + + _playObj = player; + /* Realize the player in synchronous mode. */ + result = (*player)->Realize(player, SL_BOOLEAN_FALSE); + SL_RETURN_VAL_IF_FAILED(result, false, "Realize failed"); + } + + /* Get the play interface which is implicit */ + result = (*player)->GetInterface(player, SL_IID_PLAY, reinterpret_cast(&playItf)); + SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PLAY failed"); + + /* Set up the player callback to get events during the decoding */ + // IDEA: currently ignored + result = (*playItf)->SetMarkerPosition(playItf, 2000); + SL_RETURN_VAL_IF_FAILED(result, false, "SetMarkerPosition failed"); + + result = (*playItf)->SetPositionUpdatePeriod(playItf, 500); + SL_RETURN_VAL_IF_FAILED(result, false, "SetPositionUpdatePeriod failed"); + result = (*playItf)->SetCallbackEventsMask(playItf, + SL_PLAYEVENT_HEADATMARKER | + SL_PLAYEVENT_HEADATNEWPOS | SL_PLAYEVENT_HEADATEND); + SL_RETURN_VAL_IF_FAILED(result, false, "SetCallbackEventsMask failed"); + result = (*playItf)->RegisterCallback(playItf, SLAudioDecoderCallbackProxy::decProgressCallback, + this); + SL_RETURN_VAL_IF_FAILED(result, false, "RegisterCallback failed"); + ALOGV("Play callback registered"); + + /* Get the buffer queue interface which was explicitly requested */ + result = (*player)->GetInterface(player, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + reinterpret_cast(&decBuffQueueItf)); + SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE failed"); + + /* Get the prefetch status interface which was explicitly requested */ + result = (*player)->GetInterface(player, SL_IID_PREFETCHSTATUS, reinterpret_cast(&prefetchItf)); + SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PREFETCHSTATUS failed"); + + /* Get the metadata extraction interface which was explicitly requested */ + result = (*player)->GetInterface(player, SL_IID_METADATAEXTRACTION, reinterpret_cast(&mdExtrItf)); + SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_METADATAEXTRACTION failed"); + + /* ------------------------------------------------------ */ + /* Initialize the callback and its context for the decoding buffer queue */ + _decContext.playItf = playItf; + _decContext.metaItf = mdExtrItf; + _decContext.pDataBase = reinterpret_cast(_pcmData); + _decContext.pData = _decContext.pDataBase; + _decContext.size = NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES; + + result = (*decBuffQueueItf)->RegisterCallback(decBuffQueueItf, SLAudioDecoderCallbackProxy::decPlayCallback, this); + SL_RETURN_VAL_IF_FAILED(result, false, "decBuffQueueItf RegisterCallback failed"); + + /* Enqueue buffers to map the region of memory allocated to store the decoded data */ + // ALOGV("Enqueueing buffer "); + for (int i = 0; i < NB_BUFFERS_IN_QUEUE; i++) { + result = (*decBuffQueueItf)->Enqueue(decBuffQueueItf, _decContext.pData, BUFFER_SIZE_IN_BYTES); + SL_RETURN_VAL_IF_FAILED(result, false, "Enqueue failed"); + _decContext.pData += BUFFER_SIZE_IN_BYTES; + } + + _decContext.pData = _decContext.pDataBase; + + /* ------------------------------------------------------ */ + /* Initialize the callback for prefetch errors, if we can't open the resource to decode */ + result = (*prefetchItf)->RegisterCallback(prefetchItf, SLAudioDecoderCallbackProxy::prefetchEventCallback, this); + SL_RETURN_VAL_IF_FAILED(result, false, "prefetchItf RegisterCallback failed"); + + result = (*prefetchItf)->SetCallbackEventsMask(prefetchItf, PREFETCHEVENT_ERROR_CANDIDATE); + SL_RETURN_VAL_IF_FAILED(result, false, "prefetchItf SetCallbackEventsMask failed"); + + /* ------------------------------------------------------ */ + /* Prefetch the data so we can get information about the format before starting to decode */ + /* 1/ cause the player to prefetch the data */ + result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PAUSED); + SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_PAUSED failed"); + + /* 2/ block until data has been prefetched */ + SLuint32 prefetchStatus = SL_PREFETCHSTATUS_UNDERFLOW; + SLuint32 timeOutIndex = 1000; //cjh time out prefetching after 2s + while ((prefetchStatus != SL_PREFETCHSTATUS_SUFFICIENTDATA) && (timeOutIndex > 0) && + !_prefetchError) { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + (*prefetchItf)->GetPrefetchStatus(prefetchItf, &prefetchStatus); + timeOutIndex--; + } + if (timeOutIndex == 0 || _prefetchError) { + ALOGE("Failure to prefetch data in time, exiting"); + SL_RETURN_VAL_IF_FAILED(SL_RESULT_CONTENT_NOT_FOUND, false, + "Failure to prefetch data in time"); + } + + /* ------------------------------------------------------ */ + /* Display duration */ + SLmillisecond durationInMsec = SL_TIME_UNKNOWN; + result = (*playItf)->GetDuration(playItf, &durationInMsec); + SL_RETURN_VAL_IF_FAILED(result, false, "GetDuration failed"); + + if (durationInMsec == SL_TIME_UNKNOWN) { + ALOGV("Content duration is unknown"); + } else { + ALOGV("Content duration is %dms", (int)durationInMsec); + } + + /* ------------------------------------------------------ */ + /* Display the metadata obtained from the decoder */ + // This is for test / demonstration purposes only where we discover the key and value sizes + // of a PCM decoder. An application that would want to directly get access to those values + // can make assumptions about the size of the keys and their matching values (all SLuint32) + SLuint32 itemCount; + result = (*mdExtrItf)->GetItemCount(mdExtrItf, &itemCount); + SLuint32 i; + SLuint32 keySize; + SLuint32 valueSize; + SLMetadataInfo *keyInfo; + SLMetadataInfo *value; + for (i = 0; i < itemCount; i++) { + keyInfo = nullptr; + keySize = 0; + value = nullptr; + valueSize = 0; + result = (*mdExtrItf)->GetKeySize(mdExtrItf, i, &keySize); + SL_RETURN_VAL_IF_FAILED(result, false, "GetKeySize(%d) failed", (int)i); + + result = (*mdExtrItf)->GetValueSize(mdExtrItf, i, &valueSize); + SL_RETURN_VAL_IF_FAILED(result, false, "GetValueSize(%d) failed", (int)i); + + keyInfo = reinterpret_cast(malloc(keySize)); + if (nullptr != keyInfo) { + result = (*mdExtrItf)->GetKey(mdExtrItf, i, keySize, keyInfo); + + SL_RETURN_VAL_IF_FAILED(result, false, "GetKey(%d) failed", (int)i); + + ALOGV("key[%d] size=%d, name=%s, value size=%d", + (int)i, (int)keyInfo->size, keyInfo->data, (int)valueSize); + /* find out the key index of the metadata we're interested in */ + if (!strcmp(reinterpret_cast(keyInfo->data), ANDROID_KEY_PCMFORMAT_NUMCHANNELS)) { + _numChannelsKeyIndex = i; + } else if (!strcmp(reinterpret_cast(keyInfo->data), ANDROID_KEY_PCMFORMAT_SAMPLERATE)) { + _sampleRateKeyIndex = i; + } else if (!strcmp(reinterpret_cast(keyInfo->data), ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE)) { + _bitsPerSampleKeyIndex = i; + } else if (!strcmp(reinterpret_cast(keyInfo->data), ANDROID_KEY_PCMFORMAT_CONTAINERSIZE)) { + _containerSizeKeyIndex = i; + } else if (!strcmp(reinterpret_cast(keyInfo->data), ANDROID_KEY_PCMFORMAT_CHANNELMASK)) { + _channelMaskKeyIndex = i; + } else if (!strcmp(reinterpret_cast(keyInfo->data), ANDROID_KEY_PCMFORMAT_ENDIANNESS)) { + _endiannessKeyIndex = i; + } + free(keyInfo); + } + } + + checkMetaData(_numChannelsKeyIndex, ANDROID_KEY_PCMFORMAT_NUMCHANNELS); + checkMetaData(_sampleRateKeyIndex, ANDROID_KEY_PCMFORMAT_SAMPLERATE); + checkMetaData(_bitsPerSampleKeyIndex, ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE); + checkMetaData(_containerSizeKeyIndex, ANDROID_KEY_PCMFORMAT_CONTAINERSIZE); + checkMetaData(_channelMaskKeyIndex, ANDROID_KEY_PCMFORMAT_CHANNELMASK); + checkMetaData(_endiannessKeyIndex, ANDROID_KEY_PCMFORMAT_ENDIANNESS); + + /* ------------------------------------------------------ */ + /* Start decoding */ + result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING); + SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_PLAYING failed"); + + ALOGV("Starting to decode"); + + /* Decode until the end of the stream is reached */ + { + std::unique_lock autoLock(_eosLock); + while (!_eos) { + _eosCondition.wait(autoLock); + } + } + ALOGV("EOS signaled"); + + /* ------------------------------------------------------ */ + /* End of decoding */ + + /* Stop decoding */ + result = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED); + SL_RETURN_VAL_IF_FAILED(result, false, "SetPlayState SL_PLAYSTATE_STOPPED failed"); + + ALOGV("Stopped decoding"); + + /* Destroy the UrlAudioPlayer object */ + { + std::lock_guard lk(__SLPlayerMutex); + SL_DESTROY_OBJ(_playObj); + } + + ALOGV("After destroy player ..."); + + _result.numFrames = + static_cast(_result.pcmBuffer->size() / _result.numChannels / (_result.bitsPerSample / 8)); + + ccstd::string info = _result.toString(); + ALOGI("Original audio info: %s, total size: %d", info.c_str(), (int)_result.pcmBuffer->size()); +#endif + return true; +} + +//----------------------------------------------------------------- +void AudioDecoderSLES::signalEos() { + std::unique_lock autoLock(_eosLock); + _eos = true; + _eosCondition.notify_one(); +} + +void AudioDecoderSLES::queryAudioInfo() { + if (_formatQueried) { + return; + } + + SLresult result; + /* Get duration in callback where we use the callback context for the SLPlayItf*/ + SLmillisecond durationInMsec = SL_TIME_UNKNOWN; + result = (*_decContext.playItf)->GetDuration(_decContext.playItf, &durationInMsec); + SL_RETURN_IF_FAILED(result, "decodeProgressCallback,GetDuration failed"); + + if (durationInMsec == SL_TIME_UNKNOWN) { + ALOGV("Content duration is unknown (in dec callback)"); + } else { + ALOGV("Content duration is %dms (in dec callback)", (int)durationInMsec); + _result.duration = durationInMsec / 1000.0F; + } + + /* used to query metadata values */ + SLMetadataInfo pcmMetaData; + + result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _sampleRateKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData); + + SL_RETURN_IF_FAILED(result, "%s GetValue _sampleRateKeyIndex failed", __FUNCTION__); + // Note: here we could verify the following: + // pcmMetaData->encoding == SL_CHARACTERENCODING_BINARY + // pcmMetaData->size == sizeof(SLuint32) + // but the call was successful for the PCM format keys, so those conditions are implied + + _result.sampleRate = *reinterpret_cast(pcmMetaData.data); + result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _numChannelsKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData); + SL_RETURN_IF_FAILED(result, "%s GetValue _numChannelsKeyIndex failed", __FUNCTION__); + + _result.numChannels = *reinterpret_cast(pcmMetaData.data); + + result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _bitsPerSampleKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData); + SL_RETURN_IF_FAILED(result, "%s GetValue _bitsPerSampleKeyIndex failed", __FUNCTION__) + _result.bitsPerSample = *reinterpret_cast(pcmMetaData.data); + + result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _containerSizeKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData); + SL_RETURN_IF_FAILED(result, "%s GetValue _containerSizeKeyIndex failed", __FUNCTION__) + _result.containerSize = *reinterpret_cast(pcmMetaData.data); + + result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _channelMaskKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData); + SL_RETURN_IF_FAILED(result, "%s GetValue _channelMaskKeyIndex failed", __FUNCTION__) + _result.channelMask = *reinterpret_cast(pcmMetaData.data); + + result = (*_decContext.metaItf)->GetValue(_decContext.metaItf, _endiannessKeyIndex, PCM_METADATA_VALUE_SIZE, &pcmMetaData); + SL_RETURN_IF_FAILED(result, "%s GetValue _endiannessKeyIndex failed", __FUNCTION__) + _result.endianness = *reinterpret_cast(pcmMetaData.data); + + _formatQueried = true; +} + +void AudioDecoderSLES::prefetchCallback(SLPrefetchStatusItf caller, SLuint32 event) { + SLpermille level = 0; + SLresult result; + result = (*caller)->GetFillLevel(caller, &level); + SL_RETURN_IF_FAILED(result, "GetFillLevel failed"); + + SLuint32 status; + //ALOGV("PrefetchEventCallback: received event %u", event); + result = (*caller)->GetPrefetchStatus(caller, &status); + + SL_RETURN_IF_FAILED(result, "GetPrefetchStatus failed"); + + if ((PREFETCHEVENT_ERROR_CANDIDATE == (event & PREFETCHEVENT_ERROR_CANDIDATE)) && (level == 0) && (status == SL_PREFETCHSTATUS_UNDERFLOW)) { + ALOGV("PrefetchEventCallback: Error while prefetching data, exiting"); + _prefetchError = true; + signalEos(); + } +} + +/* Callback for "playback" events, i.e. event happening during decoding */ +void AudioDecoderSLES::decodeProgressCallback(SLPlayItf caller, SLuint32 event) { + CC_UNUSED_PARAM(caller); + if (SL_PLAYEVENT_HEADATEND & event) { + ALOGV("SL_PLAYEVENT_HEADATEND"); + if (!_isDecodingCallbackInvoked) { + queryAudioInfo(); + + for (int i = 0; i < NB_BUFFERS_IN_QUEUE; ++i) { + _result.pcmBuffer->insert(_result.pcmBuffer->end(), _decContext.pData, + _decContext.pData + BUFFER_SIZE_IN_BYTES); + + /* Increase data pointer by buffer size */ + _decContext.pData += BUFFER_SIZE_IN_BYTES; + } + } + signalEos(); + } +} + +//----------------------------------------------------------------- +/* Callback for decoding buffer queue events */ +void AudioDecoderSLES::decodeToPcmCallback(CCSLBufferQueueItf queueItf) { + _isDecodingCallbackInvoked = true; + ALOGV("%s ...", __FUNCTION__); + _counter++; + SLresult result; + // IDEA: ?? + if (_counter % 1000 == 0) { + SLmillisecond msec; + result = (*_decContext.playItf)->GetPosition(_decContext.playItf, &msec); + SL_RETURN_IF_FAILED(result, "%s, GetPosition failed", __FUNCTION__); + ALOGV("%s called (iteration %d): current position=%d ms", __FUNCTION__, _counter, (int)msec); + } + + _result.pcmBuffer->insert(_result.pcmBuffer->end(), _decContext.pData, + _decContext.pData + BUFFER_SIZE_IN_BYTES); + + result = (*queueItf)->Enqueue(queueItf, _decContext.pData, BUFFER_SIZE_IN_BYTES); + SL_RETURN_IF_FAILED(result, "%s, Enqueue failed", __FUNCTION__); + + /* Increase data pointer by buffer size */ + _decContext.pData += BUFFER_SIZE_IN_BYTES; + + if (_decContext.pData >= _decContext.pDataBase + (NB_BUFFERS_IN_QUEUE * BUFFER_SIZE_IN_BYTES)) { + _decContext.pData = _decContext.pDataBase; + } + + // Note: adding a sleep here or any sync point is a way to slow down the decoding, or + // synchronize it with some other event, as the OpenSL ES framework will block until the + // buffer queue callback return to proceed with the decoding. + +#if 0 + /* Example: buffer queue state display */ + SLAndroidSimpleBufferQueueState decQueueState; + result =(*queueItf)->GetState(queueItf, &decQueueState); + SL_RETURN_IF_FAILED(result, "decQueueState.GetState failed"); + + ALOGV("DecBufferQueueCallback now has _decContext.pData=%p, _decContext.pDataBase=%p, queue: " + "count=%u playIndex=%u, count: %d", + _decContext.pData, _decContext.pDataBase, decQueueState.count, decQueueState.index, _counter); +#endif + +#if 0 + /* Example: display position in callback where we use the callback context for the SLPlayItf*/ + SLmillisecond posMsec = SL_TIME_UNKNOWN; + result = (*_decContext.playItf)->GetPosition(_decContext.playItf, &posMsec); + SL_RETURN_IF_FAILED(result, "decodeToPcmCallback,GetPosition2 failed"); + + if (posMsec == SL_TIME_UNKNOWN) { + ALOGV("Content position is unknown (in dec callback)"); + } else { + ALOGV("Content position is %ums (in dec callback)", + posMsec); + } +#endif + + queryAudioInfo(); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/android/AudioDecoderSLES.h b/cocos/audio/android/AudioDecoderSLES.h new file mode 100644 index 0000000..2f9cdc3 --- /dev/null +++ b/cocos/audio/android/AudioDecoderSLES.h @@ -0,0 +1,96 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "audio/android/AudioDecoder.h" +#include "audio/android/utils/Compat.h" + +namespace cc { + +class AudioDecoderSLES : public AudioDecoder { +protected: + AudioDecoderSLES(); + ~AudioDecoderSLES() override; + + bool init(SLEngineItf engineItf, const ccstd::string &url, int bufferSizeInFrames, int sampleRate, const FdGetterCallback &fdGetterCallback); + bool decodeToPcm() override; + +private: + void queryAudioInfo(); + + void signalEos(); + void decodeToPcmCallback(CCSLBufferQueueItf queueItf); + void prefetchCallback(SLPrefetchStatusItf caller, SLuint32 event); + void decodeProgressCallback(SLPlayItf caller, SLuint32 event); + + SLEngineItf _engineItf; + SLObjectItf _playObj; + /* Local storage for decoded audio data */ + char *_pcmData; + + /* we only want to query / display the PCM format once */ + bool _formatQueried; + /* Used to signal prefetching failures */ + bool _prefetchError; + + /* to display the number of decode iterations */ + int _counter; + + /* metadata key index for the PCM format information we want to retrieve */ + int _numChannelsKeyIndex; + int _sampleRateKeyIndex; + int _bitsPerSampleKeyIndex; + int _containerSizeKeyIndex; + int _channelMaskKeyIndex; + int _endiannessKeyIndex; + + /* to signal to the test app the end of the stream to decode has been reached */ + bool _eos; + std::mutex _eosLock; + std::condition_variable _eosCondition; + + /* Structure for passing information to callback function */ + typedef struct CallbackCntxt_ { //NOLINT(modernize-use-using, readability-identifier-naming) + SLPlayItf playItf; + SLMetadataExtractionItf metaItf; + SLuint32 size; + SLint8 *pDataBase; // Base address of local audio data storage + SLint8 *pData; // Current address of local audio data storage + } CallbackCntxt; + + CallbackCntxt _decContext; + int _bufferSizeInFrames; + int _assetFd; + FdGetterCallback _fdGetterCallback; + bool _isDecodingCallbackInvoked; + + friend class SLAudioDecoderCallbackProxy; + friend class AudioDecoderProvider; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/android/AudioDecoderWav.cpp b/cocos/audio/android/AudioDecoderWav.cpp new file mode 100644 index 0000000..4aecbeb --- /dev/null +++ b/cocos/audio/android/AudioDecoderWav.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioDecoderWav" + +#include "audio/android/AudioDecoderWav.h" +#include "audio/common/utils/include/tinysndfile.h" +#include "platform/FileUtils.h" + +namespace cc { +using namespace sf; //NOLINT +AudioDecoderWav::AudioDecoderWav() { + ALOGV("Create AudioDecoderWav"); +} + +AudioDecoderWav::~AudioDecoderWav() = default; + +void *AudioDecoderWav::onWavOpen(const char * /*path*/, void *user) { + return user; +} + +int AudioDecoderWav::onWavSeek(void *datasource, long offset, int whence) { //NOLINT(google-runtime-int) + return AudioDecoder::fileSeek(datasource, static_cast(offset), whence); +} + +int AudioDecoderWav::onWavClose(void * /*datasource*/) { + return 0; +} + +bool AudioDecoderWav::decodeToPcm() { + _fileData = FileUtils::getInstance()->getDataFromFile(_url); + if (_fileData.isNull()) { + return false; + } + + SF_INFO info; + + snd_callbacks cb; + cb.open = onWavOpen; + cb.read = AudioDecoder::fileRead; + cb.seek = onWavSeek; + cb.close = onWavClose; + cb.tell = AudioDecoder::fileTell; + + SNDFILE *handle = nullptr; + bool ret = false; + do { + handle = sf_open_read(_url.c_str(), &info, &cb, this); + if (handle == nullptr) { + break; + } + + if (info.frames == 0) { + break; + } + + ALOGD("wav info: frames: %d, samplerate: %d, channels: %d, format: %d", info.frames, info.samplerate, info.channels, info.format); + size_t bufSize = sizeof(int16_t) * info.frames * info.channels; + auto *buf = static_cast(malloc(bufSize)); + sf_count_t readFrames = sf_readf_short(handle, reinterpret_cast(buf), info.frames); + CC_ASSERT(readFrames == info.frames); + + _result.pcmBuffer->insert(_result.pcmBuffer->end(), buf, buf + bufSize); + _result.numChannels = info.channels; + _result.sampleRate = info.samplerate; + _result.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + _result.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + _result.channelMask = _result.numChannels == 1 ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT); + _result.endianness = SL_BYTEORDER_LITTLEENDIAN; + _result.numFrames = info.frames; + _result.duration = static_cast(1.0F * info.frames / _result.sampleRate); //NOLINT + + free(buf); + ret = true; + } while (false); + + if (handle != nullptr) { + sf_close(handle); + } + + return ret; +} + +} // namespace cc diff --git a/cocos/audio/android/AudioDecoderWav.h b/cocos/audio/android/AudioDecoderWav.h new file mode 100644 index 0000000..69dae44 --- /dev/null +++ b/cocos/audio/android/AudioDecoderWav.h @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/android/AudioDecoder.h" + +namespace cc { + +class AudioDecoderWav : public AudioDecoder { +protected: + AudioDecoderWav(); + virtual ~AudioDecoderWav(); + + virtual bool decodeToPcm() override; + + static void *onWavOpen(const char *path, void *user); + static int onWavSeek(void *datasource, long offset, int whence); + static int onWavClose(void *datasource); + + friend class AudioDecoderProvider; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/android/AudioEngine-inl.cpp b/cocos/audio/android/AudioEngine-inl.cpp new file mode 100644 index 0000000..c19fb54 --- /dev/null +++ b/cocos/audio/android/AudioEngine-inl.cpp @@ -0,0 +1,507 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#define LOG_TAG "AudioEngineImpl" + +#include "audio/android/AudioEngine-inl.h" + +#include +// for native asset manager +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include +#include +#include +#endif + +#include +#include +#include + +#include "application/ApplicationManager.h" +#include "audio/include/AudioEngine.h" +#include "base/Log.h" +#include "base/Scheduler.h" +#include "base/UTF8.h" +#include "base/memory/Memory.h" +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include "platform/android/FileUtils-android.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/JniImp.h" +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY +#include "cocos/platform/openharmony/FileUtils-OpenHarmony.h" +#endif + +#include "audio/android/AudioDecoder.h" +#include "audio/android/AudioDecoderProvider.h" +#include "audio/android/AudioPlayerProvider.h" +#include "audio/android/IAudioPlayer.h" +#include "audio/android/ICallerThreadUtils.h" +#include "audio/android/UrlAudioPlayer.h" +#include "audio/android/cutils/log.h" +#include "engine/EngineEvents.h" + +using namespace cc; //NOLINT + +// Audio focus values synchronized with which in cocos/platform/android/java/src/com/cocos/lib/CocosNativeActivity.java +namespace { +AudioEngineImpl *gAudioImpl = nullptr; +int outputSampleRate = 44100; +#if CC_PLATFORM == CC_PLATFORM_ANDROID +int bufferSizeInFrames = 192; +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY +// TODO(hack) : There is currently a bug in the opensles module, +// so openharmony must configure a fixed size, otherwise the callback will be suspended +int bufferSizeInFrames = 2048; +#endif + +void getAudioInfo() { +#if CC_PLATFORM == CC_PLATFORM_ANDROID + JNIEnv * env = JniHelper::getEnv(); + jclass audioSystem = env->FindClass("android/media/AudioSystem"); + jmethodID method = env->GetStaticMethodID(audioSystem, "getPrimaryOutputSamplingRate", "()I"); + outputSampleRate = env->CallStaticIntMethod(audioSystem, method); + method = env->GetStaticMethodID(audioSystem, "getPrimaryOutputFrameCount", "()I"); + bufferSizeInFrames = env->CallStaticIntMethod(audioSystem, method); +#else + // In openharmony, setting to 48K does not cause audio delays + outputSampleRate = 48000; +#endif +} +} // namespace + +class CallerThreadUtils : public ICallerThreadUtils { +public: + void performFunctionInCallerThread(const std::function &func) override { + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func); + }; + + std::thread::id getCallerThreadId() override { + return _tid; + }; + + void setCallerThreadId(std::thread::id tid) { + _tid = tid; + }; + +private: + std::thread::id _tid; +}; + +static CallerThreadUtils gCallerThreadUtils; + +static int fdGetter(const ccstd::string &url, off_t *start, off_t *length) { + int fd = -1; +#if CC_PLATFORM == CC_PLATFORM_ANDROID + if (cc::FileUtilsAndroid::getObbFile() != nullptr) { + int64_t startV; + int64_t lenV; + fd = cc::getObbAssetFileDescriptorJNI(url, &startV, &lenV); + *start = static_cast(startV); + *length = static_cast(lenV); + } + if (fd <= 0) { + auto *asset = AAssetManager_open(cc::FileUtilsAndroid::getAssetManager(), url.c_str(), AASSET_MODE_UNKNOWN); + // open asset as file descriptor + fd = AAsset_openFileDescriptor(asset, start, length); + AAsset_close(asset); + } +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY + FileUtilsOpenHarmony* fileUtils = dynamic_cast(FileUtils::getInstance()); + if(fileUtils) { + RawFileDescriptor descriptor; + fileUtils->getRawFileDescriptor(url, descriptor); + fd = descriptor.fd; + } +#endif + if (fd <= 0) { + ALOGE("Failed to open file descriptor for '%s'", url.c_str()); + } + + return fd; +}; + +//==================================================== +AudioEngineImpl::AudioEngineImpl() +: _engineObject(nullptr), + _engineEngine(nullptr), + _outputMixObject(nullptr), + _audioPlayerProvider(nullptr), + _audioIDIndex(0), + _lazyInitLoop(true) { + gCallerThreadUtils.setCallerThreadId(std::this_thread::get_id()); + gAudioImpl = this; + getAudioInfo(); +} + +AudioEngineImpl::~AudioEngineImpl() { + if (_audioPlayerProvider != nullptr) { + delete _audioPlayerProvider; + _audioPlayerProvider = nullptr; + } + + if (_outputMixObject) { + (*_outputMixObject)->Destroy(_outputMixObject); + } + if (_engineObject) { + (*_engineObject)->Destroy(_engineObject); + } + + gAudioImpl = nullptr; +} + +bool AudioEngineImpl::init() { + bool ret = false; + do { + // create engine + auto result = slCreateEngine(&_engineObject, 0, nullptr, 0, nullptr, nullptr); + if (SL_RESULT_SUCCESS != result) { + CC_LOG_ERROR("create opensl engine fail"); + break; + } + + // realize the engine + result = (*_engineObject)->Realize(_engineObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + CC_LOG_ERROR("realize the engine fail"); + break; + } + + // get the engine interface, which is needed in order to create other objects + result = (*_engineObject)->GetInterface(_engineObject, SL_IID_ENGINE, &_engineEngine); + if (SL_RESULT_SUCCESS != result) { + CC_LOG_ERROR("get the engine interface fail"); + break; + } + + // create output mix + const SLInterfaceID outputMixIIDs[] = {}; + const SLboolean outputMixReqs[] = {}; + result = (*_engineEngine)->CreateOutputMix(_engineEngine, &_outputMixObject, 0, outputMixIIDs, outputMixReqs); + if (SL_RESULT_SUCCESS != result) { + CC_LOG_ERROR("create output mix fail"); + break; + } + + // realize the output mix + result = (*_outputMixObject)->Realize(_outputMixObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + CC_LOG_ERROR("realize the output mix fail"); + break; + } + + _audioPlayerProvider = ccnew AudioPlayerProvider(_engineEngine, _outputMixObject, outputSampleRate, bufferSizeInFrames, fdGetter, &gCallerThreadUtils); + + ret = true; + } while (false); + + return ret; +} + +void AudioEngineImpl::setAudioFocusForAllPlayers(bool isFocus) { + for (const auto &e : _audioPlayers) { + e.second->setAudioFocus(isFocus); + } +} + +int AudioEngineImpl::play2d(const ccstd::string &filePath, bool loop, float volume) { + ALOGV("play2d, _audioPlayers.size=%d", (int)_audioPlayers.size()); + auto audioId = AudioEngine::INVALID_AUDIO_ID; + + do { + if (_engineEngine == nullptr || _audioPlayerProvider == nullptr) { + break; + } + + auto fullPath = FileUtils::getInstance()->fullPathForFilename(filePath); + + audioId = _audioIDIndex++; + + auto *player = _audioPlayerProvider->getAudioPlayer(fullPath); + if (player != nullptr) { + player->setId(audioId); + _audioPlayers.insert(std::make_pair(audioId, player)); + + player->setPlayEventCallback([this, player, filePath](IAudioPlayer::State state) { + if (state != IAudioPlayer::State::OVER && state != IAudioPlayer::State::STOPPED) { + ALOGV("Ignore state: %d", static_cast(state)); + return; + } + + int id = player->getId(); + + ALOGV("Removing player id=%d, state:%d", id, (int)state); + + AudioEngine::remove(id); + if (_audioPlayers.find(id) != _audioPlayers.end()) { + _audioPlayers.erase(id); + } + if (_urlAudioPlayersNeedResume.find(id) != _urlAudioPlayersNeedResume.end()) { + _urlAudioPlayersNeedResume.erase(id); + } + + auto iter = _callbackMap.find(id); + if (iter != _callbackMap.end()) { + if (state == IAudioPlayer::State::OVER) { + iter->second(id, filePath); + } + _callbackMap.erase(iter); + } + }); + + player->setLoop(loop); + player->setVolume(volume); + player->play(); + } else { + ALOGE("Oops, player is null ..."); + return AudioEngine::INVALID_AUDIO_ID; + } + + AudioEngine::sAudioIDInfoMap[audioId].state = AudioEngine::AudioState::PLAYING; + + } while (false); + + return audioId; +} + +void AudioEngineImpl::setVolume(int audioID, float volume) { + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + player->setVolume(volume); + } +} + +void AudioEngineImpl::setLoop(int audioID, bool loop) { + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + player->setLoop(loop); + } +} + +void AudioEngineImpl::pause(int audioID) { + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + player->pause(); + } +} + +void AudioEngineImpl::resume(int audioID) { + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + player->resume(); + } +} + +void AudioEngineImpl::stop(int audioID) { + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + player->stop(); + } +} + +void AudioEngineImpl::stopAll() { + if (_audioPlayers.empty()) { + return; + } + + // Create a temporary vector for storing all players since + // p->stop() will trigger _audioPlayers.erase, + // and it will cause a crash as it's already in for loop + ccstd::vector players; + players.reserve(_audioPlayers.size()); + + for (const auto &e : _audioPlayers) { + players.push_back(e.second); + } + + for (auto *p : players) { + p->stop(); + } +} + +float AudioEngineImpl::getDuration(int audioID) { + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + return player->getDuration(); + } + return 0.0F; +} + +float AudioEngineImpl::getDurationFromFile(const ccstd::string &filePath) { + if (_audioPlayerProvider != nullptr) { + auto fullPath = FileUtils::getInstance()->fullPathForFilename(filePath); + return _audioPlayerProvider->getDurationFromFile(fullPath); + } + return 0; +} + +float AudioEngineImpl::getCurrentTime(int audioID) { + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + return player->getPosition(); + } + return 0.0F; +} + +bool AudioEngineImpl::setCurrentTime(int audioID, float time) { + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + auto *player = iter->second; + return player->setPosition(time); + } + return false; +} + +void AudioEngineImpl::setFinishCallback(int audioID, const std::function &callback) { + _callbackMap[audioID] = callback; +} + +void AudioEngineImpl::preload(const ccstd::string &filePath, const std::function &callback) { + if (_audioPlayerProvider != nullptr) { + ccstd::string fullPath = FileUtils::getInstance()->fullPathForFilename(filePath); + _audioPlayerProvider->preloadEffect(fullPath, [callback](bool succeed, const PcmData & /*data*/) { + if (callback != nullptr) { + callback(succeed); + } + }); + } else { + if (callback != nullptr) { + callback(false); + } + } +} + +void AudioEngineImpl::uncache(const ccstd::string &filePath) { + if (_audioPlayerProvider != nullptr) { + ccstd::string fullPath = FileUtils::getInstance()->fullPathForFilename(filePath); + _audioPlayerProvider->clearPcmCache(fullPath); + } +} + +void AudioEngineImpl::uncacheAll() { + if (_audioPlayerProvider != nullptr) { + _audioPlayerProvider->clearAllPcmCaches(); + } +} + +void AudioEngineImpl::onPause() { + if (_audioPlayerProvider != nullptr) { + _audioPlayerProvider->pause(); + } +} + +void AudioEngineImpl::onResume() { + if (_audioPlayerProvider != nullptr) { + _audioPlayerProvider->resume(); + } +} + +PCMHeader AudioEngineImpl::getPCMHeader(const char *url) { + PCMHeader header{}; + ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url); + if (fileFullPath.empty()) { + CC_LOG_DEBUG("file %s does not exist or failed to load", url); + return header; + } + if (_audioPlayerProvider->getPcmHeader(url, header)) { + CC_LOG_DEBUG("file %s pcm data already cached", url); + return header; + } + + AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineEngine, fileFullPath, bufferSizeInFrames, outputSampleRate, fdGetter); + + if (decoder == nullptr) { + CC_LOG_DEBUG("decode %s failed, the file formate might not support", url); + return header; + } + if (!decoder->start()) { + CC_LOG_DEBUG("[Audio Decoder] Decode failed %s", url); + return header; + } + // Ready to decode + do { + PcmData data = decoder->getResult(); + header.bytesPerFrame = data.bitsPerSample / 8; + header.channelCount = data.numChannels; + header.dataFormat = AudioDataFormat::SIGNED_16; + header.sampleRate = data.sampleRate; + header.totalFrames = data.numFrames; + } while (false); + + AudioDecoderProvider::destroyAudioDecoder(&decoder); + return header; +} + +ccstd::vector AudioEngineImpl::getOriginalPCMBuffer(const char *url, uint32_t channelID) { + ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url); + ccstd::vector pcmData; + if (fileFullPath.empty()) { + CC_LOG_DEBUG("file %s does not exist or failed to load", url); + return pcmData; + } + PcmData data; + if (_audioPlayerProvider->getPcmData(url, data)) { + CC_LOG_DEBUG("file %s pcm data already cached", url); + } else { + AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineEngine, fileFullPath, bufferSizeInFrames, outputSampleRate, fdGetter); + if (decoder == nullptr) { + CC_LOG_DEBUG("decode %s failed, the file formate might not support", url); + return pcmData; + } + if (!decoder->start()) { + CC_LOG_DEBUG("[Audio Decoder] Decode failed %s", url); + return pcmData; + } + data = decoder->getResult(); + _audioPlayerProvider->registerPcmData(url, data); + AudioDecoderProvider::destroyAudioDecoder(&decoder); + } + do { + const uint32_t channelCount = data.numChannels; + if (channelID >= channelCount) { + CC_LOG_ERROR("channelID invalid, total channel count is %d but %d is required", channelCount, channelID); + break; + } + // bytesPerSample = bitsPerSample / 8, according to 1 byte = 8 bits + const uint32_t bytesPerFrame = data.numChannels * data.bitsPerSample / 8; + const uint32_t numFrames = data.numFrames; + const uint32_t bytesPerChannelInFrame = bytesPerFrame / channelCount; + + pcmData.resize(bytesPerChannelInFrame * numFrames); + uint8_t *p = pcmData.data(); + char *tmpBuf = data.pcmBuffer->data(); // shared ptr + for (int itr = 0; itr < numFrames; itr++) { + memcpy(p, tmpBuf + itr * bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame); + p += bytesPerChannelInFrame; + } + } while (false); + + return pcmData; +} diff --git a/cocos/audio/android/AudioEngine-inl.h b/cocos/audio/android/AudioEngine-inl.h new file mode 100644 index 0000000..1af9ca6 --- /dev/null +++ b/cocos/audio/android/AudioEngine-inl.h @@ -0,0 +1,105 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY +#include +#endif +#include +#include "audio/include/AudioDef.h" +#include "base/RefCounted.h" +#include "base/Utils.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#define MAX_AUDIOINSTANCES 13 + +#define ERRORLOG(msg) log("fun:%s,line:%d,msg:%s", __func__, __LINE__, #msg) + +namespace cc { + +struct CustomEvent; + +class IAudioPlayer; +class AudioPlayerProvider; + +class AudioEngineImpl; + +class AudioEngineImpl : public RefCounted { +public: + AudioEngineImpl(); + ~AudioEngineImpl() override; + + bool init(); + int play2d(const ccstd::string &filePath, bool loop, float volume); + void setVolume(int audioID, float volume); + void setLoop(int audioID, bool loop); + void pause(int audioID); + void resume(int audioID); + void stop(int audioID); + void stopAll(); + float getDuration(int audioID); + float getDurationFromFile(const ccstd::string &filePath); + float getCurrentTime(int audioID); + bool setCurrentTime(int audioID, float time); + void setFinishCallback(int audioID, const std::function &callback); + + void uncache(const ccstd::string &filePath); + void uncacheAll(); + void preload(const ccstd::string &filePath, const std::function &callback); + + void onResume(); + void onPause(); + + void setAudioFocusForAllPlayers(bool isFocus); + + PCMHeader getPCMHeader(const char *url); + std::vector getOriginalPCMBuffer(const char *url, uint32_t channelID); + +private: + // engine interfaces + SLObjectItf _engineObject; + SLEngineItf _engineEngine; + + // output mix interfaces + SLObjectItf _outputMixObject; + + //audioID,AudioInfo + ccstd::unordered_map _audioPlayers; + ccstd::unordered_map> _callbackMap; + + // UrlAudioPlayers which need to resumed while entering foreground + ccstd::unordered_map _urlAudioPlayersNeedResume; + + AudioPlayerProvider *_audioPlayerProvider; + + int _audioIDIndex; + + bool _lazyInitLoop; +}; + +} // namespace cc diff --git a/cocos/audio/android/AudioMixer.cpp b/cocos/audio/android/AudioMixer.cpp new file mode 100644 index 0000000..b4d888f --- /dev/null +++ b/cocos/audio/android/AudioMixer.cpp @@ -0,0 +1,2053 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +// clang-format off +#define LOG_TAG "AudioMixer" +#define LOG_NDEBUG 1 + +#include +#include +#include +#include +#include + +#include "audio/android/audio.h" +#include "audio/common/utils/include/primitives.h" +#include "audio/android/AudioMixerOps.h" +#include "audio/android/AudioMixer.h" +#include "base/memory/Memory.h" + +// clang-format on +// The FCC_2 macro refers to the Fixed Channel Count of 2 for the legacy integer mixer. +#ifndef FCC_2 + #define FCC_2 2 +#endif + +// Look for MONO_HACK for any Mono hack involving legacy mono channel to +// stereo channel conversion. + +/* VERY_VERY_VERBOSE_LOGGING will show exactly which process hook and track hook is + * being used. This is a considerable amount of log spam, so don't enable unless you + * are verifying the hook based code. + */ +//#define VERY_VERY_VERBOSE_LOGGING +#ifdef VERY_VERY_VERBOSE_LOGGING + #define ALOGVV ALOGV +//define ALOGVV printf // for test-mixer.cpp +#else + #define ALOGVV(a...) \ + do { \ + } while (0) +#endif + +#ifndef ARRAY_SIZE + #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +// REFINE: Move these macro/inlines to a header file. +template +static inline T max(const T &x, const T &y) { + return x > y ? x : y; +} + +// Set kUseNewMixer to true to use the new mixer engine always. Otherwise the +// original code will be used for stereo sinks, the new mixer for multichannel. +static const bool kUseNewMixer = false; //NOLINT + +// Set kUseFloat to true to allow floating input into the mixer engine. +// If kUseNewMixer is false, this is ignored or may be overridden internally +// because of downmix/upmix support. +static const bool kUseFloat = false; //NOLINT + +// Set to default copy buffer size in frames for input processing. +static const size_t kCopyBufferFrameCount = 256; //NOLINT + +namespace cc { + +// ---------------------------------------------------------------------------- + +template +T min(const T &a, const T &b) { + return a < b ? a : b; +} + +// ---------------------------------------------------------------------------- + +// Ensure mConfiguredNames bitmask is initialized properly on all architectures. +// The value of 1 << x is undefined in C when x >= 32. + +AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate, uint32_t maxNumTracks) +: mTrackNames(0), mConfiguredNames((maxNumTracks >= 32 ? 0 : 1 << maxNumTracks) - 1), mSampleRate(sampleRate) { + ALOGVV("AudioMixer constructed, frameCount: %d, sampleRate: %d", (int)frameCount, (int)sampleRate); + ALOG_ASSERT(maxNumTracks <= MAX_NUM_TRACKS, "maxNumTracks %u > MAX_NUM_TRACKS %u", + maxNumTracks, MAX_NUM_TRACKS); + + // AudioMixer is not yet capable of more than 32 active track inputs + ALOG_ASSERT(32 >= MAX_NUM_TRACKS, "bad MAX_NUM_TRACKS %d", MAX_NUM_TRACKS); + + pthread_once(&sOnceControl, &sInitRoutine); + + mState.enabledTracks = 0; + mState.needsChanged = 0; + mState.frameCount = frameCount; + mState.hook = process__nop; + mState.outputTemp = nullptr; + mState.resampleTemp = nullptr; + //cjh mState.mLog = &mDummyLog; + // mState.reserved + + // IDEA: Most of the following initialization is probably redundant since + // tracks[i] should only be referenced if (mTrackNames & (1 << i)) != 0 + // and mTrackNames is initially 0. However, leave it here until that's verified. + track_t *t = mState.tracks; + for (unsigned i = 0; i < MAX_NUM_TRACKS; i++) { + t->resampler = nullptr; + //cjh t->downmixerBufferProvider = nullptr; + // t->mReformatBufferProvider = nullptr; + // t->mTimestretchBufferProvider = nullptr; + t++; + } +} + +AudioMixer::~AudioMixer() { + track_t *t = mState.tracks; + for (unsigned i = 0; i < MAX_NUM_TRACKS; i++) { + delete t->resampler; + //cjh delete t->downmixerBufferProvider; + // delete t->mReformatBufferProvider; + // delete t->mTimestretchBufferProvider; + t++; + } + delete[] mState.outputTemp; + delete[] mState.resampleTemp; +} + +//cjh void AudioMixer::setLog(NBLog::Writer *log) +//{ +// mState.mLog = log; +//} + +static inline audio_format_t selectMixerInFormat(audio_format_t inputFormat __unused) { + return kUseFloat && kUseNewMixer ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT; +} + +int AudioMixer::getTrackName(audio_channel_mask_t channelMask, + audio_format_t format, int sessionId) { + if (!isValidPcmTrackFormat(format)) { + ALOGE("AudioMixer::getTrackName invalid format (%#x)", format); + return -1; + } + uint32_t names = (~mTrackNames) & mConfiguredNames; + if (names != 0) { + int n = __builtin_ctz(names); + ALOGV("add track (%d)", n); + // assume default parameters for the track, except where noted below + track_t *t = &mState.tracks[n]; + t->needs = 0; + + // Integer volume. + // Currently integer volume is kept for the legacy integer mixer. + // Will be removed when the legacy mixer path is removed. + t->volume[0] = UNITY_GAIN_INT; + t->volume[1] = UNITY_GAIN_INT; + t->prevVolume[0] = UNITY_GAIN_INT << 16; + t->prevVolume[1] = UNITY_GAIN_INT << 16; + t->volumeInc[0] = 0; + t->volumeInc[1] = 0; + t->auxLevel = 0; + t->auxInc = 0; + t->prevAuxLevel = 0; + + // Floating point volume. + t->mVolume[0] = UNITY_GAIN_FLOAT; + t->mVolume[1] = UNITY_GAIN_FLOAT; + t->mPrevVolume[0] = UNITY_GAIN_FLOAT; + t->mPrevVolume[1] = UNITY_GAIN_FLOAT; + t->mVolumeInc[0] = 0.; + t->mVolumeInc[1] = 0.; + t->mAuxLevel = 0.; + t->mAuxInc = 0.; + t->mPrevAuxLevel = 0.; + + // no initialization needed + // t->frameCount + t->channelCount = audio_channel_count_from_out_mask(channelMask); + t->enabled = false; + ALOGV_IF(audio_channel_mask_get_bits(channelMask) != AUDIO_CHANNEL_OUT_STEREO, + "Non-stereo channel mask: %d\n", channelMask); + t->channelMask = channelMask; + t->sessionId = sessionId; + // setBufferProvider(name, AudioBufferProvider *) is required before enable(name) + t->bufferProvider = nullptr; + t->buffer.raw = nullptr; + // no initialization needed + // t->buffer.frameCount + t->hook = nullptr; + t->in = nullptr; + t->resampler = nullptr; + t->sampleRate = mSampleRate; + // setParameter(name, TRACK, MAIN_BUFFER, mixBuffer) is required before enable(name) + t->mainBuffer = nullptr; + t->auxBuffer = nullptr; + t->mInputBufferProvider = nullptr; + //cjh t->mReformatBufferProvider = nullptr; + // t->downmixerBufferProvider = nullptr; + // t->mPostDownmixReformatBufferProvider = nullptr; + // t->mTimestretchBufferProvider = nullptr; + t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT; + t->mFormat = format; + t->mMixerInFormat = selectMixerInFormat(format); + t->mDownmixRequiresFormat = AUDIO_FORMAT_INVALID; // no format required + t->mMixerChannelMask = audio_channel_mask_from_representation_and_bits( + AUDIO_CHANNEL_REPRESENTATION_POSITION, AUDIO_CHANNEL_OUT_STEREO); + t->mMixerChannelCount = audio_channel_count_from_out_mask(t->mMixerChannelMask); + ALOGVV("t->mMixerChannelCount: %d", t->mMixerChannelCount); + t->mPlaybackRate = AUDIO_PLAYBACK_RATE_DEFAULT; + // Check the downmixing (or upmixing) requirements. + status_t status = t->prepareForDownmix(); + if (status != OK) { + ALOGE("AudioMixer::getTrackName invalid channelMask (%#x)", channelMask); + return -1; + } + // prepareForDownmix() may change mDownmixRequiresFormat + ALOGVV("mMixerFormat:%#x mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat); + t->prepareForReformat(); + mTrackNames |= 1 << n; + ALOGVV("getTrackName return: %d", TRACK0 + n); + return TRACK0 + n; + } + ALOGE("AudioMixer::getTrackName out of available tracks"); + return -1; +} + +void AudioMixer::invalidateState(uint32_t mask) { + if (mask != 0) { + mState.needsChanged |= mask; + mState.hook = process__validate; + } +} + +// Called when channel masks have changed for a track name +// REFINE: Fix DownmixerBufferProvider not to (possibly) change mixer input format, +// which will simplify this logic. +bool AudioMixer::setChannelMasks(int name, + audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask) { + track_t &track = mState.tracks[name]; + ALOGVV("AudioMixer::setChannelMask ..."); + if (trackChannelMask == track.channelMask && mixerChannelMask == track.mMixerChannelMask) { + ALOGVV("No need to change channel mask ..."); + return false; // no need to change + } + // always recompute for both channel masks even if only one has changed. + const uint32_t trackChannelCount = audio_channel_count_from_out_mask(trackChannelMask); + const uint32_t mixerChannelCount = audio_channel_count_from_out_mask(mixerChannelMask); + const bool mixerChannelCountChanged = track.mMixerChannelCount != mixerChannelCount; + + ALOG_ASSERT((trackChannelCount <= MAX_NUM_CHANNELS_TO_DOWNMIX) && trackChannelCount && mixerChannelCount); + track.channelMask = trackChannelMask; + track.channelCount = trackChannelCount; + track.mMixerChannelMask = mixerChannelMask; + track.mMixerChannelCount = mixerChannelCount; + + // channel masks have changed, does this track need a downmixer? + // update to try using our desired format (if we aren't already using it) + const audio_format_t prevDownmixerFormat = track.mDownmixRequiresFormat; + const status_t status = mState.tracks[name].prepareForDownmix(); + ALOGE_IF(status != OK, + "prepareForDownmix error %d, track channel mask %#x, mixer channel mask %#x", + status, track.channelMask, track.mMixerChannelMask); + + if (prevDownmixerFormat != track.mDownmixRequiresFormat) { + track.prepareForReformat(); // because of downmixer, track format may change! + } + + if (track.resampler && mixerChannelCountChanged) { + // resampler channels may have changed. + const uint32_t resetToSampleRate = track.sampleRate; + delete track.resampler; + track.resampler = nullptr; + track.sampleRate = mSampleRate; // without resampler, track rate is device sample rate. + // recreate the resampler with updated format, channels, saved sampleRate. + track.setResampler(resetToSampleRate /*trackSampleRate*/, mSampleRate /*devSampleRate*/); + } + return true; +} + +void AudioMixer::track_t::unprepareForDownmix() { + ALOGV("AudioMixer::unprepareForDownmix(%p)", this); + + mDownmixRequiresFormat = AUDIO_FORMAT_INVALID; + //cjh if (downmixerBufferProvider != nullptr) { + // // this track had previously been configured with a downmixer, delete it + // ALOGV(" deleting old downmixer"); + // delete downmixerBufferProvider; + // downmixerBufferProvider = nullptr; + // reconfigureBufferProviders(); + // } else + { + ALOGV(" nothing to do, no downmixer to delete"); + } +} + +status_t AudioMixer::track_t::prepareForDownmix() { + ALOGV("AudioMixer::prepareForDownmix(%p) with mask 0x%x", + this, channelMask); + + // discard the previous downmixer if there was one + unprepareForDownmix(); + // MONO_HACK Only remix (upmix or downmix) if the track and mixer/device channel masks + // are not the same and not handled internally, as mono -> stereo currently is. + if (channelMask == mMixerChannelMask || (channelMask == AUDIO_CHANNEL_OUT_MONO && mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO)) { + return NO_ERROR; + } + // DownmixerBufferProvider is only used for position masks. + //cjh if (audio_channel_mask_get_representation(channelMask) + // == AUDIO_CHANNEL_REPRESENTATION_POSITION + // && DownmixerBufferProvider::isMultichannelCapable()) { + // DownmixerBufferProvider* pDbp = ccnew DownmixerBufferProvider(channelMask, + // mMixerChannelMask, + // AUDIO_FORMAT_PCM_16_BIT /* REFINE: use mMixerInFormat, now only PCM 16 */, + // sampleRate, sessionId, kCopyBufferFrameCount); + // + // if (pDbp->isValid()) { // if constructor completed properly + // mDownmixRequiresFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix + // downmixerBufferProvider = pDbp; + // reconfigureBufferProviders(); + // return NO_ERROR; + // } + // delete pDbp; + // } + // + // // Effect downmixer does not accept the channel conversion. Let's use our remixer. + // RemixBufferProvider* pRbp = ccnew RemixBufferProvider(channelMask, + // mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount); + // // Remix always finds a conversion whereas Downmixer effect above may fail. + // downmixerBufferProvider = pRbp; + // reconfigureBufferProviders(); + return NO_ERROR; +} + +void AudioMixer::track_t::unprepareForReformat() { + ALOGV("AudioMixer::unprepareForReformat(%p)", this); + bool requiresReconfigure = false; + //cjh if (mReformatBufferProvider != nullptr) { + // delete mReformatBufferProvider; + // mReformatBufferProvider = nullptr; + // requiresReconfigure = true; + // } + // if (mPostDownmixReformatBufferProvider != nullptr) { + // delete mPostDownmixReformatBufferProvider; + // mPostDownmixReformatBufferProvider = nullptr; + // requiresReconfigure = true; + // } + if (requiresReconfigure) { + reconfigureBufferProviders(); + } +} + +status_t AudioMixer::track_t::prepareForReformat() { + ALOGV("AudioMixer::prepareForReformat(%p) with format %#x", this, mFormat); + // discard previous reformatters + unprepareForReformat(); + // only configure reformatters as needed + const audio_format_t targetFormat = mDownmixRequiresFormat != AUDIO_FORMAT_INVALID + ? mDownmixRequiresFormat + : mMixerInFormat; + bool requiresReconfigure = false; + //cjh if (mFormat != targetFormat) { + // mReformatBufferProvider = ccnew ReformatBufferProvider( + // audio_channel_count_from_out_mask(channelMask), + // mFormat, + // targetFormat, + // kCopyBufferFrameCount); + // requiresReconfigure = true; + // } + // if (targetFormat != mMixerInFormat) { + // mPostDownmixReformatBufferProvider = ccnew ReformatBufferProvider( + // audio_channel_count_from_out_mask(mMixerChannelMask), + // targetFormat, + // mMixerInFormat, + // kCopyBufferFrameCount); + // requiresReconfigure = true; + // } + if (requiresReconfigure) { + reconfigureBufferProviders(); + } + ALOGVV("prepareForReformat return ..."); + return NO_ERROR; +} + +void AudioMixer::track_t::reconfigureBufferProviders() { + bufferProvider = mInputBufferProvider; + //cjh if (mReformatBufferProvider) { + // mReformatBufferProvider->setBufferProvider(bufferProvider); + // bufferProvider = mReformatBufferProvider; + // } + // if (downmixerBufferProvider) { + // downmixerBufferProvider->setBufferProvider(bufferProvider); + // bufferProvider = downmixerBufferProvider; + // } + // if (mPostDownmixReformatBufferProvider) { + // mPostDownmixReformatBufferProvider->setBufferProvider(bufferProvider); + // bufferProvider = mPostDownmixReformatBufferProvider; + // } + // if (mTimestretchBufferProvider) { + // mTimestretchBufferProvider->setBufferProvider(bufferProvider); + // bufferProvider = mTimestretchBufferProvider; + // } +} + +void AudioMixer::deleteTrackName(int name) { + ALOGV("AudioMixer::deleteTrackName(%d)", name); + name -= TRACK0; + ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + ALOGV("deleteTrackName(%d)", name); + track_t &track(mState.tracks[name]); + if (track.enabled) { + track.enabled = false; + invalidateState(1 << name); + } + // delete the resampler + delete track.resampler; + track.resampler = nullptr; + // delete the downmixer + mState.tracks[name].unprepareForDownmix(); + // delete the reformatter + mState.tracks[name].unprepareForReformat(); + // delete the timestretch provider + //cjh delete track.mTimestretchBufferProvider; + // track.mTimestretchBufferProvider = nullptr; + mTrackNames &= ~(1 << name); +} + +void AudioMixer::enable(int name) { + name -= TRACK0; + ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + track_t &track = mState.tracks[name]; + + if (!track.enabled) { + track.enabled = true; + ALOGV("enable(%d)", name); + invalidateState(1 << name); + } +} + +void AudioMixer::disable(int name) { + name -= TRACK0; + ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + track_t &track = mState.tracks[name]; + + if (track.enabled) { + track.enabled = false; + ALOGV("disable(%d)", name); + invalidateState(1 << name); + } +} + +/* Sets the volume ramp variables for the AudioMixer. + * + * The volume ramp variables are used to transition from the previous + * volume to the set volume. ramp controls the duration of the transition. + * Its value is typically one state framecount period, but may also be 0, + * meaning "immediate." + * + * IDEA: 1) Volume ramp is enabled only if there is a nonzero integer increment + * even if there is a nonzero floating point increment (in that case, the volume + * change is immediate). This restriction should be changed when the legacy mixer + * is removed (see #2). + * IDEA: 2) Integer volume variables are used for Legacy mixing and should be removed + * when no longer needed. + * + * @param newVolume set volume target in floating point [0.0, 1.0]. + * @param ramp number of frames to increment over. if ramp is 0, the volume + * should be set immediately. Currently ramp should not exceed 65535 (frames). + * @param pIntSetVolume pointer to the U4.12 integer target volume, set on return. + * @param pIntPrevVolume pointer to the U4.28 integer previous volume, set on return. + * @param pIntVolumeInc pointer to the U4.28 increment per output audio frame, set on return. + * @param pSetVolume pointer to the float target volume, set on return. + * @param pPrevVolume pointer to the float previous volume, set on return. + * @param pVolumeInc pointer to the float increment per output audio frame, set on return. + * @return true if the volume has changed, false if volume is same. + */ +static inline bool setVolumeRampVariables(float newVolume, int32_t ramp, + int16_t *pIntSetVolume, int32_t *pIntPrevVolume, int32_t *pIntVolumeInc, + float *pSetVolume, float *pPrevVolume, float *pVolumeInc) { + // check floating point volume to see if it is identical to the previously + // set volume. + // We do not use a tolerance here (and reject changes too small) + // as it may be confusing to use a different value than the one set. + // If the resulting volume is too small to ramp, it is a direct set of the volume. + if (newVolume == *pSetVolume) { + return false; + } + if (newVolume < 0) { + newVolume = 0; // should not have negative volumes + } else { + switch (fpclassify(newVolume)) { + case FP_SUBNORMAL: + case FP_NAN: + newVolume = 0; + break; + case FP_ZERO: + break; // zero volume is fine + case FP_INFINITE: + // Infinite volume could be handled consistently since + // floating point math saturates at infinities, + // but we limit volume to unity gain float. + // ramp = 0; break; + // + newVolume = AudioMixer::UNITY_GAIN_FLOAT; + break; + case FP_NORMAL: + default: + // Floating point does not have problems with overflow wrap + // that integer has. However, we limit the volume to + // unity gain here. + // REFINE: Revisit the volume limitation and perhaps parameterize. + if (newVolume > AudioMixer::UNITY_GAIN_FLOAT) { + newVolume = AudioMixer::UNITY_GAIN_FLOAT; + } + break; + } + } + + // set floating point volume ramp + if (ramp != 0) { + // when the ramp completes, *pPrevVolume is set to *pSetVolume, so there + // is no computational mismatch; hence equality is checked here. + ALOGD_IF(*pPrevVolume != *pSetVolume, + "previous float ramp hasn't finished," + " prev:%f set_to:%f", + *pPrevVolume, *pSetVolume); + const float inc = (newVolume - *pPrevVolume) / ramp; // could be inf, nan, subnormal + const float maxv = max(newVolume, *pPrevVolume); // could be inf, cannot be nan, subnormal + + if (isnormal(inc) // inc must be a normal number (no subnormals, infinite, nan) + && maxv + inc != maxv) { // inc must make forward progress + *pVolumeInc = inc; + // ramp is set now. + // Note: if newVolume is 0, then near the end of the ramp, + // it may be possible that the ramped volume may be subnormal or + // temporarily negative by a small amount or subnormal due to floating + // point inaccuracies. + } else { + ramp = 0; // ramp not allowed + } + } + + // compute and check integer volume, no need to check negative values + // The integer volume is limited to "unity_gain" to avoid wrapping and other + // audio artifacts, so it never reaches the range limit of U4.28. + // We safely use signed 16 and 32 bit integers here. + const float scaledVolume = newVolume * AudioMixer::UNITY_GAIN_INT; // not neg, subnormal, nan + const int32_t intVolume = (scaledVolume >= static_cast(AudioMixer::UNITY_GAIN_INT)) ? AudioMixer::UNITY_GAIN_INT : static_cast(scaledVolume); + + // set integer volume ramp + if (ramp != 0) { + // integer volume is U4.12 (to use 16 bit multiplies), but ramping uses U4.28. + // when the ramp completes, *pIntPrevVolume is set to *pIntSetVolume << 16, so there + // is no computational mismatch; hence equality is checked here. + ALOGD_IF(*pIntPrevVolume != *pIntSetVolume << 16, + "previous int ramp hasn't finished," + " prev:%d set_to:%d", + *pIntPrevVolume, *pIntSetVolume << 16); + const int32_t inc = ((intVolume << 16) - *pIntPrevVolume) / ramp; + + if (inc != 0) { // inc must make forward progress + *pIntVolumeInc = inc; + } else { + ramp = 0; // ramp not allowed + } + } + + // if no ramp, or ramp not allowed, then clear float and integer increments + if (ramp == 0) { + *pVolumeInc = 0; + *pPrevVolume = newVolume; + *pIntVolumeInc = 0; + *pIntPrevVolume = intVolume << 16; + } + *pSetVolume = newVolume; + *pIntSetVolume = intVolume; + return true; +} + +void AudioMixer::setParameter(int name, int target, int param, void *value) { + name -= TRACK0; + ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + track_t &track = mState.tracks[name]; + + int valueInt = static_cast(reinterpret_cast(value)); + auto *valueBuf = reinterpret_cast(value); + + switch (target) { + case TRACK: + switch (param) { + case CHANNEL_MASK: { + const auto trackChannelMask = + static_cast(valueInt); + if (setChannelMasks(name, trackChannelMask, track.mMixerChannelMask)) { + ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", trackChannelMask); + invalidateState(1 << name); + } + } break; + case MAIN_BUFFER: + if (track.mainBuffer != valueBuf) { + track.mainBuffer = valueBuf; + ALOGV("setParameter(TRACK, MAIN_BUFFER, %p)", valueBuf); + invalidateState(1 << name); + } + break; + case AUX_BUFFER: + if (track.auxBuffer != valueBuf) { + track.auxBuffer = valueBuf; + ALOGV("setParameter(TRACK, AUX_BUFFER, %p)", valueBuf); + invalidateState(1 << name); + } + break; + case FORMAT: { + auto format = static_cast(valueInt); + if (track.mFormat != format) { + ALOG_ASSERT(audio_is_linear_pcm(format), "Invalid format %#x", format); + track.mFormat = format; + ALOGV("setParameter(TRACK, FORMAT, %#x)", format); + track.prepareForReformat(); + invalidateState(1 << name); + } + } break; + // IDEA: do we want to support setting the downmix type from AudioMixerController? + // for a specific track? or per mixer? + /* case DOWNMIX_TYPE: + break */ + case MIXER_FORMAT: { + auto format = static_cast(valueInt); + if (track.mMixerFormat != format) { + track.mMixerFormat = format; + ALOGV("setParameter(TRACK, MIXER_FORMAT, %#x)", format); + } + } break; + case MIXER_CHANNEL_MASK: { + const auto mixerChannelMask = + static_cast(valueInt); + if (setChannelMasks(name, track.channelMask, mixerChannelMask)) { + ALOGV("setParameter(TRACK, MIXER_CHANNEL_MASK, %#x)", mixerChannelMask); + invalidateState(1 << name); + } + } break; + default: + LOG_ALWAYS_FATAL("setParameter track: bad param %d", param); + } + break; + + case RESAMPLE: + switch (param) { + case SAMPLE_RATE: + ALOG_ASSERT(valueInt > 0, "bad sample rate %d", valueInt); + if (track.setResampler(static_cast(valueInt), mSampleRate)) { + ALOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)", + uint32_t(valueInt)); + invalidateState(1 << name); + } + break; + case RESET: + track.resetResampler(); + invalidateState(1 << name); + break; + case REMOVE: + delete track.resampler; + track.resampler = nullptr; + track.sampleRate = mSampleRate; + invalidateState(1 << name); + break; + default: + LOG_ALWAYS_FATAL("setParameter resample: bad param %d", param); + } + break; + + case RAMP_VOLUME: + case VOLUME: + switch (param) { + case AUXLEVEL: + if (setVolumeRampVariables(*reinterpret_cast(value), + target == RAMP_VOLUME ? mState.frameCount : 0, + &track.auxLevel, &track.prevAuxLevel, &track.auxInc, + &track.mAuxLevel, &track.mPrevAuxLevel, &track.mAuxInc)) { + ALOGV("setParameter(%s, AUXLEVEL: %04x)", + target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track.auxLevel); + invalidateState(1 << name); + } + break; + default: + if (static_cast(param) >= VOLUME0 && static_cast(param) < VOLUME0 + MAX_NUM_VOLUMES) { + if (setVolumeRampVariables(*reinterpret_cast(value), + target == RAMP_VOLUME ? mState.frameCount : 0, + &track.volume[param - VOLUME0], &track.prevVolume[param - VOLUME0], + &track.volumeInc[param - VOLUME0], + &track.mVolume[param - VOLUME0], &track.mPrevVolume[param - VOLUME0], + &track.mVolumeInc[param - VOLUME0])) { + ALOGV("setParameter(%s, VOLUME%d: %04x)", + target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0, + track.volume[param - VOLUME0]); + invalidateState(1 << name); + } + } else { + LOG_ALWAYS_FATAL("setParameter volume: bad param %d", param); + } + } + break; + case TIMESTRETCH: + switch (param) { + case PLAYBACK_RATE: { + const AudioPlaybackRate *playbackRate = + reinterpret_cast(value); + ALOGW_IF(!isAudioPlaybackRateValid(*playbackRate), + "bad parameters speed %f, pitch %f", playbackRate->mSpeed, + playbackRate->mPitch); + if (track.setPlaybackRate(*playbackRate)) { + ALOGV( + "setParameter(TIMESTRETCH, PLAYBACK_RATE, STRETCH_MODE, FALLBACK_MODE " + "%f %f %d %d", + playbackRate->mSpeed, + playbackRate->mPitch, + playbackRate->mStretchMode, + playbackRate->mFallbackMode); + // invalidateState(1 << name); + } + } break; + default: + LOG_ALWAYS_FATAL("setParameter timestretch: bad param %d", param); + } + break; + + default: + LOG_ALWAYS_FATAL("setParameter: bad target %d", target); + } +} + +bool AudioMixer::track_t::setResampler(uint32_t trackSampleRate, uint32_t devSampleRate) { + if (trackSampleRate != devSampleRate || resampler != nullptr) { + if (sampleRate != trackSampleRate) { + sampleRate = trackSampleRate; + if (resampler == nullptr) { + ALOGV("Creating resampler from track %d Hz to device %d Hz", + trackSampleRate, devSampleRate); + AudioResampler::src_quality quality; + // force lowest quality level resampler if use case isn't music or video + // IDEA: this is flawed for dynamic sample rates, as we choose the resampler + // quality level based on the initial ratio, but that could change later. + // Should have a way to distinguish tracks with static ratios vs. dynamic ratios. + //cjh if (isMusicRate(trackSampleRate)) { + quality = AudioResampler::DEFAULT_QUALITY; + //cjh } else { + // quality = AudioResampler::DYN_LOW_QUALITY; + // } + + // REFINE: Remove MONO_HACK. Resampler sees #channels after the downmixer + // but if none exists, it is the channel count (1 for mono). + const int resamplerChannelCount = false /*downmixerBufferProvider != nullptr*/ + ? static_cast(mMixerChannelCount) + : static_cast(channelCount); + ALOGVV( + "Creating resampler:" + " format(%#x) channels(%d) devSampleRate(%u) quality(%d)\n", + mMixerInFormat, resamplerChannelCount, devSampleRate, quality); + resampler = AudioResampler::create( + mMixerInFormat, + resamplerChannelCount, + devSampleRate, quality); + resampler->setLocalTimeFreq(sLocalTimeFreq); + } + return true; + } + } + return false; +} + +bool AudioMixer::track_t::setPlaybackRate(const AudioPlaybackRate &playbackRate) { + //cjh if ((mTimestretchBufferProvider == nullptr && + // fabs(playbackRate.mSpeed - mPlaybackRate.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA && + // fabs(playbackRate.mPitch - mPlaybackRate.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA) || + // isAudioPlaybackRateEqual(playbackRate, mPlaybackRate)) { + // return false; + // } + mPlaybackRate = playbackRate; + // if (mTimestretchBufferProvider == nullptr) { + // // REFINE: Remove MONO_HACK. Resampler sees #channels after the downmixer + // // but if none exists, it is the channel count (1 for mono). + // const int timestretchChannelCount = downmixerBufferProvider != nullptr + // ? mMixerChannelCount : channelCount; + // mTimestretchBufferProvider = ccnew TimestretchBufferProvider(timestretchChannelCount, + // mMixerInFormat, sampleRate, playbackRate); + // reconfigureBufferProviders(); + // } else { + // reinterpret_cast(mTimestretchBufferProvider) + // ->setPlaybackRate(playbackRate); + // } + return true; +} + +/* Checks to see if the volume ramp has completed and clears the increment + * variables appropriately. + * + * IDEA: There is code to handle int/float ramp variable switchover should it not + * complete within a mixer buffer processing call, but it is preferred to avoid switchover + * due to precision issues. The switchover code is included for legacy code purposes + * and can be removed once the integer volume is removed. + * + * It is not sufficient to clear only the volumeInc integer variable because + * if one channel requires ramping, all channels are ramped. + * + * There is a bit of duplicated code here, but it keeps backward compatibility. + */ +inline void AudioMixer::track_t::adjustVolumeRamp(bool aux, bool useFloat) { + if (useFloat) { + for (uint32_t i = 0; i < MAX_NUM_VOLUMES; i++) { + if ((mVolumeInc[i] > 0 && mPrevVolume[i] + mVolumeInc[i] >= mVolume[i]) || + (mVolumeInc[i] < 0 && mPrevVolume[i] + mVolumeInc[i] <= mVolume[i])) { + volumeInc[i] = 0; + prevVolume[i] = volume[i] << 16; + mVolumeInc[i] = 0.; + mPrevVolume[i] = mVolume[i]; + } else { + //ALOGV("ramp: %f %f %f", mVolume[i], mPrevVolume[i], mVolumeInc[i]); + prevVolume[i] = u4_28_from_float(mPrevVolume[i]); + } + } + } else { + for (uint32_t i = 0; i < MAX_NUM_VOLUMES; i++) { + if (((volumeInc[i] > 0) && (((prevVolume[i] + volumeInc[i]) >> 16) >= volume[i])) || + ((volumeInc[i] < 0) && (((prevVolume[i] + volumeInc[i]) >> 16) <= volume[i]))) { + volumeInc[i] = 0; + prevVolume[i] = volume[i] << 16; + mVolumeInc[i] = 0.; + mPrevVolume[i] = mVolume[i]; + } else { + //ALOGV("ramp: %d %d %d", volume[i] << 16, prevVolume[i], volumeInc[i]); + mPrevVolume[i] = float_from_u4_28(prevVolume[i]); + } + } + } + /* REFINE: aux is always integer regardless of output buffer type */ + if (aux) { + if (((auxInc > 0) && (((prevAuxLevel + auxInc) >> 16) >= auxLevel)) || + ((auxInc < 0) && (((prevAuxLevel + auxInc) >> 16) <= auxLevel))) { + auxInc = 0; + prevAuxLevel = auxLevel << 16; + mAuxInc = 0.; + mPrevAuxLevel = mAuxLevel; + } else { + //ALOGV("aux ramp: %d %d %d", auxLevel << 16, prevAuxLevel, auxInc); + } + } +} + +size_t AudioMixer::getUnreleasedFrames(int name) const { + name -= TRACK0; + if (static_cast(name) < MAX_NUM_TRACKS) { + return mState.tracks[name].getUnreleasedFrames(); + } + return 0; +} + +void AudioMixer::setBufferProvider(int name, AudioBufferProvider *bufferProvider) { + name -= TRACK0; + ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name); + + if (mState.tracks[name].mInputBufferProvider == bufferProvider) { + return; // don't reset any buffer providers if identical. + } + //cjh if (mState.tracks[name].mReformatBufferProvider != nullptr) { + // mState.tracks[name].mReformatBufferProvider->reset(); + // } else if (mState.tracks[name].downmixerBufferProvider != nullptr) { + // mState.tracks[name].downmixerBufferProvider->reset(); + // } else if (mState.tracks[name].mPostDownmixReformatBufferProvider != nullptr) { + // mState.tracks[name].mPostDownmixReformatBufferProvider->reset(); + // } else if (mState.tracks[name].mTimestretchBufferProvider != nullptr) { + // mState.tracks[name].mTimestretchBufferProvider->reset(); + // } + + mState.tracks[name].mInputBufferProvider = bufferProvider; + mState.tracks[name].reconfigureBufferProviders(); +} + +void AudioMixer::process(int64_t pts) { + mState.hook(&mState, pts); +} + +void AudioMixer::process__validate(state_t *state, int64_t pts) { //NOLINT + ALOGW_IF(!state->needsChanged, + "in process__validate() but nothing's invalid"); + + uint32_t changed = state->needsChanged; + state->needsChanged = 0; // clear the validation flag + + // recompute which tracks are enabled / disabled + uint32_t enabled = 0; + uint32_t disabled = 0; + while (changed) { + const int i = 31 - __builtin_clz(changed); + const uint32_t mask = 1 << i; + changed &= ~mask; + track_t &t = state->tracks[i]; + (t.enabled ? enabled : disabled) |= mask; + } + state->enabledTracks &= ~disabled; + state->enabledTracks |= enabled; + + // compute everything we need... + int countActiveTracks = 0; + // REFINE: fix all16BitsStereNoResample logic to + // either properly handle muted tracks (it should ignore them) + // or remove altogether as an obsolete optimization. + bool all16BitsStereoNoResample = true; + bool resampling = false; + bool volumeRamp = false; + uint32_t en = state->enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1 << i); + + countActiveTracks++; + track_t &t = state->tracks[i]; + uint32_t n = 0; + // IDEA: can overflow (mask is only 3 bits) + n |= NEEDS_CHANNEL_1 + t.channelCount - 1; + if (t.doesResample()) { + n |= NEEDS_RESAMPLE; + } + if (t.auxLevel != 0 && t.auxBuffer != nullptr) { + n |= NEEDS_AUX; + } + + if (t.volumeInc[0] | t.volumeInc[1]) { + volumeRamp = true; + } else if (!t.doesResample() && t.volumeRL == 0) { + n |= NEEDS_MUTE; + } + t.needs = n; + + if (n & NEEDS_MUTE) { + t.hook = track__nop; + } else { + if (n & NEEDS_AUX) { + all16BitsStereoNoResample = false; + } + if (n & NEEDS_RESAMPLE) { + all16BitsStereoNoResample = false; + resampling = true; + t.hook = getTrackHook(TRACKTYPE_RESAMPLE, t.mMixerChannelCount, + t.mMixerInFormat, t.mMixerFormat); + ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2, + "Track %d needs downmix + resample", i); + } else { + if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1) { + t.hook = getTrackHook( + (t.mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // REFINE: MONO_HACK + && t.channelMask == AUDIO_CHANNEL_OUT_MONO) + ? TRACKTYPE_NORESAMPLEMONO + : TRACKTYPE_NORESAMPLE, + t.mMixerChannelCount, + t.mMixerInFormat, t.mMixerFormat); + all16BitsStereoNoResample = false; + } + if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2) { + t.hook = getTrackHook(TRACKTYPE_NORESAMPLE, t.mMixerChannelCount, + t.mMixerInFormat, t.mMixerFormat); + ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2, + "Track %d needs downmix", i); + } + } + } + } + + // select the processing hooks + state->hook = process__nop; + if (countActiveTracks > 0) { + if (resampling) { + if (!state->outputTemp) { + state->outputTemp = ccnew int32_t[MAX_NUM_CHANNELS * state->frameCount]; + } + if (!state->resampleTemp) { + state->resampleTemp = ccnew int32_t[MAX_NUM_CHANNELS * state->frameCount]; + } + state->hook = process__genericResampling; + } else { + if (state->outputTemp) { + delete[] state->outputTemp; + state->outputTemp = nullptr; + } + if (state->resampleTemp) { + delete[] state->resampleTemp; + state->resampleTemp = nullptr; + } + state->hook = process__genericNoResampling; + if (all16BitsStereoNoResample && !volumeRamp) { + if (countActiveTracks == 1) { + const int i = 31 - __builtin_clz(state->enabledTracks); + track_t &t = state->tracks[i]; + if ((t.needs & NEEDS_MUTE) == 0) { + // The check prevents a muted track from acquiring a process hook. + // + // This is dangerous if the track is MONO as that requires + // special case handling due to implicit channel duplication. + // Stereo or Multichannel should actually be fine here. + state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, + t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat); + } + } + } + } + } + + ALOGV( + "mixer configuration change: %d activeTracks (%08x) " + "all16BitsStereoNoResample=%d, resampling=%d, volumeRamp=%d", + countActiveTracks, state->enabledTracks, + all16BitsStereoNoResample, resampling, volumeRamp); + + state->hook(state, pts); + + // Now that the volume ramp has been done, set optimal state and + // track hooks for subsequent mixer process + if (countActiveTracks > 0) { + bool allMuted = true; + uint32_t en = state->enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1 << i); + track_t &t = state->tracks[i]; + if (!t.doesResample() && t.volumeRL == 0) { + t.needs |= NEEDS_MUTE; + t.hook = track__nop; + } else { + allMuted = false; + } + } + if (allMuted) { + state->hook = process__nop; + } else if (all16BitsStereoNoResample) { + if (countActiveTracks == 1) { + const int i = 31 - __builtin_clz(state->enabledTracks); + track_t &t = state->tracks[i]; + // Muted single tracks handled by allMuted above. + state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK, + t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat); + } + } + } +} + +void AudioMixer::track__genericResample(track_t *t, int32_t *out, size_t outFrameCount, //NOLINT + int32_t *temp, int32_t *aux) { + ALOGVV("track__genericResample\n"); + t->resampler->setSampleRate(t->sampleRate); + + // ramp gain - resample to temp buffer and scale/mix in 2nd step + if (aux != nullptr) { + // always resample with unity gain when sending to auxiliary buffer to be able + // to apply send level after resampling + t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); + memset(temp, 0, outFrameCount * t->mMixerChannelCount * sizeof(int32_t)); + t->resampler->resample(temp, outFrameCount, t->bufferProvider); + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1] | t->auxInc)) { + volumeRampStereo(t, out, outFrameCount, temp, aux); + } else { + volumeStereo(t, out, outFrameCount, temp, aux); + } + } else { + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1])) { + t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); + memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t)); + t->resampler->resample(temp, outFrameCount, t->bufferProvider); + volumeRampStereo(t, out, outFrameCount, temp, aux); + } + + // constant gain + else { + t->resampler->setVolume(t->mVolume[0], t->mVolume[1]); + t->resampler->resample(out, outFrameCount, t->bufferProvider); + } + } +} + +void AudioMixer::track__nop(track_t *t __unused, int32_t *out __unused, //NOLINT + size_t outFrameCount __unused, int32_t *temp __unused, int32_t *aux __unused) { +} + +void AudioMixer::volumeRampStereo(track_t *t, int32_t *out, size_t frameCount, int32_t *temp, + int32_t *aux) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + //ALOGD("[0] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + // ramp volume + if (CC_UNLIKELY(aux != nullptr)) { + int32_t va = t->prevAuxLevel; + const int32_t vaInc = t->auxInc; + int32_t l; + int32_t r; + + do { + l = (*temp++ >> 12); + r = (*temp++ >> 12); + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * r; + *aux++ += (va >> 17) * (l + r); + vl += vlInc; + vr += vrInc; + va += vaInc; + } while (--frameCount); + t->prevAuxLevel = va; + } else { + do { + *out++ += (vl >> 16) * (*temp++ >> 12); + *out++ += (vr >> 16) * (*temp++ >> 12); + vl += vlInc; + vr += vrInc; + } while (--frameCount); + } + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(aux != nullptr); +} + +void AudioMixer::volumeStereo(track_t *t, int32_t *out, size_t frameCount, int32_t *temp, + int32_t *aux) { + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + + if (CC_UNLIKELY(aux != nullptr)) { + const int16_t va = t->auxLevel; + do { + auto l = static_cast(*temp++ >> 12); + auto r = static_cast(*temp++ >> 12); + out[0] = mulAdd(l, vl, out[0]); + auto a = static_cast((static_cast(l) + r) >> 1); + out[1] = mulAdd(r, vr, out[1]); + out += 2; + aux[0] = mulAdd(a, va, aux[0]); + aux++; + } while (--frameCount); + } else { + do { + auto l = static_cast(*temp++ >> 12); + auto r = static_cast(*temp++ >> 12); + out[0] = mulAdd(l, vl, out[0]); + out[1] = mulAdd(r, vr, out[1]); + out += 2; + } while (--frameCount); + } +} + +void AudioMixer::track__16BitsStereo(track_t *t, int32_t *out, size_t frameCount, //NOLINT + int32_t *temp __unused, int32_t *aux) { + ALOGVV("track__16BitsStereo\n"); + const auto *in = static_cast(t->in); + + if (CC_UNLIKELY(aux != nullptr)) { + int32_t l; + int32_t r; + // ramp gain + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1] | t->auxInc)) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + int32_t va = t->prevAuxLevel; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + const int32_t vaInc = t->auxInc; + // ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + l = static_cast(*in++); + r = static_cast(*in++); + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * r; + *aux++ += (va >> 17) * (l + r); + vl += vlInc; + vr += vrInc; + va += vaInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->prevAuxLevel = va; + t->adjustVolumeRamp(true); + } + + // constant gain + else { + const uint32_t vrl = t->volumeRL; + const auto va = t->auxLevel; + do { + uint32_t rl = *reinterpret_cast(in); + auto a = static_cast((static_cast(in[0]) + in[1]) >> 1); + in += 2; + out[0] = mulAddRL(1, rl, vrl, out[0]); + out[1] = mulAddRL(0, rl, vrl, out[1]); + out += 2; + aux[0] = mulAdd(a, va, aux[0]); + aux++; + } while (--frameCount); + } + } else { + // ramp gain + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1])) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + // ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + *out++ += (vl >> 16) * static_cast(*in++); + *out++ += (vr >> 16) * static_cast(*in++); + vl += vlInc; + vr += vrInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(false); + } + + // constant gain + else { + const uint32_t vrl = t->volumeRL; + do { + uint32_t rl = *reinterpret_cast(in); + in += 2; + out[0] = mulAddRL(1, rl, vrl, out[0]); + out[1] = mulAddRL(0, rl, vrl, out[1]); + out += 2; + } while (--frameCount); + } + } + t->in = in; +} + +void AudioMixer::track__16BitsMono(track_t *t, int32_t *out, size_t frameCount, //NOLINT + int32_t *temp __unused, int32_t *aux) { + ALOGVV("track__16BitsMono\n"); + const auto *in = static_cast(t->in); + + if (CC_UNLIKELY(aux != nullptr)) { + // ramp gain + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1] | t->auxInc)) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + int32_t va = t->prevAuxLevel; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + const int32_t vaInc = t->auxInc; + + // ALOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + int32_t l = *in++; + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * l; + *aux++ += (va >> 16) * l; + vl += vlInc; + vr += vrInc; + va += vaInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->prevAuxLevel = va; + t->adjustVolumeRamp(true); + } + // constant gain + else { + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + const auto va = t->auxLevel; + do { + int16_t l = *in++; + out[0] = mulAdd(l, vl, out[0]); + out[1] = mulAdd(l, vr, out[1]); + out += 2; + aux[0] = mulAdd(l, va, aux[0]); + aux++; + } while (--frameCount); + } + } else { + // ramp gain + if (CC_UNLIKELY(t->volumeInc[0] | t->volumeInc[1])) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + // ALOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + int32_t l = *in++; + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * l; + vl += vlInc; + vr += vrInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(false); + } + // constant gain + else { + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + do { + int16_t l = *in++; + out[0] = mulAdd(l, vl, out[0]); + out[1] = mulAdd(l, vr, out[1]); + out += 2; + } while (--frameCount); + } + } + t->in = in; +} + +// no-op case +void AudioMixer::process__nop(state_t *state, int64_t pts) { //NOLINT + ALOGVV("process__nop\n"); + uint32_t e0 = state->enabledTracks; + while (e0) { + // process by group of tracks with same output buffer to + // avoid multiple memset() on same buffer + uint32_t e1 = e0; + uint32_t e2 = e0; + int i = 31 - __builtin_clz(e1); + { + track_t &t1 = state->tracks[i]; + e2 &= ~(1 << i); + while (e2) { + i = 31 - __builtin_clz(e2); + e2 &= ~(1 << i); + track_t &t2 = state->tracks[i]; + if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { + e1 &= ~(1 << i); + } + } + e0 &= ~(e1); + + memset(t1.mainBuffer, 0, state->frameCount * t1.mMixerChannelCount * audio_bytes_per_sample(t1.mMixerFormat)); + } + + while (e1) { + i = 31 - __builtin_clz(e1); + e1 &= ~(1 << i); + { + track_t &t3 = state->tracks[i]; + size_t outFrames = state->frameCount; + while (outFrames) { + t3.buffer.frameCount = outFrames; + int64_t outputPTS = calculateOutputPTS( + t3, pts, state->frameCount - outFrames); //NOLINT + t3.bufferProvider->getNextBuffer(&t3.buffer, outputPTS); + if (t3.buffer.raw == nullptr) break; + outFrames -= t3.buffer.frameCount; + t3.bufferProvider->releaseBuffer(&t3.buffer); + } + } + } + } +} + +// generic code without resampling +void AudioMixer::process__genericNoResampling(state_t *state, int64_t pts) { //NOLINT + ALOGVV("process__genericNoResampling\n"); + int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32))); + + // acquire each track's buffer + uint32_t enabledTracks = state->enabledTracks; + uint32_t e0 = enabledTracks; + while (e0) { + const int i = 31 - __builtin_clz(e0); + e0 &= ~(1 << i); + track_t &t = state->tracks[i]; + t.buffer.frameCount = state->frameCount; + t.bufferProvider->getNextBuffer(&t.buffer, pts); + t.frameCount = t.buffer.frameCount; + t.in = t.buffer.raw; + } + + e0 = enabledTracks; + while (e0) { + // process by group of tracks with same output buffer to + // optimize cache use + uint32_t e1 = e0; + uint32_t e2 = e0; + int j = 31 - __builtin_clz(e1); + track_t &t1 = state->tracks[j]; + e2 &= ~(1 << j); + while (e2) { + j = 31 - __builtin_clz(e2); + e2 &= ~(1 << j); + track_t &t2 = state->tracks[j]; + if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { + e1 &= ~(1 << j); + } + } + e0 &= ~(e1); + // this assumes output 16 bits stereo, no resampling + int32_t *out = t1.mainBuffer; + size_t numFrames = 0; + do { + memset(outTemp, 0, sizeof(outTemp)); + e2 = e1; + while (e2) { + const int i = 31 - __builtin_clz(e2); + e2 &= ~(1 << i); + track_t &t = state->tracks[i]; + size_t outFrames = BLOCKSIZE; + int32_t *aux = nullptr; + if (CC_UNLIKELY(t.needs & NEEDS_AUX)) { + aux = t.auxBuffer + numFrames; + } + while (outFrames) { + // t.in == nullptr can happen if the track was flushed just after having + // been enabled for mixing. + if (t.in == nullptr) { + enabledTracks &= ~(1 << i); + e1 &= ~(1 << i); + break; + } + size_t inFrames = (t.frameCount > outFrames) ? outFrames : t.frameCount; + if (inFrames > 0) { + t.hook(&t, outTemp + (BLOCKSIZE - outFrames) * t.mMixerChannelCount, + inFrames, state->resampleTemp, aux); + t.frameCount -= inFrames; + outFrames -= inFrames; + if (CC_UNLIKELY(aux != nullptr)) { + aux += inFrames; + } + } + if (t.frameCount == 0 && outFrames) { + t.bufferProvider->releaseBuffer(&t.buffer); + t.buffer.frameCount = (state->frameCount - numFrames) - + (BLOCKSIZE - outFrames); + int64_t outputPTS = calculateOutputPTS( + t, pts, numFrames + (BLOCKSIZE - outFrames)); //NOLINT + t.bufferProvider->getNextBuffer(&t.buffer, outputPTS); + t.in = t.buffer.raw; + if (t.in == nullptr) { + enabledTracks &= ~(1 << i); + e1 &= ~(1 << i); + break; + } + t.frameCount = t.buffer.frameCount; + } + } + } + + convertMixerFormat(out, t1.mMixerFormat, outTemp, t1.mMixerInFormat, + BLOCKSIZE * t1.mMixerChannelCount); + // REFINE: fix ugly casting due to choice of out pointer type + out = reinterpret_cast(reinterpret_cast(out) + BLOCKSIZE * t1.mMixerChannelCount * audio_bytes_per_sample(t1.mMixerFormat)); + numFrames += BLOCKSIZE; + } while (numFrames < state->frameCount); + } + + // release each track's buffer + e0 = enabledTracks; + while (e0) { + const int i = 31 - __builtin_clz(e0); + e0 &= ~(1 << i); + track_t &t = state->tracks[i]; + t.bufferProvider->releaseBuffer(&t.buffer); + } +} + +// generic code with resampling +void AudioMixer::process__genericResampling(state_t *state, int64_t pts) { //NOLINT + ALOGVV("process__genericResampling\n"); + // this const just means that local variable outTemp doesn't change + int32_t *const outTemp = state->outputTemp; + size_t numFrames = state->frameCount; + + uint32_t e0 = state->enabledTracks; + while (e0) { + // process by group of tracks with same output buffer + // to optimize cache use + uint32_t e1 = e0; + uint32_t e2 = e0; + int j = 31 - __builtin_clz(e1); + track_t &t1 = state->tracks[j]; + e2 &= ~(1 << j); + while (e2) { + j = 31 - __builtin_clz(e2); + e2 &= ~(1 << j); + track_t &t2 = state->tracks[j]; + if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { + e1 &= ~(1 << j); + } + } + e0 &= ~(e1); + int32_t *out = t1.mainBuffer; + memset(outTemp, 0, sizeof(*outTemp) * t1.mMixerChannelCount * state->frameCount); + while (e1) { + const int i = 31 - __builtin_clz(e1); + e1 &= ~(1 << i); + track_t &t = state->tracks[i]; + int32_t *aux = nullptr; + if (CC_UNLIKELY(t.needs & NEEDS_AUX)) { + aux = t.auxBuffer; + } + + // this is a little goofy, on the resampling case we don't + // acquire/release the buffers because it's done by + // the resampler. + if (t.needs & NEEDS_RESAMPLE) { + t.resampler->setPTS(pts); + t.hook(&t, outTemp, numFrames, state->resampleTemp, aux); + } else { + size_t outFrames = 0; + + while (outFrames < numFrames) { + t.buffer.frameCount = numFrames - outFrames; + int64_t outputPTS = calculateOutputPTS(t, pts, outFrames); + t.bufferProvider->getNextBuffer(&t.buffer, outputPTS); + t.in = t.buffer.raw; + // t.in == nullptr can happen if the track was flushed just after having + // been enabled for mixing. + if (t.in == nullptr) break; + + if (CC_UNLIKELY(aux != nullptr)) { + aux += outFrames; + } + t.hook(&t, outTemp + outFrames * t.mMixerChannelCount, t.buffer.frameCount, + state->resampleTemp, aux); + outFrames += t.buffer.frameCount; + t.bufferProvider->releaseBuffer(&t.buffer); + } + } + } + convertMixerFormat(out, t1.mMixerFormat, + outTemp, t1.mMixerInFormat, numFrames * t1.mMixerChannelCount); + } +} + +// one track, 16 bits stereo without resampling is the most common case +void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t *state, //NOLINT + int64_t pts) { + ALOGVV("process__OneTrack16BitsStereoNoResampling\n"); + // This method is only called when state->enabledTracks has exactly + // one bit set. The asserts below would verify this, but are commented out + // since the whole point of this method is to optimize performance. + //ALOG_ASSERT(0 != state->enabledTracks, "no tracks enabled"); + const int i = 31 - __builtin_clz(state->enabledTracks); + //ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled"); + const track_t &t = state->tracks[i]; + + AudioBufferProvider::Buffer &b(t.buffer); + + int32_t *out = t.mainBuffer; + auto *fout = reinterpret_cast(out); + size_t numFrames = state->frameCount; + + const int16_t vl = t.volume[0]; + const int16_t vr = t.volume[1]; + const uint32_t vrl = t.volumeRL; + while (numFrames) { + b.frameCount = numFrames; + int64_t outputPTS = calculateOutputPTS(t, pts, out - t.mainBuffer); + t.bufferProvider->getNextBuffer(&b, outputPTS); + const int16_t *in = b.i16; + + // in == nullptr can happen if the track was flushed just after having + // been enabled for mixing. + if (in == nullptr || (((uintptr_t)in) & 3)) { //NOLINT + memset(out, 0, numFrames * t.mMixerChannelCount * audio_bytes_per_sample(t.mMixerFormat)); + ALOGE_IF((((uintptr_t)in) & 3), + "process__OneTrack16BitsStereoNoResampling: misaligned buffer" + " %p track %d, channels %d, needs %08x, volume %08x vfl %f vfr %f", + in, i, t.channelCount, t.needs, vrl, t.mVolume[0], t.mVolume[1]); + return; + } + size_t outFrames = b.frameCount; + + switch (t.mMixerFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + do { + uint32_t rl = *reinterpret_cast(in); + in += 2; + int32_t l = mulRL(1, rl, vrl); + int32_t r = mulRL(0, rl, vrl); + *fout++ = float_from_q4_27(l); + *fout++ = float_from_q4_27(r); + // Note: In case of later int16_t sink output, + // conversion and clamping is done by memcpy_to_i16_from_float(). + } while (--outFrames); + break; + case AUDIO_FORMAT_PCM_16_BIT: + if (CC_UNLIKELY(uint32_t(vl) > UNITY_GAIN_INT || uint32_t(vr) > UNITY_GAIN_INT)) { + // volume is boosted, so we might need to clamp even though + // we process only one track. + do { + uint32_t rl = *reinterpret_cast(in); + in += 2; + int32_t l = mulRL(1, rl, vrl) >> 12; + int32_t r = mulRL(0, rl, vrl) >> 12; + // clamping... + l = clamp16(l); + r = clamp16(r); + *out++ = (r << 16) | (l & 0xFFFF); + } while (--outFrames); + } else { + do { + uint32_t rl = *reinterpret_cast(in); + in += 2; + int32_t l = mulRL(1, rl, vrl) >> 12; + int32_t r = mulRL(0, rl, vrl) >> 12; + *out++ = (r << 16) | (l & 0xFFFF); + } while (--outFrames); + } + break; + default: + LOG_ALWAYS_FATAL("bad mixer format: %d", t.mMixerFormat); + } + numFrames -= b.frameCount; + t.bufferProvider->releaseBuffer(&b); + } +} + +int64_t AudioMixer::calculateOutputPTS(const track_t &t, int64_t basePTS, + int outputFrameIndex) { + if (AudioBufferProvider::kInvalidPTS == basePTS) { + return AudioBufferProvider::kInvalidPTS; + } + + return basePTS + ((outputFrameIndex * sLocalTimeFreq) / t.sampleRate); +} + +/*static*/ uint64_t AudioMixer::sLocalTimeFreq; +/*static*/ pthread_once_t AudioMixer::sOnceControl = PTHREAD_ONCE_INIT; + +/*static*/ void AudioMixer::sInitRoutine() { + //cjh LocalClock lc; + // sLocalTimeFreq = lc.getLocalFreq(); // for the resampler + // + // DownmixerBufferProvider::init(); // for the downmixer +} + +/* REFINE: consider whether this level of optimization is necessary. + * Perhaps just stick with a single for loop. + */ + +// Needs to derive a compile time constant (constexpr). Could be targeted to go +// to a MONOVOL mixtype based on MAX_NUM_VOLUMES, but that's an unnecessary complication. +#define MIXTYPE_MONOVOL(mixtype) (mixtype == MIXTYPE_MULTI ? MIXTYPE_MULTI_MONOVOL : mixtype == MIXTYPE_MULTI_SAVEONLY ? MIXTYPE_MULTI_SAVEONLY_MONOVOL \ + : mixtype) + +/* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * TO: int32_t (Q4.27) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TA: int32_t (Q4.27) + */ +template +static void volumeRampMulti(uint32_t channels, TO *out, size_t frameCount, + const TI *in, TA *aux, TV *vol, const TV *volinc, TAV *vola, TAV volainc) { + switch (channels) { + case 1: + volumeRampMulti(out, frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 2: + volumeRampMulti(out, frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 3: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 4: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 5: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 6: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 7: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + case 8: + volumeRampMulti(out, + frameCount, in, aux, vol, volinc, vola, volainc); + break; + } +} + +/* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * TO: int32_t (Q4.27) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TA: int32_t (Q4.27) + */ +template +static void volumeMulti(uint32_t channels, TO *out, size_t frameCount, + const TI *in, TA *aux, const TV *vol, TAV vola) { + switch (channels) { + case 1: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 2: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 3: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 4: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 5: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 6: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 7: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + case 8: + volumeMulti(out, frameCount, in, aux, vol, vola); + break; + } +} + +/* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * USEFLOATVOL (set to true if float volume is used) + * ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards) + * TO: int32_t (Q4.27) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TA: int32_t (Q4.27) + */ +template +void AudioMixer::volumeMix(TO *out, size_t outFrames, + const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t) { + if (USEFLOATVOL) { + if (ramp) { + volumeRampMulti(t->mMixerChannelCount, out, outFrames, in, aux, + t->mPrevVolume, t->mVolumeInc, &t->prevAuxLevel, t->auxInc); + if (ADJUSTVOL) { + t->adjustVolumeRamp(aux != nullptr, true); + } + } else { + volumeMulti(t->mMixerChannelCount, out, outFrames, in, aux, + t->mVolume, t->auxLevel); + } + } else { + if (ramp) { + volumeRampMulti(t->mMixerChannelCount, out, outFrames, in, aux, + t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc); + if (ADJUSTVOL) { + t->adjustVolumeRamp(aux != nullptr); + } + } else { + volumeMulti(t->mMixerChannelCount, out, outFrames, in, aux, + t->volume, t->auxLevel); + } + } +} + +/* This process hook is called when there is a single track without + * aux buffer, volume ramp, or resampling. + * REFINE: Update the hook selection: this can properly handle aux and ramp. + * + * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * TO: int32_t (Q4.27) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TA: int32_t (Q4.27) + */ +template +void AudioMixer::process_NoResampleOneTrack(state_t *state, int64_t pts) { //NOLINT + ALOGVV("process_NoResampleOneTrack\n"); + // CLZ is faster than CTZ on ARM, though really not sure if true after 31 - clz. + const int i = 31 - __builtin_clz(state->enabledTracks); + ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled"); + track_t *t = &state->tracks[i]; + const uint32_t channels = t->mMixerChannelCount; + TO *out = reinterpret_cast(t->mainBuffer); + TA *aux = reinterpret_cast(t->auxBuffer); + const bool ramp = t->needsRamp(); + + for (size_t numFrames = state->frameCount; numFrames;) { + AudioBufferProvider::Buffer &b(t->buffer); + // get input buffer + b.frameCount = numFrames; + const int64_t outputPTS = calculateOutputPTS(*t, pts, state->frameCount - numFrames); //NOLINT + t->bufferProvider->getNextBuffer(&b, outputPTS); + const TI *in = reinterpret_cast(b.raw); + + // in == nullptr can happen if the track was flushed just after having + // been enabled for mixing. + if (in == nullptr || (((uintptr_t)in) & 3)) { //NOLINT + memset(out, 0, numFrames * channels * audio_bytes_per_sample(t->mMixerFormat)); + ALOGE_IF((((uintptr_t)in) & 3), + "process_NoResampleOneTrack: bus error: " + "buffer %p track %p, channels %d, needs %#x", + in, t, t->channelCount, t->needs); + return; + } + + const size_t outFrames = b.frameCount; + volumeMix::value, false>( + out, outFrames, in, aux, ramp, t); + + out += outFrames * channels; + if (aux != nullptr) { + aux += channels; + } + numFrames -= b.frameCount; + + // release buffer + t->bufferProvider->releaseBuffer(&b); + } + if (ramp) { + t->adjustVolumeRamp(aux != nullptr, is_same::value); + } +} + +/* This track hook is called to do resampling then mixing, + * pulling from the track's upstream AudioBufferProvider. + * + * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * TO: int32_t (Q4.27) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TA: int32_t (Q4.27) + */ +template +void AudioMixer::track__Resample(track_t *t, TO *out, size_t outFrameCount, TO *temp, TA *aux) { //NOLINT + ALOGVV("track__Resample\n"); + t->resampler->setSampleRate(t->sampleRate); + const bool ramp = t->needsRamp(); + if (ramp || aux != nullptr) { + // if ramp: resample with unity gain to temp buffer and scale/mix in 2nd step. + // if aux != nullptr: resample with unity gain to temp buffer then apply send level. + + t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT); + memset(temp, 0, outFrameCount * t->mMixerChannelCount * sizeof(TO)); + t->resampler->resample((int32_t *)temp, outFrameCount, t->bufferProvider); //NOLINT + + volumeMix::value, true>( + out, outFrameCount, temp, aux, ramp, t); + + } else { // constant volume gain + t->resampler->setVolume(t->mVolume[0], t->mVolume[1]); + t->resampler->resample((int32_t *)out, outFrameCount, t->bufferProvider); //NOLINT + } +} + +/* This track hook is called to mix a track, when no resampling is required. + * The input buffer should be present in t->in. + * + * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * TO: int32_t (Q4.27) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TA: int32_t (Q4.27) + */ +template +void AudioMixer::track__NoResample(track_t *t, TO *out, size_t frameCount, //NOLINT + TO *temp __unused, TA *aux) { + ALOGVV("track__NoResample\n"); + const TI *in = static_cast(t->in); + + volumeMix::value, true>( + out, frameCount, in, aux, t->needsRamp(), t); + + // MIXTYPE_MONOEXPAND reads a single input channel and expands to NCHAN output channels. + // MIXTYPE_MULTI reads NCHAN input channels and places to NCHAN output channels. + in += (MIXTYPE == MIXTYPE_MONOEXPAND) ? frameCount : frameCount * t->mMixerChannelCount; + t->in = in; +} + +/* The Mixer engine generates either int32_t (Q4_27) or float data. + * We use this function to convert the engine buffers + * to the desired mixer output format, either int16_t (Q.15) or float. + */ +void AudioMixer::convertMixerFormat(void *out, audio_format_t mixerOutFormat, + void *in, audio_format_t mixerInFormat, size_t sampleCount) { + switch (mixerInFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + switch (mixerOutFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + memcpy(out, in, sampleCount * sizeof(float)); // MEMCPY. REFINE: optimize out + break; + case AUDIO_FORMAT_PCM_16_BIT: + memcpy_to_i16_from_float(static_cast(out), static_cast(in), sampleCount); + break; + default: + LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); + break; + } + break; + case AUDIO_FORMAT_PCM_16_BIT: + switch (mixerOutFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + memcpy_to_float_from_q4_27(static_cast(out), static_cast(in), sampleCount); + break; + case AUDIO_FORMAT_PCM_16_BIT: + // two int16_t are produced per iteration + ditherAndClamp(static_cast(out), static_cast(in), sampleCount >> 1); + break; + default: + LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); + break; + } + break; + default: + LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); + break; + } +} + +/* Returns the proper track hook to use for mixing the track into the output buffer. + */ +AudioMixer::hook_t AudioMixer::getTrackHook(int trackType, uint32_t channelCount, + audio_format_t mixerInFormat, audio_format_t mixerOutFormat __unused) { + if (!kUseNewMixer && channelCount == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) { + switch (trackType) { + case TRACKTYPE_NOP: + return track__nop; + case TRACKTYPE_RESAMPLE: + return track__genericResample; + case TRACKTYPE_NORESAMPLEMONO: + return track__16BitsMono; + case TRACKTYPE_NORESAMPLE: + return track__16BitsStereo; + default: + LOG_ALWAYS_FATAL("bad trackType: %d", trackType); + break; + } + } + LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS); + switch (trackType) { + case TRACKTYPE_NOP: + return track__nop; + case TRACKTYPE_RESAMPLE: + switch (mixerInFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + return reinterpret_cast(track__Resample); + case AUDIO_FORMAT_PCM_16_BIT: + return static_cast(track__Resample); + default: + LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); + break; + } + break; + case TRACKTYPE_NORESAMPLEMONO: + switch (mixerInFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + return reinterpret_cast(track__NoResample); + case AUDIO_FORMAT_PCM_16_BIT: + return static_cast(track__NoResample); + default: + LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); + break; + } + break; + case TRACKTYPE_NORESAMPLE: + switch (mixerInFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + return reinterpret_cast(track__NoResample); + case AUDIO_FORMAT_PCM_16_BIT: + return static_cast(track__NoResample); + default: + LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); + break; + } + break; + default: + LOG_ALWAYS_FATAL("bad trackType: %d", trackType); + break; + } + return nullptr; +} + +/* Returns the proper process hook for mixing tracks. Currently works only for + * PROCESSTYPE_NORESAMPLEONETRACK, a mix involving one track, no resampling. + * + * REFINE: Due to the special mixing considerations of duplicating to + * a stereo output track, the input track cannot be MONO. This should be + * prevented by the caller. + */ +AudioMixer::process_hook_t AudioMixer::getProcessHook(int processType, uint32_t channelCount, + audio_format_t mixerInFormat, audio_format_t mixerOutFormat) { + if (processType != PROCESSTYPE_NORESAMPLEONETRACK) { // Only NORESAMPLEONETRACK + LOG_ALWAYS_FATAL("bad processType: %d", processType); + return nullptr; + } + if (!kUseNewMixer && channelCount == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) { + return process__OneTrack16BitsStereoNoResampling; + } + LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS); + switch (mixerInFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + switch (mixerOutFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + return process_NoResampleOneTrack; + case AUDIO_FORMAT_PCM_16_BIT: + return process_NoResampleOneTrack; + default: + LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); + break; + } + break; + case AUDIO_FORMAT_PCM_16_BIT: + switch (mixerOutFormat) { + case AUDIO_FORMAT_PCM_FLOAT: + return process_NoResampleOneTrack; + case AUDIO_FORMAT_PCM_16_BIT: + return process_NoResampleOneTrack; + default: + LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat); + break; + } + break; + default: + LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat); + break; + } + return nullptr; +} + +// ---------------------------------------------------------------------------- +} // namespace cc diff --git a/cocos/audio/android/AudioMixer.h b/cocos/audio/android/AudioMixer.h new file mode 100644 index 0000000..729c1db --- /dev/null +++ b/cocos/audio/android/AudioMixer.h @@ -0,0 +1,379 @@ +/* +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#pragma once + +#include +#include +#include + +#include "audio/android/AudioBufferProvider.h" +#include "audio/android/AudioResamplerPublic.h" + +#include "audio/android/AudioResampler.h" +#include "audio/android/audio.h" +#include "audio/android/utils/Compat.h" + +// IDEA: This is actually unity gain, which might not be max in future, expressed in U.12 +#define MAX_GAIN_INT AudioMixer::UNITY_GAIN_INT + +namespace cc { + +// ---------------------------------------------------------------------------- + +class AudioMixer { +public: + AudioMixer(size_t frameCount, uint32_t sampleRate, + uint32_t maxNumTracks = MAX_NUM_TRACKS); + + /*virtual*/ ~AudioMixer(); // non-virtual saves a v-table, restore if sub-classed + + // This mixer has a hard-coded upper limit of 32 active track inputs. + // Adding support for > 32 tracks would require more than simply changing this value. + static const uint32_t MAX_NUM_TRACKS = 32; + // maximum number of channels supported by the mixer + + // This mixer has a hard-coded upper limit of 8 channels for output. + static const uint32_t MAX_NUM_CHANNELS = 8; + static const uint32_t MAX_NUM_VOLUMES = 2; // stereo volume only + // maximum number of channels supported for the content + static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = AUDIO_CHANNEL_COUNT_MAX; + + static const uint16_t UNITY_GAIN_INT = 0x1000; + static const CONSTEXPR float UNITY_GAIN_FLOAT = 1.0F; + + enum { // names + + // track names (MAX_NUM_TRACKS units) + TRACK0 = 0x1000, + + // 0x2000 is unused + + // setParameter targets + TRACK = 0x3000, + RESAMPLE = 0x3001, + RAMP_VOLUME = 0x3002, // ramp to new volume + VOLUME = 0x3003, // don't ramp + TIMESTRETCH = 0x3004, + + // set Parameter names + // for target TRACK + CHANNEL_MASK = 0x4000, + FORMAT = 0x4001, + MAIN_BUFFER = 0x4002, + AUX_BUFFER = 0x4003, + DOWNMIX_TYPE = 0X4004, + MIXER_FORMAT = 0x4005, // AUDIO_FORMAT_PCM_(FLOAT|16_BIT) + MIXER_CHANNEL_MASK = 0x4006, // Channel mask for mixer output + // for target RESAMPLE + SAMPLE_RATE = 0x4100, // Configure sample rate conversion on this track name; + // parameter 'value' is the new sample rate in Hz. + // Only creates a sample rate converter the first time that + // the track sample rate is different from the mix sample rate. + // If the new sample rate is the same as the mix sample rate, + // and a sample rate converter already exists, + // then the sample rate converter remains present but is a no-op. + RESET = 0x4101, // Reset sample rate converter without changing sample rate. + // This clears out the resampler's input buffer. + REMOVE = 0x4102, // Remove the sample rate converter on this track name; + // the track is restored to the mix sample rate. + // for target RAMP_VOLUME and VOLUME (8 channels max) + // IDEA: use float for these 3 to improve the dynamic range + VOLUME0 = 0x4200, + VOLUME1 = 0x4201, + AUXLEVEL = 0x4210, + // for target TIMESTRETCH + PLAYBACK_RATE = 0x4300, // Configure timestretch on this track name; + // parameter 'value' is a pointer to the new playback rate. + }; + + // For all APIs with "name": TRACK0 <= name < TRACK0 + MAX_NUM_TRACKS + + // Allocate a track name. Returns new track name if successful, -1 on failure. + // The failure could be because of an invalid channelMask or format, or that + // the track capacity of the mixer is exceeded. + int getTrackName(audio_channel_mask_t channelMask, + audio_format_t format, int sessionId); + + // Free an allocated track by name + void deleteTrackName(int name); + + // Enable or disable an allocated track by name + void enable(int name); + void disable(int name); + + void setParameter(int name, int target, int param, void *value); + + void setBufferProvider(int name, AudioBufferProvider *bufferProvider); + void process(int64_t pts); + + uint32_t trackNames() const { return mTrackNames; } + + size_t getUnreleasedFrames(int name) const; + + static inline bool isValidPcmTrackFormat(audio_format_t format) { + switch (format) { + case AUDIO_FORMAT_PCM_8_BIT: + case AUDIO_FORMAT_PCM_16_BIT: + case AUDIO_FORMAT_PCM_24_BIT_PACKED: + case AUDIO_FORMAT_PCM_32_BIT: + case AUDIO_FORMAT_PCM_FLOAT: + return true; + default: + return false; + } + } + +private: + enum { + // IDEA: this representation permits up to 8 channels + NEEDS_CHANNEL_COUNT__MASK = 0x00000007, // NOLINT(bugprone-reserved-identifier) + }; + + enum { + NEEDS_CHANNEL_1 = 0x00000000, // mono + NEEDS_CHANNEL_2 = 0x00000001, // stereo + + // sample format is not explicitly specified, and is assumed to be AUDIO_FORMAT_PCM_16_BIT + + NEEDS_MUTE = 0x00000100, + NEEDS_RESAMPLE = 0x00001000, + NEEDS_AUX = 0x00010000, + }; + + struct state_t; + struct track_t; + + typedef void (*hook_t)(track_t *t, int32_t *output, size_t numOutFrames, int32_t *temp, int32_t *aux); //NOLINT(modernize-use-using) + static const int BLOCKSIZE = 16; // 4 cache lines + + struct track_t { + uint32_t needs; + + // REFINE: Eventually remove legacy integer volume settings + union { + int16_t volume[MAX_NUM_VOLUMES]; // U4.12 fixed point (top bit should be zero) + int32_t volumeRL; + }; + + int32_t prevVolume[MAX_NUM_VOLUMES]; + + // 16-byte boundary + + int32_t volumeInc[MAX_NUM_VOLUMES]; + int32_t auxInc; + int32_t prevAuxLevel; + + // 16-byte boundary + + int16_t auxLevel; // 0 <= auxLevel <= MAX_GAIN_INT, but signed for mul performance + uint16_t frameCount; + + uint8_t channelCount; // 1 or 2, redundant with (needs & NEEDS_CHANNEL_COUNT__MASK) + uint8_t unused_padding; // formerly format, was always 16 + uint16_t enabled; // actually bool + audio_channel_mask_t channelMask; + + // actual buffer provider used by the track hooks, see DownmixerBufferProvider below + // for how the Track buffer provider is wrapped by another one when dowmixing is required + AudioBufferProvider *bufferProvider; + + // 16-byte boundary + + mutable AudioBufferProvider::Buffer buffer; // 8 bytes + + hook_t hook; + const void *in; // current location in buffer + + // 16-byte boundary + + AudioResampler *resampler; + uint32_t sampleRate; + int32_t *mainBuffer; + int32_t *auxBuffer; + + // 16-byte boundary + + /* Buffer providers are constructed to translate the track input data as needed. + * + * REFINE: perhaps make a single PlaybackConverterProvider class to move + * all pre-mixer track buffer conversions outside the AudioMixer class. + * + * 1) mInputBufferProvider: The AudioTrack buffer provider. + * 2) mReformatBufferProvider: If not NULL, performs the audio reformat to + * match either mMixerInFormat or mDownmixRequiresFormat, if the downmixer + * requires reformat. For example, it may convert floating point input to + * PCM_16_bit if that's required by the downmixer. + * 3) downmixerBufferProvider: If not NULL, performs the channel remixing to match + * the number of channels required by the mixer sink. + * 4) mPostDownmixReformatBufferProvider: If not NULL, performs reformatting from + * the downmixer requirements to the mixer engine input requirements. + * 5) mTimestretchBufferProvider: Adds timestretching for playback rate + */ + AudioBufferProvider *mInputBufferProvider; // externally provided buffer provider. + //cjh PassthruBufferProvider* mReformatBufferProvider; // provider wrapper for reformatting. + // PassthruBufferProvider* downmixerBufferProvider; // wrapper for channel conversion. + // PassthruBufferProvider* mPostDownmixReformatBufferProvider; + // PassthruBufferProvider* mTimestretchBufferProvider; + + int32_t sessionId; + + audio_format_t mMixerFormat; // output mix format: AUDIO_FORMAT_PCM_(FLOAT|16_BIT) + audio_format_t mFormat; // input track format + audio_format_t mMixerInFormat; // mix internal format AUDIO_FORMAT_PCM_(FLOAT|16_BIT) + // each track must be converted to this format. + audio_format_t mDownmixRequiresFormat; // required downmixer format + // AUDIO_FORMAT_PCM_16_BIT if 16 bit necessary + // AUDIO_FORMAT_INVALID if no required format + + float mVolume[MAX_NUM_VOLUMES]; // floating point set volume + float mPrevVolume[MAX_NUM_VOLUMES]; // floating point previous volume + float mVolumeInc[MAX_NUM_VOLUMES]; // floating point volume increment + + float mAuxLevel; // floating point set aux level + float mPrevAuxLevel; // floating point prev aux level + float mAuxInc; // floating point aux increment + + audio_channel_mask_t mMixerChannelMask; + uint32_t mMixerChannelCount; + + AudioPlaybackRate mPlaybackRate; + + bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; } + bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate); + bool doesResample() const { return resampler != nullptr; } + void resetResampler() const { + if (resampler != nullptr) resampler->reset(); + } + void adjustVolumeRamp(bool aux, bool useFloat = false); + size_t getUnreleasedFrames() const { return resampler != nullptr ? resampler->getUnreleasedFrames() : 0; }; + + status_t prepareForDownmix(); + void unprepareForDownmix(); + status_t prepareForReformat(); + void unprepareForReformat(); + bool setPlaybackRate(const AudioPlaybackRate &playbackRate); + void reconfigureBufferProviders(); + }; + + typedef void (*process_hook_t)(state_t *state, int64_t pts); // NOLINT(modernize-use-using) + + // pad to 32-bytes to fill cache line + struct state_t { + uint32_t enabledTracks; + uint32_t needsChanged; + size_t frameCount; + process_hook_t hook; // one of process__*, never NULL + int32_t *outputTemp; + int32_t *resampleTemp; + //cjh NBLog::Writer* mLog; + int32_t reserved[1]; + // IDEA: allocate dynamically to save some memory when maxNumTracks < MAX_NUM_TRACKS + track_t tracks[MAX_NUM_TRACKS] __attribute__((aligned(32))); + }; + + // bitmask of allocated track names, where bit 0 corresponds to TRACK0 etc. + uint32_t mTrackNames;// NOLINT(readability-identifier-naming) + + // bitmask of configured track names; ~0 if maxNumTracks == MAX_NUM_TRACKS, + // but will have fewer bits set if maxNumTracks < MAX_NUM_TRACKS + const uint32_t mConfiguredNames;// NOLINT(readability-identifier-naming) + + const uint32_t mSampleRate;// NOLINT(readability-identifier-naming) + + //cjh NBLog::Writer mDummyLog; +public: + //cjh void setLog(NBLog::Writer* log); +private: + state_t mState __attribute__((aligned(32)));// NOLINT(readability-identifier-naming) + + // Call after changing either the enabled status of a track, or parameters of an enabled track. + // OK to call more often than that, but unnecessary. + void invalidateState(uint32_t mask); + + bool setChannelMasks(int name, + audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask); + + static void track__genericResample(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void track__nop(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void track__16BitsStereo(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void track__16BitsMono(track_t *t, int32_t *out, size_t numFrames, int32_t *temp, int32_t *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void volumeRampStereo(track_t *t, int32_t *out, size_t frameCount, int32_t *temp, int32_t *aux); + static void volumeStereo(track_t *t, int32_t *out, size_t frameCount, int32_t *temp, + int32_t *aux); + + static void process__validate(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void process__nop(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void process__genericNoResampling(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void process__genericResampling(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + static void process__OneTrack16BitsStereoNoResampling(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + + static int64_t calculateOutputPTS(const track_t &t, int64_t basePTS, + int outputFrameIndex); + + static uint64_t sLocalTimeFreq; + static pthread_once_t sOnceControl; + static void sInitRoutine(); + + /* multi-format volume mixing function (calls template functions + * in AudioMixerOps.h). The template parameters are as follows: + * + * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration) + * USEFLOATVOL (set to true if float volume is used) + * ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards) + * TO: int32_t (Q4.27) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TA: int32_t (Q4.27) + */ + template + static void volumeMix(TO *out, size_t outFrames, + const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t); + + // multi-format process hooks + template + static void process_NoResampleOneTrack(state_t *state, int64_t pts);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + + // multi-format track hooks + template + static void track__Resample(track_t *t, TO *out, size_t frameCount, TO *temp __unused, TA *aux);// NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + template + static void track__NoResample(track_t *t, TO *out, size_t frameCount, TO *temp __unused, TA *aux); // NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + + static void convertMixerFormat(void *out, audio_format_t mixerOutFormat, + void *in, audio_format_t mixerInFormat, size_t sampleCount); + + // hook types + enum { + PROCESSTYPE_NORESAMPLEONETRACK, + }; + enum { + TRACKTYPE_NOP, + TRACKTYPE_RESAMPLE, + TRACKTYPE_NORESAMPLE, + TRACKTYPE_NORESAMPLEMONO, + }; + + // functions for determining the proper process and track hooks. + static process_hook_t getProcessHook(int processType, uint32_t channelCount, + audio_format_t mixerInFormat, audio_format_t mixerOutFormat); + static hook_t getTrackHook(int trackType, uint32_t channelCount, + audio_format_t mixerInFormat, audio_format_t mixerOutFormat); +}; + +// ---------------------------------------------------------------------------- +} // namespace cc diff --git a/cocos/audio/android/AudioMixerController.cpp b/cocos/audio/android/AudioMixerController.cpp new file mode 100644 index 0000000..31aec82 --- /dev/null +++ b/cocos/audio/android/AudioMixerController.cpp @@ -0,0 +1,296 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioMixerController" + +#include "audio/android/AudioMixerController.h" +#include +#include "audio/android/AudioMixer.h" +#include "audio/android/OpenSLHelper.h" +#include "audio/android/Track.h" +#include "base/memory/Memory.h" + +namespace cc { + +AudioMixerController::AudioMixerController(int bufferSizeInFrames, int sampleRate, int channelCount) +: _bufferSizeInFrames(bufferSizeInFrames), _sampleRate(sampleRate), _channelCount(channelCount), _mixer(nullptr), _isPaused(false), _isMixingFrame(false) { + ALOGV("In the constructor of AudioMixerController!"); + + _mixingBuffer.size = (size_t)bufferSizeInFrames * 2 * channelCount; + // Don't use posix_memalign since it was added from API 16, it will crash on Android 2.3 + // Therefore, for a workaround, we uses memalign here. + _mixingBuffer.buf = memalign(32, _mixingBuffer.size); + memset(_mixingBuffer.buf, 0, _mixingBuffer.size); +} + +AudioMixerController::~AudioMixerController() { + destroy(); + + if (_mixer != nullptr) { + delete _mixer; + _mixer = nullptr; + } + + free(_mixingBuffer.buf); +} + +bool AudioMixerController::init() { + _mixer = ccnew AudioMixer(_bufferSizeInFrames, _sampleRate); + return _mixer != nullptr; +} + +bool AudioMixerController::addTrack(Track *track) { + ALOG_ASSERT(track != nullptr, "Shouldn't pass nullptr to addTrack"); + bool ret = false; + + std::lock_guard lk(_activeTracksMutex); + + auto iter = std::find(_activeTracks.begin(), _activeTracks.end(), track); + if (iter == _activeTracks.end()) { + _activeTracks.push_back(track); + ret = true; + } + + return ret; +} + +template +static void removeItemFromVector(ccstd::vector &v, T item) { + auto iter = std::find(v.begin(), v.end(), item); + if (iter != v.end()) { + v.erase(iter); + } +} + +void AudioMixerController::initTrack(Track *track, ccstd::vector &tracksToRemove) { + if (track->isInitialized()) + return; + + uint32_t channelMask = audio_channel_out_mask_from_count(2); + int32_t name = _mixer->getTrackName(channelMask, AUDIO_FORMAT_PCM_16_BIT, + AUDIO_SESSION_OUTPUT_MIX); + if (name < 0) { + // If we could not get the track name, it means that there're MAX_NUM_TRACKS tracks + // So ignore the new track. + tracksToRemove.push_back(track); + } else { + _mixer->setBufferProvider(name, track); + _mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER, + _mixingBuffer.buf); + _mixer->setParameter( + name, + AudioMixer::TRACK, + AudioMixer::MIXER_FORMAT, + (void *)(uintptr_t)AUDIO_FORMAT_PCM_16_BIT); + _mixer->setParameter( + name, + AudioMixer::TRACK, + AudioMixer::FORMAT, + (void *)(uintptr_t)AUDIO_FORMAT_PCM_16_BIT); + _mixer->setParameter( + name, + AudioMixer::TRACK, + AudioMixer::MIXER_CHANNEL_MASK, + (void *)(uintptr_t)channelMask); + _mixer->setParameter( + name, + AudioMixer::TRACK, + AudioMixer::CHANNEL_MASK, + (void *)(uintptr_t)channelMask); + + track->setName(name); + _mixer->enable(name); + + std::lock_guard lk(track->_volumeDirtyMutex); + gain_minifloat_packed_t volume = track->getVolumeLR(); + float lVolume = float_from_gain(gain_minifloat_unpack_left(volume)); + float rVolume = float_from_gain(gain_minifloat_unpack_right(volume)); + + _mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &lVolume); + _mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &rVolume); + + track->setVolumeDirty(false); + track->setInitialized(true); + } +} + +void AudioMixerController::mixOneFrame() { + _isMixingFrame = true; + _activeTracksMutex.lock(); + + auto mixStart = clockNow(); + + ccstd::vector tracksToRemove; + tracksToRemove.reserve(_activeTracks.size()); + + // FOR TESTING BEGIN + // Track* track = _activeTracks[0]; + // + // AudioBufferProvider::Buffer buffer; + // buffer.frameCount = _bufferSizeInFrames; + // status_t r = track->getNextBuffer(&buffer); + //// ALOG_ASSERT(buffer.frameCount == _mixing->size / 2, "buffer.frameCount:%d, _mixing->size/2:%d", buffer.frameCount, _mixing->size/2); + // if (r == NO_ERROR) + // { + // ALOGV("getNextBuffer succeed ..."); + // memcpy(_mixing->buf, buffer.raw, _mixing->size); + // } + // if (buffer.raw == nullptr) + // { + // ALOGV("Play over ..."); + // tracksToRemove.push_back(track); + // } + // else + // { + // track->releaseBuffer(&buffer); + // } + // + // _mixing->state = BufferState::FULL; + // _activeTracksMutex.unlock(); + // FOR TESTING END + + Track::State state; + // set up the tracks. + for (auto &&track : _activeTracks) { + state = track->getState(); + + if (state == Track::State::PLAYING) { + initTrack(track, tracksToRemove); + + int name = track->getName(); + ALOG_ASSERT(name >= 0); + + std::lock_guard lk(track->_volumeDirtyMutex); + + if (track->isVolumeDirty()) { + gain_minifloat_packed_t volume = track->getVolumeLR(); + float lVolume = float_from_gain(gain_minifloat_unpack_left(volume)); + float rVolume = float_from_gain(gain_minifloat_unpack_right(volume)); + + ALOGV("Track (name: %d)'s volume is dirty, update volume to L: %f, R: %f", name, lVolume, rVolume); + + _mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, &lVolume); + _mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, &rVolume); + + track->setVolumeDirty(false); + } + } else if (state == Track::State::RESUMED) { + initTrack(track, tracksToRemove); + + if (track->getPrevState() == Track::State::PAUSED) { + _mixer->enable(track->getName()); + track->setState(Track::State::PLAYING); + } else { + ALOGW("Previous state (%d) isn't PAUSED, couldn't resume!", static_cast(track->getPrevState())); + } + } else if (state == Track::State::PAUSED) { + initTrack(track, tracksToRemove); + + if (track->getPrevState() == Track::State::PLAYING || track->getPrevState() == Track::State::RESUMED) { + _mixer->disable(track->getName()); + } else { + ALOGW("Previous state (%d) isn't PLAYING, couldn't pause!", static_cast(track->getPrevState())); + } + } else if (state == Track::State::STOPPED) { + if (track->isInitialized()) { + _mixer->deleteTrackName(track->getName()); + } else { + ALOGV("Track (%p) hasn't been initialized yet!", track); + } + tracksToRemove.push_back(track); + } + + if (track->isPlayOver()) { + if (track->isLoop()) { + track->reset(); + } else { + ALOGV("Play over ..."); + _mixer->deleteTrackName(track->getName()); + tracksToRemove.push_back(track); + track->setState(Track::State::OVER); + } + } + } + + bool hasAvailableTracks = _activeTracks.size() - tracksToRemove.size() > 0; + + if (hasAvailableTracks) { + ALOGV_IF(_activeTracks.size() > 8, "More than 8 active tracks: %d", (int)_activeTracks.size()); + _mixer->process(AudioBufferProvider::kInvalidPTS); + } else { + ALOGV("Doesn't have enough tracks: %d, %d", (int)_activeTracks.size(), (int)tracksToRemove.size()); + } + + // Remove stopped or playover tracks for active tracks container + for (auto &&track : tracksToRemove) { + removeItemFromVector(_activeTracks, track); + + if (track != nullptr && track->onStateChanged != nullptr) { + track->onStateChanged(Track::State::DESTROYED); + } else { + ALOGE("track (%p) was released ...", track); + } + } + + _activeTracksMutex.unlock(); + + auto mixEnd = clockNow(); + float mixInterval = intervalInMS(mixStart, mixEnd); + ALOGV_IF(mixInterval > 1.0f, "Mix a frame waste: %fms", mixInterval); + + _isMixingFrame = false; +} + +void AudioMixerController::destroy() { + while (_isMixingFrame) { + usleep(10); + } + usleep(2000); // Wait for more 2ms +} + +void AudioMixerController::pause() { + _isPaused = true; +} + +void AudioMixerController::resume() { + _isPaused = false; +} + +bool AudioMixerController::hasPlayingTacks() { + std::lock_guard lk(_activeTracksMutex); + if (_activeTracks.empty()) + return false; + + for (auto &&track : _activeTracks) { + Track::State state = track->getState(); + if (state == Track::State::IDLE || state == Track::State::PLAYING || state == Track::State::RESUMED) { + return true; + } + } + + return false; +} + +} // namespace cc diff --git a/cocos/audio/android/AudioMixerController.h b/cocos/audio/android/AudioMixerController.h new file mode 100644 index 0000000..534e61f --- /dev/null +++ b/cocos/audio/android/AudioMixerController.h @@ -0,0 +1,84 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include "audio/android/utils/Errors.h" +#include "base/std/container/vector.h" + +namespace cc { + +class Track; +class AudioMixer; + +class AudioMixerController { +public: + struct OutputBuffer { + void *buf; + size_t size; + }; + + AudioMixerController(int bufferSizeInFrames, int sampleRate, int channelCount); + + ~AudioMixerController(); + + bool init(); + + bool addTrack(Track *track); + bool hasPlayingTacks(); + + void pause(); + void resume(); + inline bool isPaused() const { return _isPaused; }; + + void mixOneFrame(); + + inline OutputBuffer *current() { return &_mixingBuffer; } + +private: + void destroy(); + void initTrack(Track *track, ccstd::vector &tracksToRemove); + +private: + int _bufferSizeInFrames; + int _sampleRate; + int _channelCount; + + AudioMixer *_mixer; + + std::mutex _activeTracksMutex; + ccstd::vector _activeTracks; + + OutputBuffer _mixingBuffer; + + std::atomic_bool _isPaused; + std::atomic_bool _isMixingFrame; +}; + +} // namespace cc diff --git a/cocos/audio/android/AudioMixerOps.h b/cocos/audio/android/AudioMixerOps.h new file mode 100644 index 0000000..dd479a4 --- /dev/null +++ b/cocos/audio/android/AudioMixerOps.h @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "audio/android/cutils/log.h" + +namespace cc { + +/* Behavior of is_same<>::value is true if the types are identical, + * false otherwise. Identical to the STL std::is_same. + */ +template +struct is_same { + static const bool value = false; +}; + +template +struct is_same // partial specialization +{ + static const bool value = true; +}; + +/* MixMul is a multiplication operator to scale an audio input signal + * by a volume gain, with the formula: + * + * O(utput) = I(nput) * V(olume) + * + * The output, input, and volume may have different types. + * There are 27 variants, of which 14 are actually defined in an + * explicitly templated class. + * + * The following type variables and the underlying meaning: + * + * Output type TO: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1] + * Input signal type TI: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1] + * Volume type TV: int32_t (U4.28) or int16_t (U4.12) or float [-1,1] + * + * For high precision audio, only the = + * needs to be accelerated. This is perhaps the easiest form to do quickly as well. + * + * A generic version is NOT defined to catch any mistake of using it. + */ + +template +TO MixMul(TI value, TV volume); + +template <> +inline int32_t MixMul(int16_t value, int16_t volume) { + return value * volume; +} + +template <> +inline int32_t MixMul(int32_t value, int16_t volume) { + return (value >> 12) * volume; +} + +template <> +inline int32_t MixMul(int16_t value, int32_t volume) { + return value * (volume >> 16); +} + +template <> +inline int32_t MixMul(int32_t value, int32_t volume) { + return (value >> 12) * (volume >> 16); +} + +template <> +inline float MixMul(float value, int16_t volume) { + static const float norm = 1. / (1 << 12); + return value * volume * norm; +} + +template <> +inline float MixMul(float value, int32_t volume) { + static const float norm = 1. / (1 << 28); + return value * volume * norm; +} + +template <> +inline int16_t MixMul(float value, int16_t volume) { + return clamp16_from_float(MixMul(value, volume)); +} + +template <> +inline int16_t MixMul(float value, int32_t volume) { + return clamp16_from_float(MixMul(value, volume)); +} + +template <> +inline float MixMul(int16_t value, int16_t volume) { + static const float norm = 1. / (1 << (15 + 12)); + return static_cast(value) * static_cast(volume) * norm; +} + +template <> +inline float MixMul(int16_t value, int32_t volume) { + static const float norm = 1. / (1ULL << (15 + 28)); + return static_cast(value) * static_cast(volume) * norm; +} + +template <> +inline int16_t MixMul(int16_t value, int16_t volume) { + return clamp16(MixMul(value, volume) >> 12); +} + +template <> +inline int16_t MixMul(int32_t value, int16_t volume) { + return clamp16(MixMul(value, volume) >> 12); +} + +template <> +inline int16_t MixMul(int16_t value, int32_t volume) { + return clamp16(MixMul(value, volume) >> 12); +} + +template <> +inline int16_t MixMul(int32_t value, int32_t volume) { + return clamp16(MixMul(value, volume) >> 12); +} + +/* Required for floating point volume. Some are needed for compilation but + * are not needed in execution and should be removed from the final build by + * an optimizing compiler. + */ +template <> +inline float MixMul(float value, float volume) { + return value * volume; +} + +template <> +inline float MixMul(int16_t value, float volume) { + static const float float_from_q_15 = 1. / (1 << 15); + return value * volume * float_from_q_15; +} + +template <> +inline int32_t MixMul(int32_t value, float volume) { + LOG_ALWAYS_FATAL("MixMul Runtime Should not be here"); + return value * volume; +} + +template <> +inline int32_t MixMul(int16_t value, float volume) { + LOG_ALWAYS_FATAL("MixMul Runtime Should not be here"); + static const float u4_12_from_float = (1 << 12); + return value * volume * u4_12_from_float; +} + +template <> +inline int16_t MixMul(int16_t value, float volume) { + LOG_ALWAYS_FATAL("MixMul Runtime Should not be here"); + return clamp16_from_float(MixMul(value, volume)); +} + +template <> +inline int16_t MixMul(float value, float volume) { + return clamp16_from_float(value * volume); +} + +/* + * MixAccum is used to add into an accumulator register of a possibly different + * type. The TO and TI types are the same as MixMul. + */ + +template +inline void MixAccum(TO *auxaccum, TI value) { + if (!is_same::value) { + LOG_ALWAYS_FATAL("MixAccum type not properly specialized: %zu %zu\n", + sizeof(TO), sizeof(TI)); + } + *auxaccum += value; +} + +template <> +inline void MixAccum(float *auxaccum, int16_t value) { + static const float norm = 1. / (1 << 15); + *auxaccum += norm * value; +} + +template <> +inline void MixAccum(float *auxaccum, int32_t value) { + static const float norm = 1. / (1 << 27); + *auxaccum += norm * value; +} + +template <> +inline void MixAccum(int32_t *auxaccum, int16_t value) { + *auxaccum += value << 12; +} + +template <> +inline void MixAccum(int32_t *auxaccum, float value) { + *auxaccum += clampq4_27_from_float(value); +} + +/* MixMulAux is just like MixMul except it combines with + * an accumulator operation MixAccum. + */ + +template +inline TO MixMulAux(TI value, TV volume, TA *auxaccum) { + MixAccum(auxaccum, value); + return MixMul(value, volume); +} + +/* MIXTYPE is used to determine how the samples in the input frame + * are mixed with volume gain into the output frame. + * See the volumeRampMulti functions below for more details. + */ +enum { + MIXTYPE_MULTI, + MIXTYPE_MONOEXPAND, + MIXTYPE_MULTI_SAVEONLY, + MIXTYPE_MULTI_MONOVOL, + MIXTYPE_MULTI_SAVEONLY_MONOVOL, +}; + +/* + * The volumeRampMulti and volumeRamp functions take a MIXTYPE + * which indicates the per-frame mixing and accumulation strategy. + * + * MIXTYPE_MULTI: + * NCHAN represents number of input and output channels. + * TO: int32_t (Q4.27) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TV: int32_t (U4.28) or int16_t (U4.12) or float + * vol: represents a volume array. + * + * This accumulates into the out pointer. + * + * MIXTYPE_MONOEXPAND: + * Single input channel. NCHAN represents number of output channels. + * TO: int32_t (Q4.27) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TV: int32_t (U4.28) or int16_t (U4.12) or float + * Input channel count is 1. + * vol: represents volume array. + * + * This accumulates into the out pointer. + * + * MIXTYPE_MULTI_SAVEONLY: + * NCHAN represents number of input and output channels. + * TO: int16_t (Q.15) or float + * TI: int32_t (Q4.27) or int16_t (Q0.15) or float + * TV: int32_t (U4.28) or int16_t (U4.12) or float + * vol: represents a volume array. + * + * MIXTYPE_MULTI_SAVEONLY does not accumulate into the out pointer. + * + * MIXTYPE_MULTI_MONOVOL: + * Same as MIXTYPE_MULTI, but uses only volume[0]. + * + * MIXTYPE_MULTI_SAVEONLY_MONOVOL: + * Same as MIXTYPE_MULTI_SAVEONLY, but uses only volume[0]. + * + */ + +template +inline void volumeRampMulti(TO *out, size_t frameCount, + const TI *in, TA *aux, TV *vol, const TV *volinc, TAV *vola, TAV volainc) { +#ifdef ALOGVV + ALOGVV("volumeRampMulti, MIXTYPE:%d\n", MIXTYPE); +#endif + if (aux != NULL) { + do { + TA auxaccum = 0; + switch (MIXTYPE) { + case MIXTYPE_MULTI: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMulAux(*in++, vol[i], &auxaccum); + vol[i] += volinc[i]; + } + break; + case MIXTYPE_MONOEXPAND: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMulAux(*in, vol[i], &auxaccum); + vol[i] += volinc[i]; + } + in++; + break; + case MIXTYPE_MULTI_SAVEONLY: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMulAux(*in++, vol[i], &auxaccum); + vol[i] += volinc[i]; + } + break; + case MIXTYPE_MULTI_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMulAux(*in++, vol[0], &auxaccum); + } + vol[0] += volinc[0]; + break; + case MIXTYPE_MULTI_SAVEONLY_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMulAux(*in++, vol[0], &auxaccum); + } + vol[0] += volinc[0]; + break; + default: + LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE); + break; + } + auxaccum /= NCHAN; + *aux++ += MixMul(auxaccum, *vola); + vola[0] += volainc; + } while (--frameCount); + } else { + do { + switch (MIXTYPE) { + case MIXTYPE_MULTI: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in++, vol[i]); + vol[i] += volinc[i]; + } + break; + case MIXTYPE_MONOEXPAND: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in, vol[i]); + vol[i] += volinc[i]; + } + in++; + break; + case MIXTYPE_MULTI_SAVEONLY: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMul(*in++, vol[i]); + vol[i] += volinc[i]; + } + break; + case MIXTYPE_MULTI_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in++, vol[0]); + } + vol[0] += volinc[0]; + break; + case MIXTYPE_MULTI_SAVEONLY_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMul(*in++, vol[0]); + } + vol[0] += volinc[0]; + break; + default: + LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE); + break; + } + } while (--frameCount); + } +} + +template +inline void volumeMulti(TO *out, size_t frameCount, + const TI *in, TA *aux, const TV *vol, TAV vola) { +#ifdef ALOGVV + ALOGVV("volumeMulti MIXTYPE:%d\n", MIXTYPE); +#endif + if (aux != NULL) { + do { + TA auxaccum = 0; + switch (MIXTYPE) { + case MIXTYPE_MULTI: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMulAux(*in++, vol[i], &auxaccum); + } + break; + case MIXTYPE_MONOEXPAND: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMulAux(*in, vol[i], &auxaccum); + } + in++; + break; + case MIXTYPE_MULTI_SAVEONLY: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMulAux(*in++, vol[i], &auxaccum); + } + break; + case MIXTYPE_MULTI_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMulAux(*in++, vol[0], &auxaccum); + } + break; + case MIXTYPE_MULTI_SAVEONLY_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMulAux(*in++, vol[0], &auxaccum); + } + break; + default: + LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE); + break; + } + auxaccum /= NCHAN; + *aux++ += MixMul(auxaccum, vola); + } while (--frameCount); + } else { + do { + switch (MIXTYPE) { + case MIXTYPE_MULTI: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in++, vol[i]); + } + break; + case MIXTYPE_MONOEXPAND: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in, vol[i]); + } + in++; + break; + case MIXTYPE_MULTI_SAVEONLY: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMul(*in++, vol[i]); + } + break; + case MIXTYPE_MULTI_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ += MixMul(*in++, vol[0]); + } + break; + case MIXTYPE_MULTI_SAVEONLY_MONOVOL: + for (int i = 0; i < NCHAN; ++i) { + *out++ = MixMul(*in++, vol[0]); + } + break; + default: + LOG_ALWAYS_FATAL("invalid mixtype %d", MIXTYPE); + break; + } + } while (--frameCount); + } +} + +} // namespace cc diff --git a/cocos/audio/android/AudioPlayerProvider.cpp b/cocos/audio/android/AudioPlayerProvider.cpp new file mode 100644 index 0000000..7817bfd --- /dev/null +++ b/cocos/audio/android/AudioPlayerProvider.cpp @@ -0,0 +1,520 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include +#include "audio/android/PcmData.h" +#include "audio/include/AudioDef.h" +#include "base/Log.h" +#define LOG_TAG "AudioPlayerProvider" + +#include // for std::find_if +#include +#include +#include "audio/android/AudioDecoder.h" +#include "audio/android/AudioDecoderProvider.h" +#include "audio/android/AudioMixerController.h" +#include "audio/android/AudioPlayerProvider.h" +#include "audio/android/ICallerThreadUtils.h" +#include "audio/android/PcmAudioPlayer.h" +#include "audio/android/PcmAudioService.h" +#include "audio/android/UrlAudioPlayer.h" +#include "audio/android/utils/Utils.h" +#include "base/ThreadPool.h" +#include "base/memory/Memory.h" +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY +#include "cocos/platform/FileUtils.h" +#include "cocos/platform/openharmony/FileUtils-OpenHarmony.h" +#endif +#include // for std::find_if +#include +#include + +namespace cc { + +static int getSystemAPILevel() { + static int sSystemApiLevel = -1; +#if CC_PLATFORM == CC_PLATFORM_ANDROID + if (sSystemApiLevel > 0) { + return sSystemApiLevel; + } + + int apiLevel = getSDKVersion(); + if (apiLevel > 0) { + ALOGD("Android API level: %d", apiLevel); + } else { + ALOGE("Fail to get Android API level!"); + } + sSystemApiLevel = apiLevel; + return apiLevel; +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY + // TODO(qgh): On the openharmony platform, pcm streaming must be used + return std::numeric_limits::max(); +#endif +} + +struct AudioFileIndicator { + ccstd::string extension; + int smallSizeIndicator; +}; + +static AudioFileIndicator gAudioFileIndicator[] = { + {"default", 128000}, // If we could not handle the audio format, return default value, the position should be first. + {".wav", 1024000}, + {".ogg", 128000}, + {".mp3", 160000}}; + +AudioPlayerProvider::AudioPlayerProvider(SLEngineItf engineItf, SLObjectItf outputMixObject, + int deviceSampleRate, int bufferSizeInFrames, + const FdGetterCallback &fdGetterCallback, //NOLINT(modernize-pass-by-value) + ICallerThreadUtils *callerThreadUtils) +: _engineItf(engineItf), _outputMixObject(outputMixObject), _deviceSampleRate(deviceSampleRate), _bufferSizeInFrames(bufferSizeInFrames), _fdGetterCallback(fdGetterCallback), _callerThreadUtils(callerThreadUtils), _pcmAudioService(nullptr), _mixController(nullptr), _threadPool(LegacyThreadPool::newCachedThreadPool(1, 8, 5, 2, 2)) { + ALOGI("deviceSampleRate: %d, bufferSizeInFrames: %d", _deviceSampleRate, _bufferSizeInFrames); + if (getSystemAPILevel() >= 17) { + _mixController = ccnew AudioMixerController(_bufferSizeInFrames, _deviceSampleRate, 2); + _mixController->init(); + _pcmAudioService = ccnew PcmAudioService(engineItf, outputMixObject); + _pcmAudioService->init(_mixController, 2, deviceSampleRate, bufferSizeInFrames * 2); + } + + ALOG_ASSERT(callerThreadUtils != nullptr, "Caller thread utils parameter should not be nullptr!"); +} + +AudioPlayerProvider::~AudioPlayerProvider() { + ALOGV("~AudioPlayerProvider()"); + UrlAudioPlayer::stopAll(); + + SL_SAFE_DELETE(_pcmAudioService); + SL_SAFE_DELETE(_mixController); + SL_SAFE_DELETE(_threadPool); +} + +IAudioPlayer *AudioPlayerProvider::getAudioPlayer(const ccstd::string &audioFilePath) { + // Pcm data decoding by OpenSLES API only supports in API level 17 and later. + if (getSystemAPILevel() < 17) { + AudioFileInfo info = getFileInfo(audioFilePath); + if (info.isValid()) { + return createUrlAudioPlayer(info); + } + + return nullptr; + } + + IAudioPlayer *player = nullptr; + + _pcmCacheMutex.lock(); + auto iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { // Found pcm cache means it was used to be a PcmAudioService + PcmData pcmData = iter->second; + _pcmCacheMutex.unlock(); + player = obtainPcmAudioPlayer(audioFilePath, pcmData); + ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str()); + } else { + _pcmCacheMutex.unlock(); + // Check audio file size to determine to use a PcmAudioService or UrlAudioPlayer, + // generally PcmAudioService is used for playing short audio like game effects while + // playing background music uses UrlAudioPlayer + AudioFileInfo info = getFileInfo(audioFilePath); + if (info.isValid()) { + if (isSmallFile(info)) { + // Put an empty lambda to preloadEffect since we only want the future object to get PcmData + auto pcmData = std::make_shared(); + auto isSucceed = std::make_shared(false); + auto isReturnFromCache = std::make_shared(false); + auto isPreloadFinished = std::make_shared(false); + + std::thread::id threadId = std::this_thread::get_id(); + + void *infoPtr = &info; + ccstd::string url = info.url; + preloadEffect( + info, [infoPtr, url, threadId, pcmData, isSucceed, isReturnFromCache, isPreloadFinished](bool succeed, PcmData data) { + // If the callback is in the same thread as caller's, it means that we found it + // in the cache + *isReturnFromCache = std::this_thread::get_id() == threadId; + *pcmData = std::move(data); + *isSucceed = succeed; + *isPreloadFinished = true; + ALOGV("FileInfo (%p), Set isSucceed flag: %d, path: %s", infoPtr, succeed, url.c_str()); + }, + true); + + if (!*isReturnFromCache && !*isPreloadFinished) { + std::unique_lock lck(_preloadWaitMutex); + // Wait for 2 seconds for the decoding in sub thread finishes. + ALOGV("FileInfo (%p), Waiting preload (%s) to finish ...", &info, audioFilePath.c_str()); + _preloadWaitCond.wait_for(lck, std::chrono::seconds(2)); + ALOGV("FileInfo (%p), Waitup preload (%s) ...", &info, audioFilePath.c_str()); + } + + if (*isSucceed) { + if (pcmData->isValid()) { + player = obtainPcmAudioPlayer(info.url, *pcmData); + ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str()); + } else { + ALOGE("pcm data is invalid, path: %s", audioFilePath.c_str()); + } + } else { + ALOGE("FileInfo (%p), preloadEffect (%s) failed", &info, audioFilePath.c_str()); + } + } else { + player = createUrlAudioPlayer(info); + ALOGV_IF(player == nullptr, "%s, %d: player is nullptr, path: %s", __FUNCTION__, __LINE__, audioFilePath.c_str()); + } + } else { + ALOGE("File info is invalid, path: %s", audioFilePath.c_str()); + } + } + + ALOGV_IF(player == nullptr, "%s, %d return nullptr", __FUNCTION__, __LINE__); + return player; +} + +void AudioPlayerProvider::preloadEffect(const ccstd::string &audioFilePath, const PreloadCallback &callback) { + // Pcm data decoding by OpenSLES API only supports in API level 17 and later. + if (getSystemAPILevel() < 17) { + PcmData data; + callback(true, data); + return; + } + + _pcmCacheMutex.lock(); + auto &&iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { + ALOGV("preload return from cache: (%s)", audioFilePath.c_str()); + _pcmCacheMutex.unlock(); + callback(true, iter->second); + return; + } + _pcmCacheMutex.unlock(); + + auto info = getFileInfo(audioFilePath); + preloadEffect( + info, [this, callback, audioFilePath](bool succeed, const PcmData &data) { + _callerThreadUtils->performFunctionInCallerThread([this, succeed, data, callback]() { + callback(succeed, data); + }); + }, + false); +} + +// Used internally +void AudioPlayerProvider::preloadEffect(const AudioFileInfo &info, const PreloadCallback &callback, bool isPreloadInPlay2d) { + PcmData pcmData; + + if (!info.isValid()) { + callback(false, pcmData); + return; + } + + if (isSmallFile(info)) { + ccstd::string audioFilePath = info.url; + + // 1. First time check, if it wasn't in the cache, goto 2 step + _pcmCacheMutex.lock(); + auto &&iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { + ALOGV("1. Return pcm data from cache, url: %s", info.url.c_str()); + _pcmCacheMutex.unlock(); + callback(true, iter->second); + return; + } + _pcmCacheMutex.unlock(); + + { + // 2. Check whether the audio file is being preloaded, if it has been removed from map just now, + // goto step 3 + std::lock_guard lck(_preloadCallbackMutex); + + auto &&preloadIter = _preloadCallbackMap.find(audioFilePath); + if (preloadIter != _preloadCallbackMap.end()) { + ALOGV("audio (%s) is being preloaded, add to callback vector!", audioFilePath.c_str()); + PreloadCallbackParam param; + param.callback = callback; + param.isPreloadInPlay2d = isPreloadInPlay2d; + preloadIter->second.push_back(std::move(param)); + return; + } + + // 3. Check it in cache again. If it has been removed from map just now, the file is in + // the cache absolutely. + _pcmCacheMutex.lock(); + auto &&iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { + ALOGV("2. Return pcm data from cache, url: %s", info.url.c_str()); + _pcmCacheMutex.unlock(); + callback(true, iter->second); + return; + } + _pcmCacheMutex.unlock(); + + PreloadCallbackParam param; + param.callback = callback; + param.isPreloadInPlay2d = isPreloadInPlay2d; + ccstd::vector callbacks; + callbacks.push_back(std::move(param)); + _preloadCallbackMap.insert(std::make_pair(audioFilePath, std::move(callbacks))); + } + + _threadPool->pushTask([this, audioFilePath](int /*tid*/) { + ALOGV("AudioPlayerProvider::preloadEffect: (%s)", audioFilePath.c_str()); + PcmData d; + AudioDecoder *decoder = AudioDecoderProvider::createAudioDecoder(_engineItf, audioFilePath, _bufferSizeInFrames, _deviceSampleRate, _fdGetterCallback); + bool ret = decoder != nullptr && decoder->start(); + if (ret) { + d = decoder->getResult(); + std::lock_guard lck(_pcmCacheMutex); + _pcmCache.insert(std::make_pair(audioFilePath, d)); + } else { + ALOGE("decode (%s) failed!", audioFilePath.c_str()); + } + + ALOGV("decode %s", (ret ? "succeed" : "failed")); + + std::lock_guard lck(_preloadCallbackMutex); + auto &&preloadIter = _preloadCallbackMap.find(audioFilePath); + if (preloadIter != _preloadCallbackMap.end()) { + auto &¶ms = preloadIter->second; + ALOGV("preload (%s) callback count: %d", audioFilePath.c_str(), (int)params.size()); + PcmData result = decoder->getResult(); + for (auto &¶m : params) { + param.callback(ret, result); + if (param.isPreloadInPlay2d) { + _preloadWaitCond.notify_one(); + } + } + _preloadCallbackMap.erase(preloadIter); + } + + AudioDecoderProvider::destroyAudioDecoder(&decoder); + }); + } else { + ALOGV("File (%s) is too large, ignore preload!", info.url.c_str()); + callback(true, pcmData); + } +} + +AudioPlayerProvider::AudioFileInfo AudioPlayerProvider::getFileInfo( + const ccstd::string &audioFilePath) { + AudioFileInfo info; + long fileSize = 0; //NOLINT(google-runtime-int) + off_t start = 0; + off_t length = 0; + int assetFd = -1; +#if CC_PLATFORM == CC_PLATFORM_ANDROID + if (audioFilePath[0] != '/') { + ccstd::string relativePath; + size_t position = audioFilePath.find("@assets/"); + + if (0 == position) { + // "@assets/" is at the beginning of the path and we don't want it + relativePath = audioFilePath.substr(strlen("@assets/")); + } else { + relativePath = audioFilePath; + } + + assetFd = _fdGetterCallback(relativePath, &start, &length); + + if (assetFd <= 0) { + ALOGE("Failed to open file descriptor for '%s'", audioFilePath.c_str()); + return info; + } + + fileSize = length; + } else { + FILE *fp = fopen(audioFilePath.c_str(), "rb"); + if (fp != nullptr) { + fseek(fp, 0, SEEK_END); + fileSize = ftell(fp); + fclose(fp); + } else { + return info; + } + } + info.url = audioFilePath; + info.assetFd = std::make_shared(assetFd); + info.start = start; + info.length = fileSize; +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY + FileUtilsOpenHarmony* fileUtils = dynamic_cast(FileUtils::getInstance()); + if(!fileUtils) { + return info; + } + + RawFileDescriptor descriptor; + fileUtils->getRawFileDescriptor(audioFilePath, descriptor); + info.url = audioFilePath; + info.assetFd = std::make_shared(descriptor.fd); + info.start = descriptor.start; + info.length = descriptor.length; +#endif + + ALOGV("(%s) file size: %ld", audioFilePath.c_str(), fileSize); + + return info; +} + +bool AudioPlayerProvider::isSmallFile(const AudioFileInfo &info) { //NOLINT(readability-convert-member-functions-to-static) +#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY + // TODO(qgh): OpenHarmony system does not support this function yet + return true; +#endif + //REFINE: If file size is smaller than 100k, we think it's a small file. This value should be set by developers. + auto &audioFileInfo = const_cast(info); + size_t judgeCount = sizeof(gAudioFileIndicator) / sizeof(gAudioFileIndicator[0]); + size_t pos = audioFileInfo.url.rfind('.'); + ccstd::string extension; + if (pos != ccstd::string::npos) { + extension = audioFileInfo.url.substr(pos); + } + auto *iter = std::find_if(std::begin(gAudioFileIndicator), std::end(gAudioFileIndicator), + [&extension](const AudioFileIndicator &judge) -> bool { + return judge.extension == extension; + }); + + if (iter != std::end(gAudioFileIndicator)) { + // ALOGV("isSmallFile: found: %s: ", iter->extension.c_str()); + return info.length < iter->smallSizeIndicator; + } + + // ALOGV("isSmallFile: not found return default value"); + return info.length < gAudioFileIndicator[0].smallSizeIndicator; +} + +float AudioPlayerProvider::getDurationFromFile(const ccstd::string &filePath) { + std::lock_guard lck(_pcmCacheMutex); + auto iter = _pcmCache.find(filePath); + if (iter != _pcmCache.end()) { + return iter->second.duration; + } + return 0; +} + +void AudioPlayerProvider::clearPcmCache(const ccstd::string &audioFilePath) { + std::lock_guard lck(_pcmCacheMutex); + auto iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { + ALOGV("clear pcm cache: (%s)", audioFilePath.c_str()); + _pcmCache.erase(iter); + } else { + ALOGW("Couldn't find the pcm cache: (%s)", audioFilePath.c_str()); + } +} + +void AudioPlayerProvider::clearAllPcmCaches() { + std::lock_guard lck(_pcmCacheMutex); + _pcmCache.clear(); +} + +PcmAudioPlayer *AudioPlayerProvider::obtainPcmAudioPlayer(const ccstd::string &url, + const PcmData &pcmData) { + PcmAudioPlayer *pcmPlayer = nullptr; + if (pcmData.isValid()) { + pcmPlayer = ccnew PcmAudioPlayer(_mixController, _callerThreadUtils); + if (pcmPlayer != nullptr) { + pcmPlayer->prepare(url, pcmData); + } + } else { + ALOGE("obtainPcmAudioPlayer failed, pcmData isn't valid!"); + } + return pcmPlayer; +} + +UrlAudioPlayer *AudioPlayerProvider::createUrlAudioPlayer( + const AudioPlayerProvider::AudioFileInfo &info) { + if (info.url.empty()) { + ALOGE("createUrlAudioPlayer failed, url is empty!"); + return nullptr; + } +#if CC_PLATFORM == CC_PLATFORM_ANDROID + SLuint32 locatorType = info.assetFd->getFd() > 0 ? SL_DATALOCATOR_ANDROIDFD : SL_DATALOCATOR_URI; +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY + SLuint32 locatorType = SL_DATALOCATOR_URI; +#endif + + auto *urlPlayer = new (std::nothrow) UrlAudioPlayer(_engineItf, _outputMixObject, _callerThreadUtils); + bool ret = urlPlayer->prepare(info.url, locatorType, info.assetFd, info.start, info.length); + if (!ret) { + SL_SAFE_DELETE(urlPlayer); + } + return urlPlayer; +} + +void AudioPlayerProvider::pause() { + if (_mixController != nullptr) { + _mixController->pause(); + } + + if (_pcmAudioService != nullptr) { + _pcmAudioService->pause(); + } +} + +void AudioPlayerProvider::resume() { + if (_mixController != nullptr) { + _mixController->resume(); + } + + if (_pcmAudioService != nullptr) { + _pcmAudioService->resume(); + } +} +void AudioPlayerProvider::registerPcmData(const ccstd::string &audioFilePath, PcmData &data) { + std::lock_guard lck(_pcmCacheMutex); + if (_pcmCache.find(audioFilePath) != _pcmCache.end()) { + CC_LOG_DEBUG("file %s pcm data is already cached.", audioFilePath.c_str()); + return; + } + _pcmCache.emplace(audioFilePath, data); +} + +bool AudioPlayerProvider::getPcmHeader(const ccstd::string &audioFilePath, PCMHeader &header) { + std::lock_guard lck(_pcmCacheMutex); + auto &&iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { + ALOGV("get pcm header from cache, url: %s", audioFilePath.c_str()); + // On Android, all pcm buffer is resampled to sign16. + header.bytesPerFrame = iter->second.bitsPerSample / 8; + header.channelCount = iter->second.numChannels; + header.dataFormat = AudioDataFormat::SIGNED_16; + header.sampleRate = iter->second.sampleRate; + header.totalFrames = iter->second.numFrames; + return true; + } + return false; +} +bool AudioPlayerProvider::getPcmData(const ccstd::string &audioFilePath, PcmData &data) { + std::lock_guard lck(_pcmCacheMutex); + auto &&iter = _pcmCache.find(audioFilePath); + if (iter != _pcmCache.end()) { + ALOGV("get pcm buffer from cache, url: %s", audioFilePath.c_str()); + // On Android, all pcm buffer is resampled to sign16. + data = iter->second; + return true; + } + return false; +} +} // namespace cc diff --git a/cocos/audio/android/AudioPlayerProvider.h b/cocos/audio/android/AudioPlayerProvider.h new file mode 100644 index 0000000..6b42cc1 --- /dev/null +++ b/cocos/audio/android/AudioPlayerProvider.h @@ -0,0 +1,122 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "audio/android/IAudioPlayer.h" +#include "audio/android/OpenSLHelper.h" +#include "audio/android/PcmData.h" +#include "audio/include/AudioDef.h" +#include "base/std/container/unordered_map.h" + +namespace cc { +// Manage PcmAudioPlayer& UrlAudioPlayer + +class PcmAudioPlayer; +class PcmAudioService; +class UrlAudioPlayer; +class AudioMixerController; +class ICallerThreadUtils; +class AssetFd; +class LegacyThreadPool; + +class AudioPlayerProvider { +public: + AudioPlayerProvider(SLEngineItf engineItf, SLObjectItf outputMixObject, int deviceSampleRate, + int bufferSizeInFrames, const FdGetterCallback &fdGetterCallback, + ICallerThreadUtils *callerThreadUtils); + + virtual ~AudioPlayerProvider(); + bool isFileCached(const ccstd::string &audioFilePath); + IAudioPlayer *getAudioPlayer(const ccstd::string &audioFilePath); + bool getPcmHeader(const ccstd::string &audioFilePath, PCMHeader &header); + bool getPcmData(const ccstd::string &audioFilePath, PcmData &data); + using PreloadCallback = std::function; + void preloadEffect(const ccstd::string &audioFilePath, const PreloadCallback &callback); + void registerPcmData(const ccstd::string &audioFilePath, PcmData &data); + float getDurationFromFile(const ccstd::string &filePath); + void clearPcmCache(const ccstd::string &audioFilePath); + + void clearAllPcmCaches(); + + void pause(); + + void resume(); + +private: + struct AudioFileInfo { + ccstd::string url; + std::shared_ptr assetFd; + off_t start{}; + off_t length; + + AudioFileInfo() + : assetFd(nullptr) {} + + inline bool isValid() const { + return !url.empty() && length > 0; + } + }; + + PcmAudioPlayer *obtainPcmAudioPlayer(const ccstd::string &url, const PcmData &pcmData); + + UrlAudioPlayer *createUrlAudioPlayer(const AudioFileInfo &info); + + void preloadEffect(const AudioFileInfo &info, const PreloadCallback &callback, bool isPreloadInPlay2d); + + AudioFileInfo getFileInfo(const ccstd::string &audioFilePath); + + bool isSmallFile(const AudioFileInfo &info); + + SLEngineItf _engineItf; + SLObjectItf _outputMixObject; + int _deviceSampleRate; + int _bufferSizeInFrames; + FdGetterCallback _fdGetterCallback; + ICallerThreadUtils *_callerThreadUtils; + + ccstd::unordered_map _pcmCache; + std::mutex _pcmCacheMutex; + + struct PreloadCallbackParam { + PreloadCallback callback; + bool isPreloadInPlay2d; + }; + + ccstd::unordered_map> _preloadCallbackMap; + std::mutex _preloadCallbackMutex; + + std::mutex _preloadWaitMutex; + std::condition_variable _preloadWaitCond; + + PcmAudioService *_pcmAudioService; + AudioMixerController *_mixController; + + LegacyThreadPool *_threadPool; +}; + +} // namespace cc diff --git a/cocos/audio/android/AudioResampler.cpp b/cocos/audio/android/AudioResampler.cpp new file mode 100644 index 0000000..32e23c4 --- /dev/null +++ b/cocos/audio/android/AudioResampler.cpp @@ -0,0 +1,792 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AudioResampler" +//#define LOG_NDEBUG 0 + +#include +#include +#include +#include +#include +#include "audio/android/cutils/log.h" +#include "audio/android/utils/Utils.h" +//#include +#include "audio/android/AudioResampler.h" +#include "audio/common/utils/include/primitives.h" +//#include "audio/android/AudioResamplerSinc.h" +#include "audio/android/AudioResamplerCubic.h" +#include "base/memory/Memory.h" + +//#include "AudioResamplerDyn.h" + +//cjh #ifdef __arm__ +// #define ASM_ARM_RESAMP1 // enable asm optimisation for ResamplerOrder1 +//#endif + +namespace cc { + +// ---------------------------------------------------------------------------- + +class AudioResamplerOrder1 : public AudioResampler { +public: + AudioResamplerOrder1(int inChannelCount, int32_t sampleRate) : AudioResampler(inChannelCount, sampleRate, LOW_QUALITY), mX0L(0), mX0R(0) { + } + virtual size_t resample(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider); + +private: + // number of bits used in interpolation multiply - 15 bits avoids overflow + static const int kNumInterpBits = 15; + + // bits to shift the phase fraction down to avoid overflow + static const int kPreInterpShift = kNumPhaseBits - kNumInterpBits; + + void init() {} + size_t resampleMono16(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider); + size_t resampleStereo16(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider); +#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1 + void AsmMono16Loop(int16_t *in, int32_t *maxOutPt, int32_t maxInIdx, + size_t &outputIndex, int32_t *out, size_t &inputIndex, int32_t vl, int32_t vr, + uint32_t &phaseFraction, uint32_t phaseIncrement); + void AsmStereo16Loop(int16_t *in, int32_t *maxOutPt, int32_t maxInIdx, + size_t &outputIndex, int32_t *out, size_t &inputIndex, int32_t vl, int32_t vr, + uint32_t &phaseFraction, uint32_t phaseIncrement); +#endif // ASM_ARM_RESAMP1 + + static inline int32_t Interp(int32_t x0, int32_t x1, uint32_t f) { + return x0 + (((x1 - x0) * (int32_t)(f >> kPreInterpShift)) >> kNumInterpBits); + } + static inline void Advance(size_t *index, uint32_t *frac, uint32_t inc) { + *frac += inc; + *index += (size_t)(*frac >> kNumPhaseBits); + *frac &= kPhaseMask; + } + int mX0L; + int mX0R; +}; + +/*static*/ +const double AudioResampler::kPhaseMultiplier = 1L << AudioResampler::kNumPhaseBits; + +bool AudioResampler::qualityIsSupported(src_quality quality) { + switch (quality) { + case DEFAULT_QUALITY: + case LOW_QUALITY: + case MED_QUALITY: + case HIGH_QUALITY: + case VERY_HIGH_QUALITY: + return true; + default: + return false; + } +} + +// ---------------------------------------------------------------------------- + +static pthread_once_t once_control = PTHREAD_ONCE_INIT; +static AudioResampler::src_quality defaultQuality = AudioResampler::DEFAULT_QUALITY; + +void AudioResampler::init_routine() { + // int resamplerQuality = getSystemProperty("af.resampler.quality"); + // if (resamplerQuality > 0) { + // defaultQuality = (src_quality) resamplerQuality; + // ALOGD("forcing AudioResampler quality to %d", defaultQuality); + // if (defaultQuality < DEFAULT_QUALITY || defaultQuality > VERY_HIGH_QUALITY) { + // defaultQuality = DEFAULT_QUALITY; + // } + // } +} + +uint32_t AudioResampler::qualityMHz(src_quality quality) { + switch (quality) { + default: + case DEFAULT_QUALITY: + case LOW_QUALITY: + return 3; + case MED_QUALITY: + return 6; + case HIGH_QUALITY: + return 20; + case VERY_HIGH_QUALITY: + return 34; + // case DYN_LOW_QUALITY: + // return 4; + // case DYN_MED_QUALITY: + // return 6; + // case DYN_HIGH_QUALITY: + // return 12; + } +} + +static const uint32_t maxMHz = 130; // an arbitrary number that permits 3 VHQ, should be tunable +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static uint32_t currentMHz = 0; + +AudioResampler *AudioResampler::create(audio_format_t format, int inChannelCount, + int32_t sampleRate, src_quality quality) { + bool atFinalQuality; + if (quality == DEFAULT_QUALITY) { + // read the resampler default quality property the first time it is needed + int ok = pthread_once(&once_control, init_routine); + if (ok != 0) { + ALOGE("%s pthread_once failed: %d", __func__, ok); + } + quality = defaultQuality; + atFinalQuality = false; + } else { + atFinalQuality = true; + } + + /* if the caller requests DEFAULT_QUALITY and af.resampler.property + * has not been set, the target resampler quality is set to DYN_MED_QUALITY, + * and allowed to "throttle" down to DYN_LOW_QUALITY if necessary + * due to estimated CPU load of having too many active resamplers + * (the code below the if). + */ + if (quality == DEFAULT_QUALITY) { + //cjh quality = DYN_MED_QUALITY; + } + + // naive implementation of CPU load throttling doesn't account for whether resampler is active + pthread_mutex_lock(&mutex); + for (;;) { + uint32_t deltaMHz = qualityMHz(quality); + uint32_t newMHz = currentMHz + deltaMHz; + if ((qualityIsSupported(quality) && newMHz <= maxMHz) || atFinalQuality) { + ALOGV("resampler load %u -> %u MHz due to delta +%u MHz from quality %d", + currentMHz, newMHz, deltaMHz, quality); + currentMHz = newMHz; + break; + } + // not enough CPU available for proposed quality level, so try next lowest level + switch (quality) { + default: + case LOW_QUALITY: + atFinalQuality = true; + break; + case MED_QUALITY: + quality = LOW_QUALITY; + break; + case HIGH_QUALITY: + quality = MED_QUALITY; + break; + case VERY_HIGH_QUALITY: + quality = HIGH_QUALITY; + break; + // case DYN_LOW_QUALITY: + // atFinalQuality = true; + // break; + // case DYN_MED_QUALITY: + // quality = DYN_LOW_QUALITY; + // break; + // case DYN_HIGH_QUALITY: + // quality = DYN_MED_QUALITY; + // break; + } + } + pthread_mutex_unlock(&mutex); + + AudioResampler *resampler; + + switch (quality) { + default: + case LOW_QUALITY: + ALOGV("Create linear Resampler"); + LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format"); + resampler = ccnew AudioResamplerOrder1(inChannelCount, sampleRate); + break; + case MED_QUALITY: + ALOGV("Create cubic Resampler"); + LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format"); + resampler = ccnew AudioResamplerCubic(inChannelCount, sampleRate); + break; + case HIGH_QUALITY: + ALOGV("Create HIGH_QUALITY sinc Resampler"); + LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format"); + ALOG_ASSERT(false, "HIGH_QUALITY isn't supported"); + // Cocos2d-x only uses MED_QUALITY, so we could remove Sinc relative files + // resampler = ccnew AudioResamplerSinc(inChannelCount, sampleRate); + break; + case VERY_HIGH_QUALITY: + ALOGV("Create VERY_HIGH_QUALITY sinc Resampler = %d", quality); + LOG_ALWAYS_FATAL_IF(format != AUDIO_FORMAT_PCM_16_BIT, "invalid pcm format"); + // Cocos2d-x only uses MED_QUALITY, so we could remove Sinc relative files + // resampler = ccnew AudioResamplerSinc(inChannelCount, sampleRate, quality); + ALOG_ASSERT(false, "VERY_HIGH_QUALITY isn't supported"); + break; + } + + // initialize resampler + resampler->init(); + return resampler; +} + +AudioResampler::AudioResampler(int inChannelCount, + int32_t sampleRate, src_quality quality) : mChannelCount(inChannelCount), + mSampleRate(sampleRate), + mInSampleRate(sampleRate), + mInputIndex(0), + mPhaseFraction(0), + mLocalTimeFreq(0), + mPTS(AudioBufferProvider::kInvalidPTS), + mQuality(quality) { + const int maxChannels = 2; //cjh quality < DYN_LOW_QUALITY ? 2 : 8; + if (inChannelCount < 1 || inChannelCount > maxChannels) { + LOG_ALWAYS_FATAL("Unsupported sample format %d quality %d channels", + quality, inChannelCount); + } + if (sampleRate <= 0) { + LOG_ALWAYS_FATAL("Unsupported sample rate %d Hz", sampleRate); + } + + // initialize common members + mVolume[0] = mVolume[1] = 0; + mBuffer.frameCount = 0; +} + +AudioResampler::~AudioResampler() { + pthread_mutex_lock(&mutex); + src_quality quality = getQuality(); + uint32_t deltaMHz = qualityMHz(quality); + int32_t newMHz = currentMHz - deltaMHz; + ALOGV("resampler load %u -> %d MHz due to delta -%u MHz from quality %d", + currentMHz, newMHz, deltaMHz, quality); + LOG_ALWAYS_FATAL_IF(newMHz < 0, "negative resampler load %d MHz", newMHz); + currentMHz = newMHz; + pthread_mutex_unlock(&mutex); +} + +void AudioResampler::setSampleRate(int32_t inSampleRate) { + mInSampleRate = inSampleRate; + mPhaseIncrement = (uint32_t)((kPhaseMultiplier * inSampleRate) / mSampleRate); +} + +void AudioResampler::setVolume(float left, float right) { + // REFINE: Implement anti-zipper filter + // convert to U4.12 for internal integer use (round down) + // integer volume values are clamped to 0 to UNITY_GAIN. + mVolume[0] = u4_12_from_float(clampFloatVol(left)); + mVolume[1] = u4_12_from_float(clampFloatVol(right)); +} + +void AudioResampler::setLocalTimeFreq(uint64_t freq) { + mLocalTimeFreq = freq; +} + +void AudioResampler::setPTS(int64_t pts) { + mPTS = pts; +} + +int64_t AudioResampler::calculateOutputPTS(int outputFrameIndex) { + if (mPTS == AudioBufferProvider::kInvalidPTS) { + return AudioBufferProvider::kInvalidPTS; + } else { + return mPTS + ((outputFrameIndex * mLocalTimeFreq) / mSampleRate); + } +} + +void AudioResampler::reset() { + mInputIndex = 0; + mPhaseFraction = 0; + mBuffer.frameCount = 0; +} + +// ---------------------------------------------------------------------------- + +size_t AudioResamplerOrder1::resample(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider) { + // should never happen, but we overflow if it does + // ALOG_ASSERT(outFrameCount < 32767); + + // select the appropriate resampler + switch (mChannelCount) { + case 1: + return resampleMono16(out, outFrameCount, provider); + case 2: + return resampleStereo16(out, outFrameCount, provider); + default: + LOG_ALWAYS_FATAL("invalid channel count: %d", mChannelCount); + return 0; + } +} + +size_t AudioResamplerOrder1::resampleStereo16(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider) { + int32_t vl = mVolume[0]; + int32_t vr = mVolume[1]; + + size_t inputIndex = mInputIndex; + uint32_t phaseFraction = mPhaseFraction; + uint32_t phaseIncrement = mPhaseIncrement; + size_t outputIndex = 0; + size_t outputSampleCount = outFrameCount * 2; + size_t inFrameCount = getInFrameCountRequired(outFrameCount); + + // ALOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d", + // outFrameCount, inputIndex, phaseFraction, phaseIncrement); + + while (outputIndex < outputSampleCount) { + // buffer is empty, fetch a new one + while (mBuffer.frameCount == 0) { + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer, + calculateOutputPTS(outputIndex / 2)); + if (mBuffer.raw == NULL) { + goto resampleStereo16_exit; + } + + // ALOGE("New buffer fetched: %d frames", mBuffer.frameCount); + if (mBuffer.frameCount > inputIndex) break; + + inputIndex -= mBuffer.frameCount; + mX0L = mBuffer.i16[mBuffer.frameCount * 2 - 2]; + mX0R = mBuffer.i16[mBuffer.frameCount * 2 - 1]; + provider->releaseBuffer(&mBuffer); + // mBuffer.frameCount == 0 now so we reload a new buffer + } + + int16_t *in = mBuffer.i16; + + // handle boundary case + while (inputIndex == 0) { + // ALOGE("boundary case"); + out[outputIndex++] += vl * Interp(mX0L, in[0], phaseFraction); + out[outputIndex++] += vr * Interp(mX0R, in[1], phaseFraction); + Advance(&inputIndex, &phaseFraction, phaseIncrement); + if (outputIndex == outputSampleCount) { + break; + } + } + + // process input samples + // ALOGE("general case"); + +#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1 + if (inputIndex + 2 < mBuffer.frameCount) { + int32_t *maxOutPt; + int32_t maxInIdx; + + maxOutPt = out + (outputSampleCount - 2); // 2 because 2 frames per loop + maxInIdx = mBuffer.frameCount - 2; + AsmStereo16Loop(in, maxOutPt, maxInIdx, outputIndex, out, inputIndex, vl, vr, + phaseFraction, phaseIncrement); + } +#endif // ASM_ARM_RESAMP1 + + while (outputIndex < outputSampleCount && inputIndex < mBuffer.frameCount) { + out[outputIndex++] += vl * Interp(in[inputIndex * 2 - 2], + in[inputIndex * 2], phaseFraction); + out[outputIndex++] += vr * Interp(in[inputIndex * 2 - 1], + in[inputIndex * 2 + 1], phaseFraction); + Advance(&inputIndex, &phaseFraction, phaseIncrement); + } + + // ALOGE("loop done - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex); + + // if done with buffer, save samples + if (inputIndex >= mBuffer.frameCount) { + inputIndex -= mBuffer.frameCount; + + // ALOGE("buffer done, new input index %d", inputIndex); + + mX0L = mBuffer.i16[mBuffer.frameCount * 2 - 2]; + mX0R = mBuffer.i16[mBuffer.frameCount * 2 - 1]; + provider->releaseBuffer(&mBuffer); + + // verify that the releaseBuffer resets the buffer frameCount + // ALOG_ASSERT(mBuffer.frameCount == 0); + } + } + + // ALOGE("output buffer full - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex); + +resampleStereo16_exit: + // save state + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; + return outputIndex / 2 /* channels for stereo */; +} + +size_t AudioResamplerOrder1::resampleMono16(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider) { + int32_t vl = mVolume[0]; + int32_t vr = mVolume[1]; + + size_t inputIndex = mInputIndex; + uint32_t phaseFraction = mPhaseFraction; + uint32_t phaseIncrement = mPhaseIncrement; + size_t outputIndex = 0; + size_t outputSampleCount = outFrameCount * 2; + size_t inFrameCount = getInFrameCountRequired(outFrameCount); + + // ALOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d", + // outFrameCount, inputIndex, phaseFraction, phaseIncrement); + while (outputIndex < outputSampleCount) { + // buffer is empty, fetch a new one + while (mBuffer.frameCount == 0) { + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer, + calculateOutputPTS(outputIndex / 2)); + if (mBuffer.raw == NULL) { + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; + goto resampleMono16_exit; + } + // ALOGE("New buffer fetched: %d frames", mBuffer.frameCount); + if (mBuffer.frameCount > inputIndex) break; + + inputIndex -= mBuffer.frameCount; + mX0L = mBuffer.i16[mBuffer.frameCount - 1]; + provider->releaseBuffer(&mBuffer); + // mBuffer.frameCount == 0 now so we reload a new buffer + } + int16_t *in = mBuffer.i16; + + // handle boundary case + while (inputIndex == 0) { + // ALOGE("boundary case"); + int32_t sample = Interp(mX0L, in[0], phaseFraction); + out[outputIndex++] += vl * sample; + out[outputIndex++] += vr * sample; + Advance(&inputIndex, &phaseFraction, phaseIncrement); + if (outputIndex == outputSampleCount) { + break; + } + } + + // process input samples + // ALOGE("general case"); + +#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1 + if (inputIndex + 2 < mBuffer.frameCount) { + int32_t *maxOutPt; + int32_t maxInIdx; + + maxOutPt = out + (outputSampleCount - 2); + maxInIdx = (int32_t)mBuffer.frameCount - 2; + AsmMono16Loop(in, maxOutPt, maxInIdx, outputIndex, out, inputIndex, vl, vr, + phaseFraction, phaseIncrement); + } +#endif // ASM_ARM_RESAMP1 + + while (outputIndex < outputSampleCount && inputIndex < mBuffer.frameCount) { + int32_t sample = Interp(in[inputIndex - 1], in[inputIndex], + phaseFraction); + out[outputIndex++] += vl * sample; + out[outputIndex++] += vr * sample; + Advance(&inputIndex, &phaseFraction, phaseIncrement); + } + + // ALOGE("loop done - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex); + + // if done with buffer, save samples + if (inputIndex >= mBuffer.frameCount) { + inputIndex -= mBuffer.frameCount; + + // ALOGE("buffer done, new input index %d", inputIndex); + + mX0L = mBuffer.i16[mBuffer.frameCount - 1]; + provider->releaseBuffer(&mBuffer); + + // verify that the releaseBuffer resets the buffer frameCount + // ALOG_ASSERT(mBuffer.frameCount == 0); + } + } + + // ALOGE("output buffer full - outputIndex=%d, inputIndex=%d", outputIndex, inputIndex); + +resampleMono16_exit: + // save state + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; + return outputIndex; +} + +#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1 + +/******************************************************************* +* +* AsmMono16Loop +* asm optimized monotonic loop version; one loop is 2 frames +* Input: +* in : pointer on input samples +* maxOutPt : pointer on first not filled +* maxInIdx : index on first not used +* outputIndex : pointer on current output index +* out : pointer on output buffer +* inputIndex : pointer on current input index +* vl, vr : left and right gain +* phaseFraction : pointer on current phase fraction +* phaseIncrement +* Output: +* outputIndex : +* out : updated buffer +* inputIndex : index of next to use +* phaseFraction : phase fraction for next interpolation +* +*******************************************************************/ +__attribute__((noinline)) void AudioResamplerOrder1::AsmMono16Loop(int16_t *in, int32_t *maxOutPt, int32_t maxInIdx, + size_t &outputIndex, int32_t *out, size_t &inputIndex, int32_t vl, int32_t vr, + uint32_t &phaseFraction, uint32_t phaseIncrement) { + (void)maxOutPt; // remove unused parameter warnings + (void)maxInIdx; + (void)outputIndex; + (void)out; + (void)inputIndex; + (void)vl; + (void)vr; + (void)phaseFraction; + (void)phaseIncrement; + (void)in; + #define MO_PARAM5 "36" // offset of parameter 5 (outputIndex) + + asm( + "stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, lr}\n" + // get parameters + " ldr r6, [sp, #" MO_PARAM5 + " + 20]\n" // &phaseFraction + " ldr r6, [r6]\n" // phaseFraction + " ldr r7, [sp, #" MO_PARAM5 + " + 8]\n" // &inputIndex + " ldr r7, [r7]\n" // inputIndex + " ldr r8, [sp, #" MO_PARAM5 + " + 4]\n" // out + " ldr r0, [sp, #" MO_PARAM5 + " + 0]\n" // &outputIndex + " ldr r0, [r0]\n" // outputIndex + " add r8, r8, r0, asl #2\n" // curOut + " ldr r9, [sp, #" MO_PARAM5 + " + 24]\n" // phaseIncrement + " ldr r10, [sp, #" MO_PARAM5 + " + 12]\n" // vl + " ldr r11, [sp, #" MO_PARAM5 + " + 16]\n" // vr + + // r0 pin, x0, Samp + + // r1 in + // r2 maxOutPt + // r3 maxInIdx + + // r4 x1, i1, i3, Out1 + // r5 out0 + + // r6 frac + // r7 inputIndex + // r8 curOut + + // r9 inc + // r10 vl + // r11 vr + + // r12 + // r13 sp + // r14 + + // the following loop works on 2 frames + + "1:\n" + " cmp r8, r2\n" // curOut - maxCurOut + " bcs 2f\n" + + #define MO_ONE_FRAME \ + " add r0, r1, r7, asl #1\n" /* in + inputIndex */ \ + " ldrsh r4, [r0]\n" /* in[inputIndex] */ \ + " ldr r5, [r8]\n" /* out[outputIndex] */ \ + " ldrsh r0, [r0, #-2]\n" /* in[inputIndex-1] */ \ + " bic r6, r6, #0xC0000000\n" /* phaseFraction & ... */ \ + " sub r4, r4, r0\n" /* in[inputIndex] - in[inputIndex-1] */ \ + " mov r4, r4, lsl #2\n" /* <<2 */ \ + " smulwt r4, r4, r6\n" /* (x1-x0)*.. */ \ + " add r6, r6, r9\n" /* phaseFraction + phaseIncrement */ \ + " add r0, r0, r4\n" /* x0 - (..) */ \ + " mla r5, r0, r10, r5\n" /* vl*interp + out[] */ \ + " ldr r4, [r8, #4]\n" /* out[outputIndex+1] */ \ + " str r5, [r8], #4\n" /* out[outputIndex++] = ... */ \ + " mla r4, r0, r11, r4\n" /* vr*interp + out[] */ \ + " add r7, r7, r6, lsr #30\n" /* inputIndex + phaseFraction>>30 */ \ + " str r4, [r8], #4\n" /* out[outputIndex++] = ... */ + + MO_ONE_FRAME // frame 1 + MO_ONE_FRAME // frame 2 + + " cmp r7, r3\n" // inputIndex - maxInIdx + " bcc 1b\n" + "2:\n" + + " bic r6, r6, #0xC0000000\n" // phaseFraction & ... + // save modified values + " ldr r0, [sp, #" MO_PARAM5 + " + 20]\n" // &phaseFraction + " str r6, [r0]\n" // phaseFraction + " ldr r0, [sp, #" MO_PARAM5 + " + 8]\n" // &inputIndex + " str r7, [r0]\n" // inputIndex + " ldr r0, [sp, #" MO_PARAM5 + " + 4]\n" // out + " sub r8, r0\n" // curOut - out + " asr r8, #2\n" // new outputIndex + " ldr r0, [sp, #" MO_PARAM5 + " + 0]\n" // &outputIndex + " str r8, [r0]\n" // save outputIndex + + " ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc}\n"); +} + +/******************************************************************* +* +* AsmStereo16Loop +* asm optimized stereo loop version; one loop is 2 frames +* Input: +* in : pointer on input samples +* maxOutPt : pointer on first not filled +* maxInIdx : index on first not used +* outputIndex : pointer on current output index +* out : pointer on output buffer +* inputIndex : pointer on current input index +* vl, vr : left and right gain +* phaseFraction : pointer on current phase fraction +* phaseIncrement +* Output: +* outputIndex : +* out : updated buffer +* inputIndex : index of next to use +* phaseFraction : phase fraction for next interpolation +* +*******************************************************************/ +__attribute__((noinline)) void AudioResamplerOrder1::AsmStereo16Loop(int16_t *in, int32_t *maxOutPt, int32_t maxInIdx, + size_t &outputIndex, int32_t *out, size_t &inputIndex, int32_t vl, int32_t vr, + uint32_t &phaseFraction, uint32_t phaseIncrement) { + (void)maxOutPt; // remove unused parameter warnings + (void)maxInIdx; + (void)outputIndex; + (void)out; + (void)inputIndex; + (void)vl; + (void)vr; + (void)phaseFraction; + (void)phaseIncrement; + (void)in; + #define ST_PARAM5 "40" // offset of parameter 5 (outputIndex) + asm( + "stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, r12, lr}\n" + // get parameters + " ldr r6, [sp, #" ST_PARAM5 + " + 20]\n" // &phaseFraction + " ldr r6, [r6]\n" // phaseFraction + " ldr r7, [sp, #" ST_PARAM5 + " + 8]\n" // &inputIndex + " ldr r7, [r7]\n" // inputIndex + " ldr r8, [sp, #" ST_PARAM5 + " + 4]\n" // out + " ldr r0, [sp, #" ST_PARAM5 + " + 0]\n" // &outputIndex + " ldr r0, [r0]\n" // outputIndex + " add r8, r8, r0, asl #2\n" // curOut + " ldr r9, [sp, #" ST_PARAM5 + " + 24]\n" // phaseIncrement + " ldr r10, [sp, #" ST_PARAM5 + " + 12]\n" // vl + " ldr r11, [sp, #" ST_PARAM5 + " + 16]\n" // vr + + // r0 pin, x0, Samp + + // r1 in + // r2 maxOutPt + // r3 maxInIdx + + // r4 x1, i1, i3, out1 + // r5 out0 + + // r6 frac + // r7 inputIndex + // r8 curOut + + // r9 inc + // r10 vl + // r11 vr + + // r12 temporary + // r13 sp + // r14 + + "3:\n" + " cmp r8, r2\n" // curOut - maxCurOut + " bcs 4f\n" + + #define ST_ONE_FRAME \ + " bic r6, r6, #0xC0000000\n" /* phaseFraction & ... */ \ + \ + " add r0, r1, r7, asl #2\n" /* in + 2*inputIndex */ \ + \ + " ldrsh r4, [r0]\n" /* in[2*inputIndex] */ \ + " ldr r5, [r8]\n" /* out[outputIndex] */ \ + " ldrsh r12, [r0, #-4]\n" /* in[2*inputIndex-2] */ \ + " sub r4, r4, r12\n" /* in[2*InputIndex] - in[2*InputIndex-2] */ \ + " mov r4, r4, lsl #2\n" /* <<2 */ \ + " smulwt r4, r4, r6\n" /* (x1-x0)*.. */ \ + " add r12, r12, r4\n" /* x0 - (..) */ \ + " mla r5, r12, r10, r5\n" /* vl*interp + out[] */ \ + " ldr r4, [r8, #4]\n" /* out[outputIndex+1] */ \ + " str r5, [r8], #4\n" /* out[outputIndex++] = ... */ \ + \ + " ldrsh r12, [r0, #+2]\n" /* in[2*inputIndex+1] */ \ + " ldrsh r0, [r0, #-2]\n" /* in[2*inputIndex-1] */ \ + " sub r12, r12, r0\n" /* in[2*InputIndex] - in[2*InputIndex-2] */ \ + " mov r12, r12, lsl #2\n" /* <<2 */ \ + " smulwt r12, r12, r6\n" /* (x1-x0)*.. */ \ + " add r12, r0, r12\n" /* x0 - (..) */ \ + " mla r4, r12, r11, r4\n" /* vr*interp + out[] */ \ + " str r4, [r8], #4\n" /* out[outputIndex++] = ... */ \ + \ + " add r6, r6, r9\n" /* phaseFraction + phaseIncrement */ \ + " add r7, r7, r6, lsr #30\n" /* inputIndex + phaseFraction>>30 */ + + ST_ONE_FRAME // frame 1 + ST_ONE_FRAME // frame 1 + + " cmp r7, r3\n" // inputIndex - maxInIdx + " bcc 3b\n" + "4:\n" + + " bic r6, r6, #0xC0000000\n" // phaseFraction & ... + // save modified values + " ldr r0, [sp, #" ST_PARAM5 + " + 20]\n" // &phaseFraction + " str r6, [r0]\n" // phaseFraction + " ldr r0, [sp, #" ST_PARAM5 + " + 8]\n" // &inputIndex + " str r7, [r0]\n" // inputIndex + " ldr r0, [sp, #" ST_PARAM5 + " + 4]\n" // out + " sub r8, r0\n" // curOut - out + " asr r8, #2\n" // new outputIndex + " ldr r0, [sp, #" ST_PARAM5 + " + 0]\n" // &outputIndex + " str r8, [r0]\n" // save outputIndex + + " ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, r12, pc}\n"); +} + +#endif // ASM_ARM_RESAMP1 + +// ---------------------------------------------------------------------------- + +} // namespace cc diff --git a/cocos/audio/android/AudioResampler.h b/cocos/audio/android/AudioResampler.h new file mode 100644 index 0000000..9fdf9c2 --- /dev/null +++ b/cocos/audio/android/AudioResampler.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include +#include +#include +#endif + +#include "audio/android/AudioBufferProvider.h" + +//#include +//#include + +//#include +//#include +#include +#include "audio/android/audio.h" + +namespace cc { + +class AudioResampler { +public: + // Determines quality of SRC. + // LOW_QUALITY: linear interpolator (1st order) + // MED_QUALITY: cubic interpolator (3rd order) + // HIGH_QUALITY: fixed multi-tap FIR (e.g. 48KHz->44.1KHz) + // NOTE: high quality SRC will only be supported for + // certain fixed rate conversions. Sample rate cannot be + // changed dynamically. + enum src_quality { // NOLINT(readability-identifier-naming) + DEFAULT_QUALITY = 0, + LOW_QUALITY = 1, + MED_QUALITY = 2, + HIGH_QUALITY = 3, + VERY_HIGH_QUALITY = 4, + }; + + static const CONSTEXPR float UNITY_GAIN_FLOAT = 1.0F; + + static AudioResampler *create(audio_format_t format, int inChannelCount, + int32_t sampleRate, src_quality quality = DEFAULT_QUALITY); + + virtual ~AudioResampler(); + + virtual void init() = 0; + virtual void setSampleRate(int32_t inSampleRate); + virtual void setVolume(float left, float right); + virtual void setLocalTimeFreq(uint64_t freq); + + // set the PTS of the next buffer output by the resampler + virtual void setPTS(int64_t pts); + + // Resample int16_t samples from provider and accumulate into 'out'. + // A mono provider delivers a sequence of samples. + // A stereo provider delivers a sequence of interleaved pairs of samples. + // + // In either case, 'out' holds interleaved pairs of fixed-point Q4.27. + // That is, for a mono provider, there is an implicit up-channeling. + // Since this method accumulates, the caller is responsible for clearing 'out' initially. + // + // For a float resampler, 'out' holds interleaved pairs of float samples. + // + // Multichannel interleaved frames for n > 2 is supported for quality DYN_LOW_QUALITY, + // DYN_MED_QUALITY, and DYN_HIGH_QUALITY. + // + // Returns the number of frames resampled into the out buffer. + virtual size_t resample(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider) = 0; + + virtual void reset(); + virtual size_t getUnreleasedFrames() const { return mInputIndex; } + + // called from destructor, so must not be virtual + src_quality getQuality() const { return mQuality; } + +protected: + // number of bits for phase fraction - 30 bits allows nearly 2x downsampling + static const int kNumPhaseBits = 30; // NOLINT(readability-identifier-naming) + + // phase mask for fraction + static const uint32_t kPhaseMask = (1LU << kNumPhaseBits) - 1; // NOLINT(readability-identifier-naming) + + // multiplier to calculate fixed point phase increment + static const double kPhaseMultiplier; // NOLINT(readability-identifier-naming) + + AudioResampler(int inChannelCount, int32_t sampleRate, src_quality quality); + + // prevent copying + AudioResampler(const AudioResampler &); + AudioResampler &operator=(const AudioResampler &); + + int64_t calculateOutputPTS(int outputFrameIndex); + + + const int32_t mChannelCount;// NOLINT(readability-identifier-naming) + const int32_t mSampleRate;// NOLINT(readability-identifier-naming) + int32_t mInSampleRate;// NOLINT(readability-identifier-naming) + AudioBufferProvider::Buffer mBuffer;// NOLINT(readability-identifier-naming) + union { + int16_t mVolume[2];// NOLINT(readability-identifier-naming) + uint32_t mVolumeRL;// NOLINT(readability-identifier-naming) + }; + int16_t mTargetVolume[2];// NOLINT(readability-identifier-naming) + size_t mInputIndex;// NOLINT(readability-identifier-naming) + int32_t mPhaseIncrement;// NOLINT(readability-identifier-naming) + uint32_t mPhaseFraction;// NOLINT(readability-identifier-naming) + uint64_t mLocalTimeFreq;// NOLINT(readability-identifier-naming) + int64_t mPTS;// NOLINT(readability-identifier-naming) + + // returns the inFrameCount required to generate outFrameCount frames. + // + // Placed here to be a consistent for all resamplers. + // + // Right now, we use the upper bound without regards to the current state of the + // input buffer using integer arithmetic, as follows: + // + // (static_cast(outFrameCount)*mInSampleRate + (mSampleRate - 1))/mSampleRate; + // + // The double precision equivalent (float may not be precise enough): + // ceil(static_cast(outFrameCount) * mInSampleRate / mSampleRate); + // + // this relies on the fact that the mPhaseIncrement is rounded down from + // #phases * mInSampleRate/mSampleRate and the fact that Sum(Floor(x)) <= Floor(Sum(x)). + // http://www.proofwiki.org/wiki/Sum_of_Floors_Not_Greater_Than_Floor_of_Sums + // + // (so long as double precision is computed accurately enough to be considered + // greater than or equal to the Floor(x) value in int32_t arithmetic; thus this + // will not necessarily hold for floats). + // + // REFINE: + // Greater accuracy and a tight bound is obtained by: + // 1) subtract and adjust for the current state of the AudioBufferProvider buffer. + // 2) using the exact integer formula where (ignoring 64b casting) + // inFrameCount = (mPhaseIncrement * (outFrameCount - 1) + mPhaseFraction) / phaseWrapLimit; + // phaseWrapLimit is the wraparound (1 << kNumPhaseBits), if not specified explicitly. + // + inline size_t getInFrameCountRequired(size_t outFrameCount) const { + return (static_cast(outFrameCount) * mInSampleRate + (mSampleRate - 1)) / mSampleRate; + } + + inline float clampFloatVol(float volume) {//NOLINT(readability-identifier-naming, readability-convert-member-functions-to-static) + float ret = 0.0F; + if (volume > UNITY_GAIN_FLOAT) { + ret = UNITY_GAIN_FLOAT; + } else if (volume >= 0.) { + ret = volume; + } + return ret; // NaN or negative volume maps to 0. + } + +private: + const src_quality mQuality;// NOLINT(readability-identifier-naming) + + // Return 'true' if the quality level is supported without explicit request + static bool qualityIsSupported(src_quality quality); + + // For pthread_once() + static void init_routine(); // NOLINT(readability-identifier-naming) + + // Return the estimated CPU load for specific resampler in MHz. + // The absolute number is irrelevant, it's the relative values that matter. + static uint32_t qualityMHz(src_quality quality); +}; +// ---------------------------------------------------------------------------- +} // namespace cc diff --git a/cocos/audio/android/AudioResamplerCubic.cpp b/cocos/audio/android/AudioResamplerCubic.cpp new file mode 100644 index 0000000..474a3a5 --- /dev/null +++ b/cocos/audio/android/AudioResamplerCubic.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AudioResamplerCubic" + +#include +#include +#include +#include "audio/android/cutils/log.h" + +#include "audio/android/AudioResampler.h" +#include "audio/android/AudioResamplerCubic.h" + +namespace cc { +// ---------------------------------------------------------------------------- + +void AudioResamplerCubic::init() { + memset(&left, 0, sizeof(state)); + memset(&right, 0, sizeof(state)); +} + +size_t AudioResamplerCubic::resample(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider) { + // should never happen, but we overflow if it does + // ALOG_ASSERT(outFrameCount < 32767); + + // select the appropriate resampler + switch (mChannelCount) { + case 1: + return resampleMono16(out, outFrameCount, provider); + case 2: + return resampleStereo16(out, outFrameCount, provider); + default: + LOG_ALWAYS_FATAL("invalid channel count: %d", mChannelCount); + return 0; + } +} + +size_t AudioResamplerCubic::resampleStereo16(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider) { + int32_t vl = mVolume[0]; + int32_t vr = mVolume[1]; + + size_t inputIndex = mInputIndex; + uint32_t phaseFraction = mPhaseFraction; + uint32_t phaseIncrement = mPhaseIncrement; + size_t outputIndex = 0; + size_t outputSampleCount = outFrameCount * 2; + size_t inFrameCount = getInFrameCountRequired(outFrameCount); + + // fetch first buffer + if (mBuffer.frameCount == 0) { + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer, mPTS); + if (mBuffer.raw == NULL) { + return 0; + } + // ALOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount); + } + int16_t *in = mBuffer.i16; + + while (outputIndex < outputSampleCount) { + int32_t sample; + int32_t x; + + // calculate output sample + x = phaseFraction >> kPreInterpShift; + out[outputIndex++] += vl * interp(&left, x); + out[outputIndex++] += vr * interp(&right, x); + // out[outputIndex++] += vr * in[inputIndex*2]; + + // increment phase + phaseFraction += phaseIncrement; + uint32_t indexIncrement = (phaseFraction >> kNumPhaseBits); + phaseFraction &= kPhaseMask; + + // time to fetch another sample + while (indexIncrement--) { + inputIndex++; + if (inputIndex == mBuffer.frameCount) { + inputIndex = 0; + provider->releaseBuffer(&mBuffer); + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer, + calculateOutputPTS(outputIndex / 2)); + if (mBuffer.raw == NULL) { + goto save_state; // ugly, but efficient + } + in = mBuffer.i16; + // ALOGW("New buffer: offset=%p, frames=%d", mBuffer.raw, mBuffer.frameCount); + } + + // advance sample state + advance(&left, in[inputIndex * 2]); + advance(&right, in[inputIndex * 2 + 1]); + } + } + +save_state: + // ALOGW("Done: index=%d, fraction=%u", inputIndex, phaseFraction); + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; + return outputIndex / 2 /* channels for stereo */; +} + +size_t AudioResamplerCubic::resampleMono16(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider) { + int32_t vl = mVolume[0]; + int32_t vr = mVolume[1]; + + size_t inputIndex = mInputIndex; + uint32_t phaseFraction = mPhaseFraction; + uint32_t phaseIncrement = mPhaseIncrement; + size_t outputIndex = 0; + size_t outputSampleCount = outFrameCount * 2; + size_t inFrameCount = getInFrameCountRequired(outFrameCount); + + // fetch first buffer + if (mBuffer.frameCount == 0) { + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer, mPTS); + if (mBuffer.raw == NULL) { + return 0; + } + // ALOGW("New buffer: offset=%p, frames=%d", mBuffer.raw, mBuffer.frameCount); + } + int16_t *in = mBuffer.i16; + + while (outputIndex < outputSampleCount) { + int32_t sample; + int32_t x; + + // calculate output sample + x = phaseFraction >> kPreInterpShift; + sample = interp(&left, x); + out[outputIndex++] += vl * sample; + out[outputIndex++] += vr * sample; + + // increment phase + phaseFraction += phaseIncrement; + uint32_t indexIncrement = (phaseFraction >> kNumPhaseBits); + phaseFraction &= kPhaseMask; + + // time to fetch another sample + while (indexIncrement--) { + inputIndex++; + if (inputIndex == mBuffer.frameCount) { + inputIndex = 0; + provider->releaseBuffer(&mBuffer); + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer, + calculateOutputPTS(outputIndex / 2)); + if (mBuffer.raw == NULL) { + goto save_state; // ugly, but efficient + } + // ALOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount); + in = mBuffer.i16; + } + + // advance sample state + advance(&left, in[inputIndex]); + } + } + +save_state: + // ALOGW("Done: index=%d, fraction=%u", inputIndex, phaseFraction); + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; + return outputIndex; +} + +// ---------------------------------------------------------------------------- +} // namespace cc diff --git a/cocos/audio/android/AudioResamplerCubic.h b/cocos/audio/android/AudioResamplerCubic.h new file mode 100644 index 0000000..b625064 --- /dev/null +++ b/cocos/audio/android/AudioResamplerCubic.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "audio/android/AudioResampler.h" +#include "audio/android/AudioBufferProvider.h" + +namespace cc { +// ---------------------------------------------------------------------------- + +class AudioResamplerCubic : public AudioResampler { +public: + AudioResamplerCubic(int inChannelCount, int32_t sampleRate) : AudioResampler(inChannelCount, sampleRate, MED_QUALITY) { + } + virtual size_t resample(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider); + +private: + // number of bits used in interpolation multiply - 14 bits avoids overflow + static const int kNumInterpBits = 14; + + // bits to shift the phase fraction down to avoid overflow + static const int kPreInterpShift = kNumPhaseBits - kNumInterpBits; + typedef struct { + int32_t a, b, c, y0, y1, y2, y3; + } state; + void init(); + size_t resampleMono16(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider); + size_t resampleStereo16(int32_t *out, size_t outFrameCount, + AudioBufferProvider *provider); + static inline int32_t interp(state *p, int32_t x) { + return (((((p->a * x >> 14) + p->b) * x >> 14) + p->c) * x >> 14) + p->y1; + } + static inline void advance(state *p, int16_t in) { + p->y0 = p->y1; + p->y1 = p->y2; + p->y2 = p->y3; + p->y3 = in; + p->a = (3 * (p->y1 - p->y2) - p->y0 + p->y3) >> 1; + p->b = (p->y2 << 1) + p->y0 - (((5 * p->y1 + p->y3)) >> 1); + p->c = (p->y2 - p->y0) >> 1; + } + state left, right; +}; + +// ---------------------------------------------------------------------------- +} // namespace cc diff --git a/cocos/audio/android/AudioResamplerPublic.h b/cocos/audio/android/AudioResamplerPublic.h new file mode 100644 index 0000000..9753ab0 --- /dev/null +++ b/cocos/audio/android/AudioResamplerPublic.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace cc { + +// AUDIO_RESAMPLER_DOWN_RATIO_MAX is the maximum ratio between the original +// audio sample rate and the target rate when downsampling, +// as permitted in the audio framework, e.g. AudioTrack and AudioFlinger. +// In practice, it is not recommended to downsample more than 6:1 +// for best audio quality, even though the audio framework permits a larger +// downsampling ratio. +// REFINE: replace with an API +#define AUDIO_RESAMPLER_DOWN_RATIO_MAX 256 + +// AUDIO_RESAMPLER_UP_RATIO_MAX is the maximum suggested ratio between the original +// audio sample rate and the target rate when upsampling. It is loosely enforced by +// the system. One issue with large upsampling ratios is the approximation by +// an int32_t of the phase increments, making the resulting sample rate inexact. +#define AUDIO_RESAMPLER_UP_RATIO_MAX 65536 + +// AUDIO_TIMESTRETCH_SPEED_MIN and AUDIO_TIMESTRETCH_SPEED_MAX define the min and max time stretch +// speeds supported by the system. These are enforced by the system and values outside this range +// will result in a runtime error. +// Depending on the AudioPlaybackRate::mStretchMode, the effective limits might be narrower than +// the ones specified here +// AUDIO_TIMESTRETCH_SPEED_MIN_DELTA is the minimum absolute speed difference that might trigger a +// parameter update +#define AUDIO_TIMESTRETCH_SPEED_MIN 0.01f +#define AUDIO_TIMESTRETCH_SPEED_MAX 20.0f +#define AUDIO_TIMESTRETCH_SPEED_NORMAL 1.0f +#define AUDIO_TIMESTRETCH_SPEED_MIN_DELTA 0.0001f + +// AUDIO_TIMESTRETCH_PITCH_MIN and AUDIO_TIMESTRETCH_PITCH_MAX define the min and max time stretch +// pitch shifting supported by the system. These are not enforced by the system and values +// outside this range might result in a pitch different than the one requested. +// Depending on the AudioPlaybackRate::mStretchMode, the effective limits might be narrower than +// the ones specified here. +// AUDIO_TIMESTRETCH_PITCH_MIN_DELTA is the minimum absolute pitch difference that might trigger a +// parameter update +#define AUDIO_TIMESTRETCH_PITCH_MIN 0.25f +#define AUDIO_TIMESTRETCH_PITCH_MAX 4.0f +#define AUDIO_TIMESTRETCH_PITCH_NORMAL 1.0f +#define AUDIO_TIMESTRETCH_PITCH_MIN_DELTA 0.0001f + +//Determines the current algorithm used for stretching +enum AudioTimestretchStretchMode : int32_t { + AUDIO_TIMESTRETCH_STRETCH_DEFAULT = 0, + AUDIO_TIMESTRETCH_STRETCH_SPEECH = 1, + //REFINE: add more stretch modes/algorithms +}; + +//Limits for AUDIO_TIMESTRETCH_STRETCH_SPEECH mode +#define TIMESTRETCH_SONIC_SPEED_MIN 0.1f +#define TIMESTRETCH_SONIC_SPEED_MAX 6.0f + +//Determines behavior of Timestretch if current algorithm can't perform +//with current parameters. +// FALLBACK_CUT_REPEAT: (internal only) for speed <1.0 will truncate frames +// for speed > 1.0 will repeat frames +// FALLBACK_MUTE: will set all processed frames to zero +// FALLBACK_FAIL: will stop program execution and log a fatal error +enum AudioTimestretchFallbackMode : int32_t { + AUDIO_TIMESTRETCH_FALLBACK_CUT_REPEAT = -1, + AUDIO_TIMESTRETCH_FALLBACK_DEFAULT = 0, + AUDIO_TIMESTRETCH_FALLBACK_MUTE = 1, + AUDIO_TIMESTRETCH_FALLBACK_FAIL = 2, +}; + +struct AudioPlaybackRate { + float mSpeed; + float mPitch; + enum AudioTimestretchStretchMode mStretchMode; + enum AudioTimestretchFallbackMode mFallbackMode; +}; + +static const AudioPlaybackRate AUDIO_PLAYBACK_RATE_DEFAULT = { + AUDIO_TIMESTRETCH_SPEED_NORMAL, + AUDIO_TIMESTRETCH_PITCH_NORMAL, + AUDIO_TIMESTRETCH_STRETCH_DEFAULT, + AUDIO_TIMESTRETCH_FALLBACK_DEFAULT}; + +static inline bool isAudioPlaybackRateEqual(const AudioPlaybackRate &pr1, + const AudioPlaybackRate &pr2) { + return fabs(pr1.mSpeed - pr2.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA && + fabs(pr1.mPitch - pr2.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA && + pr1.mStretchMode == pr2.mStretchMode && + pr1.mFallbackMode == pr2.mFallbackMode; +} + +static inline bool isAudioPlaybackRateValid(const AudioPlaybackRate &playbackRate) { + if (playbackRate.mFallbackMode == AUDIO_TIMESTRETCH_FALLBACK_FAIL && + (playbackRate.mStretchMode == AUDIO_TIMESTRETCH_STRETCH_SPEECH || + playbackRate.mStretchMode == AUDIO_TIMESTRETCH_STRETCH_DEFAULT)) { + //test sonic specific constraints + return playbackRate.mSpeed >= TIMESTRETCH_SONIC_SPEED_MIN && + playbackRate.mSpeed <= TIMESTRETCH_SONIC_SPEED_MAX && + playbackRate.mPitch >= AUDIO_TIMESTRETCH_PITCH_MIN && + playbackRate.mPitch <= AUDIO_TIMESTRETCH_PITCH_MAX; + } else { + return playbackRate.mSpeed >= AUDIO_TIMESTRETCH_SPEED_MIN && + playbackRate.mSpeed <= AUDIO_TIMESTRETCH_SPEED_MAX && + playbackRate.mPitch >= AUDIO_TIMESTRETCH_PITCH_MIN && + playbackRate.mPitch <= AUDIO_TIMESTRETCH_PITCH_MAX; + } +} + +// REFINE: Consider putting these inlines into a class scope + +// Returns the source frames needed to resample to destination frames. This is not a precise +// value and depends on the resampler (and possibly how it handles rounding internally). +// Nevertheless, this should be an upper bound on the requirements of the resampler. +// If srcSampleRate and dstSampleRate are equal, then it returns destination frames, which +// may not be true if the resampler is asynchronous. +static inline size_t sourceFramesNeeded( + uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate) { + // +1 for rounding - always do this even if matched ratio (resampler may use phases not ratio) + // +1 for additional sample needed for interpolation + return srcSampleRate == dstSampleRate ? dstFramesRequired : size_t((uint64_t)dstFramesRequired * srcSampleRate / dstSampleRate + 1 + 1); +} + +// An upper bound for the number of destination frames possible from srcFrames +// after sample rate conversion. This may be used for buffer sizing. +static inline size_t destinationFramesPossible(size_t srcFrames, uint32_t srcSampleRate, + uint32_t dstSampleRate) { + if (srcSampleRate == dstSampleRate) { + return srcFrames; + } + uint64_t dstFrames = (uint64_t)srcFrames * dstSampleRate / srcSampleRate; + return dstFrames > 2 ? static_cast(dstFrames - 2) : 0; +} + +static inline size_t sourceFramesNeededWithTimestretch( + uint32_t srcSampleRate, size_t dstFramesRequired, uint32_t dstSampleRate, + float speed) { + // required is the number of input frames the resampler needs + size_t required = sourceFramesNeeded(srcSampleRate, dstFramesRequired, dstSampleRate); + // to deliver this, the time stretcher requires: + return required * (double)speed + 1 + 1; // accounting for rounding dependencies +} + +// Identifies sample rates that we associate with music +// and thus eligible for better resampling and fast capture. +// This is somewhat less than 44100 to allow for pitch correction +// involving resampling as well as asynchronous resampling. +#define AUDIO_PROCESSING_MUSIC_RATE 40000 + +static inline bool isMusicRate(uint32_t sampleRate) { + return sampleRate >= AUDIO_PROCESSING_MUSIC_RATE; +} + +} // namespace cc + +// --------------------------------------------------------------------------- diff --git a/cocos/audio/android/IAudioPlayer.h b/cocos/audio/android/IAudioPlayer.h new file mode 100644 index 0000000..e6ff5c0 --- /dev/null +++ b/cocos/audio/android/IAudioPlayer.h @@ -0,0 +1,87 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/std/container/string.h" + +namespace cc { + +class IAudioPlayer { +public: + enum class State { + INVALID = 0, + INITIALIZED, + PLAYING, + PAUSED, + STOPPED, + OVER + }; + + using PlayEventCallback = std::function; + + virtual ~IAudioPlayer(){}; + + virtual int getId() const = 0; + + virtual void setId(int id) = 0; + + virtual ccstd::string getUrl() const = 0; + + virtual State getState() const = 0; + + virtual void play() = 0; + + virtual void pause() = 0; + + virtual void resume() = 0; + + virtual void stop() = 0; + + virtual void rewind() = 0; + + virtual void setVolume(float volume) = 0; + + virtual float getVolume() const = 0; + + virtual void setAudioFocus(bool isFocus) = 0; + + virtual void setLoop(bool isLoop) = 0; + + virtual bool isLoop() const = 0; + + virtual float getDuration() const = 0; + + virtual float getPosition() const = 0; + + virtual bool setPosition(float pos) = 0; + + // @note: STOPPED event is invoked in main thread + // OVER event is invoked in sub thread + virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) = 0; +}; + +} // namespace cc diff --git a/cocos/audio/android/ICallerThreadUtils.h b/cocos/audio/android/ICallerThreadUtils.h new file mode 100644 index 0000000..740859b --- /dev/null +++ b/cocos/audio/android/ICallerThreadUtils.h @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include + +namespace cc { + +class ICallerThreadUtils { +public: + virtual ~ICallerThreadUtils(){}; + + virtual void performFunctionInCallerThread(const std::function &func) = 0; + virtual std::thread::id getCallerThreadId() = 0; +}; + +} // namespace cc diff --git a/cocos/audio/android/IVolumeProvider.h b/cocos/audio/android/IVolumeProvider.h new file mode 100644 index 0000000..041eb14 --- /dev/null +++ b/cocos/audio/android/IVolumeProvider.h @@ -0,0 +1,42 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include "audio/common/utils/include/minifloat.h" + +namespace cc { + +class IVolumeProvider { +public: + // The provider implementation is responsible for validating that the return value is in range. + virtual gain_minifloat_packed_t getVolumeLR() = 0; + +protected: + IVolumeProvider() {} + + virtual ~IVolumeProvider() {} +}; + +} // namespace cc diff --git a/cocos/audio/android/OpenSLHelper.h b/cocos/audio/android/OpenSLHelper.h new file mode 100644 index 0000000..d1621d5 --- /dev/null +++ b/cocos/audio/android/OpenSLHelper.h @@ -0,0 +1,107 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/android/cutils/log.h" + +#include +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY +#include +#endif + +#include +#include "base/std/container/string.h" + +#define SL_SAFE_DELETE(obj) \ + if ((obj) != nullptr) { \ + delete (obj); \ + (obj) = nullptr; \ + } + +#define SL_DESTROY_OBJ(OBJ) \ + if ((OBJ) != nullptr) { \ + (*(OBJ))->Destroy(OBJ); \ + (OBJ) = nullptr; \ + } + +#define SL_RETURN_VAL_IF_FAILED(r, rval, ...) \ + if (r != SL_RESULT_SUCCESS) { \ + ALOGE(__VA_ARGS__); \ + return rval; \ + } + +#define SL_RETURN_IF_FAILED(r, ...) \ + if (r != SL_RESULT_SUCCESS) { \ + ALOGE(__VA_ARGS__); \ + return; \ + } + +#define SL_PRINT_ERROR_IF_FAILED(r, ...) \ + if (r != SL_RESULT_SUCCESS) { \ + ALOGE(__VA_ARGS__); \ + } + +typedef std::function FdGetterCallback; + +// Copied from OpenSLES_AndroidMetadata.h in android-21 +// It's because android-10 doesn't contain this header file +/** + * Additional metadata keys to be used in SLMetadataExtractionItf: + * the ANDROID_KEY_PCMFORMAT_* keys follow the fields of the SLDataFormat_PCM struct, and as such + * all values corresponding to these keys are of SLuint32 type, and are defined as the fields + * of the same name in SLDataFormat_PCM. The exception is that sample rate is expressed here + * in Hz units, rather than in milliHz units. + */ +#ifndef ANDROID_KEY_PCMFORMAT_NUMCHANNELS + #define ANDROID_KEY_PCMFORMAT_NUMCHANNELS "AndroidPcmFormatNumChannels" +#endif + +#ifndef ANDROID_KEY_PCMFORMAT_SAMPLERATE + #define ANDROID_KEY_PCMFORMAT_SAMPLERATE "AndroidPcmFormatSampleRate" +#endif + +#ifndef ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE + #define ANDROID_KEY_PCMFORMAT_BITSPERSAMPLE "AndroidPcmFormatBitsPerSample" +#endif + +#ifndef ANDROID_KEY_PCMFORMAT_CONTAINERSIZE + #define ANDROID_KEY_PCMFORMAT_CONTAINERSIZE "AndroidPcmFormatContainerSize" +#endif + +#ifndef ANDROID_KEY_PCMFORMAT_CHANNELMASK + #define ANDROID_KEY_PCMFORMAT_CHANNELMASK "AndroidPcmFormatChannelMask" +#endif + +#ifndef ANDROID_KEY_PCMFORMAT_ENDIANNESS + #define ANDROID_KEY_PCMFORMAT_ENDIANNESS "AndroidPcmFormatEndianness" +#endif + +#define clockNow() std::chrono::high_resolution_clock::now() +#define intervalInMS(oldTime, newTime) (static_cast(std::chrono::duration_cast((newTime) - (oldTime)).count()) / 1000.f) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) diff --git a/cocos/audio/android/PcmAudioPlayer.cpp b/cocos/audio/android/PcmAudioPlayer.cpp new file mode 100644 index 0000000..571db2c --- /dev/null +++ b/cocos/audio/android/PcmAudioPlayer.cpp @@ -0,0 +1,193 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "PcmAudioPlayer" + +#include "audio/android/PcmAudioPlayer.h" +#include "audio/android/AudioMixerController.h" +#include "audio/android/ICallerThreadUtils.h" +#include "audio/android/cutils/log.h" +#include "base/memory/Memory.h" + +namespace cc { + +PcmAudioPlayer::PcmAudioPlayer(AudioMixerController *controller, ICallerThreadUtils *callerThreadUtils) +: _id(-1), _track(nullptr), _playEventCallback(nullptr), _controller(controller), _callerThreadUtils(callerThreadUtils) { + ALOGV("PcmAudioPlayer constructor: %p", this); +} + +PcmAudioPlayer::~PcmAudioPlayer() { + ALOGV("In the destructor of PcmAudioPlayer (%p)", this); + delete _track; +} + +bool PcmAudioPlayer::prepare(const ccstd::string &url, const PcmData &decResult) { + _url = url; + _decResult = decResult; + + _track = ccnew Track(_decResult); + + std::thread::id callerThreadId = _callerThreadUtils->getCallerThreadId(); + + // @note The logic may cause this issue https://github.com/cocos2d/cocos2d-x/issues/17707 + // Assume that AudioEngine::stop(id) is invoked and the audio is played over meanwhile. + // Since State::OVER and State::DESTROYED are triggered in the audio mixing thread, it will + // call 'performFunctionInCallerThread' to post events to cocos's message queue. + // Therefore, the sequence in cocos's thread will be |STOP|OVER|DESTROYED|. + // Although, we remove the audio id in |STOPPED| callback, because it's asynchronous operation, + // |OVER| and |DESTROYED| callbacks will still be invoked in cocos's thread. + // HOW TO FIX: If the previous state is |STOPPED| and the current state + // is |OVER|, just skip to invoke |OVER| callback. + + _track->onStateChanged = [this, callerThreadId](Track::State state) { + // It maybe in sub thread + Track::State prevState = _track->getPrevState(); + auto func = [this, state, prevState]() { + // It's in caller's thread + if (state == Track::State::OVER && prevState != Track::State::STOPPED) { + if (_playEventCallback != nullptr) { + _playEventCallback(State::OVER); + } + } else if (state == Track::State::STOPPED) { + if (_playEventCallback != nullptr) { + _playEventCallback(State::STOPPED); + } + } else if (state == Track::State::DESTROYED) { + delete this; + } + }; + + if (callerThreadId == std::this_thread::get_id()) { // onStateChanged(Track::State::STOPPED) is in caller's (Cocos's) thread. + func(); + } else { // onStateChanged(Track::State::OVER) or onStateChanged(Track::State::DESTROYED) are in audio mixing thread. + _callerThreadUtils->performFunctionInCallerThread(func); + } + }; + + setVolume(1.0f); + + return true; +} + +void PcmAudioPlayer::rewind() { + ALOGW("PcmAudioPlayer::rewind isn't supported!"); +} + +void PcmAudioPlayer::setVolume(float volume) { + _track->setVolume(volume); +} + +float PcmAudioPlayer::getVolume() const { + return _track->getVolume(); +} + +void PcmAudioPlayer::setAudioFocus(bool isFocus) { + _track->setAudioFocus(isFocus); +} + +void PcmAudioPlayer::setLoop(bool isLoop) { + _track->setLoop(isLoop); +} + +bool PcmAudioPlayer::isLoop() const { + return _track->isLoop(); +} + +float PcmAudioPlayer::getDuration() const { + return _decResult.duration; +} + +float PcmAudioPlayer::getPosition() const { + return _track->getPosition(); +} + +bool PcmAudioPlayer::setPosition(float pos) { + return _track->setPosition(pos); +} + +void PcmAudioPlayer::setPlayEventCallback(const PlayEventCallback &playEventCallback) { + _playEventCallback = playEventCallback; +} + +void PcmAudioPlayer::play() { + // put track to AudioMixerController + ALOGV("PcmAudioPlayer (%p) play, url: %s", this, _url.c_str()); + _controller->addTrack(_track); + _track->setState(Track::State::PLAYING); +} + +void PcmAudioPlayer::pause() { + ALOGV("PcmAudioPlayer (%p) pause, url: %s", this, _url.c_str()); + _track->setState(Track::State::PAUSED); +} + +void PcmAudioPlayer::resume() { + ALOGV("PcmAudioPlayer (%p) resume, url: %s", this, _url.c_str()); + _track->setState(Track::State::RESUMED); +} + +void PcmAudioPlayer::stop() { + ALOGV("PcmAudioPlayer (%p) stop, url: %s", this, _url.c_str()); + _track->setState(Track::State::STOPPED); +} + +IAudioPlayer::State PcmAudioPlayer::getState() const { + IAudioPlayer::State state = State::INVALID; + + if (_track != nullptr) { + switch (_track->getState()) { + case Track::State::IDLE: + state = State::INITIALIZED; + break; + + case Track::State::PLAYING: + state = State::PLAYING; + break; + + case Track::State::RESUMED: + state = State::PLAYING; + break; + + case Track::State::PAUSED: + state = State::PAUSED; + break; + + case Track::State::STOPPED: + state = State::STOPPED; + break; + + case Track::State::OVER: + state = State::OVER; + break; + + default: + state = State::INVALID; + break; + } + } + return state; +} + +} // namespace cc diff --git a/cocos/audio/android/PcmAudioPlayer.h b/cocos/audio/android/PcmAudioPlayer.h new file mode 100644 index 0000000..dfb0950 --- /dev/null +++ b/cocos/audio/android/PcmAudioPlayer.h @@ -0,0 +1,96 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include "audio/android/IAudioPlayer.h" +#include "audio/android/PcmData.h" +#include "audio/android/Track.h" + +namespace cc { + +class ICallerThreadUtils; +class AudioMixerController; + +class PcmAudioPlayer : public IAudioPlayer { +public: + bool prepare(const ccstd::string &url, const PcmData &decResult); + + // Override Functions Begin + virtual int getId() const override { return _id; }; + + virtual void setId(int id) override { _id = id; }; + + virtual ccstd::string getUrl() const override { return _url; }; + + virtual State getState() const override; + + virtual void play() override; + + virtual void pause() override; + + virtual void resume() override; + + virtual void stop() override; + + virtual void rewind() override; + + virtual void setVolume(float volume) override; + + virtual float getVolume() const override; + + virtual void setAudioFocus(bool isFocus) override; + + virtual void setLoop(bool isLoop) override; + + virtual bool isLoop() const override; + + virtual float getDuration() const override; + + virtual float getPosition() const override; + + virtual bool setPosition(float pos) override; + + virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) override; + + // Override Functions End + +private: + PcmAudioPlayer(AudioMixerController *controller, ICallerThreadUtils *callerThreadUtils); + virtual ~PcmAudioPlayer(); + +private: + int _id; + ccstd::string _url; + PcmData _decResult; + Track *_track; + PlayEventCallback _playEventCallback; + AudioMixerController *_controller; + ICallerThreadUtils *_callerThreadUtils; + + friend class AudioPlayerProvider; +}; + +} // namespace cc diff --git a/cocos/audio/android/PcmAudioService.cpp b/cocos/audio/android/PcmAudioService.cpp new file mode 100644 index 0000000..4989d0a --- /dev/null +++ b/cocos/audio/android/PcmAudioService.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "PcmAudioService" + +#include "base/Macros.h" +#include "audio/android/PcmAudioService.h" +#include "audio/android/AudioMixerController.h" +#include "audio/android/utils/Compat.h" + +namespace cc { + +static ccstd::vector __silenceData;//NOLINT(bugprone-reserved-identifier, readability-identifier-naming) + +#define AUDIO_PLAYER_BUFFER_COUNT (2) + +class SLPcmAudioPlayerCallbackProxy { +public: +#if CC_PLATFORM == CC_PLATFORM_ANDROID + static void samplePlayerCallback(CCSLBufferQueueItf bq, void *context) { +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY + static void samplePlayerCallback(CCSLBufferQueueItf bq, void *context, SLuint32 size) { +#endif + auto *thiz = reinterpret_cast(context); + thiz->bqFetchBufferCallback(bq); + } +}; + +PcmAudioService::PcmAudioService(SLEngineItf engineItf, SLObjectItf outputMixObject) +: _engineItf(engineItf), _outputMixObj(outputMixObject), _playObj(nullptr), _playItf(nullptr), _volumeItf(nullptr), _bufferQueueItf(nullptr), _numChannels(-1), _sampleRate(-1), _bufferSizeInBytes(0), _controller(nullptr) { +} + +PcmAudioService::~PcmAudioService() { + ALOGV("PcmAudioServicee() (%p), before destroy play object", this); + SL_DESTROY_OBJ(_playObj); + ALOGV("PcmAudioServicee() end"); +} + +bool PcmAudioService::enqueue() { + #if CC_PLATFORM == CC_PLATFORM_OPENHARMONY + // We need to call this interface in openharmony, otherwise there will be noise + SLuint8 *buffer = nullptr; + SLuint32 size = 0; + (*_bufferQueueItf)->GetBuffer(_bufferQueueItf, &buffer, &size); + #endif + if (_controller->hasPlayingTacks()) { + if (_controller->isPaused()) { + SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size()); + SL_RETURN_VAL_IF_FAILED(r, false, "enqueue silent data failed!"); + } else { + _controller->mixOneFrame(); + + auto *current = _controller->current(); + ALOG_ASSERT(current != nullptr, "current buffer is nullptr ..."); + SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, current->buf, current->size); + SL_RETURN_VAL_IF_FAILED(r, false, "enqueue failed!"); + } + } else { + SLresult r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size()); + SL_RETURN_VAL_IF_FAILED(r, false, "enqueue silent data failed!"); + } + + return true; +} + +void PcmAudioService::bqFetchBufferCallback(CCSLBufferQueueItf bq) { + CC_UNUSED_PARAM(bq); + // IDEA: PcmAudioService instance may be destroyed, we need to find a way to wait... + // It's in sub thread + enqueue(); +} + +bool PcmAudioService::init(AudioMixerController *controller, int numChannels, int sampleRate, int bufferSizeInBytes) { + _controller = controller; + _numChannels = numChannels; + _sampleRate = sampleRate; + _bufferSizeInBytes = bufferSizeInBytes; + + SLuint32 channelMask = SL_SPEAKER_FRONT_CENTER; + + if (numChannels > 1) { + channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + } + + SLDataFormat_PCM formatPcm = { + SL_DATAFORMAT_PCM, + static_cast(numChannels), + static_cast(sampleRate * 1000), + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + channelMask, + SL_BYTEORDER_LITTLEENDIAN}; + +#if CC_PLATFORM == CC_PLATFORM_ANDROID + SLDataLocator_AndroidSimpleBufferQueue locBufQueue = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + AUDIO_PLAYER_BUFFER_COUNT}; +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY + SLDataLocator_BufferQueue locBufQueue = {SL_DATALOCATOR_BUFFERQUEUE, AUDIO_PLAYER_BUFFER_COUNT}; +#endif + SLDataSource source = {&locBufQueue, &formatPcm}; + + SLDataLocator_OutputMix locOutmix = { + SL_DATALOCATOR_OUTPUTMIX, + _outputMixObj}; + SLDataSink sink = {&locOutmix, nullptr}; + + const SLInterfaceID ids[] = { + SL_IID_PLAY, + SL_IID_VOLUME, + CC_SL_IDD_BUFFER_QUEUE, + }; + + const SLboolean req[] = { + SL_BOOLEAN_TRUE, + SL_BOOLEAN_TRUE, + SL_BOOLEAN_TRUE, + }; + + SLresult r; + + r = (*_engineItf)->CreateAudioPlayer(_engineItf, &_playObj, &source, &sink, sizeof(ids) / sizeof(ids[0]), ids, req);//NOLINT(bugprone-sizeof-expression) + SL_RETURN_VAL_IF_FAILED(r, false, "CreateAudioPlayer failed"); + + r = (*_playObj)->Realize(_playObj, SL_BOOLEAN_FALSE); + SL_RETURN_VAL_IF_FAILED(r, false, "Realize failed"); + + r = (*_playObj)->GetInterface(_playObj, SL_IID_PLAY, &_playItf); + SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface SL_IID_PLAY failed"); + + r = (*_playObj)->GetInterface(_playObj, SL_IID_VOLUME, &_volumeItf); + SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface SL_IID_VOLUME failed"); + + r = (*_playObj)->GetInterface(_playObj, CC_SL_IDD_BUFFER_QUEUE, &_bufferQueueItf); + SL_RETURN_VAL_IF_FAILED(r, false, "GetInterface CC_SL_IDD_BUFFER_QUEUE failed"); + + r = (*_bufferQueueItf)->RegisterCallback(_bufferQueueItf, SLPcmAudioPlayerCallbackProxy::samplePlayerCallback, this); + SL_RETURN_VAL_IF_FAILED(r, false, "_bufferQueueItf RegisterCallback failed"); + + if (__silenceData.empty()) { + __silenceData.resize(_numChannels * _bufferSizeInBytes, 0x00); + } + #if CC_PLATFORM == CC_PLATFORM_OPENHARMONY + // We need to call this interface in openharmony, otherwise there will be noise + SLuint8 *buffer = nullptr; + SLuint32 size = 0; + (*_bufferQueueItf)->GetBuffer(_bufferQueueItf, &buffer, &size); + #endif + r = (*_bufferQueueItf)->Enqueue(_bufferQueueItf, __silenceData.data(), __silenceData.size()); + SL_RETURN_VAL_IF_FAILED(r, false, "_bufferQueueItf Enqueue failed"); + + r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING); + SL_RETURN_VAL_IF_FAILED(r, false, "SetPlayState failed"); + + return true; +} + +void PcmAudioService::pause() { + SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PAUSED); + SL_RETURN_IF_FAILED(r, "PcmAudioService::pause failed"); +} + +void PcmAudioService::resume() { + SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING); + SL_RETURN_IF_FAILED(r, "PcmAudioService::resume failed"); +} + +} // namespace cc diff --git a/cocos/audio/android/PcmAudioService.h b/cocos/audio/android/PcmAudioService.h new file mode 100644 index 0000000..1fe69e4 --- /dev/null +++ b/cocos/audio/android/PcmAudioService.h @@ -0,0 +1,78 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/android/IAudioPlayer.h" +#include "audio/android/OpenSLHelper.h" +#include "audio/android/PcmData.h" + +#include +#include +#include "audio/android/utils/Compat.h" + +namespace cc { + +class AudioMixerController; + +class PcmAudioService { +public: + inline int getChannelCount() const { return _numChannels; }; + + inline int getSampleRate() const { return _sampleRate; }; + +private: + PcmAudioService(SLEngineItf engineItf, SLObjectItf outputMixObject); + + virtual ~PcmAudioService(); + + bool init(AudioMixerController *controller, int numChannels, int sampleRate, int bufferSizeInBytes); + + bool enqueue(); + + void bqFetchBufferCallback(CCSLBufferQueueItf bq); + + void pause(); + void resume(); + + SLEngineItf _engineItf; + SLObjectItf _outputMixObj; + + SLObjectItf _playObj; + SLPlayItf _playItf; + SLVolumeItf _volumeItf; + CCSLBufferQueueItf _bufferQueueItf; + + int _numChannels; + int _sampleRate; + int _bufferSizeInBytes; + + AudioMixerController *_controller; + + friend class SLPcmAudioPlayerCallbackProxy; + friend class AudioPlayerProvider; +}; + +} // namespace cc diff --git a/cocos/audio/android/PcmBufferProvider.cpp b/cocos/audio/android/PcmBufferProvider.cpp new file mode 100644 index 0000000..71b571e --- /dev/null +++ b/cocos/audio/android/PcmBufferProvider.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "PcmBufferProvider" + +#include "audio/android/PcmBufferProvider.h" +#include "audio/android/cutils/log.h" + +//#define VERY_VERY_VERBOSE_LOGGING +#ifdef VERY_VERY_VERBOSE_LOGGING + #define ALOGVV ALOGV +#else + #define ALOGVV(a...) \ + do { \ + } while (0) +#endif + +namespace cc { + +PcmBufferProvider::PcmBufferProvider() +: _addr(nullptr), _numFrames(0), _frameSize(0), _nextFrame(0), _unrel(0) { +} + +bool PcmBufferProvider::init(const void *addr, size_t frames, size_t frameSize) { + _addr = addr; + _numFrames = frames; + _frameSize = frameSize; + _nextFrame = 0; + _unrel = 0; + return true; +} + +status_t PcmBufferProvider::getNextBuffer(Buffer *buffer, + int64_t pts /* = kInvalidPTS*/) { + (void)pts; // suppress warning + size_t requestedFrames = buffer->frameCount; + if (requestedFrames > _numFrames - _nextFrame) { + buffer->frameCount = _numFrames - _nextFrame; + } + + ALOGVV( + "getNextBuffer() requested %zu frames out of %zu frames available," + " and returned %zu frames", + requestedFrames, (size_t)(_numFrames - _nextFrame), buffer->frameCount); + + _unrel = buffer->frameCount; + if (buffer->frameCount > 0) { + buffer->raw = (char *)_addr + _frameSize * _nextFrame; + return NO_ERROR; + } else { + buffer->raw = NULL; + return NOT_ENOUGH_DATA; + } +} + +void PcmBufferProvider::releaseBuffer(Buffer *buffer) { + if (buffer->frameCount > _unrel) { + ALOGVV( + "ERROR releaseBuffer() released %zu frames but only %zu available " + "to release", + buffer->frameCount, _unrel); + _nextFrame += _unrel; + _unrel = 0; + } else { + ALOGVV( + "releaseBuffer() released %zu frames out of %zu frames available " + "to release", + buffer->frameCount, _unrel); + _nextFrame += buffer->frameCount; + _unrel -= buffer->frameCount; + } + buffer->frameCount = 0; + buffer->raw = NULL; +} + +void PcmBufferProvider::reset() { + _nextFrame = 0; +} + +} // namespace cc diff --git a/cocos/audio/android/PcmBufferProvider.h b/cocos/audio/android/PcmBufferProvider.h new file mode 100644 index 0000000..ece61a7 --- /dev/null +++ b/cocos/audio/android/PcmBufferProvider.h @@ -0,0 +1,51 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/android/AudioBufferProvider.h" + +#include +#include + +namespace cc { + +class PcmBufferProvider : public AudioBufferProvider { +public: + PcmBufferProvider(); + bool init(const void *addr, size_t frames, size_t frameSize); + virtual status_t getNextBuffer(Buffer *buffer, int64_t pts = kInvalidPTS) override; + virtual void releaseBuffer(Buffer *buffer) override; + void reset(); + +protected: + const void *_addr; // base address + size_t _numFrames; // total frames + size_t _frameSize; // size of each frame in bytes + size_t _nextFrame; // index of next frame to provide + size_t _unrel; // number of frames not yet released +}; + +} // namespace cc diff --git a/cocos/audio/android/PcmData.cpp b/cocos/audio/android/PcmData.cpp new file mode 100644 index 0000000..3241984 --- /dev/null +++ b/cocos/audio/android/PcmData.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "PcmData" + +#include "audio/android/PcmData.h" +#include "audio/android/OpenSLHelper.h" + +namespace cc { + +PcmData::PcmData() { + // ALOGV("In the constructor of PcmData (%p)", this); + reset(); +} + +PcmData::~PcmData() { + // ALOGV("In the destructor of PcmData (%p)", this); +} + +PcmData::PcmData(const PcmData &o) { + // ALOGV("In the copy constructor of PcmData (%p)", this); + numChannels = o.numChannels; + sampleRate = o.sampleRate; + bitsPerSample = o.bitsPerSample; + containerSize = o.containerSize; + channelMask = o.channelMask; + endianness = o.endianness; + numFrames = o.numFrames; + duration = o.duration; + pcmBuffer = std::move(o.pcmBuffer); +} + +PcmData::PcmData(PcmData &&o) { + // ALOGV("In the move constructor of PcmData (%p)", this); + numChannels = o.numChannels; + sampleRate = o.sampleRate; + bitsPerSample = o.bitsPerSample; + containerSize = o.containerSize; + channelMask = o.channelMask; + endianness = o.endianness; + numFrames = o.numFrames; + duration = o.duration; + pcmBuffer = std::move(o.pcmBuffer); + o.reset(); +} + +PcmData &PcmData::operator=(const PcmData &o) { + // ALOGV("In the copy assignment of PcmData"); + numChannels = o.numChannels; + sampleRate = o.sampleRate; + bitsPerSample = o.bitsPerSample; + containerSize = o.containerSize; + channelMask = o.channelMask; + endianness = o.endianness; + numFrames = o.numFrames; + duration = o.duration; + pcmBuffer = o.pcmBuffer; + return *this; +} + +PcmData &PcmData::operator=(PcmData &&o) { + // ALOGV("In the move assignment of PcmData"); + numChannels = o.numChannels; + sampleRate = o.sampleRate; + bitsPerSample = o.bitsPerSample; + containerSize = o.containerSize; + channelMask = o.channelMask; + endianness = o.endianness; + numFrames = o.numFrames; + duration = o.duration; + pcmBuffer = std::move(o.pcmBuffer); + o.reset(); + return *this; +} + +void PcmData::reset() { + numChannels = -1; + sampleRate = -1; + bitsPerSample = -1; + containerSize = -1; + channelMask = -1; + endianness = -1; + numFrames = -1; + duration = -1.0f; + pcmBuffer = nullptr; +} + +bool PcmData::isValid() const { + return numChannels > 0 && sampleRate > 0 && bitsPerSample > 0 && containerSize > 0 && numFrames > 0 && duration > 0 && pcmBuffer != nullptr; +} + +ccstd::string PcmData::toString() const { + ccstd::string ret; + char buf[256] = {0}; + + snprintf(buf, sizeof(buf), + "numChannels: %d, sampleRate: %d, bitPerSample: %d, containerSize: %d, " + "channelMask: %d, endianness: %d, numFrames: %d, duration: %f", + numChannels, sampleRate, bitsPerSample, containerSize, channelMask, endianness, + numFrames, duration); + + ret = buf; + return ret; +} + +} // namespace cc diff --git a/cocos/audio/android/PcmData.h b/cocos/audio/android/PcmData.h new file mode 100644 index 0000000..b1096e3 --- /dev/null +++ b/cocos/audio/android/PcmData.h @@ -0,0 +1,65 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +namespace cc { + +struct PcmData { + std::shared_ptr> pcmBuffer; + int numChannels; + int sampleRate; + int bitsPerSample; + int containerSize; + int channelMask; + int endianness; + int numFrames; + float duration; // in seconds + + PcmData(); + + ~PcmData(); + + PcmData(const PcmData &o); + + PcmData(PcmData &&o); + + PcmData &operator=(const PcmData &o); + + PcmData &operator=(PcmData &&o); + + void reset(); + + bool isValid() const; + + ccstd::string toString() const; +}; + +} // namespace cc diff --git a/cocos/audio/android/Track.cpp b/cocos/audio/android/Track.cpp new file mode 100644 index 0000000..9014ae7 --- /dev/null +++ b/cocos/audio/android/Track.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "Track" + +#include "audio/android/Track.h" +#include "audio/android/cutils/log.h" + +#include + +namespace cc { + +Track::Track(const PcmData &pcmData) +: onStateChanged(nullptr), _pcmData(pcmData), _prevState(State::IDLE), _state(State::IDLE), _name(-1), _volume(1.0f), _isVolumeDirty(true), _isLoop(false), _isInitialized(false), _isAudioFocus(true) { + init(_pcmData.pcmBuffer->data(), _pcmData.numFrames, _pcmData.bitsPerSample / 8 * _pcmData.numChannels); +} + +Track::~Track() { + ALOGV("~Track(): %p", this); +} + +gain_minifloat_packed_t Track::getVolumeLR() { + float volume = _isAudioFocus ? _volume : 0.0f; + gain_minifloat_t v = gain_from_float(volume); + return gain_minifloat_pack(v, v); +} + +bool Track::setPosition(float pos) { + _nextFrame = (size_t)(pos * _numFrames / _pcmData.duration); + _unrel = 0; + return true; +} + +float Track::getPosition() const { + return _nextFrame * _pcmData.duration / _numFrames; +} + +void Track::setVolume(float volume) { + std::lock_guard lk(_volumeDirtyMutex); + if (fabs(_volume - volume) > 0.00001) { + _volume = volume; + setVolumeDirty(true); + } +} + +float Track::getVolume() const { + return _volume; +} + +void Track::setAudioFocus(bool isFocus) { + _isAudioFocus = isFocus; + setVolumeDirty(true); +} + +void Track::setState(State state) { + std::lock_guard lk(_stateMutex); + if (_state != state) { + _prevState = _state; + _state = state; + onStateChanged(_state); + } +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/android/Track.h b/cocos/audio/android/Track.h new file mode 100644 index 0000000..9b3c0b8 --- /dev/null +++ b/cocos/audio/android/Track.h @@ -0,0 +1,101 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/android/IVolumeProvider.h" +#include "audio/android/PcmBufferProvider.h" +#include "audio/android/PcmData.h" + +#include +#include + +namespace cc { + +class Track : public PcmBufferProvider, public IVolumeProvider { +public: + enum class State { + IDLE, + PLAYING, + RESUMED, + PAUSED, + STOPPED, + OVER, + DESTROYED + }; + + Track(const PcmData &pcmData); + virtual ~Track(); + + inline State getState() const { return _state; }; + void setState(State state); + + inline State getPrevState() const { return _prevState; }; + + inline bool isPlayOver() const { return _state == State::PLAYING && _nextFrame >= _numFrames; }; + inline void setName(int name) { _name = name; }; + inline int getName() const { return _name; }; + + void setVolume(float volume); + float getVolume() const; + + void setAudioFocus(bool isFocus); + + bool setPosition(float pos); + float getPosition() const; + + virtual gain_minifloat_packed_t getVolumeLR() override; + + inline void setLoop(bool isLoop) { _isLoop = isLoop; }; + inline bool isLoop() const { return _isLoop; }; + + std::function onStateChanged; + +private: + inline bool isVolumeDirty() const { return _isVolumeDirty; }; + + inline void setVolumeDirty(bool isDirty) { _isVolumeDirty = isDirty; }; + + inline bool isInitialized() const { return _isInitialized; }; + + inline void setInitialized(bool isInitialized) { _isInitialized = isInitialized; }; + +private: + PcmData _pcmData; + State _prevState; + State _state; + std::mutex _stateMutex; + int _name; + float _volume; + bool _isVolumeDirty; + std::mutex _volumeDirtyMutex; + bool _isLoop; + bool _isInitialized; + bool _isAudioFocus; + + friend class AudioMixerController; +}; + +} // namespace cc diff --git a/cocos/audio/android/UrlAudioPlayer.cpp b/cocos/audio/android/UrlAudioPlayer.cpp new file mode 100644 index 0000000..881f49b --- /dev/null +++ b/cocos/audio/android/UrlAudioPlayer.cpp @@ -0,0 +1,360 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "UrlAudioPlayer" + +#include "audio/android/UrlAudioPlayer.h" +#include "audio/android/ICallerThreadUtils.h" +#include "base/std/container/vector.h" +#include "base/Macros.h" +#include +#include // for std::find + +namespace { + +std::mutex __playerContainerMutex;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming) +ccstd::vector __playerContainer;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming) +std::once_flag __onceFlag;//NOLINT(bugprone-reserved-identifier,readability-identifier-naming) + +} // namespace + +namespace cc { + +class SLUrlAudioPlayerCallbackProxy { +public: + static void playEventCallback(SLPlayItf caller, void *context, SLuint32 playEvent) { + auto *thiz = reinterpret_cast(context); + // We must use a mutex for the whole block of the following function invocation. + std::lock_guard lk(__playerContainerMutex); + auto iter = std::find(__playerContainer.begin(), __playerContainer.end(), thiz); + if (iter != __playerContainer.end()) { + thiz->playEventCallback(caller, playEvent); + } + } +}; + +UrlAudioPlayer::UrlAudioPlayer(SLEngineItf engineItf, SLObjectItf outputMixObject, ICallerThreadUtils *callerThreadUtils) +: _engineItf(engineItf), _outputMixObj(outputMixObject), _callerThreadUtils(callerThreadUtils), _id(-1), _assetFd(nullptr), _playObj(nullptr), _playItf(nullptr), _seekItf(nullptr), _volumeItf(nullptr), _volume(0.0F), _duration(0.0F), _isLoop(false), _isAudioFocus(true), _state(State::INVALID), _playEventCallback(nullptr), _isDestroyed(std::make_shared(false)) { + std::call_once(__onceFlag, []() { + __playerContainer.reserve(10); + }); + + __playerContainerMutex.lock(); + __playerContainer.push_back(this); + ALOGV("Current UrlAudioPlayer instance count: %d", (int)__playerContainer.size()); + __playerContainerMutex.unlock(); + + _callerThreadId = callerThreadUtils->getCallerThreadId(); +} + +UrlAudioPlayer::~UrlAudioPlayer() { + ALOGV("~UrlAudioPlayer(): %p", this); + + __playerContainerMutex.lock(); + + auto iter = std::find(__playerContainer.begin(), __playerContainer.end(), this); + if (iter != __playerContainer.end()) { + __playerContainer.erase(iter); + } + + __playerContainerMutex.unlock(); +} + +void UrlAudioPlayer::playEventCallback(SLPlayItf caller, SLuint32 playEvent) { + CC_UNUSED_PARAM(caller); + // Note that it's on sub thread, please don't invoke OpenSLES API on sub thread + if (playEvent == SL_PLAYEVENT_HEADATEND) { + std::shared_ptr isDestroyed = _isDestroyed; + + auto func = [this, isDestroyed]() { + // If it was destroyed, just return. + if (*isDestroyed) { + ALOGV("The UrlAudioPlayer (%p) was destroyed!", this); + return; + } + + //Note that It's in the caller's thread (Cocos Thread) + // If state is already stopped, ignore the play over event. + + if (_state == State::STOPPED) { + return; + } + + //fix issue#8965:AudioEngine can't looping audio on Android 2.3.x + if (isLoop()) { + play(); + } else { + setState(State::OVER); + if (_playEventCallback != nullptr) { + _playEventCallback(State::OVER); + } + + ALOGV("UrlAudioPlayer (%p) played over, destroy self ...", this); + destroy(); + delete this; + } + }; + + if (_callerThreadId == std::this_thread::get_id()) { + func(); + } else { + _callerThreadUtils->performFunctionInCallerThread(func); + } + } +} + +void UrlAudioPlayer::setPlayEventCallback(const PlayEventCallback &playEventCallback) { + _playEventCallback = playEventCallback; +} + +void UrlAudioPlayer::stop() { + ALOGV("UrlAudioPlayer::stop (%p, %d)", this, getId()); + SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_STOPPED); + SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::stop failed"); + + if (_state == State::PLAYING || _state == State::PAUSED) { + setLoop(false); + setState(State::STOPPED); + + if (_playEventCallback != nullptr) { + _playEventCallback(State::STOPPED); + } + + destroy(); + delete this; + } else { + ALOGW("UrlAudioPlayer (%p, state:%d) isn't playing or paused, could not invoke stop!", this, static_cast(_state)); + } +} + +void UrlAudioPlayer::pause() { + if (_state == State::PLAYING) { + SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PAUSED); + SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::pause failed"); + setState(State::PAUSED); + } else { + ALOGW("UrlAudioPlayer (%p, state:%d) isn't playing, could not invoke pause!", this, static_cast(_state)); + } +} + +void UrlAudioPlayer::resume() { + if (_state == State::PAUSED) { + SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING); + SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::resume failed"); + setState(State::PLAYING); + } else { + ALOGW("UrlAudioPlayer (%p, state:%d) isn't paused, could not invoke resume!", this, static_cast(_state)); + } +} + +void UrlAudioPlayer::play() { + if (_state == State::INITIALIZED || _state == State::PAUSED) { + SLresult r = (*_playItf)->SetPlayState(_playItf, SL_PLAYSTATE_PLAYING); + SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::play failed"); + setState(State::PLAYING); + } else { + ALOGW("UrlAudioPlayer (%p, state:%d) isn't paused or initialized, could not invoke play!", this, static_cast(_state)); + } +} + +void UrlAudioPlayer::setVolumeToSLPlayer(float volume) { + int dbVolume = static_cast(2000 * log10(volume)); + if (dbVolume < SL_MILLIBEL_MIN) { + dbVolume = SL_MILLIBEL_MIN; + } + SLresult r = (*_volumeItf)->SetVolumeLevel(_volumeItf, dbVolume); + SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::setVolumeToSLPlayer %d failed", dbVolume); +} + +void UrlAudioPlayer::setVolume(float volume) { + _volume = volume; + if (_isAudioFocus) { + setVolumeToSLPlayer(_volume); + } +} + +float UrlAudioPlayer::getVolume() const { + return _volume; +} + +void UrlAudioPlayer::setAudioFocus(bool isFocus) { + _isAudioFocus = isFocus; + float volume = _isAudioFocus ? _volume : 0.0F; + setVolumeToSLPlayer(volume); +} + +float UrlAudioPlayer::getDuration() const { + if (_duration > 0) { + return _duration; + } + + SLmillisecond duration; + SLresult r = (*_playItf)->GetDuration(_playItf, &duration); + SL_RETURN_VAL_IF_FAILED(r, 0.0F, "UrlAudioPlayer::getDuration failed"); + + if (duration == SL_TIME_UNKNOWN) { + return -1.0F; + } else {// NOLINT(readability-else-after-return) + const_cast(this)->_duration = duration / 1000.0F; + + if (_duration <= 0) { + return -1.0F; + } + } + return _duration; +} + +float UrlAudioPlayer::getPosition() const { + SLmillisecond millisecond; + SLresult r = (*_playItf)->GetPosition(_playItf, &millisecond); + SL_RETURN_VAL_IF_FAILED(r, 0.0F, "UrlAudioPlayer::getPosition failed"); + return millisecond / 1000.0F; +} + +bool UrlAudioPlayer::setPosition(float pos) { + SLmillisecond millisecond = 1000.0F * pos; + SLresult r = (*_seekItf)->SetPosition(_seekItf, millisecond, SL_SEEKMODE_ACCURATE); + SL_RETURN_VAL_IF_FAILED(r, false, "UrlAudioPlayer::setPosition %f failed", pos); + return true; +} + +bool UrlAudioPlayer::prepare(const ccstd::string &url, SLuint32 locatorType, std::shared_ptr assetFd, int start, + int length) { + _url = url; + _assetFd = std::move(assetFd); +#if CC_PLATFORM == CC_PLATFORM_ANDROID + const char *locatorTypeStr = "UNKNOWN"; + if (locatorType == SL_DATALOCATOR_ANDROIDFD) { + locatorTypeStr = "SL_DATALOCATOR_ANDROIDFD"; + } else if (locatorType == SL_DATALOCATOR_URI) { + locatorTypeStr = "SL_DATALOCATOR_URI"; + } else { + ALOGE("Oops, invalid locatorType: %d", (int)locatorType); + return false; + } + + ALOGV("UrlAudioPlayer::prepare: %s, %s, %d, %d, %d", _url.c_str(), locatorTypeStr, _assetFd->getFd(), start, + length); + SLDataSource audioSrc; + + SLDataFormat_MIME formatMime = {SL_DATAFORMAT_MIME, nullptr, SL_CONTAINERTYPE_UNSPECIFIED}; + audioSrc.pFormat = &formatMime; + + //Note: locFd & locUri should be outside of the following if/else block + // Although locFd & locUri are only used inside if/else block, its lifecycle + // will be destroyed right after '}' block. And since we pass a pointer to + // 'audioSrc.pLocator=&locFd/&locUri', pLocator will point to an invalid address + // while invoking Engine::createAudioPlayer interface. So be care of change the position + // of these two variables. + SLDataLocator_AndroidFD locFd; + SLDataLocator_URI locUri; + + if (locatorType == SL_DATALOCATOR_ANDROIDFD) { + locFd = {locatorType, _assetFd->getFd(), start, length}; + audioSrc.pLocator = &locFd; + } else if (locatorType == SL_DATALOCATOR_URI) { + locUri = {locatorType, (SLchar *)_url.c_str()}; // NOLINT(google-readability-casting) + audioSrc.pLocator = &locUri; + ALOGV("locUri: locatorType: %d", (int)locUri.locatorType); + } + + // configure audio sink + SLDataLocator_OutputMix locOutmix = {SL_DATALOCATOR_OUTPUTMIX, _outputMixObj}; + SLDataSink audioSnk = {&locOutmix, nullptr}; + + // create audio player + const SLInterfaceID ids[3] = {SL_IID_SEEK, SL_IID_PREFETCHSTATUS, SL_IID_VOLUME}; + const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + + SLresult result = (*_engineItf)->CreateAudioPlayer(_engineItf, &_playObj, &audioSrc, &audioSnk, 3, ids, req); + SL_RETURN_VAL_IF_FAILED(result, false, "CreateAudioPlayer failed"); + + // realize the player + result = (*_playObj)->Realize(_playObj, SL_BOOLEAN_FALSE); + SL_RETURN_VAL_IF_FAILED(result, false, "Realize failed"); + + // get the play interface + result = (*_playObj)->GetInterface(_playObj, SL_IID_PLAY, &_playItf); + SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_PLAY failed"); + + // get the seek interface + result = (*_playObj)->GetInterface(_playObj, SL_IID_SEEK, &_seekItf); + SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_SEEK failed"); + + // get the volume interface + result = (*_playObj)->GetInterface(_playObj, SL_IID_VOLUME, &_volumeItf); + SL_RETURN_VAL_IF_FAILED(result, false, "GetInterface SL_IID_VOLUME failed"); + + result = (*_playItf)->RegisterCallback(_playItf, + SLUrlAudioPlayerCallbackProxy::playEventCallback, this); + SL_RETURN_VAL_IF_FAILED(result, false, "RegisterCallback failed"); + + result = (*_playItf)->SetCallbackEventsMask(_playItf, SL_PLAYEVENT_HEADATEND); + SL_RETURN_VAL_IF_FAILED(result, false, "SetCallbackEventsMask SL_PLAYEVENT_HEADATEND failed"); + + setState(State::INITIALIZED); + + setVolume(1.0F); +#endif + return true; +} + +void UrlAudioPlayer::rewind() { + // Not supported currently. since cocos audio engine will new -> prepare -> play again. +} + +void UrlAudioPlayer::setLoop(bool isLoop) { + _isLoop = isLoop; + + SLboolean loopEnable = _isLoop ? SL_BOOLEAN_TRUE : SL_BOOLEAN_FALSE; + SLresult r = (*_seekItf)->SetLoop(_seekItf, loopEnable, 0, SL_TIME_UNKNOWN); + SL_RETURN_IF_FAILED(r, "UrlAudioPlayer::setLoop %d failed", _isLoop ? 1 : 0); +} + +bool UrlAudioPlayer::isLoop() const { + return _isLoop; +} + +void UrlAudioPlayer::stopAll() { + // To avoid break the for loop, we need to copy a new map + __playerContainerMutex.lock(); + auto temp = __playerContainer; + __playerContainerMutex.unlock(); + + for (auto &&player : temp) { + player->stop(); + } +} + +void UrlAudioPlayer::destroy() { + if (!*_isDestroyed) { + *_isDestroyed = true; + ALOGV("UrlAudioPlayer::destroy() %p", this); + SL_DESTROY_OBJ(_playObj); + ALOGV("UrlAudioPlayer::destroy end"); + } +} + +} // namespace cc diff --git a/cocos/audio/android/UrlAudioPlayer.h b/cocos/audio/android/UrlAudioPlayer.h new file mode 100644 index 0000000..0b289e8 --- /dev/null +++ b/cocos/audio/android/UrlAudioPlayer.h @@ -0,0 +1,127 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include "audio/android/AssetFd.h" +#include "audio/android/IAudioPlayer.h" +#include "audio/android/OpenSLHelper.h" + +namespace cc { + +class ICallerThreadUtils; +class AssetFd; + +class UrlAudioPlayer : public IAudioPlayer { +public: + // Override Functions Begin + virtual int getId() const override { return _id; }; + + virtual void setId(int id) override { _id = id; }; + + virtual ccstd::string getUrl() const override { return _url; }; + + virtual State getState() const override { return _state; }; + + virtual void play() override; + + virtual void pause() override; + + virtual void resume() override; + + virtual void stop() override; + + virtual void rewind() override; + + virtual void setVolume(float volume) override; + + virtual float getVolume() const override; + + virtual void setAudioFocus(bool isFocus) override; + + virtual void setLoop(bool isLoop) override; + + virtual bool isLoop() const override; + + virtual float getDuration() const override; + + virtual float getPosition() const override; + + virtual bool setPosition(float pos) override; + + virtual void setPlayEventCallback(const PlayEventCallback &playEventCallback) override; + + // Override Functions EndOv + +private: + UrlAudioPlayer(SLEngineItf engineItf, SLObjectItf outputMixObject, ICallerThreadUtils *callerThreadUtils); + virtual ~UrlAudioPlayer(); + + bool prepare(const ccstd::string &url, SLuint32 locatorType, std::shared_ptr assetFd, int start, int length); + + static void stopAll(); + + void destroy(); + + inline void setState(State state) { _state = state; }; + + void playEventCallback(SLPlayItf caller, SLuint32 playEvent); + + void setVolumeToSLPlayer(float volume); + +private: + SLEngineItf _engineItf; + SLObjectItf _outputMixObj; + ICallerThreadUtils *_callerThreadUtils; + + int _id; + ccstd::string _url; + + std::shared_ptr _assetFd; + + SLObjectItf _playObj; + SLPlayItf _playItf; + SLSeekItf _seekItf; + SLVolumeItf _volumeItf; + + float _volume; + float _duration; + bool _isLoop; + bool _isAudioFocus; + State _state; + + PlayEventCallback _playEventCallback; + + std::thread::id _callerThreadId; + std::shared_ptr _isDestroyed; + + friend class SLUrlAudioPlayerCallbackProxy; + friend class AudioPlayerProvider; +}; + +} // namespace cc diff --git a/cocos/audio/android/audio.h b/cocos/audio/android/audio.h new file mode 100644 index 0000000..36d5557 --- /dev/null +++ b/cocos/audio/android/audio.h @@ -0,0 +1,491 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +// ---------------------------------------------------------------------------- + +#include +#include "audio/android/cutils/bitops.h" + +#define PROPERTY_VALUE_MAX 256 +#define CONSTEXPR constexpr + +#ifdef __cplusplus + #define CC_LIKELY(exp) (__builtin_expect(!!(exp), true)) + #define CC_UNLIKELY(exp) (__builtin_expect(!!(exp), false)) +#else + #define CC_LIKELY(exp) (__builtin_expect(!!(exp), 1)) + #define CC_UNLIKELY(exp) (__builtin_expect(!!(exp), 0)) +#endif + +/* special audio session values + * (XXX: should this be living in the audio effects land?) + */ +typedef enum { + /* session for effects attached to a particular output stream + * (value must be less than 0) + */ + AUDIO_SESSION_OUTPUT_STAGE = -1, + + /* session for effects applied to output mix. These effects can + * be moved by audio policy manager to another output stream + * (value must be 0) + */ + AUDIO_SESSION_OUTPUT_MIX = 0, + + /* application does not specify an explicit session ID to be used, + * and requests a new session ID to be allocated + * REFINE: use unique values for AUDIO_SESSION_OUTPUT_MIX and AUDIO_SESSION_ALLOCATE, + * after all uses have been updated from 0 to the appropriate symbol, and have been tested. + */ + AUDIO_SESSION_ALLOCATE = 0, +} audio_session_t; + +/* Audio sub formats (see enum audio_format). */ + +/* PCM sub formats */ +typedef enum { + /* All of these are in native byte order */ + AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1, /* DO NOT CHANGE - PCM signed 16 bits */ + AUDIO_FORMAT_PCM_SUB_8_BIT = 0x2, /* DO NOT CHANGE - PCM unsigned 8 bits */ + AUDIO_FORMAT_PCM_SUB_32_BIT = 0x3, /* PCM signed .31 fixed point */ + AUDIO_FORMAT_PCM_SUB_8_24_BIT = 0x4, /* PCM signed 8.23 fixed point */ + AUDIO_FORMAT_PCM_SUB_FLOAT = 0x5, /* PCM single-precision floating point */ + AUDIO_FORMAT_PCM_SUB_24_BIT_PACKED = 0x6, /* PCM signed .23 fixed point packed in 3 bytes */ +} audio_format_pcm_sub_fmt_t; + +/* The audio_format_*_sub_fmt_t declarations are not currently used */ + +/* MP3 sub format field definition : can use 11 LSBs in the same way as MP3 + * frame header to specify bit rate, stereo mode, version... + */ +typedef enum { + AUDIO_FORMAT_MP3_SUB_NONE = 0x0, +} audio_format_mp3_sub_fmt_t; + +/* AMR NB/WB sub format field definition: specify frame block interleaving, + * bandwidth efficient or octet aligned, encoding mode for recording... + */ +typedef enum { + AUDIO_FORMAT_AMR_SUB_NONE = 0x0, +} audio_format_amr_sub_fmt_t; + +/* AAC sub format field definition: specify profile or bitrate for recording... */ +typedef enum { + AUDIO_FORMAT_AAC_SUB_MAIN = 0x1, + AUDIO_FORMAT_AAC_SUB_LC = 0x2, + AUDIO_FORMAT_AAC_SUB_SSR = 0x4, + AUDIO_FORMAT_AAC_SUB_LTP = 0x8, + AUDIO_FORMAT_AAC_SUB_HE_V1 = 0x10, + AUDIO_FORMAT_AAC_SUB_SCALABLE = 0x20, + AUDIO_FORMAT_AAC_SUB_ERLC = 0x40, + AUDIO_FORMAT_AAC_SUB_LD = 0x80, + AUDIO_FORMAT_AAC_SUB_HE_V2 = 0x100, + AUDIO_FORMAT_AAC_SUB_ELD = 0x200, +} audio_format_aac_sub_fmt_t; + +/* VORBIS sub format field definition: specify quality for recording... */ +typedef enum { + AUDIO_FORMAT_VORBIS_SUB_NONE = 0x0, +} audio_format_vorbis_sub_fmt_t; + +/* Audio format consists of a main format field (upper 8 bits) and a sub format + * field (lower 24 bits). + * + * The main format indicates the main codec type. The sub format field + * indicates options and parameters for each format. The sub format is mainly + * used for record to indicate for instance the requested bitrate or profile. + * It can also be used for certain formats to give informations not present in + * the encoded audio stream (e.g. octet alignment for AMR). + */ +typedef enum { + AUDIO_FORMAT_INVALID = 0xFFFFFFFFUL, + AUDIO_FORMAT_DEFAULT = 0, + AUDIO_FORMAT_PCM = 0x00000000UL, /* DO NOT CHANGE */ + AUDIO_FORMAT_MP3 = 0x01000000UL, + AUDIO_FORMAT_AMR_NB = 0x02000000UL, + AUDIO_FORMAT_AMR_WB = 0x03000000UL, + AUDIO_FORMAT_AAC = 0x04000000UL, + AUDIO_FORMAT_HE_AAC_V1 = 0x05000000UL, /* Deprecated, Use AUDIO_FORMAT_AAC_HE_V1*/ + AUDIO_FORMAT_HE_AAC_V2 = 0x06000000UL, /* Deprecated, Use AUDIO_FORMAT_AAC_HE_V2*/ + AUDIO_FORMAT_VORBIS = 0x07000000UL, + AUDIO_FORMAT_OPUS = 0x08000000UL, + AUDIO_FORMAT_AC3 = 0x09000000UL, + AUDIO_FORMAT_E_AC3 = 0x0A000000UL, + AUDIO_FORMAT_DTS = 0x0B000000UL, + AUDIO_FORMAT_DTS_HD = 0x0C000000UL, + AUDIO_FORMAT_MAIN_MASK = 0xFF000000UL, + AUDIO_FORMAT_SUB_MASK = 0x00FFFFFFUL, + + /* Aliases */ + /* note != AudioFormat.ENCODING_PCM_16BIT */ + AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_16_BIT), + /* note != AudioFormat.ENCODING_PCM_8BIT */ + AUDIO_FORMAT_PCM_8_BIT = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_8_BIT), + AUDIO_FORMAT_PCM_32_BIT = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_32_BIT), + AUDIO_FORMAT_PCM_8_24_BIT = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_8_24_BIT), + AUDIO_FORMAT_PCM_FLOAT = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_FLOAT), + AUDIO_FORMAT_PCM_24_BIT_PACKED = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_24_BIT_PACKED), + AUDIO_FORMAT_AAC_MAIN = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_MAIN), + AUDIO_FORMAT_AAC_LC = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_LC), + AUDIO_FORMAT_AAC_SSR = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_SSR), + AUDIO_FORMAT_AAC_LTP = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_LTP), + AUDIO_FORMAT_AAC_HE_V1 = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_HE_V1), + AUDIO_FORMAT_AAC_SCALABLE = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_SCALABLE), + AUDIO_FORMAT_AAC_ERLC = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_ERLC), + AUDIO_FORMAT_AAC_LD = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_LD), + AUDIO_FORMAT_AAC_HE_V2 = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_HE_V2), + AUDIO_FORMAT_AAC_ELD = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_ELD), +} audio_format_t; + +/* For the channel mask for position assignment representation */ +enum { + /* These can be a complete audio_channel_mask_t. */ + AUDIO_CHANNEL_NONE = 0x0, + AUDIO_CHANNEL_INVALID = 0xC0000000, + /* These can be the bits portion of an audio_channel_mask_t + * with representation AUDIO_CHANNEL_REPRESENTATION_POSITION. + * Using these bits as a complete audio_channel_mask_t is deprecated. + */ + /* output channels */ + AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1, + AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2, + AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4, + AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8, + AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10, + AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20, + AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40, + AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80, + AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100, + AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200, + AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400, + AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800, + AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000, + AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000, + AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000, + AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000, + AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000, + AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000, + /* REFINE: should these be considered complete channel masks, or only bits? */ + AUDIO_CHANNEL_OUT_MONO = AUDIO_CHANNEL_OUT_FRONT_LEFT, + AUDIO_CHANNEL_OUT_STEREO = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT), + AUDIO_CHANNEL_OUT_QUAD = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_BACK_LEFT | + AUDIO_CHANNEL_OUT_BACK_RIGHT), + AUDIO_CHANNEL_OUT_QUAD_BACK = AUDIO_CHANNEL_OUT_QUAD, + /* like AUDIO_CHANNEL_OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_* */ + AUDIO_CHANNEL_OUT_QUAD_SIDE = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_SIDE_LEFT | + AUDIO_CHANNEL_OUT_SIDE_RIGHT), + AUDIO_CHANNEL_OUT_5POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_FRONT_CENTER | + AUDIO_CHANNEL_OUT_LOW_FREQUENCY | + AUDIO_CHANNEL_OUT_BACK_LEFT | + AUDIO_CHANNEL_OUT_BACK_RIGHT), + AUDIO_CHANNEL_OUT_5POINT1_BACK = AUDIO_CHANNEL_OUT_5POINT1, + /* like AUDIO_CHANNEL_OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_* */ + AUDIO_CHANNEL_OUT_5POINT1_SIDE = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_FRONT_CENTER | + AUDIO_CHANNEL_OUT_LOW_FREQUENCY | + AUDIO_CHANNEL_OUT_SIDE_LEFT | + AUDIO_CHANNEL_OUT_SIDE_RIGHT), + // matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND definition for 7.1 + AUDIO_CHANNEL_OUT_7POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_FRONT_CENTER | + AUDIO_CHANNEL_OUT_LOW_FREQUENCY | + AUDIO_CHANNEL_OUT_BACK_LEFT | + AUDIO_CHANNEL_OUT_BACK_RIGHT | + AUDIO_CHANNEL_OUT_SIDE_LEFT | + AUDIO_CHANNEL_OUT_SIDE_RIGHT), + AUDIO_CHANNEL_OUT_ALL = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_FRONT_CENTER | + AUDIO_CHANNEL_OUT_LOW_FREQUENCY | + AUDIO_CHANNEL_OUT_BACK_LEFT | + AUDIO_CHANNEL_OUT_BACK_RIGHT | + AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER | + AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER | + AUDIO_CHANNEL_OUT_BACK_CENTER | + AUDIO_CHANNEL_OUT_SIDE_LEFT | + AUDIO_CHANNEL_OUT_SIDE_RIGHT | + AUDIO_CHANNEL_OUT_TOP_CENTER | + AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT | + AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER | + AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_TOP_BACK_LEFT | + AUDIO_CHANNEL_OUT_TOP_BACK_CENTER | + AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT), + /* These are bits only, not complete values */ + /* input channels */ + AUDIO_CHANNEL_IN_LEFT = 0x4, + AUDIO_CHANNEL_IN_RIGHT = 0x8, + AUDIO_CHANNEL_IN_FRONT = 0x10, + AUDIO_CHANNEL_IN_BACK = 0x20, + AUDIO_CHANNEL_IN_LEFT_PROCESSED = 0x40, + AUDIO_CHANNEL_IN_RIGHT_PROCESSED = 0x80, + AUDIO_CHANNEL_IN_FRONT_PROCESSED = 0x100, + AUDIO_CHANNEL_IN_BACK_PROCESSED = 0x200, + AUDIO_CHANNEL_IN_PRESSURE = 0x400, + AUDIO_CHANNEL_IN_X_AXIS = 0x800, + AUDIO_CHANNEL_IN_Y_AXIS = 0x1000, + AUDIO_CHANNEL_IN_Z_AXIS = 0x2000, + AUDIO_CHANNEL_IN_VOICE_UPLINK = 0x4000, + AUDIO_CHANNEL_IN_VOICE_DNLINK = 0x8000, + /* REFINE: should these be considered complete channel masks, or only bits, or deprecated? */ + AUDIO_CHANNEL_IN_MONO = AUDIO_CHANNEL_IN_FRONT, + AUDIO_CHANNEL_IN_STEREO = (AUDIO_CHANNEL_IN_LEFT | AUDIO_CHANNEL_IN_RIGHT), + AUDIO_CHANNEL_IN_FRONT_BACK = (AUDIO_CHANNEL_IN_FRONT | AUDIO_CHANNEL_IN_BACK), + AUDIO_CHANNEL_IN_ALL = (AUDIO_CHANNEL_IN_LEFT | + AUDIO_CHANNEL_IN_RIGHT | + AUDIO_CHANNEL_IN_FRONT | + AUDIO_CHANNEL_IN_BACK | + AUDIO_CHANNEL_IN_LEFT_PROCESSED | + AUDIO_CHANNEL_IN_RIGHT_PROCESSED | + AUDIO_CHANNEL_IN_FRONT_PROCESSED | + AUDIO_CHANNEL_IN_BACK_PROCESSED | + AUDIO_CHANNEL_IN_PRESSURE | + AUDIO_CHANNEL_IN_X_AXIS | + AUDIO_CHANNEL_IN_Y_AXIS | + AUDIO_CHANNEL_IN_Z_AXIS | + AUDIO_CHANNEL_IN_VOICE_UPLINK | + AUDIO_CHANNEL_IN_VOICE_DNLINK), +}; +/* A channel mask per se only defines the presence or absence of a channel, not the order. + * But see AUDIO_INTERLEAVE_* below for the platform convention of order. + * + * audio_channel_mask_t is an opaque type and its internal layout should not + * be assumed as it may change in the future. + * Instead, always use the functions declared in this header to examine. + * + * These are the current representations: + * + * AUDIO_CHANNEL_REPRESENTATION_POSITION + * is a channel mask representation for position assignment. + * Each low-order bit corresponds to the spatial position of a transducer (output), + * or interpretation of channel (input). + * The user of a channel mask needs to know the context of whether it is for output or input. + * The constants AUDIO_CHANNEL_OUT_* or AUDIO_CHANNEL_IN_* apply to the bits portion. + * It is not permitted for no bits to be set. + * + * AUDIO_CHANNEL_REPRESENTATION_INDEX + * is a channel mask representation for index assignment. + * Each low-order bit corresponds to a selected channel. + * There is no platform interpretation of the various bits. + * There is no concept of output or input. + * It is not permitted for no bits to be set. + * + * All other representations are reserved for future use. + * + * Warning: current representation distinguishes between input and output, but this will not the be + * case in future revisions of the platform. Wherever there is an ambiguity between input and output + * that is currently resolved by checking the channel mask, the implementer should look for ways to + * fix it with additional information outside of the mask. + */ +typedef uint32_t audio_channel_mask_t; + +/* Maximum number of channels for all representations */ +#define AUDIO_CHANNEL_COUNT_MAX 30 + +/* log(2) of maximum number of representations, not part of public API */ +#define AUDIO_CHANNEL_REPRESENTATION_LOG2 2 + +/* Representations */ +typedef enum { + AUDIO_CHANNEL_REPRESENTATION_POSITION = 0, // must be zero for compatibility + // 1 is reserved for future use + AUDIO_CHANNEL_REPRESENTATION_INDEX = 2, + // 3 is reserved for future use +} audio_channel_representation_t; + +/* The return value is undefined if the channel mask is invalid. */ +static inline uint32_t audio_channel_mask_get_bits(audio_channel_mask_t channel) { + return channel & ((1 << AUDIO_CHANNEL_COUNT_MAX) - 1); +} + +/* The return value is undefined if the channel mask is invalid. */ +static inline audio_channel_representation_t audio_channel_mask_get_representation( + audio_channel_mask_t channel) { + // The right shift should be sufficient, but also "and" for safety in case mask is not 32 bits + return (audio_channel_representation_t)((channel >> AUDIO_CHANNEL_COUNT_MAX) & ((1 << AUDIO_CHANNEL_REPRESENTATION_LOG2) - 1)); +} + +/* Returns the number of channels from an output channel mask, + * used in the context of audio output or playback. + * If a channel bit is set which could _not_ correspond to an output channel, + * it is excluded from the count. + * Returns zero if the representation is invalid. + */ +static inline uint32_t audio_channel_count_from_out_mask(audio_channel_mask_t channel) { + uint32_t bits = audio_channel_mask_get_bits(channel); + switch (audio_channel_mask_get_representation(channel)) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + // REFINE: We can now merge with from_in_mask and remove anding + bits &= AUDIO_CHANNEL_OUT_ALL; + // fall through + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + return popcount(bits); + default: + return 0; + } +} + +static inline bool audio_is_valid_format(audio_format_t format) { + switch (format & AUDIO_FORMAT_MAIN_MASK) { + case AUDIO_FORMAT_PCM: + switch (format) { + case AUDIO_FORMAT_PCM_16_BIT: + case AUDIO_FORMAT_PCM_8_BIT: + case AUDIO_FORMAT_PCM_32_BIT: + case AUDIO_FORMAT_PCM_8_24_BIT: + case AUDIO_FORMAT_PCM_FLOAT: + case AUDIO_FORMAT_PCM_24_BIT_PACKED: + return true; + default: + return false; + } + /* not reached */ + case AUDIO_FORMAT_MP3: + case AUDIO_FORMAT_AMR_NB: + case AUDIO_FORMAT_AMR_WB: + case AUDIO_FORMAT_AAC: + case AUDIO_FORMAT_HE_AAC_V1: + case AUDIO_FORMAT_HE_AAC_V2: + case AUDIO_FORMAT_VORBIS: + case AUDIO_FORMAT_OPUS: + case AUDIO_FORMAT_AC3: + case AUDIO_FORMAT_E_AC3: + case AUDIO_FORMAT_DTS: + case AUDIO_FORMAT_DTS_HD: + return true; + default: + return false; + } +} + +static inline bool audio_is_linear_pcm(audio_format_t format) { + return ((format & AUDIO_FORMAT_MAIN_MASK) == AUDIO_FORMAT_PCM); +} + +static inline size_t audio_bytes_per_sample(audio_format_t format) { + size_t size = 0; + + switch (format) { + case AUDIO_FORMAT_PCM_32_BIT: + case AUDIO_FORMAT_PCM_8_24_BIT: + size = sizeof(int32_t); + break; + case AUDIO_FORMAT_PCM_24_BIT_PACKED: + size = sizeof(uint8_t) * 3; + break; + case AUDIO_FORMAT_PCM_16_BIT: + size = sizeof(int16_t); + break; + case AUDIO_FORMAT_PCM_8_BIT: + size = sizeof(uint8_t); + break; + case AUDIO_FORMAT_PCM_FLOAT: + size = sizeof(float); + break; + default: + break; + } + return size; +} + +/* Not part of public API */ +static inline audio_channel_mask_t audio_channel_mask_from_representation_and_bits( + audio_channel_representation_t representation, uint32_t bits) { + return (audio_channel_mask_t)((representation << AUDIO_CHANNEL_COUNT_MAX) | bits); +} + +/* Derive an output channel mask for position assignment from a channel count. + * This is to be used when the content channel mask is unknown. The 1, 2, 4, 5, 6, 7 and 8 channel + * cases are mapped to the standard game/home-theater layouts, but note that 4 is mapped to quad, + * and not stereo + FC + mono surround. A channel count of 3 is arbitrarily mapped to stereo + FC + * for continuity with stereo. + * Returns the matching channel mask, + * or AUDIO_CHANNEL_NONE if the channel count is zero, + * or AUDIO_CHANNEL_INVALID if the channel count exceeds that of the + * configurations for which a default output channel mask is defined. + */ +static inline audio_channel_mask_t audio_channel_out_mask_from_count(uint32_t channel_count) { + uint32_t bits; + switch (channel_count) { + case 0: + return AUDIO_CHANNEL_NONE; + case 1: + bits = AUDIO_CHANNEL_OUT_MONO; + break; + case 2: + bits = AUDIO_CHANNEL_OUT_STEREO; + break; + case 3: + bits = AUDIO_CHANNEL_OUT_STEREO | AUDIO_CHANNEL_OUT_FRONT_CENTER; + break; + case 4: // 4.0 + bits = AUDIO_CHANNEL_OUT_QUAD; + break; + case 5: // 5.0 + bits = AUDIO_CHANNEL_OUT_QUAD | AUDIO_CHANNEL_OUT_FRONT_CENTER; + break; + case 6: // 5.1 + bits = AUDIO_CHANNEL_OUT_5POINT1; + break; + case 7: // 6.1 + bits = AUDIO_CHANNEL_OUT_5POINT1 | AUDIO_CHANNEL_OUT_BACK_CENTER; + break; + case 8: + bits = AUDIO_CHANNEL_OUT_7POINT1; + break; + // IDEA: FCC_8 + default: + return AUDIO_CHANNEL_INVALID; + } + return audio_channel_mask_from_representation_and_bits( + AUDIO_CHANNEL_REPRESENTATION_POSITION, bits); +} diff --git a/cocos/audio/android/cutils/bitops.h b/cocos/audio/android/cutils/bitops.h new file mode 100644 index 0000000..215872f --- /dev/null +++ b/cocos/audio/android/cutils/bitops.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COCOS_CUTILS_BITOPS_H +#define COCOS_CUTILS_BITOPS_H + +#include +#include +#include + +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +static inline int popcount(unsigned int x) { + return __builtin_popcount(x); +} + +static inline int popcountl(unsigned long x) { + return __builtin_popcountl(x); +} + +static inline int popcountll(unsigned long long x) { + return __builtin_popcountll(x); +} + +#ifdef __cplusplus +} +#endif + +#endif /* COCOS_CUTILS_BITOPS_H */ diff --git a/cocos/audio/android/cutils/log.h b/cocos/audio/android/cutils/log.h new file mode 100644 index 0000000..22eacb3 --- /dev/null +++ b/cocos/audio/android/cutils/log.h @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2005-2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// C/C++ logging functions. See the logging documentation for API details. +// +// We'd like these to be available from C code (in case we import some from +// somewhere), so this has a C interface. +// +// The output will be correct when the log file is shared between multiple +// threads and/or multiple processes so long as the operating system +// supports O_APPEND. These calls have mutex-protected data structures +// and so are NOT reentrant. Do not use LOG in a signal handler. +// +#ifndef COCOS_CUTILS_LOG_H +#define COCOS_CUTILS_LOG_H + +#include +#include +#include +#include +#include +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY +// TODO(qgh):May be implemented in later versions +// #include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// --------------------------------------------------------------------- +/* + * Normally we strip ALOGV (VERBOSE messages) from release builds. + * You can modify this (for example with "#define LOG_NDEBUG 0" + * at the top of your source file) to change that behavior. + */ +#ifndef LOG_NDEBUG + #if defined(CC_DEBUG) && CC_DEBUG > 0 + #define LOG_NDEBUG 0 + #else + #define LOG_NDEBUG 1 + #endif +#endif + +/* + * This is the local tag used for the following simplified + * logging macros. You can change this preprocessor definition + * before using the other macros to change the tag. + */ +#ifndef LOG_TAG + #define LOG_TAG NULL +#endif + +// --------------------------------------------------------------------- + +#ifndef __predict_false + #define __predict_false(exp) __builtin_expect((exp) != 0, 0) +#endif + +/* + * -DLINT_RLOG in sources that you want to enforce that all logging + * goes to the radio log buffer. If any logging goes to any of the other + * log buffers, there will be a compile or link error to highlight the + * problem. This is not a replacement for a full audit of the code since + * this only catches compiled code, not ifdef'd debug code. Options to + * defining this, either temporarily to do a spot check, or permanently + * to enforce, in all the communications trees; We have hopes to ensure + * that by supplying just the radio log buffer that the communications + * teams will have their one-stop shop for triaging issues. + */ +#ifndef LINT_RLOG + + /* + * Simplified macro to send a verbose log message using the current LOG_TAG. + */ + #ifndef ALOGV + #define __ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) + #if LOG_NDEBUG + #define ALOGV(...) \ + do { \ + if (0) { \ + __ALOGV(__VA_ARGS__); \ + } \ + } while (0) + #else + #define ALOGV(...) __ALOGV(__VA_ARGS__) + #endif + #endif + + #ifndef ALOGV_IF + #if LOG_NDEBUG + #define ALOGV_IF(cond, ...) ((void)0) + #else + #define ALOGV_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif + #endif + + /* + * Simplified macro to send a debug log message using the current LOG_TAG. + */ + #ifndef ALOGD + #define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) + #endif + + #ifndef ALOGD_IF + #define ALOGD_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif + + /* + * Simplified macro to send an info log message using the current LOG_TAG. + */ + #ifndef ALOGI + #define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) + #endif + + #ifndef ALOGI_IF + #define ALOGI_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif + + /* + * Simplified macro to send a warning log message using the current LOG_TAG. + */ + #ifndef ALOGW + #define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) + #endif + + #ifndef ALOGW_IF + #define ALOGW_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif + + /* + * Simplified macro to send an error log message using the current LOG_TAG. + */ + #ifndef ALOGE + #define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) + #endif + + #ifndef ALOGE_IF + #define ALOGE_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif + + // --------------------------------------------------------------------- + + /* + * Conditional based on whether the current LOG_TAG is enabled at + * verbose priority. + */ + #ifndef IF_ALOGV + #if LOG_NDEBUG + #define IF_ALOGV() if (false) + #else + #define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG) + #endif + #endif + + /* + * Conditional based on whether the current LOG_TAG is enabled at + * debug priority. + */ + #ifndef IF_ALOGD + #define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG) + #endif + + /* + * Conditional based on whether the current LOG_TAG is enabled at + * info priority. + */ + #ifndef IF_ALOGI + #define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG) + #endif + + /* + * Conditional based on whether the current LOG_TAG is enabled at + * warn priority. + */ + #ifndef IF_ALOGW + #define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG) + #endif + + /* + * Conditional based on whether the current LOG_TAG is enabled at + * error priority. + */ + #ifndef IF_ALOGE + #define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG) + #endif + + // --------------------------------------------------------------------- + + /* + * Simplified macro to send a verbose system log message using the current LOG_TAG. + */ + #ifndef SLOGV + #define __SLOGV(...) \ + ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) + #if LOG_NDEBUG + #define SLOGV(...) \ + do { \ + if (0) { \ + __SLOGV(__VA_ARGS__); \ + } \ + } while (0) + #else + #define SLOGV(...) __SLOGV(__VA_ARGS__) + #endif + #endif + + #ifndef SLOGV_IF + #if LOG_NDEBUG + #define SLOGV_IF(cond, ...) ((void)0) + #else + #define SLOGV_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif + #endif + + /* + * Simplified macro to send a debug system log message using the current LOG_TAG. + */ + #ifndef SLOGD + #define SLOGD(...) \ + ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) + #endif + + #ifndef SLOGD_IF + #define SLOGD_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif + + /* + * Simplified macro to send an info system log message using the current LOG_TAG. + */ + #ifndef SLOGI + #define SLOGI(...) \ + ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) + #endif + + #ifndef SLOGI_IF + #define SLOGI_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif + + /* + * Simplified macro to send a warning system log message using the current LOG_TAG. + */ + #ifndef SLOGW + #define SLOGW(...) \ + ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) + #endif + + #ifndef SLOGW_IF + #define SLOGW_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif + + /* + * Simplified macro to send an error system log message using the current LOG_TAG. + */ + #ifndef SLOGE + #define SLOGE(...) \ + ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) + #endif + + #ifndef SLOGE_IF + #define SLOGE_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif + +#endif /* !LINT_RLOG */ + +// --------------------------------------------------------------------- + +/* + * Simplified macro to send a verbose radio log message using the current LOG_TAG. + */ +#ifndef RLOGV + #define __RLOGV(...) \ + ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) + #if LOG_NDEBUG + #define RLOGV(...) \ + do { \ + if (0) { \ + __RLOGV(__VA_ARGS__); \ + } \ + } while (0) + #else + #define RLOGV(...) __RLOGV(__VA_ARGS__) + #endif +#endif + +#ifndef RLOGV_IF + #if LOG_NDEBUG + #define RLOGV_IF(cond, ...) ((void)0) + #else + #define RLOGV_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ + : (void)0) + #endif +#endif + +/* + * Simplified macro to send a debug radio log message using the current LOG_TAG. + */ +#ifndef RLOGD + #define RLOGD(...) \ + ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef RLOGD_IF + #define RLOGD_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \ + : (void)0) +#endif + +/* + * Simplified macro to send an info radio log message using the current LOG_TAG. + */ +#ifndef RLOGI + #define RLOGI(...) \ + ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef RLOGI_IF + #define RLOGI_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \ + : (void)0) +#endif + +/* + * Simplified macro to send a warning radio log message using the current LOG_TAG. + */ +#ifndef RLOGW + #define RLOGW(...) \ + ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef RLOGW_IF + #define RLOGW_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0) +#endif + +/* + * Simplified macro to send an error radio log message using the current LOG_TAG. + */ +#ifndef RLOGE + #define RLOGE(...) \ + ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef RLOGE_IF + #define RLOGE_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \ + : (void)0) +#endif + +// --------------------------------------------------------------------- + +/* + * Log a fatal error. If the given condition fails, this stops program + * execution like a normal assertion, but also generating the given message. + * It is NOT stripped from release builds. Note that the condition test + * is -inverted- from the normal assert() semantics. + */ +#ifndef LOG_ALWAYS_FATAL_IF +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #define LOG_ALWAYS_FATAL_IF(cond, ...) \ + ((__predict_false(cond)) \ + ? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \ + : (void)0) +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY + #define LOG_ALWAYS_FATAL_IF(cond, ...) +#endif +#endif + +#ifndef LOG_ALWAYS_FATAL +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #define LOG_ALWAYS_FATAL(...) \ + (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__))) +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY + #define LOG_ALWAYS_FATAL(...) +#endif +#endif + +/* + * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that + * are stripped out of release builds. + */ +#if LOG_NDEBUG + + #ifndef LOG_FATAL_IF + #define LOG_FATAL_IF(cond, ...) ((void)0) + #endif + #ifndef LOG_FATAL + #define LOG_FATAL(...) ((void)0) + #endif + +#else + + #ifndef LOG_FATAL_IF + #define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__) + #endif + #ifndef LOG_FATAL + #define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__) + #endif + +#endif + +/* + * Assertion that generates a log message when the assertion fails. + * Stripped out of release builds. Uses the current LOG_TAG. + */ +#ifndef ALOG_ASSERT + #define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__) +//#define ALOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond) +#endif + +// --------------------------------------------------------------------- + +/* + * Basic log message macro. + * + * Example: + * ALOG(LOG_WARN, NULL, "Failed with error %d", errno); + * + * The second argument may be NULL or "" to indicate the "global" tag. + */ +#ifndef ALOG + #define ALOG(priority, tag, ...) \ + LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) +#endif + +/* + * Log macro that allows you to specify a number for the priority. + */ +#ifndef LOG_PRI +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #define LOG_PRI(priority, tag, ...) \ + android_printLog(priority, tag, __VA_ARGS__) +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY + #define LOG_PRI(priority, tag, ...) ((void)0) +#endif +#endif + +/* + * Log macro that allows you to pass in a varargs ("args" is a va_list). + */ +#ifndef LOG_PRI_VA + #define LOG_PRI_VA(priority, tag, fmt, args) \ + android_vprintLog(priority, NULL, tag, fmt, args) +#endif + +/* + * Conditional given a desired logging priority and tag. + */ +#ifndef IF_ALOG + #define IF_ALOG(priority, tag) \ + if (android_testLog(ANDROID_##priority, tag)) +#endif + +// --------------------------------------------------------------------- + +/* + * =========================================================================== + * + * The stuff in the rest of this file should not be used directly. + */ + +#define android_printLog(prio, tag, ...) \ + __android_log_print(prio, tag, __VA_ARGS__) + +#define android_vprintLog(prio, cond, tag, ...) \ + __android_log_vprint(prio, tag, __VA_ARGS__) + +/* XXX Macros to work around syntax errors in places where format string + * arg is not passed to ALOG_ASSERT, LOG_ALWAYS_FATAL or LOG_ALWAYS_FATAL_IF + * (happens only in debug builds). + */ + +/* Returns 2nd arg. Used to substitute default value if caller's vararg list + * is empty. + */ +#define __android_second(dummy, second, ...) second + +/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise + * returns nothing. + */ +#define __android_rest(first, ...) , ##__VA_ARGS__ + +#define android_printAssert(cond, tag, ...) \ + __android_log_assert(cond, tag, \ + __android_second(0, ##__VA_ARGS__, NULL) __android_rest(__VA_ARGS__)) + +#define android_writeLog(prio, tag, text) \ + __android_log_write(prio, tag, text) + +#define android_bWriteLog(tag, payload, len) \ + __android_log_bwrite(tag, payload, len) +#define android_btWriteLog(tag, type, payload, len) \ + __android_log_btwrite(tag, type, payload, len) + +#define android_errorWriteLog(tag, subTag) \ + __android_log_error_write(tag, subTag, -1, NULL, 0) + +#define android_errorWriteWithInfoLog(tag, subTag, uid, data, dataLen) \ + __android_log_error_write(tag, subTag, uid, data, dataLen) + +/* + * IF_ALOG uses android_testLog, but IF_ALOG can be overridden. + * android_testLog will remain constant in its purpose as a wrapper + * for Android logging filter policy, and can be subject to + * change. It can be reused by the developers that override + * IF_ALOG as a convenient means to reimplement their policy + * over Android. + */ +#if LOG_NDEBUG /* Production */ + #define android_testLog(prio, tag) \ + (__android_log_is_loggable(prio, tag, ANDROID_LOG_DEBUG) != 0) +#else + #define android_testLog(prio, tag) \ + (__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE) != 0) +#endif + +/* + * Use the per-tag properties "log.tag." to generate a runtime + * result of non-zero to expose a log. prio is ANDROID_LOG_VERBOSE to + * ANDROID_LOG_FATAL. default_prio if no property. Undefined behavior if + * any other value. + */ +int __android_log_is_loggable(int prio, const char *tag, int default_prio); + +int __android_log_security(); /* Device Owner is present */ + +int __android_log_error_write(int tag, const char *subTag, int32_t uid, const char *data, + uint32_t dataLen); + +/* + * Send a simple string to the log. + */ +int __android_log_buf_write(int bufID, int prio, const char *tag, const char *text); +int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...) +#if defined(__GNUC__) + __attribute__((__format__(printf, 4, 5))) +#endif + ; + +#ifdef __cplusplus +} +#endif + +#endif /* COCOS_CUTILS_LOG_H */ diff --git a/cocos/audio/android/mp3reader.cpp b/cocos/audio/android/mp3reader.cpp new file mode 100644 index 0000000..d7b75ba --- /dev/null +++ b/cocos/audio/android/mp3reader.cpp @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define LOG_TAG "mp3reader" + +#include +#include +#include +#include // Resolves that memset, memcpy aren't found while APP_PLATFORM >= 22 on Android +#include +#include "audio/android/cutils/log.h" + +#include "audio/android/mp3reader.h" +#include "pvmp3decoder_api.h" + +static uint32_t U32_AT(const uint8_t *ptr) { + return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; +} + +static bool parseHeader( + uint32_t header, size_t *frame_size, + uint32_t *out_sampling_rate = NULL, uint32_t *out_channels = NULL, + uint32_t *out_bitrate = NULL, uint32_t *out_num_samples = NULL) { + *frame_size = 0; + + if (out_sampling_rate) { + *out_sampling_rate = 0; + } + + if (out_channels) { + *out_channels = 0; + } + + if (out_bitrate) { + *out_bitrate = 0; + } + + if (out_num_samples) { + *out_num_samples = 1152; + } + + if ((header & 0xffe00000) != 0xffe00000) { + return false; + } + + unsigned version = (header >> 19) & 3; + + if (version == 0x01) { + return false; + } + + unsigned layer = (header >> 17) & 3; + + if (layer == 0x00) { + return false; + } + + unsigned bitrate_index = (header >> 12) & 0x0f; + + if (bitrate_index == 0 || bitrate_index == 0x0f) { + // Disallow "free" bitrate. + return false; + } + + unsigned sampling_rate_index = (header >> 10) & 3; + + if (sampling_rate_index == 3) { + return false; + } + + static const int kSamplingRateV1[] = {44100, 48000, 32000}; + int sampling_rate = kSamplingRateV1[sampling_rate_index]; + if (version == 2 /* V2 */) { + sampling_rate /= 2; + } else if (version == 0 /* V2.5 */) { + sampling_rate /= 4; + } + + unsigned padding = (header >> 9) & 1; + + if (layer == 3) { + // layer I + + static const int kBitrateV1[] = { + 32, 64, 96, 128, 160, 192, 224, 256, + 288, 320, 352, 384, 416, 448}; + + static const int kBitrateV2[] = { + 32, 48, 56, 64, 80, 96, 112, 128, + 144, 160, 176, 192, 224, 256}; + + int bitrate = + (version == 3 /* V1 */) + ? kBitrateV1[bitrate_index - 1] + : kBitrateV2[bitrate_index - 1]; + + if (out_bitrate) { + *out_bitrate = bitrate; + } + + *frame_size = (12000 * bitrate / sampling_rate + padding) * 4; + + if (out_num_samples) { + *out_num_samples = 384; + } + } else { + // layer II or III + + static const int kBitrateV1L2[] = { + 32, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 384}; + + static const int kBitrateV1L3[] = { + 32, 40, 48, 56, 64, 80, 96, 112, + 128, 160, 192, 224, 256, 320}; + + static const int kBitrateV2[] = { + 8, 16, 24, 32, 40, 48, 56, 64, + 80, 96, 112, 128, 144, 160}; + + int bitrate; + if (version == 3 /* V1 */) { + bitrate = (layer == 2 /* L2 */) + ? kBitrateV1L2[bitrate_index - 1] + : kBitrateV1L3[bitrate_index - 1]; + + if (out_num_samples) { + *out_num_samples = 1152; + } + } else { + // V2 (or 2.5) + + bitrate = kBitrateV2[bitrate_index - 1]; + if (out_num_samples) { + *out_num_samples = (layer == 1 /* L3 */) ? 576 : 1152; + } + } + + if (out_bitrate) { + *out_bitrate = bitrate; + } + + if (version == 3 /* V1 */) { + *frame_size = 144000 * bitrate / sampling_rate + padding; + } else { + // V2 or V2.5 + size_t tmp = (layer == 1 /* L3 */) ? 72000 : 144000; + *frame_size = tmp * bitrate / sampling_rate + padding; + } + } + + if (out_sampling_rate) { + *out_sampling_rate = sampling_rate; + } + + if (out_channels) { + int channel_mode = (header >> 6) & 3; + + *out_channels = (channel_mode == 3) ? 1 : 2; + } + + return true; +} + +// Mask to extract the version, layer, sampling rate parts of the MP3 header, +// which should be same for all MP3 frames. +static const uint32_t kMask = 0xfffe0c00; + +static ssize_t sourceReadAt(mp3_callbacks *callback, void *source, off64_t offset, void *data, size_t size) { + int retVal = callback->seek(source, offset, SEEK_SET); + if (retVal != EXIT_SUCCESS) { + return 0; + } else { + return callback->read(data, 1, size, source); + } +} + +// Resync to next valid MP3 frame in the file. +static bool resync( + mp3_callbacks *callback, void *source, uint32_t match_header, + off64_t *inout_pos, uint32_t *out_header) { + if (*inout_pos == 0) { + // Skip an optional ID3 header if syncing at the very beginning + // of the datasource. + + for (;;) { + uint8_t id3header[10]; + int retVal = sourceReadAt(callback, source, *inout_pos, id3header, + sizeof(id3header)); + if (retVal < (ssize_t)sizeof(id3header)) { + // If we can't even read these 10 bytes, we might as well bail + // out, even if there _were_ 10 bytes of valid mp3 audio data... + return false; + } + + if (memcmp("ID3", id3header, 3)) { + break; + } + + // Skip the ID3v2 header. + + size_t len = + ((id3header[6] & 0x7f) << 21) | ((id3header[7] & 0x7f) << 14) | ((id3header[8] & 0x7f) << 7) | (id3header[9] & 0x7f); + + len += 10; + + *inout_pos += len; + + ALOGV("skipped ID3 tag, new starting offset is %lld (0x%016llx)", + (long long)*inout_pos, (long long)*inout_pos); + } + } + + off64_t pos = *inout_pos; + bool valid = false; + + const int32_t kMaxReadBytes = 1024; + const int32_t kMaxBytesChecked = 128 * 1024; + uint8_t buf[kMaxReadBytes]; + ssize_t bytesToRead = kMaxReadBytes; + ssize_t totalBytesRead = 0; + ssize_t remainingBytes = 0; + bool reachEOS = false; + uint8_t *tmp = buf; + + do { + if (pos >= (off64_t)(*inout_pos + kMaxBytesChecked)) { + // Don't scan forever. + ALOGV("giving up at offset %lld", (long long)pos); + break; + } + + if (remainingBytes < 4) { + if (reachEOS) { + break; + } else { + memcpy(buf, tmp, remainingBytes); + bytesToRead = kMaxReadBytes - remainingBytes; + + /* + * The next read position should start from the end of + * the last buffer, and thus should include the remaining + * bytes in the buffer. + */ + totalBytesRead = sourceReadAt(callback, source, pos + remainingBytes, + buf + remainingBytes, bytesToRead); + + if (totalBytesRead <= 0) { + break; + } + reachEOS = (totalBytesRead != bytesToRead); + remainingBytes += totalBytesRead; + tmp = buf; + continue; + } + } + + uint32_t header = U32_AT(tmp); + + if (match_header != 0 && (header & kMask) != (match_header & kMask)) { + ++pos; + ++tmp; + --remainingBytes; + continue; + } + + size_t frame_size; + uint32_t sample_rate, num_channels, bitrate; + if (!parseHeader( + header, &frame_size, + &sample_rate, &num_channels, &bitrate)) { + ++pos; + ++tmp; + --remainingBytes; + continue; + } + + // ALOGV("found possible 1st frame at %lld (header = 0x%08x)", (long long)pos, header); + // We found what looks like a valid frame, + // now find its successors. + + off64_t test_pos = pos + frame_size; + + valid = true; + const int FRAME_MATCH_REQUIRED = 3; + for (int j = 0; j < FRAME_MATCH_REQUIRED; ++j) { + uint8_t tmp[4]; + ssize_t retval = sourceReadAt(callback, source, test_pos, tmp, sizeof(tmp)); + if (retval < (ssize_t)sizeof(tmp)) { + valid = false; + break; + } + + uint32_t test_header = U32_AT(tmp); + + ALOGV("subsequent header is %08x", test_header); + + if ((test_header & kMask) != (header & kMask)) { + valid = false; + break; + } + + size_t test_frame_size; + if (!parseHeader(test_header, &test_frame_size)) { + valid = false; + break; + } + + ALOGV("found subsequent frame #%d at %lld", j + 2, (long long)test_pos); + test_pos += test_frame_size; + } + + if (valid) { + *inout_pos = pos; + + if (out_header != NULL) { + *out_header = header; + } + } else { + ALOGV("no dice, no valid sequence of frames found."); + } + + ++pos; + ++tmp; + --remainingBytes; + } while (!valid); + + return valid; +} + +Mp3Reader::Mp3Reader() : mSource(NULL), mCallback(NULL) { +} + +// Initialize the MP3 reader. +bool Mp3Reader::init(mp3_callbacks *callback, void *source) { + mSource = source; + mCallback = callback; + // Open the file. + // mFp = fopen(file, "rb"); + // if (mFp == NULL) return false; + + // Sync to the first valid frame. + off64_t pos = 0; + uint32_t header; + bool success = resync(callback, source, 0 /*match_header*/, &pos, &header); + if (!success) { + ALOGE("%s, resync failed", __FUNCTION__); + return false; + } + + mCurrentPos = pos; + mFixedHeader = header; + + size_t frame_size; + return parseHeader(header, &frame_size, &mSampleRate, + &mNumChannels, &mBitrate); +} + +// Get the next valid MP3 frame. +bool Mp3Reader::getFrame(void *buffer, uint32_t *size) { + size_t frame_size; + uint32_t bitrate; + uint32_t num_samples; + uint32_t sample_rate; + for (;;) { + ssize_t n = sourceReadAt(mCallback, mSource, mCurrentPos, buffer, 4); + if (n < 4) { + return false; + } + + uint32_t header = U32_AT((const uint8_t *)buffer); + + if ((header & kMask) == (mFixedHeader & kMask) && parseHeader( + header, &frame_size, &sample_rate, NULL /*out_channels*/, + &bitrate, &num_samples)) { + break; + } + + // Lost sync. + off64_t pos = mCurrentPos; + if (!resync(mCallback, mSource, mFixedHeader, &pos, NULL /*out_header*/)) { + // Unable to resync. Signalling end of stream. + return false; + } + + mCurrentPos = pos; + + // Try again with the new position. + } + ssize_t n = sourceReadAt(mCallback, mSource, mCurrentPos, buffer, frame_size); + if (n < (ssize_t)frame_size) { + return false; + } + + *size = frame_size; + mCurrentPos += frame_size; + return true; +} + +// Close the MP3 reader. +void Mp3Reader::close() { + assert(mCallback != NULL); + mCallback->close(mSource); +} + +Mp3Reader::~Mp3Reader() { +} + +enum { + kInputBufferSize = 10 * 1024, + kOutputBufferSize = 4608 * 2, +}; + +int decodeMP3(mp3_callbacks *cb, void *source, std::vector &pcmBuffer, int *numChannels, int *sampleRate, int *numFrames) { + // Initialize the config. + tPVMP3DecoderExternal config; + config.equalizerType = flat; + config.crcEnabled = false; + + // Allocate the decoder memory. + uint32_t memRequirements = pvmp3_decoderMemRequirements(); + void *decoderBuf = malloc(memRequirements); + assert(decoderBuf != NULL); + + // Initialize the decoder. + pvmp3_InitDecoder(&config, decoderBuf); + + // Open the input file. + Mp3Reader mp3Reader; + bool success = mp3Reader.init(cb, source); + if (!success) { + ALOGE("mp3Reader.init: Encountered error reading\n"); + free(decoderBuf); + return EXIT_FAILURE; + } + + // Open the output file. + // SF_INFO sfInfo; + // memset(&sfInfo, 0, sizeof(SF_INFO)); + // sfInfo.channels = mp3Reader.getNumChannels(); + // sfInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + // sfInfo.samplerate = mp3Reader.getSampleRate(); + // SNDFILE *handle = sf_open(argv[2], SFM_WRITE, &sfInfo); + // if (handle == NULL) { + // ALOGE("Encountered error writing %s\n", argv[2]); + // mp3Reader.close(); + // free(decoderBuf); + // return EXIT_FAILURE; + // } + + // Allocate input buffer. + uint8_t *inputBuf = static_cast(malloc(kInputBufferSize)); + assert(inputBuf != NULL); + + // Allocate output buffer. + int16_t *outputBuf = static_cast(malloc(kOutputBufferSize)); + assert(outputBuf != NULL); + + // Decode loop. + int retVal = EXIT_SUCCESS; + while (1) { + // Read input from the file. + uint32_t bytesRead; + bool success = mp3Reader.getFrame(inputBuf, &bytesRead); + if (!success) break; + + *numChannels = mp3Reader.getNumChannels(); + *sampleRate = mp3Reader.getSampleRate(); + + // Set the input config. + config.inputBufferCurrentLength = bytesRead; + config.inputBufferMaxLength = 0; + config.inputBufferUsedLength = 0; + config.pInputBuffer = inputBuf; + config.pOutputBuffer = outputBuf; + config.outputFrameSize = kOutputBufferSize / sizeof(int16_t); + + ERROR_CODE decoderErr; + decoderErr = pvmp3_framedecoder(&config, decoderBuf); + if (decoderErr != NO_DECODING_ERROR) { + ALOGE("Decoder encountered error=%d", decoderErr); + retVal = EXIT_FAILURE; + break; + } + + pcmBuffer.insert(pcmBuffer.end(), (char *)outputBuf, ((char *)outputBuf) + config.outputFrameSize * 2); + *numFrames += config.outputFrameSize / mp3Reader.getNumChannels(); + } + + // Close input reader and output writer. + mp3Reader.close(); + // sf_close(handle); + + // Free allocated memory. + free(inputBuf); + free(outputBuf); + free(decoderBuf); + + return retVal; +} diff --git a/cocos/audio/android/mp3reader.h b/cocos/audio/android/mp3reader.h new file mode 100644 index 0000000..d3985df --- /dev/null +++ b/cocos/audio/android/mp3reader.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MP3READER_H_ +#define MP3READER_H_ + +typedef struct { + size_t (*read)(void *ptr, size_t size, size_t nmemb, void *datasource); + int (*seek)(void *datasource, int64_t offset, int whence); + int (*close)(void *datasource); + long (*tell)(void *datasource); +} mp3_callbacks; + +class Mp3Reader { +public: + Mp3Reader(); + bool init(mp3_callbacks *callback, void *source); + bool getFrame(void *buffer, uint32_t *size); + uint32_t getSampleRate() { return mSampleRate; } + uint32_t getNumChannels() { return mNumChannels; } + void close(); + ~Mp3Reader(); + +private: + void *mSource; + mp3_callbacks *mCallback; + uint32_t mFixedHeader; + off64_t mCurrentPos; + uint32_t mSampleRate; + uint32_t mNumChannels; + uint32_t mBitrate; +}; + +int decodeMP3(mp3_callbacks *cb, void *source, std::vector &pcmBuffer, int *numChannels, int *sampleRate, int *numFrames); + +#endif /* MP3READER_H_ */ diff --git a/cocos/audio/android/utils/Compat.h b/cocos/audio/android/utils/Compat.h new file mode 100644 index 0000000..6ccae6a --- /dev/null +++ b/cocos/audio/android/utils/Compat.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COCOS_LIB_UTILS_COMPAT_H +#define COCOS_LIB_UTILS_COMPAT_H + + +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include +#include +#include +#elif CC_PLATFORM == CC_PLATFORM_WINDOWS + +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY +#include +#include +#endif + +#if defined(__APPLE__) + +/* Mac OS has always had a 64-bit off_t, so it doesn't have off64_t. */ + +typedef off_t off64_t; + +static inline off64_t lseek64(int fd, off64_t offset, int whence) { + return lseek(fd, offset, whence); +} + +static inline ssize_t pread64(int fd, void *buf, size_t nbytes, off64_t offset) { + return pread(fd, buf, nbytes, offset); +} + +static inline ssize_t pwrite64(int fd, const void *buf, size_t nbytes, off64_t offset) { + return pwrite(fd, buf, nbytes, offset); +} + +#endif /* __APPLE__ */ + +#if defined(_WIN32) + #define O_CLOEXEC O_NOINHERIT + #define O_NOFOLLOW 0 + #define DEFFILEMODE 0666 +#endif /* _WIN32 */ + +#if defined(_WIN32) + #define ZD "%ld" + #define ZD_TYPE long +#else + #define ZD "%zd" + #define ZD_TYPE ssize_t +#endif + +/* + * Needed for cases where something should be constexpr if possible, but not + * being constexpr is fine if in pre-C++11 code (such as a const static float + * member variable). + */ +#if __cplusplus >= 201103L + #define CONSTEXPR constexpr +#else + #define CONSTEXPR +#endif + +/* + * TEMP_FAILURE_RETRY is defined by some, but not all, versions of + * . (Alas, it is not as standard as we'd hoped!) So, if it's + * not already defined, then define it here. + */ +#ifndef TEMP_FAILURE_RETRY + /* Used to retry syscalls that can return EINTR. */ + #define TEMP_FAILURE_RETRY(exp) ({ \ + typeof (exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; }) +#endif + +#if defined(_WIN32) + #define OS_PATH_SEPARATOR '\\' +#else + #define OS_PATH_SEPARATOR '/' +#endif + +#if CC_PLATFORM == CC_PLATFORM_ANDROID +typedef SLAndroidSimpleBufferQueueItf CCSLBufferQueueItf; +#define CC_SL_IDD_BUFFER_QUEUE SL_IID_ANDROIDSIMPLEBUFFERQUEUE +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY +typedef SLOHBufferQueueItf CCSLBufferQueueItf; +#define CC_SL_IDD_BUFFER_QUEUE SL_IID_OH_BUFFERQUEUE +#define __unused +#endif + +#endif /* COCOS_LIB_UTILS_COMPAT_H */ diff --git a/cocos/audio/android/utils/Errors.h b/cocos/audio/android/utils/Errors.h new file mode 100644 index 0000000..5b61b62 --- /dev/null +++ b/cocos/audio/android/utils/Errors.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COCOS_ERRORS_H +#define COCOS_ERRORS_H + +#include +#include + +namespace cc { + +// use this type to return error codes +#ifdef _WIN32 +typedef int status_t; +#else +typedef int32_t status_t; +#endif + +/* the MS C runtime lacks a few error codes */ + +/* + * Error codes. + * All error codes are negative values. + */ + +// Win32 #defines NO_ERROR as well. It has the same value, so there's no +// real conflict, though it's a bit awkward. +#ifdef _WIN32 + #undef NO_ERROR +#endif + +enum { + OK = 0, // Everything's swell. + NO_ERROR = 0, // No errors. + + UNKNOWN_ERROR = (-2147483647 - 1), // INT32_MIN value + + NO_MEMORY = -ENOMEM, + INVALID_OPERATION = -ENOSYS, + BAD_VALUE = -EINVAL, + BAD_TYPE = (UNKNOWN_ERROR + 1), + NAME_NOT_FOUND = -ENOENT, + PERMISSION_DENIED = -EPERM, + NO_INIT = -ENODEV, + ALREADY_EXISTS = -EEXIST, + DEAD_OBJECT = -EPIPE, + FAILED_TRANSACTION = (UNKNOWN_ERROR + 2), +#if !defined(_WIN32) + BAD_INDEX = -EOVERFLOW, + NOT_ENOUGH_DATA = -ENODATA, + WOULD_BLOCK = -EWOULDBLOCK, + TIMED_OUT = -ETIMEDOUT, + UNKNOWN_TRANSACTION = -EBADMSG, +#else + BAD_INDEX = -E2BIG, + NOT_ENOUGH_DATA = (UNKNOWN_ERROR + 3), + WOULD_BLOCK = (UNKNOWN_ERROR + 4), + TIMED_OUT = (UNKNOWN_ERROR + 5), + UNKNOWN_TRANSACTION = (UNKNOWN_ERROR + 6), +#endif + FDS_NOT_ALLOWED = (UNKNOWN_ERROR + 7), + UNEXPECTED_NULL = (UNKNOWN_ERROR + 8), +}; + +// Restore define; enumeration is in "android" namespace, so the value defined +// there won't work for Win32 code in a different namespace. +#ifdef _WIN32 + #define NO_ERROR 0L +#endif + +} // namespace cc + +// --------------------------------------------------------------------------- + +#endif // COCOS_ERRORS_H diff --git a/cocos/audio/android/utils/Utils.cpp b/cocos/audio/android/utils/Utils.cpp new file mode 100644 index 0000000..367048b --- /dev/null +++ b/cocos/audio/android/utils/Utils.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "audio/android/utils/Utils.h" +#include "platform/BasePlatform.h" + +namespace cc { + +int getSDKVersion() { + return BasePlatform::getPlatform()->getSdkVersion(); +} + +} // end of namespace cc diff --git a/cocos/audio/android/utils/Utils.h b/cocos/audio/android/utils/Utils.h new file mode 100644 index 0000000..bc0c644 --- /dev/null +++ b/cocos/audio/android/utils/Utils.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "base/std/container/string.h" + +namespace cc { +extern int getSDKVersion(); +} diff --git a/cocos/audio/apple/AudioCache.h b/cocos/audio/apple/AudioCache.h new file mode 100644 index 0000000..060426f --- /dev/null +++ b/cocos/audio/apple/AudioCache.h @@ -0,0 +1,110 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#import + +#include +#include "base/std/container/string.h" + +#include "audio/apple/AudioMacros.h" +#include "base/Macros.h" +#include "base/std/container/vector.h" + +#define INVALID_AL_BUFFER_ID 0xFFFFFFFF + +namespace cc { +class AudioEngineImpl; +class AudioPlayer; + +class AudioCache { +public: + enum class State { + INITIAL, + LOADING, + READY, + FAILED + }; + + AudioCache(); + ~AudioCache(); + + void addPlayCallback(const std::function &callback); + + void addLoadCallback(const std::function &callback); + inline bool isStreaming() const { return _isStreaming; } + +protected: + void setSkipReadDataTask(bool isSkip) { _isSkipReadDataTask = isSkip; }; + void readDataTask(unsigned int selfId); + + void invokingPlayCallbacks(); + + void invokingLoadCallbacks(); + + //pcm data related stuff + ALenum _format{-1}; + ALsizei _sampleRate{0}; + float _duration{0}; + uint32_t _totalFrames{0}; + uint32_t _framesRead{0}; + uint32_t _bytesPerFrame{0}; + bool _isStreaming{false}; + uint32_t _channelCount{1}; + /*Cache related stuff; + * Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE + */ + ALuint _alBufferId{INVALID_AL_BUFFER_ID}; + char *_pcmData{nullptr}; + + /*Queue buffer related stuff + * Streaming in openal when sizeInBytes greater then PCMDATA_CACHEMAXSIZE + */ + char *_queBuffers[QUEUEBUFFER_NUM]; + ALsizei _queBufferSize[QUEUEBUFFER_NUM]; + uint32_t _queBufferFrames{0}; + + std::mutex _playCallbackMutex; + ccstd::vector> _playCallbacks; + + // loadCallbacks doesn't need mutex since it's invoked only in Cocos thread. + ccstd::vector> _loadCallbacks; + + std::mutex _readDataTaskMutex; + + State _state{State::INITIAL}; + + std::shared_ptr _isDestroyed; + ccstd::string _fileFullPath; + unsigned int _id; + bool _isLoadingFinished{false}; + bool _isSkipReadDataTask{false}; + + friend class AudioEngineImpl; + friend class AudioPlayer; +}; + +} // namespace cc diff --git a/cocos/audio/apple/AudioCache.mm b/cocos/audio/apple/AudioCache.mm new file mode 100644 index 0000000..9f59441 --- /dev/null +++ b/cocos/audio/apple/AudioCache.mm @@ -0,0 +1,354 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioCache" + +#include "audio/apple/AudioCache.h" + +#import +#import +#include +#include "application/ApplicationManager.h" +#include "base/Scheduler.h" +#include "base/memory/Memory.h" + +#include "audio/apple/AudioDecoder.h" + +#ifdef VERY_VERY_VERBOSE_LOGGING + #define ALOGVV ALOGV +#else + #define ALOGVV(...) \ + do { \ + } while (false) +#endif + +namespace { +unsigned int __idIndex = 0; +} + +#define PCMDATA_CACHEMAXSIZE 1048576 + +@interface NSTimerWrapper : NSObject { + std::function _timeoutCallback; +} + +@end + +@implementation NSTimerWrapper + +- (id)initWithTimeInterval:(double)seconds callback:(const std::function &)cb { + if (self = [super init]) { + _timeoutCallback = cb; + NSTimer *timer = [NSTimer timerWithTimeInterval:seconds target:self selector:@selector(onTimeoutCallback:) userInfo:nil repeats:NO]; + [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; + } + + return self; +} + +- (void)onTimeoutCallback:(NSTimer *)timer { + if (_timeoutCallback != nullptr) { + _timeoutCallback(); + _timeoutCallback = nullptr; + } +} + +- (void)dealloc { + [super dealloc]; +} + +@end + +using namespace cc; + +AudioCache::AudioCache() +: _isDestroyed(std::make_shared(false)), _id(++__idIndex){ + ALOGVV("AudioCache() %p, id=%u", this, _id); + for (int i = 0; i < QUEUEBUFFER_NUM; ++i) { + _queBuffers[i] = nullptr; + _queBufferSize[i] = 0; + } +} + +AudioCache::~AudioCache() { + ALOGVV("~AudioCache() %p, id=%u, begin", this, _id); + *_isDestroyed = true; + while (!_isLoadingFinished) { + if (_isSkipReadDataTask) { + ALOGV("id=%u, Skip read data task, don't continue to wait!", _id); + break; + } + ALOGVV("id=%u, waiting readData thread to finish ...", _id); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + //wait for the 'readDataTask' task to exit + _readDataTaskMutex.lock(); + + if (_state == State::READY) { + if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId)) { + ALOGV("~AudioCache(id=%u), delete buffer: %u", _id, _alBufferId); + alDeleteBuffers(1, &_alBufferId); + _alBufferId = INVALID_AL_BUFFER_ID; + } + } else { + ALOGW("AudioCache (%p), id=%u, buffer isn't ready, state=%d", this, _id, _state); + } + + if (_queBufferFrames > 0) { + for (int index = 0; index < QUEUEBUFFER_NUM; ++index) { + free(_queBuffers[index]); + } + } + ALOGVV("~AudioCache() %p, id=%u, end", this, _id); + _readDataTaskMutex.unlock(); +} + +void AudioCache::readDataTask(unsigned int selfId) { + //Note: It's in sub thread + ALOGVV("readDataTask, cache id=%u", selfId); + + _readDataTaskMutex.lock(); + _state = State::LOADING; + + AudioDecoder decoder; + do { + if (!decoder.open(_fileFullPath.c_str())) + break; + + const uint32_t originalTotalFrames = decoder.getTotalFrames(); + const uint32_t bytesPerFrame = decoder.getBytesPerFrame(); + const uint32_t sampleRate = decoder.getSampleRate(); + _channelCount = decoder.getChannelCount(); + + uint32_t totalFrames = originalTotalFrames; + uint32_t dataSize = totalFrames * bytesPerFrame; + uint32_t remainingFrames = totalFrames; + uint32_t adjustFrames = 0; + + _format = _channelCount > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + _sampleRate = (ALsizei)sampleRate; + _duration = 1.0f * totalFrames / sampleRate; + _totalFrames = totalFrames; + + if (dataSize <= PCMDATA_CACHEMAXSIZE) { + uint32_t framesRead = 0; + const uint32_t framesToReadOnce = std::min(totalFrames, static_cast(sampleRate * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM)); + + BREAK_IF_ERR_LOG(!decoder.seek(totalFrames), "AudioDecoder::seek(%u) error", totalFrames); + + char *tmpBuf = (char *)malloc(framesToReadOnce * bytesPerFrame); + ccstd::vector adjustFrameBuf; + adjustFrameBuf.reserve(framesToReadOnce * bytesPerFrame); + + // Adjust total frames by setting position to the end of frames and try to read more data. + // This is a workaround for https://github.com/cocos2d/cocos2d-x/issues/16938 + do { + framesRead = decoder.read(framesToReadOnce, tmpBuf); + if (framesRead > 0) { + adjustFrames += framesRead; + adjustFrameBuf.insert(adjustFrameBuf.end(), tmpBuf, tmpBuf + framesRead * bytesPerFrame); + } + + } while (framesRead > 0); + + if (adjustFrames > 0) { + ALOGV("Orignal total frames: %u, adjust frames: %u, current total frames: %u", totalFrames, adjustFrames, totalFrames + adjustFrames); + totalFrames += adjustFrames; + _totalFrames = remainingFrames = totalFrames; + } + + // Reset dataSize + dataSize = totalFrames * bytesPerFrame; + + free(tmpBuf); + + // Reset to frame 0 + BREAK_IF_ERR_LOG(!decoder.seek(0), "AudioDecoder::seek(0) failed!"); + + _pcmData = (char *)malloc(dataSize); + memset(_pcmData, 0x00, dataSize); + ALOGV(" id=%u _pcmData alloc: %p", selfId, _pcmData); + + if (adjustFrames > 0) { + memcpy(_pcmData + (dataSize - adjustFrameBuf.size()), adjustFrameBuf.data(), adjustFrameBuf.size()); + } + + if (*_isDestroyed) + break; + + framesRead = decoder.readFixedFrames(std::min(framesToReadOnce, remainingFrames), _pcmData + _framesRead * bytesPerFrame); + _framesRead += framesRead; + remainingFrames -= framesRead; + + if (*_isDestroyed) + break; + + uint32_t frames = 0; + while (!*_isDestroyed && _framesRead < originalTotalFrames) { + frames = std::min(framesToReadOnce, remainingFrames); + if (_framesRead + frames > originalTotalFrames) { + frames = originalTotalFrames - _framesRead; + } + framesRead = decoder.read(frames, _pcmData + _framesRead * bytesPerFrame); + if (framesRead == 0) + break; + _framesRead += framesRead; + remainingFrames -= framesRead; + } + + if (_framesRead < originalTotalFrames) { + memset(_pcmData + _framesRead * bytesPerFrame, 0x00, (totalFrames - _framesRead) * bytesPerFrame); + } + + ALOGV("pcm buffer was loaded successfully, total frames: %u, total read frames: %u, adjust frames: %u, remainingFrames: %u", totalFrames, _framesRead, adjustFrames, remainingFrames); + _framesRead += adjustFrames; + + alGenBuffers(1, &_alBufferId); + auto alError = alGetError(); + if (alError != AL_NO_ERROR) { + ALOGE("%s: attaching audio to buffer fail: %x", __PRETTY_FUNCTION__, alError); + break; + } + ALOGV(" id=%u generated alGenBuffers: %u for _pcmData: %p", selfId, _alBufferId, _pcmData); + ALOGV(" id=%u _pcmData alBufferData: %p", selfId, _pcmData); + alBufferData(_alBufferId, _format, _pcmData, (ALsizei)dataSize, (ALsizei)sampleRate); + _state = State::READY; + invokingPlayCallbacks(); + + } else { + _isStreaming = true; + _queBufferFrames = sampleRate * QUEUEBUFFER_TIME_STEP; + BREAK_IF_ERR_LOG(_queBufferFrames == 0, "_queBufferFrames == 0"); + + const uint32_t queBufferBytes = _queBufferFrames * bytesPerFrame; + + for (int index = 0; index < QUEUEBUFFER_NUM; ++index) { + _queBuffers[index] = (char *)malloc(queBufferBytes); + _queBufferSize[index] = queBufferBytes; + + decoder.readFixedFrames(_queBufferFrames, _queBuffers[index]); + } + + _state = State::READY; + } + + } while (false); + + if (_pcmData != nullptr) { + CC_SAFE_FREE(_pcmData); + } + + decoder.close(); + + //IDEA: Why to invoke play callback first? Should it be after 'load' callback? + invokingPlayCallbacks(); + invokingLoadCallbacks(); + + _isLoadingFinished = true; + if (_state != State::READY) { + _state = State::FAILED; + if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId)) { + ALOGV(" id=%u readDataTask failed, delete buffer: %u", selfId, _alBufferId); + alDeleteBuffers(1, &_alBufferId); + _alBufferId = INVALID_AL_BUFFER_ID; + } + } + + _readDataTaskMutex.unlock(); +} + +void AudioCache::addPlayCallback(const std::function &callback) { + std::lock_guard lk(_playCallbackMutex); + switch (_state) { + case State::INITIAL: + case State::LOADING: + _playCallbacks.push_back(callback); + break; + + case State::READY: + // If state is failure, we still need to invoke the callback + // since the callback will set the 'AudioPlayer::_removeByAudioEngine' flag to true. + case State::FAILED: + callback(); + break; + + default: + ALOGE("Invalid state: %d", _state); + break; + } +} + +void AudioCache::invokingPlayCallbacks() { + std::lock_guard lk(_playCallbackMutex); + + for (auto &&cb : _playCallbacks) { + cb(); + } + + _playCallbacks.clear(); +} + +void AudioCache::addLoadCallback(const std::function &callback) { + switch (_state) { + case State::INITIAL: + case State::LOADING: + _loadCallbacks.push_back(callback); + break; + + case State::READY: + callback(true); + break; + case State::FAILED: + callback(false); + break; + + default: + ALOGE("Invalid state: %d", _state); + break; + } +} + +void AudioCache::invokingLoadCallbacks() { + if (*_isDestroyed) { + ALOGV("AudioCache (%p) was destroyed, don't invoke preload callback ...", this); + return; + } + + auto isDestroyed = _isDestroyed; + auto scheduler = CC_CURRENT_ENGINE()->getScheduler(); + scheduler->performFunctionInCocosThread([&, isDestroyed]() { + if (*isDestroyed) { + ALOGV("invokingLoadCallbacks perform in cocos thread, AudioCache (%p) was destroyed!", this); + return; + } + + for (auto &&cb : _loadCallbacks) { + cb(_state == State::READY); + } + + _loadCallbacks.clear(); + }); +} diff --git a/cocos/audio/apple/AudioDecoder.h b/cocos/audio/apple/AudioDecoder.h new file mode 100644 index 0000000..8e59543 --- /dev/null +++ b/cocos/audio/apple/AudioDecoder.h @@ -0,0 +1,118 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#import +#include + +namespace cc { + +/** + * @brief The class for decoding compressed audio file to PCM buffer. + */ +class AudioDecoder { +public: + static const uint32_t INVALID_FRAME_INDEX = UINT32_MAX; + + AudioDecoder(); + ~AudioDecoder(); + + /** + * @brief Opens an audio file specified by a file path. + * @return true if succeed, otherwise false. + */ + bool open(const char *path); + + /** + * @brief Checks whether decoder has opened file successfully. + * @return true if succeed, otherwise false. + */ + bool isOpened() const; + + /** + * @brief Closes opened audio file. + * @note The method will also be automatically invoked in the destructor. + */ + void close(); + + /** + * @brief Reads audio frames of PCM format. + * @param framesToRead The number of frames excepted to be read. + * @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame. + * @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file. + */ + uint32_t read(uint32_t framesToRead, char *pcmBuf); + + /** + * @brief Reads fixed audio frames of PCM format. + * @param framesToRead The number of frames excepted to be read. + * @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame. + * @return The number of frames actually read, it's probably less than |framesToRead|. Returns 0 means reach the end of file. + * @note The different between |read| and |readFixedFrames| is |readFixedFrames| will do multiple reading operations if |framesToRead| frames + * isn't filled entirely, while |read| just does reading operation once whatever |framesToRead| is or isn't filled entirely. + * If current position reaches the end of frames, the return value may smaller than |framesToRead| and the remaining + * buffer in |pcmBuf| will be set with silence data (0x00). + */ + uint32_t readFixedFrames(uint32_t framesToRead, char *pcmBuf); + + /** + * @brief Sets frame offest to be read. + * @param frameOffset The frame offest to be set. + * @return true if succeed, otherwise false + */ + bool seek(uint32_t frameOffset); + + /** + * @brief Tells the current frame offset. + * @return The current frame offset. + */ + uint32_t tell() const; + + /** Gets total frames of current audio.*/ + uint32_t getTotalFrames() const; + + /** Gets bytes per frame of current audio.*/ + uint32_t getBytesPerFrame() const; + + /** Gets sample rate of current audio.*/ + uint32_t getSampleRate() const; + + /** Gets the channel count of current audio. + * @note Currently we only support 1 or 2 channels. + */ + uint32_t getChannelCount() const; + +private: + bool _isOpened; + ExtAudioFileRef _extRef; + uint32_t _totalFrames; + uint32_t _bytesPerFrame; + uint32_t _sampleRate; + uint32_t _channelCount; + AudioStreamBasicDescription _outputFormat; +}; + +} // namespace cc diff --git a/cocos/audio/apple/AudioDecoder.mm b/cocos/audio/apple/AudioDecoder.mm new file mode 100644 index 0000000..ab46ace --- /dev/null +++ b/cocos/audio/apple/AudioDecoder.mm @@ -0,0 +1,203 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "audio/apple/AudioDecoder.h" +#include "audio/apple/AudioMacros.h" + +#import + +#define LOG_TAG "AudioDecoder" + +namespace cc { + +AudioDecoder::AudioDecoder() +: _isOpened(false), _extRef(nullptr), _totalFrames(0), _bytesPerFrame(0), _sampleRate(0), _channelCount(0) { + memset(&_outputFormat, 0, sizeof(_outputFormat)); +} + +AudioDecoder::~AudioDecoder() { + close(); +} + +bool AudioDecoder::open(const char *path) { + bool ret = false; + CFURLRef fileURL = nil; + do { + BREAK_IF_ERR_LOG(path == nullptr || strlen(path) == 0, "Invalid path!"); + + NSString *fileFullPath = [[NSString alloc] initWithCString:path encoding:NSUTF8StringEncoding]; + fileURL = (CFURLRef)[[NSURL alloc] initFileURLWithPath:fileFullPath]; + [fileFullPath release]; + BREAK_IF_ERR_LOG(fileURL == nil, "Converting path to CFURLRef failed!"); + + OSStatus status = ExtAudioFileOpenURL(fileURL, &_extRef); + BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileOpenURL FAILED, Error = %ld", (long)ret); + + AudioStreamBasicDescription fileFormat; + UInt32 propertySize = sizeof(fileFormat); + + // Get the audio data format + ret = ExtAudioFileGetProperty(_extRef, kExtAudioFileProperty_FileDataFormat, &propertySize, &fileFormat); + BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld", (long)ret); + BREAK_IF_ERR_LOG(fileFormat.mChannelsPerFrame > 2, "Unsupported Format, channel count is greater than stereo!"); + + // Set the client format to 16 bit signed integer (native-endian) data + // Maintain the channel count and sample rate of the original source format + _outputFormat.mSampleRate = fileFormat.mSampleRate; + _outputFormat.mChannelsPerFrame = fileFormat.mChannelsPerFrame; + _outputFormat.mFormatID = kAudioFormatLinearPCM; + _outputFormat.mFramesPerPacket = 1; + _outputFormat.mBitsPerChannel = 16; + _outputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; + _sampleRate = _outputFormat.mSampleRate; + _channelCount = _outputFormat.mChannelsPerFrame; + _bytesPerFrame = 2 * _outputFormat.mChannelsPerFrame; + + _outputFormat.mBytesPerPacket = _bytesPerFrame; + _outputFormat.mBytesPerFrame = _bytesPerFrame; + + ret = ExtAudioFileSetProperty(_extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(_outputFormat), &_outputFormat); + BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileSetProperty FAILED, Error = %ld", (long)ret); + + // Get the total frame count + SInt64 totalFrames = 0; + propertySize = sizeof(totalFrames); + ret = ExtAudioFileGetProperty(_extRef, kExtAudioFileProperty_FileLengthFrames, &propertySize, &totalFrames); + BREAK_IF_ERR_LOG(status != noErr, "ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld", (long)ret); + BREAK_IF_ERR_LOG(totalFrames <= 0, "Total frames is 0, it's an invalid audio file: %s", path); + _totalFrames = static_cast(totalFrames); + _isOpened = true; + + ret = true; + } while (false); + + if (fileURL != nil) + CFRelease(fileURL); + + if (!ret) { + close(); + } + + return ret; +} + +void AudioDecoder::close() { + if (_extRef != nullptr) { + ExtAudioFileDispose(_extRef); + _extRef = nullptr; + + _totalFrames = 0; + _bytesPerFrame = 0; + _sampleRate = 0; + _channelCount = 0; + } +} + +uint32_t AudioDecoder::read(uint32_t framesToRead, char *pcmBuf) { + uint32_t ret = 0; + do { + BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned"); + BREAK_IF_ERR_LOG(framesToRead == INVALID_FRAME_INDEX, "frameToRead is INVALID_FRAME_INDEX"); + BREAK_IF_ERR_LOG(framesToRead == 0, "frameToRead is 0"); + BREAK_IF_ERR_LOG(pcmBuf == nullptr, "pcmBuf is nullptr"); + + AudioBufferList bufferList; + bufferList.mNumberBuffers = 1; + bufferList.mBuffers[0].mDataByteSize = framesToRead * _bytesPerFrame; + bufferList.mBuffers[0].mNumberChannels = _outputFormat.mChannelsPerFrame; + bufferList.mBuffers[0].mData = pcmBuf; + + UInt32 frames = framesToRead; + OSStatus status = ExtAudioFileRead(_extRef, &frames, &bufferList); + BREAK_IF(status != noErr); + ret = frames; + } while (false); + + return ret; +} + +uint32_t AudioDecoder::readFixedFrames(uint32_t framesToRead, char *pcmBuf) { + uint32_t framesRead = 0; + uint32_t framesReadOnce = 0; + do { + framesReadOnce = read(framesToRead - framesRead, pcmBuf + framesRead * _bytesPerFrame); + framesRead += framesReadOnce; + } while (framesReadOnce != 0 && framesRead < framesToRead); + + if (framesRead < framesToRead) { + memset(pcmBuf + framesRead * _bytesPerFrame, 0x00, (framesToRead - framesRead) * _bytesPerFrame); + } + + return framesRead; +} + +bool AudioDecoder::seek(uint32_t frameOffset) { + bool ret = false; + do { + BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned"); + BREAK_IF_ERR_LOG(frameOffset == INVALID_FRAME_INDEX, "frameIndex is INVALID_FRAME_INDEX"); + + OSStatus status = ExtAudioFileSeek(_extRef, frameOffset); + BREAK_IF(status != noErr); + ret = true; + } while (false); + return ret; +} + +uint32_t AudioDecoder::tell() const { + uint32_t ret = INVALID_FRAME_INDEX; + do { + BREAK_IF_ERR_LOG(!isOpened(), "decoder isn't openned"); + SInt64 frameIndex = INVALID_FRAME_INDEX; + OSStatus status = ExtAudioFileTell(_extRef, &frameIndex); + BREAK_IF(status != noErr); + ret = static_cast(frameIndex); + } while (false); + + return ret; +} + +uint32_t AudioDecoder::getTotalFrames() const { + return _totalFrames; +} + +uint32_t AudioDecoder::getBytesPerFrame() const { + return _bytesPerFrame; +} + +uint32_t AudioDecoder::getSampleRate() const { + return _sampleRate; +} + +uint32_t AudioDecoder::getChannelCount() const { + return _channelCount; +} + +bool AudioDecoder::isOpened() const { + return _isOpened; +} + +} // namespace cc diff --git a/cocos/audio/apple/AudioEngine-inl.h b/cocos/audio/apple/AudioEngine-inl.h new file mode 100644 index 0000000..18bba6e --- /dev/null +++ b/cocos/audio/apple/AudioEngine-inl.h @@ -0,0 +1,91 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/apple/AudioCache.h" +#include "audio/apple/AudioPlayer.h" +#include "audio/include/AudioDef.h" +#include "base/RefCounted.h" +#include "base/std/container/list.h" +#include "base/std/container/unordered_map.h" + +namespace cc { +class Scheduler; + +#define MAX_AUDIOINSTANCES 24 + +class AudioEngineImpl : public cc::RefCounted { +public: + AudioEngineImpl(); + ~AudioEngineImpl(); + + bool init(); + int play2d(const ccstd::string &fileFullPath, bool loop, float volume); + void setVolume(int audioID, float volume); + void setLoop(int audioID, bool loop); + bool pause(int audioID); + bool resume(int audioID); + void stop(int audioID); + void stopAll(); + float getDuration(int audioID); + float getDurationFromFile(const ccstd::string &fileFullPath); + float getCurrentTime(int audioID); + bool setCurrentTime(int audioID, float time); + void setFinishCallback(int audioID, const std::function &callback); + + void uncache(const ccstd::string &filePath); + void uncacheAll(); + AudioCache *preload(const ccstd::string &filePath, std::function callback); + void update(float dt); + + PCMHeader getPCMHeader(const char *url); + std::vector getOriginalPCMBuffer(const char *url, uint32_t channelID); + +private: + bool checkAudioIdValid(int audioID); + void play2dImpl(AudioCache *cache, int audioID); + ALuint findValidSource(); + + static ALvoid myAlSourceNotificationCallback(ALuint sid, ALuint notificationID, ALvoid *userData); + + ALuint _alSources[MAX_AUDIOINSTANCES]; + + //source,used + ccstd::list _unusedSourcesPool; + + //filePath,bufferInfo + ccstd::unordered_map _audioCaches; + + //audioID,AudioInfo + ccstd::unordered_map _audioPlayers; + std::mutex _threadMutex; + + bool _lazyInitLoop; + + int _currentAudioID; + std::weak_ptr _scheduler; +}; +} // namespace cc diff --git a/cocos/audio/apple/AudioEngine-inl.mm b/cocos/audio/apple/AudioEngine-inl.mm new file mode 100644 index 0000000..c00ad0b --- /dev/null +++ b/cocos/audio/apple/AudioEngine-inl.mm @@ -0,0 +1,789 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioEngine-inl.mm" +#include "audio/apple/AudioEngine-inl.h" + +#import +#import +#if CC_PLATFORM == CC_PLATFORM_IOS + #import +#endif + +#include "audio/include/AudioEngine.h" +#include "application/ApplicationManager.h" +#include "base/Scheduler.h" +#include "base/Utils.h" +#include "base/memory/Memory.h" +#include "platform/FileUtils.h" +#include "AudioDecoder.h" + +using namespace cc; + +static ALCdevice *s_ALDevice = nullptr; +static ALCcontext *s_ALContext = nullptr; +static AudioEngineImpl *s_instance = nullptr; + +typedef ALvoid (*alSourceNotificationProc)(ALuint sid, ALuint notificationID, ALvoid *userData); +typedef ALenum (*alSourceAddNotificationProcPtr)(ALuint sid, ALuint notificationID, alSourceNotificationProc notifyProc, ALvoid *userData); +static ALenum alSourceAddNotificationExt(ALuint sid, ALuint notificationID, alSourceNotificationProc notifyProc, ALvoid *userData) { + static alSourceAddNotificationProcPtr proc = nullptr; + + if (proc == nullptr) { + proc = (alSourceAddNotificationProcPtr)alcGetProcAddress(nullptr, "alSourceAddNotification"); + } + + if (proc) { + return proc(sid, notificationID, notifyProc, userData); + } + return AL_INVALID_VALUE; +} + +#if CC_PLATFORM == CC_PLATFORM_IOS +@interface AudioEngineSessionHandler : NSObject { +} + +- (id)init; +- (void)handleInterruption:(NSNotification *)notification; + +@end + +@implementation AudioEngineSessionHandler + +void AudioEngineInterruptionListenerCallback(void *user_data, UInt32 interruption_state) { + if (kAudioSessionBeginInterruption == interruption_state) { + alcMakeContextCurrent(nullptr); + } else if (kAudioSessionEndInterruption == interruption_state) { + OSStatus result = AudioSessionSetActive(true); + if (result) NSLog(@"Error setting audio session active! %d\n", static_cast(result)); + + alcMakeContextCurrent(s_ALContext); + } +} + +- (id)init { + if (self = [super init]) { + if ([[[UIDevice currentDevice] systemVersion] intValue] > 5) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:UIApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:UIApplicationWillResignActiveNotification object:nil]; + } + else { + AudioSessionInitialize(NULL, NULL, AudioEngineInterruptionListenerCallback, self); + } + + BOOL success = [[AVAudioSession sharedInstance] + setCategory:AVAudioSessionCategoryPlayback + error:nil]; + if (!success) + ALOGE("Fail to set audio session."); + } + return self; +} + +- (void)handleInterruption:(NSNotification *)notification { + static bool isAudioSessionInterrupted = false; + static bool resumeOnBecomingActive = false; + static bool pauseOnResignActive = false; + + if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) { + NSInteger reason = [[[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] integerValue]; + if (reason == AVAudioSessionInterruptionTypeBegan) { + isAudioSessionInterrupted = true; + alcMakeContextCurrent(nullptr); + + if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { + ALOGD("AVAudioSessionInterruptionTypeBegan, application == UIApplicationStateActive, pauseOnResignActive = true"); + pauseOnResignActive = true; + } + } + + if (reason == AVAudioSessionInterruptionTypeEnded) { + isAudioSessionInterrupted = false; + + NSError *error = nil; + [[AVAudioSession sharedInstance] setActive:YES error:&error]; + alcMakeContextCurrent(s_ALContext); + + if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) { + ALOGD("AVAudioSessionInterruptionTypeEnded, application != UIApplicationStateActive, resumeOnBecomingActive = true"); + resumeOnBecomingActive = true; + } + } + } else if ([notification.name isEqualToString:UIApplicationWillResignActiveNotification]) { + ALOGD("UIApplicationWillResignActiveNotification"); + if (pauseOnResignActive) { + pauseOnResignActive = false; + ALOGD("UIApplicationWillResignActiveNotification, alcMakeContextCurrent(nullptr)"); + alcMakeContextCurrent(nullptr); + } + } else if ([notification.name isEqualToString:UIApplicationDidBecomeActiveNotification]) { + ALOGD("UIApplicationDidBecomeActiveNotification"); + if (resumeOnBecomingActive) { + resumeOnBecomingActive = false; + ALOGD("UIApplicationDidBecomeActiveNotification, alcMakeContextCurrent(s_ALContext)"); + NSError *error = nil; + BOOL success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error]; + if (!success) { + ALOGE("Fail to set audio session."); + return; + } + [[AVAudioSession sharedInstance] setActive:YES error:&error]; + alcMakeContextCurrent(s_ALContext); + } else if (isAudioSessionInterrupted) { + isAudioSessionInterrupted = false; + ALOGD("UIApplicationDidBecomeActiveNotification, alcMakeContextCurrent(s_ALContext)"); + NSError *error = nil; + BOOL success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&error]; + if (!success) { + ALOGE("Fail to set audio session."); + return; + } + [[AVAudioSession sharedInstance] setActive:YES error:&error]; + alcMakeContextCurrent(s_ALContext); + + // ALOGD("Audio session is still interrupted, pause director!"); + //IDEA: Director::getInstance()->pause(); + } + } +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; + + [super dealloc]; +} +@end + +static id s_AudioEngineSessionHandler = nullptr; +#endif + +ALvoid AudioEngineImpl::myAlSourceNotificationCallback(ALuint sid, ALuint notificationID, ALvoid *userData) { + // Currently, we only care about AL_BUFFERS_PROCESSED event + if (notificationID != AL_BUFFERS_PROCESSED) + return; + + AudioPlayer *player = nullptr; + s_instance->_threadMutex.lock(); + for (const auto &e : s_instance->_audioPlayers) { + player = e.second; + if (player->_alSource == sid && player->_streamingSource) { + player->wakeupRotateThread(); + } + } + s_instance->_threadMutex.unlock(); +} + +AudioEngineImpl::AudioEngineImpl() +: _lazyInitLoop(true), _currentAudioID(0) { + s_instance = this; +} + +AudioEngineImpl::~AudioEngineImpl() { + if (auto sche = _scheduler.lock()) { + sche->unschedule("AudioEngine", this); + } + + if (s_ALContext) { + alDeleteSources(MAX_AUDIOINSTANCES, _alSources); + + _audioCaches.clear(); + + alcMakeContextCurrent(nullptr); + alcDestroyContext(s_ALContext); + } + if (s_ALDevice) { + alcCloseDevice(s_ALDevice); + } + +#if CC_PLATFORM == CC_PLATFORM_IOS + [s_AudioEngineSessionHandler release]; +#endif + s_instance = nullptr; +} + +bool AudioEngineImpl::init() { + bool ret = false; + do { +#if CC_PLATFORM == CC_PLATFORM_IOS + s_AudioEngineSessionHandler = [[AudioEngineSessionHandler alloc] init]; +#endif + + s_ALDevice = alcOpenDevice(nullptr); + + if (s_ALDevice) { + s_ALContext = alcCreateContext(s_ALDevice, nullptr); + alcMakeContextCurrent(s_ALContext); + + alGenSources(MAX_AUDIOINSTANCES, _alSources); + auto alError = alGetError(); + if (alError != AL_NO_ERROR) { + ALOGE("%s:generating sources failed! error = %x", __PRETTY_FUNCTION__, alError); + break; + } + + for (int i = 0; i < MAX_AUDIOINSTANCES; ++i) { + _unusedSourcesPool.push_back(_alSources[i]); + alSourceAddNotificationExt(_alSources[i], AL_BUFFERS_PROCESSED, myAlSourceNotificationCallback, nullptr); + } + + // fixed #16170: Random crash in alGenBuffers(AudioCache::readDataTask) at startup + // Please note that, as we know the OpenAL operation is atomic (threadsafe), + // 'alGenBuffers' may be invoked by different threads. But in current implementation of 'alGenBuffers', + // When the first time it's invoked, application may crash!!! + // Why? OpenAL is opensource by Apple and could be found at + // http://opensource.apple.com/source/OpenAL/OpenAL-48.7/Source/OpenAL/oalImp.cpp . + /* + + void InitializeBufferMap() + { + if (gOALBufferMap == NULL) // Position 1 + { + gOALBufferMap = ccnew OALBufferMap (); // Position 2 + + // Position Gap + + gBufferMapLock = ccnew CAGuard("OAL:BufferMapLock"); // Position 3 + gDeadOALBufferMap = ccnew OALBufferMap (); + + OALBuffer *newBuffer = ccnew OALBuffer (AL_NONE); + gOALBufferMap->Add(AL_NONE, &newBuffer); + } + } + + AL_API ALvoid AL_APIENTRY alGenBuffers(ALsizei n, ALuint *bids) + { + ... + + try { + if (n < 0) + throw ((OSStatus) AL_INVALID_VALUE); + + InitializeBufferMap(); + if (gOALBufferMap == NULL) + throw ((OSStatus) AL_INVALID_OPERATION); + + CAGuard::Locker locked(*gBufferMapLock); // Position 4 + ... + ... + } + + */ + // 'gBufferMapLock' will be initialized in the 'InitializeBufferMap' function, + // that's the problem. It means that 'InitializeBufferMap' may be invoked in different threads. + // It will be very dangerous in multi-threads environment. + // Imagine there're two threads (Thread A, Thread B), they call 'alGenBuffers' simultaneously. + // While A goto 'Position Gap', 'gOALBufferMap' was assigned, then B goto 'Position 1' and find + // that 'gOALBufferMap' isn't NULL, B just jump over 'InitialBufferMap' and goto 'Position 4'. + // Meanwhile, A is still at 'Position Gap', B will crash at '*gBufferMapLock' since 'gBufferMapLock' + // is still a null pointer. Oops, how could Apple implemented this method in this fucking way? + + // Workaround is do an unused invocation in the mainthread right after OpenAL is initialized successfully + // as bellow. + // ================ Workaround begin ================ // + + ALuint unusedAlBufferId = 0; + alGenBuffers(1, &unusedAlBufferId); + alDeleteBuffers(1, &unusedAlBufferId); + + // ================ Workaround end ================ // + + _scheduler = CC_CURRENT_ENGINE()->getScheduler(); + ret = true; + ALOGI("OpenAL was initialized successfully!"); + } + } while (false); + + return ret; +} + +AudioCache *AudioEngineImpl::preload(const ccstd::string &filePath, std::function callback) { + AudioCache *audioCache = nullptr; + + auto it = _audioCaches.find(filePath); + if (it == _audioCaches.end()) { + audioCache = &_audioCaches[filePath]; + audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath); + unsigned int cacheId = audioCache->_id; + auto isCacheDestroyed = audioCache->_isDestroyed; + AudioEngine::addTask([audioCache, cacheId, isCacheDestroyed]() { + if (*isCacheDestroyed) { + ALOGV("AudioCache (id=%u) was destroyed, no need to launch readDataTask.", cacheId); + audioCache->setSkipReadDataTask(true); + return; + } + audioCache->readDataTask(cacheId); + }); + } else { + audioCache = &it->second; + } + + if (audioCache && callback) { + audioCache->addLoadCallback(callback); + } + return audioCache; +} + +int AudioEngineImpl::play2d(const ccstd::string &filePath, bool loop, float volume) { + if (s_ALDevice == nullptr) { + return AudioEngine::INVALID_AUDIO_ID; + } + + ALuint alSource = findValidSource(); + if (alSource == AL_INVALID) { + return AudioEngine::INVALID_AUDIO_ID; + } + + auto *player = ccnew AudioPlayer; + if (player == nullptr) { + return AudioEngine::INVALID_AUDIO_ID; + } + + player->_alSource = alSource; + player->_loop = loop; + player->_volume = volume; + + auto audioCache = preload(filePath, nullptr); + if (audioCache == nullptr) { + delete player; + return AudioEngine::INVALID_AUDIO_ID; + } + + player->setCache(audioCache); + _threadMutex.lock(); + _audioPlayers[_currentAudioID] = player; + _threadMutex.unlock(); + + audioCache->addPlayCallback(std::bind(&AudioEngineImpl::play2dImpl, this, audioCache, _currentAudioID)); + + if (_lazyInitLoop) { + _lazyInitLoop = false; + if (auto sche = _scheduler.lock()) { + sche->schedule(CC_CALLBACK_1(AudioEngineImpl::update, this), this, 0.05f, false, "AudioEngine"); + } + } + + return _currentAudioID++; +} + +void AudioEngineImpl::play2dImpl(AudioCache *cache, int audioID) { + //Note: It may be in sub thread or main thread :( + if (!*cache->_isDestroyed && cache->_state == AudioCache::State::READY) { + _threadMutex.lock(); + auto playerIt = _audioPlayers.find(audioID); + if (playerIt != _audioPlayers.end()) { + // Trust it, or assert it out. + bool res = playerIt->second->play2d(); + CC_ASSERT(res); + } + _threadMutex.unlock(); + } else { + ALOGD("AudioEngineImpl::play2dImpl, cache was destroyed or not ready!"); + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + iter->second->_removeByAudioEngine = true; + } + } +} + +ALuint AudioEngineImpl::findValidSource() { + ALuint sourceId = AL_INVALID; + if (!_unusedSourcesPool.empty()) { + sourceId = _unusedSourcesPool.front(); + _unusedSourcesPool.pop_front(); + } + + return sourceId; +} + +void AudioEngineImpl::setVolume(int audioID, float volume) { + if (!checkAudioIdValid(audioID)) { + return; + } + auto player = _audioPlayers[audioID]; + player->_volume = volume; + + if (player->_ready) { + alSourcef(_audioPlayers[audioID]->_alSource, AL_GAIN, volume); + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__, audioID, error); + } + } +} + +void AudioEngineImpl::setLoop(int audioID, bool loop) { + if (!checkAudioIdValid(audioID)) { + return; + } + auto player = _audioPlayers[audioID]; + + if (player->_ready) { + if (player->_streamingSource) { + player->setLoop(loop); + } else { + if (loop) { + alSourcei(player->_alSource, AL_LOOPING, AL_TRUE); + } else { + alSourcei(player->_alSource, AL_LOOPING, AL_FALSE); + } + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__, audioID, error); + } + } + } else { + player->_loop = loop; + } +} + +bool AudioEngineImpl::pause(int audioID) { + if (!checkAudioIdValid(audioID)) { + return false; + } + bool ret = true; + alSourcePause(_audioPlayers[audioID]->_alSource); + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ret = false; + ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__, audioID, error); + } + + return ret; +} + +bool AudioEngineImpl::resume(int audioID) { + if (!checkAudioIdValid(audioID)) { + return false; + } + bool ret = true; + alSourcePlay(_audioPlayers[audioID]->_alSource); + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ret = false; + ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__, audioID, error); + } + + return ret; +} + +void AudioEngineImpl::stop(int audioID) { + if (!checkAudioIdValid(audioID)) { + return; + } + auto player = _audioPlayers[audioID]; + player->destroy(); + + // Call 'update' method to cleanup immediately since the schedule may be cancelled without any notification. + update(0.0f); +} + +void AudioEngineImpl::stopAll() { + for (auto &&player : _audioPlayers) { + player.second->destroy(); + } + + // Call 'update' method to cleanup immediately since the schedule may be cancelled without any notification. + update(0.0f); +} + +float AudioEngineImpl::getDuration(int audioID) { + if (!checkAudioIdValid(audioID)) { + return 0.0f; + } + auto player = _audioPlayers[audioID]; + if (player->_ready) { + return player->_audioCache->_duration; + } else { + return AudioEngine::TIME_UNKNOWN; + } +} + +float AudioEngineImpl::getDurationFromFile(const ccstd::string &filePath) { + auto it = _audioCaches.find(filePath); + if (it == _audioCaches.end()) { + this->preload(filePath, nullptr); + return AudioEngine::TIME_UNKNOWN; + } + + return it->second._duration; +} + +float AudioEngineImpl::getCurrentTime(int audioID) { + if (!checkAudioIdValid(audioID)) { + return 0.0f; + } + float ret = 0.0f; + auto player = _audioPlayers[audioID]; + if (player->_ready) { + if (player->_streamingSource) { + ret = player->getTime(); + } else { + alGetSourcef(player->_alSource, AL_SEC_OFFSET, &ret); + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ALOGE("%s, audio id:%d,error code:%x", __PRETTY_FUNCTION__, audioID, error); + } + } + } + + return ret; +} + +bool AudioEngineImpl::setCurrentTime(int audioID, float time) { + if (!checkAudioIdValid(audioID)) { + return false; + } + bool ret = false; + auto player = _audioPlayers[audioID]; + + do { + if (!player->_ready) { + std::lock_guard lck(player->_play2dMutex);// To prevent the race condition + player->_timeDirty = true; + player->_currTime = time; + break; + } + + if (player->_streamingSource) { + ret = player->setTime(time); + break; + } else { + if (player->_audioCache->_framesRead != player->_audioCache->_totalFrames && + (time * player->_audioCache->_sampleRate) > player->_audioCache->_framesRead) { + ALOGE("%s: audio id = %d", __PRETTY_FUNCTION__, audioID); + break; + } + + alSourcef(player->_alSource, AL_SEC_OFFSET, time); + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ALOGE("%s: audio id = %d, error = %x", __PRETTY_FUNCTION__, audioID, error); + } + ret = true; + } + } while (0); + + return ret; +} + +void AudioEngineImpl::setFinishCallback(int audioID, const std::function &callback) { + if (!checkAudioIdValid(audioID)) { + return; + } + _audioPlayers[audioID]->_finishCallbak = callback; +} + +void AudioEngineImpl::update(float dt) { + ALint sourceState; + int audioID; + AudioPlayer *player; + ALuint alSource; + + // ALOGV("AudioPlayer count: %d", (int)_audioPlayers.size()); + + for (auto it = _audioPlayers.begin(); it != _audioPlayers.end();) { + audioID = it->first; + player = it->second; + alSource = player->_alSource; + alGetSourcei(alSource, AL_SOURCE_STATE, &sourceState); + + if (player->_removeByAudioEngine) { + AudioEngine::remove(audioID); + _threadMutex.lock(); + it = _audioPlayers.erase(it); + _threadMutex.unlock(); + delete player; + _unusedSourcesPool.push_back(alSource); + } else if (player->_ready && sourceState == AL_STOPPED) { + ccstd::string filePath; + if (player->_finishCallbak) { + auto &audioInfo = AudioEngine::sAudioIDInfoMap[audioID]; + filePath = *audioInfo.filePath; + } + + AudioEngine::remove(audioID); + _threadMutex.lock(); + it = _audioPlayers.erase(it); + _threadMutex.unlock(); + + if (auto sche = _scheduler.lock()) { + if (player->_finishCallbak) { + auto cb = player->_finishCallbak; + sche->performFunctionInCocosThread([audioID, cb, filePath]() { + cb(audioID, filePath); //IDEA: callback will delay 50ms + }); + } + } + + delete player; + _unusedSourcesPool.push_back(alSource); + } else { + ++it; + } + } + + if (_audioPlayers.empty()) { + _lazyInitLoop = true; + if (auto sche = _scheduler.lock()) { + sche->unschedule("AudioEngine", this); + } + } +} + +void AudioEngineImpl::uncache(const ccstd::string &filePath) { + _audioCaches.erase(filePath); +} + +void AudioEngineImpl::uncacheAll() { + _audioCaches.clear(); +} + +bool AudioEngineImpl::checkAudioIdValid(int audioID) { + return _audioPlayers.find(audioID) != _audioPlayers.end(); +} + +PCMHeader AudioEngineImpl::getPCMHeader(const char *url){ + PCMHeader header {}; + auto itr = _audioCaches.find(url); + if (itr != _audioCaches.end() && itr->second._state == AudioCache::State::READY) { + CC_LOG_DEBUG("file %s found in cache, load header directly", url); + auto cache = &itr->second; + header.bytesPerFrame = cache->_bytesPerFrame; + header.channelCount = cache->_channelCount; + header.dataFormat = AudioDataFormat::SIGNED_16; + header.sampleRate = cache->_sampleRate; + header.totalFrames = cache->_totalFrames; + return header; + } + ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url); + if (fileFullPath == "") { + CC_LOG_DEBUG("file %s does not exist or failed to load", url); + return header; + } + AudioDecoder decoder; + do { + if (!decoder.open(fileFullPath.c_str())) { + CC_LOG_ERROR("[Audio Decoder] File open failed %s", url); + break; + } + header.bytesPerFrame = decoder.getBytesPerFrame(); + header.channelCount = decoder.getChannelCount(); + header.dataFormat = AudioDataFormat::SIGNED_16; + header.sampleRate = decoder.getSampleRate(); + header.totalFrames = decoder.getTotalFrames(); + } while (false); + + decoder.close(); + + return header; +} + +ccstd::vector AudioEngineImpl::getOriginalPCMBuffer(const char *url, uint32_t channelID) { + ccstd::vector pcmData; + auto itr = _audioCaches.find(url); + if (itr != _audioCaches.end() && itr->second._state == AudioCache::State::READY) { + auto cache = &itr->second; + auto bytesPerChannelInFrame = cache->_bytesPerFrame / cache->_channelCount; + pcmData.resize(bytesPerChannelInFrame * cache->_totalFrames); + auto *p = pcmData.data(); + if (!cache->isStreaming()) { // Cache contains a fully prepared buffer. + for (int itr = 0; itr < cache->_totalFrames; itr++) { + memcpy(p, cache->_pcmData + itr * cache->_bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame); + p += bytesPerChannelInFrame; + } + return pcmData; + } + } + ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url); + if (fileFullPath.empty()) { + CC_LOG_DEBUG("file %s does not exist or failed to load", url); + return pcmData; + } + AudioDecoder decoder; + + do { + if (!decoder.open(fileFullPath.c_str())) { + CC_LOG_ERROR("[Audio Decoder] File open failed %s", url); + break; + } + const uint32_t bytesPerFrame = decoder.getBytesPerFrame(); + const uint32_t channelCount = decoder.getChannelCount(); + if (channelID >= channelCount) { + CC_LOG_ERROR("channelID invalid, total channel count is %d but %d is required", channelCount, channelID); + break; + } + uint32_t totalFrames = decoder.getTotalFrames(); + uint32_t remainingFrames = totalFrames; + uint32_t framesRead = 0; + uint32_t framesToReadOnce = std::min(totalFrames, static_cast(decoder.getSampleRate() * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM)); + const uint32_t bytesPerChannelInFrame = bytesPerFrame / channelCount; + + pcmData.resize(bytesPerChannelInFrame * totalFrames); + uint8_t *p = pcmData.data(); + + auto tmpBuf = static_cast(malloc(framesToReadOnce * bytesPerFrame)); + + while (remainingFrames > 0) { + framesToReadOnce = std::min(framesToReadOnce, remainingFrames); + framesRead = decoder.read(framesToReadOnce, tmpBuf); + for (int itr = 0; itr < framesToReadOnce; itr++) { + memcpy(p, tmpBuf + itr * bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame); + p += bytesPerChannelInFrame; + } + remainingFrames -= framesToReadOnce; + + }; + free(tmpBuf); + // Adjust total frames by setting position to the end of frames and try to read more data. + // This is a workaround for https://github.com/cocos2d/cocos2d-x/issues/16938 + if (decoder.seek(totalFrames)) { + tmpBuf = static_cast(malloc(bytesPerFrame * framesToReadOnce)); + do { + framesRead = decoder.read(framesToReadOnce, tmpBuf); //read one by one to easy divide + if (framesRead > 0) { // Adjust frames exist + // transfer char data to float data + for (int itr = 0; itr < framesRead; itr++) { + memcpy(p, tmpBuf + itr * bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame); + p += bytesPerChannelInFrame; + } + } + } while (framesRead > 0); + free(tmpBuf); + } + BREAK_IF_ERR_LOG(!decoder.seek(0), "AudioDecoder::seek(0) failed!"); + } while (false); + decoder.close(); + return pcmData; +} diff --git a/cocos/audio/apple/AudioMacros.h b/cocos/audio/apple/AudioMacros.h new file mode 100644 index 0000000..79bdd42 --- /dev/null +++ b/cocos/audio/apple/AudioMacros.h @@ -0,0 +1,67 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#define QUEUEBUFFER_NUM (4) +#define QUEUEBUFFER_TIME_STEP (0.05f) + +#define QUOTEME_(x) #x +#define QUOTEME(x) QUOTEME_(x) + +#if defined(CC_DEBUG) && CC_DEBUG > 0 + #define ALOGV(fmt, ...) printf("V/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__) +#else + #define ALOGV(fmt, ...) \ + do { \ + } while (false) +#endif +#define ALOGD(fmt, ...) printf("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__) +#define ALOGI(fmt, ...) printf("I/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__) +#define ALOGW(fmt, ...) printf("W/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__) +#define ALOGE(fmt, ...) printf("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__) + +#if defined(CC_DEBUG) && CC_DEBUG > 0 + #define CHECK_AL_ERROR_DEBUG() \ + do { \ + ALenum __error = alGetError(); \ + if (__error) { \ + ALOGE("OpenAL error 0x%04X in %s %s %d\n", __error, __FILE__, __FUNCTION__, __LINE__); \ + } \ + } while (false) +#else + #define CHECK_AL_ERROR_DEBUG() +#endif + +#define BREAK_IF(condition) \ + if (!!(condition)) { \ + break; \ + } + +#define BREAK_IF_ERR_LOG(condition, fmt, ...) \ + if (!!(condition)) { \ + ALOGE("(" QUOTEME(condition) ") failed, message: " fmt, ##__VA_ARGS__); \ + break; \ + } diff --git a/cocos/audio/apple/AudioPlayer.h b/cocos/audio/apple/AudioPlayer.h new file mode 100644 index 0000000..62e7ea1 --- /dev/null +++ b/cocos/audio/apple/AudioPlayer.h @@ -0,0 +1,88 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/apple/AudioMacros.h" +#include "base/Macros.h" + +#include +#include +#include +#include +#include "base/std/container/string.h" + +namespace cc { +class AudioCache; +class AudioEngineImpl; + +class AudioPlayer { +public: + AudioPlayer(); + ~AudioPlayer(); + + void destroy(); + + //queue buffer related stuff + bool setTime(float time); + float getTime() { return _currTime; } + bool setLoop(bool loop); + +protected: + void setCache(AudioCache *cache); + void rotateBufferThread(int offsetFrame); + bool play2d(); + void wakeupRotateThread(); + + AudioCache *_audioCache; + + float _volume; + bool _loop; + std::function _finishCallbak; + + bool _isDestroyed; + bool _removeByAudioEngine; + bool _ready; + ALuint _alSource; + + //play by circular buffer + float _currTime; + bool _streamingSource; + ALuint _bufferIds[QUEUEBUFFER_NUM]; + std::thread *_rotateBufferThread; + std::condition_variable _sleepCondition; + std::mutex _sleepMutex; + bool _timeDirty; + bool _isRotateThreadExited; + std::atomic_bool _needWakeupRotateThread; + + std::mutex _play2dMutex; + + unsigned int _id; + + friend class AudioEngineImpl; +}; + +} // namespace cc diff --git a/cocos/audio/apple/AudioPlayer.mm b/cocos/audio/apple/AudioPlayer.mm new file mode 100644 index 0000000..63bdae6 --- /dev/null +++ b/cocos/audio/apple/AudioPlayer.mm @@ -0,0 +1,350 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioPlayer" + +#import +#include "audio/apple/AudioCache.h" +#include "audio/apple/AudioDecoder.h" +#include "audio/apple/AudioPlayer.h" +#include "base/memory/Memory.h" +#include "platform/FileUtils.h" + +#ifdef VERY_VERY_VERBOSE_LOGGING + #define ALOGVV ALOGV +#else + #define ALOGVV(...) \ + do { \ + } while (false) +#endif + +using namespace cc; + +namespace { +unsigned int __idIndex = 0; +} + +AudioPlayer::AudioPlayer() +: _audioCache(nullptr), _finishCallbak(nullptr), _isDestroyed(false), _removeByAudioEngine(false), _ready(false), _currTime(0.0f), _streamingSource(false), _rotateBufferThread(nullptr), _timeDirty(false), _isRotateThreadExited(false), _needWakeupRotateThread(false), _id(++__idIndex) { + memset(_bufferIds, 0, sizeof(_bufferIds)); +} + +AudioPlayer::~AudioPlayer() { + ALOGVV("~AudioPlayer() (%p), id=%u", this, _id); + destroy(); + + if (_streamingSource) { + alDeleteBuffers(QUEUEBUFFER_NUM, _bufferIds); + } +} + +void AudioPlayer::destroy() { + if (_isDestroyed) + return; + + ALOGVV("AudioPlayer::destroy begin, id=%u", _id); + + _isDestroyed = true; + + do { + if (_audioCache != nullptr) { + if (_audioCache->_state == AudioCache::State::INITIAL) { + ALOGV("AudioPlayer::destroy, id=%u, cache isn't ready!", _id); + break; + } + + while (!_audioCache->_isLoadingFinished) { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + } + + // Wait for play2d to be finished. + _play2dMutex.lock(); + _play2dMutex.unlock(); + + if (_streamingSource) { + if (_rotateBufferThread != nullptr) { + while (!_isRotateThreadExited) { + _sleepCondition.notify_one(); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + if (_rotateBufferThread->joinable()) { + _rotateBufferThread->join(); + } + + delete _rotateBufferThread; + _rotateBufferThread = nullptr; + ALOGVV("rotateBufferThread exited!"); + +#if CC_TARGET_PLATFORM == CC_PLATFORM_IOS + // some specific OpenAL implement defects existed on iOS platform + // refer to: https://github.com/cocos2d/cocos2d-x/issues/18597 + ALint sourceState; + ALint bufferProcessed = 0; + alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState); + if (sourceState == AL_PLAYING) { + alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed); + while (bufferProcessed < QUEUEBUFFER_NUM) { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed); + } + alSourceUnqueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds); + CHECK_AL_ERROR_DEBUG(); + } + ALOGVV("UnqueueBuffers Before alSourceStop"); +#endif + } + } + } while (false); + + ALOGVV("Before alSourceStop"); + alSourceStop(_alSource); + CHECK_AL_ERROR_DEBUG(); + ALOGVV("Before alSourcei"); + alSourcei(_alSource, AL_BUFFER, 0); + CHECK_AL_ERROR_DEBUG(); + + _removeByAudioEngine = true; + + _ready = false; + ALOGVV("AudioPlayer::destroy end, id=%u", _id); +} + +void AudioPlayer::setCache(AudioCache *cache) { + _audioCache = cache; +} + +bool AudioPlayer::play2d() { + _play2dMutex.lock(); + ALOGVV("AudioPlayer::play2d, _alSource: %u", _alSource); + + /*********************************************************************/ + /* Note that it may be in sub thread or in main thread. **/ + /*********************************************************************/ + bool ret = false; + do { + if (_audioCache->_state != AudioCache::State::READY) { + ALOGE("alBuffer isn't ready for play!"); + break; + } + + alSourcei(_alSource, AL_BUFFER, 0); + CHECK_AL_ERROR_DEBUG(); + alSourcef(_alSource, AL_PITCH, 1.0f); + CHECK_AL_ERROR_DEBUG(); + alSourcef(_alSource, AL_GAIN, _volume); + CHECK_AL_ERROR_DEBUG(); + alSourcei(_alSource, AL_LOOPING, AL_FALSE); + CHECK_AL_ERROR_DEBUG(); + + if (_audioCache->_queBufferFrames == 0) { + if (_loop) { + alSourcei(_alSource, AL_LOOPING, AL_TRUE); + CHECK_AL_ERROR_DEBUG(); + } + } else { + if (_currTime > _audioCache->_duration) { + _currTime = 0.F; // Target current start time is invalid, reset to 0. + } + alGenBuffers(QUEUEBUFFER_NUM, _bufferIds); + + auto alError = alGetError(); + if (alError == AL_NO_ERROR) { + for (int index = 0; index < QUEUEBUFFER_NUM; ++index) { + alBufferData(_bufferIds[index], _audioCache->_format, _audioCache->_queBuffers[index], _audioCache->_queBufferSize[index], _audioCache->_sampleRate); + } + CHECK_AL_ERROR_DEBUG(); + } else { + ALOGE("%s:alGenBuffers error code:%x", __PRETTY_FUNCTION__, alError); + break; + } + _streamingSource = true; + } + + { + std::unique_lock lk(_sleepMutex); + if (_isDestroyed) + break; + + if (_streamingSource) { + // To continuously stream audio from a source without interruption, buffer queuing is required. + alSourceQueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds); + CHECK_AL_ERROR_DEBUG(); + _rotateBufferThread = ccnew std::thread(&AudioPlayer::rotateBufferThread, this, _audioCache->_queBufferFrames * QUEUEBUFFER_NUM + 1); + } else { + alSourcei(_alSource, AL_BUFFER, _audioCache->_alBufferId); + CHECK_AL_ERROR_DEBUG(); + } + + alSourcePlay(_alSource); + } + + auto alError = alGetError(); + if (alError != AL_NO_ERROR) { + ALOGE("%s:alSourcePlay error code:%x", __PRETTY_FUNCTION__, alError); + break; + } + /** Due to the bug of OpenAL, when the second time OpenAL trying to mix audio into bus, the mRampState become kRampingComplete, and for those oalSource whose mRampState == kRampingComplete, nothing happens. + * OALSource::Play{ + * switch(mState){ + * case kTransitionToStop: + * case kTransitionToStop: + * if(mRampState != kRampingComplete){..} + * break; + * } + * } + * So the assert here will trigger this bug as aolSource is reused. + * Replace OpenAL with AVAudioEngine on V3.6 mightbe helpful + */ +// CC_ASSERT_EQ(state, AL_PLAYING); + _ready = true; + ret = true; + } while (false); + + if (!ret) { + _removeByAudioEngine = true; + } + + _play2dMutex.unlock(); + return ret; +} + +// rotateBufferThread is used to rotate alBufferData for _alSource when playing big audio file +void AudioPlayer::rotateBufferThread(int offsetFrame) { + char *tmpBuffer = nullptr; + AudioDecoder decoder; + long long rotateSleepTime = static_cast(QUEUEBUFFER_TIME_STEP * 1000) / 2; + do { + BREAK_IF(!decoder.open(_audioCache->_fileFullPath.c_str())); + + uint32_t framesRead = 0; + const uint32_t framesToRead = _audioCache->_queBufferFrames; + const uint32_t bufferSize = framesToRead * decoder.getBytesPerFrame(); + tmpBuffer = (char *)malloc(bufferSize); + memset(tmpBuffer, 0, bufferSize); + + if (offsetFrame != 0) { + decoder.seek(offsetFrame); + } + + ALint sourceState; + ALint bufferProcessed = 0; + bool needToExitThread = false; + + while (!_isDestroyed) { + alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState); + /* On IOS, audio state will lie, when the system is not fully foreground, + * openAl will process the buffer in queue, but our condition cannot make sure that the audio + * is playing as it's too short. Interesting IOS system. + * Solution is to load buffer even if it's paused, just make sure that there's no bufferProcessed in + */ + if (sourceState == AL_PLAYING || sourceState == AL_PAUSED) { + alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed); + while (bufferProcessed > 0) { + bufferProcessed--; + if (_timeDirty) { + _timeDirty = false; + offsetFrame = _currTime * decoder.getSampleRate(); + decoder.seek(offsetFrame); + } else { + _currTime += QUEUEBUFFER_TIME_STEP; + if (_currTime > _audioCache->_duration) { + if (_loop) { + _currTime = 0.0f; + } else { + _currTime = _audioCache->_duration; + } + } + } + + framesRead = decoder.readFixedFrames(framesToRead, tmpBuffer); + + if (framesRead == 0) { + if (_loop) { + decoder.seek(0); + framesRead = decoder.readFixedFrames(framesToRead, tmpBuffer); + } else { + needToExitThread = true; + break; + } + } + /* + While the source is playing, alSourceUnqueueBuffers can be called to remove buffers which have + already played. Those buffers can then be filled with new data or discarded. New or refilled + buffers can then be attached to the playing source using alSourceQueueBuffers. As long as there is + always a new buffer to play in the queue, the source will continue to play. + */ + ALuint bid; + alSourceUnqueueBuffers(_alSource, 1, &bid); + alBufferData(bid, _audioCache->_format, tmpBuffer, framesRead * decoder.getBytesPerFrame(), decoder.getSampleRate()); + alSourceQueueBuffers(_alSource, 1, &bid); + } + } + + std::unique_lock lk(_sleepMutex); + if (_isDestroyed || needToExitThread) { + break; + } + + if (!_needWakeupRotateThread) { + _sleepCondition.wait_for(lk, std::chrono::milliseconds(rotateSleepTime)); + } + + _needWakeupRotateThread = false; + } + + } while (false); + + ALOGVV("Exit rotate buffer thread ..."); + decoder.close(); + free(tmpBuffer); + _isRotateThreadExited = true; +} + +void AudioPlayer::wakeupRotateThread() { + _needWakeupRotateThread = true; + _sleepCondition.notify_all(); +} + +bool AudioPlayer::setLoop(bool loop) { + if (!_isDestroyed) { + _loop = loop; + return true; + } + + return false; +} + +bool AudioPlayer::setTime(float time) { + if (!_isDestroyed && time >= 0.0f && time < _audioCache->_duration) { + _currTime = time; + _timeDirty = true; + + return true; + } + return false; +} diff --git a/cocos/audio/common/decoder/AudioDecoder.cpp b/cocos/audio/common/decoder/AudioDecoder.cpp new file mode 100644 index 0000000..dad85d1 --- /dev/null +++ b/cocos/audio/common/decoder/AudioDecoder.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "audio/common/decoder/AudioDecoder.h" +#include +#include +#include "audio/include/AudioMacros.h" +#include "platform/FileUtils.h" + +#ifdef LOG_TAG + #undef LOG_TAG +#endif +#define LOG_TAG "AudioDecoder" + +namespace cc { + +AudioDecoder::AudioDecoder() +: _isOpened(false) {} + +AudioDecoder::~AudioDecoder() = default; + +bool AudioDecoder::isOpened() const { + return _isOpened; +} + +uint32_t AudioDecoder::readFixedFrames(uint32_t framesToRead, char *pcmBuf) { + uint32_t framesRead = 0; + uint32_t framesReadOnce = 0; + do { + framesReadOnce = read(framesToRead - framesRead, pcmBuf + framesRead * _pcmHeader.bytesPerFrame); + framesRead += framesReadOnce; + } while (framesReadOnce != 0 && framesRead < framesToRead); + + if (framesRead < framesToRead) { + memset(pcmBuf + framesRead * _pcmHeader.bytesPerFrame, 0x00, (framesToRead - framesRead) * _pcmHeader.bytesPerFrame); + } + + return framesRead; +} + +uint32_t AudioDecoder::getTotalFrames() const { + return _pcmHeader.totalFrames; +} + +uint32_t AudioDecoder::getBytesPerFrame() const { + return _pcmHeader.bytesPerFrame; +} + +uint32_t AudioDecoder::getSampleRate() const { + return _pcmHeader.sampleRate; +} + +uint32_t AudioDecoder::getChannelCount() const { + return _pcmHeader.channelCount; +} + +AudioDataFormat AudioDecoder::getDataFormat() const { + return _pcmHeader.dataFormat; +} + +PCMHeader AudioDecoder::getPCMHeader() const { + return _pcmHeader; +} + +} // namespace cc diff --git a/cocos/audio/common/decoder/AudioDecoder.h b/cocos/audio/common/decoder/AudioDecoder.h new file mode 100644 index 0000000..8bf3449 --- /dev/null +++ b/cocos/audio/common/decoder/AudioDecoder.h @@ -0,0 +1,130 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include + +#include "audio/include/AudioDef.h" + +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + #include "vorbis/vorbisfile.h" +#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX + #include "vorbis/vorbisfile.h" +#elif CC_PLATFORM == CC_PLATFORM_OHOS + #include "ivorbisfile.h" +#endif + +namespace cc { + +/** + * @brief The class for decoding compressed audio file to PCM buffer. + */ +class AudioDecoder { +public: + static const uint32_t INVALID_FRAME_INDEX = UINT32_MAX; + + /** + * @brief Opens an audio file specified by a file path. + * @return true if succeed, otherwise false. + */ + virtual bool open(const char *path) = 0; + + /** + * @brief Checks whether decoder has opened file successfully. + * @return true if succeed, otherwise false. + */ + virtual bool isOpened() const; + + /** + * @brief Closes opened audio file. + * @note The method will also be automatically invoked in the destructor. + */ + virtual void close() = 0; + + /** + * @brief Reads audio frames of PCM format. + * @param framesToRead The number of frames excepted to be read. + * @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame. + * @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file. + */ + virtual uint32_t read(uint32_t framesToRead, char *pcmBuf) = 0; + + /** + * @brief Reads fixed audio frames of PCM format. + * @param framesToRead The number of frames excepted to be read. + * @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame. + * @return The number of frames actually read, it's probably less than |framesToRead|. Returns 0 means reach the end of file. + * @note The different between |read| and |readFixedFrames| is |readFixedFrames| will do multiple reading operations if |framesToRead| frames + * isn't filled entirely, while |read| just does reading operation once whatever |framesToRead| is or isn't filled entirely. + * If current position reaches the end of frames, the return value may smaller than |framesToRead| and the remaining + * buffer in |pcmBuf| will be set with silence data (0x00). + */ + virtual uint32_t readFixedFrames(uint32_t framesToRead, char *pcmBuf); + + /** + * @brief Sets frame offest to be read. + * @param frameOffset The frame offest to be set. + * @return true if succeed, otherwise false + */ + virtual bool seek(uint32_t frameOffset) = 0; + + /** + * @brief Tells the current frame offset. + * @return The current frame offset. + */ + virtual uint32_t tell() const = 0; + + /** Gets total frames of current audio.*/ + virtual uint32_t getTotalFrames() const; + + /** Gets bytes per frame of current audio.*/ + virtual uint32_t getBytesPerFrame() const; + + /** Gets sample rate of current audio.*/ + virtual uint32_t getSampleRate() const; + + /** Gets the channel count of current audio. + * @note Currently we only support 1 or 2 channels. + */ + virtual uint32_t getChannelCount() const; + + virtual AudioDataFormat getDataFormat() const; + + virtual PCMHeader getPCMHeader() const; + +protected: + AudioDecoder(); + virtual ~AudioDecoder(); + + bool _isOpened; + PCMHeader _pcmHeader; + void *_fsHooks = nullptr; + + friend class AudioDecoderManager; +}; + +} // namespace cc diff --git a/cocos/audio/common/decoder/AudioDecoderManager.cpp b/cocos/audio/common/decoder/AudioDecoderManager.cpp new file mode 100644 index 0000000..a248372 --- /dev/null +++ b/cocos/audio/common/decoder/AudioDecoderManager.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioDecoderManager" + +#include "audio/common/decoder/AudioDecoderManager.h" +#include "audio/common/decoder/AudioDecoderMp3.h" +#include "audio/common/decoder/AudioDecoderOgg.h" +#include "audio/common/decoder/AudioDecoderWav.h" +#include "audio/include/AudioMacros.h" +#include "base/memory/Memory.h" +#include "platform/FileUtils.h" + +namespace cc { + +bool AudioDecoderManager::init() { + return true; +} + +void AudioDecoderManager::destroy() { + AudioDecoderMp3::destroy(); +} + +AudioDecoder *AudioDecoderManager::createDecoder(const char *path) { + ccstd::string suffix = FileUtils::getInstance()->getFileExtension(path); + if (suffix == ".ogg") { + return ccnew AudioDecoderOgg(); + } + + if (suffix == ".mp3") { + return ccnew AudioDecoderMp3(); + } +#if CC_PLATFORM == CC_PLATFORM_OHOS || CC_PLATFORM == CC_PLATFORM_WINDOWS + if (suffix == ".wav") { + return ccnew AudioDecoderWav(); + } +#endif + + return nullptr; +} + +void AudioDecoderManager::destroyDecoder(AudioDecoder *decoder) { + delete decoder; +} + +} // namespace cc diff --git a/cocos/audio/common/decoder/AudioDecoderManager.h b/cocos/audio/common/decoder/AudioDecoderManager.h new file mode 100644 index 0000000..49ac29c --- /dev/null +++ b/cocos/audio/common/decoder/AudioDecoderManager.h @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace cc { + +class AudioDecoder; + +class AudioDecoderManager { +public: + static bool init(); + static void destroy(); + static AudioDecoder *createDecoder(const char *path); + static void destroyDecoder(AudioDecoder *decoder); +}; + +} // namespace cc diff --git a/cocos/audio/common/decoder/AudioDecoderMp3.cpp b/cocos/audio/common/decoder/AudioDecoderMp3.cpp new file mode 100644 index 0000000..7b9f313 --- /dev/null +++ b/cocos/audio/common/decoder/AudioDecoderMp3.cpp @@ -0,0 +1,179 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "audio/common/decoder/AudioDecoderMp3.h" +#include +#include +#include "audio/include/AudioMacros.h" +#include "platform/FileUtils.h" + +#if CC_PLATFORM == CC_PLATFORM_WINDOWS || CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX + #include "mpg123/mpg123.h" +#elif CC_PLATFORM == CC_PLATFORM_OHOS + #include + #include "cocos/platform/ohos/FileUtils-ohos.h" + #include "mpg123.h" +#endif + +#include +#include + +#ifdef LOG_TAG + #undef LOG_TAG +#endif +#define LOG_TAG "AudioDecoderMp3" + +namespace cc { + +static bool sMp3Inited = false; + +bool AudioDecoderMp3::lazyInit() { + bool ret = true; + if (!sMp3Inited) { + int error = mpg123_init(); + if (error == MPG123_OK) { + sMp3Inited = true; + } else { + ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error)); + ret = false; + } + } + return ret; +} + +void AudioDecoderMp3::destroy() { + if (sMp3Inited) { + mpg123_exit(); + sMp3Inited = false; + } +} + +AudioDecoderMp3::AudioDecoderMp3() { + lazyInit(); +} + +AudioDecoderMp3::~AudioDecoderMp3() { + close(); +} + +bool AudioDecoderMp3::open(const char *path) { + ccstd::string fullPath = FileUtils::getInstance()->fullPathForFilename(path); + + long rate = 0; //NOLINT(google-runtime-int) + int error = MPG123_OK; + int mp3Encoding = 0; + int channel = 0; + do { + _mpg123handle = mpg123_new(nullptr, &error); + if (nullptr == _mpg123handle) { + ALOGE("Basic setup goes wrong: %s", mpg123_plain_strerror(error)); + break; + } +#if CC_PLATFORM_OHOS == CC_PLATFORM + auto *fu = static_cast(FileUtils::getInstance()); + _fdAndDeleter = fu->getFd(fullPath); + if (mpg123_open_fd(_mpg123handle, _fdAndDeleter.first) != MPG123_OK || mpg123_getformat(_mpg123handle, &rate, &channel, &mp3Encoding) != MPG123_OK) { +#else + if (mpg123_open(_mpg123handle, FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str()) != MPG123_OK || mpg123_getformat(_mpg123handle, &rate, &channel, &mp3Encoding) != MPG123_OK) { +#endif + ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(_mpg123handle)); + break; + } + + _pcmHeader.channelCount = channel; + _pcmHeader.sampleRate = rate; + + if (mp3Encoding == MPG123_ENC_SIGNED_16) { + _pcmHeader.bytesPerFrame = 2 * _pcmHeader.channelCount; + _pcmHeader.dataFormat = AudioDataFormat::SIGNED_16; + } else if (mp3Encoding == MPG123_ENC_FLOAT_32) { + _pcmHeader.bytesPerFrame = 4 * _pcmHeader.channelCount; + _pcmHeader.dataFormat = AudioDataFormat::FLOAT_32; + } else { + ALOGE("Bad encoding: 0x%x!\n", mp3Encoding); + break; + } + + /* Ensure that this output format will not change (it could, when we allow it). */ + mpg123_format_none(_mpg123handle); + mpg123_format(_mpg123handle, rate, channel, mp3Encoding); + /* Ensure that we can get accurate length by call mpg123_length */ + mpg123_scan(_mpg123handle); + + _pcmHeader.totalFrames = mpg123_length(_mpg123handle); + + _isOpened = true; + return true; + } while (false); + + if (_mpg123handle != nullptr) { + mpg123_close(_mpg123handle); + mpg123_delete(_mpg123handle); + _mpg123handle = nullptr; + } + return false; +} + +void AudioDecoderMp3::close() { + if (isOpened()) { + if (_mpg123handle != nullptr) { + mpg123_close(_mpg123handle); + + mpg123_delete(_mpg123handle); + _mpg123handle = nullptr; + } + _isOpened = false; + } +#if CC_PLATFORM_OHOS == CC_PLATFORM + if (_fdAndDeleter.second) { + _fdAndDeleter.second(); + _fdAndDeleter.second = nullptr; + } +#endif +} + +uint32_t AudioDecoderMp3::read(uint32_t framesToRead, char *pcmBuf) { + size_t bytesToRead = framesToRead * _pcmHeader.bytesPerFrame; + size_t bytesRead = 0; + int err = mpg123_read(_mpg123handle, reinterpret_cast(pcmBuf), bytesToRead, &bytesRead); + if (err == MPG123_ERR) { + ALOGE("Trouble with mpg123: %s\n", mpg123_strerror(_mpg123handle)); + return 0; + } + + return static_cast(bytesRead / _pcmHeader.bytesPerFrame); +} + +bool AudioDecoderMp3::seek(uint32_t frameOffset) { + off_t offset = mpg123_seek(_mpg123handle, frameOffset, SEEK_SET); + //ALOGD("mpg123_seek return: %d", (int)offset); + return offset >= 0 && offset == frameOffset; +} + +uint32_t AudioDecoderMp3::tell() const { + return static_cast(mpg123_tell(_mpg123handle)); +} + +} // namespace cc diff --git a/cocos/audio/common/decoder/AudioDecoderMp3.h b/cocos/audio/common/decoder/AudioDecoderMp3.h new file mode 100644 index 0000000..e77a831 --- /dev/null +++ b/cocos/audio/common/decoder/AudioDecoderMp3.h @@ -0,0 +1,90 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/common/decoder/AudioDecoder.h" + +#include + +struct mpg123_handle_struct; + +namespace cc { + +/** + * @brief The class for decoding compressed audio file to PCM buffer. + */ +class AudioDecoderMp3 : public AudioDecoder { +public: + /** + * @brief Opens an audio file specified by a file path. + * @return true if succeed, otherwise false. + */ + bool open(const char *path) override; + + /** + * @brief Closes opened audio file. + * @note The method will also be automatically invoked in the destructor. + */ + void close() override; + + /** + * @brief Reads audio frames of PCM format. + * @param framesToRead The number of frames excepted to be read. + * @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame. + * @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file. + */ + uint32_t read(uint32_t framesToRead, char *pcmBuf) override; + + /** + * @brief Sets frame offest to be read. + * @param frameOffset The frame offest to be set. + * @return true if succeed, otherwise false + */ + bool seek(uint32_t frameOffset) override; + + /** + * @brief Tells the current frame offset. + * @return The current frame offset. + */ + uint32_t tell() const override; + +protected: + AudioDecoderMp3(); + ~AudioDecoderMp3() override; + + static bool lazyInit(); + static void destroy(); + + struct mpg123_handle_struct *_mpg123handle = nullptr; + +#if CC_PLATFORM_OHOS == CC_PLATFORM + std::pair> _fdAndDeleter; +#endif + + friend class AudioDecoderManager; +}; + +} // namespace cc diff --git a/cocos/audio/common/decoder/AudioDecoderOgg.cpp b/cocos/audio/common/decoder/AudioDecoderOgg.cpp new file mode 100644 index 0000000..e78babc --- /dev/null +++ b/cocos/audio/common/decoder/AudioDecoderOgg.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "audio/common/decoder/AudioDecoderOgg.h" +#include + +#include "audio/include/AudioMacros.h" +#include "platform/FileUtils.h" + +#if CC_PLATFORM == CC_PLATFORM_OHOS + #include "audio/ohos/FsCallback.h" +namespace { +int ohosSeek_wrap(void *source, ogg_int64_t offset, int whence) { //NOLINT + return cc::ohosSeek(source, static_cast(offset), whence); //NOLINT +} + +ov_callbacks ogg_callbacks = { //NOLINT + static_cast(cc::ohosRead), + static_cast(ohosSeek_wrap), + static_cast(cc::ohosClose), + static_cast(cc::ohosTell)}; //NOLINT +} // namespace +#endif + +#ifdef LOG_TAG + #undef LOG_TAG +#endif +#define LOG_TAG "AudioDecoderOgg" + +namespace cc { + +AudioDecoderOgg::AudioDecoderOgg() = default; + +AudioDecoderOgg::~AudioDecoderOgg() { + close(); +} + +bool AudioDecoderOgg::open(const char *path) { + ccstd::string fullPath = FileUtils::getInstance()->fullPathForFilename(path); +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + if (0 == ov_fopen(FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str(), &_vf)) { +#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX + if (0 == ov_fopen(FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str(), &_vf)) { +#elif CC_PLATFORM == CC_PLATFORM_OHOS + auto *fp = cc::ohosOpen(FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str(), this); + if (0 == ov_open_callbacks(fp, &_vf, nullptr, 0, ogg_callbacks)) { +#endif + // header + vorbis_info *vi = ov_info(&_vf, -1); + _pcmHeader.sampleRate = static_cast(vi->rate); + _pcmHeader.channelCount = vi->channels; + _pcmHeader.bytesPerFrame = vi->channels * sizeof(int16_t); + _pcmHeader.dataFormat = AudioDataFormat::SIGNED_16; + _pcmHeader.totalFrames = static_cast(ov_pcm_total(&_vf, -1)); + _isOpened = true; + return true; + } + return false; +} + +void AudioDecoderOgg::close() { + if (isOpened()) { + ov_clear(&_vf); + _isOpened = false; + } +} + +uint32_t AudioDecoderOgg::read(uint32_t framesToRead, char *pcmBuf) { + int currentSection = 0; + auto bytesToRead = framesToRead * _pcmHeader.bytesPerFrame; +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + auto bytesRead = ov_read(&_vf, pcmBuf, bytesToRead, 0, 2, 1, ¤tSection); +#elif CC_PLATFORM == CC_PLATFORM_OHOS + int bitstream = 0; + auto bytesRead = ov_read(&_vf, pcmBuf, bytesToRead, &bitstream); +#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX + auto bytesRead = ov_read(&_vf, pcmBuf, bytesToRead, 0, 2, 1, ¤tSection); +#endif + return static_cast(bytesRead / _pcmHeader.bytesPerFrame); +} + +bool AudioDecoderOgg::seek(uint32_t frameOffset) { + return 0 == ov_pcm_seek(&_vf, frameOffset); +} + +uint32_t AudioDecoderOgg::tell() const { + return static_cast(ov_pcm_tell(const_cast(&_vf))); +} + +} // namespace cc diff --git a/cocos/audio/common/decoder/AudioDecoderOgg.h b/cocos/audio/common/decoder/AudioDecoderOgg.h new file mode 100644 index 0000000..2a2a854 --- /dev/null +++ b/cocos/audio/common/decoder/AudioDecoderOgg.h @@ -0,0 +1,85 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/common/decoder/AudioDecoder.h" + +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + #include "vorbis/vorbisfile.h" +#elif CC_PLATFORM == CC_PLATFORM_OHOS + #include "ivorbisfile.h" +#endif + +namespace cc { + +/** + * @brief The class for decoding compressed audio file to PCM buffer. + */ +class AudioDecoderOgg : public AudioDecoder { +public: + /** + * @brief Opens an audio file specified by a file path. + * @return true if succeed, otherwise false. + */ + bool open(const char *path) override; + + /** + * @brief Closes opened audio file. + * @note The method will also be automatically invoked in the destructor. + */ + void close() override; + + /** + * @brief Reads audio frames of PCM format. + * @param framesToRead The number of frames excepted to be read. + * @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame. + * @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file. + */ + uint32_t read(uint32_t framesToRead, char *pcmBuf) override; + + /** + * @brief Sets frame offest to be read. + * @param frameOffset The frame offest to be set. + * @return true if succeed, otherwise false + */ + bool seek(uint32_t frameOffset) override; + + /** + * @brief Tells the current frame offset. + * @return The current frame offset. + */ + uint32_t tell() const override; + +protected: + AudioDecoderOgg(); + ~AudioDecoderOgg() override; + + OggVorbis_File _vf; + + friend class AudioDecoderManager; +}; + +} // namespace cc diff --git a/cocos/audio/common/decoder/AudioDecoderWav.cpp b/cocos/audio/common/decoder/AudioDecoderWav.cpp new file mode 100644 index 0000000..6487f33 --- /dev/null +++ b/cocos/audio/common/decoder/AudioDecoderWav.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +Copyright (c) 2016 Chukong Technologies Inc. +Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioDecoderWav" + +#include "audio/common/decoder/AudioDecoderWav.h" +#include "base/Log.h" +#include "platform/FileUtils.h" + +namespace cc { + +AudioDecoderWav::AudioDecoderWav() { + CC_LOG_DEBUG("Create AudioDecoderWav"); +} + +AudioDecoderWav::~AudioDecoderWav() { + close(); +}; + +bool AudioDecoderWav::open(const char *path) { + bool ret{false}; + auto fullPath = FileUtils::getInstance()->fullPathForFilename(path); + if (fullPath.empty()) { + CC_LOG_DEBUG("File does not exist %s", fullPath.c_str()); + return false; + } + do { + sf::SF_INFO info; + _sf_handle = sf::sf_open_read(fullPath.c_str(), &info, nullptr, nullptr); + if (_sf_handle == nullptr) { + CC_LOG_ERROR("file %s open failed, it might be invalid", fullPath.c_str()); + break; + } + if (info.frames == 0) { + CC_LOG_ERROR("file %s has no frame, is it an invalid wav file?", fullPath.c_str()); + break; + } + CC_LOG_DEBUG("wav info: frames: %d, samplerate: %d, channels: %d, format: %d", info.frames, info.samplerate, info.channels, info.format); + _pcmHeader.channelCount = info.channels; + _pcmHeader.bytesPerFrame = 2 * info.channels; // FIXED_16 + _pcmHeader.dataFormat = AudioDataFormat::SIGNED_16; //FIXED, + _pcmHeader.sampleRate = info.samplerate; + _pcmHeader.totalFrames = info.frames; + _isOpened = true; + ret = true; + + } while (false); + return ret; +} + +uint32_t AudioDecoderWav::read(uint32_t framesToRead, char *pcmBuf) { + //size_t bytesToRead = framesToRead * _pcmHeader.bytesPerFrame; + size_t framesRead = sf::sf_readf_short(_sf_handle, reinterpret_cast(pcmBuf), framesToRead); + return framesRead; +} + +bool AudioDecoderWav::seek(uint32_t frameOffset) { + auto offset = sf::sf_seek(_sf_handle, frameOffset, SEEK_SET); + return offset >= 0 && offset == frameOffset; +} +uint32_t AudioDecoderWav::tell() const { + return static_cast(sf::sf_tell(_sf_handle)); +} +void AudioDecoderWav::close() { + if (_isOpened) { + if (_sf_handle) { + sf::sf_close(_sf_handle); + } + _isOpened = false; + } +} +} // namespace cc diff --git a/cocos/audio/common/decoder/AudioDecoderWav.h b/cocos/audio/common/decoder/AudioDecoderWav.h new file mode 100644 index 0000000..8016310 --- /dev/null +++ b/cocos/audio/common/decoder/AudioDecoderWav.h @@ -0,0 +1,76 @@ +/**************************************************************************** +Copyright (c) 2016 Chukong Technologies Inc. +Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/common/decoder/AudioDecoder.h" +#include "audio/common/utils/include/tinysndfile.h" + +namespace cc { + +class AudioDecoderWav : public AudioDecoder { +public: + /** + * @brief Opens an audio file specified by a file path. + * @return true if succeed, otherwise false. + */ + bool open(const char *path) override; + + /** + * @brief Closes opened audio file. + * @note The method will also be automatically invoked in the destructor. + */ + void close() override; + + /** + * @brief Reads audio frames of PCM format. + * @param framesToRead The number of frames excepted to be read. + * @param pcmBuf The buffer to hold the frames to be read, its size should be >= |framesToRead| * _bytesPerFrame. + * @return The number of frames actually read, it's probably less than 'framesToRead'. Returns 0 means reach the end of file. + */ + uint32_t read(uint32_t framesToRead, char *pcmBuf) override; + + /** + * @brief Sets frame offest to be read. + * @param frameOffset The frame offest to be set. + * @return true if succeed, otherwise false + */ + bool seek(uint32_t frameOffset) override; + + /** + * @brief Tells the current frame offset. + * @return The current frame offset. + */ + uint32_t tell() const override; + +protected: + AudioDecoderWav(); + ~AudioDecoderWav() override; + sf::SNDFILE *_sf_handle{nullptr}; + + friend class AudioDecoderManager; +}; + +} // namespace cc diff --git a/cocos/audio/common/utils/format.cpp b/cocos/audio/common/utils/format.cpp new file mode 100644 index 0000000..caa776c --- /dev/null +++ b/cocos/audio/common/utils/format.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* #define LOG_NDEBUG 0 */ +#define LOG_TAG "audio_utils_format" + +#include "audio/common/utils/include/format.h" +#include "audio/android/audio.h" +#include "audio/android/cutils/log.h" +#include "audio/common/utils/include/primitives.h" + +void memcpy_by_audio_format(void *dst, audio_format_t dst_format, + const void *src, audio_format_t src_format, size_t count) { + /* default cases for error falls through to fatal log below. */ + if (dst_format == src_format) { + switch (dst_format) { + case AUDIO_FORMAT_PCM_16_BIT: + case AUDIO_FORMAT_PCM_FLOAT: + case AUDIO_FORMAT_PCM_8_BIT: + case AUDIO_FORMAT_PCM_24_BIT_PACKED: + case AUDIO_FORMAT_PCM_32_BIT: + case AUDIO_FORMAT_PCM_8_24_BIT: + memcpy(dst, src, count * audio_bytes_per_sample(dst_format)); + return; + default: + break; + } + } + switch (dst_format) { + case AUDIO_FORMAT_PCM_16_BIT: + switch (src_format) { + case AUDIO_FORMAT_PCM_FLOAT: + memcpy_to_i16_from_float((int16_t *)dst, (float *)src, count); + return; + case AUDIO_FORMAT_PCM_8_BIT: + memcpy_to_i16_from_u8((int16_t *)dst, (uint8_t *)src, count); + return; + case AUDIO_FORMAT_PCM_24_BIT_PACKED: + memcpy_to_i16_from_p24((int16_t *)dst, (uint8_t *)src, count); + return; + case AUDIO_FORMAT_PCM_32_BIT: + memcpy_to_i16_from_i32((int16_t *)dst, (int32_t *)src, count); + return; + case AUDIO_FORMAT_PCM_8_24_BIT: + memcpy_to_i16_from_q8_23((int16_t *)dst, (int32_t *)src, count); + return; + default: + break; + } + break; + case AUDIO_FORMAT_PCM_FLOAT: + switch (src_format) { + case AUDIO_FORMAT_PCM_16_BIT: + memcpy_to_float_from_i16((float *)dst, (int16_t *)src, count); + return; + case AUDIO_FORMAT_PCM_8_BIT: + memcpy_to_float_from_u8((float *)dst, (uint8_t *)src, count); + return; + case AUDIO_FORMAT_PCM_24_BIT_PACKED: + memcpy_to_float_from_p24((float *)dst, (uint8_t *)src, count); + return; + case AUDIO_FORMAT_PCM_32_BIT: + memcpy_to_float_from_i32((float *)dst, (int32_t *)src, count); + return; + case AUDIO_FORMAT_PCM_8_24_BIT: + memcpy_to_float_from_q8_23((float *)dst, (int32_t *)src, count); + return; + default: + break; + } + break; + case AUDIO_FORMAT_PCM_8_BIT: + switch (src_format) { + case AUDIO_FORMAT_PCM_16_BIT: + memcpy_to_u8_from_i16((uint8_t *)dst, (int16_t *)src, count); + return; + case AUDIO_FORMAT_PCM_FLOAT: + memcpy_to_u8_from_float((uint8_t *)dst, (float *)src, count); + return; + default: + break; + } + break; + case AUDIO_FORMAT_PCM_24_BIT_PACKED: + switch (src_format) { + case AUDIO_FORMAT_PCM_16_BIT: + memcpy_to_p24_from_i16((uint8_t *)dst, (int16_t *)src, count); + return; + case AUDIO_FORMAT_PCM_FLOAT: + memcpy_to_p24_from_float((uint8_t *)dst, (float *)src, count); + return; + default: + break; + } + break; + case AUDIO_FORMAT_PCM_32_BIT: + switch (src_format) { + case AUDIO_FORMAT_PCM_16_BIT: + memcpy_to_i32_from_i16((int32_t *)dst, (int16_t *)src, count); + return; + case AUDIO_FORMAT_PCM_FLOAT: + memcpy_to_i32_from_float((int32_t *)dst, (float *)src, count); + return; + default: + break; + } + break; + case AUDIO_FORMAT_PCM_8_24_BIT: + switch (src_format) { + case AUDIO_FORMAT_PCM_16_BIT: + memcpy_to_q8_23_from_i16((int32_t *)dst, (int16_t *)src, count); + return; + case AUDIO_FORMAT_PCM_FLOAT: + memcpy_to_q8_23_from_float_with_clamp((int32_t *)dst, (float *)src, count); + return; + case AUDIO_FORMAT_PCM_24_BIT_PACKED: { + memcpy_to_q8_23_from_p24((int32_t *)dst, (uint8_t *)src, count); + return; + } + default: + break; + } + break; + default: + break; + } + LOG_ALWAYS_FATAL("invalid src format %#x for dst format %#x", + src_format, dst_format); +} + +size_t memcpy_by_index_array_initialization_from_channel_mask(int8_t *idxary, size_t arysize, + audio_channel_mask_t dst_channel_mask, audio_channel_mask_t src_channel_mask) { + const audio_channel_representation_t src_representation = + audio_channel_mask_get_representation(src_channel_mask); + const audio_channel_representation_t dst_representation = + audio_channel_mask_get_representation(dst_channel_mask); + const uint32_t src_bits = audio_channel_mask_get_bits(src_channel_mask); + const uint32_t dst_bits = audio_channel_mask_get_bits(dst_channel_mask); + + switch (src_representation) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + switch (dst_representation) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + return memcpy_by_index_array_initialization(idxary, arysize, + dst_bits, src_bits); + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + return memcpy_by_index_array_initialization_dst_index(idxary, arysize, + dst_bits, src_bits); + default: + return 0; + } + break; + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + switch (dst_representation) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + return memcpy_by_index_array_initialization_src_index(idxary, arysize, + dst_bits, src_bits); + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + return memcpy_by_index_array_initialization(idxary, arysize, + dst_bits, src_bits); + default: + return 0; + } + break; + default: + return 0; + } +} diff --git a/cocos/audio/common/utils/include/format.h b/cocos/audio/common/utils/include/format.h new file mode 100644 index 0000000..854c160 --- /dev/null +++ b/cocos/audio/common/utils/include/format.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COCOS_AUDIO_FORMAT_H +#define COCOS_AUDIO_FORMAT_H + +#include +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include +#endif + +#include "audio/android/audio.h" + +/* Copy buffers with conversion between buffer sample formats. + * + * dst Destination buffer + * dst_format Destination buffer format + * src Source buffer + * src_format Source buffer format + * count Number of samples to copy + * + * Allowed format conversions are given by either case 1 or 2 below: + * + * 1) One of src_format or dst_format is AUDIO_FORMAT_PCM_16_BIT or + * AUDIO_FORMAT_PCM_FLOAT, and the other format type is one of: + * + * AUDIO_FORMAT_PCM_16_BIT + * AUDIO_FORMAT_PCM_FLOAT + * AUDIO_FORMAT_PCM_8_BIT + * AUDIO_FORMAT_PCM_24_BIT_PACKED + * AUDIO_FORMAT_PCM_32_BIT + * AUDIO_FORMAT_PCM_8_24_BIT + * + * 2) Both dst_format and src_format are identical and of the list given + * in (1). This is a straight copy. + * + * The destination and source buffers must be completely separate if the destination + * format size is larger than the source format size. These routines call functions + * in primitives.h, so descriptions of detailed behavior can be reviewed there. + * + * Logs a fatal error if dst or src format is not allowed by the conversion rules above. + */ +void memcpy_by_audio_format(void *dst, audio_format_t dst_format, + const void *src, audio_format_t src_format, size_t count); + +/* This function creates an index array for converting audio data with different + * channel position and index masks, used by memcpy_by_index_array(). + * Returns the number of array elements required. + * This may be greater than idxcount, so the return value should be checked + * if idxary size is less than 32. Returns zero if the input masks are unrecognized. + * + * Note that idxary is a caller allocated array + * of at least as many channels as present in the dst_mask. + * + * Parameters: + * idxary Updated array of indices of channels in the src frame for the dst frame + * idxcount Number of caller allocated elements in idxary + * dst_mask Bit mask corresponding to destination channels present + * src_mask Bit mask corresponding to source channels present + */ +size_t memcpy_by_index_array_initialization_from_channel_mask(int8_t *idxary, size_t arysize, + audio_channel_mask_t dst_channel_mask, audio_channel_mask_t src_channel_mask); + +#endif // COCOS_AUDIO_FORMAT_H diff --git a/cocos/audio/common/utils/include/minifloat.h b/cocos/audio/common/utils/include/minifloat.h new file mode 100644 index 0000000..4fe6873 --- /dev/null +++ b/cocos/audio/common/utils/include/minifloat.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COCOS_AUDIO_MINIFLOAT_H +#define COCOS_AUDIO_MINIFLOAT_H + +#include +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include +#elif CC_PLATFORM == CC_PLATFORM_WINDOWS + #include +#endif + +/* A single gain expressed as minifloat */ +typedef uint16_t gain_minifloat_t; + +/* A pair of gain_minifloat_t packed into a single word */ +typedef uint32_t gain_minifloat_packed_t; + +/* The nominal range of a gain, expressed as a float */ +#define GAIN_FLOAT_ZERO 0.0f +#define GAIN_FLOAT_UNITY 1.0f + +/* Unity gain expressed as a minifloat */ +#define GAIN_MINIFLOAT_UNITY 0xE000 + +/* Pack a pair of gain_mini_float_t into a combined gain_minifloat_packed_t */ +static inline gain_minifloat_packed_t gain_minifloat_pack(gain_minifloat_t left, + gain_minifloat_t right) { + return (right << 16) | left; +} + +/* Unpack a gain_minifloat_packed_t into the two gain_minifloat_t components */ +static inline gain_minifloat_t gain_minifloat_unpack_left(gain_minifloat_packed_t packed) { + return packed & 0xFFFF; +} + +static inline gain_minifloat_t gain_minifloat_unpack_right(gain_minifloat_packed_t packed) { + return packed >> 16; +} + +/* A pair of unity gains expressed as a gain_minifloat_packed_t */ +#define GAIN_MINIFLOAT_PACKED_UNITY gain_minifloat_pack(GAIN_MINIFLOAT_UNITY, GAIN_MINIFLOAT_UNITY) + +/* Convert a float to the internal representation used for gains. + * The nominal range [0.0, 1.0], but the hard range is [0.0, 2.0). + * Negative and underflow values are converted to 0.0, + * and values larger than the hard maximum are truncated to the hard maximum. + * + * Minifloats are ordered, and standard comparisons may be used between them + * in the gain_minifloat_t representation. + * + * Details on internal representation of gains, based on mini-floats: + * The nominal maximum is 1.0 and the hard maximum is 1 ULP less than 2.0, or +6 dB. + * The minimum non-zero value is approximately 1.9e-6 or -114 dB. + * Negative numbers, infinity, and NaN are not supported. + * There are 13 significand bits specified, 1 implied hidden bit, 3 exponent bits, + * and no sign bit. Denormals are supported. + */ +gain_minifloat_t gain_from_float(float v); + +/* Convert the internal representation used for gains to float */ +float float_from_gain(gain_minifloat_t a); + +#endif // COCOS_AUDIO_MINIFLOAT_H diff --git a/cocos/audio/common/utils/include/primitives.h b/cocos/audio/common/utils/include/primitives.h new file mode 100644 index 0000000..0901e20 --- /dev/null +++ b/cocos/audio/common/utils/include/primitives.h @@ -0,0 +1,936 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include +#elif CC_PLATFORM == CC_PLATFORM_WINDOWS + #include +#endif + +#include +#include +#include +#if CC_PLATFORM == CC_PLATFORM_ANDROID +#include +#endif + +/* The memcpy_* conversion routines are designed to work in-place on same dst as src + * buffers only if the types shrink on copy, with the exception of memcpy_to_i16_from_u8(). + * This allows the loops to go upwards for faster cache access (and may be more flexible + * for future optimization later). + */ + +/** + * Dither and clamp pairs of 32-bit input samples (sums) to 16-bit output samples (out). + * Each 32-bit input sample can be viewed as a signed fixed-point Q19.12 of which the + * .12 fraction bits are dithered and the 19 integer bits are clamped to signed 16 bits. + * Alternatively the input can be viewed as Q4.27, of which the lowest .12 of the fraction + * is dithered and the remaining fraction is converted to the output Q.15, with clamping + * on the 4 integer guard bits. + * + * For interleaved stereo, c is the number of sample pairs, + * and out is an array of interleaved pairs of 16-bit samples per channel. + * For mono, c is the number of samples / 2, and out is an array of 16-bit samples. + * The name "dither" is a misnomer; the current implementation does not actually dither + * but uses truncation. This may change. + * The out and sums buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void ditherAndClamp(int32_t *out, const int32_t *sums, size_t c); + +/* Expand and copy samples from unsigned 8-bit offset by 0x80 to signed 16-bit. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void memcpy_to_i16_from_u8(int16_t *dst, const uint8_t *src, size_t count); + +/* Shrink and copy samples from signed 16-bit to unsigned 8-bit offset by 0x80. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + * The conversion is done by truncation, without dithering, so it loses resolution. + */ +void memcpy_to_u8_from_i16(uint8_t *dst, const int16_t *src, size_t count); + +/* Copy samples from float to unsigned 8-bit offset by 0x80. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + * The conversion is done by truncation, without dithering, so it loses resolution. + */ +void memcpy_to_u8_from_float(uint8_t *dst, const float *src, size_t count); + +/* Shrink and copy samples from signed 32-bit fixed-point Q0.31 to signed 16-bit Q0.15. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + * The conversion is done by truncation, without dithering, so it loses resolution. + */ +void memcpy_to_i16_from_i32(int16_t *dst, const int32_t *src, size_t count); + +/* Shrink and copy samples from single-precision floating-point to signed 16-bit. + * Each float should be in the range -1.0 to 1.0. Values outside that range are clamped, + * refer to clamp16_from_float(). + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + * The conversion is done by truncation, without dithering, so it loses resolution. + */ +void memcpy_to_i16_from_float(int16_t *dst, const float *src, size_t count); + +/* Copy samples from signed fixed-point 32-bit Q4.27 to single-precision floating-point. + * The nominal output float range is [-1.0, 1.0] if the fixed-point range is + * [0xf8000000, 0x07ffffff]. The full float range is [-16.0, 16.0]. Note the closed range + * at 1.0 and 16.0 is due to rounding on conversion to float. See float_from_q4_27() for details. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void memcpy_to_float_from_q4_27(float *dst, const int32_t *src, size_t count); + +/* Copy samples from signed fixed-point 16 bit Q0.15 to single-precision floating-point. + * The output float range is [-1.0, 1.0) for the fixed-point range [0x8000, 0x7fff]. + * No rounding is needed as the representation is exact. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must be completely separate. + */ +void memcpy_to_float_from_i16(float *dst, const int16_t *src, size_t count); + +/* Copy samples from unsigned fixed-point 8 bit to single-precision floating-point. + * The output float range is [-1.0, 1.0) for the fixed-point range [0x00, 0xFF]. + * No rounding is needed as the representation is exact. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must be completely separate. + */ +void memcpy_to_float_from_u8(float *dst, const uint8_t *src, size_t count); + +/* Copy samples from signed fixed-point packed 24 bit Q0.23 to single-precision floating-point. + * The packed 24 bit input is stored in native endian format in a uint8_t byte array. + * The output float range is [-1.0, 1.0) for the fixed-point range [0x800000, 0x7fffff]. + * No rounding is needed as the representation is exact. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must be completely separate. + */ +void memcpy_to_float_from_p24(float *dst, const uint8_t *src, size_t count); + +/* Copy samples from signed fixed-point packed 24 bit Q0.23 to signed fixed point 16 bit Q0.15. + * The packed 24 bit output is stored in native endian format in a uint8_t byte array. + * The data is truncated without rounding. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void memcpy_to_i16_from_p24(int16_t *dst, const uint8_t *src, size_t count); + +/* Copy samples from signed fixed-point packed 24 bit Q0.23 to signed fixed-point 32-bit Q0.31. + * The packed 24 bit input is stored in native endian format in a uint8_t byte array. + * The output data range is [0x80000000, 0x7fffff00] at intervals of 0x100. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must be completely separate. + */ +void memcpy_to_i32_from_p24(int32_t *dst, const uint8_t *src, size_t count); + +/* Copy samples from signed fixed point 16 bit Q0.15 to signed fixed-point packed 24 bit Q0.23. + * The packed 24 bit output is assumed to be a native-endian uint8_t byte array. + * The output data range is [0x800000, 0x7fff00] (not full). + * Nevertheless there is no DC offset on the output, if the input has no DC offset. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must be completely separate. + */ +void memcpy_to_p24_from_i16(uint8_t *dst, const int16_t *src, size_t count); + +/* Copy samples from single-precision floating-point to signed fixed-point packed 24 bit Q0.23. + * The packed 24 bit output is assumed to be a native-endian uint8_t byte array. + * The data is clamped and rounded to nearest, ties away from zero. See clamp24_from_float() + * for details. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void memcpy_to_p24_from_float(uint8_t *dst, const float *src, size_t count); + +/* Copy samples from signed fixed-point 32-bit Q8.23 to signed fixed-point packed 24 bit Q0.23. + * The packed 24 bit output is assumed to be a native-endian uint8_t byte array. + * The data is clamped to the range is [0x800000, 0x7fffff]. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must be completely separate. + */ +void memcpy_to_p24_from_q8_23(uint8_t *dst, const int32_t *src, size_t count); + +/* Shrink and copy samples from signed 32-bit fixed-point Q0.31 + * to signed fixed-point packed 24 bit Q0.23. + * The packed 24 bit output is assumed to be a native-endian uint8_t byte array. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + * The conversion is done by truncation, without dithering, so it loses resolution. + */ +void memcpy_to_p24_from_i32(uint8_t *dst, const int32_t *src, size_t count); + +/* Copy samples from signed fixed point 16-bit Q0.15 to signed fixed-point 32-bit Q8.23. + * The output data range is [0xff800000, 0x007fff00] at intervals of 0x100. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must be completely separate. + */ +void memcpy_to_q8_23_from_i16(int32_t *dst, const int16_t *src, size_t count); + +/* Copy samples from single-precision floating-point to signed fixed-point 32-bit Q8.23. + * This copy will clamp the Q8.23 representation to [0xff800000, 0x007fffff] even though there + * are guard bits available. Fractional lsb is rounded to nearest, ties away from zero. + * See clamp24_from_float() for details. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void memcpy_to_q8_23_from_float_with_clamp(int32_t *dst, const float *src, size_t count); + +/* Copy samples from signed fixed point packed 24-bit Q0.23 to signed fixed-point 32-bit Q8.23. + * The output data range is [0xff800000, 0x007fffff]. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must be completely separate. + */ +void memcpy_to_q8_23_from_p24(int32_t *dst, const uint8_t *src, size_t count); + +/* Copy samples from single-precision floating-point to signed fixed-point 32-bit Q4.27. + * The conversion will use the full available Q4.27 range, including guard bits. + * Fractional lsb is rounded to nearest, ties away from zero. + * See clampq4_27_from_float() for details. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void memcpy_to_q4_27_from_float(int32_t *dst, const float *src, size_t count); + +/* Copy samples from signed fixed-point 32-bit Q8.23 to signed fixed point 16-bit Q0.15. + * The data is clamped, and truncated without rounding. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void memcpy_to_i16_from_q8_23(int16_t *dst, const int32_t *src, size_t count); + +/* Copy samples from signed fixed-point 32-bit Q8.23 to single-precision floating-point. + * The nominal output float range is [-1.0, 1.0) for the fixed-point + * range [0xff800000, 0x007fffff]. The maximum output float range is [-256.0, 256.0). + * No rounding is needed as the representation is exact for nominal values. + * Rounding for overflow values is to nearest, ties to even. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void memcpy_to_float_from_q8_23(float *dst, const int32_t *src, size_t count); + +/* Copy samples from signed fixed point 16-bit Q0.15 to signed fixed-point 32-bit Q0.31. + * The output data range is [0x80000000, 0x7fff0000] at intervals of 0x10000. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must be completely separate. + */ +void memcpy_to_i32_from_i16(int32_t *dst, const int16_t *src, size_t count); + +/* Copy samples from single-precision floating-point to signed fixed-point 32-bit Q0.31. + * If rounding is needed on truncation, the fractional lsb is rounded to nearest, + * ties away from zero. See clamp32_from_float() for details. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void memcpy_to_i32_from_float(int32_t *dst, const float *src, size_t count); + +/* Copy samples from signed fixed-point 32-bit Q0.31 to single-precision floating-point. + * The float range is [-1.0, 1.0] for the fixed-point range [0x80000000, 0x7fffffff]. + * Rounding is done according to float_from_i32(). + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of samples to copy + * The destination and source buffers must either be completely separate (non-overlapping), or + * they must both start at the same address. Partially overlapping buffers are not supported. + */ +void memcpy_to_float_from_i32(float *dst, const int32_t *src, size_t count); + +/* Downmix pairs of interleaved stereo input 16-bit samples to mono output 16-bit samples. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of stereo frames to downmix + * The destination and source buffers must be completely separate (non-overlapping). + * The current implementation truncates the mean rather than dither, but this may change. + */ +void downmix_to_mono_i16_from_stereo_i16(int16_t *dst, const int16_t *src, size_t count); + +/* Upmix mono input 16-bit samples to pairs of interleaved stereo output 16-bit samples by + * duplicating. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of mono samples to upmix + * The destination and source buffers must be completely separate (non-overlapping). + */ +void upmix_to_stereo_i16_from_mono_i16(int16_t *dst, const int16_t *src, size_t count); + +/* Downmix pairs of interleaved stereo input float samples to mono output float samples + * by averaging the stereo pair together. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of stereo frames to downmix + * The destination and source buffers must be completely separate (non-overlapping), + * or they must both start at the same address. + */ +void downmix_to_mono_float_from_stereo_float(float *dst, const float *src, size_t count); + +/* Upmix mono input float samples to pairs of interleaved stereo output float samples by + * duplicating. + * Parameters: + * dst Destination buffer + * src Source buffer + * count Number of mono samples to upmix + * The destination and source buffers must be completely separate (non-overlapping). + */ +void upmix_to_stereo_float_from_mono_float(float *dst, const float *src, size_t count); + +/* Return the total number of non-zero 32-bit samples */ +size_t nonZeroMono32(const int32_t *samples, size_t count); + +/* Return the total number of non-zero 16-bit samples */ +size_t nonZeroMono16(const int16_t *samples, size_t count); + +/* Return the total number of non-zero stereo frames, where a frame is considered non-zero + * if either of its constituent 32-bit samples is non-zero + */ +size_t nonZeroStereo32(const int32_t *frames, size_t count); + +/* Return the total number of non-zero stereo frames, where a frame is considered non-zero + * if either of its constituent 16-bit samples is non-zero + */ +size_t nonZeroStereo16(const int16_t *frames, size_t count); + +/* Copy frames, selecting source samples based on a source channel mask to fit + * the destination channel mask. Unmatched channels in the destination channel mask + * are zero filled. Unmatched channels in the source channel mask are dropped. + * Channels present in the channel mask are represented by set bits in the + * uint32_t value and are matched without further interpretation. + * Parameters: + * dst Destination buffer + * dst_mask Bit mask corresponding to destination channels present + * src Source buffer + * src_mask Bit mask corresponding to source channels present + * sample_size Size of each sample in bytes. Must be 1, 2, 3, or 4. + * count Number of frames to copy + * The destination and source buffers must be completely separate (non-overlapping). + * If the sample size is not in range, the function will abort. + */ +void memcpy_by_channel_mask(void *dst, uint32_t dst_mask, + const void *src, uint32_t src_mask, size_t sample_size, size_t count); + +/* Copy frames, selecting source samples based on an index array (idxary). + * The idxary[] consists of dst_channels number of elements. + * The ith element if idxary[] corresponds the ith destination channel. + * A non-negative value is the channel index in the source frame. + * A negative index (-1) represents filling with 0. + * + * Example: Swapping L and R channels for stereo streams + * idxary[0] = 1; + * idxary[1] = 0; + * + * Example: Copying a mono source to the front center 5.1 channel + * idxary[0] = -1; + * idxary[1] = -1; + * idxary[2] = 0; + * idxary[3] = -1; + * idxary[4] = -1; + * idxary[5] = -1; + * + * This copy allows swizzling of channels or replication of channels. + * + * Parameters: + * dst Destination buffer + * dst_channels Number of destination channels per frame + * src Source buffer + * src_channels Number of source channels per frame + * idxary Array of indices representing channels in the source frame + * sample_size Size of each sample in bytes. Must be 1, 2, 3, or 4. + * count Number of frames to copy + * The destination and source buffers must be completely separate (non-overlapping). + * If the sample size is not in range, the function will abort. + */ +void memcpy_by_index_array(void *dst, uint32_t dst_channels, + const void *src, uint32_t src_channels, + const int8_t *idxary, size_t sample_size, size_t count); + +/* Prepares an index array (idxary) from channel masks, which can be later + * used by memcpy_by_index_array(). Returns the number of array elements required. + * This may be greater than idxcount, so the return value should be checked + * if idxary size is less than 32. Note that idxary is a caller allocated array + * of at least as many channels as present in the dst_mask. + * Channels present in the channel mask are represented by set bits in the + * uint32_t value and are matched without further interpretation. + * + * This function is typically used for converting audio data with different + * channel position masks. + * + * Parameters: + * idxary Updated array of indices of channels in the src frame for the dst frame + * idxcount Number of caller allocated elements in idxary + * dst_mask Bit mask corresponding to destination channels present + * src_mask Bit mask corresponding to source channels present + */ +size_t memcpy_by_index_array_initialization(int8_t *idxary, size_t idxcount, + uint32_t dst_mask, uint32_t src_mask); + +/* Prepares an index array (idxary) from channel masks, which can be later + * used by memcpy_by_index_array(). Returns the number of array elements required. + * + * For a source channel index mask, the source channels will map to the destination + * channels as if counting the set bits in dst_mask in order from lsb to msb + * (zero bits are ignored). The ith bit of the src_mask corresponds to the + * ith SET bit of dst_mask and the ith destination channel. Hence, a zero ith + * bit of the src_mask indicates that the ith destination channel plays silence. + * + * Parameters: + * idxary Updated array of indices of channels in the src frame for the dst frame + * idxcount Number of caller allocated elements in idxary + * dst_mask Bit mask corresponding to destination channels present + * src_mask Bit mask corresponding to source channels present + */ +size_t memcpy_by_index_array_initialization_src_index(int8_t *idxary, size_t idxcount, + uint32_t dst_mask, uint32_t src_mask); + +/* Prepares an index array (idxary) from channel mask bits, which can be later + * used by memcpy_by_index_array(). Returns the number of array elements required. + * + * This initialization is for a destination channel index mask from a positional + * source mask. + * + * For an destination channel index mask, the input channels will map + * to the destination channels, with the ith SET bit in the source bits corresponding + * to the ith bit in the destination bits. If there is a zero bit in the middle + * of set destination bits (unlikely), the corresponding source channel will + * be dropped. + * + * Parameters: + * idxary Updated array of indices of channels in the src frame for the dst frame + * idxcount Number of caller allocated elements in idxary + * dst_mask Bit mask corresponding to destination channels present + * src_mask Bit mask corresponding to source channels present + */ +size_t memcpy_by_index_array_initialization_dst_index(int8_t *idxary, size_t idxcount, + uint32_t dst_mask, uint32_t src_mask); + +/** + * Clamp (aka hard limit or clip) a signed 32-bit sample to 16-bit range. + */ +static inline int16_t clamp16(int32_t sample) { + if ((sample >> 15) ^ (sample >> 31)) + sample = 0x7FFF ^ (sample >> 31); + return sample; +} + +/* + * Convert a IEEE 754 single precision float [-1.0, 1.0) to int16_t [-32768, 32767] + * with clamping. Note the open bound at 1.0, values within 1/65536 of 1.0 map + * to 32767 instead of 32768 (early clamping due to the smaller positive integer subrange). + * + * Values outside the range [-1.0, 1.0) are properly clamped to -32768 and 32767, + * including -Inf and +Inf. NaN will generally be treated either as -32768 or 32767, + * depending on the sign bit inside NaN (whose representation is not unique). + * Nevertheless, strictly speaking, NaN behavior should be considered undefined. + * + * Rounding of 0.5 lsb is to even (default for IEEE 754). + */ +static inline int16_t clamp16_from_float(float f) { + /* Offset is used to expand the valid range of [-1.0, 1.0) into the 16 lsbs of the + * floating point significand. The normal shift is 3<<22, but the -15 offset + * is used to multiply by 32768. + */ + static const float offset = (float)(3 << (22 - 15)); + /* zero = (0x10f << 22) = 0x43c00000 (not directly used) */ + static const int32_t limneg = (0x10f << 22) /*zero*/ - 32768; /* 0x43bf8000 */ + static const int32_t limpos = (0x10f << 22) /*zero*/ + 32767; /* 0x43c07fff */ + + union { + float f; + int32_t i; + } u; + + u.f = f + offset; /* recenter valid range */ + /* Now the valid range is represented as integers between [limneg, limpos]. + * Clamp using the fact that float representation (as an integer) is an ordered set. + */ + if (u.i < limneg) + u.i = -32768; + else if (u.i > limpos) + u.i = 32767; + return u.i; /* Return lower 16 bits, the part of interest in the significand. */ +} + +/* + * Convert a IEEE 754 single precision float [-1.0, 1.0) to uint8_t [0, 0xff] + * with clamping. Note the open bound at 1.0, values within 1/128 of 1.0 map + * to 255 instead of 256 (early clamping due to the smaller positive integer subrange). + * + * Values outside the range [-1.0, 1.0) are properly clamped to 0 and 255, + * including -Inf and +Inf. NaN will generally be treated either as 0 or 255, + * depending on the sign bit inside NaN (whose representation is not unique). + * Nevertheless, strictly speaking, NaN behavior should be considered undefined. + * + * Rounding of 0.5 lsb is to even (default for IEEE 754). + */ +static inline uint8_t clamp8_from_float(float f) { + /* Offset is used to expand the valid range of [-1.0, 1.0) into the 16 lsbs of the + * floating point significand. The normal shift is 3<<22, but the -7 offset + * is used to multiply by 128. + */ + static const float offset = (float)((3 << (22 - 7)) + 1 /* to cancel -1.0 */); + /* zero = (0x11f << 22) = 0x47c00000 */ + static const int32_t limneg = (0x11f << 22) /*zero*/; + static const int32_t limpos = (0x11f << 22) /*zero*/ + 255; /* 0x47c000ff */ + + union { + float f; + int32_t i; + } u; + + u.f = f + offset; /* recenter valid range */ + /* Now the valid range is represented as integers between [limneg, limpos]. + * Clamp using the fact that float representation (as an integer) is an ordered set. + */ + if (u.i < limneg) + return 0; + if (u.i > limpos) + return 255; + return u.i; /* Return lower 8 bits, the part of interest in the significand. */ +} + +/* Convert a single-precision floating point value to a Q0.23 integer value, stored in a + * 32 bit signed integer (technically stored as Q8.23, but clamped to Q0.23). + * + * Rounds to nearest, ties away from 0. + * + * Values outside the range [-1.0, 1.0) are properly clamped to -8388608 and 8388607, + * including -Inf and +Inf. NaN values are considered undefined, and behavior may change + * depending on hardware and future implementation of this function. + */ +static inline int32_t clamp24_from_float(float f) { + static const float scale = (float)(1 << 23); + static const float limpos = 0x7fffff / (float)(1 << 23); + static const float limneg = -0x800000 / (float)(1 << 23); + + if (f <= limneg) { + return -0x800000; + } else if (f >= limpos) { + return 0x7fffff; + } + f *= scale; + /* integer conversion is through truncation (though int to float is not). + * ensure that we round to nearest, ties away from 0. + */ + return f > 0 ? f + 0.5 : f - 0.5; +} + +/* Convert a signed fixed-point 32-bit Q8.23 value to a Q0.23 integer value, + * stored in a 32-bit signed integer (technically stored as Q8.23, but clamped to Q0.23). + * + * Values outside the range [-0x800000, 0x7fffff] are clamped to that range. + */ +static inline int32_t clamp24_from_q8_23(int32_t ival) { + static const int32_t limpos = 0x7fffff; + static const int32_t limneg = -0x800000; + if (ival < limneg) { + return limneg; + } else if (ival > limpos) { + return limpos; + } else { + return ival; + } +} + +/* Convert a single-precision floating point value to a Q4.27 integer value. + * Rounds to nearest, ties away from 0. + * + * Values outside the range [-16.0, 16.0) are properly clamped to -2147483648 and 2147483647, + * including -Inf and +Inf. NaN values are considered undefined, and behavior may change + * depending on hardware and future implementation of this function. + */ +static inline int32_t clampq4_27_from_float(float f) { + static const float scale = (float)(1UL << 27); + static const float limpos = 16.; + static const float limneg = -16.; + + if (f <= limneg) { + return INT32_MIN; /* or 0x80000000 */ + } else if (f >= limpos) { + return INT32_MAX; + } + f *= scale; + /* integer conversion is through truncation (though int to float is not). + * ensure that we round to nearest, ties away from 0. + */ + return f > 0 ? f + 0.5 : f - 0.5; +} + +/* Convert a single-precision floating point value to a Q0.31 integer value. + * Rounds to nearest, ties away from 0. + * + * Values outside the range [-1.0, 1.0) are properly clamped to -2147483648 and 2147483647, + * including -Inf and +Inf. NaN values are considered undefined, and behavior may change + * depending on hardware and future implementation of this function. + */ +static inline int32_t clamp32_from_float(float f) { + static const float scale = (float)(1UL << 31); + static const float limpos = 1.; + static const float limneg = -1.; + + if (f <= limneg) { + return INT32_MIN; /* or 0x80000000 */ + } else if (f >= limpos) { + return INT32_MAX; + } + f *= scale; + /* integer conversion is through truncation (though int to float is not). + * ensure that we round to nearest, ties away from 0. + */ + return f > 0 ? f + 0.5 : f - 0.5; +} + +/* Convert a signed fixed-point 32-bit Q4.27 value to single-precision floating-point. + * The nominal output float range is [-1.0, 1.0] if the fixed-point range is + * [0xf8000000, 0x07ffffff]. The full float range is [-16.0, 16.0]. + * + * Note the closed range at 1.0 and 16.0 is due to rounding on conversion to float. + * In more detail: if the fixed-point integer exceeds 24 bit significand of single + * precision floating point, the 0.5 lsb in the significand conversion will round + * towards even, as per IEEE 754 default. + */ +static inline float float_from_q4_27(int32_t ival) { + /* The scale factor is the reciprocal of the fractional bits. + * + * Since the scale factor is a power of 2, the scaling is exact, and there + * is no rounding due to the multiplication - the bit pattern is preserved. + * However, there may be rounding due to the fixed-point to float conversion, + * as described above. + */ + static const float scale = 1. / (float)(1UL << 27); + + return ival * scale; +} + +/* Convert an unsigned fixed-point 32-bit U4.28 value to single-precision floating-point. + * The nominal output float range is [0.0, 1.0] if the fixed-point range is + * [0x00000000, 0x10000000]. The full float range is [0.0, 16.0]. + * + * Note the closed range at 1.0 and 16.0 is due to rounding on conversion to float. + * In more detail: if the fixed-point integer exceeds 24 bit significand of single + * precision floating point, the 0.5 lsb in the significand conversion will round + * towards even, as per IEEE 754 default. + */ +static inline float float_from_u4_28(uint32_t uval) { + static const float scale = 1. / (float)(1UL << 28); + + return uval * scale; +} + +/* Convert an unsigned fixed-point 16-bit U4.12 value to single-precision floating-point. + * The nominal output float range is [0.0, 1.0] if the fixed-point range is + * [0x0000, 0x1000]. The full float range is [0.0, 16.0). + */ +static inline float float_from_u4_12(uint16_t uval) { + static const float scale = 1. / (float)(1UL << 12); + + return uval * scale; +} + +/* Convert a single-precision floating point value to a U4.28 integer value. + * Rounds to nearest, ties away from 0. + * + * Values outside the range [0, 16.0] are properly clamped to [0, 4294967295] + * including -Inf and +Inf. NaN values are considered undefined, and behavior may change + * depending on hardware and future implementation of this function. + */ +static inline uint32_t u4_28_from_float(float f) { + static const float scale = (float)(1 << 28); + static const float limpos = 16.0f; + + if (f <= 0.) { + return 0; + } else if (f >= limpos) { + // return 0xffffffff; + return UINT32_MAX; + } + /* integer conversion is through truncation (though int to float is not). + * ensure that we round to nearest, ties away from 0. + */ + return f * scale + 0.5; +} + +/* Convert a single-precision floating point value to a U4.12 integer value. + * Rounds to nearest, ties away from 0. + * + * Values outside the range [0, 16.0) are properly clamped to [0, 65535] + * including -Inf and +Inf. NaN values are considered undefined, and behavior may change + * depending on hardware and future implementation of this function. + */ +static inline uint16_t u4_12_from_float(float f) { + static const float scale = (float)(1 << 12); + static const float limpos = 0xffff / (float)(1 << 12); + + if (f <= 0.) { + return 0; + } else if (f >= limpos) { + // return 0xffff; + return UINT16_MAX; + } + /* integer conversion is through truncation (though int to float is not). + * ensure that we round to nearest, ties away from 0. + */ + return f * scale + 0.5; +} + +/* Convert a signed fixed-point 16-bit Q0.15 value to single-precision floating-point. + * The output float range is [-1.0, 1.0) for the fixed-point range + * [0x8000, 0x7fff]. + * + * There is no rounding, the conversion and representation is exact. + */ +static inline float float_from_i16(int16_t ival) { + /* The scale factor is the reciprocal of the nominal 16 bit integer + * half-sided range (32768). + * + * Since the scale factor is a power of 2, the scaling is exact, and there + * is no rounding due to the multiplication - the bit pattern is preserved. + */ + static const float scale = 1. / (float)(1UL << 15); + + return ival * scale; +} + +/* Convert an unsigned fixed-point 8-bit U0.8 value to single-precision floating-point. + * The nominal output float range is [-1.0, 1.0) if the fixed-point range is + * [0x00, 0xff]. + */ +static inline float float_from_u8(uint8_t uval) { + static const float scale = 1. / (float)(1UL << 7); + + return ((int)uval - 128) * scale; +} + +/* Convert a packed 24bit Q0.23 value stored native-endian in a uint8_t ptr + * to a signed fixed-point 32 bit integer Q0.31 value. The output Q0.31 range + * is [0x80000000, 0x7fffff00] for the fixed-point range [0x800000, 0x7fffff]. + * Even though the output range is limited on the positive side, there is no + * DC offset on the output, if the input has no DC offset. + * + * Avoid relying on the limited output range, as future implementations may go + * to full range. + */ +static inline int32_t i32_from_p24(const uint8_t *packed24) { + /* convert to 32b */ + return (packed24[0] << 8) | (packed24[1] << 16) | (packed24[2] << 24); +} + +/* Convert a 32-bit Q0.31 value to single-precision floating-point. + * The output float range is [-1.0, 1.0] for the fixed-point range + * [0x80000000, 0x7fffffff]. + * + * Rounding may occur in the least significant 8 bits for large fixed point + * values due to storage into the 24-bit floating-point significand. + * Rounding will be to nearest, ties to even. + */ +static inline float float_from_i32(int32_t ival) { + static const float scale = 1. / (float)(1UL << 31); + + return ival * scale; +} + +/* Convert a packed 24bit Q0.23 value stored native endian in a uint8_t ptr + * to single-precision floating-point. The output float range is [-1.0, 1.0) + * for the fixed-point range [0x800000, 0x7fffff]. + * + * There is no rounding, the conversion and representation is exact. + */ +static inline float float_from_p24(const uint8_t *packed24) { + return float_from_i32(i32_from_p24(packed24)); +} + +/* Convert a 24-bit Q8.23 value to single-precision floating-point. + * The nominal output float range is [-1.0, 1.0) for the fixed-point + * range [0xff800000, 0x007fffff]. The maximum float range is [-256.0, 256.0). + * + * There is no rounding in the nominal range, the conversion and representation + * is exact. For values outside the nominal range, rounding is to nearest, ties to even. + */ +static inline float float_from_q8_23(int32_t ival) { + static const float scale = 1. / (float)(1UL << 23); + + return ival * scale; +} + +/** + * Multiply-accumulate 16-bit terms with 32-bit result: return a + in*v. + */ +static inline int32_t mulAdd(int16_t in, int16_t v, int32_t a) { +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + asm("smlabb %[out], %[in], %[v], %[a] \n" + : [out] "=r"(out) + : [in] "%r"(in), [v] "r"(v), [a] "r"(a) + :); + return out; +#else + return a + in * (int32_t)v; +#endif +} + +/** + * Multiply 16-bit terms with 32-bit result: return in*v. + */ +static inline int32_t mul(int16_t in, int16_t v) { +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + asm("smulbb %[out], %[in], %[v] \n" + : [out] "=r"(out) + : [in] "%r"(in), [v] "r"(v) + :); + return out; +#else + return in * (int32_t)v; +#endif +} + +/** + * Similar to mulAdd, but the 16-bit terms are extracted from a 32-bit interleaved stereo pair. + */ +static inline int32_t mulAddRL(int left, uint32_t inRL, uint32_t vRL, int32_t a) { +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + if (left) { + asm("smlabb %[out], %[inRL], %[vRL], %[a] \n" + : [out] "=r"(out) + : [inRL] "%r"(inRL), [vRL] "r"(vRL), [a] "r"(a) + :); + } else { + asm("smlatt %[out], %[inRL], %[vRL], %[a] \n" + : [out] "=r"(out) + : [inRL] "%r"(inRL), [vRL] "r"(vRL), [a] "r"(a) + :); + } + return out; +#else + if (left) { + return a + (int16_t)(inRL & 0xFFFF) * (int16_t)(vRL & 0xFFFF); + } + return a + (int16_t)(inRL >> 16) * (int16_t)(vRL >> 16); + +#endif +} + +/** + * Similar to mul, but the 16-bit terms are extracted from a 32-bit interleaved stereo pair. + */ +static inline int32_t mulRL(int left, uint32_t inRL, uint32_t vRL) { +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + if (left) { + asm("smulbb %[out], %[inRL], %[vRL] \n" + : [out] "=r"(out) + : [inRL] "%r"(inRL), [vRL] "r"(vRL) + :); + } else { + asm("smultt %[out], %[inRL], %[vRL] \n" + : [out] "=r"(out) + : [inRL] "%r"(inRL), [vRL] "r"(vRL) + :); + } + return out; +#else + if (left) { + return (int16_t)(inRL & 0xFFFF) * (int16_t)(vRL & 0xFFFF); + } + return (int16_t)(inRL >> 16) * (int16_t)(vRL >> 16); + +#endif +} diff --git a/cocos/audio/common/utils/include/tinysndfile.h b/cocos/audio/common/utils/include/tinysndfile.h new file mode 100644 index 0000000..786302c --- /dev/null +++ b/cocos/audio/common/utils/include/tinysndfile.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// This is a C library for reading and writing PCM .wav files. It is +// influenced by other libraries such as libsndfile and audiofile, except is +// much smaller and has an Apache 2.0 license. +// The API should be familiar to clients of similar libraries, but there is +// no guarantee that it will stay exactly source-code compatible with other libraries. +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include +#elif CC_PLATFORM == CC_PLATFORM_WINDOWS + #include +#elif CC_PLATFORM == CC_PLATFORM_OPENHARMONY + #include +#endif +#include +#include + +namespace sf { + +// visible to clients +using sf_count_t = int; + +struct SF_INFO { + sf_count_t frames; + int samplerate; + int channels; + int format; +}; + +// opaque to clients +using SNDFILE = struct SNDFILE_; + +// Format +#define SF_FORMAT_TYPEMASK 1 +#define SF_FORMAT_WAV 1 +#define SF_FORMAT_SUBMASK 14 +#define SF_FORMAT_PCM_16 2 +#define SF_FORMAT_PCM_U8 4 +#define SF_FORMAT_FLOAT 6 +#define SF_FORMAT_PCM_32 8 +#define SF_FORMAT_PCM_24 10 + +struct snd_callbacks { + void *(*open)(const char *path, void *user); + size_t (*read)(void *ptr, size_t size, size_t nmemb, void *datasource); + int (*seek)(void *datasource, long offset, int whence); //NOLINT(google-runtime-int) + int (*close)(void *datasource); + long (*tell)(void *datasource); //NOLINT(google-runtime-int) +}; + +// Open stream +SNDFILE *sf_open_read(const char *path, SF_INFO *info, snd_callbacks *cb, void *user); //NOLINT(readability-identifier-naming) + +// Close stream +void sf_close(SNDFILE *handle); //NOLINT(readability-identifier-naming) + +// Read interleaved frames and return actual number of frames read +sf_count_t sf_readf_short(SNDFILE *handle, int16_t *ptr, sf_count_t desired); //NOLINT(readability-identifier-naming) +/* +sf_count_t sf_readf_float(SNDFILE *handle, float *ptr, sf_count_t desired); +sf_count_t sf_readf_int(SNDFILE *handle, int *ptr, sf_count_t desired); +*/ + +off_t sf_seek(SNDFILE *handle, int offset, int whence); //NOLINT(readability-identifier-naming) +off_t sf_tell(SNDFILE *handle); //NOLINT(readability-identifier-naming) +static int sInited = 0; +static void sf_lazy_init(); //NOLINT(readability-identifier-naming) +struct SNDFILE_ { + uint8_t *temp; // realloc buffer used for shrinking 16 bits to 8 bits and byte-swapping + void *stream; + size_t bytesPerFrame; + size_t remaining; // frames unread for SFM_READ, frames written for SFM_WRITE + SF_INFO info; + snd_callbacks callback; +}; +} // namespace sf diff --git a/cocos/audio/common/utils/minifloat.cpp b/cocos/audio/common/utils/minifloat.cpp new file mode 100644 index 0000000..4462569 --- /dev/null +++ b/cocos/audio/common/utils/minifloat.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "audio/common/utils/include/minifloat.h" +#include + +#define EXPONENT_BITS 3 +#define EXPONENT_MAX ((1 << EXPONENT_BITS) - 1) +#define EXCESS ((1 << EXPONENT_BITS) - 2) + +#define MANTISSA_BITS 13 +#define MANTISSA_MAX ((1 << MANTISSA_BITS) - 1) +#define HIDDEN_BIT (1 << MANTISSA_BITS) +#define ONE_FLOAT ((float)(1 << (MANTISSA_BITS + 1))) + +#define MINIFLOAT_MAX ((EXPONENT_MAX << MANTISSA_BITS) | MANTISSA_MAX) + +#if EXPONENT_BITS + MANTISSA_BITS != 16 + #error EXPONENT_BITS and MANTISSA_BITS must sum to 16 +#endif + +gain_minifloat_t gain_from_float(float v) { + if (std::isnan(v) || v <= 0.0f) { + return 0; + } + if (v >= 2.0f) { + return MINIFLOAT_MAX; + } + int exp; + float r = frexpf(v, &exp); + if ((exp += EXCESS) > EXPONENT_MAX) { + return MINIFLOAT_MAX; + } + if (-exp >= MANTISSA_BITS) { + return 0; + } + int mantissa = (int)(r * ONE_FLOAT); + return exp > 0 ? (exp << MANTISSA_BITS) | (mantissa & ~HIDDEN_BIT) : (mantissa >> (1 - exp)) & MANTISSA_MAX; +} + +float float_from_gain(gain_minifloat_t a) { + int mantissa = a & MANTISSA_MAX; + int exponent = (a >> MANTISSA_BITS) & EXPONENT_MAX; + return ldexpf((exponent > 0 ? HIDDEN_BIT | mantissa : mantissa << 1) / ONE_FLOAT, + exponent - EXCESS); +} diff --git a/cocos/audio/common/utils/primitives.cpp b/cocos/audio/common/utils/primitives.cpp new file mode 100644 index 0000000..407e6bb --- /dev/null +++ b/cocos/audio/common/utils/primitives.cpp @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "audio/common/utils/include/primitives.h" +#include "audio/common/utils/private/private.h" +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include "audio/android/cutils/bitops.h" /* for popcount() */ +#else + #include "base/Utils.h" +using namespace cc::utils; +#endif + +// namespace { +void ditherAndClamp(int32_t *out, const int32_t *sums, size_t c) { + size_t i; + for (i = 0; i < c; i++) { + int32_t l = *sums++; + int32_t r = *sums++; + int32_t nl = l >> 12; + int32_t nr = r >> 12; + l = clamp16(nl); + r = clamp16(nr); + *out++ = (r << 16) | (l & 0xFFFF); + } +} + +void memcpy_to_i16_from_u8(int16_t *dst, const uint8_t *src, size_t count) { + dst += count; + src += count; + while (count--) { + *--dst = static_cast(*--src - 0x80) << 8; + } +} + +void memcpy_to_u8_from_i16(uint8_t *dst, const int16_t *src, size_t count) { + while (count--) { + *dst++ = (*src++ >> 8) + 0x80; + } +} + +void memcpy_to_u8_from_float(uint8_t *dst, const float *src, size_t count) { + while (count--) { + *dst++ = clamp8_from_float(*src++); + } +} + +void memcpy_to_i16_from_i32(int16_t *dst, const int32_t *src, size_t count) { + while (count--) { + *dst++ = *src++ >> 16; + } +} + +void memcpy_to_i16_from_float(int16_t *dst, const float *src, size_t count) { + while (count--) { + *dst++ = clamp16_from_float(*src++); + } +} + +void memcpy_to_float_from_q4_27(float *dst, const int32_t *src, size_t count) { + while (count--) { + *dst++ = float_from_q4_27(*src++); + } +} + +void memcpy_to_float_from_i16(float *dst, const int16_t *src, size_t count) { + while (count--) { + *dst++ = float_from_i16(*src++); + } +} + +void memcpy_to_float_from_u8(float *dst, const uint8_t *src, size_t count) { + while (count--) { + *dst++ = float_from_u8(*src++); + } +} + +void memcpy_to_float_from_p24(float *dst, const uint8_t *src, size_t count) { + while (count--) { + *dst++ = float_from_p24(src); + src += 3; + } +} + +void memcpy_to_i16_from_p24(int16_t *dst, const uint8_t *src, size_t count) { + while (count--) { +#ifdef HAVE_BIG_ENDIAN + *dst++ = src[1] | (src[0] << 8); +#else + *dst++ = src[1] | (src[2] << 8); +#endif + src += 3; + } +} + +void memcpy_to_i32_from_p24(int32_t *dst, const uint8_t *src, size_t count) { + while (count--) { +#ifdef HAVE_BIG_ENDIAN + *dst++ = (src[2] << 8) | (src[1] << 16) | (src[0] << 24); +#else + *dst++ = (src[0] << 8) | (src[1] << 16) | (src[2] << 24); +#endif + src += 3; + } +} + +void memcpy_to_p24_from_i16(uint8_t *dst, const int16_t *src, size_t count) { + while (count--) { +#ifdef HAVE_BIG_ENDIAN + *dst++ = *src >> 8; + *dst++ = *src++; + *dst++ = 0; +#else + *dst++ = 0; + *dst++ = *src; + *dst++ = *src++ >> 8; +#endif + } +} + +void memcpy_to_p24_from_float(uint8_t *dst, const float *src, size_t count) { + while (count--) { + int32_t ival = clamp24_from_float(*src++); + +#ifdef HAVE_BIG_ENDIAN + *dst++ = ival >> 16; + *dst++ = ival >> 8; + *dst++ = ival; +#else + *dst++ = ival; + *dst++ = ival >> 8; + *dst++ = ival >> 16; +#endif + } +} + +void memcpy_to_p24_from_q8_23(uint8_t *dst, const int32_t *src, size_t count) { + while (count--) { + int32_t ival = clamp24_from_q8_23(*src++); + +#ifdef HAVE_BIG_ENDIAN + *dst++ = ival >> 16; + *dst++ = ival >> 8; + *dst++ = ival; +#else + *dst++ = ival; + *dst++ = ival >> 8; + *dst++ = ival >> 16; +#endif + } +} + +void memcpy_to_p24_from_i32(uint8_t *dst, const int32_t *src, size_t count) { + while (count--) { + int32_t ival = *src++ >> 8; + +#ifdef HAVE_BIG_ENDIAN + *dst++ = ival >> 16; + *dst++ = ival >> 8; + *dst++ = ival; +#else + *dst++ = ival; + *dst++ = ival >> 8; + *dst++ = ival >> 16; +#endif + } +} + +void memcpy_to_q8_23_from_i16(int32_t *dst, const int16_t *src, size_t count) { + while (count--) { + *dst++ = static_cast(*src++) << 8; + } +} + +void memcpy_to_q8_23_from_float_with_clamp(int32_t *dst, const float *src, size_t count) { + while (count--) { + *dst++ = clamp24_from_float(*src++); + } +} + +void memcpy_to_q8_23_from_p24(int32_t *dst, const uint8_t *src, size_t count) { + while (count--) { +#ifdef HAVE_BIG_ENDIAN + *dst++ = (int8_t)src[0] << 16 | src[1] << 8 | src[2]; +#else + *dst++ = static_cast(src[2]) << 16 | src[1] << 8 | src[0]; +#endif + src += 3; + } +} + +void memcpy_to_q4_27_from_float(int32_t *dst, const float *src, size_t count) { + while (count--) { + *dst++ = clampq4_27_from_float(*src++); + } +} + +void memcpy_to_i16_from_q8_23(int16_t *dst, const int32_t *src, size_t count) { + while (count--) { + *dst++ = clamp16(*src++ >> 8); + } +} + +void memcpy_to_float_from_q8_23(float *dst, const int32_t *src, size_t count) { + while (count--) { + *dst++ = float_from_q8_23(*src++); + } +} + +void memcpy_to_i32_from_i16(int32_t *dst, const int16_t *src, size_t count) { + while (count--) { + *dst++ = static_cast(*src++) << 16; + } +} + +void memcpy_to_i32_from_float(int32_t *dst, const float *src, size_t count) { + while (count--) { + *dst++ = clamp32_from_float(*src++); + } +} + +void memcpy_to_float_from_i32(float *dst, const int32_t *src, size_t count) { + while (count--) { + *dst++ = float_from_i32(*src++); + } +} + +void downmix_to_mono_i16_from_stereo_i16(int16_t *dst, const int16_t *src, size_t count) { + while (count--) { + *dst++ = static_cast((static_cast(src[0]) + static_cast(src[1])) >> 1); + src += 2; + } +} + +void upmix_to_stereo_i16_from_mono_i16(int16_t *dst, const int16_t *src, size_t count) { + while (count--) { + int32_t temp = *src++; + dst[0] = temp; + dst[1] = temp; + dst += 2; + } +} + +void downmix_to_mono_float_from_stereo_float(float *dst, const float *src, size_t frames) { + while (frames--) { + *dst++ = (src[0] + src[1]) * 0.5; + src += 2; + } +} + +void upmix_to_stereo_float_from_mono_float(float *dst, const float *src, size_t frames) { + while (frames--) { + float temp = *src++; + dst[0] = temp; + dst[1] = temp; + dst += 2; + } +} + +size_t nonZeroMono32(const int32_t *samples, size_t count) { + size_t nonZero = 0; + while (count-- > 0) { + if (*samples++ != 0) { + nonZero++; + } + } + return nonZero; +} + +size_t nonZeroMono16(const int16_t *samples, size_t count) { + size_t nonZero = 0; + while (count-- > 0) { + if (*samples++ != 0) { + nonZero++; + } + } + return nonZero; +} + +size_t nonZeroStereo32(const int32_t *frames, size_t count) { + size_t nonZero = 0; + while (count-- > 0) { + if (frames[0] != 0 || frames[1] != 0) { + nonZero++; + } + frames += 2; + } + return nonZero; +} + +size_t nonZeroStereo16(const int16_t *frames, size_t count) { + size_t nonZero = 0; + while (count-- > 0) { + if (frames[0] != 0 || frames[1] != 0) { + nonZero++; + } + frames += 2; + } + return nonZero; +} + +/* + * C macro to do channel mask copying independent of dst/src sample type. + * Don't pass in any expressions for the macro arguments here. + */ +#define COPY_FRAME_BY_MASK(dst, dmask, src, smask, count, zero) \ + { \ + int32_t bit, ormask; \ + while ((count)--) { \ + ormask = (dmask) | (smask); \ + while (ormask) { \ + bit = ormask & -ormask; /* get lowest bit */ \ + ormask ^= bit; /* remove lowest bit */ \ + if ((dmask)&bit) { \ + *(dst)++ = (smask)&bit ? *(src)++ : (zero); \ + } else { /* source channel only */ \ + ++(src); \ + } \ + } \ + } \ + } + +void memcpy_by_channel_mask(void *dst, uint32_t dstMask, + const void *src, uint32_t srcMask, size_t sampleSize, size_t count) { +#if 0 + /* alternate way of handling memcpy_by_channel_mask by using the idxary */ + int8_t idxary[32]; + uint32_t src_channels = popcount(src_mask); + uint32_t dst_channels = + memcpy_by_index_array_initialization(idxary, 32, dst_mask, src_mask); + + memcpy_by_idxary(dst, dst_channels, src, src_channels, idxary, sample_size, count); +#else + if (dstMask == srcMask) { + memcpy(dst, src, sampleSize * popcount(dstMask) * count); + return; + } + switch (sampleSize) { + case 1: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_MASK(udst, dstMask, usrc, srcMask, count, 0); + } break; + case 2: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_MASK(udst, dstMask, usrc, srcMask, count, 0); + } break; + case 3: { /* could be slow. use a struct to represent 3 bytes of data. */ + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + static const uint8x3_t ZERO{0, 0, 0}; /* tricky - we use this to zero out a sample */ + + COPY_FRAME_BY_MASK(udst, dstMask, usrc, srcMask, count, ZERO); + } break; + case 4: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_MASK(udst, dstMask, usrc, srcMask, count, 0); + } break; + default: + abort(); /* illegal value */ + break; + } +#endif +} + +/* + * C macro to do copying by index array, to rearrange samples + * within a frame. This is independent of src/dst sample type. + * Don't pass in any expressions for the macro arguments here. + */ +#define COPY_FRAME_BY_IDX(dst, dst_channels, src, src_channels, idxary, count, zero) \ + { \ + unsigned i; \ + int index; \ + while ((count)--) { \ + for (i = 0; i < (dst_channels); ++i) { \ + index = (idxary)[i]; \ + *(dst)++ = index < 0 ? (zero) : (src)[index]; \ + } \ + (src) += (src_channels); \ + } \ + } + +void memcpy_by_index_array(void *dst, uint32_t dstChannels, + const void *src, uint32_t srcChannels, + const int8_t *idxary, size_t sampleSize, size_t count) { + switch (sampleSize) { + case 1: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_IDX(udst, dstChannels, usrc, srcChannels, idxary, count, 0); // NOLINT + } break; + case 2: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_IDX(udst, dstChannels, usrc, srcChannels, idxary, count, 0); // NOLINT + } break; + case 3: { /* could be slow. use a struct to represent 3 bytes of data. */ + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + static const uint8x3_t ZERO{0, 0, 0}; + + COPY_FRAME_BY_IDX(udst, dstChannels, usrc, srcChannels, idxary, count, ZERO); + } break; + case 4: { + auto *udst = static_cast(dst); + const auto *usrc = static_cast(src); + + COPY_FRAME_BY_IDX(udst, dstChannels, usrc, srcChannels, idxary, count, 0); + } break; + default: + abort(); /* illegal value */ + break; + } +} + +size_t memcpy_by_index_array_initialization(int8_t *idxary, size_t idxcount, + uint32_t dstMask, uint32_t srcMask) { + size_t n = 0; + int srcidx = 0; + int32_t bit; + int32_t ormask = srcMask | dstMask; + + while (ormask && n < idxcount) { + bit = ormask & -ormask; /* get lowest bit */ + ormask ^= bit; /* remove lowest bit */ + if (srcMask & dstMask & bit) { /* matching channel */ + idxary[n++] = srcidx++; + } else if (srcMask & bit) { /* source channel only */ + ++srcidx; + } else { /* destination channel only */ + idxary[n++] = -1; + } + } + return n + popcount(ormask & dstMask); +} + +size_t memcpy_by_index_array_initialization_src_index(int8_t *idxary, size_t idxcount, + uint32_t dstMask, uint32_t srcMask) { + size_t dstCount = popcount(dstMask); + if (idxcount == 0) { + return dstCount; + } + if (dstCount > idxcount) { + dstCount = idxcount; + } + + size_t srcIdx; + size_t dstIdx; + for (srcIdx = 0, dstIdx = 0; dstIdx < dstCount; ++dstIdx) { + if (srcMask & 1) { + idxary[dstIdx] = srcIdx++; + } else { + idxary[dstIdx] = -1; + } + srcMask >>= 1; + } + return dstIdx; +} + +size_t memcpy_by_index_array_initialization_dst_index(int8_t *idxary, size_t idxcount, + uint32_t dstMask, uint32_t srcMask) { + size_t srcIdx; + size_t dstIdx; + size_t dstCount = popcount(dstMask); + size_t srcCount = popcount(srcMask); + if (idxcount == 0) { + return dstCount; + } + if (dstCount > idxcount) { + dstCount = idxcount; + } + for (srcIdx = 0, dstIdx = 0; dstIdx < dstCount; ++srcIdx) { + if (dstMask & 1) { + idxary[dstIdx++] = srcIdx < srcCount ? static_cast(srcIdx) : -1; + } + dstMask >>= 1; + } + return dstIdx; +} +//} // namespace diff --git a/cocos/audio/common/utils/private/private.h b/cocos/audio/common/utils/private/private.h new file mode 100644 index 0000000..5efd7c0 --- /dev/null +++ b/cocos/audio/common/utils/private/private.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_AUDIO_PRIVATE_H +#define ANDROID_AUDIO_PRIVATE_H +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include +#elif CC_PLATFORM == CC_PLATFORM_WINDOWS + #include + #define __attribute__(x) +#endif +#include +/* Defines not necessary for external use but kept here to be common + * to the audio_utils library. + */ + +/* struct representation of 3 bytes for packed PCM 24 bit data. + * The naming follows the ARM NEON convention. + */ +extern "C" { +typedef struct __attribute__((__packed__)) { + uint8_t c[3]; +} uint8x3_t; +} +#endif /*ANDROID_AUDIO_PRIVATE_H*/ diff --git a/cocos/audio/common/utils/tinysndfile.cpp b/cocos/audio/common/utils/tinysndfile.cpp new file mode 100644 index 0000000..ae302c6 --- /dev/null +++ b/cocos/audio/common/utils/tinysndfile.cpp @@ -0,0 +1,522 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "tinysndfile" + +#include "audio/common/utils/include/tinysndfile.h" +#include "audio/common/utils/include/primitives.h" + +#include "base/Log.h" + +// #ifdef HAVE_STDERR +// #include +// #endif + +#include +#include +#include + +#ifndef HAVE_STDERR + #define HAVE_STDERR +#endif + +#define WAVE_FORMAT_PCM 1 +#define WAVE_FORMAT_IEEE_FLOAT 3 +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE + +namespace sf { + +static snd_callbacks sDefaultCallback; + +static unsigned little2u(unsigned char *ptr) { + return (ptr[1] << 8) + ptr[0]; +} + +static unsigned little4u(unsigned char *ptr) { + return (ptr[3] << 24) + (ptr[2] << 16) + (ptr[1] << 8) + ptr[0]; +} + +static int isLittleEndian() { + static const uint16_t ONE = 1; + return *(reinterpret_cast(&ONE)) == 1; +} + +// "swab" conflicts with OS X +static void my_swab(int16_t *ptr, size_t numToSwap) { //NOLINT(readability-identifier-naming) + while (numToSwap > 0) { + *ptr = static_cast(little2u(reinterpret_cast(ptr))); + --numToSwap; + ++ptr; + } +} + +static void *open_func(const char *path, void * /*user*/) { //NOLINT(readability-identifier-naming) + return fopen(path, "rb"); +} + +static size_t read_func(void *ptr, size_t size, size_t nmemb, void *datasource) { //NOLINT(readability-identifier-naming) + return fread(ptr, size, nmemb, static_cast(datasource)); +} + +static int seek_func(void *datasource, long offset, int whence) { //NOLINT(google-runtime-int,readability-identifier-naming) + return fseek(static_cast(datasource), offset, whence); +} + +static int close_func(void *datasource) { //NOLINT(readability-identifier-naming) + return fclose(static_cast(datasource)); +} + +static long tell_func(void *datasource) { //NOLINT(google-runtime-int,readability-identifier-naming) + return ftell(static_cast(datasource)); +} + +static void sf_lazy_init() { //NOLINT(readability-identifier-naming) + if (sInited == 0) { + sDefaultCallback.open = open_func; + sDefaultCallback.read = read_func; + sDefaultCallback.seek = seek_func; + sDefaultCallback.close = close_func; + sDefaultCallback.tell = tell_func; + sInited = 1; + } +} + +SNDFILE *sf_open_read(const char *path, SF_INFO *info, snd_callbacks *cb, void *user) { //NOLINT(readability-identifier-naming) + sf_lazy_init(); + + if (path == nullptr || info == nullptr) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("path=%p info=%p\n", path, info); +#endif + return nullptr; + } + + auto *handle = static_cast(malloc(sizeof(SNDFILE))); + handle->temp = nullptr; + + handle->info.format = SF_FORMAT_WAV; + if (cb != nullptr) { + handle->callback = *cb; + } else { + handle->callback = sDefaultCallback; + } + + void *stream = handle->callback.open(path, user); + if (stream == nullptr) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("fopen %s failed errno %d\n", path, errno); +#endif + free(handle); + return nullptr; + } + handle->stream = stream; + + // don't attempt to parse all valid forms, just the most common ones + unsigned char wav[12]; + size_t actual; + unsigned riffSize; + size_t remaining; + int hadFmt = 0; + int hadData = 0; + long dataTell = 0L; //NOLINT(google-runtime-int) + + actual = handle->callback.read(wav, sizeof(char), sizeof(wav), stream); + if (actual < 12) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("actual %zu < 44\n", actual); +#endif + goto close; + } + if (memcmp(wav, "RIFF", 4)) { //NOLINT(bugprone-suspicious-string-compare) +#ifdef HAVE_STDERR + CC_LOG_ERROR("wav != RIFF\n"); +#endif + goto close; + } + riffSize = little4u(&wav[4]); + if (riffSize < 4) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("riffSize %u < 4\n", riffSize); +#endif + goto close; + } + if (memcmp(&wav[8], "WAVE", 4)) { //NOLINT(bugprone-suspicious-string-compare) +#ifdef HAVE_STDERR + CC_LOG_ERROR("missing WAVE\n"); +#endif + goto close; + } + remaining = riffSize - 4; + + while (remaining >= 8) { + unsigned char chunk[8]; + actual = handle->callback.read(chunk, sizeof(char), sizeof(chunk), stream); + if (actual != sizeof(chunk)) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("actual %zu != %zu\n", actual, sizeof(chunk)); +#endif + goto close; + } + remaining -= 8; + unsigned chunkSize = little4u(&chunk[4]); + if (chunkSize > remaining) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("chunkSize %u > remaining %zu\n", chunkSize, remaining); +#endif + goto close; + } + if (!memcmp(&chunk[0], "fmt ", 4)) { + if (hadFmt) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("multiple fmt\n"); +#endif + goto close; + } + if (chunkSize < 2) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("chunkSize %u < 2\n", chunkSize); +#endif + goto close; + } + unsigned char fmt[40]; + actual = handle->callback.read(fmt, sizeof(char), 2, stream); + if (actual != 2) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("actual %zu != 2\n", actual); +#endif + goto close; + } + unsigned format = little2u(&fmt[0]); + size_t minSize = 0; + switch (format) { + case WAVE_FORMAT_PCM: + case WAVE_FORMAT_IEEE_FLOAT: + minSize = 16; + break; + case WAVE_FORMAT_EXTENSIBLE: + minSize = 40; + break; + default: +#ifdef HAVE_STDERR + CC_LOG_ERROR("unsupported format %u\n", format); +#endif + goto close; + } + if (chunkSize < minSize) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("chunkSize %u < minSize %zu\n", chunkSize, minSize); +#endif + goto close; + } + actual = handle->callback.read(&fmt[2], sizeof(char), minSize - 2, stream); + if (actual != minSize - 2) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("actual %zu != %zu\n", actual, minSize - 16); +#endif + goto close; + } + if (chunkSize > minSize) { + handle->callback.seek(stream, static_cast(chunkSize - minSize), SEEK_CUR); //NOLINT(google-runtime-int) + } + unsigned channels = little2u(&fmt[2]); + // IDEA: FCC_8 + if (channels != 1 && channels != 2 && channels != 4 && channels != 6 && channels != 8) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("unsupported channels %u\n", channels); +#endif + goto close; + } + unsigned samplerate = little4u(&fmt[4]); + if (samplerate == 0) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("samplerate %u == 0\n", samplerate); +#endif + goto close; + } + // ignore byte rate + // ignore block alignment + unsigned bitsPerSample = little2u(&fmt[14]); + if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && + bitsPerSample != 32) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("bitsPerSample %u != 8 or 16 or 24 or 32\n", bitsPerSample); +#endif + goto close; + } + unsigned bytesPerFrame = (bitsPerSample >> 3) * channels; + handle->bytesPerFrame = bytesPerFrame; + handle->info.samplerate = static_cast(samplerate); + handle->info.channels = static_cast(channels); + switch (bitsPerSample) { + case 8: + handle->info.format |= SF_FORMAT_PCM_U8; + break; + case 16: + handle->info.format |= SF_FORMAT_PCM_16; + break; + case 24: + handle->info.format |= SF_FORMAT_PCM_24; + break; + case 32: + if (format == WAVE_FORMAT_IEEE_FLOAT) { + handle->info.format |= SF_FORMAT_FLOAT; + } else { + handle->info.format |= SF_FORMAT_PCM_32; + } + break; + } + hadFmt = 1; + } else if (!memcmp(&chunk[0], "data", 4)) { + if (!hadFmt) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("data not preceded by fmt\n"); +#endif + goto close; + } + if (hadData) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("multiple data\n"); +#endif + goto close; + } + handle->remaining = chunkSize / handle->bytesPerFrame; + handle->info.frames = handle->remaining; + dataTell = handle->callback.tell(stream); + if (chunkSize > 0) { + handle->callback.seek(stream, static_cast(chunkSize), SEEK_CUR); //NOLINT(google-runtime-int) + } + hadData = 1; + } else if (!memcmp(&chunk[0], "fact", 4)) { + // ignore fact + if (chunkSize > 0) { + handle->callback.seek(stream, static_cast(chunkSize), SEEK_CUR); //NOLINT(google-runtime-int) + } + } else { + // ignore unknown chunk +#ifdef HAVE_STDERR + CC_LOG_ERROR("ignoring unknown chunk %c%c%c%c\n", + chunk[0], chunk[1], chunk[2], chunk[3]); +#endif + if (chunkSize > 0) { + handle->callback.seek(stream, static_cast(chunkSize), SEEK_CUR); //NOLINT(google-runtime-int) + } + } + remaining -= chunkSize; + } + if (remaining > 0) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("partial chunk at end of RIFF, remaining %zu\n", remaining); +#endif + goto close; + } + if (!hadData) { +#ifdef HAVE_STDERR + CC_LOG_ERROR("missing data\n"); +#endif + goto close; + } + (void)handle->callback.seek(stream, dataTell, SEEK_SET); + *info = handle->info; + return handle; + +close: + handle->callback.close(stream); + free(handle); + return nullptr; +} + +void sf_close(SNDFILE *handle) { //NOLINT(readability-identifier-naming) + if (handle == nullptr) { + return; + } + free(handle->temp); + (void)handle->callback.close(handle->stream); + free(handle); +} + +off_t sf_seek(SNDFILE *handle, int offset, int whence) { //NOLINT(readability-identifier-naming) + if (whence == SEEK_SET) { + assert(offset >= 0 && offset <= handle->info.frames); + } else if (whence == SEEK_CUR) { + offset += sf_tell(handle); + assert(offset >= 0 && offset <= handle->info.frames); + } else if (whence == SEEK_END) { + offset += handle->info.frames; + assert(offset >= 0 && offset <= handle->info.frames); + } else { + assert(false); // base whence value + } + handle->remaining = handle->info.frames - offset; + return offset; +} + +off_t sf_tell(SNDFILE *handle) { //NOLINT(readability-identifier-naming) + return handle->info.frames - handle->remaining; +} + +sf_count_t sf_readf_short(SNDFILE *handle, int16_t *ptr, sf_count_t desiredFrames) { //NOLINT(readability-identifier-naming) + if (handle == nullptr || ptr == nullptr || !handle->remaining || + desiredFrames <= 0) { + return 0; + } + if (handle->remaining < static_cast(desiredFrames)) { + desiredFrames = handle->remaining; + } + // does not check for numeric overflow + size_t desiredBytes = desiredFrames * handle->bytesPerFrame; + size_t actualBytes; + void *temp = nullptr; + unsigned format = handle->info.format & SF_FORMAT_SUBMASK; + if (format == SF_FORMAT_PCM_32 || format == SF_FORMAT_FLOAT || format == SF_FORMAT_PCM_24) { + temp = malloc(desiredBytes); + actualBytes = handle->callback.read(temp, sizeof(char), desiredBytes, handle->stream); + } else { + actualBytes = handle->callback.read(ptr, sizeof(char), desiredBytes, handle->stream); + } + size_t actualFrames = actualBytes / handle->bytesPerFrame; + handle->remaining -= actualFrames; + switch (format) { + case SF_FORMAT_PCM_U8: + memcpy_to_i16_from_u8(ptr, reinterpret_cast(ptr), actualFrames * handle->info.channels); + break; + case SF_FORMAT_PCM_16: + if (!isLittleEndian()) { + my_swab(ptr, actualFrames * handle->info.channels); + } + break; + case SF_FORMAT_PCM_32: + memcpy_to_i16_from_i32(ptr, static_cast(temp), actualFrames * handle->info.channels); + free(temp); + break; + case SF_FORMAT_FLOAT: + memcpy_to_i16_from_float(ptr, static_cast(temp), actualFrames * handle->info.channels); + free(temp); + break; + case SF_FORMAT_PCM_24: + memcpy_to_i16_from_p24(ptr, static_cast(temp), actualFrames * handle->info.channels); + free(temp); + break; + default: + memset(ptr, 0, actualFrames * handle->info.channels * sizeof(int16_t)); + break; + } + return actualFrames; +} + +/* +sf_count_t sf_readf_float(SNDFILE *handle, float *ptr, sf_count_t desiredFrames) +{ + if (handle == nullptr || ptr == nullptr || !handle->remaining || + desiredFrames <= 0) { + return 0; + } + if (handle->remaining < (size_t) desiredFrames) { + desiredFrames = handle->remaining; + } + // does not check for numeric overflow + size_t desiredBytes = desiredFrames * handle->bytesPerFrame; + size_t actualBytes; + void *temp = nullptr; + unsigned format = handle->info.format & SF_FORMAT_SUBMASK; + if (format == SF_FORMAT_PCM_16 || format == SF_FORMAT_PCM_U8 || format == SF_FORMAT_PCM_24) { + temp = malloc(desiredBytes); + actualBytes = handle->callback.read(temp, sizeof(char), desiredBytes, handle->stream); + } else { + actualBytes = handle->callback.read(ptr, sizeof(char), desiredBytes, handle->stream); + } + size_t actualFrames = actualBytes / handle->bytesPerFrame; + handle->remaining -= actualFrames; + switch (format) { + case SF_FORMAT_PCM_U8: +#if 0 + // REFINE: - implement + memcpy_to_float_from_u8(ptr, (const unsigned char *) temp, + actualFrames * handle->info.channels); +#endif + free(temp); + break; + case SF_FORMAT_PCM_16: + memcpy_to_float_from_i16(ptr, (const int16_t *) temp, actualFrames * handle->info.channels); + free(temp); + break; + case SF_FORMAT_PCM_32: + memcpy_to_float_from_i32(ptr, (const int *) ptr, actualFrames * handle->info.channels); + break; + case SF_FORMAT_FLOAT: + break; + case SF_FORMAT_PCM_24: + memcpy_to_float_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels); + free(temp); + break; + default: + memset(ptr, 0, actualFrames * handle->info.channels * sizeof(float)); + break; + } + return actualFrames; +} + +sf_count_t sf_readf_int(SNDFILE *handle, int *ptr, sf_count_t desiredFrames) +{ + if (handle == nullptr || ptr == nullptr || !handle->remaining || + desiredFrames <= 0) { + return 0; + } + if (handle->remaining < (size_t) desiredFrames) { + desiredFrames = handle->remaining; + } + // does not check for numeric overflow + size_t desiredBytes = desiredFrames * handle->bytesPerFrame; + void *temp = nullptr; + unsigned format = handle->info.format & SF_FORMAT_SUBMASK; + size_t actualBytes; + if (format == SF_FORMAT_PCM_16 || format == SF_FORMAT_PCM_U8 || format == SF_FORMAT_PCM_24) { + temp = malloc(desiredBytes); + actualBytes = handle->callback.read(temp, sizeof(char), desiredBytes, handle->stream); + } else { + actualBytes = handle->callback.read(ptr, sizeof(char), desiredBytes, handle->stream); + } + size_t actualFrames = actualBytes / handle->bytesPerFrame; + handle->remaining -= actualFrames; + switch (format) { + case SF_FORMAT_PCM_U8: +#if 0 + // REFINE: - implement + memcpy_to_i32_from_u8(ptr, (const unsigned char *) temp, + actualFrames * handle->info.channels); +#endif + free(temp); + break; + case SF_FORMAT_PCM_16: + memcpy_to_i32_from_i16(ptr, (const int16_t *) temp, actualFrames * handle->info.channels); + free(temp); + break; + case SF_FORMAT_PCM_32: + break; + case SF_FORMAT_FLOAT: + memcpy_to_i32_from_float(ptr, (const float *) ptr, actualFrames * handle->info.channels); + break; + case SF_FORMAT_PCM_24: + memcpy_to_i32_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels); + free(temp); + break; + default: + memset(ptr, 0, actualFrames * handle->info.channels * sizeof(int)); + break; + } + return actualFrames; +} + */ +} // namespace sf diff --git a/cocos/audio/include/AudioDef.h b/cocos/audio/include/AudioDef.h new file mode 100644 index 0000000..6fcf1dd --- /dev/null +++ b/cocos/audio/include/AudioDef.h @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include +#include +enum class AudioDataFormat { + UNKNOWN = 0, + SIGNED_8, + UNSIGNED_8, + SIGNED_16, + UNSIGNED_16, + SIGNED_32, + UNSIGNED_32, + FLOAT_32, + FLOAT_64, +}; +struct PCMHeader { + uint32_t totalFrames{0}; + uint32_t bytesPerFrame{0}; + uint32_t sampleRate{0}; + uint32_t channelCount{0}; + AudioDataFormat dataFormat{AudioDataFormat::UNKNOWN}; +}; diff --git a/cocos/audio/include/AudioEngine.h b/cocos/audio/include/AudioEngine.h new file mode 100644 index 0000000..06de510 --- /dev/null +++ b/cocos/audio/include/AudioEngine.h @@ -0,0 +1,406 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include "audio/include/AudioDef.h" +#include "audio/include/Export.h" +#include "base/Macros.h" +#include "base/std/container/list.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" +#include "engine/EngineEvents.h" + +#ifdef ERROR + #undef ERROR +#endif // ERROR + +/** + * @addtogroup audio + * @{ + */ + +namespace cc { +/** + * @class AudioProfile + * + * @brief + * @js NA + */ +class EXPORT_DLL AudioProfile { +public: + //Profile name can't be empty. + ccstd::string name; + //The maximum number of simultaneous audio instance. + unsigned int maxInstances{}; + + /* Minimum delay in between sounds */ + double minDelay{}; + + /** + * Default constructor + * + * @lua new + */ + AudioProfile() = default; +}; + +class AudioEngineImpl; + +/** + * @class AudioEngine + * + * @brief Offers a interface to play audio. + * + * @note Make sure to call AudioEngine::end() when the audio engine is not needed anymore to release resources. + * @js NA + */ + +class EXPORT_DLL AudioEngine { +public: + /** AudioState enum,all possible states of an audio instance.*/ + enum class AudioState { + ERROR = -1, + INITIALIZING, + PLAYING, + PAUSED + }; + + static const int INVALID_AUDIO_ID; + + static const float TIME_UNKNOWN; + + static bool lazyInit(); + + /** + * Release objects relating to AudioEngine. + * + * @warning It must be called before the application exit. + * @lua endToLua + */ + static void end(); + + /** + * Gets the default profile of audio instances. + * + * @return The default profile of audio instances. + */ + static AudioProfile *getDefaultProfile(); + + /** + * Play 2d sound. + * + * @param filePath The path of an audio file. + * @param loop Whether audio instance loop or not. + * @param volume Volume value (range from 0.0 to 1.0). + * @param profile A profile for audio instance. When profile is not specified, default profile will be used. + * @return An audio ID. It allows you to dynamically change the behavior of an audio instance on the fly. + * + * @see `AudioProfile` + */ + static int play2d(const ccstd::string &filePath, bool loop = false, float volume = 1.0F, const AudioProfile *profile = nullptr); + + /** + * Sets whether an audio instance loop or not. + * + * @param audioID An audioID returned by the play2d function. + * @param loop Whether audio instance loop or not. + */ + static void setLoop(int audioID, bool loop); + + /** + * Checks whether an audio instance is loop. + * + * @param audioID An audioID returned by the play2d function. + * @return Whether or not an audio instance is loop. + */ + static bool isLoop(int audioID); + + /** + * Sets volume for an audio instance. + * + * @param audioID An audioID returned by the play2d function. + * @param volume Volume value (range from 0.0 to 1.0). + */ + static void setVolume(int audioID, float volume); + + /** + * sets volume factor for all audio instance + * @param factor, Volume factor(range from 0.0 to 1.0). + */ + static void setVolumeFactor(float factor); + + /** + * Gets the volume value of an audio instance. + * + * @param audioID An audioID returned by the play2d function. + * @return Volume value (range from 0.0 to 1.0). + */ + static float getVolume(int audioID); + + /** + * Pause an audio instance. + * + * @param audioID An audioID returned by the play2d function. + */ + static void pause(int audioID); + + /** Pause all playing audio instances. */ + static void pauseAll(); + + /** + * Resume an audio instance. + * + * @param audioID An audioID returned by the play2d function. + */ + static void resume(int audioID); + + /** Resume all suspended audio instances. */ + static void resumeAll(); + + /** + * Stop an audio instance. + * + * @param audioID An audioID returned by the play2d function. + */ + static void stop(int audioID); + + /** Stop all audio instances. */ + static void stopAll(); + + /** + * Sets the current playback position of an audio instance. + * + * @param audioID An audioID returned by the play2d function. + * @param time The offset in seconds from the start to seek to. + * @return + */ + static bool setCurrentTime(int audioID, float time); + + /** + * Gets the current playback position of an audio instance. + * + * @param audioID An audioID returned by the play2d function. + * @return The current playback position of an audio instance. + */ + static float getCurrentTime(int audioID); + + /** + * Gets the duration of an audio instance. + * + * @param audioID An audioID returned by the play2d function. + * @return The duration of an audio instance. + */ + static float getDuration(int audioID); + + /** + * Gets the duration of an audio file. + * + * @param filePath The path of an audio file. + * @return The duration of an audio file. + */ + static float getDurationFromFile(const ccstd::string &filePath); + + /** + * Returns the state of an audio instance. + * + * @param audioID An audioID returned by the play2d function. + * @return The status of an audio instance. + */ + static AudioState getState(int audioID); + + /** + * Register a callback to be invoked when an audio instance has completed playing. + * + * @param audioID An audioID returned by the play2d function. + * @param callback + */ + static void setFinishCallback(int audioID, const std::function &callback); + + /** + * Gets the maximum number of simultaneous audio instance of AudioEngine. + */ + static int getMaxAudioInstance() { return static_cast(sMaxInstances); } + + /** + * Sets the maximum number of simultaneous audio instance for AudioEngine. + * + * @param maxInstances The maximum number of simultaneous audio instance. + */ + static bool setMaxAudioInstance(int maxInstances); + + /** + * Uncache the audio data from internal buffer. + * AudioEngine cache audio data on ios,mac, and oalsoft platform. + * + * @warning This can lead to stop related audio first. + * @param filePath Audio file path. + */ + static void uncache(const ccstd::string &filePath); + + /** + * Uncache all audio data from internal buffer. + * + * @warning All audio will be stopped first. + */ + static void uncacheAll(); + + /** + * Gets the audio profile by id of audio instance. + * + * @param audioID An audioID returned by the play2d function. + * @return The audio profile. + */ + static AudioProfile *getProfile(int audioID); + + /** + * Gets an audio profile by name. + * + * @param profileName A name of audio profile. + * @return The audio profile. + */ + static AudioProfile *getProfile(const ccstd::string &profileName); + + /** + * Preload audio file. + * @param filePath The file path of an audio. + */ + static void preload(const ccstd::string &filePath) { preload(filePath, nullptr); } + + /** + * Preload audio file. + * @param filePath The file path of an audio. + * @param callback A callback which will be called after loading is finished. + */ + static void preload(const ccstd::string &filePath, const std::function &callback); + + /** + * Gets playing audio count. + */ + static int getPlayingAudioCount(); + + /** + * Whether to enable playing audios + * @note If it's disabled, current playing audios will be stopped and the later 'preload', 'play2d' methods will take no effects. + */ + static void setEnabled(bool isEnabled); + /** + * Check whether AudioEngine is enabled. + */ + static bool isEnabled(); + + /** + * @brief Get the PCMHeader of audio + * + * @param url The file url of an audio. same as filePath + * @return PCMHeader of audio + */ + static PCMHeader getPCMHeader(const char *url); + + /** + * @brief Get the Buffer object + * + * @param channelID as there might be several channels at same time, select one to get buffer. + * Start from 0 + * @return PCM datas behave as a ccstd::vector. You can check byte length in PCMHeader. + */ + static ccstd::vector getOriginalPCMBuffer(const char *url, uint32_t channelID); + +protected: + static void addTask(const std::function &task); + static void remove(int audioID); + + static void pauseAll(ccstd::vector *pausedAudioIDs); + static void resumeAll(ccstd::vector *pausedAudioIDs); + + struct ProfileHelper { + AudioProfile profile; + + ccstd::list audioIDs; + + std::chrono::high_resolution_clock::time_point lastPlayTime; + + ProfileHelper() = default; + }; + + struct AudioInfo { + const ccstd::string *filePath; + ProfileHelper *profileHelper; + + float volume; + bool loop; + float duration; + AudioState state; + + AudioInfo(); + ~AudioInfo() = default; + + private: + AudioInfo(const AudioInfo &info); + AudioInfo(AudioInfo &&info) noexcept; + AudioInfo &operator=(const AudioInfo &info); + AudioInfo &operator=(AudioInfo &&info) noexcept; + }; + + //audioID,audioAttribute + static ccstd::unordered_map sAudioIDInfoMap; + + //audio file path,audio IDs + static ccstd::unordered_map> sAudioPathIDMap; + + //profileName,ProfileHelper + static ccstd::unordered_map sAudioPathProfileHelperMap; + + static unsigned int sMaxInstances; + + static ProfileHelper *sDefaultProfileHelper; + + static AudioEngineImpl *sAudioEngineImpl; + + class AudioEngineThreadPool; + static AudioEngineThreadPool *sThreadPool; + + static bool sIsEnabled; + +private: + static float sVolumeFactor; + static events::EnterBackground::Listener sOnPauseListenerID; + static events::EnterForeground::Listener sOnResumeListenerID; + static ccstd::vector sBreakAudioID; + + static void onEnterBackground(); + static void onEnterForeground(); + + friend class AudioEngineImpl; +}; + +} // namespace cc + +// end group +/// @} diff --git a/cocos/audio/include/AudioMacros.h b/cocos/audio/include/AudioMacros.h new file mode 100644 index 0000000..e7564c8 --- /dev/null +++ b/cocos/audio/include/AudioMacros.h @@ -0,0 +1,76 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Log.h" + +#define QUEUEBUFFER_NUM (3) +#define QUEUEBUFFER_TIME_STEP (0.1f) + +// log, CC_LOG_DEBUG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output +// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output. + +//IDEA:Move the definition of the following macros to a separated file. + +#define audioLog(...) CC_LOG_DEBUG(__VA_ARGS__) + +#define QUOTEME_(x) #x +#define QUOTEME(x) QUOTEME_(x) + +#if defined(CC_DEBUG) && CC_DEBUG > 0 + #define ALOGV(fmt, ...) audioLog("V/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__) +#else + #define ALOGV(fmt, ...) \ + do { \ + } while (false) +#endif +#define ALOGD(fmt, ...) audioLog("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__) +#define ALOGI(fmt, ...) audioLog("I/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__) +#define ALOGW(fmt, ...) audioLog("W/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__) +#define ALOGE(fmt, ...) audioLog("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__) + +#if defined(CC_DEBUG) && CC_DEBUG > 0 + #define CHECK_AL_ERROR_DEBUG() \ + do { \ + ALenum __error = alGetError(); \ + if (__error) { \ + ALOGE("OpenAL error 0x%04X in %s %s %d\n", __error, __FILE__, __FUNCTION__, __LINE__); \ + } \ + } while (false) +#else + #define CHECK_AL_ERROR_DEBUG() +#endif + +#define BREAK_IF(condition) \ + if (!!(condition)) { \ + break; \ + } + +#define BREAK_IF_ERR_LOG(condition, fmt, ...) \ + if (!!(condition)) { \ + CC_LOG_DEBUG("(" QUOTEME(condition) ") failed, message: " fmt, ##__VA_ARGS__); \ + break; \ + } diff --git a/cocos/audio/include/Export.h b/cocos/audio/include/Export.h new file mode 100644 index 0000000..c8c654d --- /dev/null +++ b/cocos/audio/include/Export.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#if defined(SHP) + #include + #define EXPORT_DLL _EXPORT_ +#elif defined(_WIN32) + #if defined(CC_STATIC) + #define EXPORT_DLL + #else + #if defined(_EXPORT_DLL_) + #define EXPORT_DLL __declspec(dllexport) + #else /* use a DLL library */ + #define EXPORT_DLL __declspec(dllimport) + #endif + #endif +#else + #if defined(_SHARED_) + #define EXPORT_DLL __attribute__((visibility("default"))) + #elif defined(IGNORE_EXPORT) + #define EXPORT_DLL + #else + #define EXPORT_DLL + #endif +#endif diff --git a/cocos/audio/oalsoft/AudioCache.cpp b/cocos/audio/oalsoft/AudioCache.cpp new file mode 100644 index 0000000..0b80043 --- /dev/null +++ b/cocos/audio/oalsoft/AudioCache.cpp @@ -0,0 +1,339 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioCache" + +#include "audio/oalsoft/AudioCache.h" +#include +#include +#include "application/ApplicationManager.h" +#include "audio/common/decoder/AudioDecoder.h" +#include "audio/common/decoder/AudioDecoderManager.h" + +#include + +#define VERY_VERY_VERBOSE_LOGGING +#ifdef VERY_VERY_VERBOSE_LOGGING + #define ALOGVV ALOGV +#else + #define ALOGVV(...) \ + do { \ + } while (false) +#endif + +namespace { +unsigned int gIdIndex = 0; +} + +#define PCMDATA_CACHEMAXSIZE 1048576 + +using namespace cc; //NOLINT + +AudioCache::AudioCache() +: _isDestroyed(std::make_shared(false)), _id(++gIdIndex) { + ALOGVV("AudioCache() %p, id=%u", this, _id); + for (int i = 0; i < QUEUEBUFFER_NUM; ++i) { + _queBuffers[i] = nullptr; + _queBufferSize[i] = 0; + } +} + +AudioCache::~AudioCache() { + ALOGVV("~AudioCache() %p, id=%u, begin", this, _id); + *_isDestroyed = true; + while (!_isLoadingFinished) { + if (_isSkipReadDataTask) { + ALOGV("id=%u, Skip read data task, don't continue to wait!", _id); + break; + } + ALOGVV("id=%u, waiting readData thread to finish ...", _id); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + //wait for the 'readDataTask' task to exit + _readDataTaskMutex.lock(); + _readDataTaskMutex.unlock(); + + if (_pcmData) { + if (_state == State::READY) { + if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId)) { + ALOGV("~AudioCache(id=%u), delete buffer: %u", _id, _alBufferId); + alDeleteBuffers(1, &_alBufferId); + _alBufferId = INVALID_AL_BUFFER_ID; + } + } else { + ALOGW("AudioCache (%p), id=%u, buffer isn't ready, state=%d", this, _id, _state); + } + + free(_pcmData); + } + + if (_queBufferFrames > 0) { + for (auto &buffer : _queBuffers) { + free(buffer); + } + } + ALOGVV("~AudioCache() %p, id=%u, end", this, _id); +} + +void AudioCache::readDataTask(unsigned int selfId) { + //Note: It's in sub thread + ALOGVV("readDataTask begin, cache id=%u", selfId); + + _readDataTaskMutex.lock(); + _state = State::LOADING; + + AudioDecoder *decoder = AudioDecoderManager::createDecoder(_fileFullPath.c_str()); + do { + if (decoder == nullptr || !decoder->open(_fileFullPath.c_str())) { + break; + } + + const uint32_t originalTotalFrames = decoder->getTotalFrames(); + _bytesPerFrame = decoder->getBytesPerFrame(); + const uint32_t sampleRate = decoder->getSampleRate(); + _channelCount = decoder->getChannelCount(); + + uint32_t totalFrames = originalTotalFrames; + uint32_t dataSize = totalFrames * _bytesPerFrame; + uint32_t remainingFrames = totalFrames; + uint32_t adjustFrames = 0; + + _format = _channelCount > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + _sampleRate = static_cast(sampleRate); + _duration = 1.0F * totalFrames / sampleRate; + _totalFrames = totalFrames; + + if (dataSize <= PCMDATA_CACHEMAXSIZE) { + uint32_t framesRead = 0; + const uint32_t framesToReadOnce = std::min(totalFrames, static_cast(sampleRate * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM)); + + ccstd::vector adjustFrameBuf; + + if (decoder->seek(totalFrames)) { + auto *tmpBuf = static_cast(malloc(framesToReadOnce * _bytesPerFrame)); + adjustFrameBuf.reserve(framesToReadOnce * _bytesPerFrame); + + // Adjust total frames by setting position to the end of frames and try to read more data. + // This is a workaround for https://github.com/cocos2d/cocos2d-x/issues/16938 + do { + framesRead = decoder->read(framesToReadOnce, tmpBuf); + if (framesRead > 0) { + adjustFrames += framesRead; + adjustFrameBuf.insert(adjustFrameBuf.end(), tmpBuf, tmpBuf + framesRead * _bytesPerFrame); + } + + } while (framesRead > 0); + + if (adjustFrames > 0) { + ALOGV("Orignal total frames: %u, adjust frames: %u, current total frames: %u", totalFrames, adjustFrames, totalFrames + adjustFrames); + totalFrames += adjustFrames; + _totalFrames = remainingFrames = totalFrames; + } + + // Reset dataSize + dataSize = totalFrames * _bytesPerFrame; + + free(tmpBuf); + } + // Reset to frame 0 + BREAK_IF_ERR_LOG(!decoder->seek(0), "AudioDecoder::seek(0) failed!"); + + _pcmData = static_cast(malloc(dataSize)); + + CC_ASSERT(_pcmData); + memset(_pcmData, 0x00, dataSize); + + if (adjustFrames > 0) { + memcpy(_pcmData + (dataSize - adjustFrameBuf.size()), adjustFrameBuf.data(), adjustFrameBuf.size()); + } + + alGenBuffers(1, &_alBufferId); + auto alError = alGetError(); + if (alError != AL_NO_ERROR) { + ALOGE("%s: attaching audio to buffer fail: %x", __FUNCTION__, alError); + break; + } + + if (*_isDestroyed) { + break; + } + + framesRead = decoder->readFixedFrames(std::min(framesToReadOnce, remainingFrames), _pcmData + _framesRead * _bytesPerFrame); + _framesRead += framesRead; + remainingFrames -= framesRead; + + if (*_isDestroyed) { + break; + } + + uint32_t frames = 0; + while (!*_isDestroyed && _framesRead < originalTotalFrames) { + frames = std::min(framesToReadOnce, remainingFrames); + if (_framesRead + frames > originalTotalFrames) { + frames = originalTotalFrames - _framesRead; + } + framesRead = decoder->read(frames, _pcmData + _framesRead * _bytesPerFrame); + if (framesRead == 0) { + break; + } + _framesRead += framesRead; + remainingFrames -= framesRead; + } + + if (*_isDestroyed) { + break; + } + + if (_framesRead < originalTotalFrames) { + memset(_pcmData + _framesRead * _bytesPerFrame, 0x00, (totalFrames - _framesRead) * _bytesPerFrame); + } + ALOGV("pcm buffer was loaded successfully, total frames: %u, total read frames: %u, adjust frames: %u, remainingFrames: %u", totalFrames, _framesRead, adjustFrames, remainingFrames); + + _framesRead += adjustFrames; + + alBufferData(_alBufferId, _format, _pcmData, static_cast(dataSize), static_cast(sampleRate)); + + _state = State::READY; + } else { + _isStreaming = true; + _queBufferFrames = static_cast(sampleRate * QUEUEBUFFER_TIME_STEP); + BREAK_IF_ERR_LOG(_queBufferFrames == 0, "_queBufferFrames == 0"); + + const uint32_t queBufferBytes = _queBufferFrames * _bytesPerFrame; + + for (int index = 0; index < QUEUEBUFFER_NUM; ++index) { + _queBuffers[index] = static_cast(malloc(queBufferBytes)); + _queBufferSize[index] = queBufferBytes; + + decoder->readFixedFrames(_queBufferFrames, _queBuffers[index]); + } + + _state = State::READY; + } + + } while (false); + + if (decoder != nullptr) { + decoder->close(); + } + + AudioDecoderManager::destroyDecoder(decoder); + + if (_state != State::READY) { + _state = State::FAILED; + if (_alBufferId != INVALID_AL_BUFFER_ID && alIsBuffer(_alBufferId)) { + ALOGV("readDataTask failed, delete buffer: %u", _alBufferId); + alDeleteBuffers(1, &_alBufferId); + _alBufferId = INVALID_AL_BUFFER_ID; + } + } + + //IDEA: Why to invoke play callback first? Should it be after 'load' callback? + invokingPlayCallbacks(); + invokingLoadCallbacks(); + + _isLoadingFinished = true; + _readDataTaskMutex.unlock(); + ALOGVV("readDataTask end, cache id=%u", selfId); +} + +void AudioCache::addPlayCallback(const std::function &callback) { + std::lock_guard lk(_playCallbackMutex); + switch (_state) { + case State::INITIAL: + case State::LOADING: + _playCallbacks.push_back(callback); + break; + + case State::READY: + // If state is failure, we still need to invoke the callback + // since the callback will set the 'AudioPlayer::_removeByAudioEngine' flag to true. + case State::FAILED: + callback(); + break; + + default: + ALOGE("Invalid state: %d", _state); + break; + } +} + +void AudioCache::invokingPlayCallbacks() { + std::lock_guard lk(_playCallbackMutex); + + for (auto &&cb : _playCallbacks) { + cb(); + } + + _playCallbacks.clear(); +} + +void AudioCache::addLoadCallback(const std::function &callback) { + switch (_state) { + case State::INITIAL: + case State::LOADING: + _loadCallbacks.push_back(callback); + break; + + case State::READY: + callback(true); + break; + case State::FAILED: + callback(false); + break; + + default: + ALOGE("Invalid state: %d", _state); + break; + } +} + +void AudioCache::invokingLoadCallbacks() { + if (*_isDestroyed) { + ALOGV("AudioCache (%p) was destroyed, don't invoke preload callback ...", this); + return; + } + + auto isDestroyed = _isDestroyed; + + BaseEngine::SchedulerPtr scheduler = + CC_CURRENT_APPLICATION() ? CC_CURRENT_APPLICATION()->getEngine()->getScheduler() : nullptr; + if (!scheduler) { + return; + } + + scheduler->performFunctionInCocosThread([&, isDestroyed]() { + if (*isDestroyed) { + ALOGV("invokingLoadCallbacks perform in cocos thread, AudioCache (%p) was destroyed!", this); + return; + } + + for (auto &&cb : _loadCallbacks) { + cb(_state == State::READY); + } + + _loadCallbacks.clear(); + }); +} diff --git a/cocos/audio/oalsoft/AudioCache.h b/cocos/audio/oalsoft/AudioCache.h new file mode 100644 index 0000000..160d946 --- /dev/null +++ b/cocos/audio/oalsoft/AudioCache.h @@ -0,0 +1,121 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include "base/std/container/string.h" +#if defined(OPENAL_PLAIN_INCLUDES) + #include +#elif CC_PLATFORM == CC_PLATFORM_WINDOWS + #include +#elif CC_PLATFORM == CC_PLATFORM_OHOS + #include +#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX + #include +#endif +#include "audio/include/AudioMacros.h" +#include "base/Macros.h" +#include "base/std/container/vector.h" +#define INVALID_AL_BUFFER_ID 0xFFFFFFFF +namespace cc { +class AudioEngineImpl; +class AudioPlayer; + +class CC_DLL AudioCache { +public: + enum class State { + INITIAL, + LOADING, + READY, + FAILED + }; + + AudioCache(); + ~AudioCache(); + + void addPlayCallback(const std::function &callback); + + void addLoadCallback(const std::function &callback); + + uint32_t getChannelCount() const { return _channelCount; } + bool isStreaming() const { return _isStreaming; } + +protected: + void setSkipReadDataTask(bool isSkip) { _isSkipReadDataTask = isSkip; }; + void readDataTask(unsigned int selfId); + + void invokingPlayCallbacks(); + + void invokingLoadCallbacks(); + + //pcm data related stuff + ALenum _format{-1}; + ALsizei _sampleRate{-1}; + float _duration{0.0F}; + uint32_t _totalFrames{0}; + uint32_t _framesRead{0}; + uint32_t _bytesPerFrame{0}; + + bool _isStreaming{false}; + uint32_t _channelCount{1}; + + /*Cache related stuff; + * Cache pcm data when sizeInBytes less than PCMDATA_CACHEMAXSIZE + */ + ALuint _alBufferId{INVALID_AL_BUFFER_ID}; + char *_pcmData{nullptr}; + + /*Queue buffer related stuff + * Streaming in OpenAL when sizeInBytes greater then PCMDATA_CACHEMAXSIZE + */ + char *_queBuffers[QUEUEBUFFER_NUM]; + ALsizei _queBufferSize[QUEUEBUFFER_NUM]; + uint32_t _queBufferFrames{0}; + + std::mutex _playCallbackMutex; + ccstd::vector> _playCallbacks; + + // loadCallbacks doesn't need mutex since it's invoked only in Cocos thread. + ccstd::vector> _loadCallbacks; + + std::mutex _readDataTaskMutex; + + State _state{State::INITIAL}; + + std::shared_ptr _isDestroyed; + ccstd::string _fileFullPath; + unsigned int _id; + bool _isLoadingFinished{false}; + bool _isSkipReadDataTask{false}; + + friend class AudioEngineImpl; + friend class AudioPlayer; +}; + +} // namespace cc diff --git a/cocos/audio/oalsoft/AudioEngine-soft.cpp b/cocos/audio/oalsoft/AudioEngine-soft.cpp new file mode 100644 index 0000000..0831fb4 --- /dev/null +++ b/cocos/audio/oalsoft/AudioEngine-soft.cpp @@ -0,0 +1,645 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include +#include +#include +#include +#include +#include "audio/common/decoder/AudioDecoder.h" +#include "base/Log.h" +#include "base/Utils.h" +#include "base/std/container/vector.h" +#define LOG_TAG "AudioEngine-OALSOFT" + +#include "audio/oalsoft/AudioEngine-soft.h" + +#ifdef OPENAL_PLAIN_INCLUDES + #include "alc.h" + #include "alext.h" +#elif CC_PLATFORM == CC_PLATFORM_WINDOWS + #include "OpenalSoft/alc.h" + #include "OpenalSoft/alext.h" +#elif CC_PLATFORM == CC_PLATFORM_OHOS + #include "AL/alc.h" + #include "AL/alext.h" +#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX + #include "AL/alc.h" + #include "AL/alext.h" +#endif +#include "application/ApplicationManager.h" +#include "audio/common/decoder/AudioDecoderManager.h" +#include "audio/include/AudioEngine.h" +#include "base/Scheduler.h" +#include "base/memory/Memory.h" +#include "platform/FileUtils.h" + +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + #include + +// log, CC_LOG_DEBUG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output +// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output. + +//IDEA: Move _winLog, winLog to a separated file +static void _winLog(const char *format, va_list args) { + static const int MAX_LOG_LENGTH = 16 * 1024; + int bufferSize = MAX_LOG_LENGTH; + char *buf = nullptr; + + do { + buf = ccnew char[bufferSize]; + if (buf == nullptr) + return; // not enough memory + + int ret = vsnprintf(buf, bufferSize - 3, format, args); + if (ret < 0) { + bufferSize *= 2; + + delete[] buf; + } else + break; + + } while (true); + + strcat(buf, "\n"); + + int pos = 0; + auto len = static_cast(strlen(buf)); + char tempBuf[MAX_LOG_LENGTH + 1] = {0}; + WCHAR wszBuf[MAX_LOG_LENGTH + 1] = {0}; + + do { + std::copy(buf + pos, buf + pos + MAX_LOG_LENGTH, tempBuf); + + tempBuf[MAX_LOG_LENGTH] = 0; + + MultiByteToWideChar(CP_UTF8, 0, tempBuf, -1, wszBuf, sizeof(wszBuf)); + OutputDebugStringW(wszBuf); + + pos += MAX_LOG_LENGTH; + + } while (pos < len); + + delete[] buf; +} + + #ifndef audioLog +void audioLog(const char *format, ...) { + va_list args; + va_start(args, format); + _winLog(format, args); + va_end(args); +} + #endif + +#else + + #define audioLog(...) CC_LOG_DEBUG(__VA_ARGS__) + +#endif + +using namespace cc; //NOLINT + +static ALCdevice *sALDevice = nullptr; +static ALCcontext *sALContext = nullptr; + +AudioEngineImpl::AudioEngineImpl() +: _lazyInitLoop(true), + _currentAudioID(0) { +} + +AudioEngineImpl::~AudioEngineImpl() { + if (auto sche = _scheduler.lock()) { + sche->unschedule("AudioEngine", this); + } + + if (sALContext) { + alDeleteSources(MAX_AUDIOINSTANCES, _alSources); + + _audioCaches.clear(); + + alcMakeContextCurrent(nullptr); + alcDestroyContext(sALContext); + sALContext = nullptr; + } + + if (sALDevice) { + alcCloseDevice(sALDevice); + sALDevice = nullptr; + } + + AudioDecoderManager::destroy(); +} + +bool AudioEngineImpl::init() { + bool ret = false; + do { + sALDevice = alcOpenDevice(nullptr); + + if (sALDevice) { + alGetError(); + sALContext = alcCreateContext(sALDevice, nullptr); + alcMakeContextCurrent(sALContext); + + alGenSources(MAX_AUDIOINSTANCES, _alSources); + auto alError = alGetError(); + if (alError != AL_NO_ERROR) { + CC_LOG_ERROR("%s:generating sources failed! error = %x\n", __FUNCTION__, alError); + break; + } + + for (unsigned int src : _alSources) { + _alSourceUsed[src] = false; + } + + _scheduler = CC_CURRENT_ENGINE()->getScheduler(); + ret = AudioDecoderManager::init(); + CC_LOG_DEBUG("OpenAL was initialized successfully!"); + } + } while (false); + + return ret; +} + +AudioCache *AudioEngineImpl::preload(const ccstd::string &filePath, const std::function &callback) { + AudioCache *audioCache = nullptr; + + auto it = _audioCaches.find(filePath); + if (it == _audioCaches.end()) { + audioCache = &_audioCaches[filePath]; + audioCache->_fileFullPath = FileUtils::getInstance()->fullPathForFilename(filePath); + unsigned int cacheId = audioCache->_id; + auto isCacheDestroyed = audioCache->_isDestroyed; + AudioEngine::addTask([audioCache, cacheId, isCacheDestroyed]() { + if (*isCacheDestroyed) { + ALOGV("AudioCache (id=%u) was destroyed, no need to launch readDataTask.", cacheId); + audioCache->setSkipReadDataTask(true); + return; + } + audioCache->readDataTask(cacheId); + }); + } else { + audioCache = &it->second; + } + + if (audioCache && callback) { + audioCache->addLoadCallback(callback); + } + return audioCache; +} + +int AudioEngineImpl::play2d(const ccstd::string &filePath, bool loop, float volume) { + if (sALDevice == nullptr) { + return AudioEngine::INVALID_AUDIO_ID; + } + + bool sourceFlag = false; + ALuint alSource = 0; + for (unsigned int src : _alSources) { + alSource = src; + + if (!_alSourceUsed[alSource]) { + sourceFlag = true; + break; + } + } + if (!sourceFlag) { + return AudioEngine::INVALID_AUDIO_ID; + } + + auto player = ccnew AudioPlayer; + if (player == nullptr) { + return AudioEngine::INVALID_AUDIO_ID; + } + + player->_alSource = alSource; + player->_loop = loop; + player->_volume = volume; + + auto audioCache = preload(filePath, nullptr); + if (audioCache == nullptr) { + delete player; + return AudioEngine::INVALID_AUDIO_ID; + } + + player->setCache(audioCache); + _threadMutex.lock(); + _audioPlayers[_currentAudioID] = player; + _threadMutex.unlock(); + + _alSourceUsed[alSource] = true; + + audioCache->addPlayCallback(std::bind(&AudioEngineImpl::play2dImpl, this, audioCache, _currentAudioID)); + + if (_lazyInitLoop) { + _lazyInitLoop = false; + if (auto sche = _scheduler.lock()) { + sche->schedule(CC_CALLBACK_1(AudioEngineImpl::update, this), this, 0.05F, false, "AudioEngine"); + } + } + + return _currentAudioID++; +} + +void AudioEngineImpl::play2dImpl(AudioCache *cache, int audioID) { + //Note: It may bn in sub thread or main thread :( + if (!*cache->_isDestroyed && cache->_state == AudioCache::State::READY) { + _threadMutex.lock(); + auto playerIt = _audioPlayers.find(audioID); + if (playerIt != _audioPlayers.end() && playerIt->second->play2d()) { + if (auto sche = _scheduler.lock()) { + sche->performFunctionInCocosThread([audioID]() { + if (AudioEngine::sAudioIDInfoMap.find(audioID) != AudioEngine::sAudioIDInfoMap.end()) { + AudioEngine::sAudioIDInfoMap[audioID].state = AudioEngine::AudioState::PLAYING; + } + }); + } + } + _threadMutex.unlock(); + } else { + CC_LOG_DEBUG("AudioEngineImpl::play2dImpl, cache was destroyed or not ready!"); + auto iter = _audioPlayers.find(audioID); + if (iter != _audioPlayers.end()) { + iter->second->_removeByAudioEngine = true; + } + } +} + +void AudioEngineImpl::setVolume(int audioID, float volume) { + if (!checkAudioIdValid(audioID)) { + return; + } + auto player = _audioPlayers[audioID]; + player->_volume = volume; + + if (player->_ready) { + alSourcef(_audioPlayers[audioID]->_alSource, AL_GAIN, volume); + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ALOGE("%s: audio id = %d, error = %x", __FUNCTION__, audioID, error); + } + } +} + +void AudioEngineImpl::setLoop(int audioID, bool loop) { + if (!checkAudioIdValid(audioID)) { + return; + } + auto player = _audioPlayers[audioID]; + + if (player->_ready) { + if (player->_streamingSource) { + player->setLoop(loop); + } else { + if (loop) { + alSourcei(player->_alSource, AL_LOOPING, AL_TRUE); + } else { + alSourcei(player->_alSource, AL_LOOPING, AL_FALSE); + } + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ALOGE("%s: audio id = %d, error = %x", __FUNCTION__, audioID, error); + } + } + } else { + player->_loop = loop; + } +} + +bool AudioEngineImpl::pause(int audioID) { + if (!checkAudioIdValid(audioID)) { + return false; + } + bool ret = true; + alSourcePause(_audioPlayers[audioID]->_alSource); + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ret = false; + ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__, audioID, error); + } + + return ret; +} + +bool AudioEngineImpl::resume(int audioID) { + if (!checkAudioIdValid(audioID)) { + return false; + } + bool ret = true; + alSourcePlay(_audioPlayers[audioID]->_alSource); + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ret = false; + ALOGE("%s: audio id = %d, error = %x\n", __FUNCTION__, audioID, error); + } + + return ret; +} + +void AudioEngineImpl::stop(int audioID) { + if (!checkAudioIdValid(audioID)) { + return; + } + auto player = _audioPlayers[audioID]; + player->destroy(); + //Note: Don't set the flag to false here, it should be set in 'update' function. + // Otherwise, the state got from alSourceState may be wrong + // _alSourceUsed[player->_alSource] = false; + + // Call 'update' method to cleanup immediately since the schedule may be cancelled without any notification. + update(0.0F); +} + +void AudioEngineImpl::stopAll() { + for (auto &&player : _audioPlayers) { + player.second->destroy(); + } + //Note: Don't set the flag to false here, it should be set in 'update' function. + // Otherwise, the state got from alSourceState may be wrong + // for(int index = 0; index < MAX_AUDIOINSTANCES; ++index) + // { + // _alSourceUsed[_alSources[index]] = false; + // } + + // Call 'update' method to cleanup immediately since the schedule may be cancelled without any notification. + update(0.0F); +} + +float AudioEngineImpl::getDuration(int audioID) { + if (!checkAudioIdValid(audioID)) { + return 0.0F; + } + auto player = _audioPlayers[audioID]; + if (player->_ready) { + return player->_audioCache->_duration; + } + return AudioEngine::TIME_UNKNOWN; +} + +float AudioEngineImpl::getDurationFromFile(const ccstd::string &filePath) { + auto it = _audioCaches.find(filePath); + if (it == _audioCaches.end()) { + this->preload(filePath, nullptr); + return AudioEngine::TIME_UNKNOWN; + } + + return it->second._duration; +} + +float AudioEngineImpl::getCurrentTime(int audioID) { + if (!checkAudioIdValid(audioID)) { + return 0.0F; + } + float ret = 0.0F; + auto player = _audioPlayers[audioID]; + if (player->_ready) { + if (player->_streamingSource) { + ret = player->getTime(); + } else { + alGetSourcef(player->_alSource, AL_SEC_OFFSET, &ret); + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ALOGE("%s, audio id:%d,error code:%x", __FUNCTION__, audioID, error); + } + } + } + + return ret; +} + +bool AudioEngineImpl::setCurrentTime(int audioID, float time) { + if (!checkAudioIdValid(audioID)) { + return false; + } + bool ret = false; + auto player = _audioPlayers[audioID]; + + do { + if (!player->_ready) { + std::lock_guard lck(player->_play2dMutex); // To prevent the race condition + player->_timeDirty = true; + player->_currTime = time; + break; + } + + if (player->_streamingSource) { + ret = player->setTime(time); + break; + } + + if (player->_audioCache->_framesRead != player->_audioCache->_totalFrames && + (time * player->_audioCache->_sampleRate) > player->_audioCache->_framesRead) { + ALOGE("%s: audio id = %d", __FUNCTION__, audioID); + break; + } + + alSourcef(player->_alSource, AL_SEC_OFFSET, time); + + auto error = alGetError(); + if (error != AL_NO_ERROR) { + ALOGE("%s: audio id = %d, error = %x", __FUNCTION__, audioID, error); + } + ret = true; + + } while (false); + + return ret; +} + +void AudioEngineImpl::setFinishCallback(int audioID, const std::function &callback) { + if (!checkAudioIdValid(audioID)) { + return; + } + _audioPlayers[audioID]->_finishCallbak = callback; +} + +void AudioEngineImpl::update(float /*dt*/) { + ALint sourceState; + int audioID; + AudioPlayer *player; + ALuint alSource; + + // ALOGV("AudioPlayer count: %d", (int)_audioPlayers.size()); + + for (auto it = _audioPlayers.begin(); it != _audioPlayers.end();) { + audioID = it->first; + player = it->second; + alSource = player->_alSource; + alGetSourcei(alSource, AL_SOURCE_STATE, &sourceState); + + if (player->_removeByAudioEngine) { + AudioEngine::remove(audioID); + _threadMutex.lock(); + it = _audioPlayers.erase(it); + _threadMutex.unlock(); + delete player; + _alSourceUsed[alSource] = false; + } else if (player->_ready && sourceState == AL_STOPPED) { + ccstd::string filePath; + if (player->_finishCallbak) { + auto &audioInfo = AudioEngine::sAudioIDInfoMap[audioID]; + filePath = *audioInfo.filePath; + } + + AudioEngine::remove(audioID); + + _threadMutex.lock(); + it = _audioPlayers.erase(it); + _threadMutex.unlock(); + + if (player->_finishCallbak) { + player->_finishCallbak(audioID, filePath); //IDEA: callback will delay 50ms + } + delete player; + _alSourceUsed[alSource] = false; + } else { + ++it; + } + } + + if (_audioPlayers.empty()) { + _lazyInitLoop = true; + if (auto sche = _scheduler.lock()) { + sche->unschedule("AudioEngine", this); + } + } +} + +void AudioEngineImpl::uncache(const ccstd::string &filePath) { + _audioCaches.erase(filePath); +} + +void AudioEngineImpl::uncacheAll() { + _audioCaches.clear(); +} + +bool AudioEngineImpl::checkAudioIdValid(int audioID) { + return _audioPlayers.find(audioID) != _audioPlayers.end(); +} + +PCMHeader AudioEngineImpl::getPCMHeader(const char *url) { + PCMHeader header{}; + auto itr = _audioCaches.find(url); + if (itr != _audioCaches.end() && itr->second._state == AudioCache::State::READY) { + CC_LOG_DEBUG("file %s found in cache, load header directly", url); + auto cache = &itr->second; + header.bytesPerFrame = cache->_bytesPerFrame; + header.channelCount = cache->_channelCount; + header.dataFormat = AudioDataFormat::SIGNED_16; + header.sampleRate = cache->_sampleRate; + header.totalFrames = cache->_totalFrames; + return header; + } + ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url); + if (fileFullPath.empty()) { + CC_LOG_DEBUG("file %s does not exist or failed to load", url); + return header; + } + + AudioDecoder *decoder = AudioDecoderManager::createDecoder(fileFullPath.c_str()); + if (decoder == nullptr) { + CC_LOG_DEBUG("decode %s failed, the file formate might not support", url); + return header; + } + // Ready to decode + do { + if (!decoder->open(fileFullPath.c_str())) { + CC_LOG_ERROR("[Audio Decoder] File open failed %s", url); + break; + } + header = decoder->getPCMHeader(); + } while (false); + + AudioDecoderManager::destroyDecoder(decoder); + return header; +} + +ccstd::vector AudioEngineImpl::getOriginalPCMBuffer(const char *url, uint32_t channelID) { + ccstd::vector pcmData; + auto itr = _audioCaches.find(url); + if (itr != _audioCaches.end() && itr->second._state == AudioCache::State::READY) { + auto cache = &itr->second; + auto bytesPerChannelInFrame = cache->_bytesPerFrame / cache->_channelCount; + pcmData.resize(bytesPerChannelInFrame * cache->_totalFrames); + auto p = pcmData.data(); + if (!cache->isStreaming()) { // Cache contains a fully prepared buffer. + for (int itr = 0; itr < cache->_totalFrames; itr++) { + memcpy(p, cache->_pcmData + itr * cache->_bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame); + p += bytesPerChannelInFrame; + } + return pcmData; + } + } + ccstd::string fileFullPath = FileUtils::getInstance()->fullPathForFilename(url); + + if (fileFullPath.empty()) { + CC_LOG_DEBUG("file %s does not exist or failed to load", url); + return pcmData; + } + + AudioDecoder *decoder = AudioDecoderManager::createDecoder(fileFullPath.c_str()); + if (decoder == nullptr) { + CC_LOG_DEBUG("decode %s failed, the file formate might not support", url); + return pcmData; + } + do { + if (!decoder->open(fileFullPath.c_str())) { + CC_LOG_ERROR("[Audio Decoder] File open failed %s", url); + break; + } + auto audioInfo = decoder->getPCMHeader(); + const uint32_t bytesPerChannelInFrame = audioInfo.bytesPerFrame / audioInfo.channelCount; + if (channelID >= audioInfo.channelCount) { + CC_LOG_ERROR("channelID invalid, total channel count is %d but %d is required", audioInfo.channelCount, channelID); + break; + } + uint32_t totalFrames = decoder->getTotalFrames(); + uint32_t remainingFrames = totalFrames; + uint32_t framesRead = 0; + uint32_t framesToReadOnce = std::min(remainingFrames, static_cast(decoder->getSampleRate() * QUEUEBUFFER_TIME_STEP * QUEUEBUFFER_NUM)); + AudioDataFormat type = audioInfo.dataFormat; + char *tmpBuf = static_cast(malloc(framesToReadOnce * audioInfo.bytesPerFrame)); + pcmData.resize(bytesPerChannelInFrame * audioInfo.totalFrames); + uint8_t *p = pcmData.data(); + while (remainingFrames > 0) { + framesToReadOnce = std::min(framesToReadOnce, remainingFrames); + framesRead = decoder->read(framesToReadOnce, tmpBuf); + for (int itr = 0; itr < framesToReadOnce; itr++) { + memcpy(p, tmpBuf + itr * audioInfo.bytesPerFrame + channelID * bytesPerChannelInFrame, bytesPerChannelInFrame); + + p += bytesPerChannelInFrame; + } + remainingFrames -= framesToReadOnce; + }; + free(tmpBuf); + } while (false); + AudioDecoderManager::destroyDecoder(decoder); + return pcmData; +} diff --git a/cocos/audio/oalsoft/AudioEngine-soft.h b/cocos/audio/oalsoft/AudioEngine-soft.h new file mode 100644 index 0000000..0d49942 --- /dev/null +++ b/cocos/audio/oalsoft/AudioEngine-soft.h @@ -0,0 +1,89 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "audio/include/AudioDef.h" +#include "audio/oalsoft/AudioCache.h" +#include "audio/oalsoft/AudioPlayer.h" +#include "base/std/container/unordered_map.h" +#include "cocos/base/RefCounted.h" +#include "cocos/base/std/any.h" + +namespace cc { + +class Scheduler; + +#define MAX_AUDIOINSTANCES 32 + +class CC_DLL AudioEngineImpl : public RefCounted { +public: + AudioEngineImpl(); + ~AudioEngineImpl() override; + + bool init(); + int play2d(const ccstd::string &filePath, bool loop, float volume); + void setVolume(int audioID, float volume); + void setLoop(int audioID, bool loop); + bool pause(int audioID); + bool resume(int audioID); + void stop(int audioID); + void stopAll(); + float getDuration(int audioID); + float getDurationFromFile(const ccstd::string &filePath); + float getCurrentTime(int audioID); + bool setCurrentTime(int audioID, float time); + void setFinishCallback(int audioID, const std::function &callback); + + void uncache(const ccstd::string &filePath); + void uncacheAll(); + AudioCache *preload(const ccstd::string &filePath, const std::function &callback); + void update(float dt); + PCMHeader getPCMHeader(const char *url); + ccstd::vector getOriginalPCMBuffer(const char *url, uint32_t channelID); + +private: + bool checkAudioIdValid(int audioID); + void play2dImpl(AudioCache *cache, int audioID); + + ALuint _alSources[MAX_AUDIOINSTANCES]; + + //source,used + ccstd::unordered_map _alSourceUsed; + + //filePath,bufferInfo + ccstd::unordered_map _audioCaches; + + //audioID,AudioInfo + ccstd::unordered_map _audioPlayers; + std::mutex _threadMutex; + + bool _lazyInitLoop; + + int _currentAudioID; + std::weak_ptr _scheduler; +}; +} // namespace cc diff --git a/cocos/audio/oalsoft/AudioPlayer.cpp b/cocos/audio/oalsoft/AudioPlayer.cpp new file mode 100644 index 0000000..c5e2a4f --- /dev/null +++ b/cocos/audio/oalsoft/AudioPlayer.cpp @@ -0,0 +1,318 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioPlayer" + +#include "audio/oalsoft/AudioPlayer.h" +#include +#include +#include "audio/common/decoder/AudioDecoder.h" +#include "audio/common/decoder/AudioDecoderManager.h" +#include "audio/oalsoft/AudioCache.h" +#include "base/Log.h" +#include "base/memory/Memory.h" + +using namespace cc; // NOLINT + +namespace { +unsigned int gIdIndex = 0; +} + +AudioPlayer::AudioPlayer() +: _audioCache(nullptr), + _finishCallbak(nullptr), + _isDestroyed(false), + _removeByAudioEngine(false), + _ready(false), + _currTime(0.0F), + _streamingSource(false), + _rotateBufferThread(nullptr), + _timeDirty(false), + _isRotateThreadExited(false), + _id(++gIdIndex) { + memset(_bufferIds, 0, sizeof(_bufferIds)); +} + +AudioPlayer::~AudioPlayer() { + // CC_LOG_DEBUG("~AudioPlayer() (%p), id=%u", this, _id); + destroy(); + + if (_streamingSource) { + alDeleteBuffers(3, _bufferIds); + } +} + +void AudioPlayer::destroy() { + if (_isDestroyed) { + return; + } + + // CC_LOG_DEBUG("AudioPlayer::destroy begin, id=%u", _id); + + _isDestroyed = true; + + do { + if (_audioCache != nullptr) { + if (_audioCache->_state == AudioCache::State::INITIAL) { + CC_LOG_INFO("AudioPlayer::destroy, id=%u, cache isn't ready!", _id); + break; + } + + while (!_audioCache->_isLoadingFinished) { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + } + + // Wait for play2d to be finished. + _play2dMutex.lock(); + _play2dMutex.unlock(); + + if (_streamingSource) { + if (_rotateBufferThread != nullptr) { + while (!_isRotateThreadExited) { + _sleepCondition.notify_one(); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + if (_rotateBufferThread->joinable()) { + _rotateBufferThread->join(); + } + + delete _rotateBufferThread; + _rotateBufferThread = nullptr; + CC_LOG_DEBUG("rotateBufferThread exited!"); + } + } + } while (false); + + // CC_LOG_DEBUG("Before alSourceStop"); + alSourceStop(_alSource); + CHECK_AL_ERROR_DEBUG(); + // CC_LOG_DEBUG("Before alSourcei"); + alSourcei(_alSource, AL_BUFFER, 0); + CHECK_AL_ERROR_DEBUG(); + + _removeByAudioEngine = true; + + _ready = false; + // CC_LOG_DEBUG("AudioPlayer::destroy end, id=%u", _id); +} + +void AudioPlayer::setCache(AudioCache *cache) { + _audioCache = cache; +} + +bool AudioPlayer::play2d() { + _play2dMutex.lock(); + //CC_LOG_INFO("AudioPlayer::play2d, _alSource: %u, player id=%u", _alSource, _id); + + /*********************************************************************/ + /* Note that it may be in sub thread or in main thread. **/ + /*********************************************************************/ + bool ret = false; + do { + if (_audioCache->_state != AudioCache::State::READY) { + CC_LOG_ERROR("alBuffer isn't ready for play!"); + break; + } + + alSourcei(_alSource, AL_BUFFER, 0); + CHECK_AL_ERROR_DEBUG(); + alSourcef(_alSource, AL_PITCH, 1.0F); + CHECK_AL_ERROR_DEBUG(); + alSourcef(_alSource, AL_GAIN, _volume); + CHECK_AL_ERROR_DEBUG(); + alSourcei(_alSource, AL_LOOPING, AL_FALSE); + CHECK_AL_ERROR_DEBUG(); + + if (_audioCache->_queBufferFrames == 0) { + if (_loop) { + alSourcei(_alSource, AL_LOOPING, AL_TRUE); + CHECK_AL_ERROR_DEBUG(); + } + } else { + if (_currTime > _audioCache->_duration) { + _currTime = 0.F; // Target current start time is invalid, reset to 0. + } + alGenBuffers(3, _bufferIds); + + auto alError = alGetError(); + if (alError == AL_NO_ERROR) { + for (int index = 0; index < QUEUEBUFFER_NUM; ++index) { + alBufferData(_bufferIds[index], _audioCache->_format, _audioCache->_queBuffers[index], + _audioCache->_queBufferSize[index], _audioCache->_sampleRate); + } + CHECK_AL_ERROR_DEBUG(); + } else { + ALOGE("%s:alGenBuffers error code:%x", __FUNCTION__, alError); + break; + } + _streamingSource = true; + } + + { + std::unique_lock lk(_sleepMutex); + if (_isDestroyed) { + break; + } + + if (_streamingSource) { + alSourceQueueBuffers(_alSource, QUEUEBUFFER_NUM, _bufferIds); + CHECK_AL_ERROR_DEBUG(); + _rotateBufferThread = ccnew std::thread(&AudioPlayer::rotateBufferThread, this, + _audioCache->_queBufferFrames * QUEUEBUFFER_NUM + 1); + } else { + alSourcei(_alSource, AL_BUFFER, _audioCache->_alBufferId); + CHECK_AL_ERROR_DEBUG(); + } + + alSourcePlay(_alSource); + } + + auto alError = alGetError(); + if (alError != AL_NO_ERROR) { + ALOGE("%s:alSourcePlay error code:%x", __FUNCTION__, alError); + break; + } + + ALint state; + alGetSourcei(_alSource, AL_SOURCE_STATE, &state); + if (state != AL_PLAYING) { + ALOGE("state isn't playing, %d, %s, cache id=%u, player id=%u", state, _audioCache->_fileFullPath.c_str(), + _audioCache->_id, _id); + // abort playing if the state is incorrect + break; + } + _ready = true; + ret = true; + } while (false); + + if (!ret) { + _removeByAudioEngine = true; + } + + _play2dMutex.unlock(); + return ret; +} + +void AudioPlayer::rotateBufferThread(int offsetFrame) { + char *tmpBuffer = nullptr; + AudioDecoder *decoder = AudioDecoderManager::createDecoder(_audioCache->_fileFullPath.c_str()); + do { + BREAK_IF(decoder == nullptr || !decoder->open(_audioCache->_fileFullPath.c_str())); + + uint32_t framesRead = 0; + const uint32_t framesToRead = _audioCache->_queBufferFrames; + const uint32_t bufferSize = framesToRead * decoder->getBytesPerFrame(); + tmpBuffer = static_cast(malloc(bufferSize)); + memset(tmpBuffer, 0, bufferSize); + + if (offsetFrame != 0) { + decoder->seek(offsetFrame); + } + + ALint sourceState; + ALint bufferProcessed = 0; + bool needToExitThread = false; + + while (!_isDestroyed) { + alGetSourcei(_alSource, AL_SOURCE_STATE, &sourceState); + if (sourceState == AL_PLAYING) { + alGetSourcei(_alSource, AL_BUFFERS_PROCESSED, &bufferProcessed); + while (bufferProcessed > 0) { + bufferProcessed--; + if (_timeDirty) { + _timeDirty = false; + offsetFrame = static_cast(_currTime * decoder->getSampleRate()); + decoder->seek(offsetFrame); + } else { + _currTime += QUEUEBUFFER_TIME_STEP; + if (_currTime > _audioCache->_duration) { + if (_loop) { + _currTime = 0.0F; + } else { + _currTime = _audioCache->_duration; + } + } + } + + framesRead = decoder->readFixedFrames(framesToRead, tmpBuffer); + + if (framesRead == 0) { + if (_loop) { + decoder->seek(0); + framesRead = decoder->readFixedFrames(framesToRead, tmpBuffer); + } else { + needToExitThread = true; + break; + } + } + + ALuint bid; + alSourceUnqueueBuffers(_alSource, 1, &bid); + alBufferData(bid, _audioCache->_format, tmpBuffer, framesRead * decoder->getBytesPerFrame(), + decoder->getSampleRate()); + alSourceQueueBuffers(_alSource, 1, &bid); + } + } + + std::unique_lock lk(_sleepMutex); + if (_isDestroyed || needToExitThread) { + break; + } + + _sleepCondition.wait_for(lk, std::chrono::milliseconds(75)); + } + + } while (false); + + CC_LOG_INFO("Exit rotate buffer thread ..."); + if (decoder != nullptr) { + decoder->close(); + } + AudioDecoderManager::destroyDecoder(decoder); + free(tmpBuffer); + _isRotateThreadExited = true; + CC_LOG_INFO("%s exited.\n", __FUNCTION__); +} + +bool AudioPlayer::setLoop(bool loop) { + if (!_isDestroyed) { + _loop = loop; + return true; + } + + return false; +} + +bool AudioPlayer::setTime(float time) { + if (!_isDestroyed && time >= 0.0F && time < _audioCache->_duration) { + _currTime = time; + _timeDirty = true; + + return true; + } + return false; +} diff --git a/cocos/audio/oalsoft/AudioPlayer.h b/cocos/audio/oalsoft/AudioPlayer.h new file mode 100644 index 0000000..51e4356 --- /dev/null +++ b/cocos/audio/oalsoft/AudioPlayer.h @@ -0,0 +1,94 @@ +/**************************************************************************** + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include "base/std/container/string.h" +#ifdef OPENAL_PLAIN_INCLUDES + #include +#elif CC_PLATFORM == CC_PLATFORM_WINDOWS + #include +#elif CC_PLATFORM == CC_PLATFORM_OHOS + #include +#elif CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX + #include +#endif +#include "base/Macros.h" + +namespace cc { + +class AudioCache; +class AudioEngineImpl; + +class CC_DLL AudioPlayer { +public: + AudioPlayer(); + ~AudioPlayer(); + + void destroy(); + + //queue buffer related stuff + bool setTime(float time); + float getTime() { return _currTime; } + bool setLoop(bool loop); + +protected: + void setCache(AudioCache *cache); + void rotateBufferThread(int offsetFrame); + bool play2d(); + + AudioCache *_audioCache; + + float _volume; + bool _loop; + std::function _finishCallbak; + + bool _isDestroyed; + bool _removeByAudioEngine; + bool _ready; + ALuint _alSource; + + //play by circular buffer + float _currTime; + bool _streamingSource; + ALuint _bufferIds[3]; + std::thread *_rotateBufferThread; + std::condition_variable _sleepCondition; + std::mutex _sleepMutex; + bool _timeDirty; + bool _isRotateThreadExited; + + std::mutex _play2dMutex; + + unsigned int _id; + + friend class AudioEngineImpl; +}; + +} // namespace cc diff --git a/cocos/audio/ohos/AudioDecoderWav.cpp b/cocos/audio/ohos/AudioDecoderWav.cpp new file mode 100644 index 0000000..6fd0028 --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderWav.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#define LOG_TAG "AudioDecoderWav" + +#include "audio/ohos/AudioDecoderWav.h" +#include "audio/ohos/FsCallback.h" +#include "base/Log.h" + +namespace { +snd_callbacks wavCallbacks = { + .open = cc::ohosOpen, + .read = cc::ohosRead, + .seek = cc::ohosSeek, + .close = cc::ohosClose, + .tell = cc::ohosTell, +}; +} + +namespace cc { + +AudioDecoderWav::AudioDecoderWav() { + CC_LOG_DEBUG("Create AudioDecoderWav"); +} + +AudioDecoderWav::~AudioDecoderWav() { + close(); +} + +bool AudioDecoderWav::open(const char *path) { + _sndHandle = sf_open_read(path, &_sndInfo, &wavCallbacks, this); + _isOpened = (_sndHandle != nullptr) && _sndInfo.frames > 0; + if (!_isOpened) return false; + + _pcmHeader.channelCount = _sndInfo.channels; + _pcmHeader.sampleRate = _sndInfo.samplerate; + _pcmHeader.bytesPerFrame = 2 * _pcmHeader.channelCount; // short + _pcmHeader.totalFrames = _sndInfo.frames; + return true; +} + +void AudioDecoderWav::close() { + if (_sndHandle) { + sf_close(_sndHandle); + _sndHandle = nullptr; + } + _isOpened = false; +} + +uint32_t AudioDecoderWav::read(uint32_t framesToRead, char *pcmBuf) { + auto *output = reinterpret_cast(pcmBuf); + auto actualFrames = sf_readf_short(_sndHandle, output, framesToRead); + return actualFrames; +} + +bool AudioDecoderWav::seek(uint32_t frameOffset) { + off_t offset = sf_seek(_sndHandle, frameOffset, SEEK_SET); + return offset >= 0 && offset == frameOffset; +} + +uint32_t AudioDecoderWav::tell() const { + return sf_tell(_sndHandle); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/ohos/AudioDecoderWav.h b/cocos/audio/ohos/AudioDecoderWav.h new file mode 100644 index 0000000..4c6275d --- /dev/null +++ b/cocos/audio/ohos/AudioDecoderWav.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "audio/android/tinysndfile.h" +#include "audio/common/decoder/AudioDecoder.h" + +namespace cc { + +class AudioDecoderWav : public AudioDecoder { +public: + AudioDecoderWav(); + ~AudioDecoderWav() override; + + bool open(const char *path) override; + void close() override; + uint32_t read(uint32_t framesToRead, char *pcmBuf) override; + bool seek(uint32_t frameOffset) override; + uint32_t tell() const override; + +private: + SF_INFO _sndInfo; + SNDFILE *_sndHandle{}; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/ohos/FsCallback.cpp b/cocos/audio/ohos/FsCallback.cpp new file mode 100644 index 0000000..7b1fd9c --- /dev/null +++ b/cocos/audio/ohos/FsCallback.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "audio/ohos/FsCallback.h" + +#include +#include +#include "base/memory/Memory.h" +#include "platform/ohos/FileUtils-ohos.h" + +namespace { +inline cc::FileUtilsOHOS *getFU() { + return static_cast(cc::FileUtils::getInstance()); +} + +struct FatFd { + union { + FILE *fp; + RawFile *rf; + } file; + void *user; + bool isRawFile; +}; +} // namespace + +namespace cc { +void *ohosOpen(const char *path, void *user) { + bool isRawfile = false; + const auto newPath = getFU()->expandPath(path, &isRawfile); + auto *ret = ccnew FatFd(); + if (isRawfile) { + ret->file.rf = OpenRawFile(cc::FileUtilsOHOS::getResourceManager(), newPath.c_str()); + } else { + ret->file.fp = fopen(newPath.c_str(), "rb"); + } + ret->user = user; + ret->isRawFile = isRawfile; + return ret; +} + +size_t ohosRead(void *ptr, size_t size, size_t nmemb, void *datasource) { + auto *fatFd = static_cast(datasource); + if (fatFd->isRawFile) { + return ReadRawFile(fatFd->file.rf, ptr, size * nmemb) / size; + } + return fread(ptr, size, nmemb, fatFd->file.fp); +} + +int ohosSeek(void *datasource, long offset, int whence) { //NOLINT(google-runtime-int) + auto *fatFd = static_cast(datasource); + if (fatFd->isRawFile) { + return SeekRawFile(fatFd->file.rf, offset, whence); + } + return fseek(fatFd->file.fp, offset, whence); +} + +int ohosClose(void *datasource) { + auto *fatFd = static_cast(datasource); + int code = 0; + if (fatFd->isRawFile) { + CloseRawFile(fatFd->file.rf); + code = 0; + } else { + code = fclose(fatFd->file.fp); + } + delete fatFd; + return code; +} + +long ohosTell(void *datasource) { //NOLINT(google-runtime-int) + auto *fatFd = static_cast(datasource); + if (fatFd->isRawFile) { + return GetRawFileOffset(fatFd->file.rf); + } + return ftell(fatFd->file.fp); +} +} // namespace cc \ No newline at end of file diff --git a/cocos/audio/ohos/FsCallback.h b/cocos/audio/ohos/FsCallback.h new file mode 100644 index 0000000..b75ca87 --- /dev/null +++ b/cocos/audio/ohos/FsCallback.h @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +namespace cc { +void *ohosOpen(const char *path, void *user); + +size_t ohosRead(void *ptr, size_t size, size_t nmemb, void *datasource); + +int ohosSeek(void *datasource, long offset, int whence); //NOLINT(google-runtime-int) + +int ohosClose(void *datasource); + +long ohosTell(void *datasource); //NOLINT(google-runtime-int) + +} // namespace cc \ No newline at end of file diff --git a/cocos/base/Agent.h b/cocos/base/Agent.h new file mode 100644 index 0000000..48542ea --- /dev/null +++ b/cocos/base/Agent.h @@ -0,0 +1,56 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "Macros.h" + +namespace cc { + +template +class CC_DLL Agent : public Actor { +public: + Agent() noexcept = delete; + + explicit Agent(Actor *const actor) noexcept + : Actor(), _actor(actor) {} + + ~Agent() override = default; + + Agent(Agent const &) = delete; + + Agent(Agent &&) = delete; + + Agent &operator=(Agent const &) = delete; + + Agent &operator=(Agent &&) = delete; + + inline Actor *getActor() const noexcept { return _actor; } + +protected: + Actor *_actor{nullptr}; +}; + +} // namespace cc diff --git a/cocos/base/Assertf.h b/cocos/base/Assertf.h new file mode 100644 index 0000000..cea0270 --- /dev/null +++ b/cocos/base/Assertf.h @@ -0,0 +1,181 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include // NOLINT +#include +#include + +#if CC_DEBUG && !NDEBUG + + #if (CC_PLATFORM == CC_PLATFORM_WINDOWS) + #define CC_ASSERT_FORMAT(cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + char message[256] = {0}; \ + wchar_t messageTmp[256] = {0}; \ + snprintf(message, sizeof(message), "CC_ASSERT(%s) failed. " fmt "\n%s", #cond, ##__VA_ARGS__, __FUNCTION__); \ + std::copy(message, message + strlen(message), messageTmp); \ + _wassert(messageTmp, _CRT_WIDE(__FILE__), (unsigned)__LINE__); \ + } \ + } while (false) + + #define CC_ABORTF(fmt, ...) \ + do { \ + char message[256] = {0}; \ + wchar_t messageTmp[256] = {0}; \ + snprintf(message, sizeof(message), fmt, ##__VA_ARGS__); \ + std::copy(message, message + strlen(message), messageTmp); \ + _wassert(messageTmp, _CRT_WIDE(__FILE__), (unsigned)__LINE__); \ + } while (false) + + #elif (CC_PLATFORM == CC_PLATFORM_ANDROID) + #define CC_ASSERT_FORMAT(cond, fmt, ...) \ + do { \ + if (__builtin_expect(!(cond), 0)) { \ + char message[256] = {0}; \ + snprintf(message, sizeof(message), "CC_ASSERT(%s) failed. " fmt, #cond, ##__VA_ARGS__); \ + __assert2(__FILE__, __LINE__, __PRETTY_FUNCTION__, message); \ + } \ + } while (false) + #define CC_ABORTF(fmt, ...) \ + do { \ + char message[256] = {0}; \ + snprintf(message, sizeof(message), fmt, ##__VA_ARGS__); \ + __assert2(__FILE__, __LINE__, __PRETTY_FUNCTION__, message); \ + } while (false) + #elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + #define CC_ASSERT_FORMAT(cond, fmt, ...) \ + do { \ + if (__builtin_expect(!(cond), 0)) { \ + char message[256] = {0}; \ + snprintf(message, sizeof(message), "CC_ASSERT(%s) failed. " fmt, #cond, ##__VA_ARGS__); \ + __assert(message, __FILE__, __LINE__); \ + } \ + } while (false) + #define CC_ABORTF(fmt, ...) \ + do { \ + char message[256] = {0}; \ + snprintf(message, sizeof(message), fmt, ##__VA_ARGS__); \ + __assert(message, __FILE__, __LINE__); \ + } while (false) + #else + #define CC_ASSERT_FORMAT(cond, ...) assert(cond) + #define CC_ABORTF(fmt, ...) abort() + #endif + + // NOLINTNEXTLINE + #define _CC_ASSERT_(cond) assert(cond) + + // NOLINTNEXTLINE + #define _CC_ASSERTF_CMP(expr1, op, expr2, fmt, ...) \ + do { \ + CC_ASSERT_FORMAT((expr1)op(expr2), fmt, ##__VA_ARGS__); \ + } while (false) + + // NOLINTNEXTLINE + #define _CC_ASSERT_CMP(expr1, op, expr2) \ + do { \ + _CC_ASSERT_((expr1)op(expr2)); \ + } while (false) + + // CC_ASSERTF variants + #define CC_ASSERTF_EQ(expr1, expr2, fmt, ...) _CC_ASSERTF_CMP(expr1, ==, expr2, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_NE(expr1, expr2, fmt, ...) _CC_ASSERTF_CMP(expr1, !=, expr2, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_LE(expr1, expr2, fmt, ...) _CC_ASSERTF_CMP(expr1, <=, expr2, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_GE(expr1, expr2, fmt, ...) _CC_ASSERTF_CMP(expr1, >=, expr2, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_LT(expr1, expr2, fmt, ...) _CC_ASSERTF_CMP(expr1, <, expr2, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_GT(expr1, expr2, fmt, ...) _CC_ASSERTF_CMP(expr1, >, expr2, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_TRUE(expr1, fmt, ...) _CC_ASSERTF_CMP(expr1, ==, true, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_FALSE(expr1, fmt, ...) _CC_ASSERTF_CMP(expr1, ==, false, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_NULL(expr1, fmt, ...) _CC_ASSERTF_CMP(expr1, ==, nullptr, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_NOT_NULL(expr1, fmt, ...) _CC_ASSERTF_CMP(expr1, !=, nullptr, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_ZERO(expr1, fmt, ...) _CC_ASSERTF_CMP(expr1, ==, 0, fmt, ##__VA_ARGS__) + #define CC_ASSERTF_NONZERO(expr1, fmt, ...) _CC_ASSERTF_CMP(expr1, !=, 0, fmt, ##__VA_ARGS__) + // CC_ASSERT variants + #define CC_ASSERT_EQ(expr1, expr2) _CC_ASSERT_CMP(expr1, ==, expr2) + #define CC_ASSERT_NE(expr1, expr2) _CC_ASSERT_CMP(expr1, !=, expr2) + #define CC_ASSERT_LE(expr1, expr2) _CC_ASSERT_CMP(expr1, <=, expr2) + #define CC_ASSERT_GE(expr1, expr2) _CC_ASSERT_CMP(expr1, >=, expr2) + #define CC_ASSERT_LT(expr1, expr2) _CC_ASSERT_CMP(expr1, <, expr2) + #define CC_ASSERT_GT(expr1, expr2) _CC_ASSERT_CMP(expr1, >, expr2) + #define CC_ASSERT_TRUE(expr1) _CC_ASSERT_CMP(expr1, ==, true) + #define CC_ASSERT_FALSE(expr1) _CC_ASSERT_CMP(expr1, ==, false) + #define CC_ASSERT_NULL(expr1) _CC_ASSERT_CMP(expr1, ==, nullptr) + #define CC_ASSERT_NOT_NULL(expr1) _CC_ASSERT_CMP(expr1, !=, nullptr) + #define CC_ASSERT_ZERO(expr1) _CC_ASSERT_CMP(expr1, ==, 0) + #define CC_ASSERT_NONZERO(expr1) _CC_ASSERT_CMP(expr1, !=, 0) + /** + * @brief printf like assert + * + * CC_ASSERTF(1==2, "value %d should not be equal to %d", 1, 2); + * CC_ASSERTF_EQ(1, 3/3, "n equals to n"); + * CC_ASSERTF_NE(1, s, "not initial value"); + * + */ + #define CC_ASSERTF(cond, fmt, ...) CC_ASSERT_FORMAT(cond, fmt, ##__VA_ARGS__) + + /** + * CC_ABORT call abort() in debug mode. + * + * printf like abort: CC_ABORTF + * + * CC_ABORTF("Dead Code") + * CC_ABORTF("Invalidate state code %d", statusCode); + * CC_ABORTF("Should crash in debug mode") + */ + #define CC_ABORT() abort() + #define CC_ASSERT(cond) _CC_ASSERT_(cond) + +#else + #define CC_ASSERTF(...) ((void)0) + #define CC_ASSERTF_EQ(expr1, expr2, ...) ((void)0) + #define CC_ASSERTF_NE(expr1, expr2, ...) ((void)0) + #define CC_ASSERTF_LE(expr1, expr2, ...) ((void)0) + #define CC_ASSERTF_GE(expr1, expr2, ...) ((void)0) + #define CC_ASSERTF_LT(expr1, expr2, ...) ((void)0) + #define CC_ASSERTF_GT(expr1, expr2, ...) ((void)0) + #define CC_ASSERTF_TRUE(expr1, ...) ((void)0) + #define CC_ASSERTF_FALSE(expr1, ...) ((void)0) + #define CC_ASSERTF_NULL(expr1, ...) ((void)0) + #define CC_ASSERTF_NOT_NULL(expr1, ...) ((void)0) + #define CC_ASSERTF_ZERO(expr1, ...) ((void)0) + #define CC_ASSERTF_NONZERO(expr1, ...) ((void)0) + #define CC_ASSERT(...) ((void)0) + #define CC_ASSERT_EQ(expr1, expr2, ...) ((void)0) + #define CC_ASSERT_NE(expr1, expr2, ...) ((void)0) + #define CC_ASSERT_LE(expr1, expr2, ...) ((void)0) + #define CC_ASSERT_GE(expr1, expr2, ...) ((void)0) + #define CC_ASSERT_LT(expr1, expr2, ...) ((void)0) + #define CC_ASSERT_GT(expr1, expr2, ...) ((void)0) + #define CC_ASSERT_TRUE(expr1, ...) ((void)0) + #define CC_ASSERT_FALSE(expr1, ...) ((void)0) + #define CC_ASSERT_NULL(expr1, ...) ((void)0) + #define CC_ASSERT_NOT_NULL(expr1, ...) ((void)0) + #define CC_ASSERT_ZERO(expr1, ...) ((void)0) + #define CC_ASSERT_NONZERO(expr1, ...) ((void)0) + #define CC_ABORTF(...) ((void)0) // ignored in release builds + #define CC_ABORT() ((void)0) // ignored in release builds +#endif diff --git a/cocos/base/BinaryArchive.cpp b/cocos/base/BinaryArchive.cpp new file mode 100644 index 0000000..ac47e66 --- /dev/null +++ b/cocos/base/BinaryArchive.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "BinaryArchive.h" +#include "base/Assertf.h" + +namespace cc { + +bool BinaryInputArchive::load(char *data, uint32_t size) { + CC_ASSERT(!!_stream); + return _stream.rdbuf()->sgetn(data, size) == size; +} + +void BinaryInputArchive::move(uint32_t length) { + CC_ASSERT(!!_stream); + _stream.ignore(length); +} + +void BinaryOutputArchive::save(const char *data, uint32_t size) { + CC_ASSERT(!!_stream); + auto len = _stream.rdbuf()->sputn(data, size); + CC_ASSERT(len == size); +} + +} // namespace cc diff --git a/cocos/base/BinaryArchive.h b/cocos/base/BinaryArchive.h new file mode 100644 index 0000000..9f79cab --- /dev/null +++ b/cocos/base/BinaryArchive.h @@ -0,0 +1,93 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +namespace cc { + +/** + * Binary input stream archive. + */ +class BinaryInputArchive { +public: + explicit BinaryInputArchive(std::istream &stream) : _stream(stream) {} + ~BinaryInputArchive() = default; + + /** + * Read data from stream. + * @param data Pointer to data address to read. + * @param size Length of the data. + */ + bool load(char *data, uint32_t size); + + /** + * Read arithmetic data from stream. + * @param val Data to read. + */ + template >> + bool load(T &val) { + return load(reinterpret_cast(std::addressof(val)), sizeof(T)); + } + + /** + * Skip data length + * @param length Skip length + */ + void move(uint32_t length); + +private: + std::istream &_stream; +}; + +/** + * Binary output stream archive. + */ +class BinaryOutputArchive { +public: + explicit BinaryOutputArchive(std::ostream &stream) : _stream(stream) {} + ~BinaryOutputArchive() = default; + + /** + * Write data to stream. + * @param data Pointer to data address to write. + * @param size Length of the data. + */ + void save(const char *data, uint32_t size); + + /** + * Write arithmetic data to stream. + * @param val Data to write. + */ + template >> + void save(const T &v) { + save(reinterpret_cast(std::addressof(v)), sizeof(T)); + } + +private: + std::ostream &_stream; +}; + +} // namespace cc diff --git a/cocos/base/Compressed.cpp b/cocos/base/Compressed.cpp new file mode 100644 index 0000000..4f738ec --- /dev/null +++ b/cocos/base/Compressed.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include + +#include "base/Compressed.h" +#include "base/Utils.h" + +// compressed file header +static const uint32_t COMPRESSED_HEADER_LENGTH = 4; +static const uint32_t COMPRESSED_MIPMAP_LEVEL_COUNT_LENGTH = 4; +static const uint32_t COMPRESSED_MIPMAP_DATA_SIZE_LENGTH = 4; +static const uint32_t COMPRESSED_MAGIC = 0x50494d43; + +bool compressedIsValid(const unsigned char* pFile) { + const uint32_t magic = static_cast(pFile[0]) + + static_cast(pFile[1]) * 256 + + static_cast(pFile[2]) * 65536 + + static_cast(pFile[3]) * 16777216; + + return magic == COMPRESSED_MAGIC; +} + +uint32_t getChunkNumbers(const unsigned char* pFile) { + return static_cast(pFile[COMPRESSED_MIPMAP_LEVEL_COUNT_LENGTH + 0]) + + static_cast(pFile[COMPRESSED_MIPMAP_LEVEL_COUNT_LENGTH + 1]) * 256 + + static_cast(pFile[COMPRESSED_MIPMAP_LEVEL_COUNT_LENGTH + 2]) * 65536 + + static_cast(pFile[COMPRESSED_MIPMAP_LEVEL_COUNT_LENGTH + 3]) * 16777216; +} + +uint32_t getChunkSizes(const unsigned char* pFile, uint32_t level) { + const uint32_t byteOffset = COMPRESSED_HEADER_LENGTH + COMPRESSED_MIPMAP_LEVEL_COUNT_LENGTH + COMPRESSED_MIPMAP_DATA_SIZE_LENGTH * level; + return (pFile[byteOffset + 0]) + + (pFile[byteOffset + 1]) * 256 + + (pFile[byteOffset + 2]) * 65536 + + (pFile[byteOffset + 3]) * 16777216; +} + +unsigned char* getChunk(const unsigned char* pFile, uint32_t level) { + unsigned char* dstData = nullptr; + const auto chunkCount = getChunkNumbers(pFile); + const auto compressedFileHeaderLength = COMPRESSED_HEADER_LENGTH + COMPRESSED_MIPMAP_LEVEL_COUNT_LENGTH + COMPRESSED_MIPMAP_DATA_SIZE_LENGTH * chunkCount; + + uint32_t byteOffset = compressedFileHeaderLength; + for (uint32_t i = 0; i < chunkCount; ++i) { + const auto chunkSize = getChunkSizes(pFile, i); + if (i == level) { + dstData = static_cast(malloc(chunkSize * sizeof(unsigned char))); + memcpy(dstData, pFile + byteOffset, chunkSize); + break; + } + byteOffset += chunkSize; + } + + return dstData; +} diff --git a/cocos/base/Compressed.h b/cocos/base/Compressed.h new file mode 100644 index 0000000..6e02e1b --- /dev/null +++ b/cocos/base/Compressed.h @@ -0,0 +1,38 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include + +// Check if a compressed file header is correctly formatted +bool compressedIsValid(const unsigned char *pFile); + +// Read the chunks count from compressed header +uint32_t getChunkNumbers(const unsigned char *pFile); + +// Read the chunk size from compressed header +uint32_t getChunkSizes(const unsigned char *pFile, uint32_t level); + +// Read the chunk from compressed header +unsigned char *getChunk(const unsigned char *pFile, uint32_t level); diff --git a/cocos/base/Config.h b/cocos/base/Config.h new file mode 100644 index 0000000..31805b9 --- /dev/null +++ b/cocos/base/Config.h @@ -0,0 +1,69 @@ +/**************************************************************************** + Copyright (c) 2008-2010 Ricardo Quesada + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2011 Zynga Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +/** Support PNG or not. If your application don't use png format picture, you can undefine this macro to save package size. +*/ +#ifndef CC_USE_PNG + #define CC_USE_PNG 1 +#endif // CC_USE_PNG + +/** Support JPEG or not. If your application don't use jpeg format picture, you can undefine this macro to save package size. + */ +#ifndef CC_USE_JPEG + #define CC_USE_JPEG 1 +#endif // CC_USE_JPEG + +/** Support webp or not. If your application don't use webp format picture, you can undefine this macro to save package size. + */ +#ifndef CC_USE_WEBP + #define CC_USE_WEBP 1 +#endif // CC_USE_WEBP + +/** Support EditBox + */ +#ifndef CC_USE_EDITBOX + #define CC_USE_EDITBOX 1 +#endif + +#ifndef CC_FILEUTILS_APPLE_ENABLE_OBJC + #define CC_FILEUTILS_APPLE_ENABLE_OBJC 1 +#endif + +#ifndef CC_ENABLE_CACHE_JSB_FUNC_RESULT + #define CC_ENABLE_CACHE_JSB_FUNC_RESULT 1 +#endif + +#ifndef USE_MEMORY_LEAK_DETECTOR + #define USE_MEMORY_LEAK_DETECTOR 0 +#endif + +#ifndef CC_USE_PROFILER + #define CC_USE_PROFILER 0 +#endif diff --git a/cocos/base/Data.cpp b/cocos/base/Data.cpp new file mode 100644 index 0000000..b755846 --- /dev/null +++ b/cocos/base/Data.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/Data.h" +#include +#include "base/Log.h" + +namespace cc { + +const Data Data::NULL_DATA; + +Data::Data() = default; + +Data::Data(Data &&other) noexcept { + // CC_LOG_INFO("In the move constructor of Data."); + move(other); +} + +Data::Data(const Data &other) { + // CC_LOG_INFO("In the copy constructor of Data."); + copy(other._bytes, other._size); +} + +Data::~Data() { + // CC_LOG_INFO("deallocing Data: %p", this); + clear(); +} + +Data &Data::operator=(const Data &other) { + // CC_LOG_INFO("In the copy assignment of Data."); + if (this != &other) { + copy(other._bytes, other._size); + } + return *this; +} + +Data &Data::operator=(Data &&other) noexcept { + // CC_LOG_INFO("In the move assignment of Data."); + move(other); + return *this; +} + +void Data::move(Data &other) { + clear(); + + _bytes = other._bytes; + _size = other._size; + + other._bytes = nullptr; + other._size = 0; +} + +bool Data::isNull() const { + return (_bytes == nullptr || _size == 0); +} + +uint8_t *Data::getBytes() const { + return _bytes; +} + +uint32_t Data::getSize() const { + return _size; +} + +void Data::copy(const unsigned char *bytes, uint32_t size) { + clear(); + + if (size > 0) { + _size = size; + _bytes = static_cast(malloc(sizeof(unsigned char) * _size)); + memcpy(_bytes, bytes, _size); + } +} + +void Data::fastSet(unsigned char *bytes, uint32_t size) { + free(_bytes); + _bytes = bytes; + _size = size; +} + +void Data::resize(uint32_t size) { + CC_ASSERT(size); + if (_size == size) { + return; + } + _size = size; + _bytes = static_cast(realloc(_bytes, sizeof(unsigned char) * _size)); +} + +void Data::clear() { + free(_bytes); + _bytes = nullptr; + _size = 0; +} + +uint8_t *Data::takeBuffer(uint32_t *size) { + auto *buffer = getBytes(); + if (size) { + *size = getSize(); + } + + _bytes = nullptr; + _size = 0; + return buffer; +} + +} // namespace cc diff --git a/cocos/base/Data.h b/cocos/base/Data.h new file mode 100644 index 0000000..951070e --- /dev/null +++ b/cocos/base/Data.h @@ -0,0 +1,145 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" + +namespace cc { + +class CC_DLL Data { + friend class Properties; + +public: + /** + * This parameter is defined for convenient reference if a null Data object is needed. + */ + static const Data NULL_DATA; + + /** + * Constructor of Data. + */ + Data(); + + /** + * Copy constructor of Data. + */ + Data(const Data &other); + + /** + * Copy constructor of Data. + */ + Data(Data &&other) noexcept; + + /** + * Destructor of Data. + */ + ~Data(); + + /** + * Overloads of operator=. + */ + Data &operator=(const Data &other); + + /** + * Overloads of operator=. + */ + Data &operator=(Data &&other) noexcept; + + /** + * Gets internal bytes of Data. It will return the pointer directly used in Data, so don't delete it. + * + * @return Pointer of bytes used internal in Data. + */ + uint8_t *getBytes() const; + + /** + * Gets the size of the bytes. + * + * @return The size of bytes of Data. + */ + uint32_t getSize() const; + + /** Copies the buffer pointer and its size. + * @note This method will copy the whole buffer. + * Developer should free the pointer after invoking this method. + * @see Data::fastSet + */ + void copy(const unsigned char *bytes, uint32_t size); + + /** Fast set the buffer pointer and its size. Please use it carefully. + * @param bytes The buffer pointer, note that it have to be allocated by 'malloc' or 'calloc', + * since in the destructor of Data, the buffer will be deleted by 'free'. + * @note 1. This method will move the ownship of 'bytes'pointer to Data, + * 2. The pointer should not be used outside after it was passed to this method. + * @see Data::copy + */ + void fastSet(unsigned char *bytes, uint32_t size); + + void resize(uint32_t size); + + /** + * Clears data, free buffer and reset data size. + */ + void clear(); + + /** + * Check whether the data is null. + * + * @return True if the Data is null, false if not. + */ + bool isNull() const; + + /** + * Get the internal buffer of data and set data to empty state. + * + * The ownership of the buffer removed from the data object. + * That is the user have to free the returned buffer. + * The data object is set to empty state, that is internal buffer is set to nullptr + * and size is set to zero. + * Usage: + * @code + * Data d; + * // ... + * uint32_t size; + * uint8_t* buffer = d.takeBuffer(&size); + * // use buffer and size + * free(buffer); + * @endcode + * + * @param size Will fill with the data buffer size in bytes, if you do not care buffer size, pass nullptr. + * @return the internal data buffer, free it after use. + */ + unsigned char *takeBuffer(uint32_t *size = nullptr); + +private: + void move(Data &other); //NOLINT + + uint8_t *_bytes{nullptr}; + uint32_t _size{0}; +}; + +} // namespace cc diff --git a/cocos/base/DeferredReleasePool.cpp b/cocos/base/DeferredReleasePool.cpp new file mode 100644 index 0000000..e5d8baf --- /dev/null +++ b/cocos/base/DeferredReleasePool.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/DeferredReleasePool.h" +#include "base/Log.h" +namespace cc { + +ccstd::vector DeferredReleasePool::managedObjectArray{}; + +void DeferredReleasePool::add(RefCounted *object) { + DeferredReleasePool::managedObjectArray.push_back(object); +} + +void DeferredReleasePool::clear() { + for (const auto &obj : DeferredReleasePool::managedObjectArray) { + obj->release(); + } + DeferredReleasePool::managedObjectArray.clear(); +} + +bool DeferredReleasePool::contains(RefCounted *object) { + for (const auto &obj : DeferredReleasePool::managedObjectArray) { // NOLINT(readability-use-anyofallof) // remove after using C++20 + if (obj == object) { + return true; + } + } + return false; +} + +void DeferredReleasePool::dump() { + CC_LOG_DEBUG("number of managed object %ul\n", DeferredReleasePool::managedObjectArray.size()); + CC_LOG_DEBUG("%20s%20s%20s", "Object pointer", "Object id", "reference count"); + for (const auto &obj : DeferredReleasePool::managedObjectArray) { + CC_UNUSED_PARAM(obj); + CC_LOG_DEBUG("%20p%20u\n", obj, obj->getRefCount()); + } +} + +} // namespace cc diff --git a/cocos/base/DeferredReleasePool.h b/cocos/base/DeferredReleasePool.h new file mode 100644 index 0000000..e121b8e --- /dev/null +++ b/cocos/base/DeferredReleasePool.h @@ -0,0 +1,69 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/RefCounted.h" +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +namespace cc { + +class CC_DLL DeferredReleasePool { +public: + static void add(RefCounted *object); + static void clear(); + + /** + * Checks whether the autorelease pool contains the specified object. + * + * @param object The object to be checked. + * @return True if the autorelease pool contains the object, false if not + */ + static bool contains(RefCounted *object); + + /** + * Dump the objects that are put into the autorelease pool. It is used for debugging. + * + * The result will look like: + * Object pointer address object id reference count + */ + static void dump(); + +private: + /** + * The underlying array of object managed by the pool. + * + * Although Array retains the object once when an object is added, proper + * Ref::release() is called outside the array to make sure that the pool + * does not affect the managed object's reference count. So an object can + * be destructed properly by calling Ref::release() even if the object + * is in the pool. + */ + static ccstd::vector managedObjectArray; +}; + +} // namespace cc diff --git a/cocos/base/HasMemberFunction.h b/cocos/base/HasMemberFunction.h new file mode 100644 index 0000000..3e0900a --- /dev/null +++ b/cocos/base/HasMemberFunction.h @@ -0,0 +1,97 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +/* This macro is used for checking whether a member function exists in a class in compiling time. +Usage: + If we have a class called `MyClass` as follows: + + ```c++ + class MyClass { + public: + int myMethod(bool a, float b) { return 100; } + }; +``` + +Test code: +```c++ + #include "base/HasMemberFunction.h" + + CC_DEFINE_HAS_MEMBER_FUNC(myMethod) + + template + void myTest(T* arg0) { + if constexpr (has_myMethod::value) { + // <1>: DO SOMETHING if T owns `myMethod` function. + // ... + } else { + // <2>: DO SOMETHING if T doesn't own `myMethod` function. + // ... + } + } + + static int myTestEntry() { + MyClass a; + myTest(&a); // --> Go to <1> + int b; + myTest(&b; // --> Go to <2> + } + +``` +*/ + +#define CC_DEFINE_HAS_MEMBER_FUNC(memFunc) \ + template \ + struct has_##memFunc { \ + static_assert( \ + std::integral_constant::value, \ + "Second template parameter needs to be of function type."); \ + }; \ + \ + template \ + struct has_##memFunc { \ + private: \ + template \ + static constexpr auto check(T*) \ + -> typename std::is_same< \ + decltype(std::declval().memFunc(std::declval()...)), \ + Ret>::type; \ + \ + template \ + static constexpr std::false_type check(...); \ + typedef decltype(check(0)) type; \ + \ + public: \ + static constexpr bool value = type::value; \ + }; + +namespace cc { + +CC_DEFINE_HAS_MEMBER_FUNC(setScriptObject) +CC_DEFINE_HAS_MEMBER_FUNC(getScriptObject) + +} // namespace cc diff --git a/cocos/base/IndexHandle.h b/cocos/base/IndexHandle.h new file mode 100644 index 0000000..58d31bf --- /dev/null +++ b/cocos/base/IndexHandle.h @@ -0,0 +1,89 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace cc { + +template ::value>> +class IndexHandle { +public: + struct Hasher { + inline std::size_t operator()(IndexHandle const &s) const noexcept { return s._index; } + }; + + using IndexType = Index; + + IndexHandle() noexcept = default; + explicit IndexHandle(IndexType index) noexcept : _index(index) {} + + inline bool isValid() const noexcept; + inline void clear() noexcept; + + inline bool operator<(IndexHandle const &rhs) const noexcept; + inline bool operator==(IndexHandle const &rhs) const noexcept; + inline bool operator!=(IndexHandle const &rhs) const noexcept; + inline operator IndexType() const noexcept; // NOLINT(google-explicit-constructor) we need this implicitly + + static IndexType constexpr UNINITIALIZED{std::numeric_limits::max()}; + +private: + IndexType _index{UNINITIALIZED}; +}; + +template +bool IndexHandle::isValid() const noexcept { + return _index != UNINITIALIZED; +} + +template +void IndexHandle::clear() noexcept { + _index = UNINITIALIZED; +} + +template +bool IndexHandle::operator<(IndexHandle const &rhs) const noexcept { + return _index < rhs._index; +} + +template +bool IndexHandle::operator==(IndexHandle const &rhs) const noexcept { + return (_index == rhs._index); +} + +template +bool IndexHandle::operator!=(IndexHandle const &rhs) const noexcept { + return !operator==(rhs); +} + +template +IndexHandle::operator IndexType() const noexcept { + return _index; +} + +} // namespace cc diff --git a/cocos/base/Locked.h b/cocos/base/Locked.h new file mode 100644 index 0000000..c841647 --- /dev/null +++ b/cocos/base/Locked.h @@ -0,0 +1,91 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +namespace cc { + +template +class UniqueLockedRef { +public: + UniqueLockedRef(T *t, LK *mtx) : _data(t), _mtx(mtx) { + _mtx->lock(); + } + UniqueLockedRef(UniqueLockedRef &&t) noexcept { + _data = t._data; + _mtx = t._mtx; + t._data = nullptr; + t._mtx = nullptr; + } + + UniqueLockedRef &operator=(UniqueLockedRef &&t) noexcept { + _data = t._data; + _mtx = t._mtx; + t._data = nullptr; + t._mtx = nullptr; + return *this; + } + + UniqueLockedRef(const T &t) = delete; + UniqueLockedRef &operator=(const UniqueLockedRef &) = delete; + + ~UniqueLockedRef() { + if (_mtx) { + _mtx->unlock(); + } + } + + T *operator->() { + return _data; + } + T &operator*() { + return *_data; + } + +private: + T *_data{}; + LK *_mtx{}; +}; + +template +class Locked { +public: + Locked() = default; + Locked(const Locked &) = delete; + Locked(Locked &&) = delete; + Locked &operator=(const Locked &) = delete; + Locked &operator=(Locked &&) = delete; + UniqueLockedRef lock() { + UniqueLockedRef ref(&_data, &_mutex); + return std::move(ref); + } + +private: + LK _mutex{}; + T _data{}; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/base/Log.cpp b/cocos/base/Log.cpp new file mode 100644 index 0000000..034b0b8 --- /dev/null +++ b/cocos/base/Log.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Log.h" + +#include +#include +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + + #define COLOR_FATAL FOREGROUND_INTENSITY | FOREGROUND_RED + #define COLOR_ERROR FOREGROUND_RED + #define COLOR_WARN 6 + #define COLOR_INFO FOREGROUND_GREEN | FOREGROUND_BLUE + #define COLOR_DEBUG 7 + #define COLOR_NORMAL 8 + #define SET_CONSOLE_TEXT_COLOR(color) SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color) + +#elif (CC_PLATFORM == CC_PLATFORM_ANDROID) + #include +#elif CC_PLATFORM == CC_PLATFORM_OHOS + #include +#elif (CC_PLATFORM == CC_PLATFORM_OPENHARMONY) + #include +#endif + +namespace cc { + +#define LOG_USE_TIMESTAMP +#if (CC_DEBUG == 1) +LogLevel Log::slogLevel = LogLevel::LEVEL_DEBUG; +#else +LogLevel Log::slogLevel = LogLevel::INFO; +#endif + +FILE *Log::slogFile = nullptr; +const ccstd::vector LOG_LEVEL_DESCS{"FATAL", "ERROR", "WARN", "INFO", "DEBUG"}; + +void Log::setLogFile(const ccstd::string &filename) { +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) + if (slogFile) { + fclose(slogFile); + } + + slogFile = fopen(filename.c_str(), "w"); + + if (slogFile) { + ccstd::string msg; + msg += "------------------------------------------------------\n"; + + struct tm *tm_time; + time_t ct_time; + time(&ct_time); + tm_time = localtime(&ct_time); + char dateBuffer[256] = {0}; + snprintf(dateBuffer, sizeof(dateBuffer), "LOG DATE: %04d-%02d-%02d %02d:%02d:%02d\n", + tm_time->tm_year + 1900, + tm_time->tm_mon + 1, + tm_time->tm_mday, + tm_time->tm_hour, + tm_time->tm_min, + tm_time->tm_sec); + + msg += dateBuffer; + + msg += "------------------------------------------------------\n"; + + fputs(msg.c_str(), slogFile); + fflush(slogFile); + } +#endif +} + +void Log::close() { + if (slogFile) { + fclose(slogFile); + slogFile = nullptr; + } +} + +void Log::logMessage(LogType type, LogLevel level, const char *formats, ...) { + char buff[4096]; + char *p = buff; + char *last = buff + sizeof(buff) - 3; + +#if defined(LOG_USE_TIMESTAMP) + struct tm *tmTime; + time_t ctTime; + time(&ctTime); + tmTime = localtime(&ctTime); + + p += sprintf(p, "%02d:%02d:%02d ", tmTime->tm_hour, tmTime->tm_min, tmTime->tm_sec); +#endif + + p += sprintf(p, "[%s]: ", LOG_LEVEL_DESCS[static_cast(level)].c_str()); + + va_list args; + va_start(args, formats); + // p += StringUtil::vprintf(p, last, formats, args); + + std::ptrdiff_t count = (last - p); + int ret = vsnprintf(p, count, formats, args); + if (ret >= count - 1) { + p += (count - 1); + } else if (ret >= 0) { + p += ret; + } + + va_end(args); + + *p++ = '\n'; + *p = 0; + + if (slogFile) { + fputs(buff, slogFile); + fflush(slogFile); + } + +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) + WCHAR wszBuf[4096] = {0}; + MultiByteToWideChar(CP_UTF8, 0, buff, -1, wszBuf, sizeof(wszBuf)); + + WORD color; + switch (level) { + case LogLevel::LEVEL_DEBUG: color = COLOR_DEBUG; break; + case LogLevel::INFO: color = COLOR_INFO; break; + case LogLevel::WARN: color = COLOR_WARN; break; + case LogLevel::ERR: color = COLOR_ERROR; break; + case LogLevel::FATAL: color = COLOR_FATAL; break; + default: color = COLOR_INFO; + } + SET_CONSOLE_TEXT_COLOR(color); + wprintf(L"%s", wszBuf); + SET_CONSOLE_TEXT_COLOR(COLOR_NORMAL); + + OutputDebugStringW(wszBuf); +#elif (CC_PLATFORM == CC_PLATFORM_ANDROID) + android_LogPriority priority; + switch (level) { + case LogLevel::LEVEL_DEBUG: + priority = ANDROID_LOG_DEBUG; + break; + case LogLevel::INFO: + priority = ANDROID_LOG_INFO; + break; + case LogLevel::WARN: + priority = ANDROID_LOG_WARN; + break; + case LogLevel::ERR: + priority = ANDROID_LOG_ERROR; + break; + case LogLevel::FATAL: + priority = ANDROID_LOG_FATAL; + break; + default: + priority = ANDROID_LOG_INFO; + } + + __android_log_write(priority, (type == LogType::KERNEL ? "Cocos" : "CocosScript"), buff); +#elif (CC_PLATFORM == CC_PLATFORM_OHOS) + const char *typeStr = (type == LogType::KERNEL ? "Cocos %{public}s" : "CocosScript %{public}s"); + switch (level) { + case LogLevel::LEVEL_DEBUG: + HILOG_DEBUG(LOG_APP, typeStr, (const char *)buff); + break; + case LogLevel::INFO: + HILOG_INFO(LOG_APP, typeStr, buff); + break; + case LogLevel::WARN: + HILOG_WARN(LOG_APP, typeStr, buff); + break; + case LogLevel::ERR: + HILOG_ERROR(LOG_APP, typeStr, buff); + break; + case LogLevel::FATAL: + HILOG_FATAL(LOG_APP, typeStr, buff); + break; + default: + HILOG_DEBUG(LOG_APP, typeStr, buff); + } +#elif (CC_PLATFORM == CC_PLATFORM_OPENHARMONY) + ::LogLevel ohosLoglevel = ::LogLevel::LOG_DEBUG; + switch (level) { + case LogLevel::LEVEL_DEBUG: + ohosLoglevel = ::LogLevel::LOG_DEBUG; + break; + case LogLevel::INFO: + ohosLoglevel = ::LogLevel::LOG_INFO; + break; + case LogLevel::WARN: + ohosLoglevel = ::LogLevel::LOG_WARN; + break; + case LogLevel::ERR: + ohosLoglevel = ::LogLevel::LOG_ERROR; + break; + case LogLevel::FATAL: + ohosLoglevel = ::LogLevel::LOG_FATAL; + break; + default: + ohosLoglevel = ::LogLevel::LOG_INFO; + break; + } + OH_LOG_Print(LOG_APP, ohosLoglevel, LOG_DOMAIN, "HMG_LOG", "%{public}s", buff); +#else + fputs(buff, stdout); +#endif +#if CC_REMOTE_LOG + logRemote(buff); +#endif +} + +} // namespace cc diff --git a/cocos/base/Log.h b/cocos/base/Log.h new file mode 100644 index 0000000..3613fc2 --- /dev/null +++ b/cocos/base/Log.h @@ -0,0 +1,79 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "Macros.h" +#include "base/std/container/string.h" + +namespace cc { + +enum class LogType { + KERNEL, + SCRIPT, + COUNT, +}; + +enum class LogLevel { + FATAL, + ERR, + WARN, + INFO, + LEVEL_DEBUG, // DEBUG is a macro on windows, so use LEVEL_DEBUG instead. + COUNT, +}; + +class CC_DLL Log { +public: + static LogLevel slogLevel; // for read only + + static inline void setLogLevel(LogLevel level) { slogLevel = level; } + static inline FILE *getLogFile() { return slogFile; } + static void setLogFile(const ccstd::string &filename); + static void close(); + static void logMessage(LogType type, LogLevel level, const char *formats, ...); + +private: + static void logRemote(const char *msg); + static FILE *slogFile; +}; + +} // namespace cc + +#define CC_LOG_DEBUG(formats, ...) \ + if (cc::Log::slogLevel >= cc::LogLevel::LEVEL_DEBUG) cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, formats, ##__VA_ARGS__) +#define CC_LOG_INFO(formats, ...) \ + if (cc::Log::slogLevel >= cc::LogLevel::INFO) cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::INFO, formats, ##__VA_ARGS__) +#define CC_LOG_WARNING(formats, ...) \ + if (cc::Log::slogLevel >= cc::LogLevel::WARN) cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::WARN, formats, ##__VA_ARGS__) +#define DO_CC_LOG_ERROR(formats, ...) \ + if (cc::Log::slogLevel >= cc::LogLevel::ERR) cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::ERR, formats, ##__VA_ARGS__) +#define CC_LOG_FATAL(formats, ...) \ + if (cc::Log::slogLevel >= cc::LogLevel::FATAL) cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::FATAL, formats, ##__VA_ARGS__) + +#define CC_LOG_ERROR(formats, ...) \ + do { \ + DO_CC_LOG_ERROR("[ERROR] file %s: line %d ", __FILE__, __LINE__); \ + DO_CC_LOG_ERROR(formats, ##__VA_ARGS__); \ + } while (0) diff --git a/cocos/base/LogRemote.cpp b/cocos/base/LogRemote.cpp new file mode 100644 index 0000000..b082050 --- /dev/null +++ b/cocos/base/LogRemote.cpp @@ -0,0 +1,336 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/Log.h" + +#if CC_REMOTE_LOG + #include + #include + #include + #include "platform/FileUtils.h" + #include "rapidjson/document.h" + + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #include + #include + #pragma comment(lib, "ws2_32.lib") + + #else + #include + #include + #include + #include + #include + #endif + +namespace { + + // #define AUTO_TEST_CONFIG_FILE "auto-test-config.json" // v1 + #define AUTO_TEST_CONFIG_FILE "testConfig.json" // v2 + + #define OLD_ATC_KEY_CONFIG "ServerConfig" + #define OLD_ATC_KEY_IP "IP" + #define OLD_ATC_KEY_PORT "PORT" + #define OLD_ATC_KEY_PLANID "planId" + #define OLD_ATC_KEY_FLAGID "flagId" + + #define ATC_KEY_CONFIG "localServer" + #define ATC_KEY_IP "ip" + #define ATC_KEY_PORT "port" + #define ATC_KEY_JOBID "jobId" + #define ATC_KEY_PLATFORMS "platforms" + #define ATC_KEY_PLATFORM_INDEX "platformIndex" + +enum class UdpLogClientState { + UNINITIALIZED, + INITIALIZED, + CONFIGURED, + OK, + DONE, // FAILED +}; + +uint64_t logId = 0; + +/** +* Parse auto-test-config.json to get ServerConfig.IP & ServerConfig.PORT +* Logs will be formated with 5 fields +* 1. testId +* 2. clientId +* 3. bootId, the boot timestamp +* 4. sequence number of the message +* 5. log content +* +* These parts are joined with '\n'. +* +* The log text is sent to the server via UDP. Due to the nature of the UDP protocol, +* there may be packet loss/missequencing issues. +*/ +class UdpLogClient { +public: + UdpLogClient() { + init(); + tryParseConfig(); + } + + ~UdpLogClient() { + deinit(); + } + + void sendFullLog(const std::string_view &msg) { + if (_status == UdpLogClientState::DONE) { + return; + } + std::stringstream ss; + ss << _testID << std::endl + << _clientID << std::endl + << _bootID << std::endl + << ++logId << std::endl + << msg; + sendLog(ss.str()); + } + +private: + void init() { + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + WSAData wsa; + if (WSAStartup(MAKEWORD(1, 2), &wsa) != 0) { + printf("WSAStartup failed, code: %d\n", WSAGetLastError()); + _status = UdpLogClientState::DONE; + return; + } + #endif + _status = UdpLogClientState::INITIALIZED; + } + + void deinit() { + if (_status == UdpLogClientState::OK) { + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + closesocket(_sock); + WSACleanup(); + #else + close(_sock); + #endif + } + } + void tryParseConfig() { + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + if (_status != UdpLogClientState::INITIALIZED) { + return; + } + #endif + auto *fu = cc::FileUtils::getInstance(); + if (!fu) { + // engine is not ready, retry later + return; + } + + if (!fu->isFileExist(AUTO_TEST_CONFIG_FILE)) { + _status = UdpLogClientState::DONE; + return; + } + auto content = fu->getStringFromFile(AUTO_TEST_CONFIG_FILE); + rapidjson::Document doc; + doc.Parse(content.c_str(), content.length()); + + if (doc.HasParseError()) { + auto code = doc.GetParseError(); + _status = UdpLogClientState::DONE; + return; + } + + if (doc.HasMember(OLD_ATC_KEY_CONFIG)) { + // parse clientID & testID + if (doc.HasMember(OLD_ATC_KEY_FLAGID)) { + _clientID = doc[OLD_ATC_KEY_FLAGID].GetString(); + } else { + _clientID = "flagId is not set!"; + } + + if (doc.HasMember(OLD_ATC_KEY_PLANID)) { + _testID = doc[OLD_ATC_KEY_PLANID].GetString(); + } else { + _testID = "planId is not set!"; + } + + // parse ip & port + rapidjson::Value &cfg = doc[OLD_ATC_KEY_CONFIG]; + if (!cfg.HasMember(OLD_ATC_KEY_IP) || !cfg.HasMember(OLD_ATC_KEY_PORT)) { + _status = UdpLogClientState::DONE; + return; + } + const char *remoteIp = cfg[OLD_ATC_KEY_IP].GetString(); + // The `PORT` property is used by other service and the next port is for log collection. + int remotePort = cfg[OLD_ATC_KEY_PORT].GetInt() + 1; + setServerAddr(remoteIp, remotePort); + } else if (doc.HasMember(ATC_KEY_CONFIG)) { + if (doc.HasMember(ATC_KEY_JOBID)) { + _testID = doc[ATC_KEY_JOBID].GetString(); + } else { + _testID = "jobId is not set!"; + } + if (doc.HasMember(ATC_KEY_PLATFORMS)) { + rapidjson::Value &platforms = doc[ATC_KEY_PLATFORMS]; + if (!platforms.IsArray() || platforms.Size() < 1) { + _clientID = "array platforms is empty"; + } else { + rapidjson::Value &plt = platforms[0]; + if (!plt.HasMember(ATC_KEY_PLATFORM_INDEX)) { + _clientID = "platforms[0] not key platformIndex"; + } else { + rapidjson::Value &index = plt[ATC_KEY_PLATFORM_INDEX]; + _clientID = index.IsInt() ? std::to_string(index.GetInt()) : index.GetString(); + } + } + } else { + _clientID = "platforms not set!"; + } + + // parse ip & port + rapidjson::Value &cfg = doc[ATC_KEY_CONFIG]; + if (!cfg.HasMember(ATC_KEY_IP) || !cfg.HasMember(ATC_KEY_PORT)) { + _status = UdpLogClientState::DONE; + return; + } + const char *remoteIp = cfg[ATC_KEY_IP].GetString(); + // The `PORT` property is used by other service and the next port is for log collection. + int remotePort = cfg[ATC_KEY_PORT].GetInt() + 1; + setServerAddr(remoteIp, remotePort); + } else { + _status = UdpLogClientState::DONE; + return; + } + + _bootID = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + _status = UdpLogClientState::CONFIGURED; + } + + void setServerAddr(const std::string_view &addr, int port) { + memset(&_serverAddr, 0, sizeof(_serverAddr)); + _serverAddr.sin_family = AF_INET; + _serverAddr.sin_addr.s_addr = inet_addr(addr.data()); + _serverAddr.sin_port = htons(port); + } + + static auto getErrorCode() { + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + return WSAGetLastError(); + #else + return errno; + #endif + } + + void ensureInitSocket() { + if (_status == UdpLogClientState::INITIALIZED) { + tryParseConfig(); + } + if (_status != UdpLogClientState::CONFIGURED) { + return; + } + memset(&_localAddr, 0, sizeof(_localAddr)); + _localAddr.sin_family = AF_INET; + _localAddr.sin_addr.s_addr = INADDR_ANY; + _localAddr.sin_port = 0; // bind random port + + if ((_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + auto errorCode = getErrorCode(); + _status = UdpLogClientState::DONE; + printf("create socket failed, code: %d\n", errorCode); + return; + } + if (bind(_sock, reinterpret_cast(&_localAddr), sizeof(_localAddr))) { + _status = UdpLogClientState::DONE; + auto errorCode = getErrorCode(); + printf("bind socket failed, code: %d\n", errorCode); + return; + } + if (connect(_sock, reinterpret_cast(&_serverAddr), sizeof(_serverAddr))) { + auto errorCode = getErrorCode(); + printf("connect socket failed, code: %d\n", errorCode); + _status = UdpLogClientState::DONE; + return; + } + + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + u_long mode = 1; + if (ioctlsocket(_sock, FIONBIO, &mode)) { + auto errorCode = getErrorCode(); + printf("set nonblock failed, code: %d\n", errorCode); + // continue + } + #else + int flags = fcntl(_sock, F_GETFL); + if (fcntl(_sock, F_SETFL, flags | O_NONBLOCK)) { + auto errorCode = getErrorCode(); + printf("set nonblock failed, code: %d\n", errorCode); + // continue + } + #endif + _status = UdpLogClientState::OK; + } + + void sendLog(const std::string_view &msg) { + ensureInitSocket(); + if (_status == UdpLogClientState::OK) { + send(_sock, msg.data(), msg.size(), 0); + } + } + + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + SOCKET _sock{INVALID_SOCKET}; + #else + int _sock{-1}; + #endif + sockaddr_in _serverAddr; + sockaddr_in _localAddr; + UdpLogClientState _status{UdpLogClientState::UNINITIALIZED}; + std::string _testID; + std::string _clientID; + uint64_t _bootID{0}; +}; + +void sendLogThroughUDP(const std::string_view &msg) { + static UdpLogClient remote; + remote.sendFullLog(msg); +} + +} // namespace + +#endif + +namespace cc { + +void Log::logRemote(const char *msg) { +#if CC_REMOTE_LOG + sendLogThroughUDP(msg); +#endif +} + +} // namespace cc diff --git a/cocos/base/Macros.h b/cocos/base/Macros.h new file mode 100644 index 0000000..36fcbc4 --- /dev/null +++ b/cocos/base/Macros.h @@ -0,0 +1,411 @@ +/**************************************************************************** + Copyright (c) 2008-2010 Ricardo Quesada + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2011 Zynga Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include // To include uint8_t, uint16_t and so on. + +#include "Assertf.h" + +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) + #if defined(CC_STATIC) + #define CC_DLL + #else + #if defined(_USRDLL) + #define CC_DLL __declspec(dllexport) + #else /* use a DLL library */ + #define CC_DLL __declspec(dllimport) + #endif + #endif +#else + #define CC_DLL +#endif + +/** @def CC_DEGREES_TO_RADIANS + converts degrees to radians + */ +#define CC_DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__)*0.01745329252f) // PI / 180 + +/** @def CC_RADIANS_TO_DEGREES + converts radians to degrees + */ +#define CC_RADIANS_TO_DEGREES(__ANGLE__) ((__ANGLE__)*57.29577951f) // PI * 180 + +#ifndef FLT_EPSILON + #define FLT_EPSILON 1.192092896e-07F +#endif // FLT_EPSILON + +/** +Helper macros which converts 4-byte little/big endian +integral number to the machine native number representation + +It should work same as apples CFSwapInt32LittleToHost(..) +*/ + +/// when define returns true it means that our architecture uses big endian +#define CC_HOST_IS_BIG_ENDIAN (bool)(*(unsigned short *)"\0\xff" < 0x100) +#define CC_SWAP32(i) ((i & 0x000000ff) << 24 | (i & 0x0000ff00) << 8 | (i & 0x00ff0000) >> 8 | (i & 0xff000000) >> 24) +#define CC_SWAP16(i) ((i & 0x00ff) << 8 | (i & 0xff00) >> 8) +#define CC_SWAP_INT32_LITTLE_TO_HOST(i) ((CC_HOST_IS_BIG_ENDIAN == true) ? CC_SWAP32(i) : (i)) +#define CC_SWAP_INT16_LITTLE_TO_HOST(i) ((CC_HOST_IS_BIG_ENDIAN == true) ? CC_SWAP16(i) : (i)) +#define CC_SWAP_INT32_BIG_TO_HOST(i) ((CC_HOST_IS_BIG_ENDIAN == true) ? (i) : CC_SWAP32(i)) +#define CC_SWAP_INT16_BIG_TO_HOST(i) ((CC_HOST_IS_BIG_ENDIAN == true) ? (i) : CC_SWAP16(i)) + +// new callbacks based on C++11 +#define CC_CALLBACK_0(__selector__, __target__, ...) std::bind(&__selector__, __target__, ##__VA_ARGS__) +#define CC_CALLBACK_1(__selector__, __target__, ...) std::bind(&__selector__, __target__, std::placeholders::_1, ##__VA_ARGS__) +#define CC_CALLBACK_2(__selector__, __target__, ...) std::bind(&__selector__, __target__, std::placeholders::_1, std::placeholders::_2, ##__VA_ARGS__) +#define CC_CALLBACK_3(__selector__, __target__, ...) std::bind(&__selector__, __target__, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__) + +// Generic macros + +#define CC_BREAK_IF(cond) \ + if (cond) break + +/** @def CC_DEPRECATED_ATTRIBUTE + * Only certain compilers support __attribute__((deprecated)). + */ +#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))) + #define CC_DEPRECATED_ATTRIBUTE __attribute__((deprecated)) +#elif _MSC_VER >= 1400 // vs 2005 or higher + #define CC_DEPRECATED_ATTRIBUTE __declspec(deprecated) +#else + #define CC_DEPRECATED_ATTRIBUTE +#endif + +/** @def CC_DEPRECATED(...) + * Macro to mark things deprecated as of a particular version + * can be used with arbitrary parameters which are thrown away. + * e.g. CC_DEPRECATED(4.0) or CC_DEPRECATED(4.0, "not going to need this anymore") etc. + */ +#define CC_DEPRECATED(...) CC_DEPRECATED_ATTRIBUTE + +#ifdef __GNUC__ + #define CC_UNUSED __attribute__((unused)) +#else + #define CC_UNUSED +#endif + +#define CC_UNUSED_PARAM(unusedparam) (void)unusedparam + +/** @def CC_FORMAT_PRINTF(formatPos, argPos) + * Only certain compiler support __attribute__((format)) + * + * @param formatPos 1-based position of format string argument. + * @param argPos 1-based position of first format-dependent argument. + */ +#if defined(__GNUC__) && (__GNUC__ >= 4) + #define CC_FORMAT_PRINTF(formatPos, argPos) __attribute__((__format__(printf, formatPos, argPos))) +#elif defined(__has_attribute) + #if __has_attribute(format) + #define CC_FORMAT_PRINTF(formatPos, argPos) __attribute__((__format__(printf, formatPos, argPos))) + #else + #define CC_FORMAT_PRINTF(formatPos, argPos) + #endif // __has_attribute(format) +#else + #define CC_FORMAT_PRINTF(formatPos, argPos) +#endif + +// Initial compiler-related stuff to set. +#define CC_COMPILER_MSVC 1 +#define CC_COMPILER_CLANG 2 +#define CC_COMPILER_GNUC 3 + +// CPU Architecture +#define CC_CPU_UNKNOWN 0 +#define CC_CPU_X86 1 +#define CC_CPU_PPC 2 +#define CC_CPU_ARM 3 +#define CC_CPU_MIPS 4 + +// 32-bits or 64-bits CPU +#define CC_CPU_ARCH_32 1 +#define CC_CPU_ARCH_64 2 + +// Mode +#define CC_MODE_DEBUG 1 +#define CC_MODE_RELEASE 2 + +// Compiler type and version recognition +#if defined(_MSC_VER) + #define CC_COMPILER CC_COMPILER_MSVC +#elif defined(__clang__) + #define CC_COMPILER CC_COMPILER_CLANG +#elif defined(__GNUC__) + #define CC_COMPILER CC_COMPILER_GNUC +#else + #error "Unknown compiler. Abort!" +#endif + +#if INTPTR_MAX == INT32_MAX + #define CC_CPU_ARCH CC_CPU_ARCH_32 +#else + #define CC_CPU_ARCH CC_CPU_ARCH_64 +#endif + +#if defined(__arm64__) || defined(__aarch64__) + #define CC_ARCH_ARM64 1 +#else + #define CC_ARCH_ARM64 0 +#endif + +// CC_HAS_ARM64_FP16 set to 1 if the architecture provides an IEEE compliant ARM fp16 type +#if CC_ARCH_ARM64 + #ifndef CC_HAS_ARM64_FP16 + #if defined(__ARM_FP16_FORMAT_IEEE) + #define CC_HAS_ARM64_FP16 1 + #else + #define CC_HAS_ARM64_FP16 0 + #endif + #endif +#endif + +// CC_HAS_ARM64_FP16_VECTOR_ARITHMETIC set to 1 if the architecture supports Neon vector intrinsics for fp16. +#if CC_ARCH_ARM64 + #ifndef CC_HAS_ARM64_FP16_VECTOR_ARITHMETIC + #if defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) + #define CC_HAS_ARM64_FP16_VECTOR_ARITHMETIC 1 + #else + #define CC_HAS_ARM64_FP16_VECTOR_ARITHMETIC 0 + #endif + #endif +#endif + +// CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC set to 1 if the architecture supports Neon scalar intrinsics for fp16. +#if CC_ARCH_ARM64 + #ifndef CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC + #if defined(__ARM_FEATURE_FP16_SCALAR_ARITHMETIC) + #define CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC 1 + #else + #define CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC 0 + #endif + #endif +#endif + +// Disable MSVC warning +#if (CC_COMPILER == CC_COMPILER_MSVC) + #pragma warning(disable : 4251 4275 4819) + #ifndef _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif + #ifndef _SCL_SECURE_NO_DEPRECATE + #define _SCL_SECURE_NO_DEPRECATE + #endif +#endif + +#define CC_CACHELINE_SIZE 64 + +#if (CC_COMPILER == CC_COMPILER_MSVC) + // MSVC ENABLE/DISABLE WARNING DEFINITION + #define CC_DISABLE_WARNINGS() \ + __pragma(warning(push, 0)) + + #define CC_ENABLE_WARNINGS() \ + __pragma(warning(pop)) +#elif (CC_COMPILER == CC_COMPILER_GNUC) + // GCC ENABLE/DISABLE WARNING DEFINITION + #define CC_DISABLE_WARNINGS() \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wall\"") \ + _Pragma("clang diagnostic ignored \"-Wextra\"") \ + _Pragma("clang diagnostic ignored \"-Wtautological-compare\"") + + #define CC_ENABLE_WARNINGS() \ + _Pragma("GCC diagnostic pop") +#elif (CC_COMPILER == CC_COMPILER_CLANG) + // CLANG ENABLE/DISABLE WARNING DEFINITION + #define CC_DISABLE_WARNINGS() \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wall\"") \ + _Pragma("clang diagnostic ignored \"-Wextra\"") \ + _Pragma("clang diagnostic ignored \"-Wtautological-compare\"") + + #define CC_ENABLE_WARNINGS() \ + _Pragma("clang diagnostic pop") +#endif + +#define CC_DISALLOW_ASSIGN(TypeName) \ + TypeName &operator=(const TypeName &) = delete; \ + TypeName &operator=(TypeName &&) = delete; + +#define CC_DISALLOW_COPY_MOVE_ASSIGN(TypeName) \ + TypeName(const TypeName &) = delete; \ + TypeName(TypeName &&) = delete; \ + CC_DISALLOW_ASSIGN(TypeName) + +#if (CC_COMPILER == CC_COMPILER_MSVC) + #define CC_ALIGN(N) __declspec(align(N)) + #define CC_CACHE_ALIGN __declspec(align(CC_CACHELINE_SIZE)) + #define CC_PACKED_ALIGN(N) __declspec(align(N)) + + #define CC_ALIGNED_DECL(type, var, alignment) __declspec(align(alignment)) type var + + #define CC_READ_COMPILER_BARRIER() _ReadBarrier() + #define CC_WRITE_COMPILER_BARRIER() _WriteBarrier() + #define CC_COMPILER_BARRIER() _ReadWriteBarrier() + + #define CC_READ_MEMORY_BARRIER() MemoryBarrier() + #define CC_WRITE_MEMORY_BARRIER() MemoryBarrier() + #define CC_MEMORY_BARRIER() MemoryBarrier() + + #define CC_CPU_READ_MEMORY_BARRIER() \ + do { \ + __asm { lfence} \ + } while (0) + #define CC_CPU_WRITE_MEMORY_BARRIER() \ + do { \ + __asm { sfence} \ + } while (0) + #define CC_CPU_MEMORY_BARRIER() \ + do { \ + __asm { mfence} \ + } while (0) + +#elif (CC_COMPILER == CC_COMPILER_GNUC) || (CC_COMPILER == CC_COMPILER_CLANG) + #define CC_ALIGN(N) __attribute__((__aligned__((N)))) + #define CC_CACHE_ALIGN __attribute__((__aligned__((CC_CACHELINE_SIZE)))) + #define CC_PACKED_ALIGN(N) __attribute__((packed, aligned(N))) + + #define CC_ALIGNED_DECL(type, var, alignment) type var __attribute__((__aligned__(alignment))) + + #define CC_READ_COMPILER_BARRIER() \ + do { \ + __asm__ __volatile__("" \ + : \ + : \ + : "memory"); \ + } while (0) + #define CC_WRITE_COMPILER_BARRIER() \ + do { \ + __asm__ __volatile__("" \ + : \ + : \ + : "memory"); \ + } while (0) + #define CC_COMPILER_BARRIER() \ + do { \ + __asm__ __volatile__("" \ + : \ + : \ + : "memory"); \ + } while (0) + + #define CC_READ_MEMORY_BARRIER() \ + do { \ + __sync_synchronize(); \ + } while (0) + #define CC_WRITE_MEMORY_BARRIER() \ + do { \ + __sync_synchronize(); \ + } while (0) + #define CC_MEMORY_BARRIER() \ + do { \ + __sync_synchronize(); \ + } while (0) + + #define CC_CPU_READ_MEMORY_BARRIER() \ + do { \ + __asm__ __volatile__("lfence" \ + : \ + : \ + : "memory"); \ + } while (0) + #define CC_CPU_WRITE_MEMORY_BARRIER() \ + do { \ + __asm__ __volatile__("sfence" \ + : \ + : \ + : "memory"); \ + } while (0) + #define CC_CPU_MEMORY_BARRIER() \ + do { \ + __asm__ __volatile__("mfence" \ + : \ + : \ + : "memory"); \ + } while (0) + +#else + #error "Unsupported compiler!" +#endif + +/* Stack-alignment + If macro __CC_SIMD_ALIGN_STACK defined, means there requests + special code to ensure stack align to a 16-bytes boundary. + + Note: + This macro can only guarantee callee stack pointer (esp) align + to a 16-bytes boundary, but not that for frame pointer (ebp). + Because most compiler might use frame pointer to access to stack + variables, so you need to wrap those alignment required functions + with extra function call. + */ +#if defined(__INTEL_COMPILER) + // For intel's compiler, simply calling alloca seems to do the right + // thing. The size of the allocated block seems to be irrelevant. + #define CC_SIMD_ALIGN_STACK() _alloca(16) + #define CC_SIMD_ALIGN_ATTRIBUTE + +#elif (CC_CPU == CC_CPU_X86) && (CC_COMPILER == CC_COMPILER_GNUC || CC_COMPILER == CC_COMPILER_CLANG) && (CC_CPU_ARCH != CC_CPU_ARCH_64) + // mark functions with GCC attribute to force stack alignment to 16 bytes + #define CC_SIMD_ALIGN_ATTRIBUTE __attribute__((force_align_arg_pointer)) +#elif (CC_COMPILER == CC_COMPILER_MSVC) + // Fortunately, MSVC will align the stack automatically + #define CC_SIMD_ALIGN_ATTRIBUTE +#else + #define CC_SIMD_ALIGN_ATTRIBUTE +#endif + +// mode +#if (defined(_DEBUG) || defined(DEBUG)) && (CC_PLATFORM == CC_PLATFORM_WINDOWS) + #define CC_MODE CC_MODE_DEBUG +#else + #define CC_MODE CC_MODE_RELEASE +#endif + +#define CC_TOSTR(s) #s + +#if defined(__GNUC__) && __GNUC__ >= 4 + #define CC_PREDICT_TRUE(x) __builtin_expect(!!(x), 1) + #define CC_PREDICT_FALSE(x) __builtin_expect(!!(x), 0) +#else + #define CC_PREDICT_TRUE(x) (x) + #define CC_PREDICT_FALSE(x) (x) +#endif + +#if defined(_MSC_VER) + #define CC_FORCE_INLINE __forceinline +#elif defined(__GNUC__) || defined(__clang__) + #define CC_FORCE_INLINE inline __attribute__((always_inline)) +#else + #if defined(__cplusplus) || defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ + #define CC_FORCE_INLINE static inline + #elif + #define CC_FORCE_INLINE inline + #endif +#endif diff --git a/cocos/base/Object.h b/cocos/base/Object.h new file mode 100755 index 0000000..9c769f6 --- /dev/null +++ b/cocos/base/Object.h @@ -0,0 +1,35 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace cc { + +class Object { +public: + Object() = default; + virtual ~Object() = default; +}; + +} // namespace cc diff --git a/cocos/base/Ptr.h b/cocos/base/Ptr.h new file mode 100644 index 0000000..bda8043 --- /dev/null +++ b/cocos/base/Ptr.h @@ -0,0 +1,226 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +// Originally the class is from WebRTC. +// https://chromium.googlesource.com/external/webrtc/+/master/api/scoped_refptr.h +// + +// A smart pointer class for reference counted objects. Use this class instead +// of calling addRef and Release manually on a reference counted object to +// avoid common memory leaks caused by forgetting to Release an object +// reference. Sample usage: +// +// class MyFoo : public RefCounted { +// ... +// }; +// +// void some_function() { +// IntrusivePtr foo = ccnew MyFoo(); +// foo->Method(param); +// // `foo` is released when this function returns +// } +// +// void some_other_function() { +// IntrusivePtr foo = ccnew MyFoo(); +// ... +// foo = nullptr; // explicitly releases `foo` +// ... +// if (foo) +// foo->Method(param); +// } +// +// The above examples show how IntrusivePtr acts like a pointer to T. +// Given two IntrusivePtr classes, it is also possible to exchange +// references between the two objects, like so: +// +// { +// IntrusivePtr a = ccnew MyFoo(); +// IntrusivePtr b; +// +// b.swap(a); +// // now, `b` references the MyFoo object, and `a` references null. +// } +// +// To make both `a` and `b` in the above example reference the same MyFoo +// object, simply use the assignment operator: +// +// { +// IntrusivePtr a = ccnew MyFoo(); +// IntrusivePtr b; +// +// b = a; +// // now, `a` and `b` each own a reference to the same MyFoo object. +// } +// + +#pragma once + +#include +#include "cocos/base/std/hash/hash_fwd.hpp" + +namespace cc { + +template +class IntrusivePtr { +public: + using element_type = T; + + IntrusivePtr() { + } + + IntrusivePtr(T *p) : _ptr(p) { // NOLINT + if (_ptr) { + _ptr->addRef(); + } + } + + IntrusivePtr(const IntrusivePtr &r) : _ptr(r._ptr) { + if (_ptr) { + _ptr->addRef(); + } + } + + template + IntrusivePtr(const IntrusivePtr &r) : _ptr(r.get()) { // NOLINT + if (_ptr) { + _ptr->addRef(); + } + } + + // Move constructors. + IntrusivePtr(IntrusivePtr &&r) noexcept : _ptr(r.release()) { + } + + template + IntrusivePtr(IntrusivePtr &&r) noexcept : _ptr(r.release()) { // NOLINT + } + + ~IntrusivePtr() { + if (_ptr) { + _ptr->release(); + } + } + + T *get() const { return _ptr; } + operator T *() const { return _ptr; } // NOLINT + T &operator*() const { return *_ptr; } + T *operator->() const { return _ptr; } + + // As reference count is 1 after creating a RefCounted object, so do not have to + // invoke p->addRef(); + IntrusivePtr &operator=(T *p) { + reset(p); + return *this; + } + + IntrusivePtr &operator=(const IntrusivePtr &r) { // NOLINT + return *this = r._ptr; // NOLINT + } + + template + IntrusivePtr &operator=(const IntrusivePtr &r) { + return *this = r.get(); // NOLINT + } + + IntrusivePtr &operator=(IntrusivePtr &&r) noexcept { + IntrusivePtr(std::move(r)).swap(*this); + return *this; + } + + template + IntrusivePtr &operator=(IntrusivePtr &&r) noexcept { + IntrusivePtr(std::move(r)).swap(*this); + return *this; + } + + bool operator==(std::nullptr_t) { + return _ptr == nullptr; + } + + bool operator==(T *r) { + return _ptr == r; + } + + bool operator!=(std::nullptr_t) { + return _ptr != nullptr; + } + + bool operator!=(T *r) { + return _ptr != r; + } + + void reset() noexcept { + if (_ptr) { + _ptr->release(); + } + _ptr = nullptr; + } + + void reset(T *p) { + // AddRef first so that self assignment should work + if (p) { + p->addRef(); + } + if (_ptr) { + _ptr->release(); + } + _ptr = p; + } + + void swap(T **pp) noexcept { + T *p = _ptr; + _ptr = *pp; + *pp = p; + } + + void swap(IntrusivePtr &r) noexcept { swap(&r._ptr); } + +private: + // Returns the (possibly null) raw pointer, and makes the scoped_refptr hold a + // null pointer, all without touching the reference count of the underlying + // pointed-to object. The object is still reference counted, and the caller of + // release() is now the proud owner of one reference, so it is responsible for + // calling Release() once on the object when no longer using it. + T *release() { + T *retVal = _ptr; + _ptr = nullptr; + return retVal; + } + +protected: + T *_ptr{nullptr}; +}; + +} // namespace cc + +namespace ccstd { + +template +struct hash> { + hash_t operator()(const cc::IntrusivePtr &val) const noexcept { + return hash{}(val.get()); + } +}; + +} // namespace ccstd diff --git a/cocos/base/Random.h b/cocos/base/Random.h new file mode 100644 index 0000000..7707bb5 --- /dev/null +++ b/cocos/base/Random.h @@ -0,0 +1,122 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include + +#include "base/Macros.h" + +namespace cc { + +/** + * @class RandomHelper + * @brief A helper class for creating random number. + */ +class CC_DLL RandomHelper { +public: + template + static inline T randomReal(T min, T max) { + std::uniform_real_distribution dist(min, max); + auto &mt = RandomHelper::getEngine(); + return dist(mt); + } + + template + static inline T randomInt(T min, T max) { + std::uniform_int_distribution dist(min, max); + auto &mt = RandomHelper::getEngine(); + return dist(mt); + } + +private: + static inline std::mt19937 &getEngine() { + static std::random_device seedGen; + static std::mt19937 engine(seedGen()); + return engine; + } +}; + +/** + * Returns a random value between `min` and `max`. + */ +template +inline T random(T min, T max) { + return RandomHelper::randomInt(min, max); +} + +template <> +inline float random(float min, float max) { + return RandomHelper::randomReal(min, max); +} + +template <> +inline long double random(long double min, long double max) { + return RandomHelper::randomReal(min, max); +} + +template <> +inline double random(double min, double max) { + return RandomHelper::randomReal(min, max); +} + +/** + * Returns a random int between 0 and RAND_MAX. + */ +inline int random() { + return cc::random(0, static_cast(RAND_MAX)); +}; + +/** + * Returns a random float between -1 and 1. + * It can be seeded using std::srand(seed); + */ +inline float randMinus1_1() { + // IDEA: using the new c++11 random engine generator + // without a proper way to set a seed is not useful. + // Resorting to the old random method since it can + // be seeded using std::srand() + return ((std::rand() / (float)RAND_MAX) * 2) - 1; + + // return cc::random(-1.f, 1.f); +}; + +/** + * Returns a random float between 0 and 1. + * It can be seeded using std::srand(seed); + */ +inline float rand0_1() { + // IDEA: using the new c++11 random engine generator + // without a proper way to set a seed is not useful. + // Resorting to the old random method since it can + // be seeded using std::srand() + return std::rand() / (float)RAND_MAX; + + // return cc::random(0.f, 1.f); +}; + +} // namespace cc diff --git a/cocos/base/RefCounted.cpp b/cocos/base/RefCounted.cpp new file mode 100644 index 0000000..fe80190 --- /dev/null +++ b/cocos/base/RefCounted.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/RefCounted.h" + +#if CC_REF_LEAK_DETECTION + #include // std::find + #include "base/Log.h" + #include "base/std/container/list.h" +#endif + +namespace cc { + +#if CC_REF_LEAK_DETECTION +static void trackRef(RefCounted *ref); +static void untrackRef(RefCounted *ref); +#endif + +RefCounted::RefCounted() { // NOLINT(modernize-use-equals-default) +#if CC_REF_LEAK_DETECTION + trackRef(this); +#endif +} + +RefCounted::~RefCounted() { // NOLINT(modernize-use-equals-default) +#if CC_REF_LEAK_DETECTION + untrackRef(this); +#endif +} + +void RefCounted::addRef() { + ++_referenceCount; +} + +void RefCounted::release() { + CC_ASSERT_GT(_referenceCount, 0); + --_referenceCount; + + if (_referenceCount == 0) { + delete this; + } +} + +unsigned int RefCounted::getRefCount() const { + return _referenceCount; +} + +#if CC_REF_LEAK_DETECTION + +static ccstd::list __refAllocationList; + +void RefCounted::printLeaks() { + // Dump Ref object memory leaks + if (__refAllocationList.empty()) { + CC_LOG_INFO("[memory] All Ref objects successfully cleaned up (no leaks detected).\n"); + } else { + CC_LOG_INFO("[memory] WARNING: %d Ref objects still active in memory.\n", (int)__refAllocationList.size()); + + for (const auto &ref : __refAllocationList) { + CC_ASSERT(ref); + const char *type = typeid(*ref).name(); + CC_LOG_INFO("[memory] LEAK: Ref object '%s' still active with reference count %d.\n", (type ? type : ""), ref->getRefCount()); + } + } +} + +static void trackRef(RefCounted *ref) { + CC_ASSERT(ref); + + // Create memory allocation record. + __refAllocationList.push_back(ref); +} + +static void untrackRef(RefCounted *ref) { + auto iter = std::find(__refAllocationList.begin(), __refAllocationList.end(), ref); + if (iter == __refAllocationList.end()) { + CC_LOG_INFO("[memory] CORRUPTION: Attempting to free (%s) with invalid ref tracking record.\n", typeid(*ref).name()); + return; + } + + __refAllocationList.erase(iter); +} + +#endif // #if CC_REF_LEAK_DETECTION + +} // namespace cc diff --git a/cocos/base/RefCounted.h b/cocos/base/RefCounted.h new file mode 100644 index 0000000..9220251 --- /dev/null +++ b/cocos/base/RefCounted.h @@ -0,0 +1,105 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" + +#define CC_REF_LEAK_DETECTION 0 + +namespace cc { + +class RefCounted; + +/** + * Interface that defines how to clone an Ref. + */ +class CC_DLL Clonable { +public: + /** Returns a copy of the Ref. */ + virtual Clonable *clone() const = 0; + + virtual ~Clonable() = default; +}; + +/** + * Ref is used for reference count management. If a class inherits from Ref, + * then it is easy to be shared in different places. + */ +class CC_DLL RefCounted { +public: + virtual ~RefCounted(); + + /** + * Retains the ownership. + * + * This increases the Ref's reference count. + * + * @see release, autorelease + */ + void addRef(); + + /** + * Releases the ownership immediately. + * + * This decrements the Ref's reference count. + * + * If the reference count reaches 0 after the decrement, this Ref is + * destructed. + * + * @see retain, autorelease + */ + void release(); + + /** + * Returns the Ref's current reference count. + * + * @returns The Ref's reference count. + */ + unsigned int getRefCount() const; + +protected: + /** + * Constructor + * + * The Ref's reference count is 1 after construction. + */ + RefCounted(); + + /// count of references + unsigned int _referenceCount{0}; + + // Memory leak diagnostic data (only included when CC_REF_LEAK_DETECTION is defined and its value isn't zero) +#if CC_REF_LEAK_DETECTION +public: + static void printLeaks(); +#endif +}; + +using SCHEDULE_CB = void (RefCounted::*)(float); +#define CC_SCHEDULE_CALLBACK(cb) static_cast(&cb) + +} // namespace cc diff --git a/cocos/base/RefMap.h b/cocos/base/RefMap.h new file mode 100644 index 0000000..3f92afb --- /dev/null +++ b/cocos/base/RefMap.h @@ -0,0 +1,309 @@ +/**************************************************************************** + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Log.h" +#include "base/Random.h" +#include "base/RefCounted.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" + +namespace cc { + +/** + * Similar to ccstd::unordered_map, but it will manage reference count automatically internally. + * Which means it will invoke RefCounted::addRef() when adding an element, and invoke RefCounted::release() when removing an element. + * @warning The element should be `RefCounted` or its sub-class. + */ +template +class RefMap { +public: + // ------------------------------------------ + // Iterators + // ------------------------------------------ + + /** Iterator, can be used to loop the Map. */ + using iterator = typename ccstd::unordered_map::iterator; + /** Const iterator, can be used to loop the Map. */ + using const_iterator = typename ccstd::unordered_map::const_iterator; + + /** Return iterator to beginning. */ + iterator begin() { return _data.begin(); } + /** Return const_iterator to beginning. */ + const_iterator begin() const { return _data.begin(); } + + /** Return iterator to end.*/ + iterator end() { return _data.end(); } + /** Return const_iterator to end.*/ + const_iterator end() const { return _data.end(); } + + /** Return const_iterator to beginning.*/ + const_iterator cbegin() const { return _data.cbegin(); } + /** Return const_iterator to end.*/ + const_iterator cend() const { return _data.cend(); } + + /** Default constructor */ + RefMap() { + static_assert(std::is_convertible::value, "Invalid Type for cc::Map!"); + } + + /** Constructor with capacity. */ + explicit RefMap(uint32_t capacity) { + static_assert(std::is_convertible::value, "Invalid Type for cc::Map!"); + _data.reserve(capacity); + } + + /** Copy constructor. */ + RefMap(const RefMap &other) { + static_assert(std::is_convertible::value, "Invalid Type for cc::Map!"); + _data = other._data; + addRefForAllObjects(); + } + + /** Move constructor. */ + RefMap(RefMap &&other) noexcept { + static_assert(std::is_convertible::value, "Invalid Type for cc::Map!"); + _data = std::move(other._data); + } + + /** + * Destructor. + * It will release all objects in map. + */ + ~RefMap() { + clear(); + } + + /** Sets capacity of the map. */ + void reserve(uint32_t capacity) { + _data.reserve(capacity); + } + + /** Returns the number of buckets in the Map container. */ + uint32_t bucketCount() const { + return static_cast(_data.bucket_count()); + } + + /** Returns the number of elements in bucket n. */ + uint32_t bucketSize(uint32_t n) const { + return static_cast(_data.bucket_size(n)); + } + + /** Returns the bucket number where the element with key k is located. */ + uint32_t bucket(const K &k) const { + return _data.bucket(k); + } + + /** The number of elements in the map. */ + uint32_t size() const { + return static_cast(_data.size()); + } + + /** + * Returns a bool value indicating whether the map container is empty, i.e. whether its size is 0. + * @note This function does not modify the content of the container in any way. + * To clear the content of an array object, member function unordered_map::clear exists. + */ + bool empty() const { + return _data.empty(); + } + + /** Returns all keys in the map. */ + ccstd::vector keys() const { + ccstd::vector keys; + + if (!_data.empty()) { + keys.reserve(_data.size()); + + for (const auto &element : _data) { + keys.push_back(element.first); + } + } + return keys; + } + + /** Returns all keys that matches the object. */ + ccstd::vector keys(V object) const { + ccstd::vector keys; + + if (!_data.empty()) { + keys.reserve(_data.size() / 10); + + for (const auto &element : _data) { + if (element.second == object) { + keys.push_back(element.first); + } + } + } + + keys.shrink_to_fit(); + + return keys; + } + + /** + * Returns a reference to the mapped value of the element with key k in the map. + * + * @note If key does not match the key of any element in the container, the function return nullptr. + * @param key Key value of the element whose mapped value is accessed. + * Member type K is the keys for the elements in the container. defined in Map as an alias of its first template parameter (Key). + */ + V at(const K &key) { + auto iter = _data.find(key); + if (iter != _data.end()) { + return iter->second; + } + return nullptr; + } + + /** + * Searches the container for an element with 'key' as key and returns an iterator to it if found, + * otherwise it returns an iterator to Map::end (the element past the end of the container). + * + * @param key Key to be searched for. + * Member type 'K' is the type of the keys for the elements in the container, + * defined in Map as an alias of its first template parameter (Key). + */ + const_iterator find(const K &key) const { + return _data.find(key); + } + + iterator find(const K &key) { + return _data.find(key); + } + + /** + * Inserts new elements in the map. + * + * @note If the container has already contained the key, this function will erase the old pair(key, object) and insert the new pair. + * @param key The key to be inserted. + * @param object The object to be inserted. + */ + void insert(const K &key, V object) { + CC_ASSERT_NOT_NULL(object); + object->addRef(); + erase(key); + _data.insert(std::make_pair(key, object)); + } + + /** + * Removes an element with an iterator from the Map container. + * + * @param position Iterator pointing to a single element to be removed from the Map. + * Member type const_iterator is a forward iterator type. + */ + iterator erase(const_iterator position) { + CC_ASSERT(position != _data.cend()); + position->second->release(); + return _data.erase(position); + } + + /** + * Removes an element with an iterator from the Map container. + * + * @param k Key of the element to be erased. + * Member type 'K' is the type of the keys for the elements in the container, + * defined in Map as an alias of its first template parameter (Key). + */ + size_t erase(const K &k) { + auto iter = _data.find(k); + if (iter != _data.end()) { + iter->second->release(); + _data.erase(iter); + return 1; + } + return 0; + } + + /** + * Removes some elements with a vector which contains keys in the map. + * + * @param keys Keys of elements to be erased. + */ + void erase(const ccstd::vector &keys) { + for (const auto &key : keys) { + this->erase(key); + } + } + + /** + * All the elements in the Map container are dropped: + * their reference count will be decreased, and they are removed from the container, + * leaving it with a size of 0. + */ + void clear() { + for (const auto &element : _data) { + element.second->release(); + } + + _data.clear(); + } + + /** + * Gets a random object in the map. + * @return Returns the random object if the map isn't empty, otherwise it returns nullptr. + */ + V getRandomObject() const { + if (!_data.empty()) { + auto randIdx = RandomHelper::randomInt(0, static_cast(_data.size()) - 1); + const_iterator randIter = _data.begin(); + std::advance(randIter, randIdx); + return randIter->second; + } + return nullptr; + } + + /** Copy assignment operator. */ + RefMap &operator=(const RefMap &other) { + if (this != &other) { + clear(); + _data = other._data; + addRefForAllObjects(); + } + return *this; + } + + /** Move assignment operator. */ + RefMap &operator=(RefMap &&other) noexcept { + if (this != &other) { + clear(); + _data = std::move(other._data); + } + return *this; + } + +private: + /** Retains all the objects in the map */ + void addRefForAllObjects() { + for (const auto &element : _data) { + element.second->addRef(); + } + } + + ccstd::unordered_map _data; +}; + +} // namespace cc diff --git a/cocos/base/RefVector.h b/cocos/base/RefVector.h new file mode 100644 index 0000000..97db4e2 --- /dev/null +++ b/cocos/base/RefVector.h @@ -0,0 +1,523 @@ +/**************************************************************************** + Copyright (c) 2010 ForzeField Studios S.L. http://forzefield.com + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include // for std::find +#include +#include + +#include "base/Log.h" +#include "base/Random.h" +#include "base/RefCounted.h" +#include "base/memory/Memory.h" +#include "base/std/container/vector.h" + +namespace cc { + +/* + * Similar to ccstd::vector, but it will manage reference count automatically internally. + * Which means it will invoke RefCounted::addRef() when adding an element, and invoke RefCounted::release() when removing an element. + * @warn The element should be `RefCounted` or its sub-class. + */ +template +class RefVector { +public: + // ------------------------------------------ + // Iterators + // ------------------------------------------ + + /** Iterator, can be used to loop the Vector. */ + using iterator = typename ccstd::vector::iterator; + /** Const iterator, can be used to loop the Vector. */ + using const_iterator = typename ccstd::vector::const_iterator; + + /** Reversed iterator, can be used to loop the Vector in reverse sequence. */ + using reverse_iterator = typename ccstd::vector::reverse_iterator; + /** Reversed iterator, can be used to loop the Vector in reverse sequence. */ + using const_reverse_iterator = typename ccstd::vector::const_reverse_iterator; + + /** Returns an iterator pointing the first element of the Vector. */ + iterator begin() { return _data.begin(); } + /** Returns an iterator pointing the first element of the Vector. */ + const_iterator begin() const { return _data.begin(); } + + /** + * Returns an iterator referring to the `past-the-end` element in the Vector container. + * The past-the-end element is the theoretical element that would follow the last element in the Vector. + * It does not point to any element, and thus shall not be dereferenced. + */ + iterator end() { return _data.end(); } + /** + * Returns iterator referring to the `past-the-end` element in the Vector container. + * The past-the-end element is the theoretical element that would follow the last element in the Vector. + * It does not point to any element, and thus shall not be dereferenced. + */ + const_iterator end() const { return _data.end(); } + + /** Returns a const_iterator pointing the first element of the Vector. */ + const_iterator cbegin() const { return _data.cbegin(); } + /** Returns a const_iterator pointing the `past-the-end` element of the Vector. */ + const_iterator cend() const { return _data.cend(); } + + /** Returns a reverse iterator pointing to the last element of the Vector. */ + reverse_iterator rbegin() { return _data.rbegin(); } + /** Returns a reverse iterator pointing to the last element of the Vector. */ + const_reverse_iterator rbegin() const { return _data.rbegin(); } + + /** Returns a reverse iterator pointing to the theoretical element preceding the + * first element of the vector (which is considered its reverse end). + */ + reverse_iterator rend() { return _data.rend(); } + /** Returns a reverse iterator pointing to the theoretical element preceding the + * first element of the vector (which is considered its reverse end). + */ + const_reverse_iterator rend() const { return _data.rend(); } + + /** Returns a const_reverse_iterator pointing to the last element in the container (i.e., its reverse beginning). */ + const_reverse_iterator crbegin() const { return _data.crbegin(); } + /** Returns a const_reverse_iterator pointing to the theoretical element preceding the first element in + * the container (which is considered its reverse end). + */ + const_reverse_iterator crend() const { return _data.crend(); } + + /** Constructor. */ + RefVector() + : _data() { + static_assert(std::is_convertible::value, "Invalid Type for cc::Vector!"); + } + + /** + * Constructor with a capacity. + * @param capacity Capacity of the Vector. + */ + explicit RefVector(uint32_t capacity) + : _data() { + static_assert(std::is_convertible::value, "Invalid Type for cc::Vector!"); + // CC_LOG_INFO("In the default constructor with capacity of Vector."); + reserve(capacity); + } + + /** Constructor with initializer list. */ + RefVector(std::initializer_list list) { + for (auto &element : list) { + pushBack(element); + } + } + + /** Destructor. */ + ~RefVector() { + // CC_LOG_INFO("In the destructor of Vector."); + clear(); + } + + /** Copy constructor. */ + RefVector(const RefVector &other) { + static_assert(std::is_convertible::value, "Invalid Type for cc::Vector!"); + // CC_LOG_INFO("In the copy constructor!"); + _data = other._data; + addRefForAllObjects(); + } + + /** Copy constructor. */ + explicit RefVector(const ccstd::vector &other) { + static_assert(std::is_convertible::value, "Invalid Type for cc::Vector!"); + // CC_LOG_INFO("In the copy constructor!"); + _data = other; + addRefForAllObjects(); + } + + /** Constructor with std::move semantic. */ + RefVector(RefVector &&other) noexcept { + static_assert(std::is_convertible::value, "Invalid Type for cc::Vector!"); + // CC_LOG_INFO("In the move constructor of Vector!"); + _data = std::move(other._data); + } + + /** Constructor with std::move semantic. */ + explicit RefVector(ccstd::vector &&other) noexcept { + static_assert(std::is_convertible::value, "Invalid Type for cc::Vector!"); + // CC_LOG_INFO("In the move constructor of Vector!"); + _data = std::move(other); + // NOTE: The reference count of T in ccstd::vector may be 0 so we need to retain all elements in ccstd::vector. + addRefForAllObjects(); + } + + /** Copy assignment operator. */ + RefVector &operator=(const RefVector &other) { + if (this != &other) { + // CC_LOG_INFO("In the copy assignment operator!"); + clear(); + _data = other._data; + addRefForAllObjects(); + } + return *this; + } + + RefVector &operator=(const ccstd::vector &other) { + static_assert(std::is_convertible::value, "Invalid Type for cc::Vector!"); + if (&_data != &other) { + // CC_LOG_INFO("In the copy assign operator!"); + clear(); + _data = other; + addRefForAllObjects(); + } + return *this; + } + + /** Move assignment operator with std::move semantic. */ + RefVector &operator=(RefVector &&other) noexcept { + if (this != &other) { + // CC_LOG_INFO("In the move assignment operator!"); + clear(); + _data = std::move(other._data); + } + return *this; + } + + RefVector &operator=(ccstd::vector &&other) noexcept { + if (&_data != &other) { + // CC_LOG_INFO("In the move assignment operator!"); + clear(); + _data = std::move(other); + // NOTE: The reference count of T in ccstd::vector may be 0 so we need to retain all elements in ccstd::vector. + addRefForAllObjects(); + } + return *this; + } + + RefVector &operator=(std::initializer_list list) { + clear(); + for (auto &element : list) { + pushBack(element); + } + return *this; + } + + // Can not return reference, or it can use like this + // refVector[i] = val; + // Then, reference count will be wrong. In order to correct reference count, should: + // - dec refVector[i] reference count + // - add `val` reference count. + // It is hard to use, so delete it. + T &operator[](uint32_t idx) = delete; + // As non const version is disabled, disable const version too. + const T &operator[](uint32_t idx) const = delete; + + /** + * Requests that the vector capacity be at least enough to contain n elements. + * @param capacity Minimum capacity requested of the Vector. + */ + void reserve(uint32_t n) { + _data.reserve(n); + } + + /** @brief Returns the size of the storage space currently allocated for the Vector, expressed in terms of elements. + * @note This capacity is not necessarily equal to the Vector size. + * It can be equal or greater, with the extra space allowing to accommodate for growth without the need to reallocate on each insertion. + * @return The size of the currently allocated storage capacity in the Vector, measured in terms of the number elements it can hold. + */ + uint32_t capacity() const { + return _data.capacity(); + } + + /** @brief Returns the number of elements in the Vector. + * @note This is the number of actual objects held in the Vector, which is not necessarily equal to its storage capacity. + * @return The number of elements in the Vector. + */ + uint32_t size() const { + return static_cast(_data.size()); + } + + /** @brief Returns whether the Vector is empty (i.e. whether its size is 0). + * @note This function does not modify the container in any way. To clear the content of a vector, see Vector::clear. + */ + bool empty() const { + return _data.empty(); + } + + /** Returns the maximum number of elements that the Vector can hold. */ + uint32_t maxSize() const { + return _data.max_size(); + } + + /** Returns index of a certain object, return UINT_MAX if doesn't contain the object */ + uint32_t getIndex(T object) const { + auto iter = std::find(_data.begin(), _data.end(), object); + if (iter != _data.end()) { + return iter - _data.begin(); + } + + return -1; + } + + /** @brief Find the object in the Vector. + * @param object The object to find. + * @return Returns an iterator which refers to the element that its value is equals to object. + * Returns Vector::end() if not found. + */ + const_iterator find(T object) const { + return std::find(_data.begin(), _data.end(), object); + } + + /** @brief Find the object in the Vector. + * @param object The object to find. + * @return Returns an iterator which refers to the element that its value is equals to object. + * Returns Vector::end() if not found. + */ + iterator find(T object) { + return std::find(_data.begin(), _data.end(), object); + } + + /** Returns the element at position 'index' in the Vector. */ + const T &at(uint32_t index) const { + CC_ASSERT(index < size()); + return _data[index]; + } + + T &at(uint32_t index) { + CC_ASSERT(index < size()); + return _data[index]; + } + + /** Returns the first element in the Vector. */ + T front() const { + return _data.front(); + } + + /** Returns the last element of the Vector. */ + T back() const { + return _data.back(); + } + + /** Returns a random element of the Vector. */ + T getRandomObject() const { + if (!_data.empty()) { + auto randIdx = RandomHelper::randomInt(0, static_cast(_data.size()) - 1); + return *(_data.begin() + randIdx); + } + return nullptr; + } + + /** + * Checks whether an object is in the container. + * @param object The object to be checked. + * @return True if the object is in the container, false if not. + */ + bool contains(T object) const { + return (std::find(_data.begin(), _data.end(), object) != _data.end()); + } + + /** + * Checks whether two vectors are equal. + * @param other The vector to be compared. + * @return True if two vectors are equal, false if not. + */ + bool equals(const RefVector &other) const { + uint32_t s = this->size(); + if (s != other.size()) { + return false; + } + + for (uint32_t i = 0; i < s; i++) { + if (this->at(i) != other.at(i)) { + return false; + } + } + return true; + } + + // Adds objects + + /** Adds a new element at the end of the Vector. */ + void pushBack(T object) { + CC_ASSERT_NOT_NULL(object); + _data.push_back(object); + object->addRef(); + } + + /** Push all elements of an existing Vector to the end of current Vector. */ + void pushBack(const RefVector &other) { + for (const auto &obj : other) { + _data.push_back(obj); + obj->addRef(); + } + } + + /** + * Insert an object at certain index. + * @param index The index to be inserted at. + * @param object The object to be inserted. + */ + void insert(uint32_t index, T object) { + CC_ASSERT(index <= size()); + CC_ASSERT_NOT_NULL(object); + _data.insert((std::begin(_data) + index), object); + object->addRef(); + } + + // Removes Objects + + /** Removes the last element in the Vector. */ + void popBack() { + CC_ASSERT(!_data.empty()); + auto last = _data.back(); + _data.pop_back(); + last->release(); + } + + /** Remove a certain object in Vector. + * @param object The object to be removed. + * @param removeAll Whether to remove all elements with the same value. + * If its value is 'false', it will just erase the first occurrence. + */ + void eraseObject(T object, bool removeAll = false) { + CC_ASSERT_NOT_NULL(object); + + if (removeAll) { + for (auto iter = _data.begin(); iter != _data.end();) { + if ((*iter) == object) { + iter = _data.erase(iter); + object->release(); + } else { + ++iter; + } + } + } else { + auto iter = std::find(_data.begin(), _data.end(), object); + if (iter != _data.end()) { + _data.erase(iter); + object->release(); + } + } + } + + /** @brief Removes from the vector with an iterator. + * @param position Iterator pointing to a single element to be removed from the Vector. + * @return An iterator pointing to the new location of the element that followed the last element erased by the function call. + * This is the container end if the operation erased the last element in the sequence. + */ + iterator erase(iterator position) { + CC_ASSERT(position >= _data.begin() && position < _data.end()); + (*position)->release(); + return _data.erase(position); + } + + /** @brief Removes from the Vector with a range of elements ( [first, last) ). + * @param first The beginning of the range. + * @param last The end of the range, the 'last' will not be removed, it's only for indicating the end of range. + * @return An iterator pointing to the new location of the element that followed the last element erased by the function call. + * This is the container end if the operation erased the last element in the sequence. + */ + iterator erase(iterator first, iterator last) { + for (auto iter = first; iter != last; ++iter) { + (*iter)->release(); + } + + return _data.erase(first, last); + } + + /** @brief Removes from the Vector by index. + * @param index The index of the element to be removed from the Vector. + * @return An iterator pointing to the successor of Vector[index]. + */ + iterator erase(uint32_t index) { + CC_ASSERT(!_data.empty() && index < size()); + auto it = std::next(begin(), index); + (*it)->release(); + return _data.erase(it); + } + + /** @brief Removes all elements from the Vector (which are destroyed), leaving the container with a size of 0. + * @note All the elements in the Vector will be released (reference count will be decreased). + */ + void clear() { + for (auto it = std::begin(_data); it != std::end(_data); ++it) { + auto *ptr = *it; + if (ptr) { + ptr->release(); + } + } + _data.clear(); + } + + // Rearranging Content + + /** Swap the values object1 and object2. */ + void swap(T object1, T object2) { + uint32_t idx1 = getIndex(object1); + uint32_t idx2 = getIndex(object2); + std::swap(_data[idx1], _data[idx2]); + } + + /** Swap two elements by indexes. */ + void swap(uint32_t index1, uint32_t index2) { + CC_ASSERT(index1 < size() && index2 < size()); + std::swap(_data[index1], _data[index2]); + } + + /** Replace value at index with given object. */ + void replace(uint32_t index, T object) { + CC_ASSERT(index < size()); + CC_ASSERT_NOT_NULL(object); + + CC_SAFE_RELEASE(_data[index]); + _data[index] = object; + CC_SAFE_ADD_REF(object); + } + + /** Reverses the Vector. */ + void reverse() { + std::reverse(std::begin(_data), std::end(_data)); + } + + /** Requests the container to reduce its capacity to fit its size. */ + void shrinkToFit() { + _data.shrink_to_fit(); + } + + const ccstd::vector &get() const { + return _data; + } + + void resize(uint32_t size) { + _data.resize(size); + } + +protected: + /** Retains all the objects in the vector */ + void addRefForAllObjects() { + for (const auto &obj : _data) { + obj->addRef(); + } + } + + ccstd::vector _data; +}; + +} // namespace cc diff --git a/cocos/base/Scheduler.cpp b/cocos/base/Scheduler.cpp new file mode 100644 index 0000000..074e9c6 --- /dev/null +++ b/cocos/base/Scheduler.cpp @@ -0,0 +1,398 @@ +/**************************************************************************** + Copyright (c) 2008-2010 Ricardo Quesada + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2011 Zynga Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/Scheduler.h" + +#include +#include +#include "base/Log.h" +#include "base/Macros.h" +#include "base/memory/Memory.h" + +namespace { +constexpr unsigned CC_REPEAT_FOREVER{UINT_MAX - 1}; +constexpr int MAX_FUNC_TO_PERFORM{30}; +constexpr int INITIAL_TIMER_COUND{10}; +} // namespace + +namespace cc { +// implementation Timer + +void Timer::setupTimerWithInterval(float seconds, unsigned int repeat, float delay) { + _elapsed = -1; + _interval = seconds; + _delay = delay; + _useDelay = _delay > 0.0F; + _repeat = repeat; + _runForever = _repeat == CC_REPEAT_FOREVER; +} + +void Timer::update(float dt) { + if (_elapsed == -1) { + _elapsed = 0; + _timesExecuted = 0; + return; + } + + // accumulate elapsed time + _elapsed += dt; + + // deal with delay + if (_useDelay) { + if (_elapsed < _delay) { + return; + } + trigger(_delay); + _elapsed = _elapsed - _delay; + _timesExecuted += 1; + _useDelay = false; + // after delay, the rest time should compare with interval + if (!_runForever && _timesExecuted > _repeat) { //unschedule timer + cancel(); + return; + } + } + + // if _interval == 0, should trigger once every frame + float interval = (_interval > 0) ? _interval : _elapsed; + while (_elapsed >= interval) { + trigger(interval); + _elapsed -= interval; + _timesExecuted += 1; + + if (!_runForever && _timesExecuted > _repeat) { + cancel(); + break; + } + + if (_elapsed <= 0.F) { + break; + } + + if (_scheduler->isCurrentTargetSalvaged()) { + break; + } + } +} + +// TimerTargetCallback + +bool TimerTargetCallback::initWithCallback(Scheduler *scheduler, const ccSchedulerFunc &callback, void *target, const ccstd::string &key, float seconds, unsigned int repeat, float delay) { + _scheduler = scheduler; + _target = target; + _callback = callback; + _key = key; + setupTimerWithInterval(seconds, repeat, delay); + return true; +} + +void TimerTargetCallback::trigger(float dt) { + if (_callback) { + _callback(dt); + } +} + +void TimerTargetCallback::cancel() { + _scheduler->unschedule(_key, _target); +} + +// implementation of Scheduler + +Scheduler::Scheduler() { + // I don't expect to have more than 30 functions to all per frame + _functionsToPerform.reserve(MAX_FUNC_TO_PERFORM); +} + +Scheduler::~Scheduler() { + unscheduleAll(); +} + +void Scheduler::removeHashElement(HashTimerEntry *element) { + if (element) { + for (auto &timer : element->timers) { + timer->release(); + } + element->timers.clear(); + + _hashForTimers.erase(element->target); + delete element; + } +} + +void Scheduler::schedule(const ccSchedulerFunc &callback, void *target, float interval, bool paused, const ccstd::string &key) { + this->schedule(callback, target, interval, CC_REPEAT_FOREVER, 0.0F, paused, key); +} + +void Scheduler::schedule(const ccSchedulerFunc &callback, void *target, float interval, unsigned int repeat, float delay, bool paused, const ccstd::string &key) { + CC_ASSERT(target); + CC_ASSERT(!key.empty()); + + auto iter = _hashForTimers.find(target); + HashTimerEntry *element = nullptr; + if (iter == _hashForTimers.end()) { + element = ccnew HashTimerEntry(); + element->target = target; + + _hashForTimers[target] = element; + + // Is this the 1st element ? Then set the pause level to all the selectors of this target + element->paused = paused; + } else { + element = iter->second; + CC_ASSERT(element->paused == paused); + } + + if (element->timers.empty()) { + element->timers.reserve(INITIAL_TIMER_COUND); + } else { + for (auto &e : element->timers) { + auto *timer = dynamic_cast(e); + if (key == timer->getKey()) { + CC_LOG_DEBUG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), interval); + timer->setInterval(interval); + return; + } + } + } + + auto *timer = ccnew TimerTargetCallback(); + timer->addRef(); + timer->initWithCallback(this, callback, target, key, interval, repeat, delay); + element->timers.emplace_back(timer); +} + +void Scheduler::unschedule(const ccstd::string &key, void *target) { + // explicit handle nil arguments when removing an object + if (target == nullptr || key.empty()) { + return; + } + + auto iter = _hashForTimers.find(target); + if (iter != _hashForTimers.end()) { + HashTimerEntry *element = iter->second; + int i = 0; + auto &timers = element->timers; + + for (auto *t : timers) { + auto *timer = dynamic_cast(t); + + if (timer && key == timer->getKey()) { + if (timer == element->currentTimer && (!element->currentTimerSalvaged)) { + element->currentTimer->addRef(); + element->currentTimerSalvaged = true; + } + + timers.erase(timers.begin() + i); + timer->release(); + + // update timerIndex in case we are in tick:, looping over the actions + if (element->timerIndex >= i) { + element->timerIndex--; + } + + if (timers.empty()) { + if (_currentTarget == element) { + _currentTargetSalvaged = true; + } else { + removeHashElement(element); + } + } + + return; + } + + ++i; + } + } +} + +bool Scheduler::isScheduled(const ccstd::string &key, void *target) { + CC_ASSERT(!key.empty()); + CC_ASSERT(target); + + auto iter = _hashForTimers.find(target); + if (iter == _hashForTimers.end()) { + return false; + } + + HashTimerEntry *element = iter->second; + if (element->timers.empty()) { + return false; + } + + const auto &timers = element->timers; + return std::any_of(timers.begin(), timers.end(), [&key](Timer *t) { + auto *timer = dynamic_cast(t); + return (timer && key == timer->getKey()); + }); +} + +void Scheduler::unscheduleAll() { + for (auto iter = _hashForTimers.begin(); iter != _hashForTimers.end();) { + unscheduleAllForTarget(iter++->first); + } +} + +void Scheduler::unscheduleAllForTarget(void *target) { + // explicit nullptr handling + if (target == nullptr) { + return; + } + + // Custom Selectors + + auto iter = _hashForTimers.find(target); + if (iter != _hashForTimers.end()) { + HashTimerEntry *element = iter->second; + auto &timers = element->timers; + if (std::find(timers.begin(), timers.end(), element->currentTimer) != timers.end() && + (!element->currentTimerSalvaged)) { + element->currentTimer->addRef(); + element->currentTimerSalvaged = true; + } + + for (auto *t : timers) { + t->release(); + } + timers.clear(); + + if (_currentTarget == element) { + _currentTargetSalvaged = true; + } else { + removeHashElement(element); + } + } +} + +void Scheduler::resumeTarget(void *target) { + CC_ASSERT_NOT_NULL(target); + + // custom selectors + auto iter = _hashForTimers.find(target); + if (iter != _hashForTimers.end()) { + iter->second->paused = false; + } +} + +void Scheduler::pauseTarget(void *target) { + CC_ASSERT_NOT_NULL(target); + + // custom selectors + auto iter = _hashForTimers.find(target); + if (iter != _hashForTimers.end()) { + iter->second->paused = true; + } +} + +bool Scheduler::isTargetPaused(void *target) { + CC_ASSERT_NOT_NULL(target); + + // Custom selectors + auto iter = _hashForTimers.find(target); + if (iter != _hashForTimers.end()) { + return iter->second->paused; + } + + return false; // should never get here +} + +void Scheduler::performFunctionInCocosThread(const std::function &function) { + _performMutex.lock(); + _functionsToPerform.push_back(function); + _performMutex.unlock(); +} + +void Scheduler::removeAllFunctionsToBePerformedInCocosThread() { + std::unique_lock lock(_performMutex); + _functionsToPerform.clear(); +} + +// main loop +void Scheduler::update(float dt) { + _updateHashLocked = true; + + // Iterate over all the custom selectors + HashTimerEntry *elt = nullptr; + for (auto iter = _hashForTimers.begin(); iter != _hashForTimers.end();) { + elt = iter->second; + _currentTarget = elt; + _currentTargetSalvaged = false; + + if (!_currentTarget->paused) { + // The 'timers' array may change while inside this loop + for (elt->timerIndex = 0; elt->timerIndex < static_cast(elt->timers.size()); ++(elt->timerIndex)) { + elt->currentTimer = elt->timers.at(elt->timerIndex); + elt->currentTimerSalvaged = false; + + elt->currentTimer->update(dt); + + if (elt->currentTimerSalvaged) { + // The currentTimer told the remove itself. To prevent the timer from + // accidentally deallocating itself before finishing its step, we retained + // it. Now that step is done, it's safe to release it. + elt->currentTimer->release(); + } + + elt->currentTimer = nullptr; + } + } + + // only delete currentTarget if no actions were scheduled during the cycle (issue #481) + if (_currentTargetSalvaged && _currentTarget->timers.empty()) { + ++iter; + removeHashElement(_currentTarget); + if (iter != _hashForTimers.end()) { + ++iter; + } + } else { + ++iter; + } + } + + _updateHashLocked = false; + _currentTarget = nullptr; + + // + // Functions allocated from another thread + // + + // Testing size is faster than locking / unlocking. + // And almost never there will be functions scheduled to be called. + if (!_functionsToPerform.empty()) { + _performMutex.lock(); + // fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if new functions are added in callback, it will cause thread deadlock. + auto temp = _functionsToPerform; + _functionsToPerform.clear(); + _performMutex.unlock(); + for (const auto &function : temp) { + function(); + } + } +} + +} // namespace cc diff --git a/cocos/base/Scheduler.h b/cocos/base/Scheduler.h new file mode 100644 index 0000000..1bdaea8 --- /dev/null +++ b/cocos/base/Scheduler.h @@ -0,0 +1,305 @@ +/**************************************************************************** + Copyright (c) 2008-2010 Ricardo Quesada + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2011 Zynga Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include + +#include "base/RefCounted.h" +#include "base/std/container/set.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" + +namespace cc { + +class Scheduler; + +using ccSchedulerFunc = std::function; + +/** + * @cond + */ +class CC_DLL Timer : public RefCounted { +public: + /** get interval in seconds */ + inline float getInterval() const { return _interval; }; + /** set interval in seconds */ + inline void setInterval(float interval) { _interval = interval; }; + + void setupTimerWithInterval(float seconds, unsigned int repeat, float delay); + + virtual void trigger(float dt) = 0; + virtual void cancel() = 0; + + /** triggers the timer */ + void update(float dt); + +protected: + Timer() = default; + + Scheduler *_scheduler = nullptr; + float _elapsed = 0.F; + bool _runForever = false; + bool _useDelay = false; + unsigned int _timesExecuted = 0; + unsigned int _repeat = 0; //0 = once, 1 is 2 x executed + float _delay = 0.F; + float _interval = 0.F; +}; + +class CC_DLL TimerTargetCallback final : public Timer { +public: + TimerTargetCallback() = default; + + // Initializes a timer with a target, a lambda and an interval in seconds, repeat in number of times to repeat, delay in seconds. + bool initWithCallback(Scheduler *scheduler, const ccSchedulerFunc &callback, void *target, const ccstd::string &key, float seconds, unsigned int repeat, float delay); + + inline const ccSchedulerFunc &getCallback() const { return _callback; }; + inline const ccstd::string &getKey() const { return _key; }; + + void trigger(float dt) override; + void cancel() override; + +private: + void *_target = nullptr; + ccSchedulerFunc _callback = nullptr; + ccstd::string _key; +}; + +/** + * @endcond + */ + +/** + * @addtogroup base + * @{ + */ + +struct _listEntry; +struct _hashSelectorEntry; +struct _hashUpdateEntry; + +/** @brief Scheduler is responsible for triggering the scheduled callbacks. +You should not use system timer for your game logic. Instead, use this class. + +There are 2 different types of callbacks (selectors): + +- update selector: the 'update' selector will be called every frame. You can customize the priority. +- custom selector: A custom selector will be called every frame, or with a custom interval of time + +The 'custom selectors' should be avoided when possible. It is faster, and consumes less memory to use the 'update selector'. + +*/ +class CC_DLL Scheduler final { +public: + /** + * Constructor + * + * @js ctor + */ + Scheduler(); + + /** + * Destructor + * + * @js NA + * @lua NA + */ + ~Scheduler(); + + /** 'update' the scheduler. + * You should NEVER call this method, unless you know what you are doing. + * @lua NA + */ + void update(float dt); + + ///////////////////////////////////// + + // schedule + + /** The scheduled method will be called every 'interval' seconds. + If paused is true, then it won't be called until it is resumed. + If 'interval' is 0, it will be called every frame, but if so, it's recommended to use 'scheduleUpdate' instead. + If the 'callback' is already scheduled, then only the interval parameter will be updated without re-scheduling it again. + repeat let the action be repeated repeat + 1 times, use CC_REPEAT_FOREVER to let the action run continuously + delay is the amount of time the action will wait before it'll start. + @param callback The callback function. + @param target The target of the callback function. + @param interval The interval to schedule the callback. If the value is 0, then the callback will be scheduled every frame. + @param repeat repeat+1 times to schedule the callback. + @param delay Schedule call back after `delay` seconds. If the value is not 0, the first schedule will happen after `delay` seconds. + But it will only affect first schedule. After first schedule, the delay time is determined by `interval`. + @param paused Whether or not to pause the schedule. + @param key The key to identify the callback function, because there is not way to identify a std::function<>. + @since v3.0 + */ + void schedule(const ccSchedulerFunc &callback, void *target, float interval, unsigned int repeat, float delay, bool paused, const ccstd::string &key); + + /** The scheduled method will be called every 'interval' seconds for ever. + @param callback The callback function. + @param target The target of the callback function. + @param interval The interval to schedule the callback. If the value is 0, then the callback will be scheduled every frame. + @param paused Whether or not to pause the schedule. + @param key The key to identify the callback function, because there is not way to identify a std::function<>. + @since v3.0 + */ + void schedule(const ccSchedulerFunc &callback, void *target, float interval, bool paused, const ccstd::string &key); + + ///////////////////////////////////// + + // unschedule + + /** Unschedules a callback for a key and a given target. + If you want to unschedule the 'callbackPerFrame', use unscheduleUpdate. + @param key The key to identify the callback function, because there is not way to identify a std::function<>. + @param target The target to be unscheduled. + @since v3.0 + */ + void unschedule(const ccstd::string &key, void *target); + + /** Unschedules all selectors for a given target. + This also includes the "update" selector. + @param target The target to be unscheduled. + @since v0.99.3 + @lua NA + */ + void unscheduleAllForTarget(void *target); + + /** Unschedules all selectors from all targets. + You should NEVER call this method, unless you know what you are doing. + @since v0.99.3 + */ + void unscheduleAll(); + + /** Unschedules all selectors from all targets with a minimum priority. + You should only call this with `PRIORITY_NON_SYSTEM_MIN` or higher. + @param minPriority The minimum priority of selector to be unscheduled. Which means, all selectors which + priority is higher than minPriority will be unscheduled. + @since v2.0.0 + */ + void unscheduleAllWithMinPriority(int minPriority); + + ///////////////////////////////////// + + // isScheduled + + /** Checks whether a callback associated with 'key' and 'target' is scheduled. + @param key The key to identify the callback function, because there is not way to identify a std::function<>. + @param target The target of the callback. + @return True if the specified callback is invoked, false if not. + @since v3.0.0 + */ + bool isScheduled(const ccstd::string &key, void *target); + + ///////////////////////////////////// + + /** Pauses the target. + All scheduled selectors/update for a given target won't be 'ticked' until the target is resumed. + If the target is not present, nothing happens. + @param target The target to be paused. + @since v0.99.3 + */ + void pauseTarget(void *target); + + /** Resumes the target. + The 'target' will be unpaused, so all schedule selectors/update will be 'ticked' again. + If the target is not present, nothing happens. + @param target The target to be resumed. + @since v0.99.3 + */ + void resumeTarget(void *target); + + /** Returns whether or not the target is paused. + * @param target The target to be checked. + * @return True if the target is paused, false if not. + * @since v1.0.0 + * @lua NA + */ + bool isTargetPaused(void *target); + + /** Pause all selectors from all targets with a minimum priority. + You should only call this with PRIORITY_NON_SYSTEM_MIN or higher. + @param minPriority The minimum priority of selector to be paused. Which means, all selectors which + priority is higher than minPriority will be paused. + @since v2.0.0 + */ + ccstd::set pauseAllTargetsWithMinPriority(int minPriority); + + /** Calls a function on the cocos2d thread. Useful when you need to call a cocos2d function from another thread. + This function is thread safe. + @param function The function to be run in cocos2d thread. + @since v3.0 + @js NA + */ + void performFunctionInCocosThread(const std::function &function); + + /** + * Remove all pending functions queued to be performed with Scheduler::performFunctionInCocosThread + * Functions unscheduled in this manner will not be executed + * This function is thread safe + * @since v3.14 + * @js NA + */ + void removeAllFunctionsToBePerformedInCocosThread(); + + bool isCurrentTargetSalvaged() const { return _currentTargetSalvaged; }; + +private: + // Hash Element used for "selectors with interval" + struct HashTimerEntry { + ccstd::vector timers; + void *target; + int timerIndex; + Timer *currentTimer; + bool currentTimerSalvaged; + bool paused; + }; + + void removeHashElement(struct HashTimerEntry *element); + void removeUpdateFromHash(struct _listEntry *entry); + + // update specific + + // Used for "selectors with interval" + ccstd::unordered_map _hashForTimers; + struct HashTimerEntry *_currentTarget = nullptr; + bool _currentTargetSalvaged = false; + // If true unschedule will not remove anything from a hash. Elements will only be marked for deletion. + bool _updateHashLocked = false; + + // Used for "perform Function" + ccstd::vector> _functionsToPerform; + std::mutex _performMutex; +}; + +// end of base group +/** @} */ + +} // namespace cc diff --git a/cocos/base/StringHandle.cpp b/cocos/base/StringHandle.cpp new file mode 100644 index 0000000..17a3681 --- /dev/null +++ b/cocos/base/StringHandle.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "StringHandle.h" + +namespace cc { + +StringHandle::StringHandle(IndexType handle, const char *str) noexcept +: IndexHandle(handle), + _str(str) { +} + +} // namespace cc diff --git a/cocos/base/StringHandle.h b/cocos/base/StringHandle.h new file mode 100644 index 0000000..723d848 --- /dev/null +++ b/cocos/base/StringHandle.h @@ -0,0 +1,41 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "IndexHandle.h" + +namespace cc { + +class StringHandle final : public IndexHandle { +public: + StringHandle() noexcept = default; + explicit StringHandle(IndexType handle, const char *str) noexcept; + inline char const *str() const noexcept { return _str; } + +private: + char const *_str{nullptr}; +}; + +} // namespace cc diff --git a/cocos/base/StringPool.h b/cocos/base/StringPool.h new file mode 100644 index 0000000..bbd30f5 --- /dev/null +++ b/cocos/base/StringPool.h @@ -0,0 +1,144 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "StringHandle.h" +#include "base/Macros.h" +#include "base/memory/Memory.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" +#include "base/std/hash/hash.h" +#include "threading/ReadWriteLock.h" + +namespace cc { + +template +class StringPool final { +private: + class StringHasher final { + public: + ccstd::hash_t operator()(const char *str) const noexcept { + return ccstd::hash_range(str, str + strlen(str)); + } + }; + + class StringEqual final { + public: + bool operator()(const char *c1, const char *c2) const noexcept { + return strcmp(c1, c2) == 0; + } + }; + +public: + StringPool() = default; + ~StringPool(); + StringPool(const StringPool &) = delete; + StringPool(StringPool &&) = delete; + StringPool &operator=(const StringPool &) = delete; + StringPool &operator=(StringPool &&) = delete; + + StringHandle stringToHandle(const char *str) noexcept; + char const *handleToString(const StringHandle &handle) const noexcept; + StringHandle find(const char *str) const noexcept; + +private: + StringHandle doStringToHandle(const char *str) noexcept; + char const *doHandleToString(const StringHandle &handle) const noexcept; + StringHandle doFind(const char *str) const noexcept; + + ccstd::unordered_map _stringToHandles{}; + ccstd::vector _handleToStrings{}; + mutable ReadWriteLock _readWriteLock{}; +}; + +using ThreadSafeStringPool = StringPool; + +template +StringPool::~StringPool() { + for (char const *strCache : _handleToStrings) { + delete[] strCache; + } +} + +template +inline StringHandle StringPool::stringToHandle(const char *str) noexcept { + if (ThreadSafe) { + return _readWriteLock.lockWrite([this, str]() { + return doStringToHandle(str); + }); + } + return doStringToHandle(str); +} + +template +inline char const *StringPool::handleToString(const StringHandle &handle) const noexcept { + if (ThreadSafe) { + return _readWriteLock.lockRead([this, handle]() { + return doHandleToString(handle); + }); + } + return doHandleToString(handle); +} + +template +StringHandle StringPool::find(const char *str) const noexcept { + if (ThreadSafe) { + return _readWriteLock.lockRead([this, str]() { + return doFind(str); + }); + } + return doFind(str); +} + +template +inline StringHandle StringPool::doStringToHandle(const char *str) noexcept { + const auto it = _stringToHandles.find(str); + + if (it == _stringToHandles.end()) { + size_t const strLength = strlen(str) + 1; + char *const strCache = ccnew char[strLength]; + strcpy(strCache, str); + StringHandle name(static_cast(_handleToStrings.size()), strCache); + _handleToStrings.emplace_back(strCache); + _stringToHandles.emplace(strCache, name); + return name; + } + return it->second; +} + +template +inline const char *StringPool::doHandleToString(const StringHandle &handle) const noexcept { + CC_ASSERT(handle < _handleToStrings.size()); + return _handleToStrings[handle]; +} + +template +StringHandle StringPool::doFind(char const *str) const noexcept { + auto const it = _stringToHandles.find(str); + return it == _stringToHandles.end() ? StringHandle{} : it->second; +} + +} // namespace cc diff --git a/cocos/base/StringUtil.cpp b/cocos/base/StringUtil.cpp new file mode 100644 index 0000000..30512dd --- /dev/null +++ b/cocos/base/StringUtil.cpp @@ -0,0 +1,182 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "StringUtil.h" + +#include +#include +#include +#ifndef CC_WGPU_WASM + #include "base/ZipUtils.h" +#endif +#include "base/base64.h" +#include "base/std/container/string.h" +#include "memory/Memory.h" + +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include +#endif + +namespace cc { + +#if defined(_WIN32) +int StringUtil::vprintf(char *buf, const char *last, const char *fmt, va_list args) { + if (last <= buf) return 0; + + int count = (int)(last - buf); + int ret = _vsnprintf_s(buf, count, _TRUNCATE, fmt, args); + if (ret < 0) { + if (errno == 0) { + return count - 1; + } else { + return 0; + } + } else { + return ret; + } +} +#else +int StringUtil::vprintf(char *buf, const char *last, const char *fmt, va_list args) { + if (last <= buf) { + return 0; + } + + auto count = static_cast(last - buf); + int ret = vsnprintf(buf, count, fmt, args); + if (ret >= count - 1) { + return count - 1; + } + if (ret < 0) { + return 0; + } + return ret; +} +#endif + +int StringUtil::printf(char *buf, const char *last, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int ret = vprintf(buf, last, fmt, args); + va_end(args); + return ret; +} + +ccstd::string StringUtil::format(const char *fmt, ...) { + char sz[4096]; + va_list args; + va_start(args, fmt); + vprintf(sz, sz + sizeof(sz) - 1, fmt, args); + va_end(args); + return sz; +} + +ccstd::vector StringUtil::split(const ccstd::string &str, const ccstd::string &delims, uint32_t maxSplits) { + ccstd::vector strs; + if (str.empty()) { + return strs; + } + + // Pre-allocate some space for performance + strs.reserve(maxSplits ? maxSplits + 1 : 16); // 16 is guessed capacity for most case + + uint32_t numSplits{0}; + + // Use STL methods + size_t start{0}; + size_t pos{0}; + do { + pos = str.find_first_of(delims, start); + if (pos == start) { + // Do nothing + start = pos + 1; + } else if (pos == ccstd::string::npos || (maxSplits && numSplits == maxSplits)) { + // Copy the rest of the string + strs.push_back(str.substr(start)); + break; + } else { + // Copy up to delimiter + strs.push_back(str.substr(start, pos - start)); + start = pos + 1; + } + // parse up to next real data + start = str.find_first_not_of(delims, start); + ++numSplits; + } while (pos != ccstd::string::npos); + + return strs; +} + +ccstd::string &StringUtil::replace(ccstd::string &str, const ccstd::string &findStr, const ccstd::string &replaceStr) { + size_t startPos = str.find(findStr); + if (startPos == ccstd::string::npos) { + return str; + } + str.replace(startPos, findStr.length(), replaceStr); + return str; +} + +ccstd::string &StringUtil::replaceAll(ccstd::string &str, const ccstd::string &findStr, const ccstd::string &replaceStr) { + if (findStr.empty()) { + return str; + } + size_t startPos = 0; + while ((startPos = str.find(findStr, startPos)) != ccstd::string::npos) { + str.replace(startPos, findStr.length(), replaceStr); + startPos += replaceStr.length(); + } + return str; +} + +ccstd::string &StringUtil::tolower(ccstd::string &str) { + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return std::tolower(c); }); + return str; +} + +ccstd::string &StringUtil::toupper(ccstd::string &str) { + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return std::toupper(c); }); + return str; +} + +ccstd::string GzipedString::value() const { // NOLINT(readability-convert-member-functions-to-static) +#ifndef CC_WGPU_WASM + uint8_t *outGzip{nullptr}; + uint8_t *outBase64{nullptr}; + auto *input = reinterpret_cast(const_cast(_str.c_str())); + auto lenOfBase64 = base64Decode(input, static_cast(_str.size()), &outBase64); + auto lenofUnzip = ZipUtils::inflateMemory(outBase64, static_cast(lenOfBase64), &outGzip); + ccstd::string ret(outGzip, outGzip + lenofUnzip); + free(outGzip); + free(outBase64); + return ret; +#else + return ""; +#endif +} + +} // namespace cc diff --git a/cocos/base/StringUtil.h b/cocos/base/StringUtil.h new file mode 100644 index 0000000..50866c5 --- /dev/null +++ b/cocos/base/StringUtil.h @@ -0,0 +1,78 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "Macros.h" +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +namespace cc { + +class CC_DLL StringUtil { +public: + static int vprintf(char *buf, const char *last, const char *fmt, va_list args); + static int printf(char *buf, const char *last, const char *fmt, ...); + static ccstd::string format(const char *fmt, ...); + static ccstd::vector split(const ccstd::string &str, const ccstd::string &delims, uint32_t maxSplits = 0); + static ccstd::string &replace(ccstd::string &str, const ccstd::string &findStr, const ccstd::string &replaceStr); + static ccstd::string &replaceAll(ccstd::string &str, const ccstd::string &findStr, const ccstd::string &replaceStr); + static ccstd::string &tolower(ccstd::string &str); + static ccstd::string &toupper(ccstd::string &str); +}; + +/** + * Store compressed text which compressed with gzip & base64 + * fetch plain-text with `value()`. + */ +class CC_DLL GzipedString { +public: + explicit GzipedString(ccstd::string &&dat) : _str(dat) {} + explicit GzipedString(const char *dat) : _str(dat) {} + GzipedString(const GzipedString &o) = default; + GzipedString &operator=(const GzipedString &d) = default; + + GzipedString(GzipedString &&o) noexcept { + _str = std::move(o._str); + } + GzipedString &operator=(ccstd::string &&d) { + _str = std::move(d); + return *this; + } + GzipedString &operator=(GzipedString &&d) noexcept { + _str = std::move(d._str); + return *this; + } + /** + * return text decompress with base64decode | un-gzip + */ + ccstd::string value() const; + +private: + ccstd::string _str{}; +}; + +} // namespace cc diff --git a/cocos/base/TemplateUtils.h b/cocos/base/TemplateUtils.h new file mode 100644 index 0000000..5881562 --- /dev/null +++ b/cocos/base/TemplateUtils.h @@ -0,0 +1,47 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +namespace cc { + +/* overloaded is used in ccstd::visit a variant value. For example: + + ccstd::variant value; + ccstd::visit(cc::overloaded{ + [](auto& v) { + // Do something with v + }, + [](ccstd::monostate&) {} // Do nothing if value isn't initialized + }, value); + + */ + +// https://stackoverflow.com/questions/69915380/what-does-templateclass-ts-struct-overloaded-ts-using-tsoperator +// https://en.cppreference.com/w/cpp/language/class_template_argument_deduction +template +struct overloaded : Ts... { using Ts::operator()...; }; +template +overloaded(Ts...) -> overloaded; + +} // namespace cc diff --git a/cocos/base/ThreadPool.cpp b/cocos/base/ThreadPool.cpp new file mode 100644 index 0000000..49bc422 --- /dev/null +++ b/cocos/base/ThreadPool.cpp @@ -0,0 +1,380 @@ +/**************************************************************************** + Copyright (c) 2016-2017 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/ThreadPool.h" +#include +#include +#include "base/memory/Memory.h" +#include "platform/StdC.h" + +#ifdef __ANDROID__ + #include + #define LOG_TAG "ThreadPool" + #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#else + #define LOGD(...) printf(__VA_ARGS__) +#endif + +#define TIME_MINUS(now, prev) (std::chrono::duration_cast((now) - (prev)).count() / 1000.0f) + +namespace cc { + +#define DEFAULT_THREAD_POOL_MIN_NUM (4) +#define DEFAULT_THREAD_POOL_MAX_NUM (20) + +#define DEFAULT_SHRINK_INTERVAL (5) +#define DEFAULT_SHRINK_STEP (2) +#define DEFAULT_STRETCH_STEP (2) + +LegacyThreadPool *LegacyThreadPool::_instance = nullptr; + +LegacyThreadPool *LegacyThreadPool::getDefaultThreadPool() { + if (LegacyThreadPool::_instance == nullptr) { + LegacyThreadPool::_instance = newCachedThreadPool(DEFAULT_THREAD_POOL_MIN_NUM, + DEFAULT_THREAD_POOL_MAX_NUM, + DEFAULT_SHRINK_INTERVAL, DEFAULT_SHRINK_STEP, + DEFAULT_STRETCH_STEP); + } + + return LegacyThreadPool::_instance; +} + +void LegacyThreadPool::destroyDefaultThreadPool() { + delete LegacyThreadPool::_instance; + LegacyThreadPool::_instance = nullptr; +} + +LegacyThreadPool *LegacyThreadPool::newCachedThreadPool(int minThreadNum, int maxThreadNum, int shrinkInterval, + int shrinkStep, int stretchStep) { + auto *pool = ccnew LegacyThreadPool(minThreadNum, maxThreadNum); + if (pool != nullptr) { + pool->setFixedSize(false); + pool->setShrinkInterval(shrinkInterval); + pool->setShrinkStep(shrinkStep); + pool->setStretchStep(stretchStep); + } + return pool; +} + +LegacyThreadPool *LegacyThreadPool::newFixedThreadPool(int threadNum) { + auto *pool = ccnew LegacyThreadPool(threadNum, threadNum); + if (pool != nullptr) { + pool->setFixedSize(true); + } + return pool; +} + +LegacyThreadPool *LegacyThreadPool::newSingleThreadPool() { + auto *pool = ccnew LegacyThreadPool(1, 1); + if (pool != nullptr) { + pool->setFixedSize(true); + } + return pool; +} + +LegacyThreadPool::LegacyThreadPool(int minNum, int maxNum) +: _minThreadNum(minNum), + _maxThreadNum(maxNum) { + init(); +} + +// the destructor waits for all the functions in the queue to be finished +LegacyThreadPool::~LegacyThreadPool() { + stop(); +} + +// number of idle threads +int LegacyThreadPool::getIdleThreadNum() const { + auto *thiz = const_cast(this); + std::lock_guard lk(thiz->_idleThreadNumMutex); + return _idleThreadNum; +} + +void LegacyThreadPool::init() { + _lastShrinkTime = std::chrono::high_resolution_clock::now(); + + _maxThreadNum = std::max(_minThreadNum, _maxThreadNum); + + _threads.resize(_maxThreadNum); + _abortFlags.resize(_maxThreadNum); + _idleFlags.resize(_maxThreadNum); + _initedFlags.resize(_maxThreadNum); + + for (int i = 0; i < _maxThreadNum; ++i) { + _idleFlags[i] = std::make_shared>(false); + if (i < _minThreadNum) { + _abortFlags[i] = std::make_shared>(false); + setThread(i); + _initedFlags[i] = std::make_shared>(true); + ++_initedThreadNum; + } else { + _abortFlags[i] = std::make_shared>(true); + _initedFlags[i] = std::make_shared>(false); + } + } +} + +bool LegacyThreadPool::tryShrinkPool() { + LOGD("shrink pool, _idleThreadNum = %d \n", getIdleThreadNum()); + + auto before = std::chrono::high_resolution_clock::now(); + + ccstd::vector threadIDsToJoin; + int maxThreadNumToJoin = std::min(_initedThreadNum - _minThreadNum, _shrinkStep); + + for (int i = 0; i < _maxThreadNum; ++i) { + if ((int)threadIDsToJoin.size() >= maxThreadNumToJoin) { + break; + } + + if (*_idleFlags[i]) { + *_abortFlags[i] = true; + threadIDsToJoin.push_back(i); + } + } + + { + // stop the detached threads that were waiting + std::unique_lock lock(_mutex); + _cv.notify_all(); + } + + for (const auto &threadID : threadIDsToJoin) { // wait for the computing threads to finish + if (_threads[threadID]->joinable()) { + _threads[threadID]->join(); + } + + _threads[threadID].reset(); + *_initedFlags[threadID] = false; + --_initedThreadNum; + } + + auto after = std::chrono::high_resolution_clock::now(); + + float seconds = TIME_MINUS(after, before); + + LOGD("shrink %d threads, waste: %f seconds\n", (int)threadIDsToJoin.size(), seconds); + + return (_initedThreadNum <= _minThreadNum); +} + +void LegacyThreadPool::stretchPool(int count) { + auto before = std::chrono::high_resolution_clock::now(); + + int oldThreadCount = _initedThreadNum; + int newThreadCount = 0; + + for (int i = 0; i < _maxThreadNum; ++i) { + if (!*_initedFlags[i]) { + *_abortFlags[i] = false; + setThread(i); + *_initedFlags[i] = true; + ++_initedThreadNum; + + if (++newThreadCount >= count) { + break; + } + } + } + + if (newThreadCount > 0) { + auto after = std::chrono::high_resolution_clock::now(); + float seconds = TIME_MINUS(after, before); + + LOGD("stretch pool from %d to %d, waste %f seconds\n", oldThreadCount, _initedThreadNum, + seconds); + } +} + +void LegacyThreadPool::pushTask(const std::function &runnable, + TaskType type /* = DEFAULT*/) { + if (!_isFixedSize) { + _idleThreadNumMutex.lock(); + int idleNum = _idleThreadNum; + _idleThreadNumMutex.unlock(); + + if (idleNum > _minThreadNum) { + if (_taskQueue.empty()) { + auto now = std::chrono::high_resolution_clock::now(); + float seconds = TIME_MINUS(now, _lastShrinkTime); + if (seconds > _shrinkInterval) { + tryShrinkPool(); + _lastShrinkTime = now; + } + } + } else if (idleNum == 0) { + stretchPool(_stretchStep); + } + } + + auto callback = ccnew std::function([runnable](int tid) { + runnable(tid); + }); + + Task task; + task.type = type; + task.callback = callback; + _taskQueue.push(task); + + { + std::unique_lock lock(_mutex); + _cv.notify_one(); + } +} + +void LegacyThreadPool::stopAllTasks() { + Task task; + while (_taskQueue.pop(task)) { + delete task.callback; // empty the queue + } +} + +void LegacyThreadPool::stopTasksByType(TaskType type) { + Task task; + + ccstd::vector notStopTasks; + notStopTasks.reserve(_taskQueue.size()); + + while (_taskQueue.pop(task)) { + if (task.type == type) { // Delete the task from queue + delete task.callback; + } else { // If task type isn't match, push it into a vector, then insert to task queue again + notStopTasks.push_back(task); + } + } + + if (!notStopTasks.empty()) { + for (const auto &t : notStopTasks) { + _taskQueue.push(t); + } + } +} + +void LegacyThreadPool::joinThread(int tid) { + if (tid < 0 || tid >= (int)_threads.size()) { + LOGD("Invalid thread id %d\n", tid); + return; + } + + // wait for the computing threads to finish + if (*_initedFlags[tid] && _threads[tid]->joinable()) { + _threads[tid]->join(); + *_initedFlags[tid] = false; + --_initedThreadNum; + } +} + +int LegacyThreadPool::getTaskNum() const { + return (int)_taskQueue.size(); +} + +void LegacyThreadPool::setFixedSize(bool isFixedSize) { + _isFixedSize = isFixedSize; +} + +void LegacyThreadPool::setShrinkInterval(int seconds) { + if (seconds >= 0) { + _shrinkInterval = static_cast(seconds); + } +} + +void LegacyThreadPool::setShrinkStep(int step) { + if (step > 0) { + _shrinkStep = step; + } +} + +void LegacyThreadPool::setStretchStep(int step) { + if (step > 0) { + _stretchStep = step; + } +} + +void LegacyThreadPool::stop() { + if (_isDone || _isStop) { + return; + } + + _isDone = true; // give the waiting threads a command to finish + + { + std::unique_lock lock(_mutex); + _cv.notify_all(); // stop all waiting threads + } + + for (int i = 0, n = static_cast(_threads.size()); i < n; ++i) { + joinThread(i); + } + // if there were no threads in the pool but some functors in the queue, the functors are not deleted by the threads + // therefore delete them here + stopAllTasks(); + _threads.clear(); + _abortFlags.clear(); +} + +void LegacyThreadPool::setThread(int tid) { + std::shared_ptr> abortPtr( + _abortFlags[tid]); // a copy of the shared ptr to the flag + auto f = [this, tid, abortPtr /* a copy of the shared ptr to the abort */]() { + std::atomic &abort = *abortPtr; + Task task; + bool isPop = _taskQueue.pop(task); + while (true) { + while (isPop) { // if there is anything in the queue + std::unique_ptr> func( + task.callback); // at return, delete the function even if an exception occurred + (*task.callback)(tid); + if (abort) { + return; // the thread is wanted to stop, return even if the queue is not empty yet + } + + isPop = _taskQueue.pop(task); + } + // the queue is empty here, wait for the next command + std::unique_lock lock(_mutex); + _idleThreadNumMutex.lock(); + ++_idleThreadNum; + _idleThreadNumMutex.unlock(); + + *_idleFlags[tid] = true; + _cv.wait(lock, [this, &task, &isPop, &abort]() { + isPop = _taskQueue.pop(task); + return isPop || _isDone || abort; + }); + *_idleFlags[tid] = false; + _idleThreadNumMutex.lock(); + --_idleThreadNum; + _idleThreadNumMutex.unlock(); + + if (!isPop) { + return; // if the queue is empty and isDone == true or *flag then return + } + } + }; + _threads[tid].reset( + ccnew std::thread(f)); // compiler may not support std::make_unique() +} + +} // namespace cc diff --git a/cocos/base/ThreadPool.h b/cocos/base/ThreadPool.h new file mode 100644 index 0000000..1227b0c --- /dev/null +++ b/cocos/base/ThreadPool.h @@ -0,0 +1,216 @@ +/**************************************************************************** + Copyright (c) 2016-2017 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "base/Utils.h" +#include "base/std/container/queue.h" + +namespace cc { + +class CC_DLL LegacyThreadPool { +public: + enum class TaskType { + DEFAULT = 0, + NETWORK, + IO, + AUDIO, + USER = 1000 + }; + + /* + * Gets the default thread pool which is a cached thread pool with default parameters. + */ + static LegacyThreadPool *getDefaultThreadPool(); + + /* + * Destroys the default thread pool + */ + static void destroyDefaultThreadPool(); + + /* + * Creates a cached thread pool + * @note The return value has to be delete while it doesn't needed + */ + static LegacyThreadPool *newCachedThreadPool(int minThreadNum, int maxThreadNum, int shrinkInterval, + int shrinkStep, int stretchStep); + + /* + * Creates a thread pool with fixed thread count + * @note The return value has to be delete while it doesn't needed + */ + static LegacyThreadPool *newFixedThreadPool(int threadNum); + + /* + * Creates a thread pool with only one thread in the pool, it could be used to execute multiply tasks serially in just one thread. + * @note The return value has to be delete while it doesn't needed + */ + static LegacyThreadPool *newSingleThreadPool(); + + // the destructor waits for all the functions in the queue to be finished + ~LegacyThreadPool(); + + /* Pushs a task to thread pool + * @param runnable The callback of the task executed in sub thread + * @param type The task type, it's TASK_TYPE_DEFAULT if this argument isn't assigned + * @note This function has to be invoked in cocos thread + */ + void pushTask(const std::function &runnable, TaskType type = TaskType::DEFAULT); + + // Stops all tasks, it will remove all tasks in queue + void stopAllTasks(); + + // Stops some tasks by type + void stopTasksByType(TaskType type); + + // Gets the minimum thread numbers + inline int getMinThreadNum() const { return _minThreadNum; }; + + // Gets the maximum thread numbers + inline int getMaxThreadNum() const { return _maxThreadNum; }; + + // Gets the number of idle threads + int getIdleThreadNum() const; + + // Gets the number of initialized threads + inline int getInitedThreadNum() const { return _initedThreadNum; }; + + // Gets the task number + int getTaskNum() const; + + /* + * Trys to shrink pool + * @note This method is only available for cached thread pool + */ + bool tryShrinkPool(); + +private: + LegacyThreadPool(int minNum, int maxNum); + + LegacyThreadPool(const LegacyThreadPool &); + + LegacyThreadPool(LegacyThreadPool &&) noexcept; + + LegacyThreadPool &operator=(const LegacyThreadPool &); + + LegacyThreadPool &operator=(LegacyThreadPool &&) noexcept; + + void init(); + + void stop(); + + void setThread(int tid); + + void joinThread(int tid); + + void setFixedSize(bool isFixedSize); + + void setShrinkInterval(int seconds); + + void setShrinkStep(int step); + + void setStretchStep(int step); + + void stretchPool(int count); + + ccstd::vector> _threads; + ccstd::vector>> _abortFlags; + ccstd::vector>> _idleFlags; + ccstd::vector>> _initedFlags; + + template + class ThreadSafeQueue { + public: + bool push(T const &value) { + std::unique_lock lock(this->mutex); + this->q.push(value); + return true; + } + + // deletes the retrieved element, do not use for non integral types + bool pop(T &v) { + std::unique_lock lock(this->mutex); + if (this->q.empty()) + return false; + v = this->q.front(); + this->q.pop(); + return true; + } + + bool empty() const { + auto thiz = const_cast(this); + std::unique_lock lock(thiz->mutex); + return this->q.empty(); + } + + size_t size() const { + auto thiz = const_cast(this); + std::unique_lock lock(thiz->mutex); + return this->q.size(); + } + + private: + ccstd::queue q; + std::mutex mutex; + }; + + struct Task { + TaskType type; + std::function *callback; + }; + + static LegacyThreadPool *_instance; + + ThreadSafeQueue _taskQueue; + std::atomic _isDone{false}; + std::atomic _isStop{false}; + + //IDEA: std::atomic isn't supported by ndk-r10e while compiling with `armeabi` arch. + // So using a mutex here instead. + int _idleThreadNum{0}; // how many threads are waiting + std::mutex _idleThreadNumMutex; + + std::mutex _mutex; + std::condition_variable _cv; + + int _minThreadNum{0}; + int _maxThreadNum{0}; + int _initedThreadNum{0}; + + std::chrono::time_point _lastShrinkTime; + float _shrinkInterval{5}; + int _shrinkStep{2}; + int _stretchStep{2}; + bool _isFixedSize{false}; +}; + +} // namespace cc diff --git a/cocos/base/Timer.cpp b/cocos/base/Timer.cpp new file mode 100644 index 0000000..7ce3f23 --- /dev/null +++ b/cocos/base/Timer.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Timer.h" + +namespace cc { +namespace utils { + +Timer::Timer(bool doReset) { + if (doReset) { + reset(); + } +} + +void Timer::reset() { + _startTime = Clock::now(); +} + +int64_t Timer::getMicroseconds() const { + auto currentTime = Clock::now(); + auto duration = std::chrono::duration_cast(currentTime - _startTime).count(); + if (duration < 0) { + duration = 0; + } + + return duration; +} + +int64_t Timer::getMilliseconds() const { + auto currentTime = Clock::now(); + auto duration = std::chrono::duration_cast(currentTime - _startTime).count(); + if (duration < 0) { + duration = 0; + } + + return duration; +} + +float Timer::getSeconds(bool highPrecision) const { + if (highPrecision) { + int64_t micro = getMicroseconds(); + return static_cast(micro) / 1000000.0F; + } + + int64_t milli = getMilliseconds(); + return static_cast(milli) / 1000.0F; +} + +} // namespace utils +} // namespace cc diff --git a/cocos/base/Timer.h b/cocos/base/Timer.h new file mode 100644 index 0000000..b55f994 --- /dev/null +++ b/cocos/base/Timer.h @@ -0,0 +1,51 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/Macros.h" + +namespace cc { +namespace utils { + +class CC_DLL Timer { +public: + using Clock = std::chrono::high_resolution_clock; + using TimePoint = Clock::time_point; + + explicit Timer(bool doReset = true); + + void reset(); + int64_t getMicroseconds() const; + int64_t getMilliseconds() const; + float getSeconds(bool highPrecision = false) const; + +private: + TimePoint _startTime; +}; + +} // namespace utils +} // namespace cc diff --git a/cocos/base/TypeDef.h b/cocos/base/TypeDef.h new file mode 100644 index 0000000..5a05a75 --- /dev/null +++ b/cocos/base/TypeDef.h @@ -0,0 +1,79 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" +#include "base/std/container/unordered_map.h" + +using uint = std::uint32_t; +using ushort = std::uint16_t; + +#if (CC_PLATFORM != CC_PLATFORM_LINUX && CC_PLATFORM != CC_PLATFORM_QNX && CC_PLATFORM != CC_PLATFORM_EMSCRIPTEN && CC_PLATFORM != CC_PLATFORM_OPENHARMONY) // linux and openharmony has typedef ulong +using ulong = std::uint32_t; +#endif +using FlagBits = std::uint32_t; + +using index_t = int32_t; +#define CC_INVALID_INDEX (-1) + +#define CC_ENUM_CONVERSION_OPERATOR(T) \ + inline std::underlying_type::type toNumber(const T v) { return static_cast::type>(v); } + +#define CC_ENUM_BITWISE_OPERATORS(T) \ + constexpr bool operator!(const T v) { return !static_cast::type>(v); } \ + constexpr T operator~(const T v) { return static_cast(~static_cast::type>(v)); } \ + constexpr bool operator||(const T lhs, const T rhs) { return (static_cast::type>(lhs) || static_cast::type>(rhs)); } \ + constexpr bool operator&&(const T lhs, const T rhs) { return (static_cast::type>(lhs) && static_cast::type>(rhs)); } \ + constexpr T operator|(const T lhs, const T rhs) { return static_cast(static_cast::type>(lhs) | static_cast::type>(rhs)); } \ + constexpr T operator&(const T lhs, const T rhs) { return static_cast(static_cast::type>(lhs) & static_cast::type>(rhs)); } \ + constexpr T operator^(const T lhs, const T rhs) { return static_cast(static_cast::type>(lhs) ^ static_cast::type>(rhs)); } \ + constexpr T operator+(const T lhs, const T rhs) { return static_cast(static_cast::type>(lhs) + static_cast::type>(rhs)); } \ + constexpr T operator+(const T lhs, bool rhs) { return static_cast(static_cast::type>(lhs) + rhs); } \ + constexpr void operator|=(T &lhs, const T rhs) { lhs = static_cast(static_cast::type>(lhs) | static_cast::type>(rhs)); } \ + constexpr void operator&=(T &lhs, const T rhs) { lhs = static_cast(static_cast::type>(lhs) & static_cast::type>(rhs)); } \ + constexpr void operator^=(T &lhs, const T rhs) { lhs = static_cast(static_cast::type>(lhs) ^ static_cast::type>(rhs)); } \ + constexpr bool hasFlag(const T flags, const T flagToTest) { \ + using ValueType = std::underlying_type::type; \ + CC_ASSERT((static_cast(flagToTest) & (static_cast(flagToTest) - 1)) == 0); \ + return (static_cast(flags) & static_cast(flagToTest)) != 0; \ + } \ + constexpr bool hasAnyFlags(const T flags, const T flagsToTest) { \ + using ValueType = std::underlying_type::type; \ + return (static_cast(flags) & static_cast(flagsToTest)) != 0; \ + } \ + constexpr bool hasAllFlags(const T flags, const T flagsToTest) { \ + using ValueType = std::underlying_type::type; \ + return (static_cast(flags) & static_cast(flagsToTest)) == static_cast(flagsToTest); \ + } \ + constexpr T addFlags(T &flags, const T flagsToAdd) { \ + flags |= flagsToAdd; \ + return flags; \ + } \ + constexpr T removeFlags(T &flags, const T flagsToRemove) { \ + flags &= ~flagsToRemove; \ + return flags; \ + } \ + CC_ENUM_CONVERSION_OPERATOR(T) diff --git a/cocos/base/UTF8.cpp b/cocos/base/UTF8.cpp new file mode 100644 index 0000000..5e2feac --- /dev/null +++ b/cocos/base/UTF8.cpp @@ -0,0 +1,347 @@ +/**************************************************************************** + Copyright (c) 2014 cocos2d-x.org + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/UTF8.h" + +#include +#include + +#include "ConvertUTF/ConvertUTF.h" +#include "base/Log.h" + +namespace cc { + +namespace StringUtils { //NOLINT + +ccstd::string format(const char *format, ...) { +#define CC_MAX_STRING_LENGTH (1024 * 100) + + ccstd::string ret; + + va_list ap; + va_start(ap, format); + + char *buf = static_cast(malloc(CC_MAX_STRING_LENGTH)); + if (buf != nullptr) { + vsnprintf(buf, CC_MAX_STRING_LENGTH, format, ap); + ret = buf; + free(buf); + } + va_end(ap); + + return ret; +} + +/* + * @str: the string to search through. + * @c: the character to not look for. + * + * Return value: the index of the last character that is not c. + * */ +unsigned int getIndexOfLastNotChar16(const ccstd::vector &str, char16_t c) { + int len = static_cast(str.size()); + + int i = len - 1; + for (; i >= 0; --i) { + if (str[i] != c) { + return i; + } + } + + return i; +} + +/* + * @str: the string to trim + * @index: the index to start trimming from. + * + * Trims str st str=[0, index) after the operation. + * + * Return value: the trimmed string. + * */ +static void trimUTF16VectorFromIndex(ccstd::vector &str, int index) { //NOLINT + int size = static_cast(str.size()); + if (index >= size || index < 0) { + return; + } + + str.erase(str.begin() + index, str.begin() + size); +} + +/* + * @ch is the unicode character whitespace? + * + * Reference: http://en.wikipedia.org/wiki/Whitespace_character#Unicode + * + * Return value: weather the character is a whitespace character. + * */ +bool isUnicodeSpace(char16_t ch) { + return (ch >= 0x0009 && ch <= 0x000D) || ch == 0x0020 || ch == 0x0085 || ch == 0x00A0 || ch == 0x1680 || (ch >= 0x2000 && ch <= 0x200A) || ch == 0x2028 || ch == 0x2029 || ch == 0x202F || ch == 0x205F || ch == 0x3000; +} + +bool isCJKUnicode(char16_t ch) { + return (ch >= 0x4E00 && ch <= 0x9FBF) // CJK Unified Ideographs + || (ch >= 0x2E80 && ch <= 0x2FDF) // CJK Radicals Supplement & Kangxi Radicals + || (ch >= 0x2FF0 && ch <= 0x30FF) // Ideographic Description Characters, CJK Symbols and Punctuation & Japanese + || (ch >= 0x3100 && ch <= 0x31BF) // Korean + || (ch >= 0xAC00 && ch <= 0xD7AF) // Hangul Syllables + || (ch >= 0xF900 && ch <= 0xFAFF) // CJK Compatibility Ideographs + || (ch >= 0xFE30 && ch <= 0xFE4F) // CJK Compatibility Forms + || (ch >= 0x31C0 && ch <= 0x4DFF); // Other extensions +} + +void trimUTF16Vector(ccstd::vector &str) { + int len = static_cast(str.size()); + + if (len <= 0) { + return; + } + + int lastIndex = len - 1; + + // Only start trimming if the last character is whitespace.. + if (isUnicodeSpace(str[lastIndex])) { + for (int i = lastIndex - 1; i >= 0; --i) { + if (isUnicodeSpace(str[i])) { + lastIndex = i; + } + + else { + break; + } + } + + trimUTF16VectorFromIndex(str, lastIndex); + } +} + +template +struct ConvertTrait { + using ArgType = T; +}; +template <> +struct ConvertTrait { + using ArgType = UTF8; +}; +template <> +struct ConvertTrait { + using ArgType = UTF16; +}; +template <> +struct ConvertTrait { + using ArgType = UTF32; +}; + +template , typename ToTrait = ConvertTrait> +bool utfConvert( + const std::basic_string &from, std::basic_string &to, + ConversionResult (*cvtfunc)(const typename FromTrait::ArgType **, const typename FromTrait::ArgType *, + typename ToTrait::ArgType **, typename ToTrait::ArgType *, + ConversionFlags)) { + static_assert(sizeof(From) == sizeof(typename FromTrait::ArgType), "Error size mismatched"); + static_assert(sizeof(To) == sizeof(typename ToTrait::ArgType), "Error size mismatched"); + + if (from.empty()) { + to.clear(); + return true; + } + + // See: http://unicode.org/faq/utf_bom.html#gen6 + constexpr int mostBytesPerCharacter = 4; + + const size_t maxNumberOfChars = from.length(); // all UTFs at most one element represents one character. + const size_t numberOfOut = maxNumberOfChars * mostBytesPerCharacter / sizeof(To); + + std::basic_string working(numberOfOut, 0); + + auto inbeg = reinterpret_cast(&from[0]); + auto inend = inbeg + from.length(); + + auto outbeg = reinterpret_cast(&working[0]); + auto outend = outbeg + working.length(); + auto r = cvtfunc(&inbeg, inend, &outbeg, outend, strictConversion); + if (r != conversionOK) { + return false; + } + + working.resize(reinterpret_cast(outbeg) - &working[0]); + to = std::move(working); + + return true; +}; + +CC_DLL void UTF8LooseFix(const ccstd::string &in, ccstd::string &out) { //NOLINT + const auto *p = reinterpret_cast(in.c_str()); + const auto *end = reinterpret_cast(in.c_str() + in.size()); + unsigned ucharLen = 0; + while (p < end) { + ucharLen = getNumBytesForUTF8(*p); + if (isLegalUTF8Sequence(p, p + ucharLen)) { + if (p + ucharLen < end) { + out.append(p, p + ucharLen); + } + p += ucharLen; + } else { + p += 1; //skip bad char + } + } +} + +bool UTF8ToUTF16(const ccstd::string &utf8, std::u16string &outUtf16) { //NOLINT + return utfConvert(utf8, outUtf16, ConvertUTF8toUTF16); +} + +bool UTF8ToUTF32(const ccstd::string &utf8, std::u32string &outUtf32) { //NOLINT + return utfConvert(utf8, outUtf32, ConvertUTF8toUTF32); +} + +bool UTF16ToUTF8(const std::u16string &utf16, ccstd::string &outUtf8) { //NOLINT + return utfConvert(utf16, outUtf8, ConvertUTF16toUTF8); +} + +bool UTF16ToUTF32(const std::u16string &utf16, std::u32string &outUtf32) { //NOLINT + return utfConvert(utf16, outUtf32, ConvertUTF16toUTF32); +} + +bool UTF32ToUTF8(const std::u32string &utf32, ccstd::string &outUtf8) { //NOLINT + return utfConvert(utf32, outUtf8, ConvertUTF32toUTF8); +} + +bool UTF32ToUTF16(const std::u32string &utf32, std::u16string &outUtf16) { //NOLINT + return utfConvert(utf32, outUtf16, ConvertUTF32toUTF16); +} + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS) +ccstd::string getStringUTFCharsJNI(JNIEnv *env, jstring srcjStr, bool *ret) { + ccstd::string utf8Str; + auto *unicodeChar = static_cast(env->GetStringChars(srcjStr, nullptr)); + size_t unicodeCharLength = env->GetStringLength(srcjStr); + const std::u16string unicodeStr(reinterpret_cast(unicodeChar), unicodeCharLength); + bool flag = UTF16ToUTF8(unicodeStr, utf8Str); + + if (ret) { + *ret = flag; + } + + if (!flag) { + utf8Str = ""; + } + env->ReleaseStringChars(srcjStr, unicodeChar); + return utf8Str; +} + +jstring newStringUTFJNI(JNIEnv *env, const ccstd::string &utf8Str, bool *ret) { + std::u16string utf16Str; + bool flag = cc::StringUtils::UTF8ToUTF16(utf8Str, utf16Str); + + if (ret) { + *ret = flag; + } + + if (!flag) { + utf16Str.clear(); + } + jstring stringText = env->NewString(reinterpret_cast(utf16Str.data()), utf16Str.length()); + return stringText; +} +#endif + +ccstd::vector getChar16VectorFromUTF16String(const std::u16string &utf16) { + return ccstd::vector(utf16.begin(), utf16.end()); +} + +long getCharacterCountInUTF8String(const ccstd::string &utf8) { //NOLINT + return getUTF8StringLength(reinterpret_cast(utf8.c_str())); +} + +StringUTF8::StringUTF8(const ccstd::string &newStr) { + replace(newStr); +} + +std::size_t StringUTF8::length() const { + return _str.size(); +} + +void StringUTF8::replace(const ccstd::string &newStr) { + _str.clear(); + if (!newStr.empty()) { + const auto *sequenceUtf8 = reinterpret_cast(newStr.c_str()); + + int lengthString = getUTF8StringLength(sequenceUtf8); + + if (lengthString == 0) { + CC_LOG_DEBUG("Bad utf-8 set string: %s", newStr.c_str()); + return; + } + + while (*sequenceUtf8) { + std::size_t lengthChar = getNumBytesForUTF8(*sequenceUtf8); + + CharUTF8 charUTF8; + charUTF8._char.append(reinterpret_cast(sequenceUtf8), lengthChar); + sequenceUtf8 += lengthChar; + + _str.push_back(charUTF8); + } + } +} + +ccstd::string StringUTF8::getAsCharSequence() const { + ccstd::string charSequence; + + for (auto &charUtf8 : _str) { + charSequence.append(charUtf8._char); + } + + return charSequence; +} + +bool StringUTF8::deleteChar(std::size_t pos) { + if (pos < _str.size()) { + _str.erase(_str.begin() + pos); + return true; + } + return false; +} + +bool StringUTF8::insert(std::size_t pos, const ccstd::string &insertStr) { + StringUTF8 utf8(insertStr); + + return insert(pos, utf8); +} + +bool StringUTF8::insert(std::size_t pos, const StringUTF8 &insertStr) { + if (pos <= _str.size()) { + _str.insert(_str.begin() + pos, insertStr._str.begin(), insertStr._str.end()); + + return true; + } + return false; +} + +} // namespace StringUtils + +} // namespace cc diff --git a/cocos/base/UTF8.h b/cocos/base/UTF8.h new file mode 100644 index 0000000..d8e5c61 --- /dev/null +++ b/cocos/base/UTF8.h @@ -0,0 +1,216 @@ +/**************************************************************************** + Copyright (c) 2014 cocos2d-x.org + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS) + #include "platform/java/jni/JniHelper.h" +#endif + +namespace cc { + +namespace StringUtils { //NOLINT + +template +ccstd::string toString(T arg) { + std::stringstream ss; + ss << arg; + return ss.str(); +} + +ccstd::string CC_DLL format(const char *format, ...) CC_FORMAT_PRINTF(1, 2); + +/** + * @brief Converts from UTF8 string to UTF16 string. + * + * This function resizes \p outUtf16 to required size and + * fill its contents with result UTF16 string if conversion success. + * If conversion fails it guarantees not to change \p outUtf16. + * + * @param inUtf8 The source UTF8 string to be converted from. + * @param outUtf16 The output string to hold the result UTF16s. + * @return True if succeed, otherwise false. + * @note Please check the return value before using \p outUtf16 + * e.g. + * @code + * std::u16string utf16; + * bool ret = StringUtils::UTF8ToUTF16("你好hello", utf16); + * if (ret) { + * do_some_thing_with_utf16(utf16); + * } + * @endcode + */ +CC_DLL bool UTF8ToUTF16(const ccstd::string &inUtf8, std::u16string &outUtf16); //NOLINT + +/** + * @brief Same as \a UTF8ToUTF16 but converts form UTF8 to UTF32. + * + * @see UTF8ToUTF16 + */ +CC_DLL bool UTF8ToUTF32(const ccstd::string &inUtf8, std::u32string &outUtf32); //NOLINT + +/** + * @brief Same as \a UTF8ToUTF16 but converts form UTF16 to UTF8. + * + * @see UTF8ToUTF16 + */ +CC_DLL bool UTF16ToUTF8(const std::u16string &inUtf16, ccstd::string &outUtf8); //NOLINT + +/** + * @brief Same as \a UTF8ToUTF16 but converts form UTF16 to UTF32. + * + * @see UTF8ToUTF16 + */ +CC_DLL bool UTF16ToUTF32(const std::u16string &inUtf16, std::u32string &outUtf32); //NOLINT + +/** + * @brief Same as \a UTF8ToUTF16 but converts form UTF32 to UTF8. + * + * @see UTF8ToUTF16 + */ +CC_DLL bool UTF32ToUTF8(const std::u32string &inUtf32, ccstd::string &outUtf8); //NOLINT + +/** + * @brief Same as \a UTF8ToUTF16 but converts form UTF32 to UTF16. + * + * @see UTF8ToUTF16 + */ +CC_DLL bool UTF32ToUTF16(const std::u32string &inUtf32, std::u16string &outUtf16); //NOLINT + +/** + * @brief Skip some bad char code. + */ +CC_DLL void UTF8LooseFix(const ccstd::string &in, ccstd::string &out); //NOLINT + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS) + +/** +* @brief convert jstring to utf8 ccstd::string, same function with env->getStringUTFChars. +* because getStringUTFChars can not pass special emoticon +* @param env The JNI Env +* @param srcjStr The jstring which want to convert +* @param ret True if the conversion succeeds and the ret pointer isn't null +* @returns the result of utf8 string +*/ +CC_DLL ccstd::string getStringUTFCharsJNI(JNIEnv *env, jstring srcjStr, bool *ret = nullptr); + +/** +* @brief create a jstring with utf8 ccstd::string, same function with env->newStringUTF +* because newStringUTF can not convert special emoticon +* @param env The JNI Env +* @param srcjStr The ccstd::string which want to convert +* @param ret True if the conversion succeeds and the ret pointer isn't null +* @returns the result of jstring,the jstring need to DeleteLocalRef(jstring); +*/ +CC_DLL jstring newStringUTFJNI(JNIEnv *env, const ccstd::string &utf8Str, bool *ret = nullptr); +#endif + +/** + * @brief Trims the unicode spaces at the end of char16_t vector. + */ +CC_DLL void trimUTF16Vector(ccstd::vector &str); //NOLINT + +/** + * @brief Whether the character is a whitespace character. + * @param ch The unicode character. + * @returns Whether the character is a white space character. + * + * @see http://en.wikipedia.org/wiki/Whitespace_character#Unicode + * + */ +CC_DLL bool isUnicodeSpace(char16_t ch); + +/** + * @brief Whether the character is a Chinese/Japanese/Korean character. + * @param ch The unicode character. + * @returns Whether the character is a Chinese character. + * + * @see http://www.searchtb.com/2012/04/chinese_encode.html + * @see http://tieba.baidu.com/p/748765987 + * + */ +CC_DLL bool isCJKUnicode(char16_t ch); + +/** + * @brief Returns the length of the string in characters. + * @param utf8 An UTF-8 encoded string. + * @returns The length of the string in characters. + */ +CC_DLL long getCharacterCountInUTF8String(const ccstd::string &utf8); //NOLINT + +/** + * @brief Gets the index of the last character that is not equal to the character given. + * @param str The string to be searched. + * @param c The character to be searched for. + * @returns The index of the last character that is not \p c. + */ +CC_DLL unsigned int getIndexOfLastNotChar16(const ccstd::vector &str, char16_t c); + +/** + * @brief Gets char16_t vector from a given utf16 string. + */ +CC_DLL ccstd::vector getChar16VectorFromUTF16String(const std::u16string &utf16); + +/** +* Utf8 sequence +* Store all utf8 chars as ccstd::string +* Build from ccstd::string +*/ +class CC_DLL StringUTF8 { +public: + struct CharUTF8 { + ccstd::string _char; + bool isAnsi() { return _char.size() == 1; } + }; + using CharUTF8Store = ccstd::vector; + + StringUTF8() = default; + explicit StringUTF8(const ccstd::string &newStr); + ~StringUTF8() = default; + + std::size_t length() const; + void replace(const ccstd::string &newStr); + + ccstd::string getAsCharSequence() const; + + bool deleteChar(std::size_t pos); + bool insert(std::size_t pos, const ccstd::string &insertStr); + bool insert(std::size_t pos, const StringUTF8 &insertStr); + + CharUTF8Store &getString() { return _str; } + +private: + CharUTF8Store _str; +}; + +} // namespace StringUtils + +} // namespace cc diff --git a/cocos/base/Utils.cpp b/cocos/base/Utils.cpp new file mode 100644 index 0000000..c7f268c --- /dev/null +++ b/cocos/base/Utils.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** + Copyright (c) 2010 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/Utils.h" + +#if CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + #include +#endif + +#include +#include +#include + +#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED +#include "boost/stacktrace.hpp" + +#include "base/base64.h" +#include "base/memory/MemoryHook.h" +#include "platform/FileUtils.h" + +namespace cc { +namespace utils { + +#define MAX_ITOA_BUFFER_SIZE 256 +double atof(const char *str) { + if (str == nullptr) { + return 0.0; + } + + char buf[MAX_ITOA_BUFFER_SIZE]; + strncpy(buf, str, MAX_ITOA_BUFFER_SIZE); + + // strip string, only remain 7 numbers after '.' + char *dot = strchr(buf, '.'); + if (dot != nullptr && dot - buf + 8 < MAX_ITOA_BUFFER_SIZE) { + dot[8] = '\0'; + } + + return ::atof(buf); +} + +uint32_t nextPOT(uint32_t x) { + x = x - 1; + x = x | (x >> 1); + x = x | (x >> 2); + x = x | (x >> 4); + x = x | (x >> 8); + x = x | (x >> 16); + return x + 1; +} + +// painfully slow to execute, use with caution +ccstd::string getStacktrace(uint32_t skip, uint32_t maxDepth) { + return boost::stacktrace::to_string(boost::stacktrace::stacktrace(skip, maxDepth)); +} + +} // namespace utils + +#if USE_MEMORY_LEAK_DETECTOR + + // Make sure GMemoryHook to be initialized first. + #if (CC_COMPILER == CC_COMPILER_MSVC) + #pragma warning(push) + #pragma warning(disable : 4073) + #pragma init_seg(lib) +MemoryHook GMemoryHook; + #pragma warning(pop) + #elif (CC_COMPILER == CC_COMPILER_GNUC || CC_COMPILER == CC_COMPILER_CLANG) +MemoryHook GMemoryHook __attribute__((init_priority(101))); + #endif + +#endif +} // namespace cc diff --git a/cocos/base/Utils.h b/cocos/base/Utils.h new file mode 100644 index 0000000..95da0d1 --- /dev/null +++ b/cocos/base/Utils.h @@ -0,0 +1,391 @@ +/**************************************************************************** + Copyright (c) 2010 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include "base/Macros.h" +#include "base/TypeDef.h" +#include "base/std/container/string.h" +#include "base/std/container/vector.h" +/** @file ccUtils.h +Misc free functions +*/ + +namespace cc { +namespace utils { + +CC_DLL ccstd::string getStacktrace(uint32_t skip = 0, uint32_t maxDepth = UINT32_MAX); + +/** + * Returns the Next Power of Two value. + * Examples: + * - If "value" is 15, it will return 16. + * - If "value" is 16, it will return 16. + * - If "value" is 17, it will return 32. + * @param value The value to get next power of two. + * @return Returns the next power of two value. + * @since v0.99.5 +*/ +CC_DLL uint32_t nextPOT(uint32_t x); + +/** + * Same to ::atof, but strip the string, remain 7 numbers after '.' before call atof. + * Why we need this? Because in android c++_static, atof ( and std::atof ) is unsupported for numbers have long decimal part and contain + * several numbers can approximate to 1 ( like 90.099998474121094 ), it will return inf. This function is used to fix this bug. + * @param str The string be to converted to double. + * @return Returns converted value of a string. + */ +CC_DLL double atof(const char *str); + +#pragma warning(disable : 4146) +template ::value && std::is_unsigned::value>> +inline T getLowestBit(T mask) { + return mask & (-mask); +} +#pragma warning(default : 4146) + +template ::value && std::is_unsigned::value>> +inline T clearLowestBit(T mask) { + return mask & (mask - 1); +} + +// v must be power of 2 +inline uint32_t getBitPosition(uint32_t v) { + if (!v) return 0; + uint32_t c = 32; + if (v & 0x0000FFFF) c -= 16; + if (v & 0x00FF00FF) c -= 8; + if (v & 0x0F0F0F0F) c -= 4; + if (v & 0x33333333) c -= 2; + if (v & 0x55555555) c -= 1; + return c; +} + +// v must be power of 2 +inline uint64_t getBitPosition(uint64_t v) { + if (!v) return 0; + uint64_t c = 64; + if (v & 0x00000000FFFFFFFFLL) c -= 32; + if (v & 0x0000FFFF0000FFFFLL) c -= 16; + if (v & 0x00FF00FF00FF00FFLL) c -= 8; + if (v & 0x0F0F0F0F0F0F0F0FLL) c -= 4; + if (v & 0x3333333333333333LL) c -= 2; + if (v & 0x5555555555555555LL) c -= 1; + return c; +} + +template ::value>> +inline size_t popcount(T mask) { + return std::bitset(mask).count(); +} + +template ::value>> +inline T alignTo(T size, T alignment) { + return ((size - 1) / alignment + 1) * alignment; +} + +template +constexpr uint ALIGN_TO = ((size - 1) / alignment + 1) * alignment; + +template +inline uint toUint(T value) { + static_assert(std::is_arithmetic::value, "T must be numeric"); + + CC_ASSERT(static_cast(value) <= static_cast(std::numeric_limits::max())); + + return static_cast(value); +} + +template +Map &mergeToMap(Map &outMap, const Map &inMap) { + for (const auto &e : inMap) { + outMap.emplace(e.first, e.second); + } + return outMap; +} + +namespace numext { + +template +CC_FORCE_INLINE Tgt bit_cast(const Src &src) { // NOLINT(readability-identifier-naming) + // The behaviour of memcpy is not specified for non-trivially copyable types + static_assert(std::is_trivially_copyable::value, "THIS_TYPE_IS_NOT_SUPPORTED"); + static_assert(std::is_trivially_copyable::value && std::is_default_constructible::value, + "THIS_TYPE_IS_NOT_SUPPORTED"); + static_assert(sizeof(Src) == sizeof(Tgt), "THIS_TYPE_IS_NOT_SUPPORTED"); + + Tgt tgt; + // Load src into registers first. This allows the memcpy to be elided by CUDA. + const Src staged = src; + memcpy(&tgt, &staged, sizeof(Tgt)); + return tgt; +} + +} // namespace numext + +// Following the Arm ACLE arm_neon.h should also include arm_fp16.h but not all +// compilers seem to follow this. We therefore include it explicitly. +// See also: https://bugs.llvm.org/show_bug.cgi?id=47955 +#if defined(CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC) + #include +#endif + +// Code from https://gitlab.com/libeigen/eigen/-/blob/master/Eigen/src/Core/arch/Default/Half.h#L586 +struct HalfRaw { + constexpr HalfRaw() : x(0) {} +#if defined(CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC) + explicit HalfRaw(uint16_t raw) : x(numext::bit_cast<__fp16>(raw)) { + } + __fp16 x; +#else + explicit constexpr HalfRaw(uint16_t raw) : x(raw) {} + uint16_t x; // NOLINT(modernize-use-default-member-init) +#endif +}; + +// Conversion routines, including fallbacks for the host or older CUDA. +// Note that newer Intel CPUs (Haswell or newer) have vectorized versions of +// these in hardware. If we need more performance on older/other CPUs, they are +// also possible to vectorize directly. + +CC_FORCE_INLINE HalfRaw rawUint16ToHalf(uint16_t x) { + // We cannot simply do a "return HalfRaw(x)" here, because HalfRaw is union type + // in the hip_fp16 header file, and that will trigger a compile error + // On the other hand, having anything but a return statement also triggers a compile error + // because this is constexpr function. + // Fortunately, since we need to disable EIGEN_CONSTEXPR for GPU anyway, we can get out + // of this catch22 by having separate bodies for GPU / non GPU +#if defined(CC_HAS_GPU_FP16) + HalfRaw h; + h.x = x; + return h; +#else + return HalfRaw(x); +#endif +} + +CC_FORCE_INLINE uint16_t rawHalfAsUint16(const HalfRaw &h) { + // HIP/CUDA/Default have a member 'x' of type uint16_t. + // For ARM64 native half, the member 'x' is of type __fp16, so we need to bit-cast. + // For SYCL, cl::sycl::half is _Float16, so cast directly. +#if defined(CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC) + return numext::bit_cast(h.x); +#else + return h.x; +#endif +} + +union float32_bits { + unsigned int u; + float f; +}; + +CC_FORCE_INLINE HalfRaw floatToHalf(float ff) { +#if defined(CC_HAS_FP16_C) + HalfRaw h; + #ifdef _MSC_VER + // MSVC does not have scalar instructions. + h.x = _mm_extract_epi16(_mm_cvtps_ph(_mm_set_ss(ff), 0), 0); + #else + h.x = _cvtss_sh(ff, 0); + #endif + return h; + +#elif defined(CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC) + HalfRaw h; + h.x = static_cast<__fp16>(ff); + return h; + +#else + float32_bits f; + f.f = ff; + + const float32_bits f32infty = {255 << 23}; + const float32_bits f16max = {(127 + 16) << 23}; + const float32_bits denorm_magic = {((127 - 15) + (23 - 10) + 1) << 23}; // NOLINT(readability-identifier-naming) + unsigned int sign_mask = 0x80000000U; // NOLINT + HalfRaw o; + o.x = static_cast(0x0U); + + unsigned int sign = f.u & sign_mask; + f.u ^= sign; + + // NOTE all the integer compares in this function can be safely + // compiled into signed compares since all operands are below + // 0x80000000. Important if you want fast straight SSE2 code + // (since there's no unsigned PCMPGTD). + + if (f.u >= f16max.u) { // result is Inf or NaN (all exponent bits set) + o.x = (f.u > f32infty.u) ? 0x7e00 : 0x7c00; // NaN->qNaN and Inf->Inf + } else { // (De)normalized number or zero + if (f.u < (113 << 23)) { // resulting FP16 is subnormal or zero + // use a magic value to align our 10 mantissa bits at the bottom of + // the float. as long as FP addition is round-to-nearest-even this + // just works. + f.f += denorm_magic.f; + + // and one integer subtract of the bias later, we have our final float! + o.x = static_cast(f.u - denorm_magic.u); + } else { + unsigned int mant_odd = (f.u >> 13) & 1; // NOLINT(readability-identifier-naming) // resulting mantissa is odd + + // update exponent, rounding bias part 1 + // Equivalent to `f.u += ((unsigned int)(15 - 127) << 23) + 0xfff`, but + // without arithmetic overflow. + f.u += 0xc8000fffU; + // rounding bias part 2 + f.u += mant_odd; + // take the bits! + o.x = static_cast(f.u >> 13); + } + } + + o.x |= static_cast(sign >> 16); + return o; +#endif +} + +CC_FORCE_INLINE float halfToFloat(HalfRaw h) { +#if defined(CC_HAS_FP16_C) + #ifdef _MSC_VER + // MSVC does not have scalar instructions. + return _mm_cvtss_f32(_mm_cvtph_ps(_mm_set1_epi16(h.x))); + #else + return _cvtsh_ss(h.x); + #endif +#elif defined(CC_HAS_ARM64_FP16_SCALAR_ARITHMETIC) + return static_cast(h.x); +#else + const float32_bits magic = {113 << 23}; + const unsigned int shifted_exp = 0x7c00 << 13; // NOLINT(readability-identifier-naming) // exponent mask after shift + float32_bits o; + + o.u = (h.x & 0x7fff) << 13; // exponent/mantissa bits + unsigned int exp = shifted_exp & o.u; // just the exponent + o.u += (127 - 15) << 23; // exponent adjust + + // handle exponent special cases + if (exp == shifted_exp) { // Inf/NaN? + o.u += (128 - 16) << 23; // extra exp adjust + } else if (exp == 0) { // Zero/Denormal? + o.u += 1 << 23; // extra exp adjust + o.f -= magic.f; // renormalize + } + + o.u |= (h.x & 0x8000) << 16; // sign bit + return o.f; +#endif +} + +namespace array { + +/** + * @zh + * 移除首个指定的数组元素。判定元素相等时相当于于使用了 `Array.prototype.indexOf`。 + * @en + * Removes the first occurrence of a specific object from the array. + * Decision of the equality of elements is similar to `Array.prototype.indexOf`. + * @param array 数组。 + * @param value 待移除元素。 + */ +template +bool remove(ccstd::vector &array, T value) { + auto iter = std::find(array.begin(), array.end(), value); + if (iter != array.end()) { + array.erase(iter); + return true; + } + return false; +} + +/** + * @zh + * 移除指定索引的数组元素。 + * @en + * Removes the array item at the specified index. + * @param array 数组。 + * @param index 待移除元素的索引。 + */ +template +bool removeAt(ccstd::vector &array, int32_t index) { + if (index >= 0 && index < static_cast(array.size())) { + array.erase(array.begin() + index); + return true; + } + return false; +} + +/** + * @zh + * 移除指定索引的数组元素。 + * 此函数十分高效,但会改变数组的元素次序。 + * @en + * Removes the array item at the specified index. + * It's faster but the order of the array will be changed. + * @param array 数组。 + * @param index 待移除元素的索引。 + */ +template +bool fastRemoveAt(ccstd::vector &array, int32_t index) { + const auto length = static_cast(array.size()); + if (index < 0 || index >= length) { + return false; + } + array[index] = array[length - 1]; + array.resize(length - 1); + return true; +} + +/** + * @zh + * 移除首个指定的数组元素。判定元素相等时相当于于使用了 `Array.prototype.indexOf`。 + * 此函数十分高效,但会改变数组的元素次序。 + * @en + * Removes the first occurrence of a specific object from the array. + * Decision of the equality of elements is similar to `Array.prototype.indexOf`. + * It's faster but the order of the array will be changed. + * @param array 数组。 + * @param value 待移除元素。 + */ +template +bool fastRemove(ccstd::vector &array, T value) { + auto iter = std::find(array.begin(), array.end(), value); + if (iter != array.end()) { + *iter = array[array.size() - 1]; + array.resize(array.size() - 1); + return true; + } + return false; +} + +} // namespace array +} // namespace utils +} // namespace cc diff --git a/cocos/base/Value.cpp b/cocos/base/Value.cpp new file mode 100644 index 0000000..eb50257 --- /dev/null +++ b/cocos/base/Value.cpp @@ -0,0 +1,829 @@ +/**************************************************************************** + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/Value.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "base/Utils.h" +#include "base/memory/Memory.h" + +namespace cc { + +const ValueVector VALUE_VECTOR_NULL; +const ValueMap VALUE_MAP_NULL = {}; +const ValueMapIntKey VALUE_MAP_INT_KEY_NULL = {}; + +const Value Value::VALUE_NULL; + +Value::Value() +: _type(Type::NONE) { + memset(&_field, 0, sizeof(_field)); +} + +Value::Value(unsigned char v) +: _type(Type::BYTE) { + _field.byteVal = v; +} + +Value::Value(int v) +: _type(Type::INTEGER) { + _field.intVal = v; +} + +Value::Value(unsigned int v) +: _type(Type::UNSIGNED) { + _field.unsignedVal = v; +} + +Value::Value(float v) +: _type(Type::FLOAT) { + _field.floatVal = v; +} + +Value::Value(double v) +: _type(Type::DOUBLE) { + _field.doubleVal = v; +} + +Value::Value(bool v) +: _type(Type::BOOLEAN) { + _field.boolVal = v; +} + +Value::Value(const char *v) +: _type(Type::STRING) { + _field.strVal = ccnew ccstd::string(); + if (v) { + *_field.strVal = v; + } +} + +Value::Value(const ccstd::string &v) +: _type(Type::STRING) { + _field.strVal = ccnew ccstd::string(); + *_field.strVal = v; +} + +Value::Value(const ValueVector &v) +: _type(Type::VECTOR) { + _field.vectorVal = ccnew ValueVector(); + *_field.vectorVal = v; +} + +Value::Value(ValueVector &&v) +: _type(Type::VECTOR) { + _field.vectorVal = ccnew ValueVector(); + *_field.vectorVal = std::move(v); +} + +Value::Value(const ValueMap &v) +: _type(Type::MAP) { + _field.mapVal = ccnew ValueMap(); + *_field.mapVal = v; +} + +Value::Value(ValueMap &&v) +: _type(Type::MAP) { + _field.mapVal = ccnew ValueMap(); + *_field.mapVal = std::move(v); +} + +Value::Value(const ValueMapIntKey &v) +: _type(Type::INT_KEY_MAP) { + _field.intKeyMapVal = ccnew ValueMapIntKey(); + *_field.intKeyMapVal = v; +} + +Value::Value(ValueMapIntKey &&v) +: _type(Type::INT_KEY_MAP) { + _field.intKeyMapVal = ccnew ValueMapIntKey(); + *_field.intKeyMapVal = std::move(v); +} + +Value::Value(const Value &other) //NOLINT(misc-no-recursion) +: _type(Type::NONE) { + *this = other; +} + +Value::Value(Value &&other) noexcept +: _type(Type::NONE) { + *this = std::move(other); +} + +Value::~Value() { + clear(); +} + +Value &Value::operator=(const Value &other) { //NOLINT(misc-no-recursion) + if (this != &other) { + reset(other._type); + + switch (other._type) { + case Type::BYTE: + _field.byteVal = other._field.byteVal; + break; + case Type::INTEGER: + _field.intVal = other._field.intVal; + break; + case Type::UNSIGNED: + _field.unsignedVal = other._field.unsignedVal; + break; + case Type::FLOAT: + _field.floatVal = other._field.floatVal; + break; + case Type::DOUBLE: + _field.doubleVal = other._field.doubleVal; + break; + case Type::BOOLEAN: + _field.boolVal = other._field.boolVal; + break; + case Type::STRING: + if (_field.strVal == nullptr) { + _field.strVal = ccnew ccstd::string(); + } + *_field.strVal = *other._field.strVal; + break; + case Type::VECTOR: + if (_field.vectorVal == nullptr) { + _field.vectorVal = ccnew ValueVector(); + } + *_field.vectorVal = *other._field.vectorVal; + break; + case Type::MAP: + if (_field.mapVal == nullptr) { + _field.mapVal = ccnew ValueMap(); + } + *_field.mapVal = *other._field.mapVal; + break; + case Type::INT_KEY_MAP: + if (_field.intKeyMapVal == nullptr) { + _field.intKeyMapVal = ccnew ValueMapIntKey(); + } + *_field.intKeyMapVal = *other._field.intKeyMapVal; + break; + default: + break; + } + } + return *this; +} + +Value &Value::operator=(Value &&other) noexcept { + if (this != &other) { + clear(); + switch (other._type) { + case Type::BYTE: + _field.byteVal = other._field.byteVal; + break; + case Type::INTEGER: + _field.intVal = other._field.intVal; + break; + case Type::UNSIGNED: + _field.unsignedVal = other._field.unsignedVal; + break; + case Type::FLOAT: + _field.floatVal = other._field.floatVal; + break; + case Type::DOUBLE: + _field.doubleVal = other._field.doubleVal; + break; + case Type::BOOLEAN: + _field.boolVal = other._field.boolVal; + break; + case Type::STRING: + _field.strVal = other._field.strVal; + break; + case Type::VECTOR: + _field.vectorVal = other._field.vectorVal; + break; + case Type::MAP: + _field.mapVal = other._field.mapVal; + break; + case Type::INT_KEY_MAP: + _field.intKeyMapVal = other._field.intKeyMapVal; + break; + default: + break; + } + _type = other._type; + + memset(&other._field, 0, sizeof(other._field)); + other._type = Type::NONE; + } + + return *this; +} + +Value &Value::operator=(unsigned char v) { + reset(Type::BYTE); + _field.byteVal = v; + return *this; +} + +Value &Value::operator=(int v) { + reset(Type::INTEGER); + _field.intVal = v; + return *this; +} + +Value &Value::operator=(unsigned int v) { + reset(Type::UNSIGNED); + _field.unsignedVal = v; + return *this; +} + +Value &Value::operator=(float v) { + reset(Type::FLOAT); + _field.floatVal = v; + return *this; +} + +Value &Value::operator=(double v) { + reset(Type::DOUBLE); + _field.doubleVal = v; + return *this; +} + +Value &Value::operator=(bool v) { + reset(Type::BOOLEAN); + _field.boolVal = v; + return *this; +} + +Value &Value::operator=(const char *v) { + reset(Type::STRING); + *_field.strVal = v ? v : ""; + return *this; +} + +Value &Value::operator=(const ccstd::string &v) { + reset(Type::STRING); + *_field.strVal = v; + return *this; +} + +Value &Value::operator=(const ValueVector &v) { + reset(Type::VECTOR); + *_field.vectorVal = v; + return *this; +} + +Value &Value::operator=(ValueVector &&v) { + reset(Type::VECTOR); + *_field.vectorVal = std::move(v); + return *this; +} + +Value &Value::operator=(const ValueMap &v) { + reset(Type::MAP); + *_field.mapVal = v; + return *this; +} + +Value &Value::operator=(ValueMap &&v) { + reset(Type::MAP); + *_field.mapVal = std::move(v); + return *this; +} + +Value &Value::operator=(const ValueMapIntKey &v) { + reset(Type::INT_KEY_MAP); + *_field.intKeyMapVal = v; + return *this; +} + +Value &Value::operator=(ValueMapIntKey &&v) { + reset(Type::INT_KEY_MAP); + *_field.intKeyMapVal = std::move(v); + return *this; +} + +bool Value::operator!=(const Value &v) { + return !(*this == v); +} +bool Value::operator!=(const Value &v) const { //NOLINT(misc-no-recursion) + return !(*this == v); +} + +bool Value::operator==(const Value &v) { + const auto &t = *this; + return t == v; +} +bool Value::operator==(const Value &v) const { //NOLINT(misc-no-recursion) + if (this == &v) { + return true; + } + + if (v._type != this->_type) { + return false; + } + + if (this->isNull()) { + return true; + } + + switch (_type) { + case Type::BYTE: return v._field.byteVal == this->_field.byteVal; + case Type::INTEGER: return v._field.intVal == this->_field.intVal; + case Type::UNSIGNED: return v._field.unsignedVal == this->_field.unsignedVal; + case Type::BOOLEAN: return v._field.boolVal == this->_field.boolVal; + case Type::STRING: return *v._field.strVal == *this->_field.strVal; + case Type::FLOAT: return std::abs(v._field.floatVal - this->_field.floatVal) <= FLT_EPSILON; + case Type::DOUBLE: return std::abs(v._field.doubleVal - this->_field.doubleVal) <= DBL_EPSILON; + case Type::VECTOR: { + const auto &v1 = *(this->_field.vectorVal); + const auto &v2 = *(v._field.vectorVal); + const auto size = v1.size(); + if (size == v2.size()) { + for (size_t i = 0; i < size; i++) { + if (v1[i] != v2[i]) { + return false; + } + } + return true; + } + return false; + } + case Type::MAP: { + const auto &map1 = *(this->_field.mapVal); + const auto &map2 = *(v._field.mapVal); + for (const auto &kvp : map1) { //NOLINT + auto it = map2.find(kvp.first); + if (it == map2.end() || it->second != kvp.second) { + return false; + } + } + return true; + } + case Type::INT_KEY_MAP: { + const auto &map1 = *(this->_field.intKeyMapVal); + const auto &map2 = *(v._field.intKeyMapVal); + for (const auto &kvp : map1) { //NOLINT + auto it = map2.find(kvp.first); + if (it == map2.end() || it->second != kvp.second) { + return false; + } + } + return true; + } + default: + break; + }; + + return false; +} + +/// Convert value to a specified type +unsigned char Value::asByte() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + + if (_type == Type::BYTE) { + return _field.byteVal; + } + + if (_type == Type::INTEGER) { + return static_cast(_field.intVal); + } + + if (_type == Type::UNSIGNED) { + return static_cast(_field.unsignedVal); + } + + if (_type == Type::STRING) { + return static_cast(atoi(_field.strVal->c_str())); + } + + if (_type == Type::FLOAT) { + return static_cast(_field.floatVal); + } + + if (_type == Type::DOUBLE) { + return static_cast(_field.doubleVal); + } + + if (_type == Type::BOOLEAN) { + return _field.boolVal ? 1 : 0; + } + + return 0; +} + +int Value::asInt() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + if (_type == Type::INTEGER) { + return _field.intVal; + } + + if (_type == Type::UNSIGNED) { + CC_ASSERT(_field.unsignedVal < INT_MAX); + return static_cast(_field.unsignedVal); + } + + if (_type == Type::BYTE) { + return _field.byteVal; + } + + if (_type == Type::STRING) { + return atoi(_field.strVal->c_str()); + } + + if (_type == Type::FLOAT) { + return static_cast(_field.floatVal); + } + + if (_type == Type::DOUBLE) { + return static_cast(_field.doubleVal); + } + + if (_type == Type::BOOLEAN) { + return _field.boolVal ? 1 : 0; + } + + return 0; +} + +unsigned int Value::asUnsignedInt() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + if (_type == Type::UNSIGNED) { + return _field.unsignedVal; + } + + if (_type == Type::INTEGER) { + CC_ASSERT(_field.intVal >= 0); + return static_cast(_field.intVal); + } + + if (_type == Type::BYTE) { + return static_cast(_field.byteVal); + } + + if (_type == Type::STRING) { + // NOTE: strtoul is required (need to augment on unsupported platforms) + return static_cast(strtoul(_field.strVal->c_str(), nullptr, 10)); + } + + if (_type == Type::FLOAT) { + return static_cast(_field.floatVal); + } + + if (_type == Type::DOUBLE) { + return static_cast(_field.doubleVal); + } + + if (_type == Type::BOOLEAN) { + return _field.boolVal ? 1U : 0U; + } + + return 0U; +} + +float Value::asFloat() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + if (_type == Type::FLOAT) { + return _field.floatVal; + } + + if (_type == Type::BYTE) { + return static_cast(_field.byteVal); + } + + if (_type == Type::STRING) { + return static_cast(utils::atof(_field.strVal->c_str())); + } + + if (_type == Type::INTEGER) { + return static_cast(_field.intVal); + } + + if (_type == Type::UNSIGNED) { + return static_cast(_field.unsignedVal); + } + + if (_type == Type::DOUBLE) { + return static_cast(_field.doubleVal); + } + + if (_type == Type::BOOLEAN) { + return _field.boolVal ? 1.0F : 0.0F; + } + + return 0.0F; +} + +double Value::asDouble() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + if (_type == Type::DOUBLE) { + return _field.doubleVal; + } + + if (_type == Type::BYTE) { + return static_cast(_field.byteVal); + } + + if (_type == Type::STRING) { + return static_cast(utils::atof(_field.strVal->c_str())); + } + + if (_type == Type::INTEGER) { + return static_cast(_field.intVal); + } + + if (_type == Type::UNSIGNED) { + return static_cast(_field.unsignedVal); + } + + if (_type == Type::FLOAT) { + return static_cast(_field.floatVal); + } + + if (_type == Type::BOOLEAN) { + return _field.boolVal ? 1.0 : 0.0; + } + + return 0.0; +} + +bool Value::asBool() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + if (_type == Type::BOOLEAN) { + return _field.boolVal; + } + + if (_type == Type::BYTE) { + return _field.byteVal != 0; + } + + if (_type == Type::STRING) { + return *_field.strVal != "0" || *_field.strVal != "false"; + } + + if (_type == Type::INTEGER) { + return _field.intVal != 0; + } + + if (_type == Type::UNSIGNED) { + return _field.unsignedVal != 0; + } + + if (_type == Type::FLOAT) { + return _field.floatVal != 0.0F; + } + + if (_type == Type::DOUBLE) { + return _field.doubleVal != 0.0; + } + + return false; +} + +ccstd::string Value::asString() const { + CC_ASSERT(_type != Type::VECTOR && _type != Type::MAP && _type != Type::INT_KEY_MAP); + + if (_type == Type::STRING) { + return *_field.strVal; + } + + std::stringstream ret; + + switch (_type) { + case Type::BYTE: + ret << _field.byteVal; + break; + case Type::INTEGER: + ret << _field.intVal; + break; + case Type::UNSIGNED: + ret << _field.unsignedVal; + break; + case Type::FLOAT: + ret << std::fixed << std::setprecision(7) << _field.floatVal; + break; + case Type::DOUBLE: + ret << std::fixed << std::setprecision(16) << _field.doubleVal; + break; + case Type::BOOLEAN: + ret << (_field.boolVal ? "true" : "false"); + break; + default: + break; + } + return ret.str(); +} + +ValueVector &Value::asValueVector() { + CC_ASSERT_EQ(_type, Type::VECTOR); + return *_field.vectorVal; +} + +const ValueVector &Value::asValueVector() const { + CC_ASSERT_EQ(_type, Type::VECTOR); + return *_field.vectorVal; +} + +ValueMap &Value::asValueMap() { + CC_ASSERT_EQ(_type, Type::MAP); + return *_field.mapVal; +} + +const ValueMap &Value::asValueMap() const { + CC_ASSERT_EQ(_type, Type::MAP); + return *_field.mapVal; +} + +ValueMapIntKey &Value::asIntKeyMap() { + CC_ASSERT_EQ(_type, Type::INT_KEY_MAP); + return *_field.intKeyMapVal; +} + +const ValueMapIntKey &Value::asIntKeyMap() const { + CC_ASSERT_EQ(_type, Type::INT_KEY_MAP); + return *_field.intKeyMapVal; +} + +static ccstd::string getTabs(int depth) { + ccstd::string tabWidth; + + for (int i = 0; i < depth; ++i) { + tabWidth += "\t"; + } + + return tabWidth; +} + +static ccstd::string visit(const Value &v, int depth); + +static ccstd::string visitVector(const ValueVector &v, int depth) { //NOLINT[misc-no-recursion] + std::stringstream ret; + + if (depth > 0) { + ret << "\n"; + } + + ret << getTabs(depth) << "[\n"; + + int i = 0; + for (const auto &child : v) { + ret << getTabs(depth + 1) << i << ": " << visit(child, depth + 1); + ++i; + } + + ret << getTabs(depth) << "]\n"; + + return ret.str(); +} + +template +static ccstd::string visitMap(const T &v, int depth) { //NOLINT[misc-no-recursion] + std::stringstream ret; + + if (depth > 0) { + ret << "\n"; + } + + ret << getTabs(depth) << "{\n"; + + for (auto iter = v.begin(); iter != v.end(); ++iter) { + ret << getTabs(depth + 1) << iter->first << ": "; + ret << visit(iter->second, depth + 1); + } + + ret << getTabs(depth) << "}\n"; + + return ret.str(); +} + +static ccstd::string visit(const Value &v, int depth) { //NOLINT[misc-no-recursion] + std::stringstream ret; + + switch (v.getType()) { + case Value::Type::NONE: + case Value::Type::BYTE: + case Value::Type::INTEGER: + case Value::Type::UNSIGNED: + case Value::Type::FLOAT: + case Value::Type::DOUBLE: + case Value::Type::BOOLEAN: + case Value::Type::STRING: + ret << v.asString() << "\n"; + break; + case Value::Type::VECTOR: + ret << visitVector(v.asValueVector(), depth); + break; + case Value::Type::MAP: + ret << visitMap(v.asValueMap(), depth); + break; + case Value::Type::INT_KEY_MAP: + ret << visitMap(v.asIntKeyMap(), depth); + break; + default: + CC_ABORT(); + break; + } + + return ret.str(); +} + +ccstd::string Value::getDescription() const { + ccstd::string ret("\n"); + ret += visit(*this, 0); + return ret; +} + +void Value::clear() { + // Free memory the old value allocated + switch (_type) { + case Type::BYTE: + _field.byteVal = 0; + break; + case Type::INTEGER: + _field.intVal = 0; + break; + case Type::UNSIGNED: + _field.unsignedVal = 0U; + break; + case Type::FLOAT: + _field.floatVal = 0.0F; + break; + case Type::DOUBLE: + _field.doubleVal = 0.0; + break; + case Type::BOOLEAN: + _field.boolVal = false; + break; + case Type::STRING: + CC_SAFE_DELETE(_field.strVal); + break; + case Type::VECTOR: + CC_SAFE_DELETE(_field.vectorVal); + break; + case Type::MAP: + CC_SAFE_DELETE(_field.mapVal); + break; + case Type::INT_KEY_MAP: + CC_SAFE_DELETE(_field.intKeyMapVal); + break; + default: + break; + } + + _type = Type::NONE; +} + +void Value::reset(Type type) { + if (_type == type) { + return; + } + + clear(); + + // Allocate memory for the new value + switch (type) { + case Type::STRING: + _field.strVal = ccnew ccstd::string(); + break; + case Type::VECTOR: + _field.vectorVal = ccnew ValueVector(); + break; + case Type::MAP: + _field.mapVal = ccnew ValueMap(); + break; + case Type::INT_KEY_MAP: + _field.intKeyMapVal = ccnew ValueMapIntKey(); + break; + default: + break; + } + + _type = type; +} + +} // namespace cc diff --git a/cocos/base/Value.h b/cocos/base/Value.h new file mode 100644 index 0000000..46a2bf0 --- /dev/null +++ b/cocos/base/Value.h @@ -0,0 +1,238 @@ +/**************************************************************************** + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" + +namespace cc { + +class Value; + +using ValueVector = ccstd::vector; +using ValueMap = ccstd::unordered_map; +using ValueMapIntKey = ccstd::unordered_map; + +CC_DLL extern const ValueVector VALUE_VECTOR_NULL; +CC_DLL extern const ValueMap VALUE_MAP_NULL; +CC_DLL extern const ValueMapIntKey VALUE_MAP_INT_KEY_NULL; + +/* + * This class is provide as a wrapper of basic types, such as int and bool. + */ +class CC_DLL Value { +public: + /** A predefined Value that has not value. */ + static const Value VALUE_NULL; + + /** Default constructor. */ + Value(); + + /** Create a Value by an unsigned char value. */ + explicit Value(unsigned char v); + + /** Create a Value by an integer value. */ + explicit Value(int v); + + /** Create a Value by an unsigned value. */ + explicit Value(unsigned int v); + + /** Create a Value by a float value. */ + explicit Value(float v); + + /** Create a Value by a double value. */ + explicit Value(double v); + + /** Create a Value by a bool value. */ + explicit Value(bool v); + + /** Create a Value by a char pointer. It will copy the chars internally. */ + explicit Value(const char *v); + + /** Create a Value by a string. */ + explicit Value(const ccstd::string &v); + + /** Create a Value by a ValueVector object. */ + explicit Value(const ValueVector &v); + /** Create a Value by a ValueVector object. It will use std::move internally. */ + explicit Value(ValueVector &&v); + + /** Create a Value by a ValueMap object. */ + explicit Value(const ValueMap &v); + /** Create a Value by a ValueMap object. It will use std::move internally. */ + explicit Value(ValueMap &&v); + + /** Create a Value by a ValueMapIntKey object. */ + explicit Value(const ValueMapIntKey &v); + /** Create a Value by a ValueMapIntKey object. It will use std::move internally. */ + explicit Value(ValueMapIntKey &&v); + + /** Create a Value by another Value object. */ + Value(const Value &other); + /** Create a Value by a Value object. It will use std::move internally. */ + Value(Value &&other) noexcept; + + /** Destructor. */ + ~Value(); + + /** Assignment operator, assign from Value to Value. */ + Value &operator=(const Value &other); + /** Assignment operator, assign from Value to Value. It will use std::move internally. */ + Value &operator=(Value &&other) noexcept; + + /** Assignment operator, assign from unsigned char to Value. */ + Value &operator=(unsigned char v); + /** Assignment operator, assign from integer to Value. */ + Value &operator=(int v); + /** Assignment operator, assign from integer to Value. */ + Value &operator=(unsigned int v); + /** Assignment operator, assign from float to Value. */ + Value &operator=(float v); + /** Assignment operator, assign from double to Value. */ + Value &operator=(double v); + /** Assignment operator, assign from bool to Value. */ + Value &operator=(bool v); + /** Assignment operator, assign from char* to Value. */ + Value &operator=(const char *v); + /** Assignment operator, assign from string to Value. */ + Value &operator=(const ccstd::string &v); + + /** Assignment operator, assign from ValueVector to Value. */ + Value &operator=(const ValueVector &v); + /** Assignment operator, assign from ValueVector to Value. */ + Value &operator=(ValueVector &&v); + + /** Assignment operator, assign from ValueMap to Value. */ + Value &operator=(const ValueMap &v); + /** Assignment operator, assign from ValueMap to Value. It will use std::move internally. */ + Value &operator=(ValueMap &&v); + + /** Assignment operator, assign from ValueMapIntKey to Value. */ + Value &operator=(const ValueMapIntKey &v); + /** Assignment operator, assign from ValueMapIntKey to Value. It will use std::move internally. */ + Value &operator=(ValueMapIntKey &&v); + + /** != operator overloading */ + bool operator!=(const Value &v); + /** != operator overloading */ + bool operator!=(const Value &v) const; + /** == operator overloading */ + bool operator==(const Value &v); + /** == operator overloading */ + bool operator==(const Value &v) const; + + /** Gets as a byte value. Will convert to unsigned char if possible, or will trigger assert error. */ + unsigned char asByte() const; + /** Gets as an integer value. Will convert to integer if possible, or will trigger assert error. */ + int asInt() const; + /** Gets as an unsigned value. Will convert to unsigned if possible, or will trigger assert error. */ + unsigned int asUnsignedInt() const; + /** Gets as a float value. Will convert to float if possible, or will trigger assert error. */ + float asFloat() const; + /** Gets as a double value. Will convert to double if possible, or will trigger assert error. */ + double asDouble() const; + /** Gets as a bool value. Will convert to bool if possible, or will trigger assert error. */ + bool asBool() const; + /** Gets as a string value. Will convert to string if possible, or will trigger assert error. */ + ccstd::string asString() const; + + /** Gets as a ValueVector reference. Will convert to ValueVector if possible, or will trigger assert error. */ + ValueVector &asValueVector(); + /** Gets as a const ValueVector reference. Will convert to ValueVector if possible, or will trigger assert error. */ + const ValueVector &asValueVector() const; + + /** Gets as a ValueMap reference. Will convert to ValueMap if possible, or will trigger assert error. */ + ValueMap &asValueMap(); + /** Gets as a const ValueMap reference. Will convert to ValueMap if possible, or will trigger assert error. */ + const ValueMap &asValueMap() const; + + /** Gets as a ValueMapIntKey reference. Will convert to ValueMapIntKey if possible, or will trigger assert error. */ + ValueMapIntKey &asIntKeyMap(); + /** Gets as a const ValueMapIntKey reference. Will convert to ValueMapIntKey if possible, or will trigger assert error. */ + const ValueMapIntKey &asIntKeyMap() const; + + /** + * Checks if the Value is null. + * @return True if the Value is null, false if not. + */ + inline bool isNull() const { return _type == Type::NONE; } + + /** Value type wrapped by Value. */ + enum class Type { + /// no value is wrapped, an empty Value + NONE = 0, + /// wrap byte + BYTE, + /// wrap integer + INTEGER, + /// wrap unsigned + UNSIGNED, + /// wrap float + FLOAT, + /// wrap double + DOUBLE, + /// wrap bool + BOOLEAN, + /// wrap string + STRING, + /// wrap vector + VECTOR, + /// wrap ValueMap + MAP, + /// wrap ValueMapIntKey + INT_KEY_MAP + }; + + /** Gets the value type. */ + inline Type getType() const { return _type; } + + /** Gets the description of the class. */ + ccstd::string getDescription() const; + +private: + void clear(); + void reset(Type type); + + union { + unsigned char byteVal; + int intVal; + unsigned int unsignedVal; + float floatVal; + double doubleVal; + bool boolVal; + + ccstd::string *strVal; + ValueVector *vectorVal; + ValueMap *mapVal; + ValueMapIntKey *intKeyMapVal; + } _field; + + Type _type; +}; + +} // namespace cc diff --git a/cocos/base/ZipUtils.cpp b/cocos/base/ZipUtils.cpp new file mode 100644 index 0000000..dba9561 --- /dev/null +++ b/cocos/base/ZipUtils.cpp @@ -0,0 +1,657 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +// IDEA: hack, must be included before ziputils +#include "base/ZipUtils.h" + +#include + +#ifdef MINIZIP_FROM_SYSTEM + #include +#else // from our embedded sources + #include "unzip/unzip.h" +#endif + +#include +#include +#include +#include "base/Data.h" +#include "base/Locked.h" +#include "base/Log.h" +#include "base/memory/Memory.h" +#include "platform/FileUtils.h" +#include "unzip/ioapi_mem.h" + +// minizip 1.2.0 is same with other platforms +#ifndef unzGoToFirstFile64 + #define unzGoToFirstFile64(A, B, C, D) unzGoToFirstFile2(A, B, C, D, NULL, 0, NULL, 0) // NOLINT(readability-identifier-naming) + #define unzGoToNextFile64(A, B, C, D) unzGoToNextFile2(A, B, C, D, NULL, 0, NULL, 0) // NOLINT(readability-identifier-naming)#endif +#endif + +namespace cc { + +unsigned int ZipUtils::encryptedPvrKeyParts[4] = {0, 0, 0, 0}; +unsigned int ZipUtils::encryptionKey[1024]; +bool ZipUtils::encryptionKeyIsValid = false; + +// --------------------- ZipUtils --------------------- + +inline void ZipUtils::decodeEncodedPvr(unsigned int *data, uint32_t len) { + const int enclen = 1024; + const int securelen = 512; + const int distance = 64; + + // check if key was set + // make sure to call caw_setkey_part() for all 4 key parts + CC_ASSERT(ZipUtils::encryptedPvrKeyParts[0] != 0); // CCZ file is encrypted but key part 0 is not set. Call ZipUtils::setPvrEncryptionKeyPart(...)? + CC_ASSERT(ZipUtils::encryptedPvrKeyParts[1] != 0); // CCZ file is encrypted but key part 1 is not set. Call ZipUtils::setPvrEncryptionKeyPart(...)? + CC_ASSERT(ZipUtils::encryptedPvrKeyParts[2] != 0); // CCZ file is encrypted but key part 2 is not set. Call ZipUtils::setPvrEncryptionKeyPart(...)? + CC_ASSERT(ZipUtils::encryptedPvrKeyParts[3] != 0); // CCZ file is encrypted but key part 3 is not set. Call ZipUtils::setPvrEncryptionKeyPart(...)? + + // create long key + if (!ZipUtils::encryptionKeyIsValid) { + unsigned int y{0}; + unsigned int p{0}; + unsigned int e{0}; + unsigned int rounds = 6; + unsigned int sum = 0; + unsigned int z = ZipUtils::encryptionKey[enclen - 1]; + + do { +#define DELTA 0x9e3779b9 +#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (ZipUtils::encryptedPvrKeyParts[(p & 3) ^ e] ^ z))) + + sum += DELTA; + e = (sum >> 2) & 3; + + for (p = 0; p < enclen - 1; p++) { + y = ZipUtils::encryptionKey[p + 1]; + z = ZipUtils::encryptionKey[p] += MX; + } + + y = ZipUtils::encryptionKey[0]; + z = ZipUtils::encryptionKey[enclen - 1] += MX; + + } while (--rounds); + + ZipUtils::encryptionKeyIsValid = true; + } + + int b = 0; + int i = 0; + + // encrypt first part completely + for (; i < len && i < securelen; i++) { + data[i] ^= ZipUtils::encryptionKey[b++]; + + if (b >= enclen) { + b = 0; + } + } + + // encrypt second section partially + for (; i < len; i += distance) { + data[i] ^= ZipUtils::encryptionKey[b++]; + + if (b >= enclen) { + b = 0; + } + } +} + +inline unsigned int ZipUtils::checksumPvr(const unsigned int *data, uint32_t len) { + unsigned int cs = 0; + const int cslen = 128; + + len = (len < cslen) ? len : cslen; + + for (int i = 0; i < len; i++) { + cs = cs ^ data[i]; + } + + return cs; +} + +// memory in iPhone is precious +// Should buffer factor be 1.5 instead of 2 ? +#define BUFFER_INC_FACTOR (2) + +int ZipUtils::inflateMemoryWithHint(unsigned char *in, uint32_t inLength, unsigned char **out, uint32_t *outLength, uint32_t outLengthHint) { + /* ret value */ + int err = Z_OK; + uint32_t bufferSize = outLengthHint; + *out = static_cast(malloc(bufferSize)); + + z_stream descompressionStream; /* decompression stream */ + descompressionStream.zalloc = static_cast(nullptr); + descompressionStream.zfree = static_cast(nullptr); + descompressionStream.opaque = static_cast(nullptr); + + descompressionStream.next_in = in; + descompressionStream.avail_in = inLength; + descompressionStream.next_out = *out; + descompressionStream.avail_out = bufferSize; + + /* window size to hold 256k */ + if ((err = inflateInit2(&descompressionStream, 15 + 32)) != Z_OK) { + return err; + } + + for (;;) { + err = inflate(&descompressionStream, Z_NO_FLUSH); + + if (err == Z_STREAM_END) { + break; + } + + switch (err) { + case Z_NEED_DICT: + err = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&descompressionStream); + return err; + } + + // not enough memory ? + if (err != Z_STREAM_END) { + *out = static_cast(realloc(*out, bufferSize * BUFFER_INC_FACTOR)); + + /* not enough memory, ouch */ + if (!*out) { + CC_LOG_DEBUG("ZipUtils: realloc failed"); + inflateEnd(&descompressionStream); + return Z_MEM_ERROR; + } + + descompressionStream.next_out = *out + bufferSize; + descompressionStream.avail_out = static_cast(bufferSize); + bufferSize *= BUFFER_INC_FACTOR; + } + } + + *outLength = bufferSize - descompressionStream.avail_out; + err = inflateEnd(&descompressionStream); + return err; +} + +uint32_t ZipUtils::inflateMemoryWithHint(unsigned char *in, uint32_t inLength, unsigned char **out, uint32_t outLengthHint) { + uint32_t outLength = 0; + int err = inflateMemoryWithHint(in, inLength, out, &outLength, outLengthHint); + + if (err != Z_OK || *out == nullptr) { + if (err == Z_MEM_ERROR) { + CC_LOG_DEBUG("ZipUtils: Out of memory while decompressing map data!"); + } else if (err == Z_VERSION_ERROR) { + CC_LOG_DEBUG("ZipUtils: Incompatible zlib version!"); + } else if (err == Z_DATA_ERROR) { + CC_LOG_DEBUG("ZipUtils: Incorrect zlib compressed data!"); + } else { + CC_LOG_DEBUG("ZipUtils: Unknown error while decompressing map data!"); + } + + if (*out) { + free(*out); + *out = nullptr; + } + outLength = 0; + } + + return outLength; +} + +uint32_t ZipUtils::inflateMemory(unsigned char *in, uint32_t inLength, unsigned char **out) { + // 256k for hint + return inflateMemoryWithHint(in, inLength, out, 256 * 1024); +} + +int ZipUtils::inflateGZipFile(const char *path, unsigned char **out) { + int len; + int offset = 0; + + CC_ASSERT(out); + CC_ASSERT(&*out); + + gzFile inFile = gzopen(FileUtils::getInstance()->getSuitableFOpen(path).c_str(), "rb"); + if (inFile == nullptr) { + CC_LOG_DEBUG("ZipUtils: error open gzip file: %s", path); + return -1; + } + + /* 512k initial decompress buffer */ + unsigned int bufferSize = 512 * 1024; + unsigned int totalBufferSize = bufferSize; + + *out = static_cast(malloc(bufferSize)); + if (!out) { + CC_LOG_DEBUG("ZipUtils: out of memory"); + return -1; + } + + for (;;) { + len = gzread(inFile, *out + offset, bufferSize); + if (len < 0) { + CC_LOG_DEBUG("ZipUtils: error in gzread"); + free(*out); + *out = nullptr; + return -1; + } + if (len == 0) { + break; + } + + offset += len; + + // finish reading the file + if (static_cast(len) < bufferSize) { + break; + } + + bufferSize *= BUFFER_INC_FACTOR; + totalBufferSize += bufferSize; + auto *tmp = static_cast(realloc(*out, totalBufferSize)); + + if (!tmp) { + CC_LOG_DEBUG("ZipUtils: out of memory"); + free(*out); + *out = nullptr; + return -1; + } + + *out = tmp; + } + + if (gzclose(inFile) != Z_OK) { + CC_LOG_DEBUG("ZipUtils: gzclose failed"); + } + + return offset; +} + +bool ZipUtils::isCCZFile(const char *path) { + // load file into memory + Data compressedData = FileUtils::getInstance()->getDataFromFile(path); + + if (compressedData.isNull()) { + CC_LOG_DEBUG("ZipUtils: loading file failed"); + return false; + } + + return isCCZBuffer(compressedData.getBytes(), compressedData.getSize()); +} + +bool ZipUtils::isCCZBuffer(const unsigned char *buffer, uint32_t len) { + if (len < sizeof(struct CCZHeader)) { + return false; + } + + const auto *header = reinterpret_cast(buffer); + return header->sig[0] == 'C' && header->sig[1] == 'C' && header->sig[2] == 'Z' && (header->sig[3] == '!' || header->sig[3] == 'p'); +} + +bool ZipUtils::isGZipFile(const char *path) { + // load file into memory + Data compressedData = FileUtils::getInstance()->getDataFromFile(path); + + if (compressedData.isNull()) { + CC_LOG_DEBUG("ZipUtils: loading file failed"); + return false; + } + + return isGZipBuffer(compressedData.getBytes(), compressedData.getSize()); +} + +bool ZipUtils::isGZipBuffer(const unsigned char *buffer, uint32_t len) { + if (len < 2) { + return false; + } + + return buffer[0] == 0x1F && buffer[1] == 0x8B; +} + +int ZipUtils::inflateCCZBuffer(const unsigned char *buffer, uint32_t bufferLen, unsigned char **out) { + const auto *header = reinterpret_cast(buffer); + + // verify header + if (header->sig[0] == 'C' && header->sig[1] == 'C' && header->sig[2] == 'Z' && header->sig[3] == '!') { + // verify header version + unsigned int version = CC_SWAP_INT16_BIG_TO_HOST(header->version); + if (version > 2) { + CC_LOG_DEBUG("Unsupported CCZ header format"); + return -1; + } + + // verify compression format + if (CC_SWAP_INT16_BIG_TO_HOST(header->compression_type) != CCZ_COMPRESSION_ZLIB) { + CC_LOG_DEBUG("CCZ Unsupported compression method"); + return -1; + } + } else if (header->sig[0] == 'C' && header->sig[1] == 'C' && header->sig[2] == 'Z' && header->sig[3] == 'p') { + // encrypted ccz file + header = reinterpret_cast(buffer); + + // verify header version + unsigned int version = CC_SWAP_INT16_BIG_TO_HOST(header->version); + if (version > 0) { + CC_LOG_DEBUG("Unsupported CCZ header format"); + return -1; + } + + // verify compression format + if (CC_SWAP_INT16_BIG_TO_HOST(header->compression_type) != CCZ_COMPRESSION_ZLIB) { + CC_LOG_DEBUG("CCZ Unsupported compression method"); + return -1; + } + +#if CC_DEBUG > 0 + // decrypt + auto *ints = reinterpret_cast(const_cast(buffer) + 12); + uint32_t enclen = (bufferLen - 12) / 4; + + decodeEncodedPvr(ints, enclen); + // verify checksum in debug mode + unsigned int calculated = checksumPvr(ints, enclen); + unsigned int required = CC_SWAP_INT32_BIG_TO_HOST(header->reserved); + + if (calculated != required) { + CC_LOG_DEBUG("Can't decrypt image file. Is the decryption key valid?"); + return -1; + } +#endif + } else { + CC_LOG_DEBUG("Invalid CCZ file"); + return -1; + } + + unsigned int len = CC_SWAP_INT32_BIG_TO_HOST(header->len); + + *out = static_cast(malloc(len)); + if (!*out) { + CC_LOG_DEBUG("CCZ: Failed to allocate memory for texture"); + return -1; + } + + uLongf destlen = len; + const auto *source = reinterpret_cast(buffer + sizeof(*header)); + int ret = uncompress(*out, &destlen, source, static_cast(bufferLen - sizeof(*header))); + + if (ret != Z_OK) { + CC_LOG_DEBUG("CCZ: Failed to uncompress data"); + free(*out); + *out = nullptr; + return -1; + } + + return static_cast(len); +} + +int ZipUtils::inflateCCZFile(const char *path, unsigned char **out) { + CC_ASSERT(out); + + // load file into memory + Data compressedData = FileUtils::getInstance()->getDataFromFile(path); + + if (compressedData.isNull()) { + CC_LOG_DEBUG("Error loading CCZ compressed file"); + return -1; + } + + return inflateCCZBuffer(compressedData.getBytes(), compressedData.getSize(), out); +} + +void ZipUtils::setPvrEncryptionKeyPart(int index, unsigned int value) { + CC_ASSERT_GE(index, 0); + CC_ASSERT_LE(index, 3); + + if (ZipUtils::encryptedPvrKeyParts[index] != value) { + ZipUtils::encryptedPvrKeyParts[index] = value; + ZipUtils::encryptionKeyIsValid = false; + } +} + +void ZipUtils::setPvrEncryptionKey(unsigned int keyPart1, unsigned int keyPart2, unsigned int keyPart3, unsigned int keyPart4) { + setPvrEncryptionKeyPart(0, keyPart1); + setPvrEncryptionKeyPart(1, keyPart2); + setPvrEncryptionKeyPart(2, keyPart3); + setPvrEncryptionKeyPart(3, keyPart4); +} + +// --------------------- ZipFile --------------------- +// from unzip.cpp +#define UNZ_MAXFILENAMEINZIP 256 + +static const ccstd::string EMPTY_FILE_NAME; + +struct ZipEntryInfo { + unz_file_pos pos; + uLong uncompressed_size; +}; + +class ZipFilePrivate { +public: + Locked zipFile; + std::unique_ptr memfs; + + // ccstd::unordered_map is faster if available on the platform + using FileListContainer = ccstd::unordered_map; + FileListContainer fileList; +}; + +ZipFile *ZipFile::createWithBuffer(const void *buffer, uint32_t size) { + auto *zip = ccnew ZipFile(); + if (zip && zip->initWithBuffer(buffer, size)) { + return zip; + } + delete zip; + return nullptr; +} + +ZipFile::ZipFile() +: _data(ccnew ZipFilePrivate) { + auto zipFile = _data->zipFile.lock(); + *zipFile = nullptr; +} + +ZipFile::ZipFile(const ccstd::string &zipFile, const ccstd::string &filter) +: _data(ccnew ZipFilePrivate) { + auto zipFileL = _data->zipFile.lock(); + *zipFileL = unzOpen(FileUtils::getInstance()->getSuitableFOpen(zipFile).c_str()); + setFilter(filter); +} + +ZipFile::~ZipFile() { + if (_data) { + auto zipFile = _data->zipFile.lock(); + if (*zipFile) { + unzClose(*zipFile); + } + } + + CC_SAFE_DELETE(_data); +} + +bool ZipFile::setFilter(const ccstd::string &filter) { + bool ret = false; + do { + CC_BREAK_IF(!_data); + auto zipFile = _data->zipFile.lock(); + CC_BREAK_IF(!(*zipFile)); + + // clear existing file list + _data->fileList.clear(); + + // UNZ_MAXFILENAMEINZIP + 1 - it is done so in unzLocateFile + char szCurrentFileName[UNZ_MAXFILENAMEINZIP + 1]; + unz_file_info64 fileInfo; + + // go through all files and store position information about the required files + int err = unzGoToFirstFile64(*zipFile, &fileInfo, + szCurrentFileName, sizeof(szCurrentFileName) - 1); + while (err == UNZ_OK) { + unz_file_pos posInfo; + int posErr = unzGetFilePos(*zipFile, &posInfo); + if (posErr == UNZ_OK) { + ccstd::string currentFileName = szCurrentFileName; + // cache info about filtered files only (like 'assets/') + if (filter.empty() || currentFileName.substr(0, filter.length()) == filter) { + ZipEntryInfo entry; + entry.pos = posInfo; + entry.uncompressed_size = static_cast(fileInfo.uncompressed_size); + _data->fileList[currentFileName] = entry; + } + } + // next file - also get the information about it + err = unzGoToNextFile64(*zipFile, &fileInfo, + szCurrentFileName, sizeof(szCurrentFileName) - 1); + } + ret = true; + + } while (false); + + return ret; +} + +bool ZipFile::fileExists(const ccstd::string &fileName) const { + bool ret = false; + do { + CC_BREAK_IF(!_data); + ret = _data->fileList.find(fileName) != _data->fileList.end(); + } while (false); + + return ret; +} + +unsigned char *ZipFile::getFileData(const ccstd::string &fileName, uint32_t *size) { + unsigned char *buffer = nullptr; + if (size) { + *size = 0; + } + + auto zipFile = _data->zipFile.lock(); + + do { + CC_BREAK_IF(!(*zipFile)); + CC_BREAK_IF(fileName.empty()); + + auto it = _data->fileList.find(fileName); + CC_BREAK_IF(it == _data->fileList.end()); + + ZipEntryInfo fileInfo = it->second; + + int nRet = unzGoToFilePos(*zipFile, &fileInfo.pos); + CC_BREAK_IF(UNZ_OK != nRet); + + nRet = unzOpenCurrentFile(*zipFile); + CC_BREAK_IF(UNZ_OK != nRet); + + buffer = static_cast(malloc(fileInfo.uncompressed_size)); + int CC_UNUSED nSize = unzReadCurrentFile(*zipFile, buffer, static_cast(fileInfo.uncompressed_size)); + CC_ASSERT(nSize == 0 || nSize == (int)fileInfo.uncompressed_size); + + if (size) { + *size = static_cast(fileInfo.uncompressed_size); + } + unzCloseCurrentFile(*zipFile); + } while (false); + + return buffer; +} + +bool ZipFile::getFileData(const ccstd::string &fileName, ResizableBuffer *buffer) { + bool res = false; + do { + auto zipFile = _data->zipFile.lock(); + CC_BREAK_IF(!(*zipFile)); + CC_BREAK_IF(fileName.empty()); + + auto it = _data->fileList.find(fileName); + CC_BREAK_IF(it == _data->fileList.end()); + + ZipEntryInfo fileInfo = it->second; + + int nRet = unzGoToFilePos(*zipFile, &fileInfo.pos); + CC_BREAK_IF(UNZ_OK != nRet); + + nRet = unzOpenCurrentFile(*zipFile); + CC_BREAK_IF(UNZ_OK != nRet); + + buffer->resize(fileInfo.uncompressed_size); + int CC_UNUSED nSize = unzReadCurrentFile(*zipFile, buffer->buffer(), static_cast(fileInfo.uncompressed_size)); + CC_ASSERT(nSize == 0 || nSize == (int)fileInfo.uncompressed_size); + unzCloseCurrentFile(*zipFile); + res = true; + } while (false); + + return res; +} + +ccstd::string ZipFile::getFirstFilename() { + auto zipFile = _data->zipFile.lock(); + if (unzGoToFirstFile(*zipFile) != UNZ_OK) return EMPTY_FILE_NAME; + ccstd::string path; + unz_file_info info; + getCurrentFileInfo(&path, &info); + return path; +} + +ccstd::string ZipFile::getNextFilename() { + auto zipFile = _data->zipFile.lock(); + if (unzGoToNextFile(*zipFile) != UNZ_OK) return EMPTY_FILE_NAME; + ccstd::string path; + unz_file_info info; + getCurrentFileInfo(&path, &info); + return path; +} + +int ZipFile::getCurrentFileInfo(ccstd::string *filename, unz_file_info *info) { + char path[FILENAME_MAX + 1]; + auto zipFile = _data->zipFile.lock(); + int ret = unzGetCurrentFileInfo(*zipFile, info, path, sizeof(path), nullptr, 0, nullptr, 0); + if (ret != UNZ_OK) { + *filename = EMPTY_FILE_NAME; + } else { + filename->assign(path); + } + return ret; +} + +bool ZipFile::initWithBuffer(const void *buffer, uint32_t size) { + if (!buffer || size == 0) return false; + auto zipFile = _data->zipFile.lock(); + zlib_filefunc_def memoryFile = {nullptr}; + + std::unique_ptr memfs(ccnew ourmemory_t{static_cast(const_cast(buffer)), static_cast(size), 0, 0, 0}); + if (!memfs) return false; + fill_memory_filefunc(&memoryFile, memfs.get()); + + *zipFile = unzOpen2(nullptr, &memoryFile); + if (!(*zipFile)) return false; + + setFilter(EMPTY_FILE_NAME); + return true; +} + +} // namespace cc diff --git a/cocos/base/ZipUtils.h b/cocos/base/ZipUtils.h new file mode 100644 index 0000000..52cc1df --- /dev/null +++ b/cocos/base/ZipUtils.h @@ -0,0 +1,276 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/std/container/string.h" +#include "platform/FileUtils.h" + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + #include "platform/android/FileUtils-android.h" +#endif + +namespace cc { +#ifndef _unz64_H +using unz_file_info = struct unz_file_info_s; +#endif + +struct CCZHeader { + unsigned char sig[4]; /** Signature. Should be 'CCZ!' 4 bytes. */ + uint16_t compression_type; /** Should be 0. */ + uint16_t version; /** Should be 2 (although version type==1 is also supported). */ + unsigned int reserved; /** Reserved for users. */ + unsigned int len; /** Size of the uncompressed file. */ +}; + +enum { + CCZ_COMPRESSION_ZLIB, /** zlib format. */ + CCZ_COMPRESSION_BZIP2, /** bzip2 format (not supported yet). */ + CCZ_COMPRESSION_GZIP, /** gzip format (not supported yet). */ + CCZ_COMPRESSION_NONE, /** plain (not supported yet). */ +}; + +class CC_DLL ZipUtils { +public: + /** + * Inflates either zlib or gzip deflated memory. The inflated memory is expected to be freed by the caller. + * + * It will allocate 256k for the destination buffer. If it is not enough it will multiply the previous buffer size per 2, until there is enough memory. + * + * @return The length of the deflated buffer. + * @since v0.8.1 + */ + static uint32_t inflateMemory(unsigned char *in, uint32_t inLength, unsigned char **out); + + /** + * Inflates either zlib or gzip deflated memory. The inflated memory is expected to be freed by the caller. + * + * @param outLengthHint It is assumed to be the needed room to allocate the inflated buffer. + * + * @return The length of the deflated buffer. + * @since v1.0.0 + */ + static uint32_t inflateMemoryWithHint(unsigned char *in, uint32_t inLength, unsigned char **out, uint32_t outLengthHint); + + /** + * Inflates a GZip file into memory. + * + * @return The length of the deflated buffer. + * @since v0.99.5 + */ + static int inflateGZipFile(const char *path, unsigned char **out); + + /** + * Test a file is a GZip format file or not. + * + * @return True is a GZip format file. false is not. + * @since v3.0 + */ + static bool isGZipFile(const char *path); + + /** + * Test the buffer is GZip format or not. + * + * @return True is GZip format. false is not. + * @since v3.0 + */ + static bool isGZipBuffer(const unsigned char *buffer, uint32_t len); + + /** + * Inflates a CCZ file into memory. + * + * @return The length of the deflated buffer. + * @since v0.99.5 + */ + static int inflateCCZFile(const char *path, unsigned char **out); + + /** + * Inflates a buffer with CCZ format into memory. + * + * @return The length of the deflated buffer. + * @since v3.0 + */ + static int inflateCCZBuffer(const unsigned char *buffer, uint32_t len, unsigned char **out); + + /** + * Test a file is a CCZ format file or not. + * + * @return True is a CCZ format file. false is not. + * @since v3.0 + */ + static bool isCCZFile(const char *path); + + /** + * Test the buffer is CCZ format or not. + * + * @return True is CCZ format. false is not. + * @since v3.0 + */ + static bool isCCZBuffer(const unsigned char *buffer, uint32_t len); + + /** + * Sets the pvr.ccz encryption key parts separately for added security. + * + * Example: If the key used to encrypt the pvr.ccz file is + * 0xaaaaaaaabbbbbbbbccccccccdddddddd you will call this function 4 + * different times, preferably from 4 different source files, as follows + * + * ZipUtils::setPvrEncryptionKeyPart(0, 0xaaaaaaaa); + * ZipUtils::setPvrEncryptionKeyPart(1, 0xbbbbbbbb); + * ZipUtils::setPvrEncryptionKeyPart(2, 0xcccccccc); + * ZipUtils::setPvrEncryptionKeyPart(3, 0xdddddddd); + * + * Splitting the key into 4 parts and calling the function from 4 different source + * files increases the difficulty to reverse engineer the encryption key. + * Be aware that encryption is *never* 100% secure and the key code + * can be cracked by knowledgable persons. + * + * IMPORTANT: Be sure to call setPvrEncryptionKey or + * setPvrEncryptionKeyPart with all of the key parts *before* loading + * the sprite sheet or decryption will fail and the sprite sheet + * will fail to load. + * + * @param index Part of the key [0..3]. + * @param value Value of the key part. + */ + static void setPvrEncryptionKeyPart(int index, unsigned int value); + + /** + * Sets the pvr.ccz encryption key. + * + * Example: If the key used to encrypt the pvr.ccz file is + * 0xaaaaaaaabbbbbbbbccccccccdddddddd you will call this function with + * the key split into 4 parts as follows + * + * ZipUtils::setPvrEncryptionKey(0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd); + * + * Note that using this function makes it easier to reverse engineer and discover + * the complete key because the key parts are present in one function call. + * + * IMPORTANT: Be sure to call setPvrEncryptionKey or setPvrEncryptionKeyPart + * with all of the key parts *before* loading the spritesheet or decryption + * will fail and the sprite sheet will fail to load. + * + * @param keyPart1 The key value part 1. + * @param keyPart2 The key value part 2. + * @param keyPart3 The key value part 3. + * @param keyPart4 The key value part 4. + */ + static void setPvrEncryptionKey(unsigned int keyPart1, unsigned int keyPart2, unsigned int keyPart3, unsigned int keyPart4); + +private: + static int inflateMemoryWithHint(unsigned char *in, uint32_t inLength, unsigned char **out, uint32_t *outLength, uint32_t outLengthHint); + static inline void decodeEncodedPvr(unsigned int *data, uint32_t len); + static inline unsigned int checksumPvr(const unsigned int *data, uint32_t len); + + static unsigned int encryptedPvrKeyParts[4]; + static unsigned int encryptionKey[1024]; + static bool encryptionKeyIsValid; +}; + +// forward declaration +class ZipFilePrivate; +struct unz_file_info_s; + +/** + * Zip file - reader helper class. + * + * It will cache the file list of a particular zip file with positions inside an archive, + * so it would be much faster to read some particular files or to check their existence. + * + * @since v2.0.5 + */ +class CC_DLL ZipFile { +public: + /** + * Constructor, open zip file and store file list. + * + * @param zipFile Zip file name + * @param filter The first part of file names, which should be accessible. + * For example, "@assets/". Other files will be missed. + * + * @since v2.0.5 + */ + explicit ZipFile(const ccstd::string &zipFile, const ccstd::string &filter = ccstd::string()); + virtual ~ZipFile(); + + /** + * Regenerate accessible file list based on a new filter string. + * + * @param filter New filter string (first part of files names) + * @return true whenever zip file is open successfully and it is possible to locate + * at least the first file, false otherwise + * + * @since v2.0.5 + */ + bool setFilter(const ccstd::string &filter); + + /** + * Check does a file exists or not in zip file + * + * @param fileName File to be checked on existence + * @return true whenever file exists, false otherwise + * + * @since v2.0.5 + */ + bool fileExists(const ccstd::string &fileName) const; + + /** + * Get resource file data from a zip file. + * @param fileName File name + * @param[out] pSize If the file read operation succeeds, it will be the data size, otherwise 0. + * @return Upon success, a pointer to the data is returned, otherwise nullptr. + * @warning Recall: you are responsible for calling free() on any Non-nullptr pointer returned. + * + * @since v2.0.5 + */ + unsigned char *getFileData(const ccstd::string &fileName, uint32_t *size); + + /** + * Get resource file data from a zip file. + * @param fileName File name + * @param[out] buffer If the file read operation succeeds, if will contain the file data. + * @return True if successful. + */ + bool getFileData(const ccstd::string &fileName, ResizableBuffer *buffer); + + ccstd::string getFirstFilename(); + ccstd::string getNextFilename(); + + static ZipFile *createWithBuffer(const void *buffer, uint32_t size); + +private: + /* Only used internal for createWithBuffer() */ + ZipFile(); + + bool initWithBuffer(const void *buffer, uint32_t size); + int getCurrentFileInfo(ccstd::string *filename, unz_file_info *info); + + /** Internal data like zip file pointer / file list array and so on */ + ZipFilePrivate *_data{nullptr}; +}; +} // end of namespace cc diff --git a/cocos/base/astc.cpp b/cocos/base/astc.cpp new file mode 100644 index 0000000..3c79e03 --- /dev/null +++ b/cocos/base/astc.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/astc.h" +#include "platform/Image.h" + +static const unsigned int MAGIC = 0x5CA1AB13; +static const astc_byte ASTC_HEADER_SIZE_X_BEGIN = 7; +static const astc_byte ASTC_HEADER_SIZE_Y_BEGIN = 10; + +bool astcIsValid(const astc_byte *pHeader) { + uint32_t magicval = static_cast(pHeader[0]) + + static_cast(pHeader[1]) * 256 + + static_cast(pHeader[2]) * 65536 + + static_cast(pHeader[3]) * 16777216; + + if (magicval != MAGIC) { + return false; + } + + int xdim = pHeader[ASTC_HEADER_MAGIC]; + int ydim = pHeader[ASTC_HEADER_MAGIC + 1]; + int zdim = pHeader[ASTC_HEADER_MAGIC + 2]; + return !((xdim < 3 || xdim > 6 || ydim < 3 || ydim > 6 || zdim < 3 || zdim > 6) && + (xdim < 4 || xdim == 7 || xdim == 9 || xdim == 11 || xdim > 12 || + ydim < 4 || ydim == 7 || ydim == 9 || ydim == 11 || ydim > 12 || zdim != 1)); +} + +int astcGetWidth(const astc_byte *pHeader) { + int xsize = pHeader[ASTC_HEADER_SIZE_X_BEGIN] + (pHeader[ASTC_HEADER_SIZE_X_BEGIN + 1] * 256) + (pHeader[ASTC_HEADER_SIZE_X_BEGIN + 2] * 65536); + return xsize; +} + +int astcGetHeight(const astc_byte *pHeader) { + int ysize = pHeader[ASTC_HEADER_SIZE_Y_BEGIN] + (pHeader[ASTC_HEADER_SIZE_Y_BEGIN + 1] * 256) + (pHeader[ASTC_HEADER_SIZE_Y_BEGIN + 2] * 65536); + return ysize; +} diff --git a/cocos/base/astc.h b/cocos/base/astc.h new file mode 100644 index 0000000..93b75de --- /dev/null +++ b/cocos/base/astc.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include + +#pragma once + +using astc_byte = unsigned char; +using astc_uint32 = unsigned int; + +// Size of a ASTC header + +#define ASTC_HEADER_SIZE 16 + +#define ASTC_HEADER_MAGIC 4 + +// Check if a ASTC header is correctly formatted + +bool astcIsValid(const astc_byte *pHeader); + +// Read the image width from a ASTC header + +int astcGetWidth(const astc_byte *pHeader); + +// Read the image height from a ASTC header + +int astcGetHeight(const astc_byte *pHeader); diff --git a/cocos/base/base64.cpp b/cocos/base/base64.cpp new file mode 100644 index 0000000..e559a8d --- /dev/null +++ b/cocos/base/base64.cpp @@ -0,0 +1,179 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/base64.h" + +#include +#include +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +namespace cc { + +ccstd::string alphabet{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"}; + +int doBase64Decode(const unsigned char *input, unsigned int inputLen, unsigned char *output, unsigned int *outputLen) { + static ccstd::vector inalphabet(256); + static ccstd::vector decoder(256); + int i; + int bits; + int c = 0; + int charCount; + int errors = 0; + unsigned int inputIdx = 0; + unsigned int outputIdx = 0; + int length = static_cast(alphabet.length()); + for (i = length - 1; i >= 0; i--) { + inalphabet[alphabet[i]] = 1; + decoder[alphabet[i]] = i; + } + + charCount = 0; + bits = 0; + for (inputIdx = 0; inputIdx < inputLen; inputIdx++) { + c = input[inputIdx]; + if (c == '=') { + break; + } + + if (c > 255 || !inalphabet[c]) { + continue; + } + + bits += decoder[c]; + ++charCount; + if (charCount == 4) { + output[outputIdx++] = (bits >> 16); + output[outputIdx++] = ((bits >> 8) & 0xff); + output[outputIdx++] = (bits & 0xff); + bits = 0; + charCount = 0; + } else { + bits <<= 6; + } + } + + if (c == '=') { + switch (charCount) { + case 1: +#if (CC_PLATFORM != CC_PLATFORM_BADA) + fprintf(stderr, "base64Decode: encoding incomplete: at least 2 bits missing"); +#endif + ++errors; + break; + case 2: + output[outputIdx++] = (bits >> 10); + break; + case 3: + output[outputIdx++] = (bits >> 16); + output[outputIdx++] = ((bits >> 8) & 0xff); + break; + } + } else if (inputIdx < inputLen) { + if (charCount) { +#if (CC_PLATFORM != CC_PLATFORM_BADA) + fprintf(stderr, "base64 encoding incomplete: at least %d bits truncated", + ((4 - charCount) * 6)); +#endif + ++errors; + } + } + + *outputLen = outputIdx; + return errors; +} + +void doBase64Encode(const unsigned char *input, unsigned int inputLen, char *output) { + unsigned int charCount; + unsigned int bits; + unsigned int inputIdx = 0; + unsigned int outputIdx = 0; + + charCount = 0; + bits = 0; + for (inputIdx = 0; inputIdx < inputLen; inputIdx++) { + bits |= input[inputIdx]; + + charCount++; + if (charCount == 3) { + output[outputIdx++] = alphabet[(bits >> 18) & 0x3f]; + output[outputIdx++] = alphabet[(bits >> 12) & 0x3f]; + output[outputIdx++] = alphabet[(bits >> 6) & 0x3f]; + output[outputIdx++] = alphabet[bits & 0x3f]; + bits = 0; + charCount = 0; + } else { + bits <<= 8; + } + } + + if (charCount) { + if (charCount == 1) { + bits <<= 8; + } + + output[outputIdx++] = alphabet[(bits >> 18) & 0x3f]; + output[outputIdx++] = alphabet[(bits >> 12) & 0x3f]; + if (charCount > 1) { + output[outputIdx++] = alphabet[(bits >> 6) & 0x3f]; + } else { + output[outputIdx++] = '='; + } + output[outputIdx++] = '='; + } + + output[outputIdx++] = 0; +} + +int base64Decode(const unsigned char *in, unsigned int inLength, unsigned char **out) { + unsigned int outLength = 0; + + //should be enough to store 6-bit buffers in 8-bit buffers + *out = static_cast(malloc(inLength / 4 * 3 + 1)); + if (*out) { + int ret = doBase64Decode(in, inLength, *out, &outLength); + + if (ret > 0) { + free(*out); + *out = nullptr; + outLength = 0; + } + } + return static_cast(outLength); +} + +int base64Encode(const unsigned char *in, unsigned int inLength, char **out) { + unsigned int outLength = (inLength + 2) / 3 * 4; + + //should be enough to store 8-bit buffers in 6-bit buffers + *out = static_cast(malloc(outLength + 1)); + if (*out) { + doBase64Encode(in, inLength, *out); + } + return static_cast(outLength); +} + +} // namespace cc diff --git a/cocos/base/base64.h b/cocos/base/base64.h new file mode 100644 index 0000000..6bef1fd --- /dev/null +++ b/cocos/base/base64.h @@ -0,0 +1,58 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" + +#ifdef __cplusplus +extern "C" { +#endif + +namespace cc { + +/** + * Decodes a 64base encoded memory. The decoded memory is + * expected to be freed by the caller by calling `free()` + * + * @returns the length of the out buffer + */ +int CC_DLL base64Decode(const unsigned char *in, unsigned int inLength, unsigned char **out); + +/** + * Encodes bytes into a 64base encoded memory with terminating '\0' character. + * The encoded memory is expected to be freed by the caller by calling `free()` + * + * @returns the length of the out buffer + * + */ +int CC_DLL base64Encode(const unsigned char *in, unsigned int inLength, char **out); + +} // namespace cc + +#ifdef __cplusplus +} +#endif diff --git a/cocos/base/csscolorparser.cpp b/cocos/base/csscolorparser.cpp new file mode 100644 index 0000000..f1aa95d --- /dev/null +++ b/cocos/base/csscolorparser.cpp @@ -0,0 +1,387 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +// (c) Dean McNamee , 2012. +// C++ port by Mapbox, Konstantin Käfer , 2014-2017. +// +// https://github.com/deanm/css-color-parser-js +// https://github.com/kkaefer/css-color-parser-cpp +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "csscolorparser.h" + +#include +#include +#include +#include +#include "base/std/container/vector.h" + +namespace CSSColorParser { + +// http://www.w3.org/TR/css3-color/ +struct NamedColor { + const char *name; + Color color; +}; + +const ccstd::vector NAMED_COLORS = { + {"transparent", {0, 0, 0, 0}}, + {"aliceblue", {240, 248, 255, 1}}, + {"antiquewhite", {250, 235, 215, 1}}, + {"aqua", {0, 255, 255, 1}}, + {"aquamarine", {127, 255, 212, 1}}, + {"azure", {240, 255, 255, 1}}, + {"beige", {245, 245, 220, 1}}, + {"bisque", {255, 228, 196, 1}}, + {"black", {0, 0, 0, 1}}, + {"blanchedalmond", {255, 235, 205, 1}}, + {"blue", {0, 0, 255, 1}}, + {"blueviolet", {138, 43, 226, 1}}, + {"brown", {165, 42, 42, 1}}, + {"burlywood", {222, 184, 135, 1}}, + {"cadetblue", {95, 158, 160, 1}}, + {"chartreuse", {127, 255, 0, 1}}, + {"chocolate", {210, 105, 30, 1}}, + {"coral", {255, 127, 80, 1}}, + {"cornflowerblue", {100, 149, 237, 1}}, + {"cornsilk", {255, 248, 220, 1}}, + {"crimson", {220, 20, 60, 1}}, + {"cyan", {0, 255, 255, 1}}, + {"darkblue", {0, 0, 139, 1}}, + {"darkcyan", {0, 139, 139, 1}}, + {"darkgoldenrod", {184, 134, 11, 1}}, + {"darkgray", {169, 169, 169, 1}}, + {"darkgreen", {0, 100, 0, 1}}, + {"darkgrey", {169, 169, 169, 1}}, + {"darkkhaki", {189, 183, 107, 1}}, + {"darkmagenta", {139, 0, 139, 1}}, + {"darkolivegreen", {85, 107, 47, 1}}, + {"darkorange", {255, 140, 0, 1}}, + {"darkorchid", {153, 50, 204, 1}}, + {"darkred", {139, 0, 0, 1}}, + {"darksalmon", {233, 150, 122, 1}}, + {"darkseagreen", {143, 188, 143, 1}}, + {"darkslateblue", {72, 61, 139, 1}}, + {"darkslategray", {47, 79, 79, 1}}, + {"darkslategrey", {47, 79, 79, 1}}, + {"darkturquoise", {0, 206, 209, 1}}, + {"darkviolet", {148, 0, 211, 1}}, + {"deeppink", {255, 20, 147, 1}}, + {"deepskyblue", {0, 191, 255, 1}}, + {"dimgray", {105, 105, 105, 1}}, + {"dimgrey", {105, 105, 105, 1}}, + {"dodgerblue", {30, 144, 255, 1}}, + {"firebrick", {178, 34, 34, 1}}, + {"floralwhite", {255, 250, 240, 1}}, + {"forestgreen", {34, 139, 34, 1}}, + {"fuchsia", {255, 0, 255, 1}}, + {"gainsboro", {220, 220, 220, 1}}, + {"ghostwhite", {248, 248, 255, 1}}, + {"gold", {255, 215, 0, 1}}, + {"goldenrod", {218, 165, 32, 1}}, + {"gray", {128, 128, 128, 1}}, + {"green", {0, 128, 0, 1}}, + {"greenyellow", {173, 255, 47, 1}}, + {"grey", {128, 128, 128, 1}}, + {"honeydew", {240, 255, 240, 1}}, + {"hotpink", {255, 105, 180, 1}}, + {"indianred", {205, 92, 92, 1}}, + {"indigo", {75, 0, 130, 1}}, + {"ivory", {255, 255, 240, 1}}, + {"khaki", {240, 230, 140, 1}}, + {"lavender", {230, 230, 250, 1}}, + {"lavenderblush", {255, 240, 245, 1}}, + {"lawngreen", {124, 252, 0, 1}}, + {"lemonchiffon", {255, 250, 205, 1}}, + {"lightblue", {173, 216, 230, 1}}, + {"lightcoral", {240, 128, 128, 1}}, + {"lightcyan", {224, 255, 255, 1}}, + {"lightgoldenrodyellow", {250, 250, 210, 1}}, + {"lightgray", {211, 211, 211, 1}}, + {"lightgreen", {144, 238, 144, 1}}, + {"lightgrey", {211, 211, 211, 1}}, + {"lightpink", {255, 182, 193, 1}}, + {"lightsalmon", {255, 160, 122, 1}}, + {"lightseagreen", {32, 178, 170, 1}}, + {"lightskyblue", {135, 206, 250, 1}}, + {"lightslategray", {119, 136, 153, 1}}, + {"lightslategrey", {119, 136, 153, 1}}, + {"lightsteelblue", {176, 196, 222, 1}}, + {"lightyellow", {255, 255, 224, 1}}, + {"lime", {0, 255, 0, 1}}, + {"limegreen", {50, 205, 50, 1}}, + {"linen", {250, 240, 230, 1}}, + {"magenta", {255, 0, 255, 1}}, + {"maroon", {128, 0, 0, 1}}, + {"mediumaquamarine", {102, 205, 170, 1}}, + {"mediumblue", {0, 0, 205, 1}}, + {"mediumorchid", {186, 85, 211, 1}}, + {"mediumpurple", {147, 112, 219, 1}}, + {"mediumseagreen", {60, 179, 113, 1}}, + {"mediumslateblue", {123, 104, 238, 1}}, + {"mediumspringgreen", {0, 250, 154, 1}}, + {"mediumturquoise", {72, 209, 204, 1}}, + {"mediumvioletred", {199, 21, 133, 1}}, + {"midnightblue", {25, 25, 112, 1}}, + {"mintcream", {245, 255, 250, 1}}, + {"mistyrose", {255, 228, 225, 1}}, + {"moccasin", {255, 228, 181, 1}}, + {"navajowhite", {255, 222, 173, 1}}, + {"navy", {0, 0, 128, 1}}, + {"oldlace", {253, 245, 230, 1}}, + {"olive", {128, 128, 0, 1}}, + {"olivedrab", {107, 142, 35, 1}}, + {"orange", {255, 165, 0, 1}}, + {"orangered", {255, 69, 0, 1}}, + {"orchid", {218, 112, 214, 1}}, + {"palegoldenrod", {238, 232, 170, 1}}, + {"palegreen", {152, 251, 152, 1}}, + {"paleturquoise", {175, 238, 238, 1}}, + {"palevioletred", {219, 112, 147, 1}}, + {"papayawhip", {255, 239, 213, 1}}, + {"peachpuff", {255, 218, 185, 1}}, + {"peru", {205, 133, 63, 1}}, + {"pink", {255, 192, 203, 1}}, + {"plum", {221, 160, 221, 1}}, + {"powderblue", {176, 224, 230, 1}}, + {"purple", {128, 0, 128, 1}}, + {"red", {255, 0, 0, 1}}, + {"rosybrown", {188, 143, 143, 1}}, + {"royalblue", {65, 105, 225, 1}}, + {"saddlebrown", {139, 69, 19, 1}}, + {"salmon", {250, 128, 114, 1}}, + {"sandybrown", {244, 164, 96, 1}}, + {"seagreen", {46, 139, 87, 1}}, + {"seashell", {255, 245, 238, 1}}, + {"sienna", {160, 82, 45, 1}}, + {"silver", {192, 192, 192, 1}}, + {"skyblue", {135, 206, 235, 1}}, + {"slateblue", {106, 90, 205, 1}}, + {"slategray", {112, 128, 144, 1}}, + {"slategrey", {112, 128, 144, 1}}, + {"snow", {255, 250, 250, 1}}, + {"springgreen", {0, 255, 127, 1}}, + {"steelblue", {70, 130, 180, 1}}, + {"tan", {210, 180, 140, 1}}, + {"teal", {0, 128, 128, 1}}, + {"thistle", {216, 191, 216, 1}}, + {"tomato", {255, 99, 71, 1}}, + {"turquoise", {64, 224, 208, 1}}, + {"violet", {238, 130, 238, 1}}, + {"wheat", {245, 222, 179, 1}}, + {"white", {255, 255, 255, 1}}, + {"whitesmoke", {245, 245, 245, 1}}, + {"yellow", {255, 255, 0, 1}}, + {"yellowgreen", {154, 205, 50, 1}}}; + +template +uint8_t clampCssByte(T i) { // Clamp to integer 0 .. 255. + i = static_cast(::round(i)); // Seems to be what Chrome does (vs truncation). + return static_cast(i < 0 ? 0 : i > 255 ? 255 + : i); +} + +template +float clampCssFloat(T f) { // Clamp to float 0.0 .. 1.0. + return f < 0 ? 0 : f > 1 ? 1 + : float(f); +} + +float parseFloat(const ccstd::string &str) { + return strtof(str.c_str(), nullptr); +} + +int64_t parseInt(const ccstd::string &str, uint8_t base = 10) { + return strtoll(str.c_str(), nullptr, base); +} + +uint8_t parseCssInt(const ccstd::string &str) { // int or percentage. + if (str.length() && str.back() == '%') { + return clampCssByte(parseFloat(str) / 100.0f * 255.0f); + } + return clampCssByte(parseInt(str)); +} + +float parseCssFloat(const ccstd::string &str) { // float or percentage. + if (str.length() && str.back() == '%') { + return clampCssFloat(parseFloat(str) / 100.0f); + } + return clampCssFloat(parseFloat(str)); +} + +float cssHueToRgb(float m1, float m2, float h) { + if (h < 0.0f) { + h += 1.0f; + } else if (h > 1.0f) { + h -= 1.0f; + } + + if (h * 6.0f < 1.0f) { + return m1 + (m2 - m1) * h * 6.0f; + } + if (h * 2.0f < 1.0f) { + return m2; + } + if (h * 3.0f < 2.0f) { + return m1 + (m2 - m1) * (2.0f / 3.0f - h) * 6.0f; + } + return m1; +} + +ccstd::vector split(const ccstd::string &s, char delim) { + ccstd::vector elems; + std::stringstream ss(s); + ccstd::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; +} + +Color parse(const ccstd::string &cssStr) { + ccstd::string str = cssStr; + + // Remove all whitespace, not compliant, but should just be more accepting. + str.erase(std::remove(str.begin(), str.end(), ' '), str.end()); + + // Convert to lowercase. + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + + for (const auto &namedColor : NAMED_COLORS) { + if (str == namedColor.name) { + return namedColor.color; + } + } + + // #abc and #abc123 syntax. + if (str.length() && str.front() == '#') { + if (str.length() == 4) { + int64_t iv = + parseInt(str.substr(1), 16); // REFINE(deanm): Stricter parsing. + if (!(iv >= 0 && iv <= 0xfff)) { + return {}; + } + + return Color( + static_cast(((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8)), + static_cast((iv & 0xf0) | ((iv & 0xf0) >> 4)), + static_cast((iv & 0xf) | ((iv & 0xf) << 4)), 1); + } + + if (str.length() == 7) { + int64_t iv = parseInt(str.substr(1), 16); // REFINE(deanm): Stricter parsing. + if (!(iv >= 0 && iv <= 0xffffff)) { + return {}; // Covers NaN. + } + + return Color(static_cast((iv & 0xff0000) >> 16), + static_cast((iv & 0xff00) >> 8), + static_cast(iv & 0xff), 1); + } + + return Color(); + } + + size_t op = str.find_first_of('('); + size_t ep = str.find_first_of(')'); + if (op != ccstd::string::npos && ep + 1 == str.length()) { + const ccstd::string fname = str.substr(0, op); + const ccstd::vector params = + split(str.substr(op + 1, ep - (op + 1)), ','); + + float alpha = 1.0f; + + if (fname == "rgba" || fname == "rgb") { + if (fname == "rgba") { + if (params.size() != 4) { + return {}; + } + alpha = parseCssFloat(params.back()); + } else { + if (params.size() != 3) { + return {}; + } + } + + return Color(parseCssInt(params[0]), parseCssInt(params[1]), + parseCssInt(params[2]), alpha); + } + + if (fname == "hsla" || fname == "hsl") { + if (fname == "hsla") { + if (params.size() != 4) { + return {}; + } + alpha = parseCssFloat(params.back()); + } else { + if (params.size() != 3) { + return {}; + } + } + + float h = parseFloat(params[0]) / 360.0f; + while (h < 0.0f) { + ++h; + } + while (h > 1.0f) { + --h; + } + + // NOTE(deanm): According to the CSS spec s/l should only be + // percentages, but we don't bother and let float or percentage. + float s = parseCssFloat(params[1]); + float l = parseCssFloat(params[2]); + + float m2 = l <= 0.5f ? l * (s + 1.0f) : l + s - l * s; + float m1 = l * 2.0f - m2; + + return Color( + clampCssByte(cssHueToRgb(m1, m2, h + 1.0f / 3.0f) * 255.0f), + clampCssByte(cssHueToRgb(m1, m2, h) * 255.0f), + clampCssByte(cssHueToRgb(m1, m2, h - 1.0f / 3.0f) * 255.0f), + alpha); + } + } + + return {}; +} + +} // namespace CSSColorParser diff --git a/cocos/base/csscolorparser.h b/cocos/base/csscolorparser.h new file mode 100644 index 0000000..d2125ef --- /dev/null +++ b/cocos/base/csscolorparser.h @@ -0,0 +1,76 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +// (c) Dean McNamee , 2012. +// C++ port by Mapbox, Konstantin Käfer , 2014-2017. +// +// https://github.com/deanm/css-color-parser-js +// https://github.com/kkaefer/css-color-parser-cpp +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#pragma once + +#include +#include "base/std/container/string.h" + +namespace CSSColorParser { + +struct Color { + inline Color() = default; + inline Color(unsigned char red, unsigned char green, unsigned char blue, float alpha) + : r(red), g(green), b(blue), a(alpha > 1 ? 1 : alpha < 0 ? 0 + : alpha) { + } + unsigned char r = 0, g = 0, b = 0; + float a = 1.0f; +}; + +inline bool operator==(const Color &lhs, const Color &rhs) { + return lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && ::fabs(lhs.a - rhs.a) < 0.0001f; +} + +inline bool operator!=(const Color &lhs, const Color &rhs) { + return !(lhs == rhs); +} + +Color parse(const ccstd::string &cssStr); + +} // namespace CSSColorParser diff --git a/cocos/base/etc1.cpp b/cocos/base/etc1.cpp new file mode 100644 index 0000000..570fc60 --- /dev/null +++ b/cocos/base/etc1.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "base/etc1.h" + +#include + +static const char kMagic[] = {'P', 'K', 'M', ' ', '1', '0'}; + +static const etc1_uint32 ETC1_PKM_FORMAT_OFFSET = 6; +static const etc1_uint32 ETC1_PKM_ENCODED_WIDTH_OFFSET = 8; +static const etc1_uint32 ETC1_PKM_ENCODED_HEIGHT_OFFSET = 10; +static const etc1_uint32 ETC1_PKM_WIDTH_OFFSET = 12; +static const etc1_uint32 ETC1_PKM_HEIGHT_OFFSET = 14; + +static const etc1_uint32 ETC1_RGB_NO_MIPMAPS = 0; + +static etc1_uint32 readBEUint16(const etc1_byte *pIn) { + return (pIn[0] << 8) | pIn[1]; +} + +// Check if a PKM header is correctly formatted. + +etc1_bool etc1_pkm_is_valid(const etc1_byte *pHeader) { + if (memcmp(pHeader, kMagic, sizeof(kMagic))) { + return false; + } + etc1_uint32 format = readBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET); + etc1_uint32 encodedWidth = readBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET); + etc1_uint32 encodedHeight = readBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET); + etc1_uint32 width = readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET); + etc1_uint32 height = readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET); + return format == ETC1_RGB_NO_MIPMAPS && + encodedWidth >= width && encodedWidth - width < 4 && + encodedHeight >= height && encodedHeight - height < 4; +} + +// Read the image width from a PKM header + +etc1_uint32 etc1_pkm_get_width(const etc1_byte *pHeader) { + return readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET); +} + +// Read the image height from a PKM header + +etc1_uint32 etc1_pkm_get_height(const etc1_byte *pHeader) { + return readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET); +} diff --git a/cocos/base/etc1.h b/cocos/base/etc1.h new file mode 100644 index 0000000..b9b46f2 --- /dev/null +++ b/cocos/base/etc1.h @@ -0,0 +1,78 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __etc1_h__ +#define __etc1_h__ +/// @cond DO_NOT_SHOW + +#define ETC1_ENCODED_BLOCK_SIZE 8 +#define ETC1_DECODED_BLOCK_SIZE 48 + +#ifndef ETC1_RGB8_OES + #define ETC1_RGB8_OES 0x8D64 +#endif + +typedef unsigned char etc1_byte; +typedef int etc1_bool; +typedef unsigned int etc1_uint32; + +#ifdef __cplusplus +extern "C" { +#endif +// Size of a PKM header, in bytes. + +#define ETC_PKM_HEADER_SIZE 16 + +// Check if a PKM header is correctly formatted. + +etc1_bool etc1_pkm_is_valid(const etc1_byte *pHeader); + +// Read the image width from a PKM header + +etc1_uint32 etc1_pkm_get_width(const etc1_byte *pHeader); + +// Read the image height from a PKM header + +etc1_uint32 etc1_pkm_get_height(const etc1_byte *pHeader); + +#ifdef __cplusplus +} +#endif + +/// @endcond +#endif diff --git a/cocos/base/etc2.cpp b/cocos/base/etc2.cpp new file mode 100644 index 0000000..07aae50 --- /dev/null +++ b/cocos/base/etc2.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/etc2.h" +#include +#include + +static const char kMagic[] = {'P', 'K', 'M', ' ', '2', '0'}; + +static const etc2_uint32 ETC2_PKM_FORMAT_OFFSET = 6; +static const etc2_uint32 ETC2_PKM_ENCODED_WIDTH_OFFSET = 8; +static const etc2_uint32 ETC2_PKM_ENCODED_HEIGHT_OFFSET = 10; +static const etc2_uint32 ETC2_PKM_WIDTH_OFFSET = 12; +static const etc2_uint32 ETC2_PKM_HEIGHT_OFFSET = 14; + +static etc2_uint32 readBEUint16(const etc2_byte *pIn) { + return (pIn[0] << 8) | pIn[1]; +} + +// Check if a PKM header is correctly formatted. + +etc2_bool etc2_pkm_is_valid(const etc2_byte *pHeader) { + if (memcmp(pHeader, kMagic, sizeof(kMagic))) { + return false; + } + etc2_uint32 format = readBEUint16(pHeader + ETC2_PKM_FORMAT_OFFSET); + etc2_uint32 encodedWidth = readBEUint16(pHeader + ETC2_PKM_ENCODED_WIDTH_OFFSET); + etc2_uint32 encodedHeight = readBEUint16(pHeader + ETC2_PKM_ENCODED_HEIGHT_OFFSET); + etc2_uint32 width = readBEUint16(pHeader + ETC2_PKM_WIDTH_OFFSET); + etc2_uint32 height = readBEUint16(pHeader + ETC2_PKM_HEIGHT_OFFSET); + return (format == ETC2_RGB_NO_MIPMAPS || format == ETC2_RGBA_NO_MIPMAPS) && + encodedWidth >= width && encodedWidth - width < 4 && + encodedHeight >= height && encodedHeight - height < 4; +} + +// Read the image width from a PKM header + +etc2_uint32 etc2_pkm_get_width(const etc2_byte *pHeader) { + return readBEUint16(pHeader + ETC2_PKM_WIDTH_OFFSET); +} + +// Read the image height from a PKM header + +etc2_uint32 etc2_pkm_get_height(const etc2_byte *pHeader) { + return readBEUint16(pHeader + ETC2_PKM_HEIGHT_OFFSET); +} + +etc2_uint32 etc2_pkm_get_format(const uint8_t *pHeader) { + return readBEUint16(pHeader + ETC2_PKM_FORMAT_OFFSET); +} diff --git a/cocos/base/etc2.h b/cocos/base/etc2.h new file mode 100644 index 0000000..e01d4c3 --- /dev/null +++ b/cocos/base/etc2.h @@ -0,0 +1,75 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef __etc2_h__ +#define __etc2_h__ +/// @cond DO_NOT_SHOW + +typedef unsigned char etc2_byte; +typedef int etc2_bool; +typedef unsigned int etc2_uint32; + +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif + +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Size of a PKM header, in bytes. + +#define ETC2_PKM_HEADER_SIZE 16 + +#define ETC2_RGB_NO_MIPMAPS 1 +#define ETC2_RGBA_NO_MIPMAPS 3 + +// Check if a PKM header is correctly formatted. + +etc2_bool etc2_pkm_is_valid(const etc2_byte *pHeader); + +// Read the image width from a PKM header + +etc2_uint32 etc2_pkm_get_width(const etc2_byte *pHeader); + +// Read the image height from a PKM header + +etc2_uint32 etc2_pkm_get_height(const etc2_byte *pHeader); + +// Read the image format from a PKM header + +etc2_uint32 etc2_pkm_get_format(const etc2_byte *pHeader); + +#ifdef __cplusplus +} +#endif + +/// @endcond +#endif diff --git a/cocos/base/job-system/JobSystem.h b/cocos/base/job-system/JobSystem.h new file mode 100644 index 0000000..9d06b59 --- /dev/null +++ b/cocos/base/job-system/JobSystem.h @@ -0,0 +1,51 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#if CC_USE_JOB_SYSTEM_TASKFLOW + #include "job-system-taskflow/TFJobGraph.h" + #include "job-system-taskflow/TFJobSystem.h" +namespace cc { +using JobToken = TFJobToken; +using JobGraph = TFJobGraph; +using JobSystem = TFJobSystem; +} // namespace cc +#elif CC_USE_JOB_SYSTEM_TBB + #include "job-system-tbb/TBBJobGraph.h" + #include "job-system-tbb/TBBJobSystem.h" +namespace cc { +using JobToken = TBBJobToken; +using JobGraph = TBBJobGraph; +using JobSystem = TBBJobSystem; +} // namespace cc +#else + #include "job-system-dummy/DummyJobGraph.h" + #include "job-system-dummy/DummyJobSystem.h" +namespace cc { +using JobToken = DummyJobToken; +using JobGraph = DummyJobGraph; +using JobSystem = DummyJobSystem; +} // namespace cc +#endif diff --git a/cocos/base/job-system/job-system-dummy/DummyJobGraph.cpp b/cocos/base/job-system/job-system-dummy/DummyJobGraph.cpp new file mode 100644 index 0000000..414dbed --- /dev/null +++ b/cocos/base/job-system/job-system-dummy/DummyJobGraph.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "DummyJobGraph.h" +#include "base/Macros.h" + +#define DUMMY_GRAPH_NODE_CHUNK_SIZE 64 + +namespace cc { + +namespace { +DummyGraphNode *freeList{nullptr}; +ccstd::vector allocatedChunks; +} // namespace + +DummyGraphNode::~DummyGraphNode() { + delete _callback; +} + +void DummyGraphNode::reset() { + _successors.clear(); + _predecessors.clear(); + delete _callback; + _callback = nullptr; +} + +void DummyGraphNode::succeed(DummyGraphNode *other) { + CC_ASSERT_NE(this, other); + // Run after other + this->_predecessors.emplace(other); + other->_successors.emplace(this); +} + +void DummyGraphNode::precede(DummyGraphNode *other) { + // Run before other + other->succeed(this); +} + +void DummyGraphNode::allocChunk() { + CC_ASSERT_NULL(freeList); + freeList = ccnew DummyGraphNode[DUMMY_GRAPH_NODE_CHUNK_SIZE](); + allocatedChunks.emplace_back(freeList); + for (auto i = 0; i < DUMMY_GRAPH_NODE_CHUNK_SIZE - 1; i++) { + freeList[i]._next = &freeList[i + 1]; + } + freeList[DUMMY_GRAPH_NODE_CHUNK_SIZE - 1]._next = nullptr; +} + +DummyGraphNode *DummyGraphNode::alloc() { + if (freeList == nullptr) { + DummyGraphNode::allocChunk(); + } + auto *p = freeList; + freeList = freeList->_next; + p->reset(); + return p; +} + +void DummyGraphNode::free(DummyGraphNode *node) { + node->_next = freeList; + freeList = node; +} + +void DummyGraphNode::freeAll() { + for (auto *chunk : allocatedChunks) { + delete[] chunk; + } + allocatedChunks.clear(); +} + +DummyGraph::~DummyGraph() { + clear(); +} + +void DummyGraph::clear() { + for (auto *node : _nodes) { + DummyGraphNode::free(node); + } + _nodes.clear(); +} + +void DummyGraph::link(size_t precede, size_t after) { + _nodes[precede]->precede(_nodes[after]); +} + +void DummyGraph::run() { + for (auto *node : _nodes) { + if (!excuted(node)) { + walk(node); + } + } + _generation++; +} + +void DummyGraph::walk(DummyGraphNode *node) { //NOLINT(misc-no-recursion) + for (DummyGraphNode *n : node->_predecessors) { + if (!excuted(n)) { + walk(n); + } + } + if (!excuted(node)) { + node->_callback->execute(); + node->_generation++; + } + + for (DummyGraphNode *n : node->_successors) { + if (!excuted(n)) { + walk(n); + } + } +} + +bool DummyGraph::excuted(DummyGraphNode *n) const { + return n->_generation != _generation; +} + +void DummyJobGraph::makeEdge(uint32_t j1, uint32_t j2) { + _dummyGraph.link(j1, j2); +} + +void DummyJobGraph::run() noexcept { + _dummyGraph.run(); + _dummyGraph.clear(); +} + +} // namespace cc diff --git a/cocos/base/job-system/job-system-dummy/DummyJobGraph.h b/cocos/base/job-system/job-system-dummy/DummyJobGraph.h new file mode 100644 index 0000000..dc6c16b --- /dev/null +++ b/cocos/base/job-system/job-system-dummy/DummyJobGraph.h @@ -0,0 +1,162 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/memory/Memory.h" +#include "base/std/container/unordered_set.h" +#include "base/std/container/vector.h" + +namespace cc { + +class DummyJobSystem; + +class DummyGraphNode; +class DummyGraph final { +public: + DummyGraph() = default; + DummyGraph(const DummyGraph &) = delete; + DummyGraph(DummyGraph &&) = delete; + DummyGraph &operator=(const DummyGraph &) = delete; + DummyGraph &operator=(DummyGraph &&) = delete; + ~DummyGraph(); + + template + size_t addNode(Fn &&fn); + + void run(); + void link(size_t precede, size_t after); + void walk(DummyGraphNode *node); + void clear(); + +private: + bool excuted(DummyGraphNode *n) const; + + int _generation{0}; + ccstd::vector _nodes; +}; + +class DummyGraphNodeTaskItf { +public: + virtual ~DummyGraphNodeTaskItf() = default; + virtual void execute() = 0; +}; + +template +class DummyGraphNodeTaskImpl final : public DummyGraphNodeTaskItf { +public: + explicit DummyGraphNodeTaskImpl(Fn &&t) noexcept; + DummyGraphNodeTaskImpl(const DummyGraphNodeTaskImpl &) = delete; + DummyGraphNodeTaskImpl(DummyGraphNodeTaskImpl &&) = delete; + DummyGraphNodeTaskImpl &operator=(const DummyGraphNodeTaskImpl &) = delete; + DummyGraphNodeTaskImpl &operator=(DummyGraphNodeTaskImpl &&) = delete; + ~DummyGraphNodeTaskImpl() override = default; + inline void execute() override { _task(); } + +private: + Fn _task; +}; + +class DummyGraphNode final { +public: + DummyGraphNode() = default; + DummyGraphNode(const DummyGraphNode &) = delete; + DummyGraphNode(DummyGraphNode &&) = delete; + DummyGraphNode &operator=(const DummyGraphNode &) = delete; + DummyGraphNode &operator=(DummyGraphNode &&) = delete; + ~DummyGraphNode(); + +private: + static void allocChunk(); + static DummyGraphNode *alloc(); + static void free(DummyGraphNode *n); + static void freeAll(); + + void succeed(DummyGraphNode *other); + void precede(DummyGraphNode *other); + void reset(); + + DummyGraphNodeTaskItf *_callback{nullptr}; + ccstd::unordered_set _successors{}; + ccstd::unordered_set _predecessors{}; + DummyGraphNode *_next{nullptr}; + int _generation{0}; + friend class DummyGraph; +}; + +template +DummyGraphNodeTaskImpl::DummyGraphNodeTaskImpl(Fn &&t) noexcept : _task(t) {} + +template +size_t DummyGraph::addNode(Fn &&fn) { + DummyGraphNode *n = DummyGraphNode::alloc(); + n->_callback = ccnew DummyGraphNodeTaskImpl(std::forward(fn)); + n->_generation = _generation; + _nodes.emplace_back(n); + return _nodes.size() - 1; +} + +// exported declarations + +class DummyJobGraph final { +public: + explicit DummyJobGraph(DummyJobSystem * /*system*/) noexcept {} + DummyJobGraph(const DummyJobGraph &) = delete; + DummyJobGraph(DummyJobGraph &&) = delete; + DummyJobGraph &operator=(const DummyJobGraph &) = delete; + DummyJobGraph &operator=(DummyJobGraph &&) = delete; + ~DummyJobGraph() noexcept = default; + + template + uint32_t createJob(Function &&func) noexcept; + + template + uint32_t createForEachIndexJob(uint32_t begin, uint32_t end, uint32_t step, Function &&func) noexcept; + + void makeEdge(uint32_t j1, uint32_t j2); + + void run() noexcept; + + inline void waitForAll() { run(); } + +private: + DummyGraph _dummyGraph{}; +}; + +template +uint32_t DummyJobGraph::createJob(Function &&func) noexcept { + return static_cast(_dummyGraph.addNode(std::forward(func))); +} + +template +uint32_t DummyJobGraph::createForEachIndexJob(uint32_t begin, uint32_t end, uint32_t step, Function &&func) noexcept { + return static_cast(_dummyGraph.addNode([callable = std::forward(func), first = begin, last = end, step = step]() { + for (auto i = first; i < last; i += step) { + callable(i); + } + })); +} + +} // namespace cc diff --git a/cocos/base/job-system/job-system-dummy/DummyJobSystem.cpp b/cocos/base/job-system/job-system-dummy/DummyJobSystem.cpp new file mode 100644 index 0000000..bd950a6 --- /dev/null +++ b/cocos/base/job-system/job-system-dummy/DummyJobSystem.cpp @@ -0,0 +1,32 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "DummyJobSystem.h" +#include "DummyJobGraph.h" + +namespace cc { + +DummyJobSystem *DummyJobSystem::instance = nullptr; + +} // namespace cc diff --git a/cocos/base/job-system/job-system-dummy/DummyJobSystem.h b/cocos/base/job-system/job-system-dummy/DummyJobSystem.h new file mode 100644 index 0000000..5aa8a0b --- /dev/null +++ b/cocos/base/job-system/job-system-dummy/DummyJobSystem.h @@ -0,0 +1,62 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/memory/Memory.h" + +namespace cc { + +using DummyJobToken = size_t; + +class DummyJobGraph; + +class DummyJobSystem final { +private: + static DummyJobSystem *instance; + +public: + static DummyJobSystem *getInstance() { + if (!instance) { + instance = ccnew DummyJobSystem; + } + return instance; + } + + static void destroyInstance() { + delete instance; + instance = nullptr; + } + + DummyJobSystem() noexcept = default; + explicit DummyJobSystem(uint32_t /*threadCount*/) noexcept {} + + inline uint32_t threadCount() const { return THREAD_COUNT; } //NOLINT + +private: + static constexpr uint32_t THREAD_COUNT = 1U; //always one +}; + +} // namespace cc diff --git a/cocos/base/job-system/job-system-taskflow/TFJobGraph.cpp b/cocos/base/job-system/job-system-taskflow/TFJobGraph.cpp new file mode 100644 index 0000000..36e835c --- /dev/null +++ b/cocos/base/job-system/job-system-taskflow/TFJobGraph.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "TFJobGraph.h" +#include "TFJobSystem.h" + +namespace cc { + +void TFJobGraph::makeEdge(uint32_t j1, uint32_t j2) noexcept { + _tasks[j1].precede(_tasks[j2]); +} + +void TFJobGraph::run() noexcept { + if (_pending) return; + _future = _executor->run(_flow); + _pending = true; +} + +} // namespace cc diff --git a/cocos/base/job-system/job-system-taskflow/TFJobGraph.h b/cocos/base/job-system/job-system-taskflow/TFJobGraph.h new file mode 100644 index 0000000..e64b60c --- /dev/null +++ b/cocos/base/job-system/job-system-taskflow/TFJobGraph.h @@ -0,0 +1,78 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "TFJobSystem.h" +#include "base/std/container/deque.h" +#include "taskflow/taskflow.hpp" + +namespace cc { + +using TFJobToken = void; + +class TFJobGraph final { +public: + explicit TFJobGraph(TFJobSystem *system) noexcept : _executor(&system->_executor) {} + + template + uint32_t createJob(Function &&func) noexcept; + + template + uint32_t createForEachIndexJob(uint32_t begin, uint32_t end, uint32_t step, Function &&func) noexcept; + + void makeEdge(uint32_t j1, uint32_t j2) noexcept; + + void run() noexcept; + + inline void waitForAll() { + if (_pending) { + _future.wait(); + _pending = false; + } + } + +private: + tf::Executor *_executor = nullptr; + + tf::Taskflow _flow; + ccstd::deque _tasks; // existing tasks cannot be invalidated + + std::future _future; + bool _pending = false; +}; + +template +uint32_t TFJobGraph::createJob(Function &&func) noexcept { + _tasks.emplace_back(_flow.emplace(func)); + return static_cast(_tasks.size() - 1u); +} + +template +uint32_t TFJobGraph::createForEachIndexJob(uint32_t begin, uint32_t end, uint32_t step, Function &&func) noexcept { + _tasks.emplace_back(_flow.for_each_index(begin, end, step, func)); + return static_cast(_tasks.size() - 1u); +} + +} // namespace cc diff --git a/cocos/base/job-system/job-system-taskflow/TFJobSystem.cpp b/cocos/base/job-system/job-system-taskflow/TFJobSystem.cpp new file mode 100644 index 0000000..54ae159 --- /dev/null +++ b/cocos/base/job-system/job-system-taskflow/TFJobSystem.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "TFJobSystem.h" +#include "TFJobGraph.h" +#include "base/Log.h" + +namespace cc { + +TFJobSystem *TFJobSystem::_instance = nullptr; + +TFJobSystem::TFJobSystem(uint32_t threadCount) noexcept +: _executor(threadCount) { + CC_LOG_INFO("Taskflow Job system initialized: %d worker threads", threadCount); +} + +} // namespace cc diff --git a/cocos/base/job-system/job-system-taskflow/TFJobSystem.h b/cocos/base/job-system/job-system-taskflow/TFJobSystem.h new file mode 100644 index 0000000..ec98702 --- /dev/null +++ b/cocos/base/job-system/job-system-taskflow/TFJobSystem.h @@ -0,0 +1,60 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/memory/Memory.h" +#include "taskflow/taskflow.hpp" + +namespace cc { + +class TFJobSystem final { +public: + static TFJobSystem *getInstance() { + if (!_instance) { + _instance = ccnew TFJobSystem; + } + return _instance; + } + + static void destroyInstance() { + CC_SAFE_DELETE(_instance); + } + + TFJobSystem() noexcept : TFJobSystem(std::max(2u, std::thread::hardware_concurrency() - 2u)) {} + explicit TFJobSystem(uint32_t threadCount) noexcept; + + inline uint32_t threadCount() { return static_cast(_executor.num_workers()); } + +private: + friend class TFJobGraph; + + static TFJobSystem *_instance; + + tf::Executor _executor; +}; + +} // namespace cc diff --git a/cocos/base/job-system/job-system-tbb/TBBJobGraph.cpp b/cocos/base/job-system/job-system-tbb/TBBJobGraph.cpp new file mode 100644 index 0000000..f8a71be --- /dev/null +++ b/cocos/base/job-system/job-system-tbb/TBBJobGraph.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "TBBJobGraph.h" + +namespace cc { + +void TBBJobGraph::makeEdge(uint32_t j1, uint32_t j2) noexcept { + if (j1 & PARALLEL_JOB_FLAG) { + j1 = _parallelJobs[j1 & PARALLEL_JOB_MASK].successor; + } + + if (j2 & PARALLEL_JOB_FLAG) { + j2 = _parallelJobs[j2 & PARALLEL_JOB_MASK].predecessor; + } + + tbb::flow::make_edge(_nodes[j1], _nodes[j2]); +} + +void TBBJobGraph::run() noexcept { + _nodes.front().try_put(tbb::flow::continue_msg()); + _pending = true; +} + +} // namespace cc diff --git a/cocos/base/job-system/job-system-tbb/TBBJobGraph.h b/cocos/base/job-system/job-system-tbb/TBBJobGraph.h new file mode 100644 index 0000000..9356678 --- /dev/null +++ b/cocos/base/job-system/job-system-tbb/TBBJobGraph.h @@ -0,0 +1,107 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/std/container/deque.h" +#include "base/std/container/vector.h" + +namespace cc { + +using TBBJobToken = tbb::flow::continue_msg; + +class TBBJobSystem; + +class TBBJobGraph final { +public: + explicit TBBJobGraph(TBBJobSystem *system) noexcept { + _nodes.emplace_back(_graph, [](TBBJobToken t) {}); + } + + template + uint32_t createJob(Function &&func) noexcept; + + template + uint32_t createForEachIndexJob(uint32_t begin, uint32_t end, uint32_t step, Function &&func) noexcept; + + void makeEdge(uint32_t j1, uint32_t j2) noexcept; + + void run() noexcept; + + inline void waitForAll() { + if (_pending) { + _graph.wait_for_all(); + _pending = false; + } + } + +private: + static constexpr uint32_t PARALLEL_JOB_FLAG = 1u << 20; + static constexpr uint32_t PARALLEL_JOB_MASK = ~PARALLEL_JOB_FLAG; + + tbb::flow::graph _graph; + + using TBBJobNode = tbb::flow::continue_node; + ccstd::deque _nodes; // existing nodes cannot be invalidated + + struct TBBParallelJob { + uint32_t predecessor = 0u; + uint32_t successor = 0u; + }; + ccstd::vector _parallelJobs; + + bool _pending = false; +}; + +template +uint32_t TBBJobGraph::createJob(Function &&func) noexcept { + _nodes.emplace_back(_graph, func); + tbb::flow::make_edge(_nodes.front(), _nodes.back()); + return static_cast(_nodes.size() - 1u); +} + +template +uint32_t TBBJobGraph::createForEachIndexJob(uint32_t begin, uint32_t end, uint32_t step, Function &&func) noexcept { + _nodes.emplace_back(_graph, [](TBBJobToken t) {}); + auto predecessorIdx = static_cast(_nodes.size() - 1u); + TBBJobNode &predecessor = _nodes.back(); + + tbb::flow::make_edge(_nodes.front(), predecessor); + + _nodes.emplace_back(_graph, [](TBBJobToken t) {}); + auto successorIdx = static_cast(_nodes.size() - 1u); + TBBJobNode &successor = _nodes.back(); + + for (uint32_t i = begin; i < end; i += step) { + _nodes.emplace_back(_graph, [i, &func](TBBJobToken t) { func(i); }); + tbb::flow::make_edge(predecessor, _nodes.back()); + tbb::flow::make_edge(_nodes.back(), successor); + } + + _parallelJobs.push_back({predecessorIdx, successorIdx}); + return static_cast((_parallelJobs.size() - 1u)) | PARALLEL_JOB_FLAG; +} + +} // namespace cc diff --git a/cocos/base/job-system/job-system-tbb/TBBJobSystem.cpp b/cocos/base/job-system/job-system-tbb/TBBJobSystem.cpp new file mode 100644 index 0000000..0233f4e --- /dev/null +++ b/cocos/base/job-system/job-system-tbb/TBBJobSystem.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/Log.h" + +#include "TBBJobGraph.h" +#include "TBBJobSystem.h" + +namespace cc { + +TBBJobSystem *TBBJobSystem::_instance = nullptr; + +TBBJobSystem::TBBJobSystem(uint32_t threadCount) noexcept +: _control(tbb::global_control::max_allowed_parallelism, threadCount), + _threadCount(threadCount) { + CC_LOG_INFO("TBB Job system initialized: %d worker threads", threadCount); +} + +} // namespace cc diff --git a/cocos/base/job-system/job-system-tbb/TBBJobSystem.h b/cocos/base/job-system/job-system-tbb/TBBJobSystem.h new file mode 100644 index 0000000..0c5ce33 --- /dev/null +++ b/cocos/base/job-system/job-system-tbb/TBBJobSystem.h @@ -0,0 +1,61 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/memory/Memory.h" +#include "tbb/global_control.h" + +namespace cc { + +class TBBJobGraph; + +class TBBJobSystem final { +public: + static TBBJobSystem *getInstance() { + if (!_instance) { + _instance = ccnew TBBJobSystem; + } + return _instance; + } + + static void destroyInstance() { + CC_SAFE_DELETE(_instance); + } + + TBBJobSystem() noexcept : TBBJobSystem(std::max(2u, std::thread::hardware_concurrency() - 2u)) {} + explicit TBBJobSystem(uint32_t threadCount) noexcept; + + inline uint32_t threadCount() { return _threadCount; } + +private: + static TBBJobSystem *_instance; + + tbb::global_control _control; + uint32_t _threadCount{0u}; +}; + +} // namespace cc diff --git a/cocos/base/memory/CallStack.cpp b/cocos/base/memory/CallStack.cpp new file mode 100644 index 0000000..1b105cc --- /dev/null +++ b/cocos/base/memory/CallStack.cpp @@ -0,0 +1,300 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "CallStack.h" +#if USE_MEMORY_LEAK_DETECTOR + + #if CC_PLATFORM == CC_PLATFORM_ANDROID + #define __GNU_SOURCE + #include + #include + #include + #elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + #include + #elif CC_PLATFORM == CC_PLATFORM_WINDOWS + #include + #include + + #pragma comment(lib, "dbghelp.lib") + #endif + + #include + +namespace cc { + +ccstd::string StackFrame::toString() { + static ccstd::string unknown("unknown"); + #if CC_PLATFORM == CC_PLATFORM_ANDROID + std::stringstream stream; + stream << "\tmodule: " << (module.empty() ? unknown : module) + << "\tfunction: " << (function.empty() ? unknown : function); + + return stream.str(); + + #elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + std::stringstream stream; + stream << "\tfile: " << (file.empty() ? unknown : file); + + return stream.str(); + + #elif CC_PLATFORM == CC_PLATFORM_WINDOWS + std::stringstream stream; + stream << "\tmodule: " << (module.empty() ? unknown : module) + << "\tfile: " << (file.empty() ? unknown : file) + << "\tfunction: " << (function.empty() ? unknown : function) + << "\tline: " << line; + + return stream.str(); + + #else + return unknown; + #endif +} + + #if CC_PLATFORM == CC_PLATFORM_ANDROID + +struct ThreadStack { + void *stack[MAX_STACK_FRAMES]; + int current; + int overflow; +}; + +extern "C" { + +static pthread_once_t s_once = PTHREAD_ONCE_INIT; +static pthread_key_t s_threadStackKey = 0; + +static void __attribute__((no_instrument_function)) +init_once(void) { + pthread_key_create(&s_threadStackKey, NULL); +} + +static ThreadStack *__attribute__((no_instrument_function)) +getThreadStack() { + ThreadStack *ptr = (ThreadStack *)pthread_getspecific(s_threadStackKey); + if (ptr) { + return ptr; + } + + ptr = (ThreadStack *)calloc(1, sizeof(ThreadStack)); + ptr->current = 0; + ptr->overflow = 0; + pthread_setspecific(s_threadStackKey, ptr); + return ptr; +} + +void __attribute__((no_instrument_function)) +__cyg_profile_func_enter(void *this_fn, void *call_site) { + pthread_once(&s_once, init_once); + ThreadStack *ptr = getThreadStack(); + if (ptr->current < MAX_STACK_FRAMES) { + ptr->stack[ptr->current++] = this_fn; + ptr->overflow = 0; + } else { + ptr->overflow++; + } +} + +void __attribute__((no_instrument_function)) +__cyg_profile_func_exit(void *this_fn, void *call_site) { + pthread_once(&s_once, init_once); + ThreadStack *ptr = getThreadStack(); + + if (ptr->overflow == 0 && ptr->current > 0) { + ptr->current--; + } + + if (ptr->overflow > 0) { + ptr->overflow--; + } +} +} + + #endif + +ccstd::string CallStack::basename(const ccstd::string &path) { + size_t found = path.find_last_of("/\\"); + + if (ccstd::string::npos != found) { + return path.substr(found + 1); + } else { + return path; + } +} + +ccstd::vector CallStack::backtrace() { + #if CC_PLATFORM == CC_PLATFORM_ANDROID + ccstd::vector callstack; + callstack.reserve(MAX_STACK_FRAMES); + + pthread_once(&s_once, init_once); + ThreadStack *ptr = getThreadStack(); + for (int i = ptr->current - 1; i >= 0; i--) { + callstack.push_back(ptr->stack[i]); + } + + return callstack; + + #elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + ccstd::vector callstack; + callstack.reserve(MAX_STACK_FRAMES); + + void *array[MAX_STACK_FRAMES]; + int count = ::backtrace(array, MAX_STACK_FRAMES); + for (auto i = 0; i < count; i++) { + callstack.push_back(array[i]); + } + return callstack; + + #elif CC_PLATFORM == CC_PLATFORM_WINDOWS + ccstd::vector callstack; + callstack.reserve(MAX_STACK_FRAMES); + + void *array[MAX_STACK_FRAMES]; + int count = CaptureStackBackTrace(0, MAX_STACK_FRAMES, array, NULL); + for (auto i = 0; i < count; i++) { + callstack.push_back(array[i]); + } + return callstack; + + #else + return {}; + #endif +} + +ccstd::vector CallStack::backtraceSymbols(const ccstd::vector &callstack) { + #if CC_PLATFORM == CC_PLATFORM_ANDROID + ccstd::vector frames; + size_t size = callstack.size(); + for (size_t i = 0; i < size; i++) { + Dl_info info; + StackFrame frame; + if (dladdr(callstack[i], &info)) { + if (info.dli_fname && strlen(info.dli_fname) > 0) { + frame.module = basename(info.dli_fname); + } + + if (info.dli_sname && strlen(info.dli_sname) > 0) { + char *real_name = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, nullptr); + if (real_name) { + frame.function = real_name; + free(real_name); + } else { + frame.function = info.dli_sname; + } + } + } + + frames.push_back(std::move(frame)); + } + return frames; + + #elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + size_t size = callstack.size(); + if (size == 0) { + return {}; + } + + ccstd::vector frames; + char **strs = ::backtrace_symbols(&callstack[0], size); + for (size_t i = 0; i < size; i++) { + StackFrame frame; + frame.file = strs[i]; + frames.push_back(std::move(frame)); + } + + return frames; + + #elif CC_PLATFORM == CC_PLATFORM_WINDOWS + ccstd::vector frames; + + #if _WIN64 + using PTR_DWORD = DWORD64; + #else + using PTR_DWORD = DWORD; + #endif + + size_t size = callstack.size(); + for (size_t i = 0; i < size; i++) { + StackFrame frame; + PTR_DWORD address = reinterpret_cast(callstack[i]); + + char moduelName[MAX_PATH]; + #if _WIN64 + PTR_DWORD moduleBase = SymGetModuleBase64(_process, address); + #else + PTR_DWORD moduleBase = SymGetModuleBase(_process, address); + #endif + if (moduleBase && GetModuleFileNameA((HINSTANCE)moduleBase, moduelName, MAX_PATH)) { + frame.module = basename(moduelName); + } + + DWORD64 offset = 0; + char symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYMBOL_LENGTH] = {0}; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)symbolBuffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYMBOL_LENGTH - 1; + + if (SymFromAddr(_process, address, &offset, symbol)) { + frame.function = symbol->Name; + } + + IMAGEHLP_LINE line; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE); + DWORD offset_ln = 0; + + if (SymGetLineFromAddr(_process, address, &offset_ln, &line)) { + frame.file = line.FileName; + frame.line = line.LineNumber; + } + + frames.push_back(std::move(frame)); + } + + return frames; + + #else + return {}; + #endif +} + + #if CC_PLATFORM == CC_PLATFORM_WINDOWS +void CallStack::initSym() { + _process = GetCurrentProcess(); + if (SymInitialize(_process, nullptr, true) == false) { + CC_ABORT(); + } + SymSetOptions(SYMOPT_LOAD_LINES); +} + +void CallStack::cleanupSym() { + SymCleanup(_process); +} + +HANDLE CallStack::_process = 0; + #endif + +} // namespace cc + +#endif diff --git a/cocos/base/memory/CallStack.h b/cocos/base/memory/CallStack.h new file mode 100644 index 0000000..f65501a --- /dev/null +++ b/cocos/base/memory/CallStack.h @@ -0,0 +1,76 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../Config.h" +#if USE_MEMORY_LEAK_DETECTOR + + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + #include + #endif + #include + #include "../Macros.h" + #include "base/std/container/string.h" + #include "base/std/container/vector.h" + +namespace cc { + + #define MAX_STACK_FRAMES 64 + #define MAX_SYMBOL_LENGTH 255 + +/** + * A single frame of callstack. + */ +struct CC_DLL StackFrame { + ccstd::string module; + ccstd::string file; + ccstd::string function; + uint32_t line{0}; + + ccstd::string toString(); +}; + +/** + * An utility class used to backtrace callstack. + */ +class CC_DLL CallStack { +public: + static ccstd::string basename(const ccstd::string &path); + + static ccstd::vector backtrace(); + static ccstd::vector backtraceSymbols(const ccstd::vector &callstack); + + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + static void initSym(); + static void cleanupSym(); + +private: + static HANDLE _process; + #endif +}; + +} // namespace cc + +#endif diff --git a/cocos/base/memory/Memory.h b/cocos/base/memory/Memory.h new file mode 100644 index 0000000..72dabe0 --- /dev/null +++ b/cocos/base/memory/Memory.h @@ -0,0 +1,131 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#if (CC_PLATFORM == CC_PLATFORM_IOS) + #include +#endif + +#ifdef _MSC_VER + #include +#else + #include +#endif + +#include // std::nothrow + +#include "base/Macros.h" + +namespace cc { +class MemoryAllocDealloc final { +public: + inline static void *allocateBytesAligned(size_t alignment, size_t count) { +#ifdef _MSC_VER + void *ptr = _aligned_malloc(count, alignment); +#else + // alignment is not multiple of sizeof(void*) + CC_ASSERT_ZERO(alignment % sizeof(void *)); + void *ptr = nullptr; + posix_memalign(&ptr, alignment, count); +#endif + return ptr; + } + + inline static void deallocateBytesAligned(void *ptr) { +#ifdef _MSC_VER + _aligned_free(ptr); +#else + free(ptr); +#endif + } +}; +} // namespace cc + +#define ccnew new (std::nothrow) //NOLINT(readability-identifier-naming) +#define ccnew_placement(...) new (__VA_ARGS__) //NOLINT(readability-identifier-naming) + +#define CC_SAFE_DELETE(ptr) \ + if (ptr) { \ + delete ptr; \ + (ptr) = nullptr; \ + } + +#define CC_SAFE_DELETE_ARRAY(ptr) \ + if (ptr) { \ + delete[] ptr; \ + (ptr) = nullptr; \ + } + +#define CC_MALLOC(bytes) malloc(bytes) +#define CC_MALLOC_ALIGN(bytes, align) ::cc::MemoryAllocDealloc::allocateBytesAligned(align, bytes) +#define CC_REALLOC(ptr, bytes) realloc(ptr, bytes) +#define CC_FREE(ptr) free((void *)ptr) +#define CC_FREE_ALIGN(ptr) ::cc::MemoryAllocDealloc::deallocateBytesAligned(ptr) + +#define CC_SAFE_FREE(ptr) \ + if (ptr) { \ + CC_FREE(ptr); \ + (ptr) = nullptr; \ + } + +#define CC_SAFE_DESTROY(ptr) \ + if (ptr) { \ + (ptr)->destroy(); \ + } + +#define CC_SAFE_DESTROY_AND_DELETE(ptr) \ + if (ptr) { \ + (ptr)->destroy(); \ + delete ptr; \ + (ptr) = nullptr; \ + } + +#define CC_SAFE_DESTROY_NULL(ptr) \ + if (ptr) { \ + (ptr)->destroy(); \ + (ptr) = nullptr; \ + } + +#define CC_SAFE_RELEASE(p) \ + if (p) { \ + (p)->release(); \ + } + +#define CC_SAFE_RELEASE_NULL(p) \ + if (p) { \ + (p)->release(); \ + (p) = nullptr; \ + } + +#define CC_SAFE_ADD_REF(p) \ + if (p) { \ + (p)->addRef(); \ + } + +#if ((CC_PLATFORM == CC_PLATFORM_IOS) && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0)) || SWIGCOCOS + #define ALIGNAS(x) +#else + #define ALIGNAS(x) alignas(x) +#endif diff --git a/cocos/base/memory/MemoryHook.cpp b/cocos/base/memory/MemoryHook.cpp new file mode 100644 index 0000000..9aabe37 --- /dev/null +++ b/cocos/base/memory/MemoryHook.cpp @@ -0,0 +1,338 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "MemoryHook.h" +#include "CallStack.h" +#if USE_MEMORY_LEAK_DETECTOR + + #include + + #if CC_PLATFORM == CC_PLATFORM_ANDROID + #define __GNU_SOURCE + #include + #include + +static NewHookType g_new_hooker = nullptr; +static DeleteHookType g_delete_hooker = nullptr; + +extern "C" { + +void *malloc(size_t size) __attribute__((weak)); +void free(void *ptr) __attribute__((weak)); + +// Use strong symbol to overwrite the weak one. +void *malloc(size_t size) { + static MallocType system_malloc = nullptr; + if (CC_PREDICT_FALSE(system_malloc == nullptr)) { + system_malloc = (MallocType)dlsym(RTLD_NEXT, "malloc"); + } + + void *ptr = system_malloc(size); + if (CC_PREDICT_TRUE(g_new_hooker != nullptr)) { + g_new_hooker(ptr, size); + } + + return ptr; +} + +void free(void *ptr) { + static FreeType system_free = nullptr; + if (CC_PREDICT_FALSE(system_free == nullptr)) { + system_free = (FreeType)dlsym(RTLD_NEXT, "free"); + } + + system_free(ptr); + if (CC_PREDICT_TRUE(g_delete_hooker != nullptr)) { + g_delete_hooker(ptr); + } +} +} + + #elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS +typedef void malloc_logger_t(uint32_t aType, + uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3, + uintptr_t aResult, uint32_t aNumHotFramesToSkip); + +extern malloc_logger_t *malloc_logger; +static malloc_logger_t *g_system_malloc_logger = nullptr; +static NewHookType g_new_hooker = nullptr; +static DeleteHookType g_delete_hooker = nullptr; + +static void +cc_malloc_logger(uint32_t aType, + uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3, + uintptr_t aResult, uint32_t aNumHotFramesToSkip) { + if (aResult != 0) { + size_t new_size = reinterpret_cast(aArg3); + if (new_size != 0) { + // realloc + if (CC_PREDICT_TRUE(g_delete_hooker != nullptr)) { + const void *ptr = reinterpret_cast(aArg2); + g_delete_hooker(ptr); + } + + if (CC_PREDICT_TRUE(g_new_hooker != nullptr)) { + const void *new_ptr = reinterpret_cast(aResult); + g_new_hooker(new_ptr, new_size); + } + } else { + // malloc/calloc/valloc + if (CC_PREDICT_TRUE(g_new_hooker != nullptr)) { + const void *ptr = reinterpret_cast(aResult); + size_t size = reinterpret_cast(aArg2); + g_new_hooker(ptr, size); + } + } + } else { + // free + if (CC_PREDICT_TRUE(g_delete_hooker != nullptr)) { + const void *ptr = reinterpret_cast(aArg2); + g_delete_hooker(ptr); + } + } +} + + #elif CC_PLATFORM == CC_PLATFORM_WINDOWS + #include + +extern "C" { +typedef void (*MallocHook_NewHook)(const void *ptr, size_t size); +typedef void (*MallocHook_DeleteHook)(const void *ptr); + +int MallocHook_AddNewHook(MallocHook_NewHook hook); +int MallocHook_RemoveNewHook(MallocHook_NewHook hook); +int MallocHook_AddDeleteHook(MallocHook_DeleteHook hook); +int MallocHook_RemoveDeleteHook(MallocHook_DeleteHook hook); +} + + #endif + +namespace cc { + +static void newHook(const void *ptr, size_t size) { + uint64_t address = reinterpret_cast(ptr); + GMemoryHook.addRecord(address, size); +} + +static void deleteHook(const void *ptr) { + uint64_t address = reinterpret_cast(ptr); + GMemoryHook.removeRecord(address); +} + +MemoryHook::MemoryHook() { + registerAll(); +} + +MemoryHook::~MemoryHook() { + unRegisterAll(); + dumpMemoryLeak(); +} + +void MemoryHook::addRecord(uint64_t address, size_t size) { + std::lock_guard lock(_mutex); + if (_hooking) { + return; + } + + _hooking = true; + + // {} is necessary here to make variables being destroyed before _hooking = false + { + MemoryRecord record; + record.address = address; + record.size = size; + record.callstack = CallStack::backtrace(); + _records.insert({address, record}); + _totalSize += size; + } + + _hooking = false; +} + +void MemoryHook::removeRecord(uint64_t address) { + std::lock_guard lock(_mutex); + if (_hooking) { + return; + } + + _hooking = true; + + // {} is necessary here to make variables being destroyed before _hooking = false + { + auto iter = _records.find(address); + if (iter != _records.end()) { + _totalSize -= iter->second.size; + _records.erase(iter); + } + } + + _hooking = false; +} + +static bool isIgnored(const StackFrame &frame) { + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + static const ccstd::vector ignoreModules = { + "SDL2", + "EGL", + "GLESv2", + "opengl32", + "nvoglv64", + "sqlite3", + "libuv", + "SogouPy"}; + + static const ccstd::vector ignoreFunctions = { + "type_info::name"}; + + for (auto &module : ignoreModules) { + if (frame.module.find(module) != ccstd::string::npos) { + return true; + } + } + + for (auto &function : ignoreFunctions) { + if (frame.function.find(function) != ccstd::string::npos) { + return true; + } + } + + return false; + #else + return false; + #endif +} + +void MemoryHook::dumpMemoryLeak() { + std::lock_guard lock(_mutex); + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + CallStack::initSym(); + #endif + + std::stringstream startStream; + startStream << std::endl; + startStream << "---------------------------------------------------------------------------------------------------------" << std::endl; + startStream << "--------------------------------------memory leak report start-------------------------------------------" << std::endl; + startStream << "---------------------------------------------------------------------------------------------------------" << std::endl; + log(startStream.str()); + + if (_records.size() == 0) { + std::stringstream stream; + stream << std::endl; + stream << "Congratulations! There is no memory leak at all." << std::endl; + log(stream.str()); + } + + uint32_t i = 0; + size_t skipSize = 0; + uint32_t skipCount = 0; + + for (const auto &iter : _records) { + bool skip = false; + auto frames = CallStack::backtraceSymbols(iter.second.callstack); + for (auto &frame : frames) { + if (isIgnored(frame)) { + skip = true; + break; + } + } + + if (skip) { + skipSize += iter.second.size; + skipCount++; + continue; + } + + std::stringstream stream; + int k = 0; + + stream << std::endl; + stream << "<" << ++i << ">:" + << "leak " << iter.second.size << " bytes at 0x" << std::hex << iter.second.address << std::dec << std::endl; + stream << "\tcallstack:" << std::endl; + + for (auto &frame : frames) { + stream << "\t[" << ++k << "]:" << frame.toString() << std::endl; + } + + log(stream.str()); + } + + std::stringstream endStream; + endStream << std::endl + << "Total leak count: " << _records.size() << " with " << _totalSize << " bytes, " + << "Total skip count: " << skipCount << " with " << skipSize << " bytes" << std::endl; + + endStream << "---------------------------------------------------------------------------------------------------------" << std::endl; + endStream << "--------------------------------------memory leak report end---------------------------------------------" << std::endl; + endStream << "---------------------------------------------------------------------------------------------------------" << std::endl; + log(endStream.str()); + + #if CC_PLATFORM == CC_PLATFORM_WINDOWS + CallStack::cleanupSym(); + #endif +} + +void MemoryHook::log(const ccstd::string &msg) { + #if (CC_PLATFORM == CC_PLATFORM_ANDROID) + __android_log_write(ANDROID_LOG_WARN, "Cocos", msg.c_str()); + #elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + fputs(msg.c_str(), stdout); + #elif (CC_PLATFORM == CC_PLATFORM_WINDOWS) + OutputDebugStringA(msg.c_str()); + #endif +} + +void MemoryHook::registerAll() { + #if CC_PLATFORM == CC_PLATFORM_ANDROID + g_new_hooker = newHook; + g_delete_hooker = deleteHook; + free(malloc(1)); // force to init system_malloc/system_free + #elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + g_system_malloc_logger = malloc_logger; + malloc_logger = cc_malloc_logger; + g_new_hooker = newHook; + g_delete_hooker = deleteHook; + #elif CC_PLATFORM == CC_PLATFORM_WINDOWS + MallocHook_AddNewHook(&newHook); + MallocHook_AddDeleteHook(&deleteHook); + #endif +} + +void MemoryHook::unRegisterAll() { + #if CC_PLATFORM == CC_PLATFORM_ANDROID + g_new_hooker = nullptr; + g_delete_hooker = nullptr; + #elif CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS + malloc_logger = g_system_malloc_logger; + g_new_hooker = nullptr; + g_delete_hooker = nullptr; + #elif CC_PLATFORM == CC_PLATFORM_WINDOWS + MallocHook_RemoveNewHook(&newHook); + MallocHook_RemoveDeleteHook(&deleteHook); + #endif +} + +} // namespace cc + +#endif diff --git a/cocos/base/memory/MemoryHook.h b/cocos/base/memory/MemoryHook.h new file mode 100644 index 0000000..4330777 --- /dev/null +++ b/cocos/base/memory/MemoryHook.h @@ -0,0 +1,93 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../Config.h" +#if USE_MEMORY_LEAK_DETECTOR + + #include + #include "../Macros.h" + #include "base/std/container/string.h" + #include "base/std/container/unordered_map.h" + #include "base/std/container/vector.h" + +typedef void *(*MallocType)(size_t size); +typedef void (*FreeType)(void *ptr); + +typedef void (*NewHookType)(const void *ptr, size_t size); +typedef void (*DeleteHookType)(const void *ptr); + +namespace cc { + +struct CC_DLL MemoryRecord { + uint64_t address{0}; + size_t size{0}; + ccstd::vector callstack; +}; + +class CC_DLL MemoryHook { +public: + MemoryHook(); + ~MemoryHook(); + + /** + * RecordMap's key is memory address. + */ + using RecordMap = ccstd::unordered_map; + + void addRecord(uint64_t address, size_t size); + void removeRecord(uint64_t address); + inline size_t getTotalSize() const { return _totalSize; } + +private: + /** + * Dump all memory leaks to output window + */ + void dumpMemoryLeak(); + + static void log(const ccstd::string &msg); + + /** + * Register all malloc hooks + */ + void registerAll(); + + /** + * Unregister all malloc hooks + */ + void unRegisterAll(); + +private: + std::recursive_mutex _mutex; + bool _hooking{false}; + RecordMap _records; + size_t _totalSize{0U}; +}; + +extern MemoryHook GMemoryHook; + +} // namespace cc + +#endif diff --git a/cocos/base/std/any.h b/cocos/base/std/any.h new file mode 100644 index 0000000..15273c3 --- /dev/null +++ b/cocos/base/std/any.h @@ -0,0 +1,79 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#ifdef USE_CXX_17 + + #include + +namespace ccstd { + +using std::any; +using std::any_cast; + +} // namespace ccstd + +#else + + #include "boost/any.hpp" + +namespace ccstd { + +class any : public boost::any { // NOLINT // use std style +public: + using boost::any::any; + + inline bool has_value() const noexcept { // NOLINT // use std style + return !this->empty(); + } +}; + +template +inline ValueType *any_cast(any *operand) noexcept { // NOLINT // use std style + return boost::any_cast(operand); +} + +template +inline const ValueType *any_cast(const any *operand) noexcept { // NOLINT // use std style + return boost::any_cast(operand); +} + +template +inline ValueType any_cast(any &operand) { // NOLINT // use std style + return boost::any_cast(operand); +} + +template +inline ValueType any_cast(const any &operand) { // NOLINT // use std style + return boost::any_cast(operand); +} + +template +inline ValueType any_cast(any &&operand) { // NOLINT // use std style + return boost::any_cast(operand); +} + +} // namespace ccstd +#endif diff --git a/cocos/base/std/container/array.h b/cocos/base/std/container/array.h new file mode 100644 index 0000000..c378611 --- /dev/null +++ b/cocos/base/std/container/array.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +namespace ccstd { +using std::array; +} // namespace ccstd diff --git a/cocos/base/std/container/deque.h b/cocos/base/std/container/deque.h new file mode 100644 index 0000000..8e7bd41 --- /dev/null +++ b/cocos/base/std/container/deque.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "boost/container/pmr/polymorphic_allocator.hpp" + +namespace ccstd { +using std::deque; + +namespace pmr { +template +using deque = std::deque>; +} +} // namespace ccstd diff --git a/cocos/base/std/container/list.h b/cocos/base/std/container/list.h new file mode 100644 index 0000000..833e62a --- /dev/null +++ b/cocos/base/std/container/list.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "boost/container/pmr/polymorphic_allocator.hpp" + +namespace ccstd { +using std::list; + +namespace pmr { +template +using list = std::list>; +} +} // namespace ccstd diff --git a/cocos/base/std/container/map.h b/cocos/base/std/container/map.h new file mode 100644 index 0000000..80952c0 --- /dev/null +++ b/cocos/base/std/container/map.h @@ -0,0 +1,38 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "boost/container/pmr/polymorphic_allocator.hpp" + +namespace ccstd { +using std::map; + +namespace pmr { +template > +using map = std::map>>; +} +} // namespace ccstd diff --git a/cocos/base/std/container/queue.h b/cocos/base/std/container/queue.h new file mode 100644 index 0000000..406011b --- /dev/null +++ b/cocos/base/std/container/queue.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "boost/container/pmr/polymorphic_allocator.hpp" + +namespace ccstd { +using std::queue; + +namespace pmr { +template +using queue = std::queue>; +} +} // namespace ccstd diff --git a/cocos/base/std/container/set.h b/cocos/base/std/container/set.h new file mode 100644 index 0000000..072aef2 --- /dev/null +++ b/cocos/base/std/container/set.h @@ -0,0 +1,39 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "boost/container/pmr/polymorphic_allocator.hpp" + +namespace ccstd { +using std::set; + +namespace pmr { +template < + class Key, + class Compare = std::less> +using set = std::set>; +} +} // namespace ccstd diff --git a/cocos/base/std/container/string.h b/cocos/base/std/container/string.h new file mode 100644 index 0000000..53da990 --- /dev/null +++ b/cocos/base/std/container/string.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "boost/container/pmr/polymorphic_allocator.hpp" + +namespace ccstd { +using std::string; + +namespace pmr { +using string = std::basic_string, boost::container::pmr::polymorphic_allocator>; +} +} // namespace ccstd diff --git a/cocos/base/std/container/unordered_map.h b/cocos/base/std/container/unordered_map.h new file mode 100644 index 0000000..5927c9f --- /dev/null +++ b/cocos/base/std/container/unordered_map.h @@ -0,0 +1,51 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/std/hash/hash_fwd.hpp" +#include "boost/container/pmr/polymorphic_allocator.hpp" + +namespace ccstd { +using std::unordered_map; +using std::unordered_multimap; + +namespace pmr { +template < + class Key, + class T, + class Hash = ccstd::hash, + class Pred = std::equal_to> +using unordered_map = std::unordered_map>>; + +template < + class Key, + class T, + class Hash = ccstd::hash, + class Pred = std::equal_to> +using unordered_multimap = std::unordered_multimap>>; +} // namespace pmr +} // namespace ccstd diff --git a/cocos/base/std/container/unordered_set.h b/cocos/base/std/container/unordered_set.h new file mode 100644 index 0000000..0843762 --- /dev/null +++ b/cocos/base/std/container/unordered_set.h @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "boost/container/pmr/polymorphic_allocator.hpp" + +namespace ccstd { +using std::unordered_set; + +namespace pmr { +template < + class Key, + class Hash = std::hash, + class KeyEqual = std::equal_to> +using unordered_set = std::unordered_set>; +} +} // namespace ccstd diff --git a/cocos/base/std/container/vector.h b/cocos/base/std/container/vector.h new file mode 100644 index 0000000..3a34a02 --- /dev/null +++ b/cocos/base/std/container/vector.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "boost/container/pmr/polymorphic_allocator.hpp" + +namespace ccstd { +using std::vector; + +namespace pmr { +template +using vector = std::vector>; +} +} // namespace ccstd diff --git a/cocos/base/std/hash/detail/float_functions.hpp b/cocos/base/std/hash/detail/float_functions.hpp new file mode 100644 index 0000000..26701fb --- /dev/null +++ b/cocos/base/std/hash/detail/float_functions.hpp @@ -0,0 +1,336 @@ + +// Copyright 2005-2009 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#if !defined(CCSTD_FUNCTIONAL_HASH_DETAIL_FLOAT_FUNCTIONS_HPP) +#define CCSTD_FUNCTIONAL_HASH_DETAIL_FLOAT_FUNCTIONS_HPP + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include + +// Set BOOST_HASH_CONFORMANT_FLOATS to 1 for libraries known to have +// sufficiently good floating point support to not require any +// workarounds. +// +// When set to 0, the library tries to automatically +// use the best available implementation. This normally works well, but +// breaks when ambiguities are created by odd namespacing of the functions. +// +// Note that if this is set to 0, the library should still take full +// advantage of the platform's floating point support. + +#if defined(__SGI_STL_PORT) || defined(_STLPORT_VERSION) +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif defined(__LIBCOMO__) +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif defined(__STD_RWCOMPILER_H__) || defined(_RWSTD_VER) +// Rogue Wave library: +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif defined(_LIBCPP_VERSION) +// libc++ +# define BOOST_HASH_CONFORMANT_FLOATS 1 +#elif defined(__GLIBCPP__) || defined(__GLIBCXX__) +// GNU libstdc++ 3 +# if defined(__GNUC__) && __GNUC__ >= 4 +# define BOOST_HASH_CONFORMANT_FLOATS 1 +# else +# define BOOST_HASH_CONFORMANT_FLOATS 0 +# endif +#elif defined(__STL_CONFIG_H) +// generic SGI STL +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif defined(__MSL_CPP__) +// MSL standard lib: +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif defined(__IBMCPP__) +// VACPP std lib (probably conformant for much earlier version). +# if __IBMCPP__ >= 1210 +# define BOOST_HASH_CONFORMANT_FLOATS 1 +# else +# define BOOST_HASH_CONFORMANT_FLOATS 0 +# endif +#elif defined(MSIPL_COMPILE_H) +// Modena C++ standard library +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif (defined(_YVALS) && !defined(__IBMCPP__)) || defined(_CPPLIB_VER) +// Dinkumware Library (this has to appear after any possible replacement libraries): +# if _CPPLIB_VER >= 405 +# define BOOST_HASH_CONFORMANT_FLOATS 1 +# else +# define BOOST_HASH_CONFORMANT_FLOATS 0 +# endif +#else +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#endif + +#if BOOST_HASH_CONFORMANT_FLOATS + +// The standard library is known to be compliant, so don't use the +// configuration mechanism. + +namespace ccstd { + namespace hash_detail { + template + struct call_ldexp { + typedef Float float_type; + inline Float operator()(Float x, int y) const { + return std::ldexp(x, y); + } + }; + + template + struct call_frexp { + typedef Float float_type; + inline Float operator()(Float x, int* y) const { + return std::frexp(x, y); + } + }; + + template + struct select_hash_type + { + typedef Float type; + }; + } +} + +#else // BOOST_HASH_CONFORMANT_FLOATS == 0 + +// The C++ standard requires that the C float functions are overloarded +// for float, double and long double in the std namespace, but some of the older +// library implementations don't support this. On some that don't, the C99 +// float functions (frexpf, frexpl, etc.) are available. +// +// The following tries to automatically detect which are available. + +namespace ccstd { + namespace hash_detail { + + // Returned by dummy versions of the float functions. + + struct not_found { + // Implicitly convertible to float and long double in order to avoid + // a compile error when the dummy float functions are used. + + inline operator float() const { return 0; } + inline operator long double() const { return 0; } + }; + + // A type for detecting the return type of functions. + + template struct is; + template <> struct is { char x[10]; }; + template <> struct is { char x[20]; }; + template <> struct is { char x[30]; }; + template <> struct is { char x[40]; }; + + // Used to convert the return type of a function to a type for sizeof. + + template is float_type(T); + + // call_ldexp + // + // This will get specialized for float and long double + + template struct call_ldexp + { + typedef double float_type; + + inline double operator()(double a, int b) const + { + using namespace std; + return ldexp(a, b); + } + }; + + // call_frexp + // + // This will get specialized for float and long double + + template struct call_frexp + { + typedef double float_type; + + inline double operator()(double a, int* b) const + { + using namespace std; + return frexp(a, b); + } + }; + } +} + +// A namespace for dummy functions to detect when the actual function we want +// isn't available. ldexpl, ldexpf etc. might be added tby the macros below. +// +// AFAICT these have to be outside of the boost namespace, as if they're in +// the boost namespace they'll always be preferable to any other function +// (since the arguments are built in types, ADL can't be used). + +namespace ccstd_hash_detect_float_functions { + template boost::hash_detail::not_found ldexp(Float, int); + template boost::hash_detail::not_found frexp(Float, int*); +} + +// Macros for generating specializations of call_ldexp and call_frexp. +// +// check_cpp and check_c99 check if the C++ or C99 functions are available. +// +// Then the call_* functions select an appropriate implementation. +// +// I used c99_func in a few places just to get a unique name. +// +// Important: when using 'using namespace' at namespace level, include as +// little as possible in that namespace, as Visual C++ has an odd bug which +// can cause the namespace to be imported at the global level. This seems to +// happen mainly when there's a template in the same namesapce. + +#define BOOST_HASH_CALL_FLOAT_FUNC(cpp_func, c99_func, type1, type2) \ +namespace ccstd_hash_detect_float_functions { \ + template \ + boost::hash_detail::not_found c99_func(Float, type2); \ +} \ + \ +namespace ccstd { \ + namespace hash_detail { \ + namespace c99_func##_detect { \ + using namespace std; \ + using namespace ccstd_hash_detect_float_functions; \ + \ + struct check { \ + static type1 x; \ + static type2 y; \ + BOOST_STATIC_CONSTANT(bool, cpp = \ + sizeof(float_type(cpp_func(x,y))) \ + == sizeof(is)); \ + BOOST_STATIC_CONSTANT(bool, c99 = \ + sizeof(float_type(c99_func(x,y))) \ + == sizeof(is)); \ + }; \ + } \ + \ + template \ + struct call_c99_##c99_func : \ + boost::hash_detail::call_##cpp_func {}; \ + \ + template <> \ + struct call_c99_##c99_func { \ + typedef type1 float_type; \ + \ + template \ + inline type1 operator()(type1 a, T b) const \ + { \ + using namespace std; \ + return c99_func(a, b); \ + } \ + }; \ + \ + template \ + struct call_cpp_##c99_func : \ + call_c99_##c99_func< \ + ::boost::hash_detail::c99_func##_detect::check::c99 \ + > {}; \ + \ + template <> \ + struct call_cpp_##c99_func { \ + typedef type1 float_type; \ + \ + template \ + inline type1 operator()(type1 a, T b) const \ + { \ + using namespace std; \ + return cpp_func(a, b); \ + } \ + }; \ + \ + template <> \ + struct call_##cpp_func : \ + call_cpp_##c99_func< \ + ::boost::hash_detail::c99_func##_detect::check::cpp \ + > {}; \ + } \ +} + +#define BOOST_HASH_CALL_FLOAT_MACRO(cpp_func, c99_func, type1, type2) \ +namespace ccstd { \ + namespace hash_detail { \ + \ + template <> \ + struct call_##cpp_func { \ + typedef type1 float_type; \ + inline type1 operator()(type1 x, type2 y) const { \ + return c99_func(x, y); \ + } \ + }; \ + } \ +} + +#if defined(ldexpf) +BOOST_HASH_CALL_FLOAT_MACRO(ldexp, ldexpf, float, int) +#else +BOOST_HASH_CALL_FLOAT_FUNC(ldexp, ldexpf, float, int) +#endif + +#if defined(ldexpl) +BOOST_HASH_CALL_FLOAT_MACRO(ldexp, ldexpl, long double, int) +#else +BOOST_HASH_CALL_FLOAT_FUNC(ldexp, ldexpl, long double, int) +#endif + +#if defined(frexpf) +BOOST_HASH_CALL_FLOAT_MACRO(frexp, frexpf, float, int*) +#else +BOOST_HASH_CALL_FLOAT_FUNC(frexp, frexpf, float, int*) +#endif + +#if defined(frexpl) +BOOST_HASH_CALL_FLOAT_MACRO(frexp, frexpl, long double, int*) +#else +BOOST_HASH_CALL_FLOAT_FUNC(frexp, frexpl, long double, int*) +#endif + +#undef BOOST_HASH_CALL_FLOAT_MACRO +#undef BOOST_HASH_CALL_FLOAT_FUNC + + +namespace ccstd +{ + namespace hash_detail + { + template + struct select_hash_type_impl { + typedef double type; + }; + + template <> + struct select_hash_type_impl { + typedef float type; + }; + + template <> + struct select_hash_type_impl { + typedef long double type; + }; + + + // select_hash_type + // + // If there is support for a particular floating point type, use that + // otherwise use double (there's always support for double). + + template + struct select_hash_type : select_hash_type_impl< + BOOST_DEDUCED_TYPENAME call_ldexp::float_type, + BOOST_DEDUCED_TYPENAME call_frexp::float_type + > {}; + } +} + +#endif // BOOST_HASH_CONFORMANT_FLOATS + +#endif diff --git a/cocos/base/std/hash/detail/hash_float.hpp b/cocos/base/std/hash/detail/hash_float.hpp new file mode 100644 index 0000000..0b777df --- /dev/null +++ b/cocos/base/std/hash/detail/hash_float.hpp @@ -0,0 +1,271 @@ + +// Copyright 2005-2012 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#if !defined(CCSTD_FUNCTIONAL_HASH_DETAIL_HASH_FLOAT_HEADER) +#define CCSTD_FUNCTIONAL_HASH_DETAIL_HASH_FLOAT_HEADER + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include "base/std/hash/detail/float_functions.hpp" +#include "base/std/hash/detail/limits.hpp" +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_MSVC) +#pragma warning(push) +#if BOOST_MSVC >= 1400 +#pragma warning(disable:6294) // Ill-defined for-loop: initial condition does + // not satisfy test. Loop body not executed +#endif +#endif + +// Can we use fpclassify? + +// STLport +#if defined(__SGI_STL_PORT) || defined(_STLPORT_VERSION) +#define BOOST_HASH_USE_FPCLASSIFY 0 + +// GNU libstdc++ 3 +#elif defined(__GLIBCPP__) || defined(__GLIBCXX__) +# if (defined(__USE_ISOC99) || defined(_GLIBCXX_USE_C99_MATH)) && \ + !(defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)) +# define BOOST_HASH_USE_FPCLASSIFY 1 +# else +# define BOOST_HASH_USE_FPCLASSIFY 0 +# endif + +// Everything else +#else +# define BOOST_HASH_USE_FPCLASSIFY 0 +#endif + +namespace ccstd +{ + namespace hash_detail + { + inline void hash_float_combine(ccstd::hash_t& seed, ccstd::hash_t value) + { + seed ^= value + (seed<<6) + (seed>>2); + } + + //////////////////////////////////////////////////////////////////////// + // Binary hash function + // + // Only used for floats with known iec559 floats, and certain values in + // numeric_limits + + inline ccstd::hash_t hash_binary(char* ptr, ccstd::hash_t length) + { + ccstd::hash_t seed = 0; + + if (length >= sizeof(ccstd::hash_t)) { + std::memcpy(&seed, ptr, sizeof(ccstd::hash_t)); + length -= sizeof(ccstd::hash_t); + ptr += sizeof(ccstd::hash_t); + + while(length >= sizeof(ccstd::hash_t)) { + ccstd::hash_t buffer = 0; + std::memcpy(&buffer, ptr, sizeof(ccstd::hash_t)); + hash_float_combine(seed, buffer); + length -= sizeof(ccstd::hash_t); + ptr += sizeof(ccstd::hash_t); + } + } + + if (length > 0) { + ccstd::hash_t buffer = 0; + std::memcpy(&buffer, ptr, length); + hash_float_combine(seed, buffer); + } + + return seed; + } + + template + struct enable_binary_hash + { + BOOST_STATIC_CONSTANT(bool, value = + std::numeric_limits::is_iec559 && + std::numeric_limits::digits == digits && + std::numeric_limits::radix == 2 && + std::numeric_limits::max_exponent == max_exponent); + }; + + template + inline ccstd::hash_t float_hash_impl(Float v, + BOOST_DEDUCED_TYPENAME boost::enable_if_c< + enable_binary_hash::value, + ccstd::hash_t>::type) + { + return hash_binary((char*) &v, 4); + } + + + template + inline ccstd::hash_t float_hash_impl(Float v, + BOOST_DEDUCED_TYPENAME boost::enable_if_c< + enable_binary_hash::value, + ccstd::hash_t>::type) + { + return hash_binary((char*) &v, 8); + } + + template + inline ccstd::hash_t float_hash_impl(Float v, + BOOST_DEDUCED_TYPENAME boost::enable_if_c< + enable_binary_hash::value, + ccstd::hash_t>::type) + { + return hash_binary((char*) &v, 10); + } + + template + inline ccstd::hash_t float_hash_impl(Float v, + BOOST_DEDUCED_TYPENAME boost::enable_if_c< + enable_binary_hash::value, + ccstd::hash_t>::type) + { + return hash_binary((char*) &v, 16); + } + + //////////////////////////////////////////////////////////////////////// + // Portable hash function + // + // Used as a fallback when the binary hash function isn't supported. + + template + inline ccstd::hash_t float_hash_impl2(T v) + { + boost::hash_detail::call_frexp frexp; + boost::hash_detail::call_ldexp ldexp; + + int exp = 0; + + v = frexp(v, &exp); + + // A postive value is easier to hash, so combine the + // sign with the exponent and use the absolute value. + if(v < 0) { + v = -v; + exp += limits::max_exponent - + limits::min_exponent; + } + + v = ldexp(v, limits::digits); + ccstd::hash_t seed = static_cast(v); + v -= static_cast(seed); + + // ceiling(digits(T) * log2(radix(T))/ digits(size_t)) - 1; + ccstd::hash_t const length + = (limits::digits * + boost::static_log2::radix>::value + + limits::digits - 1) + / limits::digits; + + for(ccstd::hash_t i = 0; i != length; ++i) + { + v = ldexp(v, limits::digits); + ccstd::hash_t part = static_cast(v); + v -= static_cast(part); + hash_float_combine(seed, part); + } + + hash_float_combine(seed, static_cast(exp)); + + return seed; + } + +#if !defined(BOOST_HASH_DETAIL_TEST_WITHOUT_GENERIC) + template + inline ccstd::hash_t float_hash_impl(T v, ...) + { + typedef BOOST_DEDUCED_TYPENAME select_hash_type::type type; + return float_hash_impl2(static_cast(v)); + } +#endif + } +} + +#if BOOST_HASH_USE_FPCLASSIFY + +#include + +namespace ccstd +{ + namespace hash_detail + { + template + inline ccstd::hash_t float_hash_value(T v) + { +#if defined(fpclassify) + switch (fpclassify(v)) +#elif BOOST_HASH_CONFORMANT_FLOATS + switch (std::fpclassify(v)) +#else + using namespace std; + switch (fpclassify(v)) +#endif + { + case FP_ZERO: + return 0; + case FP_INFINITE: + return (ccstd::hash_t)(v > 0 ? -1 : -2); + case FP_NAN: + return (ccstd::hash_t)(-3); + case FP_NORMAL: + case FP_SUBNORMAL: + return float_hash_impl(v, 0); + default: + BOOST_ASSERT(0); + return 0; + } + } + } +} + +#else // !BOOST_HASH_USE_FPCLASSIFY + +namespace ccstd +{ + namespace hash_detail + { + template + inline bool is_zero(T v) + { +#if !defined(__GNUC__) && !defined(__clang__) + return v == 0; +#else + // GCC's '-Wfloat-equal' will complain about comparing + // v to 0, but because it disables warnings for system + // headers it won't complain if you use std::equal_to to + // compare with 0. Resulting in this silliness: + return std::equal_to()(v, 0); +#endif + } + + template + inline ccstd::hash_t float_hash_value(T v) + { + return boost::hash_detail::is_zero(v) ? 0 : float_hash_impl(v, 0); + } + } +} + +#endif // BOOST_HASH_USE_FPCLASSIFY + +#undef BOOST_HASH_USE_FPCLASSIFY + +#if defined(BOOST_MSVC) +#pragma warning(pop) +#endif + +#endif diff --git a/cocos/base/std/hash/detail/limits.hpp b/cocos/base/std/hash/detail/limits.hpp new file mode 100644 index 0000000..a9cb7bb --- /dev/null +++ b/cocos/base/std/hash/detail/limits.hpp @@ -0,0 +1,62 @@ + +// Copyright 2005-2009 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// On some platforms std::limits gives incorrect values for long double. +// This tries to work around them. + +#if !defined(CCSTD_FUNCTIONAL_HASH_DETAIL_LIMITS_HEADER) +#define CCSTD_FUNCTIONAL_HASH_DETAIL_LIMITS_HEADER + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include + +// On OpenBSD, numeric_limits is not reliable for long doubles, but +// the macros defined in are and support long double when STLport +// doesn't. + +#if defined(__OpenBSD__) || defined(_STLP_NO_LONG_DOUBLE) +#include +#endif + +namespace ccstd +{ + namespace hash_detail + { + template + struct limits : std::numeric_limits {}; + +#if defined(__OpenBSD__) || defined(_STLP_NO_LONG_DOUBLE) + template <> + struct limits + : std::numeric_limits + { + static long double epsilon() { + return LDBL_EPSILON; + } + + static long double (max)() { + return LDBL_MAX; + } + + static long double (min)() { + return LDBL_MIN; + } + + BOOST_STATIC_CONSTANT(int, digits = LDBL_MANT_DIG); + BOOST_STATIC_CONSTANT(int, max_exponent = LDBL_MAX_EXP); + BOOST_STATIC_CONSTANT(int, min_exponent = LDBL_MIN_EXP); +#if defined(_STLP_NO_LONG_DOUBLE) + BOOST_STATIC_CONSTANT(int, radix = FLT_RADIX); +#endif + }; +#endif // __OpenBSD__ + } +} + +#endif diff --git a/cocos/base/std/hash/extensions.hpp b/cocos/base/std/hash/extensions.hpp new file mode 100644 index 0000000..9e7429f --- /dev/null +++ b/cocos/base/std/hash/extensions.hpp @@ -0,0 +1,361 @@ + +// Copyright 2005-2009 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Based on Peter Dimov's proposal +// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf +// issue 6.18. + +// This implements the extensions to the standard. +// It's undocumented, so you shouldn't use it.... + +#if !defined(CCSTD_FUNCTIONAL_HASH_EXTENSIONS_HPP) +#define CCSTD_FUNCTIONAL_HASH_EXTENSIONS_HPP + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include "base/std/hash/hash.h" +#include +#include +#include + +#if !defined(BOOST_NO_CXX11_HDR_ARRAY) +# include +#endif + +#if !defined(BOOST_NO_CXX11_HDR_TUPLE) +# include +#endif + +#include + +#if defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) +#include +#endif + +namespace ccstd +{ + template + hash_t hash_value(std::pair const&); + template + hash_t hash_value(std::vector const&); + template + hash_t hash_value(std::list const& v); + template + hash_t hash_value(std::deque const& v); + template + hash_t hash_value(std::set const& v); + template + hash_t hash_value(std::multiset const& v); + template + hash_t hash_value(std::map const& v); + template + hash_t hash_value(std::multimap const& v); + + template + hash_t hash_value(std::complex const&); + + template + hash_t hash_value(std::pair const& v) + { + hash_t seed = 0; + ccstd::hash_combine(seed, v.first); + ccstd::hash_combine(seed, v.second); + return seed; + } + + template + hash_t hash_value(std::vector const& v) + { + return ccstd::hash_range(v.begin(), v.end()); + } + + template + hash_t hash_value(std::list const& v) + { + return ccstd::hash_range(v.begin(), v.end()); + } + + template + hash_t hash_value(std::deque const& v) + { + return ccstd::hash_range(v.begin(), v.end()); + } + + template + hash_t hash_value(std::set const& v) + { + return ccstd::hash_range(v.begin(), v.end()); + } + + template + hash_t hash_value(std::multiset const& v) + { + return ccstd::hash_range(v.begin(), v.end()); + } + + template + hash_t hash_value(std::map const& v) + { + return ccstd::hash_range(v.begin(), v.end()); + } + + template + hash_t hash_value(std::multimap const& v) + { + return ccstd::hash_range(v.begin(), v.end()); + } + + template + hash_t hash_value(std::complex const& v) + { + ccstd::hash hasher; + hash_t seed = hasher(v.imag()); + seed ^= hasher(v.real()) + (seed<<6) + (seed>>2); + return seed; + } + +#if !defined(BOOST_NO_CXX11_HDR_ARRAY) + template + hash_t hash_value(std::array const& v) + { + return ccstd::hash_range(v.begin(), v.end()); + } +#endif + +#if !defined(BOOST_NO_CXX11_HDR_TUPLE) + namespace hash_detail { + template + inline typename boost::enable_if_c<(I == std::tuple_size::value), + void>::type + hash_combine_tuple(hash_t&, T const&) + { + } + + template + inline typename boost::enable_if_c<(I < std::tuple_size::value), + void>::type + hash_combine_tuple(hash_t& seed, T const& v) + { + ccstd::hash_combine(seed, std::get(v)); + ccstd::hash_detail::hash_combine_tuple(seed, v); + } + + template + inline hash_t hash_tuple(T const& v) + { + hash_t seed = 0; + ccstd::hash_detail::hash_combine_tuple<0>(seed, v); + return seed; + } + } + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } +#else + + inline hash_t hash_value(std::tuple<> const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + + template + inline hash_t hash_value(std::tuple const& v) + { + return ccstd::hash_detail::hash_tuple(v); + } + +#endif + +#endif + +#if !defined(BOOST_NO_CXX11_SMART_PTR) + template + inline hash_t hash_value(std::shared_ptr const& x) { + return ccstd::hash_value(x.get()); + } + + template + inline hash_t hash_value(std::unique_ptr const& x) { + return ccstd::hash_value(x.get()); + } +#endif + + // + // call_hash_impl + // + + // On compilers without function template ordering, this deals with arrays. + +#if defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + namespace hash_detail + { + template + struct call_hash_impl + { + template + struct inner + { + static hash_t call(T const& v) + { + using namespace ccstd; + return hash_value(v); + } + }; + }; + + template <> + struct call_hash_impl + { + template + struct inner + { + static hash_t call(Array const& v) + { + const int size = sizeof(v) / sizeof(*v); + return ccstd::hash_range(v, v + size); + } + }; + }; + + template + struct call_hash + : public call_hash_impl::value> + ::BOOST_NESTED_TEMPLATE inner + { + }; + } +#endif // BOOST_NO_FUNCTION_TEMPLATE_ORDERING + + // + // ccstd::hash + // + + +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) + + template struct hash + : ccstd::hash_detail::hash_base + { +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + hash_t operator()(T const& val) const + { + return hash_value(val); + } +#else + hash_t operator()(T const& val) const + { + return hash_detail::call_hash::call(val); + } +#endif + }; + +#if BOOST_WORKAROUND(__DMC__, <= 0x848) + template struct hash + : ccstd::hash_detail::hash_base + { + hash_t operator()(const T* val) const + { + return ccstd::hash_range(val, val+n); + } + }; +#endif + +#else // BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION + + // On compilers without partial specialization, ccstd::hash + // has already been declared to deal with pointers, so just + // need to supply the non-pointer version of hash_impl. + + namespace hash_detail + { + template + struct hash_impl; + + template <> + struct hash_impl + { + template + struct inner + : ccstd::hash_detail::hash_base + { +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + hash_t operator()(T const& val) const + { + return hash_value(val); + } +#else + hash_t operator()(T const& val) const + { + return hash_detail::call_hash::call(val); + } +#endif + }; + }; + } +#endif // BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION +} + +#endif diff --git a/cocos/base/std/hash/hash.h b/cocos/base/std/hash/hash.h new file mode 100644 index 0000000..9d24611 --- /dev/null +++ b/cocos/base/std/hash/hash.h @@ -0,0 +1,783 @@ + +// Copyright 2005-2014 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Based on Peter Dimov's proposal +// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf +// issue 6.18. +// +// This also contains public domain code from MurmurHash. From the +// MurmurHash header: + +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +#if !defined(CCSTD_FUNCTIONAL_HASH_HASH_HPP) +#define CCSTD_FUNCTIONAL_HASH_HASH_HPP + +#include "boost/container_hash/hash.hpp" + +#include "base/std/hash/hash_fwd.hpp" +#include +#include +#include "base/std/hash/detail/hash_float.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) +#include +#endif + +#if !defined(BOOST_NO_CXX11_HDR_TYPEINDEX) +#include +#endif + +#if !defined(BOOST_NO_CXX11_HDR_SYSTEM_ERROR) +#include +#endif + +#if defined(BOOST_MSVC) +#pragma warning(push) + +#if BOOST_MSVC >= 1400 +#pragma warning(disable:6295) // Ill-defined for-loop : 'unsigned int' values + // are always of range '0' to '4294967295'. + // Loop executes infinitely. +#endif + +#endif + +#if BOOST_WORKAROUND(__GNUC__, < 3) \ + && !defined(__SGI_STL_PORT) && !defined(_STLPORT_VERSION) +#define CCSTD_HASH_CHAR_TRAITS string_char_traits +#else +#define CCSTD_HASH_CHAR_TRAITS char_traits +#endif + +#if defined(_MSC_VER) +# define CCSTD_FUNCTIONAL_HASH_ROTL32(x, r) _rotl(x,r) +#else +# define CCSTD_FUNCTIONAL_HASH_ROTL32(x, r) (x << r) | (x >> (32 - r)) +#endif + +// Detect whether standard library has C++17 headers + +#if !defined(CCSTD_HASH_CXX17) +# if defined(BOOST_MSVC) +# if defined(_HAS_CXX17) && _HAS_CXX17 +# define CCSTD_HASH_CXX17 1 +# endif +# elif defined(__cplusplus) && __cplusplus >= 201703 +# define CCSTD_HASH_CXX17 1 +# endif +#endif + +#if !defined(CCSTD_HASH_CXX17) +# define CCSTD_HASH_CXX17 0 +#endif + +#if CCSTD_HASH_CXX17 && defined(__has_include) +# if !defined(CCSTD_HASH_HAS_STRING_VIEW) && __has_include() +# define CCSTD_HASH_HAS_STRING_VIEW 1 +# endif +# if !defined(CCSTD_HASH_HAS_OPTIONAL) && __has_include() +# define CCSTD_HASH_HAS_OPTIONAL 1 +# endif +# if !defined(CCSTD_HASH_HAS_VARIANT) && __has_include() +# define CCSTD_HASH_HAS_VARIANT 1 +# endif +#endif + +#if !defined(CCSTD_HASH_HAS_STRING_VIEW) +# define CCSTD_HASH_HAS_STRING_VIEW 0 +#endif + +#if !defined(CCSTD_HASH_HAS_OPTIONAL) +# define CCSTD_HASH_HAS_OPTIONAL 0 +#endif + +#if !defined(CCSTD_HASH_HAS_VARIANT) +# define CCSTD_HASH_HAS_VARIANT 0 +#endif + +#if CCSTD_HASH_HAS_STRING_VIEW +# include +#endif + +#if CCSTD_HASH_HAS_OPTIONAL +# include +#endif + +#if CCSTD_HASH_HAS_VARIANT +# include +#endif + +namespace ccstd +{ + namespace hash_detail + { +#if defined(BOOST_NO_CXX98_FUNCTION_BASE) + template + struct hash_base + { + typedef T argument_type; + typedef hash_t result_type; + }; +#else + template + struct hash_base : std::unary_function {}; +#endif + + struct enable_hash_value { typedef hash_t type; }; + + template struct basic_numbers {}; + template struct long_numbers; + template struct ulong_numbers; + template struct float_numbers {}; + + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; + +#if !defined(BOOST_NO_INTRINSIC_WCHAR_T) + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; +#endif + +#if !defined(BOOST_NO_CXX11_CHAR16_T) + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; +#endif + +#if !defined(BOOST_NO_CXX11_CHAR32_T) + template <> struct basic_numbers : + ccstd::hash_detail::enable_hash_value {}; +#endif + + // long_numbers is defined like this to allow for separate + // specialization for long_long and int128_type, in case + // they conflict. + template struct long_numbers2 {}; + template struct ulong_numbers2 {}; + template struct long_numbers : long_numbers2 {}; + template struct ulong_numbers : ulong_numbers2 {}; + +#if !defined(BOOST_NO_LONG_LONG) + template <> struct long_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct ulong_numbers : + ccstd::hash_detail::enable_hash_value {}; +#endif + +#if defined(BOOST_HAS_INT128) + template <> struct long_numbers2 : + ccstd::hash_detail::enable_hash_value {}; + template <> struct ulong_numbers2 : + ccstd::hash_detail::enable_hash_value {}; +#endif + + template <> struct float_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct float_numbers : + ccstd::hash_detail::enable_hash_value {}; + template <> struct float_numbers : + ccstd::hash_detail::enable_hash_value {}; + } + + template + typename ccstd::hash_detail::basic_numbers::type hash_value(T); + template + typename ccstd::hash_detail::long_numbers::type hash_value(T); + template + typename ccstd::hash_detail::ulong_numbers::type hash_value(T); + + template + typename boost::enable_if, hash_t>::type + hash_value(T); + +#if !BOOST_WORKAROUND(__DMC__, <= 0x848) + template hash_t hash_value(T* const&); +#else + template hash_t hash_value(T*); +#endif + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + template< class T, unsigned N > + hash_t hash_value(const T (&x)[N]); + + template< class T, unsigned N > + hash_t hash_value(T (&x)[N]); +#endif + + template + hash_t hash_value( + std::basic_string, A> const&); + +#if CCSTD_HASH_HAS_STRING_VIEW + template + hash_t hash_value( + std::basic_string_view > const&); +#endif + + template + typename ccstd::hash_detail::float_numbers::type hash_value(T); + +#if CCSTD_HASH_HAS_OPTIONAL + template + hash_t hash_value(std::optional const&); +#endif + +#if CCSTD_HASH_HAS_VARIANT + hash_t hash_value(std::monostate); + template + hash_t hash_value(std::variant const&); +#endif + +#if !defined(BOOST_NO_CXX11_HDR_TYPEINDEX) + hash_t hash_value(std::type_index); +#endif + +#if !defined(BOOST_NO_CXX11_HDR_SYSTEM_ERROR) + hash_t hash_value(std::error_code const&); + hash_t hash_value(std::error_condition const&); +#endif + + // Implementation + + namespace hash_detail + { + template + inline hash_t hash_value_signed(T val) + { + const unsigned int size_t_bits = std::numeric_limits::digits; + // ceiling(std::numeric_limits::digits / size_t_bits) - 1 + const int length = (std::numeric_limits::digits - 1) + / static_cast(size_t_bits); + + hash_t seed = 0; + T positive = val < 0 ? -1 - val : val; + + // Hopefully, this loop can be unrolled. + for(unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits) + { + seed ^= (hash_t) (positive >> i) + (seed<<6) + (seed>>2); + } + seed ^= (hash_t) val + (seed<<6) + (seed>>2); + + return seed; + } + + template + inline hash_t hash_value_unsigned(T val) + { + const unsigned int size_t_bits = std::numeric_limits::digits; + // ceiling(std::numeric_limits::digits / size_t_bits) - 1 + const int length = (std::numeric_limits::digits - 1) + / static_cast(size_t_bits); + + hash_t seed = 0; + + // Hopefully, this loop can be unrolled. + for(unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits) + { + seed ^= (hash_t) (val >> i) + (seed<<6) + (seed>>2); + } + seed ^= (hash_t) val + (seed<<6) + (seed>>2); + + return seed; + } + + template struct hash_combine_impl + { + template + inline static SizeT fn(SizeT seed, SizeT value) + { + seed ^= value + 0x9e3779b9 + (seed<<6) + (seed>>2); + return seed; + } + }; + + template<> struct hash_combine_impl<32> + { + inline static std::uint32_t fn(std::uint32_t h1, std::uint32_t k1) + { + const std::uint32_t c1 = 0xcc9e2d51; + const std::uint32_t c2 = 0x1b873593; + + k1 *= c1; + k1 = CCSTD_FUNCTIONAL_HASH_ROTL32(k1,15); + k1 *= c2; + + h1 ^= k1; + h1 = CCSTD_FUNCTIONAL_HASH_ROTL32(h1,13); + h1 = h1*5+0xe6546b64; + + return h1; + } + }; + + template<> struct hash_combine_impl<64> + { + inline static std::uint64_t fn(std::uint64_t h, std::uint64_t k) + { + const std::uint64_t m = (std::uint64_t(0xc6a4a793) << 32) + 0x5bd1e995; + const int r = 47; + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + + // Completely arbitrary number, to prevent 0's + // from hashing to 0. + h += 0xe6546b64; + + return h; + } + }; + } + + template + typename ccstd::hash_detail::basic_numbers::type hash_value(T v) + { + return static_cast(v); + } + + template + typename ccstd::hash_detail::long_numbers::type hash_value(T v) + { + return hash_detail::hash_value_signed(v); + } + + template + typename ccstd::hash_detail::ulong_numbers::type hash_value(T v) + { + return hash_detail::hash_value_unsigned(v); + } + + template + typename boost::enable_if, hash_t>::type + hash_value(T v) + { + return static_cast(v); + } + + // Implementation by Alberto Barbati and Dave Harris. +#if !BOOST_WORKAROUND(__DMC__, <= 0x848) + template hash_t hash_value(T* const& v) +#else + template hash_t hash_value(T* v) +#endif + { +#if defined(__VMS) && __INITIAL_POINTER_SIZE == 64 + // for some reason ptrdiff_t on OpenVMS compiler with + // 64 bit is not 64 bit !!! + hash_t x = static_cast( + reinterpret_cast(v)); +#else + hash_t x = static_cast( + reinterpret_cast(v)); +#endif + return x + (x >> 3); + } + +#if defined(BOOST_MSVC) +#pragma warning(push) +#if BOOST_MSVC <= 1400 +#pragma warning(disable:4267) // 'argument' : conversion from 'size_t' to + // 'unsigned int', possible loss of data + // A misguided attempt to detect 64-bit + // incompatability. +#endif +#endif + + template + inline void hash_combine(hash_t& seed, T const& v) + { + ccstd::hash hasher; + seed = ccstd::hash_detail::hash_combine_impl::fn(seed, hasher(v)); + } + +#if defined(BOOST_MSVC) +#pragma warning(pop) +#endif + + template + inline hash_t hash_range(It first, It last) + { + hash_t seed = 0; + + for(; first != last; ++first) + { + hash_combine::value_type>(seed, *first); + } + + return seed; + } + + template + inline void hash_range(hash_t& seed, It first, It last) + { + for(; first != last; ++first) + { + hash_combine::value_type>(seed, *first); + } + } + +#if BOOST_WORKAROUND(BOOST_BORLANDC, BOOST_TESTED_AT(0x551)) + template + inline hash_t hash_range(T* first, T* last) + { + hash_t seed = 0; + + for(; first != last; ++first) + { + ccstd::hash hasher; + seed ^= hasher(*first) + 0x9e3779b9 + (seed<<6) + (seed>>2); + } + + return seed; + } + + template + inline void hash_range(hash_t& seed, T* first, T* last) + { + for(; first != last; ++first) + { + ccstd::hash hasher; + seed ^= hasher(*first) + 0x9e3779b9 + (seed<<6) + (seed>>2); + } + } +#endif + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + template< class T, unsigned N > + inline hash_t hash_value(const T (&x)[N]) + { + return hash_range(x, x + N); + } + + template< class T, unsigned N > + inline hash_t hash_value(T (&x)[N]) + { + return hash_range(x, x + N); + } +#endif + + template + inline hash_t hash_value( + std::basic_string, A> const& v) + { + return hash_range(v.begin(), v.end()); + } + +#if CCSTD_HASH_HAS_STRING_VIEW + template + inline hash_t hash_value( + std::basic_string_view > const& v) + { + return hash_range(v.begin(), v.end()); + } +#endif + + template + typename ccstd::hash_detail::float_numbers::type hash_value(T v) + { + return ccstd::hash_detail::float_hash_value(v); + } + +#if CCSTD_HASH_HAS_OPTIONAL + template + inline hash_t hash_value(std::optional const& v) { + if (!v) { + // Arbitray value for empty optional. + return 0x12345678; + } else { + ccstd::hash hf; + return hf(*v); + } + } +#endif + +#if CCSTD_HASH_HAS_VARIANT + inline hash_t hash_value(std::monostate) { + return 0x87654321; + } + + template + inline hash_t hash_value(std::variant const& v) { + hash_t seed = 0; + hash_combine(seed, v.index()); + std::visit([&seed](auto&& x) { hash_combine(seed, x); }, v); + return seed; + } +#endif + + +#if !defined(BOOST_NO_CXX11_HDR_TYPEINDEX) + inline hash_t hash_value(std::type_index v) + { + size_t hash = v.hash_code(); + +#ifndef INTPTR_MAX +#error "no INTPTR_MAX" +#endif + +#ifndef INT64_MAX +#error "no INT64_MAX" +#endif + +#if INTPTR_MAX == INT64_MAX + hash = (hash >> 32) ^ (hash & 0xFFFFFFFF); +#endif + return static_cast(hash); + } +#endif + +#if !defined(BOOST_NO_CXX11_HDR_SYSTEM_ERROR) + inline hash_t hash_value(std::error_code const& v) { + hash_t seed = 0; + hash_combine(seed, v.value()); + hash_combine(seed, &v.category()); + return seed; + } + + inline hash_t hash_value(std::error_condition const& v) { + hash_t seed = 0; + hash_combine(seed, v.value()); + hash_combine(seed, &v.category()); + return seed; + } +#endif + + // + // ccstd::hash + // + + // Define the specializations required by the standard. The general purpose + // ccstd::hash is defined later in extensions.hpp if + // CCSTD_HASH_NO_EXTENSIONS is not defined. + + // CCSTD_HASH_SPECIALIZE - define a specialization for a type which is + // passed by copy. + // + // CCSTD_HASH_SPECIALIZE_REF - define a specialization for a type which is + // passed by const reference. + // + // These are undefined later. + +#define CCSTD_HASH_SPECIALIZE(type) \ + template <> struct hash \ + : public ccstd::hash_detail::hash_base \ + { \ + hash_t operator()(type v) const \ + { \ + return ccstd::hash_value(v); \ + } \ + }; + +#define CCSTD_HASH_SPECIALIZE_REF(type) \ + template <> struct hash \ + : public ccstd::hash_detail::hash_base \ + { \ + hash_t operator()(type const& v) const \ + { \ + return ccstd::hash_value(v); \ + } \ + }; + +#define CCSTD_HASH_SPECIALIZE_TEMPLATE_REF(type) \ + struct hash \ + : public ccstd::hash_detail::hash_base \ + { \ + hash_t operator()(type const& v) const \ + { \ + return ccstd::hash_value(v); \ + } \ + }; + + CCSTD_HASH_SPECIALIZE(bool) + CCSTD_HASH_SPECIALIZE(char) + CCSTD_HASH_SPECIALIZE(signed char) + CCSTD_HASH_SPECIALIZE(unsigned char) +#if !defined(BOOST_NO_INTRINSIC_WCHAR_T) + CCSTD_HASH_SPECIALIZE(wchar_t) +#endif +#if !defined(BOOST_NO_CXX11_CHAR16_T) + CCSTD_HASH_SPECIALIZE(char16_t) +#endif +#if !defined(BOOST_NO_CXX11_CHAR32_T) + CCSTD_HASH_SPECIALIZE(char32_t) +#endif + CCSTD_HASH_SPECIALIZE(short) + CCSTD_HASH_SPECIALIZE(unsigned short) + CCSTD_HASH_SPECIALIZE(int) + CCSTD_HASH_SPECIALIZE(unsigned int) + CCSTD_HASH_SPECIALIZE(long) + CCSTD_HASH_SPECIALIZE(unsigned long) + + CCSTD_HASH_SPECIALIZE(float) + CCSTD_HASH_SPECIALIZE(double) + CCSTD_HASH_SPECIALIZE(long double) + + CCSTD_HASH_SPECIALIZE_REF(std::string) +#if !defined(BOOST_NO_STD_WSTRING) && !defined(BOOST_NO_INTRINSIC_WCHAR_T) + CCSTD_HASH_SPECIALIZE_REF(std::wstring) +#endif +#if !defined(BOOST_NO_CXX11_CHAR16_T) + CCSTD_HASH_SPECIALIZE_REF(std::basic_string) +#endif +#if !defined(BOOST_NO_CXX11_CHAR32_T) + CCSTD_HASH_SPECIALIZE_REF(std::basic_string) +#endif + +#if CCSTD_HASH_HAS_STRING_VIEW + CCSTD_HASH_SPECIALIZE_REF(std::string_view) +# if !defined(BOOST_NO_STD_WSTRING) && !defined(BOOST_NO_INTRINSIC_WCHAR_T) + CCSTD_HASH_SPECIALIZE_REF(std::wstring_view) +# endif +# if !defined(BOOST_NO_CXX11_CHAR16_T) + CCSTD_HASH_SPECIALIZE_REF(std::basic_string_view) +# endif +# if !defined(BOOST_NO_CXX11_CHAR32_T) + CCSTD_HASH_SPECIALIZE_REF(std::basic_string_view) +# endif +#endif + +#if !defined(BOOST_NO_LONG_LONG) + CCSTD_HASH_SPECIALIZE(boost::long_long_type) + CCSTD_HASH_SPECIALIZE(boost::ulong_long_type) +#endif + +#if defined(BOOST_HAS_INT128) + CCSTD_HASH_SPECIALIZE(boost::int128_type) + CCSTD_HASH_SPECIALIZE(boost::uint128_type) +#endif + +#if CCSTD_HASH_HAS_OPTIONAL + template + CCSTD_HASH_SPECIALIZE_TEMPLATE_REF(std::optional) +#endif + +#if !defined(CCSTD_HASH_HAS_VARIANT) + template + CCSTD_HASH_SPECIALIZE_TEMPLATE_REF(std::variant) + CCSTD_HASH_SPECIALIZE(std::monostate) +#endif + +#if !defined(BOOST_NO_CXX11_HDR_TYPEINDEX) + CCSTD_HASH_SPECIALIZE(std::type_index) +#endif + +#undef CCSTD_HASH_SPECIALIZE +#undef CCSTD_HASH_SPECIALIZE_REF +#undef CCSTD_HASH_SPECIALIZE_TEMPLATE_REF + +// Specializing ccstd::hash for pointers. + +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) + + template + struct hash + : public ccstd::hash_detail::hash_base + { + hash_t operator()(T* v) const + { +#if !BOOST_WORKAROUND(__SUNPRO_CC, <= 0x590) + return ccstd::hash_value(v); +#else + hash_t x = static_cast( + reinterpret_cast(v)); + + return x + (x >> 3); +#endif + } + }; + +#else + + // For compilers without partial specialization, we define a + // ccstd::hash for all remaining types. But hash_impl is only defined + // for pointers in 'extensions.hpp' - so when CCSTD_HASH_NO_EXTENSIONS + // is defined there will still be a compile error for types not supported + // in the standard. + + namespace hash_detail + { + template + struct hash_impl; + + template <> + struct hash_impl + { + template + struct inner + : public ccstd::hash_detail::hash_base + { + hash_t operator()(T val) const + { +#if !BOOST_WORKAROUND(__SUNPRO_CC, <= 590) + return ccstd::hash_value(val); +#else + hash_t x = static_cast( + reinterpret_cast(val)); + + return x + (x >> 3); +#endif + } + }; + }; + } + + template struct hash + : public ccstd::hash_detail::hash_impl::value> + ::BOOST_NESTED_TEMPLATE inner + { + }; + +#endif +} + +#undef CCSTD_HASH_CHAR_TRAITS +#undef CCSTD_FUNCTIONAL_HASH_ROTL32 + +#if defined(BOOST_MSVC) +#pragma warning(pop) +#endif + +#endif // CCSTD_FUNCTIONAL_HASH_HASH_HPP + +// Include this outside of the include guards in case the file is included +// twice - once with CCSTD_HASH_NO_EXTENSIONS defined, and then with it +// undefined. + +#if !defined(CCSTD_HASH_NO_EXTENSIONS) \ + && !defined(CCSTD_FUNCTIONAL_HASH_EXTENSIONS_HPP) +#include "base/std/hash/extensions.hpp" +#endif diff --git a/cocos/base/std/hash/hash_fwd.hpp b/cocos/base/std/hash/hash_fwd.hpp new file mode 100644 index 0000000..f4e4fe3 --- /dev/null +++ b/cocos/base/std/hash/hash_fwd.hpp @@ -0,0 +1,38 @@ + +// Copyright 2005-2009 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Based on Peter Dimov's proposal +// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf +// issue 6.18. + +#if !defined(CCSTD_FUNCTIONAL_HASH_FWD_HPP) +#define CCSTD_FUNCTIONAL_HASH_FWD_HPP + +#include +#include +#include + +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +namespace ccstd +{ + using hash_t = std::uint32_t; + + template struct hash; + + template void hash_combine(hash_t& seed, T const& v); + + template hash_t hash_range(It, It); + template void hash_range(hash_t&, It, It); + +#if BOOST_WORKAROUND(BOOST_BORLANDC, BOOST_TESTED_AT(0x551)) + template inline hash_t hash_range(T*, T*); + template inline void hash_range(hash_t&, T*, T*); +#endif +} + +#endif diff --git a/cocos/base/std/optional.h b/cocos/base/std/optional.h new file mode 100644 index 0000000..4f14586 --- /dev/null +++ b/cocos/base/std/optional.h @@ -0,0 +1,52 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#ifdef USE_CXX_17 + + #include + +namespace ccstd { + +using std::nullopt; +using std::nullopt_t; +using std::optional; + +}; // namespace ccstd +#else + #include "base/std/container/string.h" + #include "boost/none.hpp" + #include "boost/optional.hpp" + +namespace ccstd { + +using boost::optional; +using nullopt_t = boost::none_t; + +const nullopt_t nullopt((boost::none_t::init_tag())); // NOLINT // use std style + +}; // namespace ccstd + +#endif diff --git a/cocos/base/std/variant.h b/cocos/base/std/variant.h new file mode 100644 index 0000000..ef28fbf --- /dev/null +++ b/cocos/base/std/variant.h @@ -0,0 +1,66 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#ifdef USE_CXX_17 + + #include + +namespace ccstd { + +using std::get; +using std::get_if; +using std::holds_alternative; +using std::in_place_index; +using std::monostate; +using std::variant; +using std::variant_alternative; +using std::variant_alternative_t; +using std::variant_size; +using std::variant_size_v; +using std::visit; + +}; // namespace ccstd + +#else + + #include "boost/variant2/variant.hpp" + +namespace ccstd { + +using boost::variant2::get; +using boost::variant2::get_if; +using boost::variant2::holds_alternative; +using boost::variant2::in_place_index; +using boost::variant2::monostate; +using boost::variant2::variant; +using boost::variant2::variant_alternative; +using boost::variant2::variant_alternative_t; +using boost::variant2::variant_size; +using boost::variant2::variant_size_v; +using boost::variant2::visit; + +}; // namespace ccstd +#endif diff --git a/cocos/base/threading/AutoReleasePool-apple.mm b/cocos/base/threading/AutoReleasePool-apple.mm new file mode 100644 index 0000000..f909684 --- /dev/null +++ b/cocos/base/threading/AutoReleasePool-apple.mm @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2020-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "AutoReleasePool.h" +#import + +namespace cc { + +AutoReleasePool::AutoReleasePool() { + _nsReleasePool = [[NSAutoreleasePool alloc] init]; +} + +AutoReleasePool::~AutoReleasePool() { + drain(); +} + +void AutoReleasePool::drain() { + if (_nsReleasePool) { + [static_cast(_nsReleasePool) drain]; + _nsReleasePool = nullptr; + } +} + +} // namespace cc diff --git a/cocos/base/threading/AutoReleasePool.cpp b/cocos/base/threading/AutoReleasePool.cpp new file mode 100644 index 0000000..cded966 --- /dev/null +++ b/cocos/base/threading/AutoReleasePool.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "AutoReleasePool.h" + +namespace cc { + +AutoReleasePool::AutoReleasePool() = default; +AutoReleasePool::~AutoReleasePool() = default; + +void AutoReleasePool::drain() { +} + +} // namespace cc diff --git a/cocos/base/threading/AutoReleasePool.h b/cocos/base/threading/AutoReleasePool.h new file mode 100644 index 0000000..6086333 --- /dev/null +++ b/cocos/base/threading/AutoReleasePool.h @@ -0,0 +1,44 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace cc { + +class AutoReleasePool final { +public: + AutoReleasePool(); + AutoReleasePool(const AutoReleasePool &) = delete; + AutoReleasePool(AutoReleasePool &&) = delete; + ~AutoReleasePool(); + AutoReleasePool &operator=(const AutoReleasePool &) = delete; + AutoReleasePool &operator=(AutoReleasePool &&) = delete; + + void drain(); + +private: + void *_nsReleasePool{nullptr}; +}; + +} // namespace cc diff --git a/cocos/base/threading/ConditionVariable.cpp b/cocos/base/threading/ConditionVariable.cpp new file mode 100644 index 0000000..b34e93b --- /dev/null +++ b/cocos/base/threading/ConditionVariable.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "ConditionVariable.h" + +namespace cc { + +void ConditionVariable::wait() noexcept { + std::unique_lock lock(_mutex); + _condVar.wait(lock); +} + +void ConditionVariable::signal() noexcept { + std::lock_guard lock(_mutex); + _condVar.notify_one(); +} + +void ConditionVariable::signalAll() noexcept { + std::lock_guard lock(_mutex); + _condVar.notify_all(); +} + +} // namespace cc diff --git a/cocos/base/threading/ConditionVariable.h b/cocos/base/threading/ConditionVariable.h new file mode 100644 index 0000000..4be0504 --- /dev/null +++ b/cocos/base/threading/ConditionVariable.h @@ -0,0 +1,54 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +namespace cc { + +class ConditionVariable final { +public: + void wait() noexcept; + template + void wait(Function &&func, Args &&...args) noexcept; + void signal() noexcept; + void signalAll() noexcept; + +private: + std::mutex _mutex; + std::condition_variable _condVar; +}; + +// DO NOT MANIPULATE ANY SYCHRONIZATION PRIMITIVES INSIDE THE CALLBACK +template +void ConditionVariable::wait(Function &&func, Args &&...args) noexcept { + std::unique_lock lock(_mutex); + _condVar.wait(lock, std::bind(std::forward(func), std::forward(args)...)); +} + +} // namespace cc diff --git a/cocos/base/threading/Event.h b/cocos/base/threading/Event.h new file mode 100644 index 0000000..eee27bb --- /dev/null +++ b/cocos/base/threading/Event.h @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "ConditionVariable.h" +#include "Semaphore.h" + +namespace cc { + +template +class Event final { +public: + inline void wait() noexcept { _syncObject.wait(); } + inline void signal() noexcept { _syncObject.signal(); }; + inline void signalAll() noexcept { _syncObject.signalAll(); } + +private: + T _syncObject{}; +}; + +using EventCV = Event; +using EventSem = Event; + +} // namespace cc diff --git a/cocos/base/threading/MessageQueue.cpp b/cocos/base/threading/MessageQueue.cpp new file mode 100644 index 0000000..febebe9 --- /dev/null +++ b/cocos/base/threading/MessageQueue.cpp @@ -0,0 +1,321 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "MessageQueue.h" +#include "AutoReleasePool.h" +#include "base/Utils.h" + +namespace cc { + +namespace { +uint32_t constexpr MEMORY_CHUNK_POOL_CAPACITY = 64; +uint32_t constexpr SWITCH_CHUNK_MEMORY_REQUIREMENT = sizeof(MemoryChunkSwitchMessage) + utils::ALIGN_TO; +} // namespace + +MessageQueue::MemoryAllocator &MessageQueue::MemoryAllocator::getInstance() noexcept { + static MessageQueue::MemoryAllocator instance; + return instance; +} + +uint8_t *MessageQueue::MemoryAllocator::request() noexcept { + uint8_t *newChunk = nullptr; + + if (_chunkPool.try_dequeue(newChunk)) { + _chunkCount.fetch_sub(1, std::memory_order_acq_rel); + } else { + newChunk = memoryAllocateForMultiThread(MEMORY_CHUNK_SIZE); + } + + return newChunk; +} + +void MessageQueue::MemoryAllocator::recycle(uint8_t *const chunk, bool const freeByUser) noexcept { + if (freeByUser) { + _chunkFreeQueue.enqueue(chunk); + } else { + free(chunk); + } +} + +void MessageQueue::MemoryAllocator::freeByUser(MessageQueue *const mainMessageQueue) noexcept { + auto *queue = &_chunkFreeQueue; + + ENQUEUE_MESSAGE_1( + mainMessageQueue, FreeChunksInFreeQueue, + queue, queue, + { + uint8_t *chunk = nullptr; + + while (queue->try_dequeue(chunk)) { + MessageQueue::MemoryAllocator::getInstance().free(chunk); + } + }); + + mainMessageQueue->kick(); +} + +MessageQueue::MemoryAllocator::~MemoryAllocator() noexcept { + destroy(); +} + +void MessageQueue::MemoryAllocator::destroy() noexcept { + uint8_t *chunk = nullptr; + if (_chunkPool.try_dequeue(chunk)) { + ::free(chunk); + _chunkCount.fetch_sub(1, std::memory_order_acq_rel); + } +} + +void MessageQueue::MemoryAllocator::free(uint8_t *const chunk) noexcept { + if (_chunkCount.load(std::memory_order_acquire) >= MEMORY_CHUNK_POOL_CAPACITY) { + memoryFreeForMultiThread(chunk); + } else { + _chunkPool.enqueue(chunk); + _chunkCount.fetch_add(1, std::memory_order_acq_rel); + } +} + +MessageQueue::MessageQueue() { + uint8_t *const chunk = MemoryAllocator::getInstance().request(); + + _writer.currentMemoryChunk = chunk; + _reader.currentMemoryChunk = chunk; + + // sentinel node will not be executed + Message *const msg = allocate(1); + pushMessages(); + pullMessages(); + _reader.lastMessage = msg; + --_reader.newMessageCount; +} + +void MessageQueue::kick() noexcept { + pushMessages(); + + std::lock_guard lock(_mutex); + _condVar.notify_all(); +} + +void MessageQueue::kickAndWait() noexcept { + EventSem event; + EventSem *const pEvent = &event; + + ENQUEUE_MESSAGE_1(this, WaitUntilFinish, + pEvent, pEvent, + { + pEvent->signal(); + }); + + kick(); + event.wait(); +} + +void MessageQueue::runConsumerThread() noexcept { + if (_immediateMode || _workerAttached) return; + + _reader.terminateConsumerThread = false; + _reader.flushingFinished = false; + + _consumerThread = ccnew std::thread(&MessageQueue::consumerThreadLoop, this); + _workerAttached = true; +} + +void MessageQueue::terminateConsumerThread() noexcept { + if (_immediateMode || !_workerAttached) return; + + EventSem event; + EventSem *const pEvent = &event; + + ReaderContext *const pR = &_reader; + + ccnew_placement(allocate(1)) TerminateConsumerThreadMessage(pEvent, pR); + + kick(); + event.wait(); + + if (_consumerThread != nullptr) { + if (_consumerThread->joinable()) { + _consumerThread->join(); + } + } + CC_SAFE_DELETE(_consumerThread); +} + +void MessageQueue::finishWriting() noexcept { + if (!_immediateMode) { + bool *const flushingFinished = &_reader.flushingFinished; + + ENQUEUE_MESSAGE_1(this, finishWriting, + flushingFinished, flushingFinished, + { + *flushingFinished = true; + }); + + kick(); + } +} + +void MessageQueue::recycleMemoryChunk(uint8_t *const chunk) const noexcept { + MessageQueue::MemoryAllocator::getInstance().recycle(chunk, _freeChunksByUser); +} + +void MessageQueue::freeChunksInFreeQueue(MessageQueue *const mainMessageQueue) noexcept { + MessageQueue::MemoryAllocator::getInstance().freeByUser(mainMessageQueue); +} + +// NOLINTNEXTLINE(misc-no-recursion) +uint8_t *MessageQueue::allocateImpl(uint32_t allocatedSize, uint32_t const requestSize) noexcept { + uint32_t const alignedSize = align(requestSize, 16); + CC_ASSERT(alignedSize + SWITCH_CHUNK_MEMORY_REQUIREMENT <= MEMORY_CHUNK_SIZE); + + uint32_t const newOffset = _writer.offset + alignedSize; + + // newOffset contains the DummyMessage + if (newOffset + sizeof(MemoryChunkSwitchMessage) <= MEMORY_CHUNK_SIZE) { + uint8_t *const allocatedMemory = _writer.currentMemoryChunk + _writer.offset; + _writer.offset = newOffset; + return allocatedMemory; + } + uint8_t *const newChunk = MessageQueue::MemoryAllocator::getInstance().request(); + auto *const switchMessage = reinterpret_cast(_writer.currentMemoryChunk + _writer.offset); + ccnew_placement(switchMessage) MemoryChunkSwitchMessage(this, newChunk, _writer.currentMemoryChunk); + switchMessage->_next = reinterpret_cast(newChunk); // point to start position + _writer.lastMessage = switchMessage; + ++_writer.pendingMessageCount; + _writer.currentMemoryChunk = newChunk; + _writer.offset = 0; + + DummyMessage *const head = allocate(1); + ccnew_placement(head) DummyMessage; + + if (_immediateMode) { + pushMessages(); + pullMessages(); + CC_ASSERT_EQ(_reader.newMessageCount, 2); + executeMessages(); + executeMessages(); + } + + return allocateImpl(allocatedSize, requestSize); +} + +void MessageQueue::pushMessages() noexcept { + _writer.writtenMessageCount.fetch_add(_writer.pendingMessageCount, std::memory_order_acq_rel); + _writer.pendingMessageCount = 0; +} + +void MessageQueue::pullMessages() noexcept { + uint32_t const writtenMessageCountNew = _writer.writtenMessageCount.load(std::memory_order_acquire); + _reader.newMessageCount += writtenMessageCountNew - _reader.writtenMessageCountSnap; + _reader.writtenMessageCountSnap = writtenMessageCountNew; +} + +void MessageQueue::flushMessages() noexcept { + while (!_reader.flushingFinished) { + executeMessages(); + } + + _reader.flushingFinished = false; +} + +void MessageQueue::executeMessages() noexcept { + Message *const msg = readMessage(); + + if (!msg) { + return; + } + + msg->execute(); + msg->~Message(); +} + +Message *MessageQueue::readMessage() noexcept { + while (!hasNewMessage()) { // if empty + std::unique_lock lock(_mutex); + pullMessages(); // try pulling data from consumer + if (!hasNewMessage()) { // still empty + _condVar.wait(lock); // wait for the producer to wake me up + pullMessages(); // pulling again + } + } + + Message *const msg = _reader.lastMessage->getNext(); + _reader.lastMessage = msg; + --_reader.newMessageCount; + CC_ASSERT(msg); + return msg; +} + +MessageQueue::~MessageQueue() { + recycleMemoryChunk(_writer.currentMemoryChunk); +} + +void MessageQueue::consumerThreadLoop() noexcept { + while (!_reader.terminateConsumerThread) { + AutoReleasePool autoReleasePool; + flushMessages(); + } + + _workerAttached = false; +} + +char const *DummyMessage::getName() const noexcept { + return "Dummy"; +} + +MemoryChunkSwitchMessage::MemoryChunkSwitchMessage(MessageQueue *const queue, uint8_t *const newChunk, uint8_t *const oldChunk) noexcept +: _messageQueue(queue), + _newChunk(newChunk), + _oldChunk(oldChunk) { +} + +MemoryChunkSwitchMessage::~MemoryChunkSwitchMessage() { + _messageQueue->recycleMemoryChunk(_oldChunk); +} + +void MemoryChunkSwitchMessage::execute() noexcept { + _messageQueue->_reader.currentMemoryChunk = _newChunk; + _messageQueue->pullMessages(); +} + +char const *MemoryChunkSwitchMessage::getName() const noexcept { + return "MemoryChunkSwitch"; +} + +TerminateConsumerThreadMessage::TerminateConsumerThreadMessage(EventSem *const pEvent, ReaderContext *const pR) noexcept +: _event(pEvent), + _reader(pR) { +} + +void TerminateConsumerThreadMessage::execute() noexcept { + _reader->terminateConsumerThread = true; + _reader->flushingFinished = true; + _event->signal(); +} + +char const *TerminateConsumerThreadMessage::getName() const noexcept { + return "TerminateConsumerThread"; +} + +} // namespace cc diff --git a/cocos/base/threading/MessageQueue.h b/cocos/base/threading/MessageQueue.h new file mode 100644 index 0000000..868643c --- /dev/null +++ b/cocos/base/threading/MessageQueue.h @@ -0,0 +1,725 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "../memory/Memory.h" +#include "Event.h" +#include "concurrentqueue/concurrentqueue.h" + +namespace cc { + +// TODO(YunHsiao): thread-specific allocators +template +inline T *memoryAllocateForMultiThread(uint32_t const count) noexcept { + return static_cast(malloc(sizeof(T) * count)); +} + +template +inline void memoryFreeForMultiThread(T *const p) noexcept { + free(p); +} + +inline uint32_t constexpr align(uint32_t const val, uint32_t const alignment) noexcept { + return (val + alignment - 1) & ~(alignment - 1); +} + +class Message { +public: + Message() = default; + virtual ~Message() = default; + Message(Message const &) = delete; + Message(Message &&) = delete; + Message &operator=(Message const &) = delete; + Message &operator=(Message &&) = delete; + + virtual void execute() = 0; + virtual char const *getName() const noexcept = 0; + inline Message *getNext() const noexcept { return _next; } + +private: + Message *_next; // explicitly assigned beforehand, don't init the member here + + friend class MessageQueue; +}; + +// structs may be padded +#if (CC_COMPILER == CC_COMPILER_MSVC) + #pragma warning(disable : 4324) +#endif + +struct ALIGNAS(64) WriterContext final { + uint8_t *currentMemoryChunk{nullptr}; + Message *lastMessage{nullptr}; + uint32_t offset{0}; + uint32_t pendingMessageCount{0}; + std::atomic writtenMessageCount{0}; +}; + +struct ALIGNAS(64) ReaderContext final { + uint8_t *currentMemoryChunk{nullptr}; + Message *lastMessage{nullptr}; + uint32_t offset{0}; + uint32_t writtenMessageCountSnap{0}; + uint32_t newMessageCount{0}; + bool terminateConsumerThread{false}; + bool flushingFinished{false}; +}; + +// A single-producer single-consumer circular buffer queue. +// Both the messages and their submitting data should be allocated from here. +class ALIGNAS(64) MessageQueue final { +public: + static constexpr uint32_t MEMORY_CHUNK_SIZE = 4096 * 16; + + MessageQueue(); + ~MessageQueue(); + MessageQueue(MessageQueue const &) = delete; + MessageQueue(MessageQueue &&) = delete; + MessageQueue &operator=(MessageQueue const &) = delete; + MessageQueue &operator=(MessageQueue &&) = delete; + + // message allocation + template + std::enable_if_t::value, T *> + allocate(uint32_t count) noexcept; + + // general-purpose allocation + template + std::enable_if_t::value, T *> + allocate(uint32_t count) noexcept; + template + T *allocateAndCopy(uint32_t count, void const *data) noexcept; + template + T *allocateAndZero(uint32_t count) noexcept; + + // notify the consumer to start working + void kick() noexcept; + + // notify the consumer to start working and block the producer until finished + void kickAndWait() noexcept; + + void runConsumerThread() noexcept; + void terminateConsumerThread() noexcept; + void finishWriting() noexcept; + void flushMessages() noexcept; + + inline bool isImmediateMode() const noexcept { return _immediateMode; } + + void recycleMemoryChunk(uint8_t *chunk) const noexcept; + static void freeChunksInFreeQueue(MessageQueue *mainMessageQueue) noexcept; + + inline void setImmediateMode(bool immediateMode) noexcept { _immediateMode = immediateMode; } + +private: + class ALIGNAS(64) MemoryAllocator final { + public: + MemoryAllocator() = default; + ~MemoryAllocator() noexcept; + MemoryAllocator(MemoryAllocator const &) = delete; + MemoryAllocator(MemoryAllocator &&) = delete; + MemoryAllocator &operator=(MemoryAllocator const &) = delete; + MemoryAllocator &operator=(MemoryAllocator &&) = delete; + + static MemoryAllocator &getInstance() noexcept; + uint8_t *request() noexcept; + void recycle(uint8_t *chunk, bool freeByUser) noexcept; + void freeByUser(MessageQueue *mainMessageQueue) noexcept; + void destroy() noexcept; + + private: + using ChunkQueue = moodycamel::ConcurrentQueue; + + void free(uint8_t *chunk) noexcept; + std::atomic _chunkCount{0}; + ChunkQueue _chunkPool{}; + ChunkQueue _chunkFreeQueue{}; + }; + +// structs may be padded +#if (CC_COMPILER == CC_COMPILER_MSVC) + #pragma warning(default : 4324) +#endif + + uint8_t *allocateImpl(uint32_t allocatedSize, uint32_t requestSize) noexcept; + void pushMessages() noexcept; + + // consumer thread specifics + void pullMessages() noexcept; + void executeMessages() noexcept; + Message *readMessage() noexcept; + inline bool hasNewMessage() const noexcept { return _reader.newMessageCount > 0 && !_reader.flushingFinished; } + void consumerThreadLoop() noexcept; + + WriterContext _writer; + ReaderContext _reader; + std::mutex _mutex; + std::condition_variable _condVar; + bool _immediateMode{true}; + bool _workerAttached{false}; + bool _freeChunksByUser{true}; // recycled chunks will be stashed until explicit free instruction + std::thread *_consumerThread{nullptr}; + + friend class MemoryChunkSwitchMessage; +}; + +class DummyMessage final : public Message { +public: + void execute() noexcept override {} + char const *getName() const noexcept override; +}; + +class MemoryChunkSwitchMessage final : public Message { +public: + MemoryChunkSwitchMessage(MessageQueue *queue, uint8_t *newChunk, uint8_t *oldChunk) noexcept; + ~MemoryChunkSwitchMessage() override; + + void execute() noexcept override; + char const *getName() const noexcept override; + +private: + MessageQueue *_messageQueue{nullptr}; + uint8_t *_newChunk{nullptr}; + uint8_t *_oldChunk{nullptr}; +}; + +class TerminateConsumerThreadMessage final : public Message { +public: + TerminateConsumerThreadMessage(EventSem *pEvent, ReaderContext *pR) noexcept; + + void execute() noexcept override; + char const *getName() const noexcept override; + +private: + EventSem *_event{nullptr}; + ReaderContext *_reader{nullptr}; +}; + +template +std::enable_if_t::value, T *> +MessageQueue::allocate(uint32_t const /*count*/) noexcept { + uint32_t allocatedSize = 0; + T *const msg = reinterpret_cast(allocateImpl(allocatedSize, sizeof(T))); + msg->_next = reinterpret_cast(_writer.currentMemoryChunk + _writer.offset); + ++_writer.pendingMessageCount; + _writer.lastMessage = msg; + return msg; +} + +template +std::enable_if_t::value, T *> +MessageQueue::allocate(uint32_t const count) noexcept { + uint32_t const requestSize = sizeof(T) * count; + CC_ASSERT(requestSize); + uint32_t allocatedSize = 0; + uint8_t *const allocatedMemory = allocateImpl(allocatedSize, requestSize); + _writer.lastMessage->_next = reinterpret_cast(_writer.currentMemoryChunk + _writer.offset); + return reinterpret_cast(allocatedMemory); +} + +template +T *MessageQueue::allocateAndCopy(uint32_t const count, void const *data) noexcept { + T *const allocatedMemory = allocate(count); + memcpy(allocatedMemory, data, sizeof(T) * count); + return allocatedMemory; +} + +template +T *MessageQueue::allocateAndZero(uint32_t const count) noexcept { + T *const allocatedMemory = allocate(count); + memset(allocatedMemory, 0, sizeof(T) * count); + return allocatedMemory; +} + +// utility macros for the producer thread to enqueue messages + +#define WRITE_MESSAGE(queue, MessageName, Params) \ + { \ + if (!queue->isImmediateMode()) { \ + ccnew_placement(queue->allocate(1)) MessageName Params; \ + } else { \ + MessageName msg Params; \ + msg.execute(); \ + } \ + } + +#define ENQUEUE_MESSAGE_0(queue, MessageName, Code) \ + { \ + class MessageName final : public Message { \ + public: \ + void execute() override { \ + Code \ + } \ + char const *getName() const noexcept override { \ + return (#MessageName); \ + } \ + }; \ + WRITE_MESSAGE(queue, MessageName, ); \ + } + +#define ENQUEUE_MESSAGE_1(queue, MessageName, \ + Param1, Value1, \ + Code) \ + { \ + using Type1 = typename std::decay::type; \ + \ + class MessageName final : public Message { \ + public: \ + explicit MessageName(Type1 In##Param1) \ + : Param1(std::move(In##Param1)) { \ + } \ + void execute() override { \ + Code \ + } \ + char const *getName() const noexcept override { \ + return (#MessageName); \ + } \ + \ + private: \ + Type1 Param1; \ + }; \ + WRITE_MESSAGE(queue, MessageName, (Value1)) \ + } + +#define ENQUEUE_MESSAGE_2(queue, MessageName, \ + Param1, Value1, \ + Param2, Value2, \ + Code) \ + { \ + using Type1 = typename std::decay::type; \ + using Type2 = typename std::decay::type; \ + \ + class MessageName final : public Message { \ + public: \ + MessageName( \ + Type1 In##Param1, Type2 In##Param2) \ + : Param1(std::move(In##Param1)), \ + Param2(std::move(In##Param2)) { \ + } \ + void execute() override { \ + Code \ + } \ + char const *getName() const noexcept override { \ + return (#MessageName); \ + } \ + \ + private: \ + Type1 Param1; \ + Type2 Param2; \ + }; \ + WRITE_MESSAGE(queue, MessageName, (Value1, Value2)) \ + } + +#define ENQUEUE_MESSAGE_3(queue, MessageName, \ + Param1, Value1, \ + Param2, Value2, \ + Param3, Value3, \ + Code) \ + { \ + using Type1 = typename std::decay::type; \ + using Type2 = typename std::decay::type; \ + using Type3 = typename std::decay::type; \ + \ + class MessageName final : public Message { \ + public: \ + MessageName( \ + Type1 In##Param1, \ + Type2 In##Param2, \ + Type3 In##Param3) \ + : Param1(std::move(In##Param1)), \ + Param2(std::move(In##Param2)), \ + Param3(std::move(In##Param3)) { \ + } \ + void execute() override { \ + Code \ + } \ + char const *getName() const noexcept override { \ + return (#MessageName); \ + } \ + \ + private: \ + Type1 Param1; \ + Type2 Param2; \ + Type3 Param3; \ + }; \ + WRITE_MESSAGE(queue, MessageName, \ + (Value1, \ + Value2, \ + Value3)) \ + } + +#define ENQUEUE_MESSAGE_4(queue, MessageName, \ + Param1, Value1, \ + Param2, Value2, \ + Param3, Value3, \ + Param4, Value4, \ + Code) \ + { \ + using Type1 = typename std::decay::type; \ + using Type2 = typename std::decay::type; \ + using Type3 = typename std::decay::type; \ + using Type4 = typename std::decay::type; \ + \ + class MessageName : public Message { \ + public: \ + MessageName( \ + Type1 In##Param1, \ + Type2 In##Param2, \ + Type3 In##Param3, \ + Type4 In##Param4) \ + : Param1(std::move(In##Param1)), \ + Param2(std::move(In##Param2)), \ + Param3(std::move(In##Param3)), \ + Param4(std::move(In##Param4)) { \ + } \ + void execute() override { \ + Code \ + } \ + char const *getName() const noexcept override { \ + return (#MessageName); \ + } \ + \ + private: \ + Type1 Param1; \ + Type2 Param2; \ + Type3 Param3; \ + Type4 Param4; \ + }; \ + WRITE_MESSAGE(queue, MessageName, \ + (Value1, \ + Value2, \ + Value3, \ + Value4)) \ + } + +#define ENQUEUE_MESSAGE_5(queue, MessageName, \ + Param1, Value1, \ + Param2, Value2, \ + Param3, Value3, \ + Param4, Value4, \ + Param5, Value5, \ + Code) \ + { \ + using Type1 = typename std::decay::type; \ + using Type2 = typename std::decay::type; \ + using Type3 = typename std::decay::type; \ + using Type4 = typename std::decay::type; \ + using Type5 = typename std::decay::type; \ + \ + class MessageName : public Message { \ + public: \ + MessageName( \ + Type1 In##Param1, \ + Type2 In##Param2, \ + Type3 In##Param3, \ + Type4 In##Param4, \ + Type5 In##Param5) \ + : Param1(std::move(In##Param1)), \ + Param2(std::move(In##Param2)), \ + Param3(std::move(In##Param3)), \ + Param4(std::move(In##Param4)), \ + Param5(std::move(In##Param5)) { \ + } \ + void execute() override { \ + Code \ + } \ + char const *getName() const noexcept override { \ + return (#MessageName); \ + } \ + \ + private: \ + Type1 Param1; \ + Type2 Param2; \ + Type3 Param3; \ + Type4 Param4; \ + Type5 Param5; \ + }; \ + WRITE_MESSAGE(queue, MessageName, \ + (Value1, \ + Value2, \ + Value3, \ + Value4, \ + Value5)) \ + } + +#define ENQUEUE_MESSAGE_6(queue, MessageName, \ + Param1, Value1, \ + Param2, Value2, \ + Param3, Value3, \ + Param4, Value4, \ + Param5, Value5, \ + Param6, Value6, \ + Code) \ + { \ + using Type1 = typename std::decay::type; \ + using Type2 = typename std::decay::type; \ + using Type3 = typename std::decay::type; \ + using Type4 = typename std::decay::type; \ + using Type5 = typename std::decay::type; \ + using Type6 = typename std::decay::type; \ + \ + class MessageName : public Message { \ + public: \ + MessageName( \ + Type1 In##Param1, \ + Type2 In##Param2, \ + Type3 In##Param3, \ + Type4 In##Param4, \ + Type5 In##Param5, \ + Type6 In##Param6) \ + : Param1(std::move(In##Param1)), \ + Param2(std::move(In##Param2)), \ + Param3(std::move(In##Param3)), \ + Param4(std::move(In##Param4)), \ + Param5(std::move(In##Param5)), \ + Param6(std::move(In##Param6)) { \ + } \ + void execute() override { \ + Code \ + } \ + char const *getName() const noexcept override { \ + return (#MessageName); \ + } \ + \ + private: \ + Type1 Param1; \ + Type2 Param2; \ + Type3 Param3; \ + Type4 Param4; \ + Type5 Param5; \ + Type6 Param6; \ + }; \ + WRITE_MESSAGE(queue, MessageName, \ + (Value1, \ + Value2, \ + Value3, \ + Value4, \ + Value5, \ + Value6)) \ + } + +#define ENQUEUE_MESSAGE_7(queue, MessageName, \ + Param1, Value1, \ + Param2, Value2, \ + Param3, Value3, \ + Param4, Value4, \ + Param5, Value5, \ + Param6, Value6, \ + Param7, Value7, \ + Code) \ + { \ + using Type1 = typename std::decay::type; \ + using Type2 = typename std::decay::type; \ + using Type3 = typename std::decay::type; \ + using Type4 = typename std::decay::type; \ + using Type5 = typename std::decay::type; \ + using Type6 = typename std::decay::type; \ + using Type7 = typename std::decay::type; \ + \ + class MessageName : public Message { \ + public: \ + MessageName( \ + Type1 In##Param1, \ + Type2 In##Param2, \ + Type3 In##Param3, \ + Type4 In##Param4, \ + Type5 In##Param5, \ + Type6 In##Param6, \ + Type7 In##Param7) \ + : Param1(std::move(In##Param1)), \ + Param2(std::move(In##Param2)), \ + Param3(std::move(In##Param3)), \ + Param4(std::move(In##Param4)), \ + Param5(std::move(In##Param5)), \ + Param6(std::move(In##Param6)), \ + Param7(std::move(In##Param7)) { \ + } \ + void execute() override { \ + Code \ + } \ + const char *getName() const noexcept override { \ + return (#MessageName); \ + } \ + \ + private: \ + Type1 Param1; \ + Type2 Param2; \ + Type3 Param3; \ + Type4 Param4; \ + Type5 Param5; \ + Type6 Param6; \ + Type7 Param7; \ + }; \ + WRITE_MESSAGE(queue, MessageName, \ + (Value1, \ + Value2, \ + Value3, \ + Value4, \ + Value5, \ + Value6, \ + Value7)) \ + } + +#define ENQUEUE_MESSAGE_8(queue, MessageName, \ + Param1, Value1, \ + Param2, Value2, \ + Param3, Value3, \ + Param4, Value4, \ + Param5, Value5, \ + Param6, Value6, \ + Param7, Value7, \ + Param8, Value8, \ + Code) \ + { \ + using Type1 = typename std::decay::type; \ + using Type2 = typename std::decay::type; \ + using Type3 = typename std::decay::type; \ + using Type4 = typename std::decay::type; \ + using Type5 = typename std::decay::type; \ + using Type6 = typename std::decay::type; \ + using Type7 = typename std::decay::type; \ + using Type8 = typename std::decay::type; \ + \ + class MessageName : public Message { \ + public: \ + MessageName(Type1 In##Param1, \ + Type2 In##Param2, \ + Type3 In##Param3, \ + Type4 In##Param4, \ + Type5 In##Param5, \ + Type6 In##Param6, \ + Type7 In##Param7, \ + Type8 In##Param8) \ + : Param1(std::move(In##Param1)), \ + Param2(std::move(In##Param2)), \ + Param3(std::move(In##Param3)), \ + Param4(std::move(In##Param4)), \ + Param5(std::move(In##Param5)), \ + Param6(std::move(In##Param6)), \ + Param7(std::move(In##Param7)), \ + Param8(std::move(In##Param8)) { \ + } \ + void execute() override { \ + Code \ + } \ + char const *getName() const noexcept override { \ + return (#MessageName); \ + } \ + \ + private: \ + Type1 Param1; \ + Type2 Param2; \ + Type3 Param3; \ + Type4 Param4; \ + Type5 Param5; \ + Type6 Param6; \ + Type7 Param7; \ + Type8 Param8; \ + }; \ + WRITE_MESSAGE(queue, MessageName, \ + (Value1, \ + Value2, \ + Value3, \ + Value4, \ + Value5, \ + Value6, \ + Value7, \ + Value8)) \ + } + +#define ENQUEUE_MESSAGE_9(queue, MessageName, \ + Param1, Value1, \ + Param2, Value2, \ + Param3, Value3, \ + Param4, Value4, \ + Param5, Value5, \ + Param6, Value6, \ + Param7, Value7, \ + Param8, Value8, \ + Param9, Value9, \ + Code) \ + { \ + using Type1 = typename std::decay::type; \ + using Type2 = typename std::decay::type; \ + using Type3 = typename std::decay::type; \ + using Type4 = typename std::decay::type; \ + using Type5 = typename std::decay::type; \ + using Type6 = typename std::decay::type; \ + using Type7 = typename std::decay::type; \ + using Type8 = typename std::decay::type; \ + using Type9 = typename std::decay::type; \ + \ + class MessageName : public Message { \ + public: \ + MessageName(Type1 In##Param1, \ + Type2 In##Param2, \ + Type3 In##Param3, \ + Type4 In##Param4, \ + Type5 In##Param5, \ + Type6 In##Param6, \ + Type7 In##Param7, \ + Type8 In##Param8, \ + Type9 In##Param9) \ + : Param1(std::move(In##Param1)), \ + Param2(std::move(In##Param2)), \ + Param3(std::move(In##Param3)), \ + Param4(std::move(In##Param4)), \ + Param5(std::move(In##Param5)), \ + Param6(std::move(In##Param6)), \ + Param7(std::move(In##Param7)), \ + Param8(std::move(In##Param8)), \ + Param9(std::move(In##Param9)) { \ + } \ + void execute() override { \ + Code \ + } \ + char const *getName() const noexcept override { \ + return (#MessageName); \ + } \ + \ + private: \ + Type1 Param1; \ + Type2 Param2; \ + Type3 Param3; \ + Type4 Param4; \ + Type5 Param5; \ + Type6 Param6; \ + Type7 Param7; \ + Type8 Param8; \ + Type9 Param9; \ + }; \ + WRITE_MESSAGE(queue, MessageName, \ + (Value1, \ + Value2, \ + Value3, \ + Value4, \ + Value5, \ + Value6, \ + Value7, \ + Value8, \ + Value9)) \ + } + +} // namespace cc diff --git a/cocos/base/threading/ReadWriteLock.h b/cocos/base/threading/ReadWriteLock.h new file mode 100644 index 0000000..5442d15 --- /dev/null +++ b/cocos/base/threading/ReadWriteLock.h @@ -0,0 +1,57 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +namespace cc { + +class ReadWriteLock final { +public: + ReadWriteLock() = default; + + template + auto lockRead(Function &&func, Args &&...args) noexcept -> decltype(func(std::forward(args)...)); + + template + auto lockWrite(Function &&func, Args &&...args) noexcept -> decltype(func(std::forward(args)...)); + +private: + std::shared_mutex _mutex; +}; + +template +auto ReadWriteLock::lockRead(Function &&func, Args &&...args) noexcept -> decltype(func(std::forward(args)...)) { + std::shared_lock lock(_mutex); + return func(std::forward(args)...); +} + +template +auto ReadWriteLock::lockWrite(Function &&func, Args &&...args) noexcept -> decltype(func(std::forward(args)...)) { + std::lock_guard lock(_mutex); + return func(std::forward(args)...); +} + +} // namespace cc diff --git a/cocos/base/threading/Semaphore.cpp b/cocos/base/threading/Semaphore.cpp new file mode 100644 index 0000000..86249a2 --- /dev/null +++ b/cocos/base/threading/Semaphore.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Semaphore.h" + +namespace cc { + +Semaphore::Semaphore() noexcept +: _semaphore(0) { +} + +Semaphore::Semaphore(int initialCount) noexcept +: _semaphore(initialCount) { +} + +void Semaphore::wait() noexcept { + _semaphore.wait(); +} + +void Semaphore::signal(int count) noexcept { + _semaphore.signal(count); +} + +} // namespace cc diff --git a/cocos/base/threading/Semaphore.h b/cocos/base/threading/Semaphore.h new file mode 100644 index 0000000..b0974ae --- /dev/null +++ b/cocos/base/threading/Semaphore.h @@ -0,0 +1,45 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "concurrentqueue/concurrentqueue.h" +#include "concurrentqueue/lightweightsemaphore.h" +namespace cc { + +class Semaphore final { +public: + Semaphore() noexcept; + explicit Semaphore(int initialCount) noexcept; + + void wait() noexcept; + void signal(int count = 1) noexcept; + void signalAll() noexcept { CC_ABORT(); } // NOLINT(readability-convert-member-functions-to-static) + +private: + moodycamel::details::Semaphore _semaphore; +}; + +} // namespace cc diff --git a/cocos/base/threading/ThreadPool.cpp b/cocos/base/threading/ThreadPool.cpp new file mode 100644 index 0000000..3eb983c --- /dev/null +++ b/cocos/base/threading/ThreadPool.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "ThreadPool.h" + +namespace cc { + +uint8_t const ThreadPool::CPU_CORE_COUNT = std::thread::hardware_concurrency(); +uint8_t const ThreadPool::MAX_THREAD_COUNT = CPU_CORE_COUNT - 1; + +void ThreadPool::start() { + if (_running) { + return; + } + + _running = true; + + for (uint8_t i = 0; i < MAX_THREAD_COUNT; ++i) { + addThread(); + } +} + +void ThreadPool::stop() { + if (!_running) { + return; + } + + _running = false; + _event.signalAll(); + + for (auto &worker : _workers) { + if (worker.joinable()) { + worker.join(); + } + } + + _workers.clear(); +} + +void ThreadPool::addThread() { + CC_ASSERT(_workers.size() < MAX_THREAD_COUNT); + + auto workerLoop = [this]() { + while (_running) { + Task task = nullptr; + + if (_tasks.try_dequeue(task)) { + task(); + } else { + // Double Check + _event.wait([this]() { + return _tasks.size_approx() != 0; + }); + } + } + }; + + _workers.emplace_back(workerLoop); +} + +} // namespace cc diff --git a/cocos/base/threading/ThreadPool.h b/cocos/base/threading/ThreadPool.h new file mode 100644 index 0000000..4487093 --- /dev/null +++ b/cocos/base/threading/ThreadPool.h @@ -0,0 +1,85 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include "Event.h" +#include "base/Macros.h" +#include "base/std/container/list.h" +#include "concurrentqueue/concurrentqueue.h" + +namespace cc { + +class ThreadPool final { +public: + using Task = std::function; + using TaskQueue = moodycamel::ConcurrentQueue; + + static uint8_t const CPU_CORE_COUNT; + static uint8_t const MAX_THREAD_COUNT; + + ThreadPool() = default; + ~ThreadPool() = default; + ThreadPool(ThreadPool const &) = delete; + ThreadPool(ThreadPool &&) noexcept = delete; + ThreadPool &operator=(ThreadPool const &) = delete; + ThreadPool &operator=(ThreadPool &&) noexcept = delete; + + template + auto dispatchTask(Function &&func, Args &&...args) -> std::future(args)...))>; + void start(); + void stop(); + +private: + using Event = ConditionVariable; + + void addThread(); + + TaskQueue _tasks{}; + ccstd::list _workers{}; + Event _event{}; + std::atomic _running{false}; + uint8_t _workerCount{MAX_THREAD_COUNT}; +}; + +template +auto ThreadPool::dispatchTask(Function &&func, Args &&...args) -> std::future(args)...))> { + CC_ASSERT(_running); + + using ReturnType = decltype(func(std::forward(args)...)); + auto task = std::make_shared>(std::bind(std::forward(func), std::forward(args)...)); + bool const succeed = _tasks.enqueue([task]() { + (*task)(); + }); + CC_ASSERT(succeed); + _event.signal(); + return task->get_future(); +} + +} // namespace cc diff --git a/cocos/base/threading/ThreadSafeCounter.h b/cocos/base/threading/ThreadSafeCounter.h new file mode 100644 index 0000000..92ab1f4 --- /dev/null +++ b/cocos/base/threading/ThreadSafeCounter.h @@ -0,0 +1,47 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include + +namespace cc { + +template ::value>> +class ThreadSafeCounter final { +public: + inline T increment() noexcept { return add(1); } + inline T add(T const v) noexcept { return _counter.fetch_add(v, std::memory_order_relaxed); } + inline T decrement() noexcept { return subtract(1); } + inline T subtract(T const v) noexcept { return _counter.fetch_sub(v, std::memory_order_relaxed); } + inline void set(T const v) noexcept { _counter.store(v, std::memory_order_relaxed); } + inline T get() const noexcept { return _counter.load(std::memory_order_relaxed); } + inline void reset() noexcept { set(0); } + +private: + std::atomic _counter{0}; +}; + +} // namespace cc diff --git a/cocos/base/threading/ThreadSafeLinearAllocator.cpp b/cocos/base/threading/ThreadSafeLinearAllocator.cpp new file mode 100644 index 0000000..1d3b3e0 --- /dev/null +++ b/cocos/base/threading/ThreadSafeLinearAllocator.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "ThreadSafeLinearAllocator.h" +#include "acl/core/memory_utils.h" +#include "base/Macros.h" + +namespace cc { + +ThreadSafeLinearAllocator::ThreadSafeLinearAllocator(size_t size, size_t alignment) noexcept +: _capacity(size), _alignment(alignment) { + if (alignment == 1) { + _buffer = CC_MALLOC(size); + } else { + _buffer = CC_MALLOC_ALIGN(size, alignment); + } + CC_ASSERT(_buffer); +} + +ThreadSafeLinearAllocator::~ThreadSafeLinearAllocator() { + if (_alignment == 1) { + CC_FREE(_buffer); + } else { + CC_FREE_ALIGN(_buffer); + } +} + +void *ThreadSafeLinearAllocator::doAllocate(size_t size, size_t alignment) noexcept { + if (size == 0) { + return nullptr; + } + + void *allocatedMemory = nullptr; + size_t oldUsedSize = 0; + uint64_t newUsedSize = 0; // force 64-bit here to correctly detect overflows + + do { + oldUsedSize = getUsedSize(); + allocatedMemory = acl::align_to(acl::add_offset_to_ptr(_buffer, oldUsedSize), alignment); + newUsedSize = reinterpret_cast(allocatedMemory) - reinterpret_cast(_buffer) + size; + + if (newUsedSize > _capacity) { + return nullptr; // overflows + } + } while (!_usedSize.compare_exchange_weak(oldUsedSize, static_cast(newUsedSize), std::memory_order_relaxed, std::memory_order_relaxed)); // no ABA possible + + return allocatedMemory; +} + +} // namespace cc diff --git a/cocos/base/threading/ThreadSafeLinearAllocator.h b/cocos/base/threading/ThreadSafeLinearAllocator.h new file mode 100644 index 0000000..666da17 --- /dev/null +++ b/cocos/base/threading/ThreadSafeLinearAllocator.h @@ -0,0 +1,78 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include "../memory/Memory.h" + +namespace cc { + +// class may be padded +#if (CC_COMPILER == CC_COMPILER_MSVC) + #pragma warning(disable : 4324) +#endif + +class ALIGNAS(16) ThreadSafeLinearAllocator final { +public: + explicit ThreadSafeLinearAllocator(size_t size, size_t alignment = 1) noexcept; + ~ThreadSafeLinearAllocator(); + ThreadSafeLinearAllocator(ThreadSafeLinearAllocator const &) = delete; + ThreadSafeLinearAllocator(ThreadSafeLinearAllocator &&) = delete; + ThreadSafeLinearAllocator &operator=(ThreadSafeLinearAllocator const &) = delete; + ThreadSafeLinearAllocator &operator=(ThreadSafeLinearAllocator &&) = delete; + + template + inline T *allocate(size_t count, size_t alignment = 1) noexcept { + return reinterpret_cast(doAllocate(count * sizeof(T), alignment)); + } + + inline std::ptrdiff_t allocateToOffset(size_t size, size_t alignment = 1) noexcept { + return reinterpret_cast(doAllocate(size, alignment)) - reinterpret_cast(_buffer); + } + + inline void *getBuffer() const noexcept { return _buffer; } + inline size_t getCapacity() const noexcept { return _capacity; } + inline size_t getUsedSize() const noexcept { return _usedSize.load(std::memory_order_relaxed); } + inline size_t getBalance() const noexcept { return getCapacity() - getUsedSize(); } + + inline void recycle() noexcept { _usedSize.store(0, std::memory_order_relaxed); } + +private: + void *doAllocate(size_t size, size_t alignment) noexcept; + + void *_buffer{nullptr}; + size_t _capacity{0}; + size_t _alignment{1}; + std::atomic _usedSize{0}; +}; + +// class may be padded +#if (CC_COMPILER == CC_COMPILER_MSVC) + #pragma warning(default : 4324) +#endif + +} // namespace cc diff --git a/cocos/bindings/docs/JSB2.0-Architecture.png b/cocos/bindings/docs/JSB2.0-Architecture.png new file mode 100644 index 0000000..dd64ace Binary files /dev/null and b/cocos/bindings/docs/JSB2.0-Architecture.png differ diff --git a/cocos/bindings/docs/JSB2.0-learning-en.md b/cocos/bindings/docs/JSB2.0-learning-en.md new file mode 100644 index 0000000..0a16e41 --- /dev/null +++ b/cocos/bindings/docs/JSB2.0-learning-en.md @@ -0,0 +1,1197 @@ +# The Tutorial for JSB 2.0 + +## The Abstraction Layer of Script Engine + +### Architecture + +![](JSB2.0-Architecture.png) + +### Macro + +The abstraction layer is bound to take more CPU execution time than using the JS engine API directly. How to minimize the overhead of the abstraction layer becomes the first goal of the design. + +Most of work in JS binding is actually setting JS related operations with CPP callbacks and associating CPP object within the callback function. In fact, it mainly contains the following two situation: + +* Register JS functions (including global functions, class constructors, class destructors, class member functions, and class static member functions), binding revevant CPP callbacks + +* Register accessors for JS properties, bind CPP callbacks for reading and writing properties respectively + +How to achieve the minimum overhead for the abstract layer and expose the unified API? + +For example, to register a JS function in CPP, there are different definitions in JavaScriptCore, SpiderMonkey, V8, ChakraCore as follows: + +**JavaScriptCore:** + +```c++ +JSValueRef JSB_foo_func( + JSContextRef _cx, + JSObjectRef _function, + JSObjectRef _thisObject, + size_t argc, + const JSValueRef _argv[], + JSValueRef* _exception + ); +``` + +**SpiderMonkey:** + +```c++ +bool JSB_foo_func( + JSContext* _cx, + unsigned argc, + JS::Value* _vp + ); +``` + +**V8:** + +```c++ +void JSB_foo_func( + const v8::FunctionCallbackInfo& v8args + ); +``` + +**ChakraCore:** + +```c++ +JsValueRef JSB_foo_func( + JsValueRef _callee, + bool _isConstructCall, + JsValueRef* _argv, + unsigned short argc, + void* _callbackState + ); +``` + +We evaluated several options and eventually decided to use `macros` to reduce the differences between the definition and parameter types of different JS engine callbacks, regardless of which engine is used, and developers could use an unified callback definition. We refer to the definition of Lua callback function. The definition of all JS to CPP callback functions in the abstract layer is defined as: + +```c++ +bool foo(se::State& s) +{ + ... + ... +} +SE_BIND_FUNC(foo) // Binding a JS function as an example +``` + +After a developer has bound a JS function, remember to wrap the callback function with the macros which start with `SE_BIND_`. Currently, we provide the following macros: + +* SE\_BIND\_PROP_GET: Wrap a JS object property read callback function +* SE\_BIND\_PROP_SET: Wrap a JS object property written callback function +* SE\_BIND\_FUNC: Wrap a JS function that can be used for global functions, class member functions or class static functions +* SE\_DECLARE\_FUNC: Declare a JS function, generally used in the header file +* SE\_BIND\_CTOR: Wrap a JS constructor +* SE\_BIND\_SUB\_CLS\_CTOR: Wrap the constructor of a JS subclass by using cc.Class.extend. +* SE\_FINALIZE\_FUNC: Wrap the finalize function of a JS object, finalize function is invoked when the object is released by Garbage Collector +* SE\_DECLARE\_FINALIZE\_FUNC: Declares the finalize function of a JS object +* _SE: The macro for making callback be recognized by different JS engine. Note that the first character is underscored, similar to `_T ('xxx')` in Windows for wrapping Unicode or MultiBytes string + +## API + +### CPP Namespace + +All types of the abstraction layer are under the `se` namespace, which is an abbreviation of `ScriptEngine`. + +### Types + +#### se::ScriptEngine + +`se::ScriptEngine` is the JS engine administrator, responsible for JS engine initialization, destruction, restart, native module registration, loading scripts, doing garbage collection, JS exception cleanup and whether to enable the debugger. +It is a singleton that could be accessed via `se::ScriptEngine::getInstance()`. + +#### se::Value + +`se::Value` can be understood as a JS variable reference in the CPP layer. There are six types of JS variables: `object`,` number`, `string`,` boolean`, `null`,` undefined`, so `se::Value` uses an `union` to include ` object`, `number`,` string `,` boolean` these 4 kinds of `value types`, `non-value types` like `null` and `undefined` can be represented by `_type` directly. + +```c++ +namespace se { + class Value { + enum class Type : char + { + Undefined = 0, + Null, + Number, + Boolean, + String, + Object + }; + ... + ... + private: + union { + bool _boolean; + double _number; + std::string* _string; + Object* _object; + } _u; + + Type _type; + ... + ... + }; +} +``` + +If a `se::Value` stores the underlying data types, such as `number`,` string`, `boolean`, which is directly stored by `value copy`. +The storage of `object` is special because it is a `weak reference` to JS objects via `se::Object*`. + +#### se::Object + +`se::Object` extends from `se::RefCounter` which is a class for reference count management. Currently, only `se::Object` inherits from `se::RefCounter` in the abstraction layer. +As we mentioned in the last section, `se::Object` is a weak reference to the JS object, therefore I will explain why it's a weak reference. + +* Reason 1: The requirement of controlling the life cycle of CPP objects by JS objects + +After creating a Sprite in the script layer via `var sp = new cc.Sprite("a.png");`, we create a `se::Object` in the constructor callback and leave it in a global map (NativePtrToObjectMap), this map is used to query the `cc::Sprite*` to get the corresponding JS object `se::Object*`. + +```c++ +static bool js_cocos2d_Sprite_finalize(se::State& s) +{ + CC_LOG_DEBUG("jsbindings: finalizing JS object %p (cc::Sprite)", s.nativeThisObject()); + cc::Sprite* cobj = (cc::Sprite*)s.nativeThisObject(); + if (cobj->getReferenceCount() == 1) + cobj->autorelease(); + else + cobj->release(); + return true; +} +SE_BIND_FINALIZE_FUNC(js_cocos2d_Sprite_finalize) + +static bool js_cocos2dx_Sprite_constructor(se::State& s) +{ + cc::Sprite* cobj = new (std::nothrow) cc::Sprite(); // cobj will be released in the finalize callback + s.thisObject()->setPrivateData(cobj); // setPrivateData will make a mapping between se::Object* and cobj + return true; +} +SE_BIND_CTOR(js_cocos2dx_Sprite_constructor, __jsb_cocos2d_Sprite_class, js_cocos2d_Sprite_finalize) +``` + +Imagine if you force `se::Object` to be a strong reference to a JS object that leaves JS objects out of GC control and the finalize callback will never be fired because `se::Object` is always present in map which will cause memory leak. + +It is precisely because the `se::Object` holds a weak reference to a JS object so that controlling the life of the CPP object by JS object can be achieved. In the above code, when the JS object is released, it will trigger the finalize callback, developers only need to release the corresponding CPP object in `js_cocos2d_Sprite_finalize`, the release of `se::Object` has been included in the` SE_BIND_FINALIZE_FUNC` macro by automatic processing, developers do not have to manage the release of `se::Object` in `JS Object Control CPP Object` mode, but in` CPP Object Control JS Object` mode, developers have the responsibility to manage the release of `se::Object`. I will give an example in the next section. + +* Reason 2:More flexible, supporting strong reference by calling the se::Object::root method manually + +`se::Object` provides `root/unroot` method for developers to invoke, `root` will put JS object into the area not be scanned by the GC. After calling `root`, `se::Object*` is a strong reference to the JS object. JS object will be put back to the area scanned by the GC only when `se::Object` is destructed or `unroot` is called to make root count to zero. + +Under normal circumstances, if CPP object is not a subclass of `cocos2d :: Ref`, CPP object will be used to control the life cycle of the JS object in binding. Binding the engine modules, like spine, dragonbones, box2d, anysdk and other third-party libraries uses this method. When the CPP object is released, you need to find the corresponding `se::Object` in the `NativePtrToObjectMap`, then manually `unroot` and `decRef` it. Take the binding of `spTrackEntry` in spine as an example: + +```c++ +spTrackEntry_setDisposeCallback([](spTrackEntry* entry){ + se::Object* seObj = nullptr; + + auto iter = se::NativePtrToObjectMap::find(entry); + if (iter != se::NativePtrToObjectMap::end()) + { + // Save se::Object pointer for being used in cleanup method. + seObj = iter->second; + // Unmap native and js object since native object was destroyed. + // Otherwise, it may trigger 'assertion' in se::Object::setPrivateData later + // since native obj is already released and the new native object may be assigned with + // the same address. + se::NativePtrToObjectMap::erase(iter); + } + else + { + return; + } + + auto cleanup = [seObj](){ + + auto se = se::ScriptEngine::getInstance(); + if (!se->isValid() || se->isInCleanup()) + return; + + se::AutoHandleScope hs; + se->clearException(); + + // The mapping of native object & se::Object was cleared in above code. + // The private data (native object) may be a different object associated with other se::Object. + // Therefore, don't clear the mapping again. + seObj->clearPrivateData(false); + seObj->unroot(); + seObj->decRef(); + }; + + if (!se::ScriptEngine::getInstance()->isGarbageCollecting()) + { + cleanup(); + } + else + { + CleanupTask::pushTaskToAutoReleasePool(cleanup); + } + }); +``` + + +__se::Object Types__ + +* Native Binding Object + + The creation of native binding object has been hidden in the `SE_BIND_CTOR` and` SE_BIND_SUB_CLS_CTOR` macros, if developers need to use the `se::Object` in the binding callback, just get it by invoking `s.thisObject()`. Where `s` is `se::State&` which will be described in the following chapters. + +In addition, `se::Object` currently supports the manual creation of the following objects: + +* Plain Object: Created by `se::Object::createPlainObject`, similar to `var a = {};` in JS +* Array Object: Created by `se::Object::createArrayObject`, similar to `var a = [];` in JS +* Uint8 Typed Array Object: Created by `se::Object::createTypedArray`, like `var a = new Uint8Array(buffer);` in JS +* Array Buffer Object: Created by `se::Object::createArrayBufferObject` similar to `var a = new ArrayBuffer(len);` in JS + +__The Release of The Objects Created Manually__ + +`se::Object::createXXX` is unlike the create method in cocos2d-x, the abstraction layer is a completely separate module which does not rely on the autorelease mechanism in cocos2d-x. Although `se::Object` also inherits the reference count class `se::RefCounter`, developers need to handle the release for **objects created manually**. + +```c++ +se::Object* obj = se::Object::createPlainObject(); +... +... +obj->decRef(); // Decrease the reference count to avoid memory leak +``` + +#### se::HandleObject + +`se::HandleObject` is the recommended helper class for managing the objects created manually. + +* If using manual creation of objects in complex logic, developers often forget to deal with `decRef` in different conditions + +```c++ +bool foo() +{ + se::Object* obj = se::Object::createPlainObject(); + if (var1) + return false; // Return directly, forget to do 'decRef' operation + + if (var2) + return false; // Return directly, forget to do 'decRef' operation + ... + ... + obj->decRef(); + return true; +} +``` + +Plus adding `decRef` to different return condition branches can result in logically complex and difficult to maintain, and it is easy to forget about `decRef` if you make another return branch later. + +* If the JS engine did a GC operationJS engine right after `se::Object::createXXX`, which will result in the `se::Object` reference to an illegal pointer, the program may crash. + +In order to solve the above problems, the abstraction layer defines a type that assists in the management of **manually created objects**, namely `se::HandleObject`. + +`se::HandleObject` is a helper class for easier management of the `release (decRef)`, `root`, and `unroot` operations of manually created `se::Object` objects. +The following two code snippets are equivalent, the use of `se::HandleObject` significantly smaller amount of code, and more secure. + +```c++ + { + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty(...); + otherObject->setProperty("foo", se::Value(obj)); + } + + is equal to: + + { + se::Object* obj = se::Object::createPlainObject(); + obj->root(); // Root the object immediatelly to prevent the object being garabge collected. + + obj->setProperty(...); + otherObject->setProperty("foo", se::Value(obj)); + + obj->unroot(); // Call unroot while the object is needed anymore. + obj->decRef(); // Decrease the reference count to avoid memory leak. + } +``` + +**NOTE** + +* Do not try to use `se::HandleObject` to create a native binding object. In the `JS controls of CPP` mode, the release of the bound object will be automatically handled by the abstraction layer. In the `CPP controls JS` mode, the previous chapter has already described. + +* The `se::HandleObject` object can only be allocated on the stack, and a `se::Object` pointer must be passed in. + + +#### se::Class + +`se::Class` is used to expose CPP classes to JS, it creates a constructor function in JS that has a corresponding name. + +It has the following methods: + +* `static se::Class* create(className, obj, parentProto, ctor)` + + **Creating a Class**. If the registration is successful, we could create an object by `var xxx = new SomeClass ();` in the JS layer. + +* `bool defineFunction(name, func)`: Define a member function for a class. +* `bool defineProperty(name, getter, setter)`: Define a property accessor for a class. +* `bool defineStaticFunction(name, func)`: Define a static function for a class, the JS function could be accessed by `SomeClass.foo()` rather than the method of `var obj = new SomeClass(); obj.foo()`, means it' s a class method instead of an instance method. +* `bool defineStaticProperty(name, getter, setter)`: Define a static property accessor which could be invoked by `SomeClass.propertyA`,it's nothing about instance object. +* `bool defineFinalizeFunction(func)`: Define the finalize callback function after JS object is garbage collected. +* `bool install()`: Install a class JS engine. +* `Object* getProto()`: Get the prototype of JS constructor installed, similar to `function Foo(){}` `Foo.prototype` in JS. +* `const char* getName() const`: Get the class name which is also the name of JS constructor. + +**NOTE** + +You do not need to release memory manually after `se::Class` type is created, it will be automatically encapsulated layer. + +You could look through the API documentation or code comments for more specific API instructions. + +#### se::AutoHandleScope + +The `se::AutoHandleScope` object type is purely a concept introduced to address V8 compatibility issues. +In V8, any action that calls `v8::Local<>` on a CPP function that needs to trigger a JS related operation, such as calling a JS function, accessing a JS property, etc, requires a `v8::HandleScope` function be invoked before calling these operations, otherwise it will cause the program to crash. + +So the concept of `se::AutoHandleScope` was introduced into the abstraction layer, which is implemented only on V8, and the other JS engines are currently just empty implementations. + +Developers need to remember that in any code execution from CPP, you need to declare a `se::AutoHandleScope` before calling JS's logic. For example: + +```c++ +class SomeClass { + void update(float dt) { + se::ScriptEngine::getInstance()->clearException(); // Clear JS exceptions + se::AutoHandleScope hs; // Declare a handle scope, it's needed for V8 + + se::Object* obj = ...; + obj->setProperty(...); + ... + ... + obj->call(...); + } +}; +``` + +#### se::State + +In the previous section, we have mentioned the `se::State` type, which is an environment in the binding callback. We can get the current CPP pointer, `se::Object` object pointer, parameter list and return value reference through `se::State` argument. + +```c++ +bool foo(se::State& s) +{ + // Get native object pointer bound with the current JS object. + SomeClass* cobj = (SomeClass*)s.nativeThisObject(); + // Get se::Object pointer that represents the current JS object. + se::Object* thisObject = s.thisObject(); + // Get argument list of the current function. + const se::ValueArray& args = s.args(); + // Set return value for current function. + s.rval().setInt32(100); + // Return true to indicate the function is executed successfully. + return true; +} +SE_BIND_FUNC(foo) +``` + +## Does The Abstraction Layer Depend on Cocos2D-X? + +No. + +This abstraction layer was originally designed as a stand-alone module which is completely independent of Cocos2D-X engine. Developers can copy the abstraction layer code in `cocos/scripting/js-bindings/jswrapper` directory and paste them to other projects directly. + +## Manual Binding + +### Define A Callback Function + +```c++ +static bool Foo_balabala(se::State& s) +{ + const auto& args = s.args(); + int argc = (int)args.size(); + + if (argc >= 2) // Limit the number of parameters must be greater than or equal to 2, or throw an error to the JS layer and return false. { + ... + ... + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 2); + return false; +} + +// If binding a function, we use SE_BIND_FUNC macro. For binding a constructor, destructor, subclass constructor, please use SE_BIND_balabala macros memtioned above. +SE_BIND_FUNC(Foo_balabala) +``` + +### Set A Property Value for JS object + +```c++ +se::Object* globalObj = se::ScriptEngine::getInstance()->getGlobalObject(); // We get the global object just for easiler demenstration. +globalObj->setProperty("foo", se::Value(100)); // Set a property called `foo` with a value of 100 to the global object. +``` + +Then, you can use the `foo` global variable in JS directly. + +```js +cc.log("foo value: " + foo); // Print `foo value: 100`. +``` + +### Set A Property Accessor for JS Object + +```c++ +// The read callback of `foo` property of the global object +static bool Global_get_foo(se::State& s) +{ + NativeObj* cobj = (NativeObj*)s.nativeThisObject(); + int32_t ret = cobj->getValue(); + s.rval().setInt32(ret); + return true; +} +SE_BIND_PROP_GET(Global_get_foo) + +// The write callback of `foo` property of the global object +static bool Global_set_foo(se::State& s) +{ + const auto& args = s.args(); + int argc = (int)args.size(); + if (argc >= 1) + { + NativeObj* cobj = (NativeObj*)s.nativeThisObject(); + int32_t arg1 = args[0].toInt32(); + cobj->setValue(arg1); + // Do not need to call `s.rval().set(se::Value::Undefined)` for functions without return value. + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} +SE_BIND_PROP_SET(Global_set_foo) + +void some_func() +{ + se::Object* globalObj = se::ScriptEngine::getInstance()->getGlobalObject(); // We get the global object just for easiler demenstration. + globalObj->defineProperty("foo", _SE(Global_get_foo), _SE(Global_set_foo)); // Use _SE macro to package specific function name. +} +``` + +### Define A Function for JS Object + +```c++ +static bool Foo_function(se::State& s) +{ + ... + ... +} +SE_BIND_FUNC(Foo_function) + +void some_func() +{ + se::Object* globalObj = se::ScriptEngine::getInstance()->getGlobalObject(); // We get the global object just for easiler demenstration. + globalObj->defineFunction("foo", _SE(Foo_function)); // Use _SE macro to package specific function name. +} + +``` + +### Register A CPP Class to JS Virtual Machine + +```c++ +static se::Object* __jsb_ns_SomeClass_proto = nullptr; +static se::Class* __jsb_ns_SomeClass_class = nullptr; + +namespace ns { + class SomeClass + { + public: + SomeClass() + : xxx(0) + {} + + void foo() { + printf("SomeClass::foo\n"); + + Application::getInstance()->getScheduler()->schedule([this](float dt){ + static int counter = 0; + ++counter; + if (_cb != nullptr) + _cb(counter); + }, this, 1.0f, CC_REPEAT_FOREVER, 0.0f, false, "iamkey"); + } + + static void static_func() { + printf("SomeClass::static_func\n"); + } + + void setCallback(const std::function& cb) { + _cb = cb; + if (_cb != nullptr) + { + printf("setCallback(cb)\n"); + } + else + { + printf("setCallback(nullptr)\n"); + } + } + + int xxx; + private: + std::function _cb; + }; +} // namespace ns { + +static bool js_SomeClass_finalize(se::State& s) +{ + ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject(); + delete cobj; + return true; +} +SE_BIND_FINALIZE_FUNC(js_SomeClass_finalize) + +static bool js_SomeClass_constructor(se::State& s) +{ + ns::SomeClass* cobj = new ns::SomeClass(); + s.thisObject()->setPrivateData(cobj); + return true; +} +SE_BIND_CTOR(js_SomeClass_constructor, __jsb_ns_SomeClass_class, js_SomeClass_finalize) + +static bool js_SomeClass_foo(se::State& s) +{ + ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject(); + cobj->foo(); + return true; +} +SE_BIND_FUNC(js_SomeClass_foo) + +static bool js_SomeClass_get_xxx(se::State& s) +{ + ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject(); + s.rval().setInt32(cobj->xxx); + return true; +} +SE_BIND_PROP_GET(js_SomeClass_get_xxx) + +static bool js_SomeClass_set_xxx(se::State& s) +{ + const auto& args = s.args(); + int argc = (int)args.size(); + if (argc > 0) + { + ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject(); + cobj->xxx = args[0].toInt32(); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} +SE_BIND_PROP_SET(js_SomeClass_set_xxx) + +static bool js_SomeClass_static_func(se::State& s) +{ + ns::SomeClass::static_func(); + return true; +} +SE_BIND_FUNC(js_SomeClass_static_func) + +bool js_register_ns_SomeClass(se::Object* global) +{ + // Make sure the namespace exists + se::Value nsVal; + if (!global->getProperty("ns", &nsVal)) + { + // If it doesn't exist, create one. Similar as `var ns = {};` in JS. + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + + // Set the object to the global object with the property name `ns`. + global->setProperty("ns", nsVal); + } + se::Object* ns = nsVal.toObject(); + + // Create a se::Class object, developers do not need to consider the release of the se::Class object, which is automatically handled by the ScriptEngine. + auto cls = se::Class::create("SomeClass", ns, nullptr, _SE(js_SomeClass_constructor)); // If the registered class doesn't need a constructor, the last argument can be passed in with nullptr, it will make `new SomeClass();` illegal. + + // Define member functions, member properties. + cls->defineFunction("foo", _SE(js_SomeClass_foo)); + cls->defineProperty("xxx", _SE(js_SomeClass_get_xxx), _SE(js_SomeClass_set_xxx)); + + // Define finalize callback function + cls->defineFinalizeFunction(_SE(js_SomeClass_finalize)); + + // Install the class to JS virtual machine + cls->install(); + + // JSBClassType::registerClass is a helper function in the Cocos2D-X native binding code, which is not a part of the ScriptEngine. + JSBClassType::registerClass(cls); + + // Save the result to global variable for easily use in other places, for example class inheritence. + __jsb_ns_SomeClass_proto = cls->getProto(); + __jsb_ns_SomeClass_class = cls; + + // Set a property `yyy` with the string value `helloyyy` for each object instantiated by this class. + __jsb_ns_SomeClass_proto->setProperty("yyy", se::Value("helloyyy")); + + // Register static member variables and static member functions + se::Value ctorVal; + if (ns->getProperty("SomeClass", &ctorVal) && ctorVal.isObject()) + { + ctorVal.toObject()->setProperty("static_val", se::Value(200)); + ctorVal.toObject()->defineFunction("static_func", _SE(js_SomeClass_static_func)); + } + + // Clear JS exceptions + se::ScriptEngine::getInstance()->clearException(); + return true; +} +``` + +### How to Bind A CPP Callback Function + +```c++ +static bool js_SomeClass_setCallback(se::State& s) +{ + const auto& args = s.args(); + int argc = (int)args.size(); + if (argc >= 1) + { + ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject(); + + se::Value jsFunc = args[0]; + se::Value jsTarget = argc > 1 ? args[1] : se::Value::Undefined; + + if (jsFunc.isNullOrUndefined()) + { + cobj->setCallback(nullptr); + } + else + { + assert(jsFunc.isObject() && jsFunc.toObject()->isFunction()); + + // If the current SomeClass is a class that can be created by `new`, we use se::Object::attachObject to associate jsFunc with jsTarget to the current object. + s.thisObject()->attachObject(jsFunc.toObject()); + s.thisObject()->attachObject(jsTarget.toObject()); + + // If the current SomeClass class is a singleton, or a class that always has only one instance, we can not associate it with se::Object::attachObject. + // Instead, you must use se::Object::root, developers do not need to unroot since unroot operation will be triggered in the destruction of lambda which makes the se::Value jsFunc be destroyed, then se::Object destructor will do the unroot operation automatically. + // The binding function `js_cocos2dx_EventDispatcher_addCustomEventListener` implements it in this way because `EventDispatcher` is always a singleton. + // Using s.thisObject->attachObject(jsFunc.toObject); for binding addCustomEventListener will cause jsFunc and jsTarget varibales can't be released, which will result in memory leak. + + // jsFunc.toObject()->root(); + // jsTarget.toObject()->root(); + + cobj->setCallback([jsFunc, jsTarget](int counter){ + + // Add the following two lines of code in CPP callback function before passing data to the JS. + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + // + + se::ValueArray args; + args.push_back(se::Value(counter)); + + se::Object* target = jsTarget.isObject() ? jsTarget.toObject() : nullptr; + jsFunc.toObject()->call(args, target); + }); + } + + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} +SE_BIND_FUNC(js_SomeClass_setCallback) +``` + +After SomeClass is registered, you can use it in JS like the following: + +```js + var myObj = new ns.SomeClass(); + myObj.foo(); + ns.SomeClass.static_func(); + cc.log("ns.SomeClass.static_val: " + ns.SomeClass.static_val); + cc.log("Old myObj.xxx:" + myObj.xxx); + myObj.xxx = 1234; + cc.log("New myObj.xxx:" + myObj.xxx); + cc.log("myObj.yyy: " + myObj.yyy); + + var delegateObj = { + onCallback: function(counter) { + cc.log("Delegate obj, onCallback: " + counter + ", this.myVar: " + this.myVar); + this.setVar(); + }, + + setVar: function() { + this.myVar++; + }, + + myVar: 100 + }; + + myObj.setCallback(delegateObj.onCallback, delegateObj); + + setTimeout(function(){ + myObj.setCallback(null); + }, 6000); // Clear callback after 6 seconds. +``` + +There will be some logs outputed in console: + +``` +SomeClass::foo +SomeClass::static_func +ns.SomeClass.static_val: 200 +Old myObj.xxx:0 +New myObj.xxx:1234 +myObj.yyy: helloyyy +setCallback(cb) +Delegate obj, onCallback: 1, this.myVar: 100 +Delegate obj, onCallback: 2, this.myVar: 101 +Delegate obj, onCallback: 3, this.myVar: 102 +Delegate obj, onCallback: 4, this.myVar: 103 +Delegate obj, onCallback: 5, this.myVar: 104 +Delegate obj, onCallback: 6, this.myVar: 105 +setCallback(nullptr) +``` + +### How to Use The Helper Functions in Cocos2D-X Binding for Easiler Native<->JS Type Conversions + +The helper functions for native<->JS type conversions are located in `cocos/scripting/js-bindings/manual/jsb_conversions.h/.cpp`, it includes: + +#### Convert se::Value to CPP Type + +``` +bool seval_to_int32(const se::Value& v, int32_t* ret); +bool seval_to_uint32(const se::Value& v, uint32_t* ret); +bool seval_to_int8(const se::Value& v, int8_t* ret); +bool seval_to_uint8(const se::Value& v, uint8_t* ret); +bool seval_to_int16(const se::Value& v, int16_t* ret); +bool seval_to_uint16(const se::Value& v, uint16_t* ret); +bool seval_to_boolean(const se::Value& v, bool* ret); +bool seval_to_float(const se::Value& v, float* ret); +bool seval_to_double(const se::Value& v, double* ret); +bool seval_to_long(const se::Value& v, long* ret); +bool seval_to_ulong(const se::Value& v, unsigned long* ret); +bool seval_to_longlong(const se::Value& v, long long* ret); +bool seval_to_ssize(const se::Value& v, ssize_t* ret); +bool seval_to_std_string(const se::Value& v, std::string* ret); +bool seval_to_Vec2(const se::Value& v, cc::Vec2* pt); +bool seval_to_Vec3(const se::Value& v, cc::Vec3* pt); +bool seval_to_Vec4(const se::Value& v, cc::Vec4* pt); +bool seval_to_Mat4(const se::Value& v, cc::Mat4* mat); +bool seval_to_Size(const se::Value& v, cc::Size* size); +bool seval_to_Rect(const se::Value& v, cc::Rect* rect); +bool seval_to_Color3B(const se::Value& v, cc::Color3B* color); +bool seval_to_Color4B(const se::Value& v, cc::Color4B* color); +bool seval_to_Color4F(const se::Value& v, cc::Color4F* color); +bool seval_to_ccvalue(const se::Value& v, cc::Value* ret); +bool seval_to_ccvaluemap(const se::Value& v, cc::ValueMap* ret); +bool seval_to_ccvaluemapintkey(const se::Value& v, cc::ValueMapIntKey* ret); +bool seval_to_ccvaluevector(const se::Value& v, cc::ValueVector* ret); +bool sevals_variadic_to_ccvaluevector(const se::ValueArray& args, cc::ValueVector* ret); +bool seval_to_blendfunc(const se::Value& v, cc::BlendFunc* ret); +bool seval_to_std_vector_string(const se::Value& v, std::vector* ret); +bool seval_to_std_vector_int(const se::Value& v, std::vector* ret); +bool seval_to_std_vector_float(const se::Value& v, std::vector* ret); +bool seval_to_std_vector_Vec2(const se::Value& v, std::vector* ret); +bool seval_to_std_vector_Touch(const se::Value& v, std::vector* ret); +bool seval_to_std_map_string_string(const se::Value& v, std::map* ret); +bool seval_to_FontDefinition(const se::Value& v, cc::FontDefinition* ret); +bool seval_to_Acceleration(const se::Value& v, cc::Acceleration* ret); +bool seval_to_Quaternion(const se::Value& v, cc::Quaternion* ret); +bool seval_to_AffineTransform(const se::Value& v, cc::AffineTransform* ret); +//bool seval_to_Viewport(const se::Value& v, cc::experimental::Viewport* ret); +bool seval_to_Data(const se::Value& v, cc::Data* ret); +bool seval_to_DownloaderHints(const se::Value& v, cc::network::DownloaderHints* ret); +bool seval_to_TTFConfig(const se::Value& v, cc::TTFConfig* ret); + +//box2d seval to native convertion +bool seval_to_b2Vec2(const se::Value& v, b2Vec2* ret); +bool seval_to_b2AABB(const se::Value& v, b2AABB* ret); + +template +bool seval_to_native_ptr(const se::Value& v, T* ret); + +template +bool seval_to_Vector(const se::Value& v, cc::Vector* ret); + +template +bool seval_to_Map_string_key(const se::Value& v, cc::Map* ret) + +``` + +#### Convert C++ Type to se::Value + +```c++ +bool int8_to_seval(int8_t v, se::Value* ret); +bool uint8_to_seval(uint8_t v, se::Value* ret); +bool int32_to_seval(int32_t v, se::Value* ret); +bool uint32_to_seval(uint32_t v, se::Value* ret); +bool int16_to_seval(uint16_t v, se::Value* ret); +bool uint16_to_seval(uint16_t v, se::Value* ret); +bool boolean_to_seval(bool v, se::Value* ret); +bool float_to_seval(float v, se::Value* ret); +bool double_to_seval(double v, se::Value* ret); +bool long_to_seval(long v, se::Value* ret); +bool ulong_to_seval(unsigned long v, se::Value* ret); +bool longlong_to_seval(long long v, se::Value* ret); +bool ssize_to_seval(ssize_t v, se::Value* ret); +bool std_string_to_seval(const std::string& v, se::Value* ret); + +bool Vec2_to_seval(const cc::Vec2& v, se::Value* ret); +bool Vec3_to_seval(const cc::Vec3& v, se::Value* ret); +bool Vec4_to_seval(const cc::Vec4& v, se::Value* ret); +bool Mat4_to_seval(const cc::Mat4& v, se::Value* ret); +bool Size_to_seval(const cc::Size& v, se::Value* ret); +bool Rect_to_seval(const cc::Rect& v, se::Value* ret); +bool Color3B_to_seval(const cc::Color3B& v, se::Value* ret); +bool Color4B_to_seval(const cc::Color4B& v, se::Value* ret); +bool Color4F_to_seval(const cc::Color4F& v, se::Value* ret); +bool ccvalue_to_seval(const cc::Value& v, se::Value* ret); +bool ccvaluemap_to_seval(const cc::ValueMap& v, se::Value* ret); +bool ccvaluemapintkey_to_seval(const cc::ValueMapIntKey& v, se::Value* ret); +bool ccvaluevector_to_seval(const cc::ValueVector& v, se::Value* ret); +bool blendfunc_to_seval(const cc::BlendFunc& v, se::Value* ret); +bool std_vector_string_to_seval(const std::vector& v, se::Value* ret); +bool std_vector_int_to_seval(const std::vector& v, se::Value* ret); +bool std_vector_float_to_seval(const std::vector& v, se::Value* ret); +bool std_vector_Touch_to_seval(const std::vector& v, se::Value* ret); +bool std_map_string_string_to_seval(const std::map& v, se::Value* ret); +bool uniform_to_seval(const cc::Uniform* v, se::Value* ret); +bool FontDefinition_to_seval(const cc::FontDefinition& v, se::Value* ret); +bool Acceleration_to_seval(const cc::Acceleration* v, se::Value* ret); +bool Quaternion_to_seval(const cc::Quaternion& v, se::Value* ret); +bool ManifestAsset_to_seval(const cc::extension::ManifestAsset& v, se::Value* ret); +bool AffineTransform_to_seval(const cc::AffineTransform& v, se::Value* ret); +bool Data_to_seval(const cc::Data& v, se::Value* ret); +bool DownloadTask_to_seval(const cc::network::DownloadTask& v, se::Value* ret); + +template +bool Vector_to_seval(const cc::Vector& v, se::Value* ret); + +template +bool Map_string_key_to_seval(const cc::Map& v, se::Value* ret); + +template +bool native_ptr_to_seval(typename std::enable_if::value,T>::type* v, se::Value* ret, bool* isReturnCachedValue = nullptr); + +template +bool native_ptr_to_seval(typename std::enable_if::value,T>::type* v, se::Class* cls, se::Value* ret, bool* isReturnCachedValue = nullptr) + +template +bool native_ptr_to_seval(typename std::enable_if::value,T>::type* v, se::Value* ret, bool* isReturnCachedValue = nullptr); + +template +bool native_ptr_to_seval(typename std::enable_if::value,T>::type* v, se::Class* cls, se::Value* ret, bool* isReturnCachedValue = nullptr); + +template +bool native_ptr_to_rooted_seval(typename std::enable_if::value,T>::type* v, se::Value* ret, bool* isReturnCachedValue = nullptr); + +template +bool native_ptr_to_rooted_seval(typename std::enable_if::value,T>::type* v, se::Class* cls, se::Value* ret, bool* isReturnCachedValue = nullptr); + + +// Spine conversions +bool speventdata_to_seval(const spEventData& v, se::Value* ret); +bool spevent_to_seval(const spEvent& v, se::Value* ret); +bool spbonedata_to_seval(const spBoneData& v, se::Value* ret); +bool spbone_to_seval(const spBone& v, se::Value* ret); +bool spskeleton_to_seval(const spSkeleton& v, se::Value* ret); +bool spattachment_to_seval(const spAttachment& v, se::Value* ret); +bool spslotdata_to_seval(const spSlotData& v, se::Value* ret); +bool spslot_to_seval(const spSlot& v, se::Value* ret); +bool sptimeline_to_seval(const spTimeline& v, se::Value* ret); +bool spanimationstate_to_seval(const spAnimationState& v, se::Value* ret); +bool spanimation_to_seval(const spAnimation& v, se::Value* ret); +bool sptrackentry_to_seval(const spTrackEntry& v, se::Value* ret); + +// Box2d +bool b2Vec2_to_seval(const b2Vec2& v, se::Value* ret); +bool b2Manifold_to_seval(const b2Manifold* v, se::Value* ret); +bool b2AABB_to_seval(const b2AABB& v, se::Value* ret); + +``` + +Auxiliary conversion functions are not part of the abstraction layer (`Script Engine Wrapper`), they belong to the Cocos2D-X binding layer and are encapsulated to facilitate more convenient conversion in the binding code. +Each conversion function returns the type `bool` indicating whether the conversion was successful or not. Developers need to check the return value after calling these interfaces. + +You can know the specific usage directly according to interface names. The first parameter in the interface is input, and the second parameter is the output parameter. The usage is as follows: + +```c++ +se::Value v; +bool ok = int32_to_seval(100, &v); // The second parameter is the output parameter, passing in the address of the output parameter +``` + +```c++ +int32_t v; +bool ok = seval_to_int32(args[0], &v); // The second parameter is the output parameter, passing in the address of the output parameter +``` + +#### (IMPORTANT) Understand The Difference Between native\_ptr\_to\_seval and native\_ptr\_to\_rooted\_seval + +**Developers must understand the difference to make sure these conversion functions not being misused. In that case, JS memory leaks, which is really difficult to fix, could be avoided.** + +* `native_ptr_to_seval` is used in `JS control CPP object life cycle` mode. This method can be called when a `se::Value` needs to be obtained from a CPP object pointer at the binding code. Most subclasses in the Cocos2D-X that inherit from `cc::Ref` take this approach to get `se::Value`. Please remember, when the binding object, which is controlled by the JS object's life cycle, need to be converted to seval, use this method, otherwise consider using `native_ptr_to_rooted_seval`. +* `native_ptr_to_rooted_seval` is used in `CPP controlling JS object lifecycle` mode. In general, this method is used for object bindings in third-party libraries. This method will try to find the cached `se::Object` according the incoming CPP object pointer, if the cached `se::Object`is not exist, then it will create a rooted `se::Object` which isn't controlled by Garbage Collector and will always keep alive until `unroot` is called. Developers need to observe the release of the CPP object, and `unroot` `se::Object`. Please refer to the section introduces `spTrackEntry` binding (spTrackEntry_setDisposeCallback) described above. + + +## Automatic Binding + +### Configure Module .ini Files + +The configuration method is the same as that in Creator v1.6. The main points to note are: In Creator v1.7 `script_control_cpp` field is deprecated because `script_control_cpp` field affects the entire module. If the module needs to bind the `cc::Ref` subclass and non- `cc::Ref` class, the original binding configuration in v1.6 can not meet the demand. The new field introduced in v1.7 is `classes_owned_by_cpp`, which indicates which classes need to be controlled by the CPP object's life cycle. + +An additional, there is a configuration field in v1.7 is `persistent_classes` to indicate which classes are always present during game play, such as: `TextureCache`, `SpriteFrameCache`, `FileUtils`, `EventDispatcher`, `ActionManager`, `Scheduler`. + +Other fields are the same as v1.6. + +For more specific, please refer to the engine directory `tools/tojs/cocos2dx.ini` file. + +### Understand The Meaning of Each Field in The .ini file + +``` +# Module name +[cocos2d-x] + +# The prefix for callback functions and the binding file name. +prefix = engine + +# The namspace of the binding class attaches to. +target_namespace = jsb + +# Automatic binding tools is based on the Android NDK. The android_headers field configures the search path of Android header file. +android_headers = + +# Configure building parameters for Android. +android_flags = -target armv7-none-linux-androideabi -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -DANDROID -D__ANDROID_API__=14 -gcc-toolchain %(gcc_toolchain_dir)s --sysroot=%(androidndkdir)s/platforms/android-14/arch-arm -idirafter %(androidndkdir)s/sources/android/support/include -idirafter %(androidndkdir)s/sysroot/usr/include -idirafter %(androidndkdir)s/sysroot/usr/include/arm-linux-androideabi -idirafter %(clangllvmdir)s/lib64/clang/5.0/include -I%(androidndkdir)s/sources/ + +# Configure the search path for clang header file. +clang_headers = + +# Configure building parameters for clang +clang_flags = -nostdinc -x c++ -std=c++11 -fsigned-char -U__SSE__ + +# Configure the search path for Cocos2D-X header file +cocos_headers = -I%(cocosdir)s/cocos -I%(cocosdir)s/cocos/platform/android -I%(cocosdir)s/external/sources + +# Configure building parameters for Cocos2D-X +cocos_flags = -DANDROID -DCC_PLATFORM=3 -DCC_PLATFORM_IOS=1 -DCC_PLATFORM_MACOS=4 -DCC_PLATFORM_WINDOWS=2 -DCC_PLATFORM_ANDROID=3 + + +# Configure extra building parameters +extra_arguments = %(android_headers)s %(clang_headers)s %(cxxgenerator_headers)s %(cocos_headers)s %(android_flags)s %(clang_flags)s %(cocos_flags)s %(extra_flags)s + +# Which header files needed to be parsed +headers = %(cocosdir)s/cocos/cocos2d.h + +# Rename the header file in the generated binding code +replace_headers= + +# Which classes need to be bound, you can use regular expressions, separated by space. +classes = FileUtils$ SAXParser CanvasRenderingContext2D CanvasGradient Device + +# Which classes which use cc.Class.extend to inherit, separated by space. +classes_need_extend = + +# Which classes need to bind properties, separated by commas +field = + +# Which classes need to be skipped, separated by commas +skip = FileUtils::[getFileData setFilenameLookupDictionary destroyInstance getFullPathCache getContents], + SAXParser::[(?!(init))], + Device::[getDeviceMotionValue], + CanvasRenderingContext2D::[setCanvasBufferUpdatedCallback set_.+] + +# Which functions need to be renamed, separated by commas +rename_functions = FileUtils::[loadFilenameLookupDictionaryFromFile=loadFilenameLookup], + CanvasRenderingContext2D::[getImageData=_getImageData] + +# Which classes need to be renamed, separated by commas +rename_classes = SAXParser::PlistParser + +# Which classes do not have parents in JS +classes_have_no_parents = SAXParser + +# Which C++ base classes need to be skipped +base_classes_to_skip = Ref Clonable + +# Which classes are abstract classes which do not have a constructor in JS +abstract_classes = SAXParser Device + +# Which classes are singleton or always keep alive until game exits +persistent_classes = FileUtils + +# Which classes use `CPP object controls JS object's life cycle`, the unconfigured classes will use `JS controls CPP object's life cycle`. +classes_owned_by_cpp = +``` + +## Remote Debugging and Profile + +The remote debugging and profile are valid in debug mode, if you need to enable in release mode, you need to manually modify the macro in `cocos/scripting/js-bindings/jswrapper/config.h`. + +```c++ +#if defined(CC_DEBUG) && CC_DEBUG > 0 +#define SE_ENABLE_INSPECTOR 1 +#define SE_DEBUG 2 +#else +#define SE_ENABLE_INSPECTOR 0 +#define SE_DEBUG 0 +#endif +``` + +Change to: + +```c++ +#if 1 // Change to 1 to force enable remote debugging +#define SE_ENABLE_INSPECTOR 1 +#define SE_DEBUG 2 +#else +#define SE_ENABLE_INSPECTOR 0 +#define SE_DEBUG 0 +#endif +``` + +### Remote Debugging V8 in Chrome + +#### Windows + +* Compile, run the game (or run directly in the Creator simulator) +* Open with Chrome: [devtools://devtools/bundled/js_app.html?v8only=true&ws=127.0.0.1:6086/00010002-0003-4004-8005-000600070008](devtools://devtools/bundled/js_app.html?v8only=true&ws=127.0.0.1:6086/00010002-0003-4004-8005-000600070008) + +Breakpoint debugging: +![](v8-win32-debug.jpg) + +Catch JS Heap +![](v8-win32-memory.jpg) + +Profile +![](v8-win32-profile.jpg) + +#### Android + +* Make sure your Android device is on the same network as your PC or Mac +* Compile and run your game +* Open with Chrome: [devtools://devtools/bundled/js_app.html?v8only=true&ws=xxx.xxx.xxx.xxx:6086/00010002-0003-4004-8005-000600070008](devtools://devtools/bundled/js_app.html?v8only=true&ws=xxx.xxx.xxx.xxx:6086/00010002-0003-4004-8005-000600070008), `xxx.xxx.xxx.xxx` is the IP address of Android device +* The remote debugging interface is the same as debugging Windows. + + +### Remote Debugging JavaScriptCore in Safari + +#### macOS + +1. Open Safari on your Mac, Preferences -> Advanced -> Show Develop menu in menu bar +2. Add entitlements file to Xcode project, skip this step if entitlements exist. If it does not exist, open the App Sandbox in the Capabilities setting of the project, and then close again. At this point, the .entitlements file is automatically added to the project.![](jsc-entitlements.png). You also need to make sure the entitlements file is included in the Code Signing Entitlemenets option in the Build Setting. ![](jsc-entitlements-check.png) +3. Open the entitlements file, add com.apple.security.get-task-allow, the value type is Boolean, the value is YES. ![](jsc-security-key.png) +4. Signature: General -> Choose your Mac Project -> Signing -> Choose your Developer Certificate +5. Compile and run your game +6. If it is run directly in Creator's simulator, you can skip steps 2,3,4,5 +7. Click Safari menu, select Develop -> your Mac device name -> Cocos2d-x JSB will automatically open the Web Inspector page, and then you can set breakpoints, Timeline profile, console and other operations.![](jsc-mac-debug.png) ![](jsc-breakpoint.png) ![](jsc-timeline.png) + +**NOTE** + +If developers have to modify the engine source or merge some patches, they need to recompile the simulator, remember to reset the simulator project certificate. + +![](jsc-mac-simulator-sign.png) + +Then run `gulp gen-simulator` in terminal to generate simulator. + +#### iOS + +1. Open the iPhone Settings -> Safari -> Advanced -> Web Inspector +2. Add entitlements file to Xcode project, skip this step if entitlements exist. If it does not exist, open the App Sandbox in the Capabilities setting of the project, and then close again. At this point, the .entitlements file is automatically added to the project. You also need to make sure the entitlements file is included in the Code Signing Entitlemenets option in the Build Setting. (The illustration image is similar to step 2 of macOS) +3. Open the entitlements file, add com.apple.security.get-task-allow, the value type is Boolean, the value is YES. (The illustration image is similar to step 3 of macOS) +4. Signature: General -> Choose your iOS project -> Signing -> Choose your developer certificate +5. Compile and run your game +6. Click Safari menu, select Develop -> your iOS device name -> Cocos2d-x JSB will automatically open the Web Inspector page, and then you can set breakpoints, Timeline profile, console and other operations.(The illustration image is similar to step 7 of macOS) + +## Q & A + +### What's The Difference between se::Object::root/unroot and se::Object::incRef/decRef? + +`root`/`unroot` is used to control whether JS objects are controlled by GC, `root` means JS object should not be controlled by GC, `unroot` means it should be controlled by GC. For a `se::Object`, `root` and `unroot` can be called multiple times, `se::Object`'s internal `_rootCount` variables is used to indicate the count of `root` operation. When `unroot` is called and `_rootCount` reach **0**, the JS object associated with `se::Object` is handed over to the GC. Another situation is that if `se::Object` destructor is triggered and `_rootCount` is still greater than 0, it will force the JS object to be controlled by the GC. + +`incRef`/`decRef` is used to control the life cycle of `se::Object` CPP object. As mentioned in the previous section, it is recommended that you use `se::HandleObject` to control the manual creation of unbound objects's life cycle. So, in general, developers do not need to touch `incRef`/`decRef`. + + +### The Association and Disassociation of Object's Life Cycle + +Use se::Object::attachObject to associate object's life cycle. +Use se::Object::dettachObject to disassociate object's life cycle. + +`objA->attachObject(objB);` is similar as `objA .__ nativeRefs [index] = objB` in JS. Only when `objA` is garbage collected, `objB` will be possible garbage collected. +`objA->dettachObject(objB);` is similar as `delete objA.__nativeRefs[index];` in JS. After invoking dettachObject, objB's life cycle will not be controlled by objA + +### What's The Difference of Object Life Management between The Subclass of `cc::Ref` and non-`cc::Ref` class? + +The binding of `cc::Ref` subclass in the current engine adopts JS object controls the life cycle of CPP object. The advantage of doing so is to solve the `retain`/`release` problem that has been criticized in the JS layer. + +Non-`cc::Ref` class takes the way of CPP object controls the life of a JS object. This method requires that after CPP object is destroyed, it needs to notify the binding layer to call the `clearPrivateData`, `unroot`, and `decRef` methods corresponding to `se::Object`. JS code must be careful operation of the object, when there may be illegal object logic, use `cc.sys.isObjectValid` to determine whether the CPP object is released. + +### NOTE of Binding The Finalize Function for cc::Ref Subclass + +Calling any JS engine's API in a finalize callback can lead to a crash. Because the current engine is in garbage collection process, which can not be interrupted to deal with other operations. +Finalize callback is to tell the CPP layer to release the memory of the corresponding CPP object, we should not call any JS engine API in the CPP object's destructor either. + +#### But if that must be called, how should we deal with? + +In Cocos2D-X binding, if the native object's reference count is 1, we do not use the `release`, but using `autorelease` to delay CPP object's destructor to be executed at the end of frame. For instance: + +```c++ +static bool js_cocos2d_Sprite_finalize(se::State& s) +{ + CC_LOG_DEBUG("jsbindings: finalizing JS object %p (cc::Sprite)", s.nativeThisObject()); + cc::Sprite* cobj = (cc::Sprite*)s.nativeThisObject(); + if (cobj->getReferenceCount() == 1) + cobj->autorelease(); + else + cobj->release(); + return true; +} +SE_BIND_FINALIZE_FUNC(js_cocos2d_Sprite_finalize) +``` + +### Please DO NOT Assign A Subclass of cc::Ref on The Stack + +Subclasses of `cc::Ref` must be allocated on the heap, via `new`, and then released by` release`. In JS object's finalize callback function, we should use 'autorelease` or `release` to release. If it is allocated on the stack, the reference count is likely to be 0, and then calling `release` in finalize callback will result `delete` is invoked, which causing the program to crash. So in order to prevent this behavior from happening, developers can identify destructors as `protected` or` private` in the binding classes that inherit from `cc::Ref`, ensuring that this problem can be found during compilation. + +E.g: + +```c++ +class CC_EX_DLL EventAssetsManagerEx : public cc::EventCustom +{ +public: + ... + ... +private: + virtual ~EventAssetsManagerEx() {} + ... + ... +}; + +EventAssetsManagerEx event(...); // Compilation ERROR +dispatcher->dispatchEvent(&event); + +// Must modify to: + +EventAssetsManagerEx* event = new EventAssetsManagerEx(...); +dispatcher->dispatchEvent(event); +event->release(); +``` + + + +### How to Observe JS Exception? + +In AppDelegate.cpp, using `se::ScriptEngine::getInstance()->setExceptionCallback(...)` to set the callback of JS exception. + +```c++ +bool AppDelegate::applicationDidFinishLaunching() +{ + ... + ... + se::ScriptEngine* se = se::ScriptEngine::getInstance(); + + se->setExceptionCallback([](const char* location, const char* message, const char* stack){ + // Send exception information to server like Tencent Bugly. + // ... + // ... + }); + + jsb_register_all_modules(); + ... + ... + return true; +} + +``` + + + + diff --git a/cocos/bindings/docs/JSB2.0-learning-zh.md b/cocos/bindings/docs/JSB2.0-learning-zh.md new file mode 100644 index 0000000..ba0cb2e --- /dev/null +++ b/cocos/bindings/docs/JSB2.0-learning-zh.md @@ -0,0 +1,1184 @@ +# JSB 2.0 绑定教程 + +## 抽象层 + +### 架构 + +![](JSB2.0-Architecture.png) + +### 宏(Macro) + +抽象层必然会比直接使用 JS 引擎 API 的方式多占用一些 CPU 执行时间,如何把抽象层本身的开销降到最低成为设计的第一目标。 + +JS 绑定的大部分工作其实就是设定 JS 相关操作的 CPP 回调,在回调函数中关联 CPP 对象。其实主要包含如下两种类型: + +* 注册 JS 函数(包含全局函数,类构造函数、类析构函数、类成员函数,类静态成员函数),绑定一个 CPP 回调 +* 注册 JS 对象的属性读写访问器,分别绑定读与写的 CPP 回调 + +如何做到抽象层开销最小而且暴露统一的 API 供上层使用? + +以注册 JS 函数的回调定义为例,JavaScriptCore,SpiderMonkey,V8,ChakraCore 的定义各不相同,具体如下: + +**JavaScriptCore:** + +```c++ +JSValueRef JSB_foo_func( + JSContextRef _cx, + JSObjectRef _function, + JSObjectRef _thisObject, + size_t argc, + const JSValueRef _argv[], + JSValueRef* _exception + ); +``` + +**SpiderMonkey:** + +```c++ +bool JSB_foo_func( + JSContext* _cx, + unsigned argc, + JS::Value* _vp + ); +``` + +**V8:** + +```c++ +void JSB_foo_func( + const v8::FunctionCallbackInfo& v8args + ); +``` + +**ChakraCore:** + +```c++ +JsValueRef JSB_foo_func( + JsValueRef _callee, + bool _isConstructCall, + JsValueRef* _argv, + unsigned short argc, + void* _callbackState + ); +``` + +我们评估了几种方案,最终确定使用`宏`来抹平不同 JS 引擎回调函数定义与参数类型的不同,不管底层是使用什么引擎,开发者统一使用一种回调函数的定义。我们借鉴了 lua 的回调函数定义方式,抽象层所有的 JS 到 CPP 的回调函数的定义为: + +```c++ +bool foo(se::State& s) +{ + ... + ... +} +SE_BIND_FUNC(foo) // 此处以回调函数的定义为例 +``` + +开发者编写完回调函数后,记住使用 `SE_BIND_XXX` 系列的宏对回调函数进行包装。目前提供了如下几个宏: + +* SE\_BIND\_PROP_GET:包装一个 JS 对象属性读取的回调函数 +* SE\_BIND\_PROP_SET:包装一个 JS 对象属性写入的回调函数 +* SE\_BIND_FUNC:包装一个 JS 函数,可用于全局函数、类成员函数、类静态函数 +* SE\_DECLARE_FUNC:声明一个 JS 函数,一般在 .h 头文件中使用 +* SE\_BIND_CTOR:包装一个 JS 构造函数 +* SE\_BIND\_SUB\_CLS\_CTOR:包装一个 JS 子类的构造函数,此子类使用 cc.Class.extend 继承 Native 绑定类 +* SE\_FINALIZE_FUNC:包装一个 JS 对象被 GC 回收后的回调函数 +* SE\_DECLARE\_FINALIZE_FUNC:声明一个 JS 对象被 GC 回收后的回调函数 +* _SE:包装回调函数的名称,转义为每个 JS 引擎能够识别的回调函数的定义,注意,第一个字符为下划线,类似 Windows 下用的`_T("xxx")`来包装 Unicode 或者 MultiBytes 字符串 + +## API + +### CPP 命名空间(namespace) + +CPP 抽象层所有的类型都在 `se` 命名空间下,其为 ScriptEngine 的缩写。 + +### 类型 + +#### se::ScriptEngine + +se::ScriptEngine 为 JS 引擎的管理员,掌管 JS 引擎初始化、销毁、重启、Native 模块注册、加载脚本、强制垃圾回收、JS 异常清理、是否启用调试器。 +它是一个单例,可通过 se::ScriptEngine::getInstance() 得到对应的实例。 + +#### se::Value + +se::Value 可以被理解为 JS 变量在 CPP 层的引用。JS 变量有 `object`, `number`, `string`, `boolean`, `null`, `undefined` 六种类型,因此 se::Value 使用 `union` 包含 `object`, `number`, `string`, `boolean`4 种`有值类型`,`无值类型`: `null`, `undefined` 可由 `_type` 直接表示。 + +```c++ +namespace se { + class Value { + enum class Type : char + { + Undefined = 0, + Null, + Number, + Boolean, + String, + Object + }; + ... + ... + private: + union { + bool _boolean; + double _number; + std::string* _string; + Object* _object; + } _u; + + Type _type; + ... + ... + }; +} +``` + +如果 se::Value 中保存基础数据类型,比如 `number`,`string`,`boolean`,其内部是直接存储一份值副本。 +`object` 的存储比较特殊,是通过 `se::Object*` 对 JS 对象的弱引用(weak reference)。 + +#### se::Object + +se::Object 继承于 se::RefCounter 引用计数管理类。目前抽象层中只有 se::Object 继承于 se::RefCounter。 +上一小节我们说到,se::Object 是保存了对 JS 对象的弱引用,这里笔者有必要解释一下为什么是弱引用。 + +* 原因一:JS 对象控制 CPP 对象的生命周期的需要 + +当在脚本层中通过 `var sp = new cc.Sprite("a.png");` 创建了一个 Sprite 后,在构造回调函数绑定中我们会创建一个 se::Object 并保留在一个全局的 map (NativePtrToObjectMap) 中,此 map 用于查询 `cc::Sprite*` 指针获取对应的 JS 对象 `se::Object*` 。 + +```c++ +static bool js_cocos2d_Sprite_finalize(se::State& s) +{ + CC_LOG_DEBUG("jsbindings: finalizing JS object %p (cc::Sprite)", s.nativeThisObject()); + cc::Sprite* cobj = (cc::Sprite*)s.nativeThisObject(); + if (cobj->getReferenceCount() == 1) + cobj->autorelease(); + else + cobj->release(); + return true; +} +SE_BIND_FINALIZE_FUNC(js_cocos2d_Sprite_finalize) + +static bool js_cocos2dx_Sprite_constructor(se::State& s) +{ + cc::Sprite* cobj = new (std::nothrow) cc::Sprite(); // cobj 将在 finalize 函数中被释放 + s.thisObject()->setPrivateData(cobj); // setPrivateData 内部会去保存 cobj 到 NativePtrToObjectMap 中 + return true; +} +SE_BIND_CTOR(js_cocos2dx_Sprite_constructor, __jsb_cocos2d_Sprite_class, js_cocos2d_Sprite_finalize) +``` + +设想如果强制要求 se::Object 为 JS 对象的强引用(strong reference),即让 JS 对象不受 GC 控制,由于 se::Object 一直存在于 map 中,finalize 回调将永远无法被触发,从而导致内存泄露。 + +正是由于 se::Object 保存的是 JS 对象的弱引用,JS 对象控制 CPP 对象的生命周期才能够实现。以上代码中,当 JS 对象被释放后,会触发 finalize 回调,开发者只需要在 `js_cocos2d_Sprite_finalize` 中释放对应的 c++ 对象即可,se::Object 的释放已经被包含在 `SE_BIND_FINALIZE_FUNC` 宏中自动处理,开发者无需管理在`JS 对象控制 CPP 对象`模式中 se::Object 的释放,但是在 `CPP 对象控制 JS 对象` 模式中,开发者需要管理对 se::Object 的释放,具体下一节中会举例说明。 + +* 原因二:更加灵活,手动调用 root 方法以支持强引用 + +se::Object 中提供了 root/unroot 方法供开发者调用,root 会把 JS 对象放入到不受 GC 扫描到的区域,调用 root 后,se::Object 就强引用了 JS 对象,只有当 unroot 被调用,或者 se::Object 被释放后,JS 对象才会放回到受 GC 扫描到的区域。 + +一般情况下,如果对象是非 `cc::Ref` 的子类,会采用 CPP 对象控制 JS 对象的生命周期的方式去绑定。引擎内 spine, dragonbones, box2d,anysdk 等第三方库的绑定就是采用此方式。当 CPP 对象被释放的时候,需要在 NativePtrToObjectMap 中查找对应的 se::Object,然后手动 unroot 和 decRef。以 spine 中 spTrackEntry 的绑定为例: + +```c++ +spTrackEntry_setDisposeCallback([](spTrackEntry* entry){ + // spTrackEntry 的销毁回调 + se::Object* seObj = nullptr; + + auto iter = se::NativePtrToObjectMap::find(entry); + if (iter != se::NativePtrToObjectMap::end()) + { + // 保存 se::Object 指针,用于在下面的 cleanup 函数中释放其内存 + seObj = iter->second; + // Native 对象 entry 的内存已经被释放,因此需要立马解除 Native 对象与 JS 对象的关联。 + // 如果解除引用关系放在下面的 cleanup 函数中处理,有可能触发 se::Object::setPrivateData 中 + // 的断言,因为新生成的 Native 对象的地址可能与当前对象相同,而 cleanup 可能被延迟到帧结束前执行。 + se::NativePtrToObjectMap::erase(iter); + } + else + { + return; + } + + auto cleanup = [seObj](){ + + auto se = se::ScriptEngine::getInstance(); + if (!se->isValid() || se->isInCleanup()) + return; + + se::AutoHandleScope hs; + se->clearException(); + + // 由于上面逻辑已经把映射关系解除了,这里传入 false 表示不用再次解除映射关系, + // 因为当前 seObj 的 private data 可能已经是另外一个不同的对象 + seObj->clearPrivateData(false); + seObj->unroot(); // unroot,使 JS 对象受 GC 管理 + seObj->decRef(); // 释放 se::Object + }; + + // 确保不再垃圾回收中去操作 JS 引擎的 API + if (!se::ScriptEngine::getInstance()->isGarbageCollecting()) + { + cleanup(); + } + else + { // 如果在垃圾回收,把清理任务放在帧结束中进行 + CleanupTask::pushTaskToAutoReleasePool(cleanup); + } + }); +``` + + +__对象类型__ + +绑定对象的创建已经被隐藏在对应的 `SE_BIND_CTOR` 和 `SE_BIND_SUB_CLS_CTOR` 函数中,开发者在绑定回调中如果需要用到当前对象对应的 se::Object,只需要通过 s.thisObject() 即可获取。其中 s 为 se::State 类型,具体会在后续章节中说明。 + +此外,se::Object 目前支持以下几种对象的手动创建: + +* Plain Object : 通过 se::Object::createPlainObject 创建,类似 JS 中的 `var a = {};` +* Array Object : 通过 se::Object::createArrayObject 创建,类似 JS 中的 `var a = [];` +* Uint8 Typed Array Object : 通过 se::Object::createTypedArray 创建,类似 JS 中的 `var a = new Uint8Array(buffer);` +* Array Buffer Object : 通过 se::Object::createArrayBufferObject,类似 JS 中的 `var a = new ArrayBuffer(len);` + +__手动创建对象的释放__ + +se::Object::createXXX 方法与 cocos2d-x 中的 create 方法不同,抽象层是完全独立的一个模块,并不依赖与 cocos2d-x 的 autorelease 机制。虽然 se::Object 也是继承引用计数类,但开发者需要处理**手动创建出来的对象**的释放。 + +```c++ +se::Object* obj = se::Object::createPlainObject(); +... +... +obj->decRef(); // 释放引用,避免内存泄露 +``` + +#### se::HandleObject (推荐的管理手动创建对象的辅助类) + +* 在比较复杂的逻辑中使用手动创建对象,开发者往往会忘记在不同的逻辑中处理 decRef + +```c++ +bool foo() +{ + se::Object* obj = se::Object::createPlainObject(); + if (var1) + return false; // 这里直接返回了,忘记做 decRef 释放操作 + + if (var2) + return false; // 这里直接返回了,忘记做 decRef 释放操作 + ... + ... + obj->decRef(); + return true; +} +``` + +就算在不同的返回条件分支中加上了 decRef 也会导致逻辑复杂,难以维护,如果后期加入另外一个返回分支,很容易忘记 decRef。 + +* JS 引擎在 se::Object::createXXX 后,如果由于某种原因 JS 引擎做了 GC 操作,导致后续使用的 se::Object 内部引用了一个非法指针,引发程序崩溃 + +为了解决上述两个问题,抽象层定义了一个辅助管理**手动创建对象**的类型,即 `se::HandleObject` 。 + +`se::HandleObject` 是一个辅助类,用于更加简单地管理手动创建的 se::Object 对象的释放、root 和 unroot 操作。 +以下两种代码写法是等价的,使用 se::HandleObject 的代码量明显少很多,而且更加安全。 + +```c++ + { + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty(...); + otherObject->setProperty("foo", se::Value(obj)); + } + + 等价于: + + { + se::Object* obj = se::Object::createPlainObject(); + obj->root(); // 在手动创建完对象后立马 root,防止对象被 GC + + obj->setProperty(...); + otherObject->setProperty("foo", se::Value(obj)); + + obj->unroot(); // 当对象被使用完后,调用 unroot + obj->decRef(); // 引用计数减一,避免内存泄露 + } +``` + +注意: + +* 不要尝试使用 se::HandleObject 创建一个 native 与 JS 的绑定对象,在 JS 控制 CPP 的模式中,绑定对象的释放会被抽象层自动处理,在 CPP 控制 JS 的模式中,前一章节中已经有描述了。 +* se::HandleObject 对象只能够在栈上被分配,而且栈上构造的时候必须传入一个 se::Object 指针。 + + +#### se::Class + +se::Class 用于暴露 CPP 类到 JS 中,它会在 JS 中创建一个对应名称的 constructor function。 + +它有如下方法: + +* `static se::Class* create(className, obj, parentProto, ctor)`: 创建一个 Class,注册成功后,在 JS 层中可以通过`var xxx = new SomeClass();`的方式创建一个对象 +* `bool defineFunction(name, func)`: 定义 Class 中的成员函数 +* `bool defineProperty(name, getter, setter)`: 定义 Class 属性读写器 +* `bool defineStaticFunction(name, func)`: 定义 Class 的静态成员函数,可通过 SomeClass.foo() 这种非 new 的方式访问,与类实例对象无关 +* `bool defineStaticProperty(name, getter, setter)`: 定义 Class 的静态属性读写器,可通过 SomeClass.propertyA 直接读写,与类实例对象无关 +* `bool defineFinalizeFunction(func)`: 定义 JS 对象被 GC 后的 CPP 回调 +* `bool install()`: 注册此类到 JS 虚拟机中 +* `Object* getProto()`: 获取注册到 JS 中的类(其实是 JS 的 constructor)的 prototype 对象,类似 function Foo(){}的 Foo.prototype +* `const char* getName() const`: 获取当前 Class 的名称 + +**注意:** + +Class 类型创建后,不需要手动释放内存,它会被封装层自动处理。 + +更具体 API 说明可以翻看 API 文档或者代码注释 + +#### se::AutoHandleScope + +se::AutoHandleScope 对象类型完全是为了解决 V8 的兼容问题而引入的概念。 +V8 中,当有 CPP 函数中需要触发 JS 相关操作,比如调用 JS 函数,访问 JS 属性等任何调用 v8::Local<> 的操作,V8 强制要求在调用这些操作前必须存在一个 v8::HandleScope 作用域,否则会引发程序崩溃。 + +因此抽象层中引入了 se::AutoHandleScope 的概念,其只在 V8 上有实现,其他 JS 引擎目前都只是空实现。 + +开发者需要记住,在任何代码执行中,需要调用 JS 的逻辑前,声明一个 se::AutoHandleScope 即可,比如: + +```c++ +class SomeClass { + void update(float dt) { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + se::Object* obj = ...; + obj->setProperty(...); + ... + ... + obj->call(...); + } +}; +``` + +#### se::State + +之前章节我们有提及 State 类型,它是绑定回调中的一个环境,我们通过 se::State 可以取得当前的 CPP 指针、se::Object 对象指针、参数列表、返回值引用。 + +```c++ +bool foo(se::State& s) +{ + // 获取 native 对象指针 + SomeClass* cobj = (SomeClass*)s.nativeThisObject(); + // 获取 se::Object 对象指针 + se::Object* thisObject = s.thisObject(); + // 获取参数列表 + const se::ValueArray& args = s.args(); + // 设置返回值 + s.rval().setInt32(100); + return true; +} +SE_BIND_FUNC(foo) +``` + +## 抽象层依赖 Cocos 引擎么? + +不依赖。 + +ScriptEngine 这层设计之初就将其定义为一个独立模块,完全不依赖 Cocos 引擎。开发者完整可以通过 copy、paste 把 cocos/scripting/js-bindings/jswrapper 下的所有抽象层源码拷贝到其他项目中直接使用。 + +## 手动绑定 + +### 回调函数声明 + +```c++ +static bool Foo_balabala(se::State& s) +{ + const auto& args = s.args(); + int argc = (int)args.size(); + + if (argc >= 2) // 这里约定参数个数必须大于等于 2,否则抛出错误到 JS 层且返回 false + { + ... + ... + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 2); + return false; +} + +// 如果是绑定函数,则用 SE_BIND_FUNC,构造函数、析构函数、子类构造函数等类似 +SE_BIND_FUNC(Foo_balabala) +``` + +### 为 JS 对象设置一个属性值 + +```c++ +se::Object* globalObj = se::ScriptEngine::getInstance()->getGlobalObject(); // 这里为了演示方便,获取全局对象 +globalObj->setProperty("foo", se::Value(100)); // 给全局对象设置一个 foo 属性,值为 100 +``` + +在 JS 中就可以直接使用 foo 这个全局变量了 + +```js +cc.log("foo value: " + foo); // 打印出 foo value: 100 +``` + +### 为 JS 对象定义一个属性读写回调 + +```c++ +// 全局对象的 foo 属性的读回调 +static bool Global_get_foo(se::State& s) +{ + NativeObj* cobj = (NativeObj*)s.nativeThisObject(); + int32_t ret = cobj->getValue(); + s.rval().setInt32(ret); + return true; +} +SE_BIND_PROP_GET(Global_get_foo) + +// 全局对象的 foo 属性的写回调 +static bool Global_set_foo(se::State& s) +{ + const auto& args = s.args(); + int argc = (int)args.size(); + if (argc >= 1) + { + NativeObj* cobj = (NativeObj*)s.nativeThisObject(); + int32_t arg1 = args[0].toInt32(); + cobj->setValue(arg1); + // void 类型的函数,无需设置 s.rval,未设置默认返回 undefined 给 JS 层 + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} +SE_BIND_PROP_SET(Global_set_foo) + +void some_func() +{ + se::Object* globalObj = se::ScriptEngine::getInstance()->getGlobalObject(); // 这里为了演示方便,获取全局对象 + globalObj->defineProperty("foo", _SE(Global_get_foo), _SE(Global_set_foo)); // 使用_SE 宏包装一下具体的函数名称 +} +``` + +### 为 JS 对象设置一个函数 + +```c++ +static bool Foo_function(se::State& s) +{ + ... + ... +} +SE_BIND_FUNC(Foo_function) + +void some_func() +{ + se::Object* globalObj = se::ScriptEngine::getInstance()->getGlobalObject(); // 这里为了演示方便,获取全局对象 + globalObj->defineFunction("foo", _SE(Foo_function)); // 使用_SE 宏包装一下具体的函数名称 +} + +``` + +### 注册一个 CPP 类到 JS 虚拟机中 + +```c++ +static se::Object* __jsb_ns_SomeClass_proto = nullptr; +static se::Class* __jsb_ns_SomeClass_class = nullptr; + +namespace ns { + class SomeClass + { + public: + SomeClass() + : xxx(0) + {} + + void foo() { + printf("SomeClass::foo\n"); + + Application::getInstance()->getScheduler()->schedule([this](float dt){ + static int counter = 0; + ++counter; + if (_cb != nullptr) + _cb(counter); + }, this, 1.0f, CC_REPEAT_FOREVER, 0.0f, false, "iamkey"); + } + + static void static_func() { + printf("SomeClass::static_func\n"); + } + + void setCallback(const std::function& cb) { + _cb = cb; + if (_cb != nullptr) + { + printf("setCallback(cb)\n"); + } + else + { + printf("setCallback(nullptr)\n"); + } + } + + int xxx; + private: + std::function _cb; + }; +} // namespace ns { + +static bool js_SomeClass_finalize(se::State& s) +{ + ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject(); + delete cobj; + return true; +} +SE_BIND_FINALIZE_FUNC(js_SomeClass_finalize) + +static bool js_SomeClass_constructor(se::State& s) +{ + ns::SomeClass* cobj = new ns::SomeClass(); + s.thisObject()->setPrivateData(cobj); + return true; +} +SE_BIND_CTOR(js_SomeClass_constructor, __jsb_ns_SomeClass_class, js_SomeClass_finalize) + +static bool js_SomeClass_foo(se::State& s) +{ + ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject(); + cobj->foo(); + return true; +} +SE_BIND_FUNC(js_SomeClass_foo) + +static bool js_SomeClass_get_xxx(se::State& s) +{ + ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject(); + s.rval().setInt32(cobj->xxx); + return true; +} +SE_BIND_PROP_GET(js_SomeClass_get_xxx) + +static bool js_SomeClass_set_xxx(se::State& s) +{ + const auto& args = s.args(); + int argc = (int)args.size(); + if (argc > 0) + { + ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject(); + cobj->xxx = args[0].toInt32(); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} +SE_BIND_PROP_SET(js_SomeClass_set_xxx) + +static bool js_SomeClass_static_func(se::State& s) +{ + ns::SomeClass::static_func(); + return true; +} +SE_BIND_FUNC(js_SomeClass_static_func) + +bool js_register_ns_SomeClass(se::Object* global) +{ + // 保证 namespace 对象存在 + se::Value nsVal; + if (!global->getProperty("ns", &nsVal)) + { + // 不存在则创建一个 JS 对象,相当于 var ns = {}; + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + + // 将 ns 对象挂载到 global 对象中,名称为 ns + global->setProperty("ns", nsVal); + } + se::Object* ns = nsVal.toObject(); + + // 创建一个 Class 对象,开发者无需考虑 Class 对象的释放,其交由 ScriptEngine 内部自动处理 + auto cls = se::Class::create("SomeClass", ns, nullptr, _SE(js_SomeClass_constructor)); // 如果无构造函数,最后一个参数可传入 nullptr,则这个类在 JS 中无法被 new SomeClass()出来 + + // 为这个 Class 对象定义成员函数、属性、静态函数、析构函数 + cls->defineFunction("foo", _SE(js_SomeClass_foo)); + cls->defineProperty("xxx", _SE(js_SomeClass_get_xxx), _SE(js_SomeClass_set_xxx)); + + cls->defineFinalizeFunction(_SE(js_SomeClass_finalize)); + + // 注册类型到 JS VirtualMachine 的操作 + cls->install(); + + // JSBClassType 为 Cocos 引擎绑定层封装的类型注册的辅助函数,此函数不属于 ScriptEngine 这层 + JSBClassType::registerClass(cls); + + // 保存注册的结果,便于其他地方使用,比如类继承 + __jsb_ns_SomeClass_proto = cls->getProto(); + __jsb_ns_SomeClass_class = cls; + + // 为每个此 Class 实例化出来的对象附加一个属性 + __jsb_ns_SomeClass_proto->setProperty("yyy", se::Value("helloyyy")); + + // 注册静态成员变量和静态成员函数 + se::Value ctorVal; + if (ns->getProperty("SomeClass", &ctorVal) && ctorVal.isObject()) + { + ctorVal.toObject()->setProperty("static_val", se::Value(200)); + ctorVal.toObject()->defineFunction("static_func", _SE(js_SomeClass_static_func)); + } + + // 清空异常 + se::ScriptEngine::getInstance()->clearException(); + return true; +} +``` + +### 如何绑定 CPP 接口中的回调函数? + +```c++ +static bool js_SomeClass_setCallback(se::State& s) +{ + const auto& args = s.args(); + int argc = (int)args.size(); + if (argc >= 1) + { + ns::SomeClass* cobj = (ns::SomeClass*)s.nativeThisObject(); + + se::Value jsFunc = args[0]; + se::Value jsTarget = argc > 1 ? args[1] : se::Value::Undefined; + + if (jsFunc.isNullOrUndefined()) + { + cobj->setCallback(nullptr); + } + else + { + assert(jsFunc.isObject() && jsFunc.toObject()->isFunction()); + + // 如果当前 SomeClass 是可以被 new 出来的类,我们 使用 se::Object::attachObject 把 jsFunc 和 jsTarget 关联到当前对象中 + s.thisObject()->attachObject(jsFunc.toObject()); + s.thisObject()->attachObject(jsTarget.toObject()); + + // 如果当前 SomeClass 类是一个单例类,或者永远只有一个实例的类,我们不能用 se::Object::attachObject 去关联 + // 必须使用 se::Object::root,开发者无需关系 unroot,unroot 的操作会随着 lambda 的销毁触发 jsFunc 的析构,在 se::Object 的析构函数中进行 unroot 操作。 + // js_cocos2dx_EventDispatcher_addCustomEventListener 的绑定代码就是使用此方式,因为 EventDispatcher 始终只有一个实例, + // 如果使用 s.thisObject->attachObject(jsFunc.toObject);会导致对应的 func 和 target 永远无法被释放,引发内存泄露。 + + // jsFunc.toObject()->root(); + // jsTarget.toObject()->root(); + + cobj->setCallback([jsFunc, jsTarget](int counter){ + + // CPP 回调函数中要传递数据给 JS 或者调用 JS 函数,在回调函数开始需要添加如下两行代码。 + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + se::ValueArray args; + args.push_back(se::Value(counter)); + + se::Object* target = jsTarget.isObject() ? jsTarget.toObject() : nullptr; + jsFunc.toObject()->call(args, target); + }); + } + + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1); + return false; +} +SE_BIND_FUNC(js_SomeClass_setCallback) +``` + +SomeClass 类注册后,就可以在 JS 中这样使用了: + +```js + var myObj = new ns.SomeClass(); + myObj.foo(); + ns.SomeClass.static_func(); + cc.log("ns.SomeClass.static_val: " + ns.SomeClass.static_val); + cc.log("Old myObj.xxx:" + myObj.xxx); + myObj.xxx = 1234; + cc.log("New myObj.xxx:" + myObj.xxx); + cc.log("myObj.yyy: " + myObj.yyy); + + var delegateObj = { + onCallback: function(counter) { + cc.log("Delegate obj, onCallback: " + counter + ", this.myVar: " + this.myVar); + this.setVar(); + }, + + setVar: function() { + this.myVar++; + }, + + myVar: 100 + }; + + myObj.setCallback(delegateObj.onCallback, delegateObj); + + setTimeout(function(){ + myObj.setCallback(null); + }, 6000); // 6 秒后清空 callback +``` + +Console 中会输出: + +``` +SomeClass::foo +SomeClass::static_func +ns.SomeClass.static_val: 200 +Old myObj.xxx:0 +New myObj.xxx:1234 +myObj.yyy: helloyyy +setCallback(cb) +Delegate obj, onCallback: 1, this.myVar: 100 +Delegate obj, onCallback: 2, this.myVar: 101 +Delegate obj, onCallback: 3, this.myVar: 102 +Delegate obj, onCallback: 4, this.myVar: 103 +Delegate obj, onCallback: 5, this.myVar: 104 +Delegate obj, onCallback: 6, this.myVar: 105 +setCallback(nullptr) +``` + +### 如何使用 cocos2d-x bindings 这层的类型转换辅助函数? + +类型转换辅助函数位于`cocos/scripting/js-bindings/manual/jsb_conversions.h/.cpp`中,其包含: + +#### se::Value 转换为 C++ 类型 +``` +bool seval_to_int32(const se::Value& v, int32_t* ret); +bool seval_to_uint32(const se::Value& v, uint32_t* ret); +bool seval_to_int8(const se::Value& v, int8_t* ret); +bool seval_to_uint8(const se::Value& v, uint8_t* ret); +bool seval_to_int16(const se::Value& v, int16_t* ret); +bool seval_to_uint16(const se::Value& v, uint16_t* ret); +bool seval_to_boolean(const se::Value& v, bool* ret); +bool seval_to_float(const se::Value& v, float* ret); +bool seval_to_double(const se::Value& v, double* ret); +bool seval_to_long(const se::Value& v, long* ret); +bool seval_to_ulong(const se::Value& v, unsigned long* ret); +bool seval_to_longlong(const se::Value& v, long long* ret); +bool seval_to_ssize(const se::Value& v, ssize_t* ret); +bool seval_to_std_string(const se::Value& v, std::string* ret); +bool seval_to_Vec2(const se::Value& v, cc::Vec2* pt); +bool seval_to_Vec3(const se::Value& v, cc::Vec3* pt); +bool seval_to_Vec4(const se::Value& v, cc::Vec4* pt); +bool seval_to_Mat4(const se::Value& v, cc::Mat4* mat); +bool seval_to_Size(const se::Value& v, cc::Size* size); +bool seval_to_Rect(const se::Value& v, cc::Rect* rect); +bool seval_to_Color3B(const se::Value& v, cc::Color3B* color); +bool seval_to_Color4B(const se::Value& v, cc::Color4B* color); +bool seval_to_Color4F(const se::Value& v, cc::Color4F* color); +bool seval_to_ccvalue(const se::Value& v, cc::Value* ret); +bool seval_to_ccvaluemap(const se::Value& v, cc::ValueMap* ret); +bool seval_to_ccvaluemapintkey(const se::Value& v, cc::ValueMapIntKey* ret); +bool seval_to_ccvaluevector(const se::Value& v, cc::ValueVector* ret); +bool sevals_variadic_to_ccvaluevector(const se::ValueArray& args, cc::ValueVector* ret); +bool seval_to_blendfunc(const se::Value& v, cc::BlendFunc* ret); +bool seval_to_std_vector_string(const se::Value& v, std::vector* ret); +bool seval_to_std_vector_int(const se::Value& v, std::vector* ret); +bool seval_to_std_vector_float(const se::Value& v, std::vector* ret); +bool seval_to_std_vector_Vec2(const se::Value& v, std::vector* ret); +bool seval_to_std_vector_Touch(const se::Value& v, std::vector* ret); +bool seval_to_std_map_string_string(const se::Value& v, std::map* ret); +bool seval_to_FontDefinition(const se::Value& v, cc::FontDefinition* ret); +bool seval_to_Acceleration(const se::Value& v, cc::Acceleration* ret); +bool seval_to_Quaternion(const se::Value& v, cc::Quaternion* ret); +bool seval_to_AffineTransform(const se::Value& v, cc::AffineTransform* ret); +//bool seval_to_Viewport(const se::Value& v, cc::experimental::Viewport* ret); +bool seval_to_Data(const se::Value& v, cc::Data* ret); +bool seval_to_DownloaderHints(const se::Value& v, cc::network::DownloaderHints* ret); +bool seval_to_TTFConfig(const se::Value& v, cc::TTFConfig* ret); + +//box2d seval to native convertion +bool seval_to_b2Vec2(const se::Value& v, b2Vec2* ret); +bool seval_to_b2AABB(const se::Value& v, b2AABB* ret); + +template +bool seval_to_native_ptr(const se::Value& v, T* ret); + +template +bool seval_to_Vector(const se::Value& v, cc::Vector* ret); + +template +bool seval_to_Map_string_key(const se::Value& v, cc::Map* ret) + +``` + +#### C++ 类型转换为 se::Value + +```c++ +bool int8_to_seval(int8_t v, se::Value* ret); +bool uint8_to_seval(uint8_t v, se::Value* ret); +bool int32_to_seval(int32_t v, se::Value* ret); +bool uint32_to_seval(uint32_t v, se::Value* ret); +bool int16_to_seval(uint16_t v, se::Value* ret); +bool uint16_to_seval(uint16_t v, se::Value* ret); +bool boolean_to_seval(bool v, se::Value* ret); +bool float_to_seval(float v, se::Value* ret); +bool double_to_seval(double v, se::Value* ret); +bool long_to_seval(long v, se::Value* ret); +bool ulong_to_seval(unsigned long v, se::Value* ret); +bool longlong_to_seval(long long v, se::Value* ret); +bool ssize_to_seval(ssize_t v, se::Value* ret); +bool std_string_to_seval(const std::string& v, se::Value* ret); + +bool Vec2_to_seval(const cc::Vec2& v, se::Value* ret); +bool Vec3_to_seval(const cc::Vec3& v, se::Value* ret); +bool Vec4_to_seval(const cc::Vec4& v, se::Value* ret); +bool Mat4_to_seval(const cc::Mat4& v, se::Value* ret); +bool Size_to_seval(const cc::Size& v, se::Value* ret); +bool Rect_to_seval(const cc::Rect& v, se::Value* ret); +bool Color3B_to_seval(const cc::Color3B& v, se::Value* ret); +bool Color4B_to_seval(const cc::Color4B& v, se::Value* ret); +bool Color4F_to_seval(const cc::Color4F& v, se::Value* ret); +bool ccvalue_to_seval(const cc::Value& v, se::Value* ret); +bool ccvaluemap_to_seval(const cc::ValueMap& v, se::Value* ret); +bool ccvaluemapintkey_to_seval(const cc::ValueMapIntKey& v, se::Value* ret); +bool ccvaluevector_to_seval(const cc::ValueVector& v, se::Value* ret); +bool blendfunc_to_seval(const cc::BlendFunc& v, se::Value* ret); +bool std_vector_string_to_seval(const std::vector& v, se::Value* ret); +bool std_vector_int_to_seval(const std::vector& v, se::Value* ret); +bool std_vector_float_to_seval(const std::vector& v, se::Value* ret); +bool std_vector_Touch_to_seval(const std::vector& v, se::Value* ret); +bool std_map_string_string_to_seval(const std::map& v, se::Value* ret); +bool uniform_to_seval(const cc::Uniform* v, se::Value* ret); +bool FontDefinition_to_seval(const cc::FontDefinition& v, se::Value* ret); +bool Acceleration_to_seval(const cc::Acceleration* v, se::Value* ret); +bool Quaternion_to_seval(const cc::Quaternion& v, se::Value* ret); +bool ManifestAsset_to_seval(const cc::extension::ManifestAsset& v, se::Value* ret); +bool AffineTransform_to_seval(const cc::AffineTransform& v, se::Value* ret); +bool Data_to_seval(const cc::Data& v, se::Value* ret); +bool DownloadTask_to_seval(const cc::network::DownloadTask& v, se::Value* ret); + +template +bool Vector_to_seval(const cc::Vector& v, se::Value* ret); + +template +bool Map_string_key_to_seval(const cc::Map& v, se::Value* ret); + +template +bool native_ptr_to_seval(typename std::enable_if::value,T>::type* v, se::Value* ret, bool* isReturnCachedValue = nullptr); + +template +bool native_ptr_to_seval(typename std::enable_if::value,T>::type* v, se::Class* cls, se::Value* ret, bool* isReturnCachedValue = nullptr) + +template +bool native_ptr_to_seval(typename std::enable_if::value,T>::type* v, se::Value* ret, bool* isReturnCachedValue = nullptr); + +template +bool native_ptr_to_seval(typename std::enable_if::value,T>::type* v, se::Class* cls, se::Value* ret, bool* isReturnCachedValue = nullptr); + +template +bool native_ptr_to_rooted_seval(typename std::enable_if::value,T>::type* v, se::Value* ret, bool* isReturnCachedValue = nullptr); + +template +bool native_ptr_to_rooted_seval(typename std::enable_if::value,T>::type* v, se::Class* cls, se::Value* ret, bool* isReturnCachedValue = nullptr); + + +// Spine conversions +bool speventdata_to_seval(const spEventData& v, se::Value* ret); +bool spevent_to_seval(const spEvent& v, se::Value* ret); +bool spbonedata_to_seval(const spBoneData& v, se::Value* ret); +bool spbone_to_seval(const spBone& v, se::Value* ret); +bool spskeleton_to_seval(const spSkeleton& v, se::Value* ret); +bool spattachment_to_seval(const spAttachment& v, se::Value* ret); +bool spslotdata_to_seval(const spSlotData& v, se::Value* ret); +bool spslot_to_seval(const spSlot& v, se::Value* ret); +bool sptimeline_to_seval(const spTimeline& v, se::Value* ret); +bool spanimationstate_to_seval(const spAnimationState& v, se::Value* ret); +bool spanimation_to_seval(const spAnimation& v, se::Value* ret); +bool sptrackentry_to_seval(const spTrackEntry& v, se::Value* ret); + +// Box2d +bool b2Vec2_to_seval(const b2Vec2& v, se::Value* ret); +bool b2Manifold_to_seval(const b2Manifold* v, se::Value* ret); +bool b2AABB_to_seval(const b2AABB& v, se::Value* ret); + +``` + +辅助转换函数不属于`Script Engine Wrapper`抽象层,属于 cocos2d-x 绑定层,封装这些函数是为了在绑定代码中更加方便的转换。 +每个转换函数都返回 `bool` 类型,表示转换是否成功,开发者如果调用这些接口,需要去判断这个返回值。 + +以上接口,直接根据接口名称即可知道具体的用法,接口中第一个参数为输入,第二个参数为输出参数。用法如下: + +```c++ +se::Value v; +bool ok = int32_to_seval(100, &v); // 第二个参数为输出参数,传入输出参数的地址 +``` + +```c++ +int32_t v; +bool ok = seval_to_int32(args[0], &v); // 第二个参数为输出参数,传入输出参数的地址 +``` + +#### (IMPORTANT)理解 native\_ptr\_to\_seval 与 native\_ptr\_to\_rooted\_seval 的区别 + +**开发者一定要理解清楚这二者的区别,才不会因为误用导致 JS 层内存泄露这种比较难查的 bug。** + +* `native_ptr_to_seval` 用于 `JS 控制 CPP 对象生命周期` 的模式。当在绑定层需要根据一个 CPP 对象指针获取一个 se::Value 的时候,可调用此方法。引擎内大部分继承于 `cc::Ref` 的子类都采取这种方式去获取 se::Value。记住一点,当你管理的绑定对象是由 JS 控制生命周期,需要转换为 seval 的时候,请用此方法,否则考虑用 `native_ptr_to_rooted_seval` 。 +* `native_ptr_to_rooted_seval`用于`CPP 控制 JS 对象生命周期`的模式。一般而言,第三方库中的对象绑定都会用到此方法。此方法会根据传入的 CPP 对象指针查找 cache 住的 se::Object,如果不存在,则创建一个 rooted 的 se::Object,即这个创建出来的 JS 对象将不受 GC 控制,并永远在内存中。开发者需要监听 CPP 对象的释放,并在释放的时候去做 se::Object 的 unroot 操作,具体可参照前面章节中描述的 spTrackEntry_setDisposeCallback 中的内容。 + + +## 自动绑定 + +### 配置模块 ini 文件 + +配置方法与 1.6 中的方法相同,主要注意的是:1.7 中废弃了 `script_control_cpp` ,因为 `script_control_cpp` 字段会影响到整个模块,如果模块中需要绑定 cc::Ref 子类和非 cocos::Ref 子类,原来的绑定配置则无法满足需求。1.7 中取而代之的新字段为 `classes_owned_by_cpp` ,表示哪些类是需要由 CPP 来控制 JS 对象的生命周期。 + +1.7 中另外加入的一个配置字段为 `persistent_classes` , 用于表示哪些类是在游戏运行中一直存在的,比如:TextureCache SpriteFrameCache FileUtils EventDispatcher ActionManager Scheduler + +其他字段与 1.6 一致。 + +具体可以参考引擎目录下的 tools/tojs/cocos2dx.ini 等 ini 配置。 + +### 理解 ini 文件中每个字段的意义 + +``` +# 模块名称 +[cocos2d-x] + +# 绑定回调函数的前缀,也是生成的自动绑定文件的前缀 +prefix = engine + +# 绑定的类挂载在 JS 中的哪个对象中,类似命名空间 +target_namespace = jsb + +# 自动绑定工具基于 Android 编译环境,此处配置 Android 头文件搜索路径 +android_headers = + +# 配置 Android 编译参数 +android_flags = -target armv7-none-linux-androideabi -D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS -DANDROID -D__ANDROID_API__=14 -gcc-toolchain %(gcc_toolchain_dir)s --sysroot=%(androidndkdir)s/platforms/android-14/arch-arm -idirafter %(androidndkdir)s/sources/android/support/include -idirafter %(androidndkdir)s/sysroot/usr/include -idirafter %(androidndkdir)s/sysroot/usr/include/arm-linux-androideabi -idirafter %(clangllvmdir)s/lib64/clang/5.0/include -I%(androidndkdir)s/sources/cxx-stl/llvm-libc++/include + +# 配置 clang 头文件搜索路径 +clang_headers = + +# 配置 clang 编译参数 +clang_flags = -nostdinc -x c++ -std=c++11 -fsigned-char -U__SSE__ + +# 配置引擎的头文件搜索路径 +cocos_headers = -I%(cocosdir)s/cocos -I%(cocosdir)s/cocos/platform/android -I%(cocosdir)s/external/sources + +# 配置引擎编译参数 +cocos_flags = -DANDROID -DCC_PLATFORM=3 -DCC_PLATFORM_IOS=1 -DCC_PLATFORM_MACOS=4 -DCC_PLATFORM_WINDOWS=2 -DCC_PLATFORM_ANDROID=3 + + +# 配置额外的编译参数 +extra_arguments = %(android_headers)s %(clang_headers)s %(cxxgenerator_headers)s %(cocos_headers)s %(android_flags)s %(clang_flags)s %(cocos_flags)s %(extra_flags)s + +# 需要自动绑定工具解析哪些头文件 +headers = %(cocosdir)s/cocos/cocos2d.h + +# 在生成的绑定代码中,重命名头文件 +replace_headers= + +# 需要绑定哪些类,可以使用正则表达式,以空格为间隔 +classes = FileUtils$ SAXParser CanvasRenderingContext2D CanvasGradient Device + +# 哪些类需要在 JS 层通过 cc.Class.extend,以空格为间隔 +classes_need_extend = + +# 需要为哪些类绑定属性,以逗号为间隔 +field = + +# 需要忽略绑定哪些类,以逗号为间隔 +skip = FileUtils::[getFileData setFilenameLookupDictionary destroyInstance getFullPathCache getContents], + SAXParser::[(?!(init))], + Device::[getDeviceMotionValue], + CanvasRenderingContext2D::[setCanvasBufferUpdatedCallback set_.+] + +# 重命名函数,以逗号为间隔 +rename_functions = FileUtils::[loadFilenameLookupDictionaryFromFile=loadFilenameLookup], + CanvasRenderingContext2D::[getImageData=_getImageData] + +# 重命名类,以逗号为间隔 +rename_classes = SAXParser::PlistParser + +# 配置哪些类不需要搜索其父类 +classes_have_no_parents = SAXParser + +# 配置哪些父类需要被忽略 +base_classes_to_skip = Ref Clonable + +# 配置哪些类是抽象类,抽象类没有构造函数,即在 js 层无法通过 var a = new SomeClass();的方式构造 JS 对象 +abstract_classes = SAXParser Device + +# 配置哪些类是始终以一个实例的方式存在的,游戏运行过程中不会被销毁 +persistent_classes = FileUtils + +# 配置哪些类是需要由 CPP 对象来控制 JS 对象生命周期的,未配置的类,默认采用 JS 控制 CPP 对象生命周期 +classes_owned_by_cpp = +``` + +## 远程调试与 Profile + +默认远程调试和 Profile 是在 debug 模式中生效的,如果需要在 release 模式下也启用,需要手动修改 cocos/scripting/js-bindings/jswrapper/config.h 中的宏开关。 + +```c++ +#if defined(CC_DEBUG) && CC_DEBUG > 0 +#define SE_ENABLE_INSPECTOR 1 +#define SE_DEBUG 2 +#else +#define SE_ENABLE_INSPECTOR 0 +#define SE_DEBUG 0 +#endif +``` + +改为: + +```c++ +#if 1 // 这里改为 1,强制启用调试 +#define SE_ENABLE_INSPECTOR 1 +#define SE_DEBUG 2 +#else +#define SE_ENABLE_INSPECTOR 0 +#define SE_DEBUG 0 +#endif +``` + +### Chrome 远程调试 V8 + +#### Windows + +* 编译、运行游戏(或在 Creator 中直接使用模拟器运行) +* 用 Chrome 浏览器打开[devtools://devtools/bundled/js_app.html?v8only=true&ws=127.0.0.1:6086/00010002-0003-4004-8005-000600070008](devtools://devtools/bundled/js_app.html?v8only=true&ws=127.0.0.1:6086/00010002-0003-4004-8005-000600070008) + +断点调试: +![](v8-win32-debug.jpg) + +抓取 JS Heap +![](v8-win32-memory.jpg) + +Profile +![](v8-win32-profile.jpg) + +#### Android + +* 保证 Android 设备与 PC 或者 Mac 在同一个局域网中 +* 编译,运行游戏 +* 用 Chrome 浏览器打开[devtools://devtools/bundled/js_app.html?v8only=true&ws=xxx.xxx.xxx.xxx:6086/00010002-0003-4004-8005-000600070008](devtools://devtools/bundled/js_app.html?v8only=true&ws=xxx.xxx.xxx.xxx:6086/00010002-0003-4004-8005-000600070008), 其中 `xxx.xxx.xxx.xxx` 为局域网中 Android 设备的 IP 地址 +* 调试界面与 Windows 相同 + + +### Safari 远程调试 JavaScriptCore + +#### macOS + +1. 打开 Mac 上的 Safari,偏好设置 -> 高级 -> 显示开发者选项 +2. 为 Xcode 工程添加 entitlements 文件,如果 entitlements 存在则跳过此步骤。如果不存在,则到工程的 Capabilities 设置中打开 App Sandbox,然后再关闭,这时 .entitlements 文件会自动被添加进工程。![](jsc-entitlements.png),还需要确保 Build Setting 里面 Code Signing Entitlemenets 选项中包含 entitlements 文件。 ![](jsc-entitlements-check.png) +3. 打开 entitlements 文件,添加 com.apple.security.get-task-allow,值类型为 Boolean,值为 YES. ![](jsc-security-key.png) +4. 签名 : General -> 选择你的 Mac 工程 -> Signing -> 选择你的开发者证书 +5. 编译、运行游戏 +6. 如果是直接在 Creator 的模拟器中运行,则可以跳过第 2,3,4,5 步骤 +7. Safari 菜单中选择 Develop -> 你的 Mac 设备名称 -> Cocos2d-x JSB 会自动打开 Web Inspector 页面,然后即可进行设置断点、Timeline profile、console 等操作。![](jsc-mac-debug.png) ![](jsc-breakpoint.png) ![](jsc-timeline.png) + +**注意** + +如果开发者有修改引擎源码或者自己合并了一些 Patch,需要重新编译模拟器,记得重新设置一下模拟器工程的证书。 + +![](jsc-mac-simulator-sign.png) + +然后再调用 `gulp gen-simulator` 生成模拟器。 + +#### iOS + +1. 先打开 iPhone 的设置 -> Safari -> 高级 -> Web 检查器 +2. 为 Xcode 工程添加 entitlements 文件,如果 entitlements 存在则跳过此步骤。如果不存在,则到工程的 Capabilities 设置中打开 App Sandbox,然后再关闭,这时 .entitlements 文件会自动被添加进工程。 (图示与 macOS 的第 2 步类似) +3. 打开 entitlements 文件,添加 com.apple.security.get-task-allow,值类型为 Boolean,值为 YES。(图示与 macOS 的第 3 步类似) +4. 签名 : General -> 选择你的 iOS 工程 -> Signing -> 选择你的开发者证书 +5. 编译、运行游戏 +6. Safari 菜单中选择 Develop -> 你的 iPhone 设备名称 -> Cocos2d-x JSB 会自动打开 Web Inspector 页面,然后即可进行设置断点、Timeline profile、console 等操作。(图示与 macOS 的第 6 步类似) + +## Q & A + +### se::Object::root/unroot 与 se::Object::incRef/decRef 的区别? + +root/unroot 用于控制 JS 对象是否受 GC 控制,root 表示不受 GC 控制,unroot 则相反,表示交由 GC 控制,对一个 se::Object 来说,root 和 unroot 可以被调用多次,se::Object 内部有_rootCount 变量用于表示 root 的次数。当 unroot 被调用,且_rootCount 为 0 时,se::Object 关联的 JS 对象将交由 GC 管理。还有一种情况,即如果 se::Object 的析构被触发了,如果_rootCount > 0,则强制把 JS 对象交由 GC 控制。 + +incRef/decRef 用于控制 se::Object 这个 `cpp` 对象的生命周期,前面章节已经提及,建议用户使用 se::HandleObject 来控制`手动创建非绑定对象`的方式控制 se::Object 的生命周期。因此,一般情况下,开发者不需要接触到 incRef/decRef。 + + +### 对象生命周期的关联与解除关联 + +se::Object::attachObject/dettachObject + +`objA->attachObject(objB);`类似于 JS 中执行`objA.__nativeRefs[index] = objB`,只有当 objA 被 GC 后,objB 才有可能被 GC +`objA->dettachObject(objB);`类似于 JS 中执行`delete objA.__nativeRefs[index];`,这样 objB 的生命周期就不受 objA 控制了 + +### cc::Ref 子类与非 cc::Ref 子类 JS/CPP 对象生命周期管理有何不同? + +目前引擎中 cc::Ref 子类的绑定采用 JS 对象控制 CPP 对象生命周期的方式,这样做的好处是,解决了一直以来被诟病的需要在 JS 层 retain,release 对象的烦恼。 + +非 cc::Ref 子类采用 CPP 对象控制 JS 对象生命周期的方式。此方式要求,CPP 对象销毁后需要通知绑定层去调用对应 se::Object 的 clearPrivateData, unroot, decRef 的方法。JS 代码中一定要慎重操作对象,当有可能出现非法对象的逻辑中,使用 cc.sys.isObjectValid 来判断 CPP 对象是否被释放了。 + +### 绑定 cc::Ref 子类的析构函数需要注意的事项 + +如果在 JS 对象的 finalize 回调中调用任何 JS 引擎的 API,可能导致崩溃。因为当前引擎正在进行垃圾回收的流程,无法被打断处理其他操作。 +finalize 回调中是告诉 CPP 层是否对应的 CPP 对象的内存,不能在 CPP 对象的析构中又去操作 JS 引擎 API。 + +那如果必须调用,应该如何处理? + +cocos2d-x 的绑定中,如果引用计数为 1 了,我们不使用 release,而是使用 autorelease 延时 CPP 类的析构到帧结束去执行。 + +```c++ +static bool js_cocos2d_Sprite_finalize(se::State& s) +{ + CC_LOG_DEBUG("jsbindings: finalizing JS object %p (cc::Sprite)", s.nativeThisObject()); + cc::Sprite* cobj = (cc::Sprite*)s.nativeThisObject(); + if (cobj->getReferenceCount() == 1) + cobj->autorelease(); + else + cobj->release(); + return true; +} +SE_BIND_FINALIZE_FUNC(js_cocos2d_Sprite_finalize) +``` + +### 请不要在栈(Stack)上分配 cc::Ref 的子类对象 + +Ref 的子类必须在堆(Heap)上分配,即通过 `new` ,然后通过 `release` 来释放。当 JS 对象的 finalize 回调函数中统一使用 `autorelease` 或 `release` 来释放。如果是在栈上的对象,reference count 很有可能为 0,而这时调用 `release` ,其内部会调用 `delete` ,从而导致程序崩溃。所以为了防止这个行为的出现,开发者可以在继承于 cc::Ref 的绑定类中,标识析构函数为 `protected` 或者 `private` ,保证在编译阶段就能发现这个问题。 + +例如: + +```c++ +class CC_EX_DLL EventAssetsManagerEx : public cc::EventCustom +{ +public: + ... + ... +private: + virtual ~EventAssetsManagerEx() {} + ... + ... +}; + +EventAssetsManagerEx event(...); // 编译阶段报错 +dispatcher->dispatchEvent(&event); + +// 必须改为 + +EventAssetsManagerEx* event = new EventAssetsManagerEx(...); +dispatcher->dispatchEvent(event); +event->release(); +``` + + + +### 如何监听脚本错误 + +在 AppDelegate.cpp 中通过 se::ScriptEngine::getInstance()->setExceptionCallback(...)设置 JS 层异常回调。 + +```c++ +bool AppDelegate::applicationDidFinishLaunching() +{ + ... + ... + se::ScriptEngine* se = se::ScriptEngine::getInstance(); + + se->setExceptionCallback([](const char* location, const char* message, const char* stack){ + // Send exception information to server like Tencent Bugly. + // ... + // ... + }); + + jsb_register_all_modules(); + ... + ... + return true; +} + +``` + + + + diff --git a/cocos/bindings/docs/jsc-breakpoint.png b/cocos/bindings/docs/jsc-breakpoint.png new file mode 100644 index 0000000..5551a83 Binary files /dev/null and b/cocos/bindings/docs/jsc-breakpoint.png differ diff --git a/cocos/bindings/docs/jsc-entitlements-check.png b/cocos/bindings/docs/jsc-entitlements-check.png new file mode 100644 index 0000000..1f5d2d8 Binary files /dev/null and b/cocos/bindings/docs/jsc-entitlements-check.png differ diff --git a/cocos/bindings/docs/jsc-entitlements.png b/cocos/bindings/docs/jsc-entitlements.png new file mode 100644 index 0000000..16807e9 Binary files /dev/null and b/cocos/bindings/docs/jsc-entitlements.png differ diff --git a/cocos/bindings/docs/jsc-mac-debug.png b/cocos/bindings/docs/jsc-mac-debug.png new file mode 100644 index 0000000..cc02459 Binary files /dev/null and b/cocos/bindings/docs/jsc-mac-debug.png differ diff --git a/cocos/bindings/docs/jsc-mac-simulator-sign.png b/cocos/bindings/docs/jsc-mac-simulator-sign.png new file mode 100644 index 0000000..1854313 Binary files /dev/null and b/cocos/bindings/docs/jsc-mac-simulator-sign.png differ diff --git a/cocos/bindings/docs/jsc-security-key.png b/cocos/bindings/docs/jsc-security-key.png new file mode 100644 index 0000000..50ca422 Binary files /dev/null and b/cocos/bindings/docs/jsc-security-key.png differ diff --git a/cocos/bindings/docs/jsc-timeline.png b/cocos/bindings/docs/jsc-timeline.png new file mode 100644 index 0000000..8b65b22 Binary files /dev/null and b/cocos/bindings/docs/jsc-timeline.png differ diff --git a/cocos/bindings/docs/v8-win32-debug.jpg b/cocos/bindings/docs/v8-win32-debug.jpg new file mode 100644 index 0000000..40b6aac Binary files /dev/null and b/cocos/bindings/docs/v8-win32-debug.jpg differ diff --git a/cocos/bindings/docs/v8-win32-memory.jpg b/cocos/bindings/docs/v8-win32-memory.jpg new file mode 100644 index 0000000..54f8ba2 Binary files /dev/null and b/cocos/bindings/docs/v8-win32-memory.jpg differ diff --git a/cocos/bindings/docs/v8-win32-profile.jpg b/cocos/bindings/docs/v8-win32-profile.jpg new file mode 100644 index 0000000..3297816 Binary files /dev/null and b/cocos/bindings/docs/v8-win32-profile.jpg differ diff --git a/cocos/bindings/dop/BufferAllocator.cpp b/cocos/bindings/dop/BufferAllocator.cpp new file mode 100644 index 0000000..b2b1086 --- /dev/null +++ b/cocos/bindings/dop/BufferAllocator.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "BufferAllocator.h" +#include "base/Log.h" +#include "base/memory/Memory.h" + +namespace se { + +BufferAllocator::BufferAllocator(PoolType type) +: _type(type) { +} + +BufferAllocator::~BufferAllocator() { + for (auto buffer : _buffers) { + buffer.second->decRef(); + } + _buffers.clear(); +} + +se::Object *BufferAllocator::alloc(uint32_t index, uint32_t bytes) { + if (_buffers.count(index)) { + se::Object *oldObj = _buffers[index]; + oldObj->decRef(); + } + se::Object *obj = se::Object::createArrayBufferObject(nullptr, bytes); + + _buffers[index] = obj; + + uint8_t *ret = nullptr; + size_t len; + obj->getArrayBufferData(static_cast(&ret), &len); + + return obj; +} + +void BufferAllocator::free(uint32_t index) { + if (_buffers.count(index)) { + se::Object *oldObj = _buffers[index]; + oldObj->decRef(); + _buffers.erase(index); + } +} + +} // namespace se diff --git a/cocos/bindings/dop/BufferAllocator.h b/cocos/bindings/dop/BufferAllocator.h new file mode 100644 index 0000000..bf2f1b2 --- /dev/null +++ b/cocos/bindings/dop/BufferAllocator.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "PoolType.h" +#include "cocos/base/Macros.h" +#include "cocos/base/std/container/unordered_map.h" +#include "cocos/bindings/jswrapper/Object.h" + +namespace se { + +class CC_DLL BufferAllocator final { +public: + explicit BufferAllocator(PoolType type); + ~BufferAllocator(); + + se::Object *alloc(uint32_t index, uint32_t bytes); + void free(uint32_t index); + +private: + static constexpr uint32_t BUFFER_MASK = ~(1 << 30); + + ccstd::unordered_map _buffers; + PoolType _type = PoolType::UNKNOWN; +}; + +} // namespace se diff --git a/cocos/bindings/dop/BufferPool.cpp b/cocos/bindings/dop/BufferPool.cpp new file mode 100644 index 0000000..0333d9b --- /dev/null +++ b/cocos/bindings/dop/BufferPool.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "BufferPool.h" +#include "base/Macros.h" +#include "base/memory/Memory.h" + +namespace se { + +BufferPool::BufferPool(PoolType type, uint32_t entryBits, uint32_t bytesPerEntry) +: _allocator(type), + _entryBits(entryBits), + _bytesPerEntry(bytesPerEntry), + _type(type) { + _entriesPerChunk = 1 << entryBits; + _entryMask = _entriesPerChunk - 1; + _chunkMask = 0xffffffff & ~(_entryMask | POOL_FLAG); + + _bytesPerChunk = _bytesPerEntry * _entriesPerChunk; +} + +BufferPool::~BufferPool() = default; + +se::Object *BufferPool::allocateNewChunk() { + se::Object *jsObj = _allocator.alloc(static_cast(_chunks.size()), _bytesPerChunk); + + uint8_t *realPtr = nullptr; + size_t len = 0; + jsObj->getArrayBufferData(&realPtr, &len); + _chunks.push_back(realPtr); + + return jsObj; +} + +} // namespace se diff --git a/cocos/bindings/dop/BufferPool.h b/cocos/bindings/dop/BufferPool.h new file mode 100644 index 0000000..fbc5602 --- /dev/null +++ b/cocos/bindings/dop/BufferPool.h @@ -0,0 +1,68 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "BufferAllocator.h" +#include "PoolType.h" +#include "base/std/container/vector.h" +#include "cocos/base/Macros.h" +#include "cocos/bindings/jswrapper/Object.h" + +namespace se { + +class CC_DLL BufferPool final { +public: + using Chunk = uint8_t *; + + inline static uint32_t getPoolFlag() { return POOL_FLAG; } + + BufferPool(PoolType type, uint32_t entryBits, uint32_t bytesPerEntry); + ~BufferPool(); + + template + T *getTypedObject(uint32_t id) const { + uint32_t chunk = (_chunkMask & id) >> _entryBits; + uint32_t entry = _entryMask & id; + CC_ASSERT(chunk < _chunks.size() && entry < _entriesPerChunk); + return reinterpret_cast(_chunks[chunk] + (entry * _bytesPerEntry)); + } + + se::Object *allocateNewChunk(); + +private: + static constexpr uint32_t POOL_FLAG{1 << 30}; + + BufferAllocator _allocator; + ccstd::vector _chunks; + uint32_t _entryBits{1 << 8}; + uint32_t _chunkMask{0}; + uint32_t _entryMask{0}; + uint32_t _bytesPerChunk{0}; + uint32_t _entriesPerChunk{0}; + uint32_t _bytesPerEntry{0}; + PoolType _type{PoolType::UNKNOWN}; +}; + +} // namespace se diff --git a/cocos/bindings/dop/PoolType.h b/cocos/bindings/dop/PoolType.h new file mode 100644 index 0000000..d0a2f63 --- /dev/null +++ b/cocos/bindings/dop/PoolType.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { + +#define CAST_POOL_TYPE(type) static_cast(type) +#define GET_BUFFER_POOL_ID(type) CAST_POOL_TYPE(type) + +enum class PoolType { + // Buffers + NODE, + UNKNOWN +}; +} // namespace se diff --git a/cocos/bindings/dop/jsb_dop.cpp b/cocos/bindings/dop/jsb_dop.cpp new file mode 100644 index 0000000..8304446 --- /dev/null +++ b/cocos/bindings/dop/jsb_dop.cpp @@ -0,0 +1,185 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_dop.h" + +#include "BufferAllocator.h" +#include "BufferPool.h" +#include "cocos/bindings/manual/jsb_classtype.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" + +/******************************************************** + BufferPool binding + *******************************************************/ +se::Class *jsb_BufferPool_class = nullptr; // NOLINT + +static bool jsb_BufferPool_allocateNewChunk(se::State &s) { // NOLINT + auto *pool = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(pool, false, "Invalid Native Object"); + s.rval().setObject(pool->allocateNewChunk()); + return true; +} +SE_BIND_FUNC(jsb_BufferPool_allocateNewChunk); + +SE_DECLARE_FINALIZE_FUNC(jsb_BufferPool_finalize) + +static bool jsb_BufferPool_constructor(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 3) { + uint32_t poolType{0}; + uint32_t entryBits{0}; + uint32_t bytesPerEntry{0}; + + bool ok = true; + ok &= sevalue_to_native(args[0], &poolType); + ok &= sevalue_to_native(args[1], &entryBits); + ok &= sevalue_to_native(args[2], &bytesPerEntry); + if (!ok) { + SE_REPORT_ERROR("jsb_BufferPool_constructor: argument convertion error"); + return false; + } + + auto *pool = JSB_ALLOC(se::BufferPool, (se::PoolType)poolType, entryBits, bytesPerEntry); + s.thisObject()->setPrivateData(pool); + return true; + } + + SE_REPORT_ERROR("jsb_BufferPool_constructor: wrong number of arguments: %d", (int)argc); + return false; +} +SE_BIND_CTOR(jsb_BufferPool_constructor, jsb_BufferPool_class, jsb_BufferPool_finalize) // NOLINT + +static bool jsb_BufferPool_finalize(se::State &s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(jsb_BufferPool_finalize) + +static bool js_register_se_BufferPool(se::Object *obj) { // NOLINT + se::Class *cls = se::Class::create("NativeBufferPool", obj, nullptr, _SE(jsb_BufferPool_constructor)); + + cls->defineFunction("allocateNewChunk", _SE(jsb_BufferPool_allocateNewChunk)); + cls->install(); + JSBClassType::registerClass(cls); + + jsb_BufferPool_class = cls; // NOLINT + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +/***************************************************** + Array binding + ******************************************************/ +static se::Class *jsb_BufferAllocator_class = nullptr; // NOLINT + +SE_DECLARE_FINALIZE_FUNC(jsb_BufferAllocator_finalize) + +static bool jsb_BufferAllocator_constructor(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + uint32_t type{0}; + + auto *bufferAllocator = JSB_ALLOC(se::BufferAllocator, static_cast(type)); + s.thisObject()->setPrivateData(bufferAllocator); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d", (int)argc); + return false; +} +SE_BIND_CTOR(jsb_BufferAllocator_constructor, jsb_BufferAllocator_class, jsb_BufferAllocator_finalize) + +static bool jsb_BufferAllocator_finalize(se::State &s) { // NOLINT + return true; +} +SE_BIND_FINALIZE_FUNC(jsb_BufferAllocator_finalize) + +static bool jsb_BufferAllocator_alloc(se::State &s) { // NOLINT + auto *bufferAllocator = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(bufferAllocator, false, "Invalid Native Object"); + + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 2) { + uint32_t index{0}; + sevalue_to_native(args[0], &index); + uint32_t bytes{0}; + sevalue_to_native(args[1], &bytes); + s.rval().setObject(bufferAllocator->alloc(index, bytes)); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d", (int)argc); + return false; +} +SE_BIND_FUNC(jsb_BufferAllocator_alloc); + +static bool jsb_BufferAllocator_free(se::State &s) { // NOLINT + auto *bufferAllocator = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(bufferAllocator, false, "Invalid Native Object"); + + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + uint32_t index{0}; + sevalue_to_native(args[0], &index); + bufferAllocator->free(index); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d", (int)argc); + return false; +} +SE_BIND_FUNC(jsb_BufferAllocator_free); + +static bool js_register_se_BufferAllocator(se::Object *obj) { // NOLINT + se::Class *cls = se::Class::create("NativeBufferAllocator", obj, nullptr, _SE(jsb_BufferAllocator_constructor)); + cls->defineFunction("alloc", _SE(jsb_BufferAllocator_alloc)); + cls->defineFunction("free", _SE(jsb_BufferAllocator_free)); + cls->install(); + JSBClassType::registerClass(cls); + + jsb_BufferAllocator_class = cls; // NOLINT + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +bool register_all_dop_bindings(se::Object *obj) { // NOLINT + // TODO(liuhang): Don't make dop into jsb namespace. Currently put it into jsb namesapce just to test codes. + se::Value nsVal; + if (!obj->getProperty("jsb", &nsVal)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + obj->setProperty("jsb", nsVal); + } + se::Object *ns = nsVal.toObject(); + + js_register_se_BufferAllocator(ns); // NOLINT + js_register_se_BufferPool(ns); // NOLINT + return true; +} diff --git a/cocos/bindings/dop/jsb_dop.h b/cocos/bindings/dop/jsb_dop.h new file mode 100644 index 0000000..f8f16fe --- /dev/null +++ b/cocos/bindings/dop/jsb_dop.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { +class Object; +} + +bool register_all_dop_bindings(se::Object *obj); // NOLINT diff --git a/cocos/bindings/event/EventDispatcher.cpp b/cocos/bindings/event/EventDispatcher.cpp new file mode 100644 index 0000000..9ebc32a --- /dev/null +++ b/cocos/bindings/event/EventDispatcher.cpp @@ -0,0 +1,459 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "EventDispatcher.h" +#include +#include "cocos/application/ApplicationManager.h" +#include "cocos/bindings/jswrapper/HandleObject.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_global_init.h" +#include "cocos/platform/interfaces/modules/ISystemWindow.h" +#include "cocos/platform/interfaces/modules/ISystemWindowManager.h" + +namespace { +se::Value tickVal; +se::ValueArray tickArgsValArr(1); +ccstd::vector jsTouchObjPool; +se::Object *jsTouchObjArray = nullptr; +se::Object *jsMouseEventObj = nullptr; +se::Object *jsKeyboardEventObj = nullptr; +se::Object *jsControllerEventArray = nullptr; +se::Object *jsControllerChangeEventArray = nullptr; +se::Object *jsResizeEventObj = nullptr; +bool inited = false; +bool busListenerInited = false; + +// attach the argument object to the function +void accessCacheArgObj(se::Object *func, se::Value *argObj, const char *cacheKey = "__reusedArgumentObject") { + func->getProperty(cacheKey, argObj); + if (argObj->isUndefined()) { + se::HandleObject argumentObj(se::Object::createPlainObject()); + argObj->setObject(argumentObj); + } +} + +} // namespace +namespace cc { + +events::EnterForeground::Listener EventDispatcher::listenerEnterForeground; +events::EnterBackground::Listener EventDispatcher::listenerEnterBackground; +events::WindowChanged::Listener EventDispatcher::listenerWindowChanged; +events::LowMemory::Listener EventDispatcher::listenerLowMemory; +events::Touch::Listener EventDispatcher::listenerTouch; +events::Mouse::Listener EventDispatcher::listenerMouse; +events::Keyboard::Listener EventDispatcher::listenerKeyboard; +events::Controller::Listener EventDispatcher::listenerConroller; +events::ControllerChange::Listener EventDispatcher::listenerConrollerChange; +events::Tick::Listener EventDispatcher::listenerTick; +events::Resize::Listener EventDispatcher::listenerResize; +events::Orientation::Listener EventDispatcher::listenerOrientation; +events::RestartVM::Listener EventDispatcher::listenerRestartVM; +events::Close::Listener EventDispatcher::listenerClose; +events::PointerLock::Listener EventDispatcher::listenerPointerLock; + +uint32_t EventDispatcher::hashListenerId = 1; + +bool EventDispatcher::initialized() { + return inited && se::ScriptEngine::getInstance()->isValid(); +} + +void EventDispatcher::init() { + inited = true; + se::ScriptEngine::getInstance()->addBeforeCleanupHook([]() { + EventDispatcher::destroy(); + }); + + if (!busListenerInited) { + listenerTouch.bind(&dispatchTouchEvent); + listenerMouse.bind(&dispatchMouseEvent); + listenerKeyboard.bind(&dispatchKeyboardEvent); + listenerConroller.bind(&dispatchControllerEvent); + listenerConrollerChange.bind(&dispatchControllerChangeEvent); + listenerTick.bind(&dispatchTickEvent); + listenerResize.bind(&dispatchResizeEvent); + listenerOrientation.bind(&dispatchOrientationChangeEvent); + listenerEnterBackground.bind(&dispatchEnterBackgroundEvent); + listenerEnterForeground.bind(&dispatchEnterForegroundEvent); + listenerLowMemory.bind(&dispatchMemoryWarningEvent); + listenerClose.bind(&dispatchCloseEvent); + listenerRestartVM.bind(&dispatchRestartVM); + listenerPointerLock.bind(&dispatchPointerlockChangeEvent); + busListenerInited = true; + } +} + +void EventDispatcher::destroy() { + for (auto *touchObj : jsTouchObjPool) { + touchObj->unroot(); + touchObj->decRef(); + } + jsTouchObjPool.clear(); + + if (jsTouchObjArray != nullptr) { + jsTouchObjArray->unroot(); + jsTouchObjArray->decRef(); + jsTouchObjArray = nullptr; + } + + if (jsControllerEventArray != nullptr) { + jsControllerEventArray->unroot(); + jsControllerEventArray->decRef(); + jsControllerEventArray = nullptr; + } + + if (jsControllerChangeEventArray != nullptr) { + jsControllerChangeEventArray->unroot(); + jsControllerChangeEventArray->decRef(); + jsControllerChangeEventArray = nullptr; + } + + if (jsMouseEventObj != nullptr) { + jsMouseEventObj->unroot(); + jsMouseEventObj->decRef(); + jsMouseEventObj = nullptr; + } + + if (jsKeyboardEventObj != nullptr) { + jsKeyboardEventObj->unroot(); + jsKeyboardEventObj->decRef(); + jsKeyboardEventObj = nullptr; + } + + if (jsResizeEventObj != nullptr) { + jsResizeEventObj->unroot(); + jsResizeEventObj->decRef(); + jsResizeEventObj = nullptr; + } + + inited = false; + tickVal.setUndefined(); +} + +void EventDispatcher::dispatchTouchEvent(const TouchEvent &touchEvent) { + se::AutoHandleScope scope; + if (!jsTouchObjArray) { + jsTouchObjArray = se::Object::createArrayObject(0); + jsTouchObjArray->root(); + } + + jsTouchObjArray->setProperty("length", se::Value(static_cast(touchEvent.touches.size()))); + + while (jsTouchObjPool.size() < touchEvent.touches.size()) { + se::Object *touchObj = se::Object::createPlainObject(); + touchObj->root(); + jsTouchObjPool.emplace_back(touchObj); + } + + uint32_t touchIndex = 0; + int poolIndex = 0; + for (const auto &touch : touchEvent.touches) { + se::Object *jsTouch = jsTouchObjPool.at(poolIndex++); + jsTouch->setProperty("identifier", se::Value(touch.index)); + jsTouch->setProperty("clientX", se::Value(touch.x)); + jsTouch->setProperty("clientY", se::Value(touch.y)); + jsTouch->setProperty("pageX", se::Value(touch.x)); + jsTouch->setProperty("pageY", se::Value(touch.y)); + + jsTouchObjArray->setArrayElement(touchIndex, se::Value(jsTouch)); + ++touchIndex; + } + + const char *eventName = nullptr; + switch (touchEvent.type) { + case TouchEvent::Type::BEGAN: + eventName = "onTouchStart"; + break; + case TouchEvent::Type::MOVED: + eventName = "onTouchMove"; + break; + case TouchEvent::Type::ENDED: + eventName = "onTouchEnd"; + break; + case TouchEvent::Type::CANCELLED: + eventName = "onTouchCancel"; + break; + default: + CC_ABORT(); + break; + } + + se::ValueArray args; + args.emplace_back(se::Value(jsTouchObjArray)); + args.emplace_back(se::Value(touchEvent.windowId)); + EventDispatcher::doDispatchJsEvent(eventName, args); +} + +void EventDispatcher::dispatchMouseEvent(const MouseEvent &mouseEvent) { + se::AutoHandleScope scope; + if (!jsMouseEventObj) { + jsMouseEventObj = se::Object::createPlainObject(); + jsMouseEventObj->root(); + } + + const auto &xVal = se::Value(mouseEvent.x); + const auto &yVal = se::Value(mouseEvent.y); + const MouseEvent::Type type = mouseEvent.type; + + if (type == MouseEvent::Type::WHEEL) { + jsMouseEventObj->setProperty("wheelDeltaX", xVal); + jsMouseEventObj->setProperty("wheelDeltaY", yVal); + } else { + if (type == MouseEvent::Type::DOWN || type == MouseEvent::Type::UP) { + jsMouseEventObj->setProperty("button", se::Value(mouseEvent.button)); + } + jsMouseEventObj->setProperty("x", xVal); + jsMouseEventObj->setProperty("y", yVal); + if (type == MouseEvent::Type::MOVE) { + const auto &xDelta = se::Value(mouseEvent.xDelta); + const auto &yDelta = se::Value(mouseEvent.yDelta); + jsMouseEventObj->setProperty("xDelta", xDelta); + jsMouseEventObj->setProperty("yDelta", yDelta); + } + } + + jsMouseEventObj->setProperty("windowId", se::Value(mouseEvent.windowId)); + + const char *eventName = nullptr; + const char *jsFunctionName = nullptr; + switch (type) { + case MouseEvent::Type::DOWN: + jsFunctionName = "onMouseDown"; + break; + case MouseEvent::Type::MOVE: + jsFunctionName = "onMouseMove"; + break; + case MouseEvent::Type::UP: + jsFunctionName = "onMouseUp"; + break; + case MouseEvent::Type::WHEEL: + jsFunctionName = "onMouseWheel"; + break; + default: + CC_ABORT(); + break; + } + + se::ValueArray args; + args.emplace_back(se::Value(jsMouseEventObj)); + EventDispatcher::doDispatchJsEvent(jsFunctionName, args); +} + +void EventDispatcher::dispatchKeyboardEvent(const KeyboardEvent &keyboardEvent) { + se::AutoHandleScope scope; + if (!jsKeyboardEventObj) { + jsKeyboardEventObj = se::Object::createPlainObject(); + jsKeyboardEventObj->root(); + } + + const char *eventName = nullptr; + switch (keyboardEvent.action) { + case KeyboardEvent::Action::PRESS: + case KeyboardEvent::Action::REPEAT: + eventName = "onKeyDown"; + break; + case KeyboardEvent::Action::RELEASE: + eventName = "onKeyUp"; + break; + default: + CC_ABORT(); + break; + } + + jsKeyboardEventObj->setProperty("altKey", se::Value(keyboardEvent.altKeyActive)); + jsKeyboardEventObj->setProperty("ctrlKey", se::Value(keyboardEvent.ctrlKeyActive)); + jsKeyboardEventObj->setProperty("metaKey", se::Value(keyboardEvent.metaKeyActive)); + jsKeyboardEventObj->setProperty("shiftKey", se::Value(keyboardEvent.shiftKeyActive)); + jsKeyboardEventObj->setProperty("repeat", se::Value(keyboardEvent.action == KeyboardEvent::Action::REPEAT)); + jsKeyboardEventObj->setProperty("keyCode", se::Value(keyboardEvent.key)); + jsKeyboardEventObj->setProperty("windowId", se::Value(keyboardEvent.windowId)); + jsKeyboardEventObj->setProperty("code", se::Value(keyboardEvent.code)); + + se::ValueArray args; + args.emplace_back(se::Value(jsKeyboardEventObj)); + EventDispatcher::doDispatchJsEvent(eventName, args); +} + +void EventDispatcher::dispatchControllerEvent(const ControllerEvent &controllerEvent) { + se::AutoHandleScope scope; + if (!jsControllerEventArray) { + jsControllerEventArray = se::Object::createArrayObject(0); + jsControllerEventArray->root(); + } + + const char *eventName = "onControllerInput"; + if (controllerEvent.type == ControllerEvent::Type::HANDLE) { + eventName = "onHandleInput"; + } + uint32_t controllerIndex = 0; + jsControllerEventArray->setProperty("length", se::Value(static_cast(controllerEvent.controllerInfos.size()))); + + for (const auto &controller : controllerEvent.controllerInfos) { + se::HandleObject jsController{se::Object::createPlainObject()}; + jsController->setProperty("id", se::Value(controller->napdId)); + + se::HandleObject jsButtonInfoList{se::Object::createArrayObject(static_cast(controller->buttonInfos.size()))}; + + uint32_t buttonIndex = 0; + for (const auto &buttonInfo : controller->buttonInfos) { + se::HandleObject jsButtonInfo{se::Object::createPlainObject()}; + jsButtonInfo->setProperty("code", se::Value(static_cast(buttonInfo.key))); + jsButtonInfo->setProperty("isPressed", se::Value(static_cast(buttonInfo.isPress))); + jsButtonInfoList->setArrayElement(buttonIndex, se::Value(jsButtonInfo)); + buttonIndex++; + } + + se::HandleObject jsAxisInfoList{se::Object::createArrayObject(static_cast(controller->axisInfos.size()))}; + + uint32_t axisIndex = 0; + for (const auto &axisInfo : controller->axisInfos) { + se::HandleObject jsAxisInfo{se::Object::createPlainObject()}; + jsAxisInfo->setProperty("code", se::Value(static_cast(axisInfo.axis))); + jsAxisInfo->setProperty("value", se::Value(axisInfo.value)); + jsAxisInfoList->setArrayElement(axisIndex, se::Value(jsAxisInfo)); + axisIndex++; + } + + se::HandleObject jsTouchInfoList{se::Object::createArrayObject(static_cast(controller->touchInfos.size()))}; + + uint32_t touchIndex = 0; + for (const auto &touchInfo : controller->touchInfos) { + se::HandleObject jsTouchInfo{se::Object::createPlainObject()}; + jsTouchInfo->setProperty("code", se::Value(static_cast(touchInfo.key))); + jsTouchInfo->setProperty("value", se::Value(touchInfo.value)); + jsTouchInfoList->setArrayElement(touchIndex, se::Value(jsTouchInfo)); + touchIndex++; + } + jsController->setProperty("axisInfoList", se::Value(jsAxisInfoList)); + jsController->setProperty("buttonInfoList", se::Value(jsButtonInfoList)); + jsController->setProperty("touchInfoList", se::Value(jsTouchInfoList)); + + jsControllerEventArray->setArrayElement(controllerIndex, se::Value(jsController)); + controllerIndex++; + } + se::ValueArray args; + args.emplace_back(se::Value(jsControllerEventArray)); + EventDispatcher::doDispatchJsEvent(eventName, args); +} + +void EventDispatcher::dispatchControllerChangeEvent(const ControllerChangeEvent &changeEvent) { + se::AutoHandleScope scope; + if (!jsControllerChangeEventArray) { + jsControllerChangeEventArray = se::Object::createArrayObject(0); + jsControllerChangeEventArray->root(); + } + + const char *eventName = "onControllerChange"; + jsControllerChangeEventArray->setProperty("length", se::Value(static_cast(changeEvent.controllerIds.size()))); + + int index = 0; + for (const auto id : changeEvent.controllerIds) { + jsControllerChangeEventArray->setArrayElement(index++, se::Value(id)); + } + se::ValueArray args; + args.emplace_back(se::Value(jsControllerChangeEventArray)); + EventDispatcher::doDispatchJsEvent(eventName, args); +} + +void EventDispatcher::dispatchTickEvent(float /*dt*/) { + if (!se::ScriptEngine::getInstance()->isValid()) { + return; + } + + se::AutoHandleScope scope; + if (tickVal.isUndefined()) { + se::ScriptEngine::getInstance()->getGlobalObject()->getProperty("gameTick", &tickVal); + } + + static std::chrono::steady_clock::time_point prevTime; + prevTime = std::chrono::steady_clock::now(); + + int64_t milliSeconds = std::chrono::duration_cast(prevTime - se::ScriptEngine::getInstance()->getStartTime()).count(); + tickArgsValArr[0].setDouble(static_cast(milliSeconds)); + + if (!tickVal.isUndefined()) { + tickVal.toObject()->call(tickArgsValArr, nullptr); + } +} +// NOLINTNEXTLINE +void EventDispatcher::dispatchResizeEvent(int width, int height, uint32_t windowId) { + se::AutoHandleScope scope; + if (!jsResizeEventObj) { + jsResizeEventObj = se::Object::createPlainObject(); + jsResizeEventObj->root(); + } + + jsResizeEventObj->setProperty("windowId", se::Value(windowId)); + jsResizeEventObj->setProperty("width", se::Value(width)); + jsResizeEventObj->setProperty("height", se::Value(height)); + + se::ValueArray args; + args.emplace_back(se::Value(jsResizeEventObj)); + EventDispatcher::doDispatchJsEvent("onResize", args); +} + +void EventDispatcher::dispatchOrientationChangeEvent(int orientation) { + // Ts's logic is same as the 'onResize', so remove code here temporary. +} + +void EventDispatcher::dispatchEnterBackgroundEvent() { + EventDispatcher::doDispatchJsEvent("onPause", se::EmptyValueArray); +} + +void EventDispatcher::dispatchEnterForegroundEvent() { + EventDispatcher::doDispatchJsEvent("onResume", se::EmptyValueArray); +} + +void EventDispatcher::dispatchMemoryWarningEvent() { + EventDispatcher::doDispatchJsEvent("onMemoryWarning", se::EmptyValueArray); +} + +void EventDispatcher::dispatchRestartVM() { + EventDispatcher::doDispatchJsEvent("onRestartVM", se::EmptyValueArray); +} + +void EventDispatcher::dispatchCloseEvent() { + EventDispatcher::doDispatchJsEvent("onClose", se::EmptyValueArray); +} + +void EventDispatcher::dispatchPointerlockChangeEvent(bool value) { + se::ValueArray args; + args.emplace_back(se::Value(value)); + EventDispatcher::doDispatchJsEvent("onPointerlockChange", args); +} + +void EventDispatcher::doDispatchJsEvent(const char *jsFunctionName, const std::vector &args) { + if (!se::ScriptEngine::getInstance()->isValid()) { + return; + } + + se::AutoHandleScope scope; + CC_ASSERT(inited); + + se::Value func; + __jsbObj->getProperty(jsFunctionName, &func); + if (func.isObject() && func.toObject()->isFunction()) { + func.toObject()->call(args, nullptr); + } +} + +} // end of namespace cc diff --git a/cocos/bindings/event/EventDispatcher.h b/cocos/bindings/event/EventDispatcher.h new file mode 100644 index 0000000..041aa87 --- /dev/null +++ b/cocos/bindings/event/EventDispatcher.h @@ -0,0 +1,79 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include + +#include "engine/EngineEvents.h" + +namespace se { +class Value; +} + +namespace cc { +class EventDispatcher { +public: + static void init(); + static void destroy(); + static bool initialized(); + + static void doDispatchJsEvent(const char *jsFunctionName, const std::vector &args); + +private: + static void dispatchTouchEvent(const TouchEvent &touchEvent); + static void dispatchMouseEvent(const MouseEvent &mouseEvent); + static void dispatchKeyboardEvent(const KeyboardEvent &keyboardEvent); + static void dispatchControllerEvent(const ControllerEvent &controllerEvent); + static void dispatchControllerChangeEvent(const ControllerChangeEvent &changeEvent); + static void dispatchTickEvent(float dt); + static void dispatchResizeEvent(int width, int height, uint32_t windowId = UINT32_MAX); + static void dispatchOrientationChangeEvent(int orientation); + static void dispatchEnterBackgroundEvent(); + static void dispatchEnterForegroundEvent(); + static void dispatchMemoryWarningEvent(); + static void dispatchRestartVM(); + static void dispatchCloseEvent(); + static void dispatchPointerlockChangeEvent(bool value); + static uint32_t hashListenerId; // simple increment hash + + static events::EnterForeground::Listener listenerEnterForeground; + static events::EnterBackground::Listener listenerEnterBackground; + static events::WindowChanged::Listener listenerWindowChanged; + static events::LowMemory::Listener listenerLowMemory; + static events::Touch::Listener listenerTouch; + static events::Mouse::Listener listenerMouse; + static events::Keyboard::Listener listenerKeyboard; + static events::Controller::Listener listenerConroller; + static events::ControllerChange::Listener listenerConrollerChange; + static events::Tick::Listener listenerTick; + static events::Resize::Listener listenerResize; + static events::Orientation::Listener listenerOrientation; + static events::RestartVM::Listener listenerRestartVM; + static events::Close::Listener listenerClose; + static events::PointerLock::Listener listenerPointerLock; +}; + +} // end of namespace cc diff --git a/cocos/bindings/jswrapper/Define.h b/cocos/bindings/jswrapper/Define.h new file mode 100644 index 0000000..98222de --- /dev/null +++ b/cocos/bindings/jswrapper/Define.h @@ -0,0 +1,47 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/TypeDef.h" + +namespace se { + +/** + * PropertyAttribute. + * @note Use the same value as which defined in v8, we convert it to v8::PropertyAttribute directly, so don't change the enum value. + */ +enum class PropertyAttribute : uint8_t { + /** NONE. **/ + NONE = 0, + /** READ_ONLY, i.e., not writable. **/ + READ_ONLY = 1 << 0, + /** DONT_ENUM, i.e., not enumerable. **/ + DONT_ENUM = 1 << 1, + /** DONT_DELETE, i.e., not configurable. **/ + DONT_DELETE = 1 << 2 +}; +CC_ENUM_BITWISE_OPERATORS(PropertyAttribute); + +} // namespace se diff --git a/cocos/bindings/jswrapper/HandleObject.cpp b/cocos/bindings/jswrapper/HandleObject.cpp new file mode 100644 index 0000000..f040f99 --- /dev/null +++ b/cocos/bindings/jswrapper/HandleObject.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "HandleObject.h" +#include "Object.h" + +namespace se { + +HandleObject::HandleObject(Object *obj) +: _obj(obj) { + if (_obj != nullptr) { + // se::HandleObject could not be used for native binding object. + CC_ASSERT(!_obj->_getClass()); + _obj->root(); + } +} + +HandleObject::HandleObject(HandleObject &&o) noexcept{ + _obj = o._obj; + o._obj = nullptr; +} + +HandleObject::~HandleObject() { + if (_obj != nullptr) { + _obj->unroot(); + _obj->decRef(); + } +} + +HandleObject& HandleObject::operator=(HandleObject &&o) noexcept { + _obj = o._obj; + o._obj = nullptr; + return *this; +} + +} // namespace se diff --git a/cocos/bindings/jswrapper/HandleObject.h b/cocos/bindings/jswrapper/HandleObject.h new file mode 100644 index 0000000..c8647b3 --- /dev/null +++ b/cocos/bindings/jswrapper/HandleObject.h @@ -0,0 +1,114 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +namespace se { + +class Object; + +/** + * HandleObject is a helper class for easily release, root and unroot an non-native-binding se::Object. + + { + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty(...); + otherObject->setProperty("foo", se::Value(obj)); + } + + is equal to: + + { + se::Object* obj = se::Object::createPlainObject(); + obj->root(); // root after object created to avoid object is garbage collected + + obj->setProperty(...); + otherObject->setProperty("foo", se::Value(obj)); + + obj->unroot(); // unroot object after obj is used. + obj->decRef(); // Decrement referent count to avoid memory leak. + } + + HandleObject should not be used to create a native binding object since the created binding object + should be holded by JavaScript VM and released in finalize callback internally. + + */ +class HandleObject { +public: + /** + * @brief The constructor of HandleObject + * @param[in] obj The se::Object to attach. + */ + explicit HandleObject(Object *obj); + + /** + * @brief The destructor of HandleObject + */ + ~HandleObject(); + + /** + * @brief The pointer operator + * @return The se::Object attached. + */ + inline Object *operator->() const { + return _obj; + } + + inline operator Object *() const { // NOLINT + return _obj; + } + + /** + * @brief Gets the se::Object attached. + * @return The se::Object attached. + */ + inline Object *get() const { + return _obj; + } + + /** + * @brief Tests whether HandleObject holds an invalid se::Object. + * @return true if HandleObject holds an invalid se::Object, otherwise false. + */ + inline bool isEmpty() const { + return (_obj == nullptr); + } + HandleObject(HandleObject &&) noexcept; + HandleObject& operator=(HandleObject &&) noexcept; + + HandleObject(const HandleObject &) = delete; + HandleObject& operator=(const HandleObject &) = delete; + + void *operator new(size_t size) = delete; + void operator delete(void *, size_t) = delete; + +private: + Object *_obj; + friend class Object; +}; + +} // namespace se diff --git a/cocos/bindings/jswrapper/MappingUtils.cpp b/cocos/bindings/jswrapper/MappingUtils.cpp new file mode 100644 index 0000000..d871bd0 --- /dev/null +++ b/cocos/bindings/jswrapper/MappingUtils.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "MappingUtils.h" +#include "base/memory/Memory.h" + +namespace se { + +// NativePtrToObjectMap +NativePtrToObjectMap::Map *NativePtrToObjectMap::__nativePtrToObjectMap = nullptr; // NOLINT +bool NativePtrToObjectMap::__isValid = false; // NOLINT + +bool NativePtrToObjectMap::init() { + if (__nativePtrToObjectMap == nullptr) { + __nativePtrToObjectMap = ccnew NativePtrToObjectMap::Map(); + } + __isValid = true; + return __nativePtrToObjectMap != nullptr; +} + +void NativePtrToObjectMap::destroy() { + if (__nativePtrToObjectMap != nullptr) { + delete __nativePtrToObjectMap; + __nativePtrToObjectMap = nullptr; + } + __isValid = false; +} + +bool NativePtrToObjectMap::isValid() { + return __isValid; +} + +CC_DEPRECATED(3.7) +NativePtrToObjectMap::Map::iterator NativePtrToObjectMap::find(void *v) { + return __nativePtrToObjectMap->find(v); +} + +CC_DEPRECATED(3.7) +NativePtrToObjectMap::Map::iterator NativePtrToObjectMap::begin() { + return __nativePtrToObjectMap->begin(); +} + +CC_DEPRECATED(3.7) +NativePtrToObjectMap::Map::iterator NativePtrToObjectMap::end() { + return __nativePtrToObjectMap->end(); +} + +void NativePtrToObjectMap::emplace(void *nativeObj, Object *seObj) { + __nativePtrToObjectMap->emplace(nativeObj, seObj); +} + +NativePtrToObjectMap::Map::iterator NativePtrToObjectMap::erase(Map::iterator iter) { + return __nativePtrToObjectMap->erase(iter); +} + +void NativePtrToObjectMap::erase(void *nativeObj) { + __nativePtrToObjectMap->erase(nativeObj); +} + +void NativePtrToObjectMap::erase(void *nativeObj, se::Object *obj) { + auto range = __nativePtrToObjectMap->equal_range(nativeObj); + for (auto itr = range.first; itr != range.second; itr++) { + if (itr->second == obj) { + __nativePtrToObjectMap->erase(itr); + break; + } + } +} + +void NativePtrToObjectMap::clear() { + __nativePtrToObjectMap->clear(); +} + +size_t NativePtrToObjectMap::size() { + return __nativePtrToObjectMap->size(); +} + +const NativePtrToObjectMap::Map &NativePtrToObjectMap::instance() { + return *__nativePtrToObjectMap; +} + +} // namespace se diff --git a/cocos/bindings/jswrapper/MappingUtils.h b/cocos/bindings/jswrapper/MappingUtils.h new file mode 100644 index 0000000..0c954ba --- /dev/null +++ b/cocos/bindings/jswrapper/MappingUtils.h @@ -0,0 +1,190 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/std/container/unordered_map.h" +#include "bindings/manual/jsb_classtype.h" + +namespace se { + +class Object; + +class NativePtrToObjectMap { +public: + // key: native ptr, value: se::Object + using Map = ccstd::unordered_multimap; + + struct OptionalCallback { + se::Object *seObj{nullptr}; + /** + * @brief Invoke callback function when object is empty + */ + template + OptionalCallback orElse(const Fn &fn) { + if (!seObj) { + fn(); + } + return *this; + } + /** + * @brief Invoke callback function when object is **NOT** empty + */ + template + OptionalCallback forEach(const Fn &fn) { + if (seObj) { + fn(seObj); + } + return *this; + } + }; + + static bool init(); + static void destroy(); + static bool isValid(); + + /** + * @deprecated Use `contains` or `filter` to query or manipulate the elements of the map, + */ + CC_DEPRECATED(3.7) + static Map::iterator find(void *v); + /** + * @deprecated Use `contains` or `filter` to query or manipulate the elements of the map, + */ + CC_DEPRECATED(3.7) + static Map::iterator begin(); + /** + * @deprecated Use `contains` or `filter` to query or manipulate the elements of the map, + */ + CC_DEPRECATED(3.7) + static Map::iterator end(); + + static Map::iterator erase(Map::iterator iter); + static void erase(void *nativeObj); + static void erase(void *nativeObj, se::Object *); + static void clear(); + static size_t size(); + + static const Map &instance(); + + /** + * @brief Return the first element of the specified key + */ + template + static se::Object *findFirst(T *nativeObj) { + auto itr = __nativePtrToObjectMap->find(nativeObj); + return itr == __nativePtrToObjectMap->end() ? nullptr : itr->second; + } + + /** + * @brief Check if the key exists in the map + */ + template + static bool contains(T *nativeObj) { + if constexpr (std::is_void_v) { + return __nativePtrToObjectMap->count(nativeObj) > 0; + } else { + auto *kls = JSBClassType::findClass(nativeObj); + auto range = __nativePtrToObjectMap->equal_range(nativeObj); + for (auto itr = range.first; itr != range.second; itr++) { + if (itr->second->_getClass() == kls) { + return true; + } + } + return false; + } + } + + /** + * @brief Iterate se::Object with specified key + */ + template + static void forEach(T *nativeObj, const Fn &func) { + se::Class *kls = nullptr; + if constexpr (!std::is_void_v) { + kls = JSBClassType::findClass(nativeObj); + } + auto range = __nativePtrToObjectMap->equal_range(nativeObj); + for (auto itr = range.first; itr != range.second; itr++) { + if (kls != nullptr && kls != itr->second->_getClass()) { + continue; + } + func(itr->second); + } + } + /** + * @brief Filter se::Object* with key and se::Class value + */ + template + static OptionalCallback filter(T *nativeObj, se::Class *kls) { + se::Object *target{nullptr}; + findWithCallback( + nativeObj, kls, + [&](se::Object *seObj) { + target = seObj; + }, + nullptr); + return OptionalCallback{target}; + } + +private: + /** + * @brief Iterate se::Object with specified se::Class + */ + template + static void findWithCallback(T *nativeObj, se::Class *kls, const Fn1 &eachCallback, const Fn2 &&emptyCallback) { + int eleCount = 0; + auto range = __nativePtrToObjectMap->equal_range(const_cast *>(nativeObj)); + constexpr bool hasEmptyCallback = std::is_invocable::value; + + if (range.first == range.second) { // empty + if constexpr (hasEmptyCallback) { + emptyCallback(); + } + } else { + for (auto itr = range.first; itr != range.second; ++itr) { + if (kls != nullptr && kls != itr->second->_getClass()) { + continue; + } + eleCount++; + CC_ASSERT_LT(eleCount, 2); + eachCallback(itr->second); + } + if constexpr (hasEmptyCallback) { + if (eleCount == 0) { + emptyCallback(); + } + } + } + } + static void emplace(void *nativeObj, Object *seObj); + static Map *__nativePtrToObjectMap; // NOLINT + static bool __isValid; // NOLINT + + friend class Object; +}; + +} // namespace se diff --git a/cocos/bindings/jswrapper/Object.h b/cocos/bindings/jswrapper/Object.h new file mode 100644 index 0000000..f439b1b --- /dev/null +++ b/cocos/bindings/jswrapper/Object.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "config.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + #include "sm/Object.h" +#endif + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + #include "v8/Object.h" +#endif + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC + #include "jsc/Object.h" +#endif + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_CHAKRACORE + #include "chakracore/Object.h" +#endif + + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_NAPI + #include "napi/Object.h" +#endif \ No newline at end of file diff --git a/cocos/bindings/jswrapper/PrivateObject.h b/cocos/bindings/jswrapper/PrivateObject.h new file mode 100644 index 0000000..b2fc727 --- /dev/null +++ b/cocos/bindings/jswrapper/PrivateObject.h @@ -0,0 +1,248 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include "base/Ptr.h" +#include "base/RefCounted.h" +#include "base/memory/Memory.h" +#include "base/HasMemberFunction.h" + +namespace se { + +class Object; +class State; +class ScriptEngine; + +template +class TypedPrivateObject; + +class PrivateObjectBase { +public: + virtual ~PrivateObjectBase() = default; + + template + inline T *get() const { + return reinterpret_cast(getRaw()); + } + + template + inline TypedPrivateObject *typed() { + return reinterpret_cast *>(this); + } + virtual const char *getName() const = 0; + virtual void *getRaw() const = 0; + virtual void allowDestroyInGC() const { + CC_ABORT(); + } + virtual void tryAllowDestroyInGC() const {} + + virtual bool isSharedPtr() const { return false; } + virtual bool isCCIntrusivePtr() const { return false; } + + friend se::Object; + friend se::State; + friend se::ScriptEngine; + + void *finalizerData{nullptr}; +}; + +template +class TypedPrivateObject : public PrivateObjectBase { +public: + inline std::shared_ptr share(); + inline cc::IntrusivePtr &ccShared(); + inline const char *getName() const override { + static_assert(!std::is_base_of::value, ""); // NOLINT // remove after using c++17 + return typeid(T).name(); + } +}; + +template +class SharedPtrPrivateObject final : public TypedPrivateObject { +public: + SharedPtrPrivateObject() = default; + explicit SharedPtrPrivateObject(const std::shared_ptr &ptr) : _data(ptr) {} + explicit SharedPtrPrivateObject(std::shared_ptr &&ptr) : _data(std::move(ptr)) {} + inline const std::shared_ptr &getData() const { + return _data; + } + + inline std::shared_ptr &getData() { + return _data; + } + constexpr bool isSharedPtr() const override { return true; } + + void *getRaw() const override { + if constexpr (std::is_const_v) { + return reinterpret_cast(const_cast *>(_data.get())); + } else { + return reinterpret_cast(_data.get()); + } + } + +private: + std::shared_ptr _data{nullptr}; +}; + +template +class CCIntrusivePtrPrivateObject final : public TypedPrivateObject { +public: + CCIntrusivePtrPrivateObject() = default; + explicit CCIntrusivePtrPrivateObject(const cc::IntrusivePtr &p) : _ptr(p) {} + explicit CCIntrusivePtrPrivateObject(cc::IntrusivePtr &&p) : _ptr(std::move(p)) {} + ~CCIntrusivePtrPrivateObject() override { + if constexpr (cc::has_setScriptObject::value) { + _ptr->setScriptObject(nullptr); + } + } + + inline const cc::IntrusivePtr &getData() const { return _ptr; } + inline cc::IntrusivePtr &getData() { return _ptr; } + + inline void *getRaw() const override { + if constexpr (std::is_const_v) { + return reinterpret_cast(const_cast *>(_ptr.get())); + } else { + return reinterpret_cast(_ptr.get()); + } + } + inline bool isCCIntrusivePtr() const override { return true; } + +private: + cc::IntrusivePtr _ptr; + friend TypedPrivateObject; +}; + +template +class RawRefPrivateObject final : public TypedPrivateObject { +public: + RawRefPrivateObject() = default; + explicit RawRefPrivateObject(T *p) : _ptr(p) {} + ~RawRefPrivateObject() override { + static_assert(!std::is_same::value, "void is not allowed!"); + if constexpr (std::is_destructible::value) { + if (_allowGC) { + delete _ptr; + } + } + _ptr = nullptr; + } + + void allowDestroyInGC() const override { + _allowGC = true; + } + void tryAllowDestroyInGC() const override { + allowDestroyInGC(); + } + + void *getRaw() const override { + // CC_ASSERT(_validate); + if constexpr (std::is_const_v) { + return reinterpret_cast(const_cast *>(_ptr)); + } else { + return reinterpret_cast(_ptr); + } + } + +private: + T *_ptr = nullptr; + // bool _validate = true; + mutable bool _allowGC = false; +}; + +template +inline std::shared_ptr TypedPrivateObject::share() { + if (isSharedPtr()) { + return reinterpret_cast *>(this)->getData(); + } + CC_ABORT(); + return std::shared_ptr(nullptr); +} +template +inline cc::IntrusivePtr &TypedPrivateObject::ccShared() { + CC_ASSERT(isCCIntrusivePtr()); + return reinterpret_cast *>(this)->_ptr; +} + +#if CC_DEBUG +inline void inHeap(void *ptr) { + constexpr size_t r = 4 * 1024; // 4K + char a; + auto anchor = reinterpret_cast(&a); + auto p = reinterpret_cast(ptr); + // must be in heaps + CC_ASSERT(std::abs(anchor - p) > r); +} +#endif + +template +inline auto *make_shared_private_object(T *cobj) { // NOLINT + static_assert(!std::is_same::value, "void * is not allowed"); +// static_assert(!std::is_pointer_v && !std::is_null_pointer_v, "bad pointer"); +#if CC_DEBUG + inHeap(cobj); +#endif + if constexpr (std::is_base_of::value) { + return ccnew CCIntrusivePtrPrivateObject(cc::IntrusivePtr(cobj)); + } else { + return ccnew SharedPtrPrivateObject(std::shared_ptr(cobj)); + } +} +template +inline auto *shared_ptr_private_object(std::shared_ptr &&ptr) { // NOLINT + static_assert(!std::is_base_of::value, "cc::RefCounted is not acceptable for shared_ptr"); + return ccnew SharedPtrPrivateObject(std::forward>(ptr)); +} + +template +inline auto *shared_ptr_private_object(const std::shared_ptr &ptr) { // NOLINT + static_assert(!std::is_base_of::value, "cc::RefCounted is not acceptable for shared_ptr"); + return ccnew SharedPtrPrivateObject(ptr); +} + +template +inline auto *rawref_private_object(T *ptr) { // NOLINT + // static_assert(false, "always fail"); +// static_assert(!std::is_base_of::value, "cc::RefCounted is not acceptable for shared_ptr"); +#if CC_DEBUG + inHeap(ptr); +#endif + return ccnew RawRefPrivateObject(ptr); +} + +template +inline auto *ccintrusive_ptr_private_object(const cc::IntrusivePtr &ptr) { // NOLINT + static_assert(std::is_base_of::value, "cc::RefCounted expected!"); + return ccnew CCIntrusivePtrPrivateObject(ptr); +} +template +inline auto *ccintrusive_ptr_private_object(T *cobj) { // NOLINT + static_assert(std::is_base_of::value, "cc::RefCounted expected!"); + return ccnew CCIntrusivePtrPrivateObject(cc::IntrusivePtr(cobj)); +} +} // namespace se diff --git a/cocos/bindings/jswrapper/RefCounter.cpp b/cocos/bindings/jswrapper/RefCounter.cpp new file mode 100644 index 0000000..b5cd282 --- /dev/null +++ b/cocos/bindings/jswrapper/RefCounter.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "RefCounter.h" + +namespace se { + +void RefCounter::incRef() { + ++_refCount; +} + +void RefCounter::decRef() { + --_refCount; + if (_refCount == 0) { + delete this; + } +} + +unsigned int RefCounter::getRefCount() { + return _refCount; +} + +RefCounter::RefCounter() +: _refCount(1) { +} + +RefCounter::~RefCounter() { +} + +} // namespace se diff --git a/cocos/bindings/jswrapper/RefCounter.h b/cocos/bindings/jswrapper/RefCounter.h new file mode 100644 index 0000000..6771b1a --- /dev/null +++ b/cocos/bindings/jswrapper/RefCounter.h @@ -0,0 +1,65 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { + +/** + * This class is used to manage reference-counting providing a simple interface and a counter. + * + */ +class RefCounter { +public: + /** + * @brief Increases reference count by one. + */ + void incRef(); + + /** + * @brief Decrements the reference count, if it reaches zero, destroys this instance of RefCounter to release its memory. + * @note Please note that after calling this function, the caller should absolutely avoid to use the pointer to this instance since it may not be valid anymore. + */ + void decRef(); + + /** + * @brief Gets reference count. + * @return The reference count. + * @note When this goes to zero during a decRef() call, the object will auto-delete itself. + */ + unsigned int getRefCount(); + +protected: + // Default constructor + // Initialises the internal reference count to 1. + RefCounter(); + // Destructor + virtual ~RefCounter(); + +private: + unsigned int _refCount; +}; + +} // namespace se diff --git a/cocos/bindings/jswrapper/SeApi.h b/cocos/bindings/jswrapper/SeApi.h new file mode 100644 index 0000000..b187a72 --- /dev/null +++ b/cocos/bindings/jswrapper/SeApi.h @@ -0,0 +1,53 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "config.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + #include "sm/SeApi.h" +#endif + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + #include "v8/SeApi.h" +#endif + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_JSC + #include "jsc/SeApi.h" +#endif + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_CHAKRACORE + #include "chakracore/SeApi.h" +#endif + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_NAPI + #include "napi/SeApi.h" +#endif + +#include "HandleObject.h" +#include "Object.h" +#include "State.h" +#include "Value.h" diff --git a/cocos/bindings/jswrapper/State.h b/cocos/bindings/jswrapper/State.h new file mode 100644 index 0000000..2580428 --- /dev/null +++ b/cocos/bindings/jswrapper/State.h @@ -0,0 +1,111 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "Object.h" +#include "PrivateObject.h" +#include "Value.h" + +namespace se { + +class Object; + +/** + * State represents an environment while a function or an accesstor is invoked from JavaScript. + */ +class State final { +public: + /** + * @brief Gets void* pointer of `this` object's private data. + * @return A void* pointer of `this` object's private data. + */ + inline void *nativeThisObject() const { + return _thisObject != nullptr ? _thisObject->getPrivateData() : nullptr; + } + + /** + * @brief Gets the arguments of native binding functions or accesstors. + * @return The arguments of native binding functions or accesstors. + */ + inline const ValueArray &args() const { + return _args != nullptr ? (*_args) : EmptyValueArray; + } + + /** + * @brief Gets the JavaScript `this` object wrapped in se::Object. + * @return The JavaScript `this` object wrapped in se::Object. + */ + inline Object *thisObject() const { + return _thisObject; + } + + /** + * @brief Gets the return value reference. Used for setting return value for a function. + * @return The return value reference. + */ + inline const Value &rval() const { + return _retVal; + } + + inline Value &rval() { + return _retVal; + } + + // Private API used in wrapper + /** + * @brief + * @param[in] + * @return + */ + ~State() { + // Inline to speed up high-frequency calls without significant impact on code size + SAFE_DEC_REF(_thisObject); + } + + explicit State(Object *thisObject) : _thisObject(thisObject) { + if (_thisObject != nullptr) { + _thisObject->incRef(); + } + } + State(Object *thisObject, const ValueArray &args) : _thisObject(thisObject), + _args(&args) { + if (_thisObject != nullptr) { + _thisObject->incRef(); + } + } + + // Disable copy/move constructor, copy/move assigment + State(const State &) = delete; + State(State &&) noexcept = delete; + State &operator=(const State &) = delete; + State &operator=(State &&) noexcept = delete; + +private: + Object *_thisObject{nullptr}; // weak ref + const ValueArray *_args{nullptr}; // weak ref + Value _retVal; // weak ref +}; +} // namespace se diff --git a/cocos/bindings/jswrapper/Value.cpp b/cocos/bindings/jswrapper/Value.cpp new file mode 100644 index 0000000..2462245 --- /dev/null +++ b/cocos/bindings/jswrapper/Value.cpp @@ -0,0 +1,576 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Value.h" +#include +#include +#include +#include +#include +#include "Object.h" +#include + +namespace se { + +#ifndef LIKELY + #ifdef __GNUC__ + #define LIKELY(expr) __builtin_expect(!!(expr), 1) + #else + #define LIKELY(expr) expr + #endif +#endif + +template +inline + typename std::enable_if::value, T>::type + fromDoubleToIntegral(double d) { + return static_cast(static_cast(d)); +} + +template +inline + typename std::enable_if::value, T>::type + fromDoubleToIntegral(double d) { + return static_cast(d); +} + +#define CONVERT_TO_TYPE(type) fromDoubleToIntegral(toDouble()) + +ValueArray EmptyValueArray; // NOLINT(readability-identifier-naming) + +Value Value::Null = Value(Type::Null); //NOLINT(readability-identifier-naming) +Value Value::Undefined = Value(Type::Undefined); //NOLINT(readability-identifier-naming) + +Value::Value() +: _type(Type::Undefined), + _autoRootUnroot(false) { + memset(&_u, 0, sizeof(_u)); +} + +Value::Value(Type type) +: _type(Type::Undefined), + _autoRootUnroot(false) { + memset(&_u, 0, sizeof(_u)); + reset(type); +} + +Value::Value(const Value &v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + *this = v; +} + +Value::Value(Value &&v) noexcept +: _type(Type::Undefined), + _autoRootUnroot(false) { + *this = std::move(v); +} + +Value::Value(bool v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setBoolean(v); +} + +Value::Value(int8_t v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setInt8(v); +} + +Value::Value(uint8_t v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setUint8(v); +} + +Value::Value(int32_t v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setInt32(v); +} + +Value::Value(uint32_t v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setUint32(v); +} + +Value::Value(int16_t v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setInt16(v); +} + +Value::Value(uint16_t v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setUint16(v); +} + +Value::Value(int64_t v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setInt64(v); +} + +Value::Value(uint64_t v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setUint64(v); +} + +Value::Value(float v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setFloat(v); +} + +Value::Value(double v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setDouble(v); +} + +Value::Value(const char *v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setString(v); +} + +Value::Value(const ccstd::string &v) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setString(v); +} + +Value::Value(Object *o, bool autoRootUnroot /* = false*/) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setObject(o, autoRootUnroot); +} + +Value::Value(const HandleObject &o, bool autoRootUnroot /* = false*/) +: _type(Type::Undefined), + _autoRootUnroot(false) { + setObject(o, autoRootUnroot); +} + +Value::~Value() { + reset(Type::Undefined); +} + +Value &Value::operator=(const Value &v) { + if (this != &v) { + reset(v.getType()); + + switch (_type) { + case Type::Null: + case Type::Undefined: { + memset(&_u, 0, sizeof(_u)); + break; + } + case Type::Number: + _u._number = v._u._number; + break; + case Type::BigInt: + _u._bigint = v._u._bigint; + break; + case Type::String: + *_u._string = *v._u._string; + break; + case Type::Boolean: + _u._boolean = v._u._boolean; + break; + case Type::Object: { + setObject(v._u._object, v._autoRootUnroot); + } break; + default: + break; + } + } + return *this; +} + +Value &Value::operator=(Value &&v) noexcept { + if (this != &v) { + reset(v.getType()); + + switch (_type) { + case Type::Null: + case Type::Undefined: { + memset(&_u, 0, sizeof(_u)); + break; + } + case Type::Number: + _u._number = v._u._number; + break; + case Type::BigInt: + _u._bigint = v._u._bigint; + break; + case Type::String: + *_u._string = std::move(*v._u._string); + break; + case Type::Boolean: + _u._boolean = v._u._boolean; + break; + case Type::Object: { + if (_u._object != nullptr) // When old value is also an Object, reset will take no effect, therefore, _u._object may not be nullptr. + { + if (_autoRootUnroot) { + _u._object->unroot(); + } + _u._object->decRef(); + } + _u._object = v._u._object; + _autoRootUnroot = v._autoRootUnroot; + v._u._object = nullptr; // Reset to nullptr here to avoid 'release' operation in v.reset(Type::Undefined) since it's a move operation here. + v._autoRootUnroot = false; + } break; + default: + break; + } + + v.reset(Type::Undefined); + } + return *this; +} + +// Value& Value::operator=(bool v) +// { +// setBoolean(v); +// return *this; +// } +// +// Value& Value::operator=(double v) +// { +// setNumber(v); +// return *this; +// } +// +// Value& Value::operator=(const ccstd::string& v) +// { +// setString(v); +// return *this; +// } +// +// Value& Value::operator=(Object* o) +// { +// setObject(o); +// return *this; +// } +// +// Value& Value::operator=(const HandleObject& o) +// { +// setObject(o); +// return *this; +// } + +void Value::setUndefined() { + reset(Type::Undefined); +} + +void Value::setNull() { + reset(Type::Null); +} + +void Value::setBoolean(bool v) { + reset(Type::Boolean); + _u._boolean = v; +} + +void Value::setInt8(int8_t v) { + reset(Type::Number); + _u._number = static_cast(v); +} + +void Value::setUint8(uint8_t v) { + reset(Type::Number); + _u._number = static_cast(v); +} + +void Value::setInt32(int32_t v) { + reset(Type::Number); + _u._number = static_cast(v); +} + +void Value::setUint32(uint32_t v) { + reset(Type::Number); + _u._number = static_cast(v); +} + +void Value::setInt16(int16_t v) { + reset(Type::Number); + _u._number = static_cast(v); +} + +void Value::setUint16(uint16_t v) { + reset(Type::Number); + _u._number = static_cast(v); +} + +void Value::setInt64(int64_t v) { + reset(Type::BigInt); + _u._bigint = v; +} + +void Value::setUint64(uint64_t v) { + reset(Type::BigInt); + _u._bigint = static_cast(v); +} + +void Value::setFloat(float v) { + reset(Type::Number); + _u._number = static_cast(v); +} + +void Value::setDouble(double v) { + reset(Type::Number); + _u._number = v; +} + +void Value::setString(const char *v) { + if (v != nullptr) { + reset(Type::String); + *_u._string = v; + } else { + reset(Type::Null); + } +} + +void Value::setString(const ccstd::string &v) { + reset(Type::String); + *_u._string = v; +} + +void Value::setString(const std::string_view &v) { + reset(Type::String); + _u._string->assign(v.data(), 0, v.length()); +} + +void Value::setObject(Object *object, bool autoRootUnroot /* = false*/) { + if (object == nullptr) { + reset(Type::Null); + return; + } + + if (_type != Type::Object) { + reset(Type::Object); + } + + if (_u._object != object) { + if (object != nullptr) { + object->incRef(); + if (autoRootUnroot) { + object->root(); + } + } + + if (_u._object != nullptr) // When old value is also an Object, reset will take no effect, therefore, _u._object may not be nullptr. + { + if (_autoRootUnroot) { + _u._object->unroot(); + } + _u._object->decRef(); + } + _u._object = object; + _autoRootUnroot = autoRootUnroot; + } else { + _autoRootUnroot = autoRootUnroot; + if (_autoRootUnroot) { + _u._object->root(); + } + } +} + +void Value::setObject(const HandleObject &o, bool autoRootUnroot /* = false*/) { + setObject(o.get(), autoRootUnroot); +} + +int8_t Value::toInt8() const { + return CONVERT_TO_TYPE(int8_t); +} + +uint8_t Value::toUint8() const { + return CONVERT_TO_TYPE(uint8_t); +} + +int16_t Value::toInt16() const { + return CONVERT_TO_TYPE(int16_t); +} + +uint16_t Value::toUint16() const { + return CONVERT_TO_TYPE(uint16_t); +} + +int32_t Value::toInt32() const { + return CONVERT_TO_TYPE(int32_t); +} + +uint32_t Value::toUint32() const { + return CONVERT_TO_TYPE(uint32_t); +} + +int64_t Value::toInt64() const { + CC_ASSERT(isBigInt() || isNumber()); + return _type == Type::BigInt ? _u._bigint : CONVERT_TO_TYPE(int64_t); +} + +uint64_t Value::toUint64() const { + CC_ASSERT(isBigInt() || isNumber()); + return _type == Type::BigInt ? static_cast(_u._bigint) : CONVERT_TO_TYPE(uint64_t); +} + +float Value::toFloat() const { + return static_cast(toDouble()); +} + +double Value::toDouble() const { + CC_ASSERT(_type == Type::Number || _type == Type::Boolean || _type == Type::BigInt || _type == Type::String); + if (LIKELY(_type == Type::Number)) { + return _u._number; + } + if (_type == Type::BigInt) { + // CC_LOG_WARNING("convert int64 to double"); + return static_cast(_u._bigint); + } + + if (_type == Type::String) { + return std::stod(*_u._string); + } + + return _u._boolean ? 1.0 : 0.0; +} + +bool Value::toBoolean() const { + CC_ASSERT_EQ(_type, Type::Boolean); + return _u._boolean; +} + +const ccstd::string &Value::toString() const { + CC_ASSERT_EQ(_type, Type::String); + return *_u._string; +} + +ccstd::string Value::toStringForce() const { + std::stringstream ss; + if (_type == Type::String) { + ss << *_u._string; + } else if (_type == Type::Boolean) { + ss << (_u._boolean ? "true" : "false"); + } else if (_type == Type::Number) { + char tmp[50] = {0}; + snprintf(tmp, sizeof(tmp), "%.17g", _u._number); + ss << tmp; + } else if (_type == Type::BigInt) { + ss << _u._bigint; + } else if (_type == Type::Object) { + ss << toObject()->toString(); + } else if (_type == Type::Null) { + ss << "null"; + } else if (_type == Type::Undefined) { + ss << "undefined"; + } else { + CC_ABORT(); + ss << "[[BadValueType]]"; + } + return ss.str(); +} + +Object *Value::toObject() const { + assert(isObject()); + return _u._object; +} + +void Value::reset(Type type) { + if (_type != type) { + switch (_type) { + case Type::String: + delete _u._string; + break; + case Type::Object: { + if (_u._object != nullptr) { + if (_autoRootUnroot) { + _u._object->unroot(); + } + _u._object->decRef(); + _u._object = nullptr; + } + + _autoRootUnroot = false; + break; + } + default: + break; + } + + memset(&_u, 0, sizeof(_u)); + + switch (type) { + case Type::String: + _u._string = ccnew ccstd::string(); + break; + default: + break; + } + _type = type; + } +} + +/////////////////// deprecated methods //////////////////// + +void Value::setLong(long v) { // NOLINT(google-runtime-int) + setDouble(static_cast(v)); +} + +void Value::setUIntptr_t(uintptr_t v) { + setDouble(static_cast(v)); +} + +void Value::setUlong(unsigned long v) { // NOLINT(google-runtime-int) + setDouble(static_cast(v)); +} + +void Value::setNumber(double v) { + setDouble(v); +} + +unsigned int Value::toUint() const { + return CONVERT_TO_TYPE(unsigned int); +} + +long Value::toLong() const { // NOLINT(google-runtime-int) + return CONVERT_TO_TYPE(long); +} + +unsigned long Value::toUlong() const { // NOLINT(google-runtime-int) + return CONVERT_TO_TYPE(unsigned long); +} + +double Value::toNumber() const { + return toDouble(); +} + +} // namespace se diff --git a/cocos/bindings/jswrapper/Value.h b/cocos/bindings/jswrapper/Value.h new file mode 100644 index 0000000..553a8fb --- /dev/null +++ b/cocos/bindings/jswrapper/Value.h @@ -0,0 +1,498 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "HandleObject.h" +#include "base/Macros.h" +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +namespace se { + +class Object; + +/** + * se::Value represents a JavaScript Value. + * It could be undefined, null, number, boolean, string and object which exists in JavaScript. + */ +class Value final { +public: + enum class Type : char { + Undefined = 0, // NOLINT(readability-identifier-naming) + Null, // NOLINT(readability-identifier-naming) + Number, // NOLINT(readability-identifier-naming) + Boolean, // NOLINT(readability-identifier-naming) + String, // NOLINT(readability-identifier-naming) + Object, // NOLINT(readability-identifier-naming) + BigInt, // NOLINT(readability-identifier-naming) + }; + + static Value Null; // NOLINT(readability-identifier-naming) + static Value Undefined; // NOLINT(readability-identifier-naming) + + /** + * @brief The default constructor. + */ + Value(); + + /** + * @brief The copy constructor. + */ + Value(const Value &v); + + /** + * @brief The move constructor. + */ + Value(Value &&v) noexcept; + + /** + * @brief The constructor with a boolean argument. + */ + explicit Value(bool v); + + /** + * @brief The constructor with a int8_t argument. + */ + explicit Value(int8_t v); + + /** + * @brief The constructor with a uint8_t argument. + */ + explicit Value(uint8_t v); + + /** + * @brief The constructor with a int16_t argument. + */ + explicit Value(int16_t v); + + /** + * @brief The constructor with a uint16_t argument. + */ + explicit Value(uint16_t v); + + /** + * @brief The constructor with a int32_t argument. + */ + explicit Value(int32_t v); + + /** + * @brief The constructor with a uint32_t argument. + */ + explicit Value(uint32_t v); + + /** + * @brief The constructor with a uint64_t argument. + */ + explicit Value(uint64_t v); + + /** + * @brief The constructor with a int64_t argument. + */ + explicit Value(int64_t v); + + /** + * @brief The constructor with a float argument. + */ + explicit Value(float v); + + /** + * @brief The constructor with a double argument. + */ + explicit Value(double v); + + /** + * @brief The constructor with an UTF8 null-terminated string argument. + */ + explicit Value(const char *v); + + /** + * @brief The constructor with an UTF8 string argument. + */ + explicit Value(const ccstd::string &v); + + /** + * @brief The constructor with an Object. + * @param o A se::Object to be set. + * @param[in] autoRootUnroot Whether to root se::Object and unroot it in se::Value's destructor or unroot it while object is replaced. Default value is false. + */ + explicit Value(Object *o, bool autoRootUnroot = false); + + /** + * @brief The constructor with a HandleObject. + * @param o A se::HandleObject to be set. + * @param[in] autoRootUnroot Whether to root se::HandleObject and unroot it in se::Value's destructor or unroot it while object is replaced. Default value is false. + */ + explicit Value(const HandleObject &o, bool autoRootUnroot = false); + + /** + * @brief The destructor of se::Value + */ + ~Value(); + + /** + * @brief The copy assignment operator. + */ + Value &operator=(const Value &v); + + /** + * @brief The move assignment operator. + */ + Value &operator=(Value &&v) noexcept; + + /** + * @brief Sets se::Value to a long value. + * @param[in] v The long value to be set. + */ + CC_DEPRECATED(3.3) + void setLong(long v); // NOLINT(google-runtime-int) + + /** + * @brief Sets se::Value to a uintptr_t value. + * @param[in] v The uintptr_t value to be set. + */ + CC_DEPRECATED(3.3) + void setUIntptr_t(uintptr_t v); // NOLINT(readability-identifier-naming) + + /** + * @brief Sets se::Value to a unsigned long value. + * @param[in] v The unsigned long value to be set. + */ + CC_DEPRECATED(3.3) + void setUlong(unsigned long v); // NOLINT(google-runtime-int) + + /** + * @brief Sets se::Value to a double value. + * @param[in] v The double value to be set. + */ + CC_DEPRECATED(3.3, "Use setDouble instead") + void setNumber(double v); + + CC_DEPRECATED(3.3) + unsigned int toUint() const; + + /** + * @brief Converts se::Value to long. + * @return long integer. + */ + CC_DEPRECATED(3.3) + long toLong() const; // NOLINT(google-runtime-int) + /** + * @brief Converts se::Value to unsigned long. + * @return unsigned long integer. + */ + CC_DEPRECATED(3.3) + unsigned long toUlong() const; // NOLINT(google-runtime-int) + + /** + * @brief Converts se::Value to double number. + * @return double number. + */ + CC_DEPRECATED(3.3, "Use toDouble instead") + double toNumber() const; + + /** + * @brief Sets se::Value to undefined. + */ + void setUndefined(); + + /** + * @brief Sets se::Value to null. + */ + void setNull(); + + /** + * @brief Sets se::Value to a boolean value. + * @param[in] v The boolean value to be set. + */ + void setBoolean(bool v); + + /** + * @brief Sets se::Value to a int8_t value. + * @param[in] v The int8_t value to be set. + */ + void setInt8(int8_t v); + + /** + * @brief Sets se::Value to a uint8_t value. + * @param[in] v The uint8_t value to be set. + */ + void setUint8(uint8_t v); + + /** + * @brief Sets se::Value to a int16_t value. + * @param[in] v The int16_t value to be set. + */ + void setInt16(int16_t v); + + /** + * @brief Sets se::Value to a uint16_t value. + * @param[in] v The uint16_t value to be set. + */ + void setUint16(uint16_t v); + + /** + * @brief Sets se::Value to a int32_t value. + * @param[in] v The int32_t value to be set. + */ + void setInt32(int32_t v); + + /** + * @brief Sets se::Value to a uint32_t value. + * @param[in] v The uint32_t value to be set. + */ + void setUint32(uint32_t v); + + /** + * @brief Sets se::Value to a unsigned int64_t + * @param[in] v The unsigned int64_t value to be set. + */ + void setUint64(uint64_t v); + + /** + * @brief Sets se::Value to a int64_t value. + * @param[in] v The int64_t value to be set. + */ + void setInt64(int64_t v); + + /** + * @brief Sets se::Value to a float value. + * @param[in] v The float value to be set. + */ + void setFloat(float v); + + /** + * @brief Sets se::Value to a double value. + * @param[in] v The double value to be set. + */ + void setDouble(double v); + + /** + * @brief Sets se::Value to an UTF8 null-terminated string value. + * @param[in] v The UTF8 null-terminated string value to be set. + */ + void setString(const char *v); + + /** + * @brief Sets se::Value to string value. + * @param[in] v The string value to be set. + */ + void setString(const ccstd::string &v); + + /** + * @brief Sets se::Value to string value by string_view. + * @param[in] v The string_view + */ + void setString(const std::string_view &v); + + /** + * @brief Sets se::Value to se::Object value. + * @param[in] o The se::Object to be set. + * @param[in] autoRootUnroot Whether to root se::Object and unroot it in se::Value's destructor or unroot it while object is replaced. Default value is false. + */ + void setObject(Object *o, bool autoRootUnroot = false); + + /** + * @brief Sets se::Value to se::HandleObject value. + * @param[in] o The se::Object to be set. + * @param[in] autoRootUnroot Whether to root se::HandleObject and unroot it in se::Value's destructor or unroot it while object is replaced. Default value is false. + */ + void setObject(const HandleObject &o, bool autoRootUnroot = false); + + /** + * @brief Converts se::Value to int8_t. + * @return int8_t integer. + */ + int8_t toInt8() const; + + /** + * @brief Converts se::Value to uint8_t. + * @return uint8_t integer. + */ + uint8_t toUint8() const; + + /** + * @brief Converts se::Value to int16_t. + * @return int16_t integer. + */ + int16_t toInt16() const; + + /** + * @brief Converts se::Value to uint16_t. + * @return uint16_t integer. + */ + uint16_t toUint16() const; + + /** + * @brief Converts se::Value to int32_t. + * @return int32_t integer. + */ + int32_t toInt32() const; + + /** + * @brief Converts se::Value to uint32_t. + * @return uint32_t integer. + */ + uint32_t toUint32() const; + + /** + * @brief Converts se::Value to int64_t + * @return signed int64 + */ + int64_t toInt64() const; + + /** + * @brief Converts se::Value to unsigned uint64_t. + * @return unsigned int64. + */ + uint64_t toUint64() const; + + /** + * @brief Converts se::Value to float number. + * @return float number. + */ + float toFloat() const; + + /** + * @brief Converts se::Value to double number. + * @return double number. + */ + double toDouble() const; + + /** + * @brief Converts se::Value to boolean. + * @return boolean. + */ + bool toBoolean() const; + + /** + * @brief Gets ccstd::string if se::Value stores a string. It will trigger an assertion if se::Value isn't a string. + * @return A ccstd::string reference. + * @see toStringForce + */ + const ccstd::string &toString() const; + + /** + * @brief Converts a se::Value to ccstd::string. Could be invoked even when se::Value isn't a string. + * @return A copied ccstd::string value. + * @see toString + */ + ccstd::string toStringForce() const; + + /** + * @brief Gets the se::Object pointer if se::Value stores an object. It will trigger an assertion if se::Value isn't an object. + * @return A se::Object pointer. + */ + Object *toObject() const; + + /** + * @brief Gets the type of se::Value. + * @return The type of se::Value. + */ + inline Type getType() const { return _type; } + + /** + * @brief Tests whether se::Value stores a number. + * @return true if se::Value stores a number, otherwise false. + */ + inline bool isNumber() const { return _type == Type::Number; } + + /** + * @brief Tests whether se::Value stores a Bigint. + * @return true if se::Value stores a uint64_t or a int64_t, otherwise false. + */ + inline bool isBigInt() const { return _type == Type::BigInt; } + + /** + * @brief Tests whether se::Value stores a string. + * @return true if se::Value stores a string, otherwise false. + */ + inline bool isString() const { return _type == Type::String; } + + /** + * @brief Tests whether se::Value stores an object. + * @return true if se::Value stores an object, otherwise false. + */ + inline bool isObject() const { return _type == Type::Object; } + + /** + * @brief Tests whether se::Value stores a boolean. + * @return true if se::Value stores a boolean, otherwise false. + */ + inline bool isBoolean() const { return _type == Type::Boolean; } + + /** + * @brief Tests whether se::Value stores an undefined value. + * @return true if se::Value stores an undefined value, otherwise false. + */ + inline bool isUndefined() const { return _type == Type::Undefined; } + + /** + * @brief Tests whether se::Value stores a null value. + * @return true if se::Value stores a null value, otherwise false. + */ + inline bool isNull() const { return _type == Type::Null; } + + /** + * @brief Tests whether se::Value stores a null or an undefined value. + * @return true if se::Value stores a null or an undefined value, otherwise false. + */ + inline bool isNullOrUndefined() const { return (isNull() || isUndefined()); } + + size_t toSize() const { + return static_cast(toDouble()); + } + + void setSize(size_t v) { + setDouble(static_cast(v)); + } + + uintptr_t asPtr() const { + return static_cast(toUint64()); + } + +private: + explicit Value(Type type); + void reset(Type type); + + union { + bool _boolean; + double _number; + ccstd::string *_string; + Object *_object; + int64_t _bigint; + } _u; + + Type _type; + bool _autoRootUnroot; +}; + +using ValueArray = ccstd::vector; +extern ValueArray EmptyValueArray; // NOLINT(readability-identifier-naming) + +} // namespace se + +using se_object_ptr = se::Object *; // NOLINT(readability-identifier-naming) diff --git a/cocos/bindings/jswrapper/ValueArrayPool.cpp b/cocos/bindings/jswrapper/ValueArrayPool.cpp new file mode 100644 index 0000000..ee2b7f0 --- /dev/null +++ b/cocos/bindings/jswrapper/ValueArrayPool.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "ValueArrayPool.h" +#include "base/Macros.h" +#include "base/memory/Memory.h" +#include "config.h" + +namespace se { + +ValueArrayPool gValueArrayPool; + +#define SE_DEFAULT_MAX_DEPTH (5) + +ValueArrayPool::ValueArrayPool() { + _pools.resize(SE_DEFAULT_MAX_DEPTH); + for (uint32_t i = 0; i < SE_DEFAULT_MAX_DEPTH; ++i) { + initPool(i); + } +} + +ValueArray &ValueArrayPool::get(uint32_t argc, bool &outNeedDelete) { + if (SE_UNLIKELY(_depth >= _pools.size())) { + outNeedDelete = true; + auto *ret = ccnew ValueArray(); + ret->resize(argc); + return *ret; + } + + outNeedDelete = false; + CC_ASSERT_LE(argc, MAX_ARGS); + auto &ret = _pools[_depth][argc]; + CC_ASSERT(ret.size() == argc); + return ret; +} + +void ValueArrayPool::initPool(uint32_t index) { + auto &pool = _pools[index]; + uint32_t i = 0; + for (auto &arr : pool) { + arr.resize(i); + ++i; + } +} + +} // namespace se diff --git a/cocos/bindings/jswrapper/ValueArrayPool.h b/cocos/bindings/jswrapper/ValueArrayPool.h new file mode 100644 index 0000000..5ace439 --- /dev/null +++ b/cocos/bindings/jswrapper/ValueArrayPool.h @@ -0,0 +1,73 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "Value.h" +#include "base/std/container/array.h" + +namespace se { + +class CallbackDepthGuard final { +public: + CallbackDepthGuard(ValueArray &arr, uint32_t &depth, bool needDelete) + : _arr(arr), _depth(depth), _needDelete(needDelete) { + ++_depth; + } + + ~CallbackDepthGuard() { + --_depth; + for (auto &e : _arr) { + e.setUndefined(); + } + if (_needDelete) { + delete &_arr; + } + } + +private: + ValueArray &_arr; + uint32_t &_depth; + const bool _needDelete{false}; +}; + +class ValueArrayPool final { +public: + static const uint32_t MAX_ARGS = 20; + + ValueArrayPool(); + + ValueArray &get(uint32_t argc, bool &outNeedDelete); + + uint32_t _depth{0}; + +private: + void initPool(uint32_t index); + ccstd::vector> _pools; +}; + +extern ValueArrayPool gValueArrayPool; + +} // namespace se diff --git a/cocos/bindings/jswrapper/config.cpp b/cocos/bindings/jswrapper/config.cpp new file mode 100644 index 0000000..0210774 --- /dev/null +++ b/cocos/bindings/jswrapper/config.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "bindings/jswrapper/config.h" +#include +#include "base/Log.h" + +void selogMessage(cc::LogLevel level, const char *tag, const char *format, ...) { + char logbuf[512] = {0}; + va_list argp; + va_start(argp, format); + (void)std::vsnprintf(logbuf, sizeof(logbuf), format, argp); + va_end(argp); + cc::Log::logMessage(cc::LogType::KERNEL, level, "%s %s", tag, logbuf); +} diff --git a/cocos/bindings/jswrapper/config.h b/cocos/bindings/jswrapper/config.h new file mode 100644 index 0000000..c66c13a --- /dev/null +++ b/cocos/bindings/jswrapper/config.h @@ -0,0 +1,107 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +#include "base/Log.h" + +#define SCRIPT_ENGINE_NONE 0 +#define SCRIPT_ENGINE_SM 1 +#define SCRIPT_ENGINE_V8 2 +#define SCRIPT_ENGINE_NAPI 5 + +#ifndef SCRIPT_ENGINE_TYPE +#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY + #define SCRIPT_ENGINE_TYPE SCRIPT_ENGINE_NAPI +#else + #define SCRIPT_ENGINE_TYPE SCRIPT_ENGINE_V8 +#endif +#endif + +#define SE_LOG_TO_JS_ENV 0 // print log to JavaScript environment, for example DevTools + +#if !defined(ANDROID_INSTANT) && defined(USE_V8_DEBUGGER) && USE_V8_DEBUGGER > 0 + #define SE_ENABLE_INSPECTOR 1 + #define SE_DEBUG 2 + #define HAVE_INSPECTOR 1 +#else + #define SE_ENABLE_INSPECTOR 0 + #define SE_DEBUG 0 + #define HAVE_INSPECTOR 0 +#endif + +#if defined(__clang__) || defined(__GNUC__) + #define CC_FORMAT_HINT(si, fi) __attribute__((__format__(__printf__, si, fi))) +#else + #define CC_FORMAT_HINT(si, fi) +#endif + +CC_FORMAT_HINT(3, 4) +void selogMessage(cc::LogLevel level, const char *tag, const char *format, ...); + +#if CC_DEBUG +#define SE_LOGD(...) selogMessage(cc::LogLevel::LEVEL_DEBUG, "D/", ##__VA_ARGS__) +#else +#define SE_LOGD(...) +#endif +#define SE_LOGE(...) selogMessage(cc::LogLevel::ERR, "E/", ##__VA_ARGS__) + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) + + #define __POSIX__ //NOLINT + +#endif + +#if defined(_WIN32) && defined(_WINDOWS) + #include + + #if !defined(__SSIZE_T) +typedef SSIZE_T ssize_t; + #define __SSIZE_T + #define _SSIZE_T_DEFINED // libuv also defines ssize_t, use the one defined here. + #endif // __SSIZE_T + +#endif // #if defined(_WIN32) && defined(_WINDOWS) + +/** @def SE_DEPRECATED_ATTRIBUTE + * Only certain compilers support __attribute__((deprecated)). + */ +#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))) + #define SE_DEPRECATED_ATTRIBUTE __attribute__((deprecated)) +#elif _MSC_VER >= 1400 //vs 2005 or higher + #define SE_DEPRECATED_ATTRIBUTE __declspec(deprecated) +#else + #define SE_DEPRECATED_ATTRIBUTE +#endif // SE_DEPRECATED_ATTRIBUTE + +#if defined(__GNUC__) && __GNUC__ >= 4 + #define SE_LIKELY(x) (__builtin_expect((x), 1)) + #define SE_UNLIKELY(x) (__builtin_expect((x), 0)) +#else + #define SE_LIKELY(x) (x) + #define SE_UNLIKELY(x) (x) +#endif diff --git a/cocos/bindings/jswrapper/napi/Class.cpp b/cocos/bindings/jswrapper/napi/Class.cpp new file mode 100644 index 0000000..0a36750 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/Class.cpp @@ -0,0 +1,248 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Class.h" +#include +#include "CommonHeader.h" +#include "ScriptEngine.h" +#include "Utils.h" + +namespace se { + +napi_value *Class::_exports = nullptr; +std::vector __allClasses; + +Class::Class() { + __allClasses.push_back(this); +}; + +Class::~Class() { +} + +/* static */ +Class *Class::create(const std::string &clsName, se::Object *parent, Object *parentProto, napi_callback ctor) { + Class *cls = new Class(); + if (cls != nullptr && !cls->init(clsName, parent, parentProto, ctor)) { + delete cls; + cls = nullptr; + } + return cls; +} + +Class* Class::create(const std::initializer_list &classPath, se::Object *parent, Object *parentProto, napi_callback ctor) { + se::AutoHandleScope scope; + se::Object *currentParent = parent; + se::Value tmp; + for (auto i = 0; i < classPath.size() - 1; i++) { + bool ok = currentParent->getProperty(*(classPath.begin() + i), &tmp); + CC_ASSERT(ok); // class or namespace in path is not defined + currentParent = tmp.toObject(); + } + return create(*(classPath.end() - 1), currentParent, parentProto, ctor); +} + +bool Class::init(const std::string &clsName, Object *parent, Object *parentProto, napi_callback ctor) { + _name = clsName; + _parent = parent; + if (_parent != nullptr) + _parent->incRef(); + _parentProto = parentProto; + + if (_parentProto != nullptr) + _parentProto->incRef(); + if (ctor) { + _ctorFunc = ctor; + } + + return true; +} + +napi_value Class::_defaultCtor(napi_env env, napi_callback_info info) { + LOGE("check default ctor called"); + return nullptr; +} + +void Class::defineProperty(const char* name, napi_callback g, napi_callback s) { + _properties.push_back({name, nullptr, nullptr, g, s, 0, napi_default_jsproperty, 0}); +} + +void Class::defineProperty(const std::initializer_list &names, napi_callback g, napi_callback s) { + for (const auto *name : names) { + defineProperty(name, g, s); + } +} + +void Class::defineStaticProperty(const char* name, napi_callback g, napi_callback s) { + if(g != nullptr && s != nullptr) + _properties.push_back({name, nullptr, nullptr, g, s, 0, napi_static, 0}); +} + +bool Class::defineStaticProperty(const char *name, const Value &v, PropertyAttribute attribute /* = PropertyAttribute::NONE */) { + // TODO(qgh): Assigning get and set to nullptr in openharmony will cause a crash + //napi_value value; + //internal::seToJsValue(v, &value); + //_properties.push_back({name, nullptr, nullptr, nullptr, nullptr, value, napi_static, 0}); + return true; +} + +void Class::defineFunction(const char* name, napi_callback func) { + // When Napi defines a function, it needs to add the enum attribute, otherwise JS cannot traverse the function + _properties.push_back({name, nullptr, func, nullptr, nullptr, nullptr, napi_default_jsproperty, nullptr}); +} + +void Class::defineStaticFunction(const char* name, napi_callback func) { + _properties.push_back({name, nullptr, func, nullptr, nullptr, 0, napi_static, 0}); +} + +void Class::defineFinalizeFunction(napi_finalize func) { + assert(func != nullptr); + _finalizeFunc = func; +} + +napi_finalize Class::_getFinalizeFunction() const { + return _finalizeFunc; +} + +void Class::install() { + napi_value cons; + napi_status status; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_define_class(ScriptEngine::getEnv(), _name.c_str(), -1, _ctorFunc, nullptr, _properties.size(), _properties.data(), &cons)); + if (_parentProto) { + inherit(ScriptEngine::getEnv(), cons, _parentProto->_getJSObject()); + } + NODE_API_CALL(status, ScriptEngine::getEnv(), + napi_create_reference(ScriptEngine::getEnv(), cons, 1, &_constructor)); + + NODE_API_CALL(status, ScriptEngine::getEnv(), + napi_set_named_property(ScriptEngine::getEnv(), _parent->_getJSObject(), _name.c_str(), cons)); + + napi_value proto; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_named_property(ScriptEngine::getEnv(), cons, "prototype", &proto)); + + if (status == napi_ok) { + _proto = Object::_createJSObject(ScriptEngine::getEnv(), proto, nullptr); + _proto->root(); + } +} + +napi_status Class::inherit(napi_env env, napi_value subclass, napi_value superProto) { + napi_value global, objectClass, setProto; + napi_value argv[2]; + napi_value callbackResult = nullptr; + + napi_get_global(env, &global); + napi_status status = napi_get_named_property(env, global, "Object", &objectClass); + if (status != napi_ok) { + LOGE("ace zbclog napi_get_named_property Object %{public}d", status); + return napi_ok; + } + status = napi_get_named_property(env, objectClass, "setPrototypeOf", &setProto); + if (status != napi_ok) { + LOGE("ace zbclog napi_get_named_property setPrototypeOf %{public}d", status); + return napi_ok; + } + + status = napi_get_named_property(env, subclass, "prototype", &argv[0]); + if (status != napi_ok) { + LOGE("ace zbclog napi_get_named_property prototype arg0 %{public}d", status); + return napi_ok; + } + argv[1] = superProto; + status = napi_call_function(env, objectClass, setProto, 2, argv, &callbackResult); + if (status != napi_ok) { + LOGE("ace zbclog napi_call_function setProto 1 %{public}d", status); + return napi_ok; + } + + return napi_ok; +} + +napi_value Class::_createJSObjectWithClass(Class *cls) { + napi_value obj = nullptr; + napi_status status; + assert(cls); + napi_value clsCtor = cls->_getCtorFunc(); + if (!clsCtor) { + LOGE("get ctor func err"); + return nullptr; + } + se::ScriptEngine::getInstance()->_setNeedCallConstructor(false); + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_new_instance( ScriptEngine::getEnv(), clsCtor, 0, nullptr, &obj)); + se::ScriptEngine::getInstance()->_setNeedCallConstructor(true); + return obj; +} + +Object *Class::getProto() const { + //not impl + return _proto; +} + +napi_ref Class::_getCtorRef() const { + return _constructor; +} + +napi_value Class::_getCtorFunc() const { + assert(_constructor); + napi_value result = nullptr; + napi_status status; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_reference_value(ScriptEngine::getEnv(), _constructor, &result)); + return result; +} + +void Class::_setCtor(Object *obj) { + assert(!_ctor.has_value()); + _ctor = obj; + if (obj != nullptr) { + obj->root(); + obj->incRef(); + } +} + +void Class::destroy() { + SAFE_DEC_REF(_parent); + SAFE_DEC_REF(_proto); + SAFE_DEC_REF(_parentProto); + if (_ctor.has_value()) { + if (_ctor.value() != nullptr) { + _ctor.value()->unroot(); + _ctor.value()->decRef(); + } + _ctor.reset(); + } +} + +void Class::cleanup() { + for (auto cls : __allClasses) { + cls->destroy(); + } + + se::ScriptEngine::getInstance()->addAfterCleanupHook([]() { + for (auto cls : __allClasses) { + delete cls; + } + __allClasses.clear(); + }); +} +}; // namespace se \ No newline at end of file diff --git a/cocos/bindings/jswrapper/napi/Class.h b/cocos/bindings/jswrapper/napi/Class.h new file mode 100644 index 0000000..d038e02 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/Class.h @@ -0,0 +1,83 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include +#include "CommonHeader.h" +#include "Object.h" +#include "../Define.h" +#include "base/std/optional.h" + +namespace se { +class Class { +public: + static Class *create(const std::string &clsName, se::Object *parent, Object *parentProto, napi_callback ctor = nullptr); + static Class *create(const std::initializer_list &classPath, se::Object *parent, Object *parentProto, napi_callback ctor = nullptr); + + void defineFunction(const char* name, napi_callback func); + void defineProperty(const char* name, napi_callback g, napi_callback s); + void defineProperty(const std::initializer_list &names, napi_callback g, napi_callback s); + + void defineStaticFunction(const char* name, napi_callback func); + void defineStaticProperty(const char* name, napi_callback g, napi_callback s); + bool defineStaticProperty(const char *name, const Value &value, PropertyAttribute attribute = PropertyAttribute::NONE); + + static napi_value _createJSObjectWithClass(Class *cls); + + void defineFinalizeFunction(napi_finalize func); + napi_finalize _getFinalizeFunction() const; + + + Object * getProto() const; + void install(); + napi_status inherit(napi_env env, napi_value subclass, napi_value superclass); + napi_ref _getCtorRef() const; + napi_value _getCtorFunc() const; + const char * getName() const { return _name.c_str(); } + static void setExports(napi_value *expPtr) { _exports = expPtr; } + static void cleanup(); + // Private API used in wrapper + void _setCtor(Object *obj); // NOLINT(readability-identifier-naming) + inline const ccstd::optional &_getCtor() const { return _ctor; } // NOLINT(readability-identifier-naming) +private: + Class(); + ~Class(); + bool init(const std::string &clsName, Object *parent, Object *parentProto, napi_callback ctor = nullptr); + void destroy(); + static napi_value _defaultCtor(napi_env env, napi_callback_info info); + +private: + ccstd::optional _ctor; + static napi_value * _exports; + std::string _name; + Object * _parent = nullptr; + Object * _proto = nullptr; + Object * _parentProto = nullptr; + napi_callback _ctorFunc = Class::_defaultCtor; + napi_ref _constructor = nullptr; + std::vector _properties; + napi_finalize _finalizeFunc = nullptr; +}; +}; // namespace se \ No newline at end of file diff --git a/cocos/bindings/jswrapper/napi/CommonHeader.h b/cocos/bindings/jswrapper/napi/CommonHeader.h new file mode 100644 index 0000000..60fb084 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/CommonHeader.h @@ -0,0 +1,74 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include +#include "native_common.h" + +// Empty value so that macros here are able to return NULL or void +#define NODE_API_RETVAL_NOTHING // Intentionally blank #define + +// Returns NULL on failed assertion. +// This is meant to be used inside napi_callback methods. +#define NODE_API_ASSERT(env, assertion, message) \ + NODE_API_ASSERT_BASE(env, assertion, message, NULL) + +// Returns empty on failed assertion. +// This is meant to be used inside functions with void return type. +#define NODE_API_ASSERT_RETURN_VOID(env, assertion, message) \ + NODE_API_ASSERT_BASE(env, assertion, message, NODE_API_RETVAL_NOTHING) + +#define NODE_API_CALL_BASE(env, the_call, ret_val) \ + do { \ + if ((the_call) != napi_ok) { \ + assert(false); \ + } \ + } while (0) + +// Returns nullptr if the_call doesn't return napi_ok. +#define NODE_API_CALL(status, env, the_call) \ + status = the_call; \ + if (status != napi_ok) \ + LOGI("error:%d", status); \ + NODE_API_CALL_BASE(env, status, nullptr) + +// Returns empty if the_call doesn't return napi_ok. +#define NODE_API_CALL_RETURN_VOID(env, the_call) \ + NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) + +#define DECLARE_NODE_API_PROPERTY(name, func) \ + { (name), nullptr, (func), nullptr, nullptr, nullptr, napi_default, nullptr } + +#define DECLARE_NODE_API_GETTER(name, func) \ + { (name), nullptr, nullptr, (func), nullptr, nullptr, napi_default, nullptr } + +void add_returned_status(napi_env env, + const char* key, + napi_value object, + char* expected_message, + napi_status expected_status, + napi_status actual_status); + +void add_last_status(napi_env env, const char* key, napi_value return_value); diff --git a/cocos/bindings/jswrapper/napi/HelperMacros.cpp b/cocos/bindings/jswrapper/napi/HelperMacros.cpp new file mode 100644 index 0000000..a6c42a2 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/HelperMacros.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "HelperMacros.h" +#include "../State.h" +#include "../ValueArrayPool.h" +#include "Class.h" +#include "Object.h" +#include "ScriptEngine.h" +#include "Utils.h" + +SE_HOT napi_value jsbFunctionWrapper(napi_env env, napi_callback_info info, se_function_ptr func, const char *funcName) { + napi_status status; + bool ret = false; + napi_value _this; + se::ValueArray seArgs; + seArgs.reserve(15); + size_t argc = 15; + napi_value args[15]; + NODE_API_CALL(status, env, napi_get_cb_info(env, info, &argc, args, &_this, NULL)); + se::Object *nativeThisObject = nullptr; + status = napi_unwrap(env, _this, reinterpret_cast(&nativeThisObject)); + se::internal::jsToSeArgs(argc, args, &seArgs); + se::State state(nativeThisObject, seArgs); + ret = func(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", funcName, __FILE__, __LINE__); + return nullptr; + } + napi_value retVal; + if (se::internal::setReturnValue(state.rval(), retVal)) + return retVal; + return nullptr; +} + +SE_HOT void jsbFinalizeWrapper(void *thisObject, se_function_ptr func, const char *funcName) { + se::State state(reinterpret_cast(thisObject)); + bool ret = func(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", funcName, __FILE__, __LINE__); + } +} + +SE_HOT napi_value jsbConstructorWrapper(napi_env env, napi_callback_info info, se_function_ptr func, se_finalize_ptr finalizeCb, se::Class *cls, const char *funcName) { + napi_status status; + bool ret = false; + napi_value _this; + se::ValueArray seArgs; + seArgs.reserve(10); + size_t argc = 10; + napi_value args[10]; + NODE_API_CALL(status, env, napi_get_cb_info(env, info, &argc, args, &_this, NULL)); + if (!se::ScriptEngine::getInstance()->_needCallConstructor()) { + return _this; + } + se::internal::jsToSeArgs(argc, args, &seArgs); + se::Object *thisObject = se::Object::_createJSObject(env, _this, cls); + thisObject->_setFinalizeCallback(finalizeCb); + se::State state(thisObject, seArgs); + ret = func(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", funcName, __FILE__, __LINE__); + } + se::Value property; + bool foundCtor = false; + if (!cls->_getCtor().has_value()) { + foundCtor = thisObject->getProperty("_ctor", &property, true); + if (foundCtor) { + cls->_setCtor(property.toObject()); + } else { + cls->_setCtor(nullptr); + } + } else { + auto *ctorObj = cls->_getCtor().value(); + if (ctorObj != nullptr) { + property.setObject(ctorObj); + foundCtor = true; + } + } + + if (foundCtor) { + property.toObject()->call(seArgs, thisObject); + } + return _this; +} + +SE_HOT napi_value jsbGetterWrapper(napi_env env, napi_callback_info info, se_function_ptr func, const char *funcName) { + napi_value _this; + napi_status status; + NODE_API_CALL(status, env, + napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr)); + se::Object *obj; + napi_unwrap(env, _this, reinterpret_cast(&obj)); + se::State state(obj); + bool ret = func(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", funcName, __FILE__, __LINE__); + return nullptr; + } + napi_value retVal; + se::internal::setReturnValue(state.rval(), retVal); + return retVal; +} + +SE_HOT napi_value jsbSetterWrapper(napi_env env, napi_callback_info info, se_function_ptr func, const char *funcName) { + napi_status status; + size_t argc = 1; + napi_value args[1]; + napi_value _this; + se::Value data; + NODE_API_CALL(status, env, napi_get_cb_info(env, info, &argc, args, &_this, nullptr)); + se::internal::jsToSeValue(args[0], &data); + se::ValueArray args2; + args2.reserve(10); + args2.push_back(std::move(data)); + se::Object *nativeThisObject; + napi_unwrap(env, _this, reinterpret_cast(&nativeThisObject)); + se::State state(nativeThisObject, args2); + bool ret = func(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", funcName, __FILE__, __LINE__); + } + return nullptr; +} diff --git a/cocos/bindings/jswrapper/napi/HelperMacros.h b/cocos/bindings/jswrapper/napi/HelperMacros.h new file mode 100644 index 0000000..87a2eeb --- /dev/null +++ b/cocos/bindings/jswrapper/napi/HelperMacros.h @@ -0,0 +1,153 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "CommonHeader.h" +#if !defined(_WIN) + #include + + #ifndef LOGI + #define LOGI(...) ((void) OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "HMG_LOG", __VA_ARGS__)) + #define LOGW(...) ((void) OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, "HMG_LOG", __VA_ARGS__)) + #define LOGE(...) ((void) OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "HMG_LOG", __VA_ARGS__)) + #define LOGD(...) ((void) OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN, "HMG_LOG", __VA_ARGS__)) + #endif +#else + #define LOGI + #define LOGW + #define LOGE +#endif +namespace se { +class Class; +class Object; +class State; +} // namespace se +using se_function_ptr = bool (*)(se::State &state); +using se_finalize_ptr = void (*)(napi_env env, void *nativeObject, void *hint); + +napi_value jsbFunctionWrapper(napi_env, napi_callback_info, + se_function_ptr, + const char *); +void jsbFinalizeWrapper(void *thisObject, + se_function_ptr, + const char *); +napi_value jsbConstructorWrapper(napi_env, napi_callback_info, + se_function_ptr, + se_finalize_ptr finalizeCb, + se::Class *, + const char *); +napi_value jsbGetterWrapper(napi_env, napi_callback_info, + se_function_ptr, + const char *); +napi_value jsbSetterWrapper(napi_env, napi_callback_info, + se_function_ptr, + const char *); +#ifdef __GNUC__ + #define SE_UNUSED __attribute__((unused)) + #define SE_HOT __attribute__((hot)) +#else + #define SE_UNUSED + #define SE_HOT +#endif + +template +constexpr inline T *SE_THIS_OBJECT(STATE &s) { // NOLINT(readability-identifier-naming) + return reinterpret_cast(s.nativeThisObject()); +} + +#define SAFE_INC_REF(obj) \ + if (obj != nullptr) obj->incRef() +#define SAFE_DEC_REF(obj) \ + if ((obj) != nullptr) { \ + (obj)->decRef(); \ + (obj) = nullptr; \ + } +#define _SE(name) name##Registry // NOLINT(readability-identifier-naming, bugprone-reserved-identifier) + +#define SE_QUOTEME_(x) #x // NOLINT(readability-identifier-naming) +#define SE_QUOTEME(x) SE_QUOTEME_(x) +#define SE_REPORT_ERROR(fmt, ...) SE_LOGE("[ERROR] (" __FILE__ ", " SE_QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__) + +#define SE_BIND_PROP_GET_IMPL(funcName, postFix) \ + napi_value funcName##postFix##Registry(napi_env env, napi_callback_info info) { \ + return jsbGetterWrapper(env, info, funcName, #funcName); \ + } + +#define SE_BIND_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, ) +#define SE_BIND_FUNC_AS_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, _asGetter) + +#define SE_BIND_PROP_SET_IMPL(funcName, postFix) \ + napi_value funcName##postFix##Registry(napi_env env, napi_callback_info info) { \ + return jsbSetterWrapper(env, info, funcName, #funcName); \ + } + +#define SE_BIND_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, ) +#define SE_BIND_FUNC_AS_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, _asSetter) + +#define SE_DECLARE_FUNC(funcName) \ + napi_value funcName##Registry(napi_env env, napi_callback_info info) + +#define SE_BIND_FUNC(funcName) \ + napi_value funcName##Registry( \ + napi_env env, napi_callback_info info) { \ + return jsbFunctionWrapper(env, info, funcName, #funcName); \ + } + +#define SE_BIND_FUNC_FAST(funcName) \ + napi_value funcName##Registry(napi_env env, napi_callback_info info) { \ + napi_status status; \ + napi_value _this; \ + size_t argc = 10; \ + napi_value args[10]; \ + se::Object *nativeThisObject = nullptr; \ + NODE_API_CALL(status, env, napi_get_cb_info(env, info, &argc, args, &_this, NULL)); \ + status = napi_unwrap(env, _this, reinterpret_cast(&nativeThisObject)); \ + auto *nativeObject = nativeThisObject != nullptr ? nativeThisObject->getPrivateData() : nullptr; \ + funcName(nativeObject); \ + return nullptr; \ + } + +#define SE_BIND_CTOR(funcName, cls, finalizeCb) \ + napi_value funcName##Registry( \ + napi_env env, napi_callback_info info) { \ + return jsbConstructorWrapper(env, info, funcName, _SE(finalizeCb), cls, #funcName); \ + } + +#define SE_BIND_SUB_CLS_CTOR SE_BIND_CTOR + +#define SE_DECLARE_FINALIZE_FUNC(funcName) \ + void funcName##Registry( \ + napi_env env, void *nativeObject, void * /*finalize_hint*/); + +#define SE_BIND_FINALIZE_FUNC(funcName) \ + void funcName##Registry( \ + napi_env env, void *nativeObject, void *hint /*finalize_hint*/) { \ + if (nativeObject == nullptr) { \ + return; \ + } \ + jsbFinalizeWrapper(nativeObject, funcName, #funcName); \ + } + diff --git a/cocos/bindings/jswrapper/napi/Object.cpp b/cocos/bindings/jswrapper/napi/Object.cpp new file mode 100644 index 0000000..6881ae0 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/Object.cpp @@ -0,0 +1,744 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Object.h" +#include +#include +#include "../MappingUtils.h" +#include "Class.h" +#include "ScriptEngine.h" +#include "Utils.h" + +#define MAX_STRING_LEN 512 +namespace se { +std::unique_ptr> __objectMap; // Currently, the value `void*` is always nullptr + +Object::Object() {} +Object::~Object() { + if (__objectMap) { + __objectMap->erase(this); + } +} + +Object* Object::createObjectWithClass(Class* cls) { + napi_value jsobj = Class::_createJSObjectWithClass(cls); + Object* obj = Object::_createJSObject(ScriptEngine::getEnv(), jsobj, cls); + return obj; +} + +bool Object::setProperty(const char* name, const Value& data) { + napi_status status; + napi_value jsVal; + internal::seToJsValue(data, &jsVal); + NODE_API_CALL(status, _env, napi_set_named_property(_env, _objRef.getValue(_env), name, jsVal)); + return status == napi_ok; +} + +bool Object::getProperty(const char* name, Value* d) { + napi_status status; + napi_value jsVal; + Value data; + NODE_API_CALL(status, _env, napi_get_named_property(_env, _objRef.getValue(_env), name, &jsVal)); + if (status == napi_ok) { + internal::jsToSeValue(jsVal, &data); + *d = data; + if (data.isUndefined()) { + return false; + } + return true; + } + return false; +} + +bool Object::isArray() const { + napi_status status; + bool ret = false; + NODE_API_CALL(status, _env, napi_is_array(_env, _objRef.getValue(_env), &ret)); + return ret; +} + +bool Object::getArrayLength(uint32_t* length) const { + napi_status status; + uint32_t len = 0; + NODE_API_CALL(status, _env, napi_get_array_length(_env, _objRef.getValue(_env), &len)); + if (length) { + *length = len; + } + return true; +} + +bool Object::getArrayElement(uint32_t index, Value* data) const { + napi_status status; + napi_value val; + NODE_API_CALL(status, _env, napi_get_element(_env, _objRef.getValue(_env), index, &val)); + internal::jsToSeValue(val, data); + return true; +} + +bool Object::setArrayElement(uint32_t index, const Value& data) { + napi_status status; + napi_value val; + internal::seToJsValue(data, &val); + NODE_API_CALL(status, _env, napi_set_element(_env, _objRef.getValue(_env), index, val)); + return true; +} + +bool Object::isTypedArray() const { + napi_status status; + bool ret = false; + NODE_API_CALL(status, _env, napi_is_typedarray(_env, _objRef.getValue(_env), &ret)); + return ret; +} + +bool Object::isProxy() const { + //return const_cast(this)->_obj.handle(__isolate)->IsProxy(); + // todo: + return false; +} + +Object *Object::createProxyTarget(se::Object *proxy) { + // SE_ASSERT(proxy->isProxy(), "parameter is not a Proxy object"); + // v8::Local jsobj = proxy->getProxyTarget().As(); + // Object *obj = Object::_createJSObject(nullptr, jsobj); + // return obj; + return nullptr; +} + +Object::TypedArrayType Object::getTypedArrayType() const { + napi_status status; + napi_typedarray_type type; + napi_value inputBuffer; + size_t byteOffset; + size_t length; + NODE_API_CALL(status, _env, napi_get_typedarray_info(_env, _objRef.getValue(_env), &type, &length, NULL, &inputBuffer, &byteOffset)); + + TypedArrayType ret = TypedArrayType::NONE; + switch (type) { + case napi_int8_array: + ret = TypedArrayType::INT8; + break; + case napi_uint8_array: + ret = TypedArrayType::UINT8; + break; + case napi_uint8_clamped_array: + ret = TypedArrayType::UINT8_CLAMPED; + break; + case napi_int16_array: + ret = TypedArrayType::INT16; + break; + case napi_uint16_array: + ret = TypedArrayType::UINT16; + break; + case napi_int32_array: + ret = TypedArrayType::INT32; + break; + case napi_uint32_array: + ret = TypedArrayType::UINT32; + break; + case napi_float32_array: + ret = TypedArrayType::FLOAT32; + break; + case napi_float64_array: + ret = TypedArrayType::FLOAT64; + break; + default: + break; + } + return ret; +} + +bool Object::getTypedArrayData(uint8_t** ptr, size_t* length) const { + napi_status status; + napi_typedarray_type type; + napi_value inputBuffer; + size_t byteOffset; + size_t byteLength; + void* data = nullptr; + NODE_API_CALL(status, _env, napi_get_typedarray_info(_env, _objRef.getValue(_env), &type, &byteLength, &data, &inputBuffer, &byteOffset)); + *ptr = (uint8_t*)(data); + if (length) { + *length = byteLength; + } + return true; +} + +bool Object::isArrayBuffer() const { + bool ret = false; + napi_status status; + NODE_API_CALL(status, _env, napi_is_arraybuffer(_env, _objRef.getValue(_env), &ret)); + return ret; +} + +bool Object::getArrayBufferData(uint8_t** ptr, size_t* length) const { + napi_status status; + size_t len = 0; + NODE_API_CALL(status, _env, napi_get_arraybuffer_info(_env, _objRef.getValue(_env), reinterpret_cast(ptr), &len)); + if (length) { + *length = len; + } + return true; +} + +Object* Object::createTypedArray(Object::TypedArrayType type, const void* data, size_t byteLength) { + napi_status status; + if (type == TypedArrayType::NONE) { + SE_LOGE("Don't pass se::Object::TypedArrayType::NONE to createTypedArray API!"); + return nullptr; + } + + if (type == TypedArrayType::UINT8_CLAMPED) { + SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!"); + return nullptr; + } + napi_typedarray_type napiType; + napi_value outputBuffer; + void* outputPtr = nullptr; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_arraybuffer(ScriptEngine::getEnv(), byteLength, &outputPtr, &outputBuffer)); + if (outputPtr && data && byteLength > 0) { + memcpy(outputPtr, data, byteLength); + } + size_t sizeOfEle = 0; + switch (type) { + case TypedArrayType::INT8: + napiType = napi_int8_array; + sizeOfEle = 1; + break; + case TypedArrayType::UINT8: + napiType = napi_uint8_array; + sizeOfEle = 1; + break; + case TypedArrayType::INT16: + napiType = napi_int16_array; + sizeOfEle = 2; + break; + case TypedArrayType::UINT16: + napiType = napi_uint16_array; + sizeOfEle = 2; + break; + case TypedArrayType::INT32: + napiType = napi_int32_array; + sizeOfEle = 4; + break; + case TypedArrayType::UINT32: + napiType = napi_uint32_array; + sizeOfEle = 4; + break; + case TypedArrayType::FLOAT32: + napiType = napi_float32_array; + sizeOfEle = 4; + break; + case TypedArrayType::FLOAT64: + napiType = napi_float64_array; + sizeOfEle = 8; + break; + default: + assert(false); // Should never go here. + break; + } + size_t eleCounts = byteLength / sizeOfEle; + napi_value outputArray; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_typedarray(ScriptEngine::getEnv(), napiType, eleCounts, outputBuffer, 0, &outputArray)); + + Object* obj = Object::_createJSObject(ScriptEngine::getEnv(), outputArray, nullptr); + return obj; +} + +Object* Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj) { + return Object::createTypedArrayWithBuffer(type, obj, 0); +} + +Object* Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset) { + size_t byteLength{0}; + uint8_t *skip{nullptr}; + + if (obj->getArrayBufferData(&skip, &byteLength)) { + return Object::createTypedArrayWithBuffer(type, obj, offset, byteLength - offset); + } + + CC_ASSERT(false); + return nullptr; +} + +Object* Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength) { + if (type == TypedArrayType::NONE) { + SE_LOGE("Don't pass se::Object::TypedArrayType::NONE to createTypedArray API!"); + return nullptr; + } + + if (type == TypedArrayType::UINT8_CLAMPED) { + SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!"); + return nullptr; + } + + CC_ASSERT(obj->isArrayBuffer()); + napi_status status; + napi_value outputBuffer = obj->_getJSObject(); + napi_typedarray_type napiType; + + size_t sizeOfEle = 0; + switch (type) { + case TypedArrayType::INT8: + napiType = napi_int8_array; + sizeOfEle = 1; + break; + case TypedArrayType::UINT8: + napiType = napi_uint8_array; + sizeOfEle = 1; + break; + case TypedArrayType::INT16: + napiType = napi_int16_array; + sizeOfEle = 2; + break; + case TypedArrayType::UINT16: + napiType = napi_uint16_array; + sizeOfEle = 2; + break; + case TypedArrayType::INT32: + napiType = napi_int32_array; + sizeOfEle = 4; + break; + case TypedArrayType::UINT32: + napiType = napi_uint32_array; + sizeOfEle = 4; + break; + case TypedArrayType::FLOAT32: + napiType = napi_float32_array; + sizeOfEle = 4; + break; + case TypedArrayType::FLOAT64: + napiType = napi_float64_array; + sizeOfEle = 8; + break; + default: + assert(false); // Should never go here. + break; + } + size_t eleCounts = byteLength / sizeOfEle; + napi_value outputArray; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_typedarray(ScriptEngine::getEnv(), napiType, eleCounts, outputBuffer, offset, &outputArray)); + + return Object::_createJSObject(ScriptEngine::getEnv(), outputArray, nullptr); +} + +Object* Object::createExternalArrayBufferObject(void *contents, size_t byteLength, BufferContentsFreeFunc freeFunc, void *freeUserData) { + napi_status status; + napi_value result; + if (freeFunc) { + struct ExternalArrayBufferCallbackParams* param = new (struct ExternalArrayBufferCallbackParams); + param->func = freeFunc; + param->contents = contents; + param->byteLength = byteLength; + param->userData = freeUserData; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_external_arraybuffer( + ScriptEngine::getEnv(), contents, byteLength, [](napi_env env, void* finalize_data, void* finalize_hint) { + if (finalize_hint) { + struct ExternalArrayBufferCallbackParams* param = reinterpret_cast(finalize_hint); + param->func(param->contents, param->byteLength, param->userData); + delete param; + } + }, + reinterpret_cast(param), &result)); + } else { + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_external_arraybuffer( + ScriptEngine::getEnv(), contents, byteLength, nullptr, + freeUserData, &result)); + } + + Object* obj = Object::_createJSObject(ScriptEngine::getEnv(), result, nullptr); + return obj; +} + +bool Object::isFunction() const { + napi_valuetype valuetype0; + napi_status status; + NODE_API_CALL(status, _env, napi_typeof(_env, _objRef.getValue(_env), &valuetype0)); + return (valuetype0 == napi_function); +} + +bool Object::defineFunction(const char* funcName, napi_callback func) { + napi_value fn; + napi_status status; + NODE_API_CALL(status, _env, napi_create_function(_env, funcName, NAPI_AUTO_LENGTH, func, NULL, &fn)); + NODE_API_CALL(status, _env, napi_set_named_property(_env, _objRef.getValue(_env), funcName, fn)); + return true; +} + +bool Object::defineProperty(const char* name, napi_callback getter, napi_callback setter) { + napi_status status; + napi_property_descriptor properties[] = {{name, nullptr, nullptr, getter, setter, 0, napi_default, 0}}; + status = napi_define_properties(_env, _objRef.getValue(_env), sizeof(properties) / sizeof(napi_property_descriptor), properties); + if (status == napi_ok) { + return true; + } + return false; +} + +Object* Object::_createJSObject(napi_env env, napi_value js_object, Class* cls) { // NOLINT(readability-identifier-naming) + auto* ret = new Object(); + if (!ret->init(env, js_object, cls)) { + delete ret; + ret = nullptr; + } + return ret; +} + +Object* Object::createPlainObject() { + napi_value result; + napi_status status; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_object(ScriptEngine::getEnv(), &result)); + Object* obj = _createJSObject(ScriptEngine::getEnv(), result, nullptr); + return obj; +} + +Object* Object::createArrayObject(size_t length) { + napi_value result; + napi_status status; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_array_with_length(ScriptEngine::getEnv(), length, &result)); + Object* obj = _createJSObject(ScriptEngine::getEnv(), result, nullptr); + return obj; +} + +Object* Object::createArrayBufferObject(const void* data, size_t byteLength) { + napi_value result; + napi_status status; + void* retData; + Object* obj = nullptr; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_arraybuffer(ScriptEngine::getEnv(), byteLength, &retData, &result)); + if (status == napi_ok) { + if (data) { + memcpy(retData, data, byteLength); + } + obj = _createJSObject(ScriptEngine::getEnv(), result, nullptr); + } + return obj; +} + +bool Object::getAllKeys(std::vector* allKeys) const { + napi_status status; + napi_value names; + + NODE_API_CALL(status, _env, napi_get_property_names(_env, _objRef.getValue(_env), &names)); + if (status != napi_ok) { + return false; + } + uint32_t name_len = 0; + NODE_API_CALL(status, _env, napi_get_array_length(_env, names, &name_len)); + for (uint32_t i = 0; i < name_len; i++) { + napi_value val; + NODE_API_CALL(status, _env, napi_get_element(_env, names, i, &val)); + if (status == napi_ok) { + char buffer[MAX_STRING_LEN]; + size_t result = 0; + NODE_API_CALL(status, _env, napi_get_value_string_utf8(_env, val, buffer, MAX_STRING_LEN, &result)); + if (result > 0) { + allKeys->push_back(buffer); + } + } + } + + return true; +} + +bool Object::init(napi_env env, napi_value js_object, Class* cls) { + assert(env); + _cls = cls; + _env = env; + _objRef.initWeakref(env, js_object); + + if (__objectMap) { + assert(__objectMap->find(this) == __objectMap->end()); + __objectMap->emplace(this, nullptr); + } + + napi_status status; + return true; +} + +bool Object::call(const ValueArray& args, Object* thisObject, Value* rval) { + size_t argc = 0; + std::vector argv; + argv.reserve(10); + argc = args.size(); + internal::seToJsArgs(_env, args, &argv); + napi_value return_val; + napi_status status; + assert(isFunction()); + napi_value thisObj = thisObject ? thisObject->_getJSObject() : nullptr; + status = + napi_call_function(_env, thisObj, _getJSObject(), argc, argv.data(), &return_val); + if (rval) { + internal::jsToSeValue(return_val, rval); + } + return true; +} + +void Object::_setFinalizeCallback(napi_finalize finalizeCb) { + assert(finalizeCb != nullptr); + _finalizeCb = finalizeCb; +} + +PrivateObjectBase* Object::getPrivateObject() const { + return _privateObject; +} + +void Object::setPrivateObject(PrivateObjectBase* data) { + assert(_privateData == nullptr); + #if CC_DEBUG + if (data != nullptr) { + NativePtrToObjectMap::filter(data->getRaw(), _getClass()) + .forEach([&](se::Object *seObj) { + auto *pri = seObj->getPrivateObject(); + SE_LOGE("Already exists object %s/[%s], trying to add %s/[%s]\n", pri->getName(), typeid(*pri).name(), data->getName(), typeid(*data).name()); + #if JSB_TRACK_OBJECT_CREATION + SE_LOGE(" previous object created at %s\n", it->second->_objectCreationStackFrame.c_str()); + #endif + CC_ABORT(); + }); + } + #endif + napi_status status; + if (data) { + _privateData = data->getRaw(); + _privateObject = data; + NativePtrToObjectMap::emplace(_privateData, this); + } + + //issue https://github.com/nodejs/node/issues/23999 + auto tmpThis = _objRef.getValue(_env); + //_objRef.deleteRef(); + napi_ref result = nullptr; + NODE_API_CALL(status, _env, + napi_wrap(_env, tmpThis, this, weakCallback, + (void*)this /* finalize_hint */, &result)); + //_objRef.setWeakref(_env, result); + setProperty("__native_ptr__", se::Value(static_cast(reinterpret_cast(data)))); + + return; +} + +bool Object::attachObject(Object* obj) { + assert(obj); + + Object* global = ScriptEngine::getInstance()->getGlobalObject(); + Value jsbVal; + if (!global->getProperty("jsb", &jsbVal)) { + return false; + } + Object* jsbObj = jsbVal.toObject(); + + Value func; + + if (!jsbObj->getProperty("registerNativeRef", &func)) { + return false; + } + + ValueArray args; + args.push_back(Value(this)); + args.push_back(Value(obj)); + func.toObject()->call(args, global); + return true; +} + +bool Object::detachObject(Object* obj) { + assert(obj); + + Object* global = ScriptEngine::getInstance()->getGlobalObject(); + Value jsbVal; + if (!global->getProperty("jsb", &jsbVal)) { + return false; + } + Object* jsbObj = jsbVal.toObject(); + + Value func; + + if (!jsbObj->getProperty("unregisterNativeRef", &func)) { + return false; + } + + ValueArray args; + args.push_back(Value(this)); + args.push_back(Value(obj)); + func.toObject()->call(args, global); + return true; +} + +std::string Object::toString() const { + std::string ret; + napi_status status; + if (isFunction() || isArray() || isTypedArray()) { + napi_value result; + NODE_API_CALL(status, _env, napi_coerce_to_string(_env, _objRef.getValue(_env), &result)); + char buffer[MAX_STRING_LEN]; + size_t result_t = 0; + NODE_API_CALL(status, _env, napi_get_value_string_utf8(_env, result, buffer, MAX_STRING_LEN, &result_t)); + ret = buffer; + } else if (isArrayBuffer()) { + ret = "[object ArrayBuffer]"; + } else { + ret = "[object Object]"; + } + return ret; +} + +void Object::root() { + napi_status status; + if (_rootCount == 0) { + uint32_t result = 0; + _objRef.incRef(_env); + //NODE_API_CALL(status, _env, napi_reference_ref(_env, _wrapper, &result)); + } + ++_rootCount; +} + +void Object::unroot() { + napi_status status; + if (_rootCount > 0) { + --_rootCount; + if (_rootCount == 0) { + _objRef.decRef(_env); + } + } +} + +bool Object::isRooted() const { + return _rootCount > 0; +} + +Class* Object::_getClass() const { + return _cls; +} + +Object* Object::getObjectWithPtr(void* ptr) { + Object* obj = nullptr; + auto iter = NativePtrToObjectMap::find(ptr); + if (iter != NativePtrToObjectMap::end()) { + obj = iter->second; + obj->incRef(); + } + return obj; +} + +napi_value Object::_getJSObject() const { + return _objRef.getValue(_env); +} + +void Object::weakCallback(napi_env env, void* nativeObject, void* finalizeHint /*finalize_hint*/) { + if (finalizeHint) { + if (nativeObject == nullptr) { + return; + } + void *rawPtr = reinterpret_cast(nativeObject)->_privateData; + Object* seObj = reinterpret_cast(nativeObject); + if (seObj->_onCleaingPrivateData) { //called by cleanPrivateData, not release seObj; + return; + } + if (seObj->_clearMappingInFinalizer && rawPtr != nullptr) { + auto iter = NativePtrToObjectMap::find(rawPtr); + if (iter != NativePtrToObjectMap::end()) { + NativePtrToObjectMap::erase(iter); + } else { + CC_LOG_INFO("not find ptr in NativePtrToObjectMap"); + } + } + + // TODO: remove test code before releasing. + const char* clsName = seObj->_getClass()->getName(); + CC_LOG_INFO("weakCallback class name:%s, ptr:%p", clsName, rawPtr); + + if (seObj->_finalizeCb != nullptr) { + seObj->_finalizeCb(env, nativeObject, finalizeHint); + } else { + assert(seObj->_getClass() != nullptr); + if (seObj->_getClass()->_getFinalizeFunction() != nullptr) { + seObj->_getClass()->_getFinalizeFunction()(env, nativeObject, finalizeHint); + } + } + seObj->decRef(); + } +} + +void Object::setup() { + __objectMap = std::make_unique>(); +} + +void Object::cleanup() { + void* nativeObj = nullptr; + Object* obj = nullptr; + Class* cls = nullptr; + + const auto& nativePtrToObjectMap = NativePtrToObjectMap::instance(); + for (const auto& e : nativePtrToObjectMap) { + nativeObj = e.first; + obj = e.second; + + if (obj->_finalizeCb != nullptr) { + obj->_finalizeCb(ScriptEngine::getEnv(), nativeObj, nullptr); + } else { + if (obj->_getClass() != nullptr) { + if (obj->_getClass()->_getFinalizeFunction() != nullptr) { + obj->_getClass()->_getFinalizeFunction()(ScriptEngine::getEnv(), nativeObj, nullptr); + } + } + } + obj->decRef(); + } + + NativePtrToObjectMap::clear(); + + if (__objectMap) { + for (const auto& e : *__objectMap) { + obj = e.first; + cls = obj->_getClass(); + obj->_rootCount = 0; + + } + } + + __objectMap.reset(); +} + +Object* Object::createJSONObject(const std::string& jsonStr) { + //not impl + return nullptr; +} + +void Object::clearPrivateData(bool clearMapping) { + if (_privateObject != nullptr) { + napi_status status; + void* result = nullptr; + auto tmpThis = _objRef.getValue(_env); + _onCleaingPrivateData = true; + if (clearMapping) { + NativePtrToObjectMap::erase(_privateData); + } + NODE_API_CALL(status, _env, napi_remove_wrap(_env, tmpThis, &result)); + delete _privateObject; + _privateObject = nullptr; + _privateData = nullptr; + _onCleaingPrivateData = false; + } +} + +} // namespace se diff --git a/cocos/bindings/jswrapper/napi/Object.h b/cocos/bindings/jswrapper/napi/Object.h new file mode 100644 index 0000000..b94c1f6 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/Object.h @@ -0,0 +1,493 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include +#include "../RefCounter.h" +#include "../Value.h" +#include "../config.h" +#include "CommonHeader.h" +#include "HelperMacros.h" +#include "../PrivateObject.h" +#include "base/std/container/unordered_map.h" + +namespace se { +class Class; +class ObjectRef { +private: + napi_ref _ref = nullptr; + int _refCounts = 0; + napi_env _env = nullptr; + napi_value _obj = nullptr; + +public: + ~ObjectRef() { + deleteRef(); + } + napi_value getValue(napi_env env) const { + napi_value result; + napi_status status; + NODE_API_CALL(status, env, napi_get_reference_value(env, _ref, &result)); + assert(status == napi_ok); + assert(result != nullptr); + return result; + } + void initWeakref(napi_env env, napi_value obj) { + assert(_ref == nullptr); + _obj = obj; + _env = env; + napi_create_reference(env, obj, 0, &_ref); + } + void setWeakref(napi_env env, napi_ref ref) { + assert(_ref == nullptr); + _ref = ref; + } + void initStrongRef(napi_env env, napi_value obj) { + assert(_ref == nullptr); + _refCounts = 1; + _obj = obj; + napi_create_reference(env, obj, _refCounts, &_ref); + _env = env; + } + void incRef(napi_env env) { + assert(_refCounts == 0); + if (_refCounts == 0) { + uint32_t result = 0; + _refCounts = 1; + napi_reference_ref(env, _ref, &result); + } + } + void decRef(napi_env env) { + assert(_refCounts == 1); + uint32_t result = 0; + if (_refCounts > 0) { + _refCounts--; + if (_refCounts == 0) { + napi_reference_unref(env, _ref, &result); + } + } + } + void deleteRef() { + _refCounts = 0; + if (!_ref) { + return; + } + napi_delete_reference(_env, _ref); + _ref = nullptr; + } +}; + +class Object; + +class Object : public RefCounter { +public: + enum class TypedArrayType { + NONE, + INT8, + INT16, + INT32, + UINT8, + UINT8_CLAMPED, + UINT16, + UINT32, + FLOAT32, + FLOAT64 + }; + + using BufferContentsFreeFunc = void (*)(void *contents, size_t byteLength, void *userData); + + struct ExternalArrayBufferCallbackParams { + BufferContentsFreeFunc func{nullptr}; + void *contents{nullptr}; + size_t byteLength{0}; + void *userData{0}; + }; + + Object(); + ~Object(); + /** + * @brief Sets a property to an object. + * @param[in] name A utf-8 string containing the property's name. + * @param[in] value A value to be used as the property's value. + * @return true if the property is set successfully, otherwise false. + */ + bool setProperty(const char *name, const Value &data); + + inline bool setProperty(const std::string &name, const Value &value) { + return setProperty(name.c_str(), value); + } + + /** + * @brief Gets a property from an object. + * @param[in] name A utf-8 string containing the property's name. + * @param[out] value The property's value if object has the property, otherwise the undefined value. + * @return true if object has the property, otherwise false. + */ + bool getProperty(const char *name, Value *data); + + inline bool getProperty(const char *name, Value *data, bool cachePropertyName) { + return getProperty(name, data); + } + + inline bool getProperty(const std::string &name, Value *value) { + return getProperty(name.c_str(), value); + } + + void setPrivateObject(PrivateObjectBase *data); + PrivateObjectBase *getPrivateObject() const; + + /** + * @brief Gets an object's private data. + * @return A void* that is the object's private data, if the object has private data, otherwise nullptr. + */ + inline void *getPrivateData() const { + return _privateData; + } + + /** + * @brief Sets a pointer to private data on an object. + * @param[in] data A void* to set as the object's private data. + * @note This method will associate private data with se::Object by ccstd::unordered_map::emplace. + * It's used for search a se::Object via a void* private data. + */ + template + inline void setPrivateData(T *data) { + static_assert(!std::is_void::value, "void * is not allowed for private data"); + setPrivateObject(se::make_shared_private_object(data)); + } + /** + * @brief Use a InstrusivePtr to hold private data on the se::Object. + * + * @tparam T + * @param data A intrusive pointer object + */ + template + inline void setPrivateData(const cc::IntrusivePtr &data) { + setPrivateObject(se::ccintrusive_ptr_private_object(data)); + } + + /** + * @brief Use a std::shared_ptr to hold private data on the se::Object. + * + * @tparam T + * @param data A shared_ptr object + */ + template + inline void setPrivateData(const std::shared_ptr &data) { + setPrivateObject(se::shared_ptr_private_object(data)); + } + /** + * @brief Sets a pointer to private data on an object. + * @param[in] data A void* to set as the object's private data. + * @note This method will associate private data with se::Object by ccstd::unordered_map::emplace. + * It's used for search a se::Object via a void* private data. + */ + template + inline void setRawPrivateData(T *data, bool tryDestroyInGC = false) { + static_assert(!std::is_void::value, "void * is not allowed for private data"); + auto *privateObject = se::rawref_private_object(data); + if (tryDestroyInGC) { + privateObject->tryAllowDestroyInGC(); + } + setPrivateObject(privateObject); + } + + /** + * @brief Get the underlying private data as std::shared_ptr + * + * @tparam T + * @return std::shared_ptr + */ + template + inline std::shared_ptr getPrivateSharedPtr() const { + assert(_privateObject->isSharedPtr()); + return static_cast *>(_privateObject)->getData(); + } + + /** + * @brief Get the underlying private data as InstrusivePtr + * + * @tparam T + * @return cc::IntrusivePtr + */ + template + inline cc::IntrusivePtr getPrivateInstrusivePtr() const { + assert(_privateObject->isCCIntrusivePtr()); + return static_cast *>(_privateObject)->getData(); + } + + + template + inline T *getTypedPrivateData() const { + return reinterpret_cast(getPrivateData()); + } + + /** + * @brief Clears private data of an object. + * @param clearMapping Whether to clear the mapping of native object & se::Object. + */ + void clearPrivateData(bool clearMapping = true); + + + /** + * @brief Sets whether to clear the mapping of native object & se::Object in finalizer + */ + void setClearMappingInFinalizer(bool v) { _clearMappingInFinalizer = v; } + + /** + * @brief Tests whether an object is an array. + * @return true if object is an array, otherwise false. + */ + bool isArray() const; + + /** + * @brief Gets array length of an array object. + * @param[out] length The array length to be stored. It's set to 0 if there is an error. + * @return true if succeed, otherwise false. + */ + bool getArrayLength(uint32_t *length) const; + + /** + * @brief Gets an element from an array object by numeric index. + * @param[in] index An integer value for index. + * @param[out] data The se::Value to be stored for the element in certain index. + * @return true if succeed, otherwise false. + */ + bool getArrayElement(uint32_t index, Value *data) const; + + /** + * @brief Sets an element to an array object by numeric index. + * @param[in] index An integer value for index. + * @param[in] data The se::Value to be set to array with certain index. + * @return true if succeed, otherwise false. + */ + bool setArrayElement(uint32_t index, const Value &data); + + /** @brief Tests whether an object is a typed array. + * @return true if object is a typed array, otherwise false. + */ + bool isTypedArray() const; + + /** @brief Tests whether an object is a proxy object. + * @return true if object is a proxy object, otherwise false. + */ + bool isProxy() const; + + /** + * @brief Gets the type of a typed array object. + * @return The type of a typed array object. + */ + TypedArrayType getTypedArrayType() const; + + /** + * @brief Gets backing store of a typed array object. + * @param[out] ptr A temporary pointer to the backing store of a JavaScript Typed Array object. + * @param[out] length The byte length of a JavaScript Typed Array object. + * @return true if succeed, otherwise false. + */ + bool getTypedArrayData(uint8_t **ptr, size_t *length) const; + /** + * @brief Tests whether an object is an array buffer object. + * @return true if object is an array buffer object, otherwise false. + */ + bool isArrayBuffer() const; + + /** + * @brief Gets buffer data of an array buffer object. + * @param[out] ptr A pointer to the data buffer that serves as the backing store for a JavaScript Typed Array object. + * @param[out] length The number of bytes in a JavaScript data object. + * @return true if succeed, otherwise false. + */ + bool getArrayBufferData(uint8_t **ptr, size_t *length) const; + + /** + * @brief Creates a JavaScript Typed Array Object with specified format from an existing pointer, + if provide a null pointer,then will create a empty JavaScript Typed Array Object. + * @param[in] type The format of typed array. + * @param[in] data A pointer to the byte buffer to be used as the backing store of the Typed Array object. + * @param[in] byteLength The number of bytes pointed to by the parameter bytes. + * @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createTypedArray(TypedArrayType type, const void *data, size_t byteLength); + + static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj); + static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset); + static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength); + + static Object *createExternalArrayBufferObject(void *contents, size_t byteLength, BufferContentsFreeFunc freeFunc, void *freeUserData = nullptr); + /** + * @brief Tests whether an object can be called as a function. + * @return true if object can be called as a function, otherwise false. + */ + bool isFunction() const; + + /** + * @brief Defines a function with a native callback for an object. + * @param[in] funcName A utf-8 string containing the function name. + * @param[in] func The native callback triggered by JavaScript code. + * @return true if succeed, otherwise false. + */ + bool defineFunction(const char *funcName, napi_callback func); + + /** + * @brief Defines a property with native accessor callbacks for an object. + * @param[in] name A utf-8 string containing the property's name. + * @param[in] getter The native callback for getter. + * @param[in] setter The native callback for setter. + * @return true if succeed, otherwise false. + */ + bool defineProperty(const char *name, napi_callback getter, napi_callback setter); + + bool attachObject(Object *obj); + + /** + * @brief Detaches an object from current object. + * @param[in] obj The object to be detached. + * @return true if succeed, otherwise false. + * @note The attached object will not be released if current object is not garbage collected. + */ + bool detachObject(Object *obj); + + /** + * @brief Calls an object as a function. + * @param[in] args A se::Value array of arguments to pass to the function. Pass se::EmptyValueArray if argumentCount is 0. + * @param[in] thisObject The object to use as "this," or NULL to use the global object as "this." + * @param[out] rval The se::Value that results from calling object as a function, passing nullptr if return value is ignored. + * @return true if object is a function and there isn't any errors, otherwise false. + */ + bool call(const ValueArray &args, Object *thisObject, Value *rval = nullptr); + + /** + * @brief Creates a JavaScript Native Binding Object from an existing se::Class instance. + * @param[in] cls The se::Class instance which stores native callback informations. + * @return A JavaScript Native Binding Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createObjectWithClass(Class *cls); + + static Object *_createJSObject(napi_env env, napi_value js_object, Class *cls); + + /** + * @brief Creates a JavaScript Object like `{} or new Object()`. + * @return A JavaScript Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createPlainObject(); + + /** + * @brief Creates a JavaScript Array Object like `[] or new Array()`. + * @param[in] length The initical length of array. + * @return A JavaScript Array Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createArrayObject(size_t length); + + /** + * @brief Creates a JavaScript Array Buffer object from an existing pointer. + * @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object. + * @param[in] byteLength The number of bytes pointed to by the parameter bytes. + * @return A Array Buffer Object whose backing store is the same as the one pointed to data, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createArrayBufferObject(const void *data, size_t byteLength); + /** + * Gets the Proxy Target object + * @param proxy The JavaScript Proxy object. + * @return The target JavaScript object of the parameter. + */ + static Object *createProxyTarget(se::Object *proxy); + /** + * @brief Roots an object from garbage collection. + * @note Use this method when you want to store an object in a global or on the heap, where the garbage collector will not be able to discover your reference to it. + * An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection. + */ + void root(); + + /** + * @brief Unroots an object from garbage collection. + * @note An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection. + */ + void unroot(); + + /** + * @brief Tests whether an object is rooted. + * @return true if it has been already rooted, otherwise false. + */ + bool isRooted() const; + + /** + * @brief Creates a JavaScript Object from a JSON formatted string. + * @param[in] jsonStr The utf-8 string containing the JSON string to be parsed. + * @return A JavaScript Object containing the parsed value, or nullptr if the input is invalid. + * @note The return value (non-null) has to be released manually. + */ + static Object *createJSONObject(const std::string &jsonStr); + + /** + * @brief Gets all property names of an object. + * @param[out] allKeys A string vector to store all property names. + * @return true if succeed, otherwise false. + */ + bool getAllKeys(std::vector *allKeys) const; + + /** + * @brief Gets a se::Object from an existing native object pointer. + * @param[in] ptr The native object pointer associated with the se::Object + * @return A JavaScript Native Binding Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *getObjectWithPtr(void *ptr); + + Class * _getClass() const; // NOLINT(readability-identifier-naming) + napi_value _getJSObject() const; + void _setFinalizeCallback(napi_finalize finalizeCb); // NOLINT(readability-identifier-naming) + /** + * @brief Returns the string for describing current object. + * @return The string for describing current object. + */ + std::string toString() const; + + bool init(napi_env env, napi_value js_object, Class *cls); + +private: + static void weakCallback(napi_env env, void *nativeObject, void * /*finalize_hint*/); + static void setup(); + static void cleanup(); + +private: + ObjectRef _objRef; + napi_finalize _finalizeCb = nullptr; + bool _clearMappingInFinalizer = true; + void *_privateData = nullptr; + PrivateObjectBase *_privateObject = nullptr; + napi_env _env = nullptr; + Class * _cls = nullptr; + uint32_t _rootCount = 0; + bool _onCleaingPrivateData = false; + + friend class ScriptEngine; +}; +}; // namespace se \ No newline at end of file diff --git a/cocos/bindings/jswrapper/napi/ScriptEngine.cpp b/cocos/bindings/jswrapper/napi/ScriptEngine.cpp new file mode 100644 index 0000000..faecdf7 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/ScriptEngine.cpp @@ -0,0 +1,312 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "ScriptEngine.h" +#include +#include "../MappingUtils.h" +#include "Class.h" +#include "Utils.h" +#include "CommonHeader.h" +#include +#include "base/std/container/array.h" + +namespace se { +AutoHandleScope::AutoHandleScope() { + napi_open_handle_scope(ScriptEngine::getEnv(), &_handleScope); +} + +AutoHandleScope::~AutoHandleScope() { + napi_close_handle_scope(ScriptEngine::getEnv(), _handleScope); +} + +ScriptEngine *gSriptEngineInstance = nullptr; + +ScriptEngine::ScriptEngine() {}; + +ScriptEngine::~ScriptEngine() = default; + +void ScriptEngine::setFileOperationDelegate(const FileOperationDelegate &delegate) { + _fileOperationDelegate = delegate; +} + +const ScriptEngine::FileOperationDelegate &ScriptEngine::getFileOperationDelegate() const { + return _fileOperationDelegate; +} + +ScriptEngine *ScriptEngine::getInstance() { + if (gSriptEngineInstance == nullptr) { + gSriptEngineInstance = new ScriptEngine(); + } + + return gSriptEngineInstance; +} + +void ScriptEngine::destroyInstance() { + if (gSriptEngineInstance) { + gSriptEngineInstance->cleanup(); + delete gSriptEngineInstance; + gSriptEngineInstance = nullptr; + } +} + +bool ScriptEngine::runScript(const std::string &path, Value *ret /* = nullptr */) { + assert(!path.empty()); + napi_status status; + napi_value result = nullptr; + LOGI("run script : %{public}s", path.c_str()); + //NODE_API_CALL(status, ScriptEngine::getEnv(), napi_run_script_path(ScriptEngine::getEnv(), path.c_str(), &result)); + if (ret && result) { + internal::jsToSeValue(result, ret); + } + return false; +} + +bool ScriptEngine::evalString(const char *scriptStr, ssize_t length, Value *ret, const char *fileName) { + napi_status status; + napi_value script; + napi_value result; + length = length < 0 ? NAPI_AUTO_LENGTH : length; + status = napi_create_string_utf8(ScriptEngine::getEnv(), scriptStr, length, &script); + LOGI("eval :%{public}s", scriptStr); + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_run_script(ScriptEngine::getEnv(), script, &result)); + return true; +} + +bool ScriptEngine::init() { + napi_status status; + napi_value result; + + for (const auto &hook : _beforeInitHookArray) { + hook(); + } + + Object::setup(); + NativePtrToObjectMap::init(); + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_global(ScriptEngine::getEnv(), &result)); + _globalObj = Object::_createJSObject(ScriptEngine::getEnv(), result, nullptr); + _globalObj->root(); + _globalObj->setProperty("window", Value(_globalObj)); + _globalObj->setProperty("scriptEngineType", se::Value("napi")); + + _isValid = true; + + for (const auto &hook : _afterInitHookArray) { + hook(); + } + _afterInitHookArray.clear(); + + return _isValid; +} + +Object *ScriptEngine::getGlobalObject() const { + return _globalObj; +} + +bool ScriptEngine::start() { + bool ok = true; + if (!init()) { + return false; + } + _startTime = std::chrono::steady_clock::now(); + for (auto cb : _permRegisterCallbackArray) { + ok = cb(_globalObj); + assert(ok); + if (!ok) { + break; + } + } + + for (auto cb : _registerCallbackArray) { + ok = cb(_globalObj); + assert(ok); + if (!ok) { + break; + } + } + + // After ScriptEngine is started, _registerCallbackArray isn't needed. Therefore, clear it here. + _registerCallbackArray.clear(); + + return ok; +} + +void ScriptEngine::cleanup() { + if (!_isValid) { + return; + } + + SE_LOGD("ScriptEngine::cleanup begin ...\n"); + _isInCleanup = true; + + for (const auto &hook : _beforeCleanupHookArray) { + hook(); + } + _beforeCleanupHookArray.clear(); + + SAFE_DEC_REF(_globalObj); + Object::cleanup(); + Class::cleanup(); + garbageCollect(); + + _globalObj = nullptr; + _isValid = false; + + _registerCallbackArray.clear(); + + for (const auto &hook : _afterCleanupHookArray) { + hook(); + } + _afterCleanupHookArray.clear(); + + _isInCleanup = false; + NativePtrToObjectMap::destroy(); + SE_LOGD("ScriptEngine::cleanup end ...\n"); +} + +void ScriptEngine::addBeforeCleanupHook(const std::function &hook) { + _beforeCleanupHookArray.push_back(hook); + return; +} + +void ScriptEngine::addAfterCleanupHook(const std::function &hook) { + _afterCleanupHookArray.push_back(hook); + return; +} + +void ScriptEngine::addRegisterCallback(RegisterCallback cb) { + assert(std::find(_registerCallbackArray.begin(), _registerCallbackArray.end(), cb) == _registerCallbackArray.end()); + _registerCallbackArray.push_back(cb); +} + +napi_env ScriptEngine::getEnv() { + return getInstance()->_env; +} + +void ScriptEngine::setEnv(napi_env env) { + getInstance()->_env = env; +} + +void ScriptEngine::addPermanentRegisterCallback(RegisterCallback cb) { + if (std::find(_permRegisterCallbackArray.begin(), _permRegisterCallbackArray.end(), cb) == _permRegisterCallbackArray.end()) { + _permRegisterCallbackArray.push_back(cb); + } +} + +void ScriptEngine::setExceptionCallback(const ExceptionCallback &cb) { + //not impl + return; +} + +const std::chrono::steady_clock::time_point &ScriptEngine::getStartTime() const { + return _startTime; +} + +bool ScriptEngine::isValid() const { + return _isValid; +} + +void ScriptEngine::enableDebugger(const std::string &serverAddr, uint32_t port, bool isWait) { + //not impl + return; +} + +bool ScriptEngine::saveByteCodeToFile(const std::string &path, const std::string &pathBc) { + //not impl + return true; +} + +void ScriptEngine::clearException() { + //not impl + return; +} + +void ScriptEngine::garbageCollect() { + //not impl + return; +} + +bool ScriptEngine::isGarbageCollecting() const { + return _isGarbageCollecting; +} + +void ScriptEngine::_setGarbageCollecting(bool isGarbageCollecting) { //NOLINT(readability-identifier-naming) + _isGarbageCollecting = isGarbageCollecting; +} + +void ScriptEngine::setJSExceptionCallback(const ExceptionCallback &cb) { + //not impl + return; +} + +void ScriptEngine::addAfterInitHook(const std::function &hook) { + _afterInitHookArray.push_back(hook); + return; +} + +std::string ScriptEngine::getCurrentStackTrace() { + //not impl + return ""; +} + +void ScriptEngine::_setNeedCallConstructor(bool need) { + _isneedCallConstructor = need; +} + +bool ScriptEngine::_needCallConstructor() { + return _isneedCallConstructor; +} + +bool ScriptEngine::callFunction(Object *targetObj, const char *funcName, uint32_t argc, Value *args, Value *rval) { + Value objFunc; + if (!targetObj->getProperty(funcName, &objFunc)) { + return false; + } + + ValueArray argv; + + for (size_t i = 0; i < argc; ++i) { + argv.push_back(args[i]); + } + + objFunc.toObject()->call(argv, targetObj, rval); + + return true; +} + +void ScriptEngine::handlePromiseExceptions() { + //not impl + assert(true); + return; +} + +void ScriptEngine::mainLoopUpdate() { + // empty implementation +} + +void ScriptEngine::throwException(const std::string &errorMessage) { + napi_status status; + NODE_API_CALL_RETURN_VOID(getEnv(), napi_throw_error(getEnv(), nullptr, errorMessage.c_str())); +} +}; // namespace se diff --git a/cocos/bindings/jswrapper/napi/ScriptEngine.h b/cocos/bindings/jswrapper/napi/ScriptEngine.h new file mode 100644 index 0000000..35835ff --- /dev/null +++ b/cocos/bindings/jswrapper/napi/ScriptEngine.h @@ -0,0 +1,305 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "../Value.h" +#include "../config.h" +#include "CommonHeader.h" + +using HandleScope = napi_handle_scope; +namespace se { +class AutoHandleScope { +public: + // This interface needs to be implemented in NAPI, similar to V8. + // Ref:https://nodejs.org/docs/latest-v17.x/api/n-api.html#object-lifetime-management + AutoHandleScope(); + ~AutoHandleScope(); + +private: + HandleScope _handleScope; +}; +using RegisterCallback = bool (*)(Object *); +using ExceptionCallback = std::function; // location, message, stack + +class ScriptEngine { +public: + /** + * Delegate class for file operation + */ + class FileOperationDelegate { + public: + FileOperationDelegate() + : onGetDataFromFile(nullptr), + onGetStringFromFile(nullptr), + onCheckFileExist(nullptr), + onGetFullPath(nullptr) {} + + /** + * @brief Tests whether delegate is valid. + */ + bool isValid() const { + return onGetDataFromFile != nullptr && onGetStringFromFile != nullptr && onCheckFileExist != nullptr && onGetFullPath != nullptr; + } + + // path, buffer, buffer size + std::function &)> onGetDataFromFile; + // path, return file string content. + std::function onGetStringFromFile; + // path + std::function onCheckFileExist; + // path, return full path + std::function onGetFullPath; + }; + ScriptEngine(); + ~ScriptEngine(); + /** + * @brief Sets the delegate for file operation. + * @param delegate[in] The delegate instance for file operation. + */ + void setFileOperationDelegate(const FileOperationDelegate &delegate); + + /** + * @brief Compile script file into v8::ScriptCompiler::CachedData and save to file. + * @param[in] path The path of script file. + * @param[in] pathBc The location where bytecode file should be written to. The path should be ends with ".bc", which indicates a bytecode file. + * @return true if succeed, otherwise false. + */ + bool saveByteCodeToFile(const std::string &path, const std::string &pathBc); + + /** + * @brief Gets the delegate for file operation. + * @return The delegate for file operation + */ + const FileOperationDelegate &getFileOperationDelegate() const; + + static ScriptEngine *getInstance(); + + /** + * @brief Destroys the instance of script engine. + */ + static void destroyInstance(); + + /** + * @brief Clears all exceptions. + */ + void clearException(); + + /** + * @brief Sets the callback function while an exception is fired in JS. + * @param[in] cb The callback function to notify that an exception is fired. + */ + void setJSExceptionCallback(const ExceptionCallback &cb); + + /** + * @brief Sets the callback function while an exception is fired. + * @param[in] cb The callback function to notify that an exception is fired. + */ + void setExceptionCallback(const ExceptionCallback &cb); + + /** + * @brief Grab a snapshot of the current JavaScript execution stack. + * @return current stack trace string + */ + std::string getCurrentStackTrace(); + /** + * @brief Executes a utf-8 string buffer which contains JavaScript code. + * @param[in] script A utf-8 string buffer, if it isn't null-terminated, parameter `length` should be assigned and > 0. + * @param[in] length The length of parameter `scriptStr`, it will be set to string length internally if passing < 0 and parameter `scriptStr` is null-terminated. + * @param[in] ret The se::Value that results from evaluating script. Passing nullptr if you don't care about the result. + * @param[in] fileName A string containing a URL for the script's source file. This is used by debuggers and when reporting exceptions. Pass NULL if you do not care to include source file information. + * @return true if succeed, otherwise false. + */ + bool evalString(const char *script, ssize_t length = -1, Value *ret = nullptr, const char *fileName = nullptr); + + /** + * @brief Adds a callback for registering a native binding module, which will not be removed by ScriptEngine::cleanup. + * @param[in] cb A callback for registering a native binding module. + * @note This method just add a callback to a vector, callbacks is invoked in `start` method. + */ + void addPermanentRegisterCallback(RegisterCallback cb); + + /** + * @brief Starts the script engine. + * @return true if succeed, otherwise false. + * @note This method will invoke all callbacks of native binding modules by the order of registration. + */ + bool start(); + + /** + * @brief Initializes script engine. + * @return true if succeed, otherwise false. + * @note This method will create JavaScript context and global object. + */ + bool init(); + + /** + * @brief Adds a hook function before initializing script engine. + * @param[in] hook A hook function to be invoked before initializing script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addBeforeInitHook(const std::function &hook); + + /** + * @brief Adds a hook function after initializing script engine. + * @param[in] hook A hook function to be invoked before initializing script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addAfterInitHook(const std::function &hook); + + /** + * @brief Cleanups script engine. + * @note This method will removes all objects in JavaScript VM even whose are rooted, then shutdown JavaScript VMf. + */ + void cleanup(); + + /** + * @brief Adds a hook function before cleanuping script engine. + * @param[in] hook A hook function to be invoked before cleanuping script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addBeforeCleanupHook(const std::function &hook); + + /** + * @brief Adds a hook function after cleanuping script engine. + * @param[in] hook A hook function to be invoked after cleanuping script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addAfterCleanupHook(const std::function &hook); + /** + * @brief Gets the global object of JavaScript VM. + * @return The se::Object stores the global JavaScript object. + */ + Object *getGlobalObject() const; + + static napi_env getEnv(); + static void setEnv(napi_env env); + + /** + * @brief Adds a callback for registering a native binding module. + * @param[in] cb A callback for registering a native binding module. + * @note This method just add a callback to a vector, callbacks is invoked in `start` method. + */ + void addRegisterCallback(RegisterCallback cb); + + const std::chrono::steady_clock::time_point &getStartTime() const; + + /** + * @brief Tests whether script engine is doing garbage collection. + * @return true if it's in garbage collection, otherwise false. + */ + bool isGarbageCollecting() const; + + /** + * @brief Performs a JavaScript garbage collection. + */ + void garbageCollect(); + + /** + * @brief Tests whether script engine is being cleaned up. + * @return true if it's in cleaning up, otherwise false. + */ + bool isInCleanup() const { return _isInCleanup; } + /** + * @brief Executes a file which contains JavaScript code. + * @param[in] path Script file path. + * @param[in] ret The se::Value that results from evaluating script. Passing nullptr if you don't care about the result. + * @return true if succeed, otherwise false. + */ + bool runScript(const std::string &path, Value *ret = nullptr); + + /** + * @brief Tests whether script engine is valid. + * @return true if it's valid, otherwise false. + */ + bool isValid() const; + + /** + * @brief Enables JavaScript debugger + * @param[in] serverAddr The address of debugger server. + * @param[in] isWait Whether wait debugger attach when loading. + */ + void enableDebugger(const std::string &serverAddr, uint32_t port, bool isWait = false); + + /** + * @brief Tests whether JavaScript debugger is enabled + * @return true if JavaScript debugger is enabled, otherwise false. + */ + bool isDebuggerEnabled() const; + + /** + * @brief Main loop update trigger, it's need to invoked in main thread every frame. + */ + void mainLoopUpdate(); + + bool runByteCodeFile(const std::string &pathBc, Value *ret /* = nullptr */); + + /** + * @brief Throw JS exception + */ + void throwException(const std::string &errorMessage); + + /** + * @brief for napi_new_instance, skip constructor. + */ + void _setNeedCallConstructor(bool need); + + /** + * @brief for napi_new_instance, skip constructor. + */ + bool _needCallConstructor(); + + /** + * @brief Fast version of call script function, faster than Object::call + */ + bool callFunction(Object *targetObj, const char *funcName, uint32_t argc, Value *args, Value *rval = nullptr); + + void _setGarbageCollecting(bool isGarbageCollecting); // NOLINT(readability-identifier-naming) + + void handlePromiseExceptions(); + +private: + FileOperationDelegate _fileOperationDelegate; + std::vector _registerCallbackArray; + std::vector _permRegisterCallbackArray; + + std::vector> _beforeInitHookArray; + std::vector> _afterInitHookArray; + std::vector> _beforeCleanupHookArray; + std::vector> _afterCleanupHookArray; + + Object * _globalObj = nullptr; + napi_env _env = nullptr; + + bool _isValid{false}; + bool _isGarbageCollecting{false}; + bool _isInCleanup{false}; + bool _isErrorHandleWorking{false}; + bool _isneedCallConstructor{true}; + std::chrono::steady_clock::time_point _startTime; +}; +}; // namespace se diff --git a/cocos/bindings/jswrapper/napi/SeApi.h b/cocos/bindings/jswrapper/napi/SeApi.h new file mode 100644 index 0000000..639abd4 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/SeApi.h @@ -0,0 +1,32 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "Class.h" +#include "HelperMacros.h" +#include "Object.h" +#include "ScriptEngine.h" +#include "Utils.h" \ No newline at end of file diff --git a/cocos/bindings/jswrapper/napi/Utils.cpp b/cocos/bindings/jswrapper/napi/Utils.cpp new file mode 100644 index 0000000..43ac5f4 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/Utils.cpp @@ -0,0 +1,195 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Utils.h" +#include "CommonHeader.h" +#include "ScriptEngine.h" +#include "Class.h" + +#define MAX_STRING_LENS 1024 + +namespace se { +namespace internal { +void jsToSeValue(const target_value& value, Value* v) { + assert(v != nullptr); + napi_status status; + napi_valuetype valType; + int64_t iRet = 0; + double dRet = 0.0; + bool bRet = false; + bool lossless = false; + size_t len = 0; + void* privateObjPtr = nullptr; + void* nativePtr = nullptr; + Object* obj = nullptr; + + if (!value) { + valType = napi_valuetype::napi_undefined; + }else { + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_typeof(ScriptEngine::getEnv(), value, &valType)); + } + + switch (valType) { + case napi_valuetype::napi_undefined: + v->setUndefined(); + break; + case napi_valuetype::napi_null: + v->setNull(); + break; + case napi_valuetype::napi_number: + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_value_double(ScriptEngine::getEnv(), value, &dRet)); + if (status == napi_ok) { + v->setDouble(dRet); + } else { + v->setUndefined(); + } + break; + case napi_valuetype::napi_bigint: + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_value_bigint_int64(ScriptEngine::getEnv(), value, &iRet, &lossless)); + if (lossless) { + v->setInt64(iRet); + } else { + v->setUndefined(); + } + + break; + case napi_valuetype::napi_string: + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_value_string_utf8(ScriptEngine::getEnv(), value, nullptr, 0, &len)); + if (status == napi_ok) { + std::string valueStr; + len += 1; + valueStr.resize(len); + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_value_string_utf8(ScriptEngine::getEnv(), value, const_cast(valueStr.data()), valueStr.size(), &len)); + if (valueStr.length() != len) { + valueStr.resize(len); + } + v->setString(valueStr); + } else { + v->setUndefined(); + } + break; + case napi_valuetype::napi_boolean: + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_value_bool(ScriptEngine::getEnv(), value, &bRet)); + if (status == napi_ok) { + v->setBoolean(bRet); + } else { + v->setUndefined(); + } + break; + case napi_valuetype::napi_object: + case napi_valuetype::napi_function: + status = napi_unwrap(ScriptEngine::getEnv(), value, &privateObjPtr); + if ((status == napi_ok) && privateObjPtr) { + nativePtr = reinterpret_cast(privateObjPtr)->getPrivateData(); + } + if (nativePtr) { + obj = Object::getObjectWithPtr(nativePtr); + } + if (obj == nullptr) { + obj = Object::_createJSObject(ScriptEngine::getEnv(), value, nullptr); + } + if (obj) { + v->setObject(obj, true); + obj->decRef(); + } else { + v->setUndefined(); + } + break; + default: + break; + } +} + +void jsToSeArgs(size_t argc, target_value* argv, ValueArray* outArr) { + assert(outArr != nullptr); + for (int i = 0; i < argc; i++) { + Value v; + jsToSeValue(argv[i], &v); + outArr->push_back(v); + } +} + +bool seToJsValue(const Value& v, target_value* outJsVal) { + assert(outJsVal != nullptr); + bool ret = false; + napi_status status = napi_ok; + switch (v.getType()) { + case Value::Type::Number: + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_double(ScriptEngine::getEnv(), v.toDouble(), outJsVal)); + ret = (status == napi_ok); + break; + case Value::Type::String: { + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_string_utf8(ScriptEngine::getEnv(), v.toString().c_str(), v.toString().length(), outJsVal)); + ret = (status == napi_ok); + } break; + case Value::Type::Boolean: + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_boolean(ScriptEngine::getEnv(), v.toBoolean(), outJsVal)); + ret = (status == napi_ok); + break; + case Value::Type::Object: + *outJsVal = v.toObject()->_getJSObject(); + ret = (outJsVal != nullptr); + break; + case Value::Type::Null: + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_null(ScriptEngine::getEnv(), outJsVal)); + ret = (status == napi_ok); + break; + case Value::Type::Undefined: + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_get_undefined(ScriptEngine::getEnv(), outJsVal)); + ret = (status == napi_ok); + break; + case Value::Type::BigInt: + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_bigint_int64(ScriptEngine::getEnv(), v.toInt64(), outJsVal)); + ret = (status == napi_ok); + break; + default: + assert(false); + break; + } + //LOGI("type :%d", v.getType()); + return ret; +} + +void seToJsArgs(napi_env env, const ValueArray& args, std::vector* outArr) { + assert(outArr != nullptr); + for (const auto& data : args) { + napi_value jsval; + seToJsValue(data, &jsval); + outArr->push_back(jsval); + } +} + +bool setReturnValue(const Value& data, target_value& argv) { + if (data.getType() == Value::Type::BigInt) { + // TODO: fix 'TypeError: Cannot mix BigInt and other types, use explicit conversions' for spine & dragonbones + napi_status status; + NODE_API_CALL(status, ScriptEngine::getEnv(), napi_create_double(ScriptEngine::getEnv(), data.toDouble(), &argv)); + return true; + } + + return seToJsValue(data, &argv); +} +} // namespace internal +}; // namespace se diff --git a/cocos/bindings/jswrapper/napi/Utils.h b/cocos/bindings/jswrapper/napi/Utils.h new file mode 100644 index 0000000..45e7bb6 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/Utils.h @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../config.h" +#include "Object.h" +namespace se { + +namespace internal { + +using target_value = napi_value; +struct PrivateData { + void * data; + Object *seObj; +}; + +bool setReturnValue(const Value &data, target_value &argv); +void jsToSeValue(const target_value &value, Value *v); +void jsToSeArgs(size_t argc, target_value *argv, ValueArray *outArr); +bool seToJsValue(const Value &v, target_value *jsval); +void seToJsArgs(napi_env env, const ValueArray &args, std::vector *outArr); +} // namespace internal +} // namespace se diff --git a/cocos/bindings/jswrapper/napi/native_common.h b/cocos/bindings/jswrapper/napi/native_common.h new file mode 100644 index 0000000..c1633d2 --- /dev/null +++ b/cocos/bindings/jswrapper/napi/native_common.h @@ -0,0 +1,115 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#ifndef FOUNDATION_ACE_NAPI_INTERFACES_KITS_NAPI_NATIVE_COMMON_H +#define FOUNDATION_ACE_NAPI_INTERFACES_KITS_NAPI_NATIVE_COMMON_H + +#define DEPRECATED __attribute__((__deprecated__)) + +#ifndef NAPI_VERSION +#define NAPI_VERSION 8 +#endif + +#define NAPI_RETVAL_NOTHING + +#define GET_AND_THROW_LAST_ERROR(env) \ + do { \ + const napi_extended_error_info* errorInfo = nullptr; \ + napi_get_last_error_info((env), &errorInfo); \ + bool isPending = false; \ + napi_is_exception_pending((env), &isPending); \ + if (!isPending && errorInfo != nullptr) { \ + const char* errorMessage = \ + errorInfo->error_message != nullptr ? errorInfo->error_message : "empty error message"; \ + napi_throw_error((env), nullptr, errorMessage); \ + } \ + } while (0) + +#define NAPI_ASSERT_BASE(env, assertion, message, retVal) \ + do { \ + if (!(assertion)) { \ + napi_throw_error((env), nullptr, "assertion (" #assertion ") failed: " message); \ + return retVal; \ + } \ + } while (0) + +#define NAPI_ASSERT(env, assertion, message) NAPI_ASSERT_BASE(env, assertion, message, nullptr) + +#define NAPI_ASSERT_RETURN_VOID(env, assertion, message) NAPI_ASSERT_BASE(env, assertion, message, NAPI_RETVAL_NOTHING) + +#define NAPI_CALL_BASE(env, theCall, retVal) \ + do { \ + if ((theCall) != napi_ok) { \ + GET_AND_THROW_LAST_ERROR((env)); \ + return retVal; \ + } \ + } while (0) + +#define NAPI_CALL(env, theCall) NAPI_CALL_BASE(env, theCall, nullptr) + +#define NAPI_CALL_RETURN_VOID(env, theCall) NAPI_CALL_BASE(env, theCall, NAPI_RETVAL_NOTHING) + +#define DECLARE_NAPI_PROPERTY(name, val) \ + { \ + (name), nullptr, nullptr, nullptr, nullptr, val, napi_default, nullptr \ + } + +#define DECLARE_NAPI_STATIC_PROPERTY(name, val) \ + { \ + (name), nullptr, nullptr, nullptr, nullptr, val, napi_static, nullptr \ + } + +#define DECLARE_NAPI_FUNCTION(name, func) \ + { \ + (name), nullptr, (func), nullptr, nullptr, nullptr, napi_default, nullptr \ + } + +#define DECLARE_NAPI_FUNCTION_WITH_DATA(name, func, data) \ + { \ + (name), nullptr, (func), nullptr, nullptr, nullptr, napi_default, data \ + } + +#define DECLARE_NAPI_STATIC_FUNCTION(name, func) \ + { \ + (name), nullptr, (func), nullptr, nullptr, nullptr, napi_static, nullptr \ + } + +#define DECLARE_NAPI_GETTER(name, getter) \ + { \ + (name), nullptr, nullptr, (getter), nullptr, nullptr, napi_default, nullptr \ + } + +#define DECLARE_NAPI_SETTER(name, setter) \ + { \ + (name), nullptr, nullptr, nullptr, (setter), nullptr, napi_default, nullptr \ + } + +#define DECLARE_NAPI_GETTER_SETTER(name, getter, setter) \ + { \ + (name), nullptr, nullptr, (getter), (setter), nullptr, napi_default, nullptr \ + } + +#endif /* FOUNDATION_ACE_NAPI_INTERFACES_KITS_NAPI_NATIVE_COMMON_H */ diff --git a/cocos/bindings/jswrapper/sm/Base.h b/cocos/bindings/jswrapper/sm/Base.h new file mode 100644 index 0000000..09abcfe --- /dev/null +++ b/cocos/bindings/jswrapper/sm/Base.h @@ -0,0 +1,63 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#ifndef UINT64_C + #define UINT64_C(value) __CONCAT(value, ULL) +#endif + +#include "jsapi.h" +#include "jsfriendapi.h" + +#include "js/Array.h" +#include "js/ArrayBuffer.h" +#include "js/BigInt.h" +#include "js/BuildId.h" +#include "js/CompilationAndEvaluation.h" +#include "js/ContextOptions.h" +#include "js/Conversions.h" +#include "js/Equality.h" +#include "js/GCAPI.h" +#include "js/Initialization.h" +#include "js/JSON.h" +#include "js/LocaleSensitive.h" +#include "js/MemoryMetrics.h" +#include "js/SourceText.h" +#include "js/Warnings.h" +#include "js/experimental/TypedData.h" + +#include "mozilla/Unused.h" + +#include "../PrivateObject.h" +#include "HelperMacros.h" + +#include +#include +#include +#include +#include +#include +#include "base/std/container/string.h" diff --git a/cocos/bindings/jswrapper/sm/Class.cpp b/cocos/bindings/jswrapper/sm/Class.cpp new file mode 100644 index 0000000..7938135 --- /dev/null +++ b/cocos/bindings/jswrapper/sm/Class.cpp @@ -0,0 +1,242 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Class.h" +#include "Object.h" +#include "ScriptEngine.h" +#include "Utils.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + +namespace se { + +// --- Global Lookup for Constructor Functions + +namespace { +// std::unordered_map __clsMap; +JSContext *__cx = nullptr; + +bool empty_constructor(JSContext *cx, uint32_t argc, JS::Value *vp) { + assert(false); + return true; +} + +ccstd::vector __allClasses; + +} // namespace + +Class::Class() +: _parent(nullptr), + _proto(nullptr), + _parentProto(nullptr), + _ctor(nullptr), + _finalizeOp(nullptr) { + memset(&_jsCls, 0, sizeof(_jsCls)); + memset(&_classOps, 0, sizeof(_classOps)); + + __allClasses.push_back(this); +} + +Class::~Class() { +} + +Class *Class::create(const char *className, Object *obj, Object *parentProto, JSNative ctor) { + Class *cls = ccnew Class(); + if (cls != nullptr && !cls->init(className, obj, parentProto, ctor)) { + delete cls; + cls = nullptr; + } + return cls; +} + +Class *Class::create(const std::initializer_list &classPath, se::Object *parent, Object *parentProto, JSNative ctor) { + se::AutoHandleScope scope; + se::Object *currentParent = parent; + se::Value tmp; + for (auto i = 0; i < classPath.size() - 1; i++) { + bool ok = currentParent->getProperty(*(classPath.begin() + i), &tmp); + CC_ASSERT(ok); // class or namespace in path is not defined + currentParent = tmp.toObject(); + } + return create(*(classPath.end() - 1), currentParent, parentProto, ctor); +} + +bool Class::init(const char *clsName, Object *parent, Object *parentProto, JSNative ctor) { + _name = clsName; + + _parent = parent; + if (_parent != nullptr) + _parent->incRef(); + + _parentProto = parentProto; + if (_parentProto != nullptr) + _parentProto->incRef(); + + _ctor = ctor; + if (_ctor == nullptr) { + _ctor = empty_constructor; + } + + // SE_LOGD("Class init ( %s ) ...\n", clsName); + return true; +} + +void Class::destroy() { + SAFE_DEC_REF(_parent); + SAFE_DEC_REF(_proto); + SAFE_DEC_REF(_parentProto); +} + +/* static */ +void Class::onTraceCallback(JSTracer *trc, JSObject *obj) { + auto *seObj = reinterpret_cast(internal::SE_JS_GetPrivate(obj, 1)); + if (seObj != nullptr) { + JS::TraceEdge(trc, &seObj->_heap, "seObj"); + } +} + +bool Class::install() { + // assert(__clsMap.find(_name) == __clsMap.end()); + // + // __clsMap.emplace(_name, this); + + _jsCls.name = _name; + _jsCls.flags = JSCLASS_USERBIT1 | JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_FOREGROUND_FINALIZE; //IDEA: Use JSCLASS_BACKGROUND_FINALIZE to improve GC performance + if (_finalizeOp != nullptr) { + _classOps.finalize = _finalizeOp; + } else { + _classOps.finalize = [](JSFreeOp *fop, JSObject *obj) {}; + } + + _classOps.trace = Class::onTraceCallback; + + _jsCls.cOps = &_classOps; + + JSObject *parentObj = _parentProto != nullptr ? _parentProto->_getJSObject() : nullptr; + JS::RootedObject parentProto(__cx, parentObj); + JS::RootedObject parent(__cx, _parent->_getJSObject()); + + _funcs.push_back(JS_FS_END); + _properties.push_back(JS_PS_END); + _staticFuncs.push_back(JS_FS_END); + _staticProperties.push_back(JS_PS_END); + + JS::RootedObject jsobj(__cx, JS_InitClass(__cx, parent, parentProto, &_jsCls, _ctor, 0, _properties.data(), _funcs.data(), _staticProperties.data(), _staticFuncs.data())); + if (jsobj != nullptr) { + _proto = Object::_createJSObject(nullptr, jsobj); + // SE_LOGD("_proto: %p, name: %s\n", _proto, _name); + _proto->root(); + return true; + } + + return false; +} + +bool Class::defineFunction(const char *name, JSNative func) { + JSFunctionSpec cb = JS_FN(name, func, 0, JSPROP_ENUMERATE); + _funcs.push_back(cb); + return true; +} + +bool Class::defineProperty(const char *name, JSNative getter, JSNative setter) { + JSPropertySpec property = JS_PSGS(name, getter, setter, JSPROP_ENUMERATE); + _properties.push_back(property); + return true; +} + +bool Class::defineProperty(const std::initializer_list &names, JSNative getter, JSNative setter) { + bool ret = true; + for (const auto *name : names) { + ret &= defineProperty(name, getter, setter); + } + return ret; +} + +bool Class::defineStaticFunction(const char *name, JSNative func) { + JSFunctionSpec cb = JS_FN(name, func, 0, JSPROP_ENUMERATE); + _staticFuncs.push_back(cb); + return true; +} + +bool Class::defineStaticProperty(const char *name, JSNative getter, JSNative setter) { + JSPropertySpec property = JS_PSGS(name, getter, setter, JSPROP_ENUMERATE); + _staticProperties.push_back(property); + return true; +} + +bool Class::defineFinalizeFunction(JSFinalizeOp func) { + _finalizeOp = func; + return true; +} + +// JSObject* Class::_createJSObject(const std::string &clsName, Class** outCls) +// { +// auto iter = __clsMap.find(clsName); +// if (iter == __clsMap.end()) +// { +// *outCls = nullptr; +// return nullptr; +// } +// +// Class* thiz = iter->second; +// JS::RootedObject obj(__cx, _createJSObjectWithClass(thiz)); +// *outCls = thiz; +// return obj; +// } + +void Class::_createJSObjectWithClass(Class *cls, JS::MutableHandleObject outObj) { + JSObject *proto = cls->_proto != nullptr ? cls->_proto->_getJSObject() : nullptr; + JS::RootedObject jsProto(__cx, proto); + outObj.set(JS_NewObjectWithGivenProto(__cx, &cls->_jsCls, jsProto)); +} + +void Class::setContext(JSContext *cx) { + __cx = cx; +} + +Object *Class::getProto() { + return _proto; +} + +JSFinalizeOp Class::_getFinalizeCb() const { + return _finalizeOp; +} + +void Class::cleanup() { + for (auto cls : __allClasses) { + cls->destroy(); + } + + se::ScriptEngine::getInstance()->addAfterCleanupHook([]() { + for (auto cls : __allClasses) { + delete cls; + } + __allClasses.clear(); + }); +} + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM diff --git a/cocos/bindings/jswrapper/sm/Class.h b/cocos/bindings/jswrapper/sm/Class.h new file mode 100644 index 0000000..c013458 --- /dev/null +++ b/cocos/bindings/jswrapper/sm/Class.h @@ -0,0 +1,157 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../config.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + + #include "Base.h" + +namespace se { + +class Object; + +/** + * se::Class represents a definition of how to create a native binding object. + */ +class Class final { +public: + /** + * @brief Creates a class used for creating relevant native binding objects. + * @param[in] className A null-terminated UTF8 string containing the class's name. + * @param[in] obj The object that current class proto object attaches to. Should not be nullptr. + * @param[in] parentProto The parent proto object that current class inherits from. Passing nullptr means a new class has no parent. + * @param[in] ctor A callback to invoke when your constructor is used in a 'new' expression. Pass nullptr to use the default object constructor. + * @return A class instance used for creating relevant native binding objects. + * @note Don't need to delete the pointer return by this method, it's managed internally. + */ + static Class *create(const char *className, Object *obj, Object *parentProto, JSNative ctor); + static Class *create(const std::initializer_list &classPath, se::Object *parent, Object *parentProto, JSNative ctor); + + /** + * @brief Defines a member function with a callback. Each objects created by class will have this function property. + * @param[in] name A null-terminated UTF8 string containing the function name. + * @param[in] func A callback to invoke when the property is called as a function. + * @return true if succeed, otherwise false. + */ + bool defineFunction(const char *name, JSNative func); + + /** + * @brief Defines a property with accessor callbacks. Each objects created by class will have this property. + * @param[in] name A null-terminated UTF8 string containing the property name. + * @param[in] getter A callback to invoke when the property is read. + * @param[in] setter A callback to invoke when the property is set. + * @return true if succeed, otherwise false. + */ + bool defineProperty(const char *name, JSNative getter, JSNative setter); + bool defineProperty(const std::initializer_list &names, JSNative getter, JSNative setter); + + /** + * @brief Defines a static function with a callback. Only JavaScript constructor object will have this function. + * @param[in] name A null-terminated UTF8 string containing the function name. + * @param[in] func A callback to invoke when the constructor's property is called as a function. + * @return true if succeed, otherwise false. + */ + bool defineStaticFunction(const char *name, JSNative func); + + /** + * @brief Defines a static property with accessor callbacks. Only JavaScript constructor object will have this property. + * @param[in] name A null-terminated UTF8 string containing the property name. + * @param[in] getter A callback to invoke when the constructor's property is read. + * @param[in] setter A callback to invoke when the constructor's property is set. + * @return true if succeed, otherwise false. + */ + bool defineStaticProperty(const char *name, JSNative getter, JSNative setter); + + /** + * @brief Defines the finalize function with a callback. + * @param[in] func The callback to invoke when a JavaScript object is garbage collected. + * @return true if succeed, otherwise false. + */ + bool defineFinalizeFunction(JSFinalizeOp func); + + /** + * @brief Installs class to JavaScript VM. + * @return true if succeed, otherwise false. + * @note After this method, an object could be created by `var foo = new Foo();`. + */ + bool install(); + + /** + * @brief Gets the proto object of this class. + * @return The proto object of this class. + * @note Don't need to be released in user code. + */ + Object *getProto(); + + /** + * @brief Gets the class name. + * @return The class name. + */ + const char *getName() const { return _name; } + + // Private API used in wrapper + JSFinalizeOp _getFinalizeCb() const; + // +private: + Class(); + ~Class(); + + bool init(const char *clsName, Object *obj, Object *parentProto, JSNative ctor); + void destroy(); + + // static JSObject* _createJSObject(const std::string &clsName, Class** outCls); + static void _createJSObjectWithClass(Class *cls, JS::MutableHandleObject outObj); + + static void setContext(JSContext *cx); + static void cleanup(); + + static void onTraceCallback(JSTracer *trc, JSObject *obj); + + const char *_name; + Object *_parent; + Object *_proto; + Object *_parentProto; + + JSNative _ctor; + + JSClass _jsCls; + JSClassOps _classOps; + + ccstd::vector _funcs; + ccstd::vector _staticFuncs; + ccstd::vector _properties; + ccstd::vector _staticProperties; + JSFinalizeOp _finalizeOp; + + friend class ScriptEngine; + friend class Object; +}; + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM diff --git a/cocos/bindings/jswrapper/sm/HelperMacros.cpp b/cocos/bindings/jswrapper/sm/HelperMacros.cpp new file mode 100644 index 0000000..b0659ee --- /dev/null +++ b/cocos/bindings/jswrapper/sm/HelperMacros.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "HelperMacros.h" + +#if defined(RECORD_JSB_INVOKING) + +namespace { +bool cmp(const std::pair> &a, const std::pair> &b) { + return std::get<1>(a.second) > std::get<1>(b.second); +} +unsigned int __jsbInvocationCount; // NOLINT(readability-identifier-naming) +ccstd::unordered_map> __jsbFunctionInvokedRecords; // NOLINT(readability-identifier-naming) +} // namespace + +JsbInvokeScopeT::JsbInvokeScopeT(const char *functionName) : _functionName(functionName) { + _start = std::chrono::high_resolution_clock::now(); + __jsbInvocationCount++; +} +JsbInvokeScopeT::~JsbInvokeScopeT() { + auto &ref = __jsbFunctionInvokedRecords[_functionName]; + std::get<0>(ref) += 1; + std::get<1>(ref) += std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - _start).count(); +} + +#endif + +void printJSBInvokeAtFrame(int n) { +#if defined(RECORD_JSB_INVOKING) + static int cnt = 0; + cnt += 1; + if (cnt % n == 0) { + printJSBInvoke(); + } +#endif +} + +void clearRecordJSBInvoke() { +#if defined(RECORD_JSB_INVOKING) + __jsbInvocationCount = 0; + __jsbFunctionInvokedRecords.clear(); +#endif +} + +void printJSBInvoke() { +#if defined(RECORD_JSB_INVOKING) + static ccstd::vector>> pairs; + for (const auto &it : __jsbFunctionInvokedRecords) { + pairs.emplace_back(it); //NOLINT + } + + std::sort(pairs.begin(), pairs.end(), cmp); + cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "Start print JSB function record info....... %d times", __jsbInvocationCount); + for (const auto &pair : pairs) { + cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "\t%s takes %.3lf ms, invoked %u times,", pair.first, std::get<1>(pair.second) / 1000000.0, std::get<0>(pair.second)); + } + pairs.clear(); + cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "End print JSB function record info.......\n"); +#endif +} \ No newline at end of file diff --git a/cocos/bindings/jswrapper/sm/HelperMacros.h b/cocos/bindings/jswrapper/sm/HelperMacros.h new file mode 100644 index 0000000..c9645a5 --- /dev/null +++ b/cocos/bindings/jswrapper/sm/HelperMacros.h @@ -0,0 +1,249 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../ValueArrayPool.h" +#include "../config.h" + +//#define RECORD_JSB_INVOKING + +#ifndef CC_DEBUG + #undef RECORD_JSB_INVOKING +#endif + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + + #if defined(RECORD_JSB_INVOKING) + +class JsbInvokeScopeT { +public: + JsbInvokeScopeT(const char *functionName); + ~JsbInvokeScopeT(); + +private: + const char *_functionName; + std::chrono::time_point _start; +}; + #define JsbInvokeScope(arg) JsbInvokeScopeT invokeScope(arg); // NOLINT(readability-identifier-naming) + + #else + // NOLINTNEXTLINE(readability-identifier-naming) + #define JsbInvokeScope(arg) \ + do { \ + } while (0) + + #endif + +template +constexpr inline T *SE_THIS_OBJECT(STATE &s) { // NOLINT(readability-identifier-naming) + return reinterpret_cast(s.nativeThisObject()); +} + +template +constexpr typename std::enable_if::value, char *>::type SE_UNDERLYING_TYPE_NAME() { // NOLINT(readability-identifier-naming) + return typeid(std::underlying_type_t).name(); +} + +template +constexpr typename std::enable_if::value, char *>::type SE_UNDERLYING_TYPE_NAME() { // NOLINT(readability-identifier-naming) + return typeid(T).name(); +} + +void clearRecordJSBInvoke(); + +void printJSBInvoke(); + +void printJSBInvokeAtFrame(int n); + + #define SAFE_INC_REF(obj) \ + if (obj != nullptr) obj->incRef() + #define SAFE_DEC_REF(obj) \ + if ((obj) != nullptr) { \ + (obj)->decRef(); \ + (obj) = nullptr; \ + } + + #define _SE(name) name##Registry + + #define SE_DECLARE_FUNC(funcName) \ + bool funcName##Registry(JSContext *_cx, unsigned argc, JS::Value *_vp) + + #define SE_BIND_FUNC(funcName) \ + bool funcName##Registry(JSContext *_cx, unsigned argc, JS::Value *_vp) { \ + JsbInvokeScope(#funcName); \ + bool ret = false; \ + JS::CallArgs _argv = JS::CallArgsFromVp(argc, _vp); \ + JS::RootedObject _thizObj(_cx); \ + _argv.computeThis(_cx, &_thizObj); \ + bool needDeleteValueArray{false}; \ + se::ValueArray &args = se::gValueArrayPool.get(argc, needDeleteValueArray); \ + se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; \ + se::internal::jsToSeArgs(_cx, argc, _argv, args); \ + se::PrivateObjectBase *privateObject = static_cast(se::internal::getPrivate(_cx, _thizObj, 0)); \ + se::Object *thisObject = reinterpret_cast(se::internal::getPrivate(_cx, _thizObj, 1)); \ + se::State state(thisObject, privateObject, args); \ + ret = funcName(state); \ + if (!ret) { \ + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \ + } \ + se::internal::setReturnValue(_cx, state.rval(), _argv); \ + return ret; \ + } + + #define SE_BIND_FUNC_FAST(funcName) \ + bool funcName##Registry(JSContext *_cx, unsigned argc, JS::Value *_vp) { \ + JS::CallArgs _argv = JS::CallArgsFromVp(argc, _vp); \ + JS::RootedObject _thizObj(_cx); \ + _argv.computeThis(_cx, &_thizObj); \ + auto *privateObject = static_cast(se::internal::SE_JS_GetPrivate(_thizObj, 0)); \ + funcName(privateObject->getRaw()); \ + _argv.rval().setUndefined(); \ + return true; \ + } + + #define SE_DECLARE_FINALIZE_FUNC(funcName) \ + void funcName##Registry(JSFreeOp *_fop, JSObject *_obj); + + #define SE_BIND_FINALIZE_FUNC(funcName) \ + void funcName##Registry(JSFreeOp *_fop, JSObject *_obj) { \ + JsbInvokeScope(#funcName); \ + se::PrivateObjectBase *privateObject = static_cast(se::internal::SE_JS_GetPrivate(_obj, 0)); \ + se::Object *seObj = static_cast(se::internal::SE_JS_GetPrivate(_obj, 1)); \ + bool ret = false; \ + if (privateObject == nullptr) \ + return; \ + se::State state(privateObject); \ + ret = funcName(state); \ + if (!ret) { \ + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \ + } \ + if (seObj->isClearMappingInFinalizer() && privateObject != nullptr) { \ + void *nativeObj = privateObject->getRaw(); \ + auto iter = se::NativePtrToObjectMap::find(nativeObj); \ + if (iter != se::NativePtrToObjectMap::end()) { \ + se::NativePtrToObjectMap::erase(iter); \ + } \ + } \ + seObj->decRef(); \ + } + + #define SE_BIND_CTOR(funcName, cls, finalizeCb) \ + bool funcName##Registry(JSContext *_cx, unsigned argc, JS::Value *_vp) { \ + JsbInvokeScope(#funcName); \ + bool ret = false; \ + JS::CallArgs _argv = JS::CallArgsFromVp(argc, _vp); \ + bool needDeleteValueArray{false}; \ + se::ValueArray &args = se::gValueArrayPool.get(argc, needDeleteValueArray); \ + se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; \ + se::internal::jsToSeArgs(_cx, argc, _argv, args); \ + se::Object *thisObject = se::Object::_createJSObjectForConstructor(cls, _argv); \ + thisObject->_setFinalizeCallback(finalizeCb##Registry); \ + _argv.rval().setObject(*thisObject->_getJSObject()); \ + se::State state(thisObject, args); \ + ret = funcName(state); \ + if (ret) { \ + se::Value _property; \ + bool _found = false; \ + _found = thisObject->getProperty("_ctor", &_property); \ + if (_found) _property.toObject()->call(args, thisObject); \ + } else { \ + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \ + } \ + return ret; \ + } + + #define SE_BIND_PROP_GET_IMPL(funcName, postFix) \ + bool funcName##postFix##Registry(JSContext *_cx, unsigned argc, JS::Value *_vp) { \ + JsbInvokeScope(#funcName); \ + bool ret = false; \ + JS::CallArgs _argv = JS::CallArgsFromVp(argc, _vp); \ + JS::RootedObject _thizObj(_cx); \ + _argv.computeThis(_cx, &_thizObj); \ + se::PrivateObjectBase *privateObject = static_cast(se::internal::getPrivate(_cx, _thizObj, 0)); \ + se::Object *thisObject = reinterpret_cast(se::internal::getPrivate(_cx, _thizObj, 1)); \ + se::State state(thisObject, privateObject); \ + ret = funcName(state); \ + if (!ret) { \ + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \ + } \ + se::internal::setReturnValue(_cx, state.rval(), _argv); \ + return ret; \ + } + + #define SE_BIND_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, ) + #define SE_BIND_FUNC_AS_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, _asGetter) + + #define SE_BIND_PROP_SET_IMPL(funcName, postFix) \ + bool funcName##postFix##Registry(JSContext *_cx, unsigned _argc, JS::Value *_vp) { \ + JsbInvokeScope(#funcName); \ + bool ret = false; \ + JS::CallArgs _argv = JS::CallArgsFromVp(_argc, _vp); \ + JS::RootedObject _thizObj(_cx); \ + _argv.computeThis(_cx, &_thizObj); \ + se::PrivateObjectBase *privateObject = static_cast(se::internal::getPrivate(_cx, _thizObj, 0)); \ + se::Object *thisObject = reinterpret_cast(se::internal::getPrivate(_cx, _thizObj, 1)); \ + bool needDeleteValueArray{false}; \ + se::ValueArray &args = se::gValueArrayPool.get(1, needDeleteValueArray); \ + se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; \ + se::Value &data{args[0]}; \ + se::internal::jsToSeValue(_cx, _argv[0], &data); \ + se::State state(thisObject, privateObject, args); \ + ret = funcName(state); \ + if (!ret) { \ + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", #funcName, __FILE__, __LINE__); \ + } \ + return ret; \ + } + + #define SE_BIND_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, ) + #define SE_BIND_FUNC_AS_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, _asSetter) + + #define SE_TYPE_NAME(t) typeid(t).name() + + #define SE_QUOTEME_(x) #x + #define SE_QUOTEME(x) SE_QUOTEME_(x) + + #define SE_REPORT_ERROR(fmt, ...) \ + SE_LOGD("ERROR (" __FILE__ ", " SE_QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__); \ + JS_ReportErrorUTF8(se::ScriptEngine::getInstance()->_getContext(), fmt, ##__VA_ARGS__) + + #if CC_DEBUG > 0 + + #define SE_ASSERT(cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + SE_LOGE("ASSERT (" __FILE__ ", " SE_QUOTEME(__LINE__) "): " fmt "\n", ##__VA_ARGS__); \ + assert(false); \ + } \ + } while (false) + + #else + + #define SE_ASSERT(cond, fmt, ...) + + #endif // #if CC_DEBUG > 0 + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM diff --git a/cocos/bindings/jswrapper/sm/Object.cpp b/cocos/bindings/jswrapper/sm/Object.cpp new file mode 100644 index 0000000..ecdfce3 --- /dev/null +++ b/cocos/bindings/jswrapper/sm/Object.cpp @@ -0,0 +1,780 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Object.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + + #include "../MappingUtils.h" + #include "Class.h" + #include "ScriptEngine.h" + #include "Utils.h" + +namespace se { + +std::unordered_map __objectMap; // Currently, the value `void*` is always nullptr + +namespace { +JSContext *__cx = nullptr; +} // namespace + +Object::Object() +: _privateObject(nullptr), + _cls(nullptr), + _finalizeCb(nullptr), + _rootCount(0) { + _currentVMId = ScriptEngine::getInstance()->getVMId(); +} + +Object::~Object() { + if (_cls == nullptr) { + unroot(); + } + + if (_rootCount > 0) { + unprotect(); + } + + auto iter = __objectMap.find(this); + if (iter != __objectMap.end()) { + __objectMap.erase(iter); + } +} + +bool Object::init(Class *cls, JSObject *obj) { + _cls = cls; + _heap = obj; + + assert(__objectMap.find(this) == __objectMap.end()); + __objectMap.emplace(this, nullptr); + + if (_cls == nullptr) { + root(); + } + + return true; +} + +Object *Object::_createJSObject(Class *cls, JSObject *obj) { + Object *ret = ccnew Object(); + if (!ret->init(cls, obj)) { + delete ret; + ret = nullptr; + } + + return ret; +} + +Object *Object::_createJSObjectForConstructor(Class *cls, const JS::CallArgs &args) { + Object *ret = ccnew Object(); + JS::RootedObject obj(__cx, JS_NewObjectForConstructor(__cx, &cls->_jsCls, args)); + if (!ret->init(cls, obj)) { + delete ret; + ret = nullptr; + } + return ret; +} + +Object *Object::createPlainObject() { + Object *obj = Object::_createJSObject(nullptr, JS_NewPlainObject(__cx)); + return obj; +} + +Object *Object::createObjectWithClass(Class *cls) { + JS::RootedObject jsobj(__cx); + Class::_createJSObjectWithClass(cls, &jsobj); + Object *obj = Object::_createJSObject(cls, jsobj); + return obj; +} + +Object *Object::getObjectWithPtr(void *ptr) { + Object *obj = nullptr; + auto iter = NativePtrToObjectMap::find(ptr); + if (iter != NativePtrToObjectMap::end()) { + obj = iter->second; + obj->incRef(); + } + return obj; +} + +Object *Object::createArrayObject(size_t length) { + JS::RootedObject jsobj(__cx, JS::NewArrayObject(__cx, length)); + Object *obj = Object::_createJSObject(nullptr, jsobj); + return obj; +} + +Object *Object::createArrayBufferObject(const void *data, size_t byteLength) { + Object *obj = nullptr; + + if (byteLength > 0 && data != nullptr) { + mozilla::UniquePtr jsBuf( + js_pod_arena_malloc(js::ArrayBufferContentsArena, byteLength)); + if (!jsBuf) + return nullptr; + + memcpy(jsBuf.get(), data, byteLength); + JS::RootedObject jsobj(__cx, JS::NewArrayBufferWithContents(__cx, byteLength, jsBuf.get())); + if (jsobj) { + // If JS::NewArrayBufferWithContents returns non-null, the ownership of + // the data is transfered to obj, so we release the ownership here. + mozilla::Unused << jsBuf.release(); + + obj = Object::_createJSObject(nullptr, jsobj); + } + } else { + JS::RootedObject jsobj(__cx, JS::NewArrayBuffer(__cx, byteLength)); + if (jsobj) { + obj = Object::_createJSObject(nullptr, jsobj); + } + } + + return obj; +} + +/* static */ +Object *Object::createExternalArrayBufferObject(void *contents, size_t byteLength, BufferContentsFreeFunc freeFunc, void *freeUserData /* = nullptr*/) { + struct BackingStoreUserData { + BufferContentsFreeFunc freeFunc; + void *freeUserData; + size_t byteLength; + }; + + auto *userData = ccnew BackingStoreUserData(); + userData->freeFunc = freeFunc; + userData->freeUserData = freeUserData; + userData->byteLength = byteLength; + + Object *obj = nullptr; + JS::RootedObject jsobj(__cx, JS::NewExternalArrayBuffer( + __cx, byteLength, contents, + [](void *data, void *deleterData) { + auto *userData = reinterpret_cast(deleterData); + userData->freeFunc(data, userData->byteLength, userData->freeUserData); + delete userData; + }, + userData)); + if (jsobj) { + obj = Object::_createJSObject(nullptr, jsobj); + } + return obj; +} + +Object *Object::createTypedArray(TypedArrayType type, const void *data, size_t byteLength) { + if (type == TypedArrayType::NONE) { + SE_LOGE("Don't pass se::Object::TypedArrayType::NONE to createTypedArray API!"); + return nullptr; + } + + if (type == TypedArrayType::UINT8_CLAMPED) { + SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!"); + return nullptr; + } + + #define CREATE_TYPEDARRAY(_type_, _data, _byteLength, count) \ + { \ + void *tmpData = nullptr; \ + JS::RootedObject arr(__cx, JS_New##_type_##Array(__cx, (uint32_t)(count))); \ + bool isShared = false; \ + if (_data != nullptr) { \ + JS::AutoCheckCannotGC nogc; \ + tmpData = JS_Get##_type_##ArrayData(arr, &isShared, nogc); \ + memcpy(tmpData, (const void *)_data, (_byteLength)); \ + } \ + Object *obj = Object::_createJSObject(nullptr, arr); \ + return obj; \ + } + + switch (type) { + case TypedArrayType::INT8: + CREATE_TYPEDARRAY(Int8, data, byteLength, byteLength); + case TypedArrayType::INT16: + CREATE_TYPEDARRAY(Int16, data, byteLength, byteLength / 2); + case TypedArrayType::INT32: + CREATE_TYPEDARRAY(Int32, data, byteLength, byteLength / 4); + case TypedArrayType::UINT8: + CREATE_TYPEDARRAY(Uint8, data, byteLength, byteLength); + case TypedArrayType::UINT16: + CREATE_TYPEDARRAY(Uint16, data, byteLength, byteLength / 2); + case TypedArrayType::UINT32: + CREATE_TYPEDARRAY(Uint32, data, byteLength, byteLength / 4); + case TypedArrayType::FLOAT32: + CREATE_TYPEDARRAY(Float32, data, byteLength, byteLength / 4); + case TypedArrayType::FLOAT64: + CREATE_TYPEDARRAY(Float64, data, byteLength, byteLength / 8); + default: + assert(false); // Should never go here. + break; + } + + return nullptr; + #undef CREATE_TYPEDARRAY +} + +/* static */ +Object *Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj) { + return Object::createTypedArrayWithBuffer(type, obj, 0); +} + +/* static */ +Object *Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset) { + size_t byteLength{0}; + uint8_t *skip{nullptr}; + obj->getTypedArrayData(&skip, &byteLength); + return Object::createTypedArrayWithBuffer(type, obj, offset, byteLength - offset); +} + +/* static */ +Object *Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength) { + if (type == TypedArrayType::NONE) { + SE_LOGE("Don't pass se::Object::TypedArrayType::NONE to createTypedArray API!"); + return nullptr; + } + + if (type == TypedArrayType::UINT8_CLAMPED) { + SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!"); + return nullptr; + } + + assert(obj->isArrayBuffer()); + JS::RootedObject jsobj(__cx, obj->_getJSObject()); + + switch (type) { + case TypedArrayType::INT8: { + JS::RootedObject typeArray(__cx, JS_NewInt8ArrayWithBuffer(__cx, jsobj, offset, byteLength)); + return Object::_createJSObject(nullptr, typeArray); + } + case TypedArrayType::INT16: { + JS::RootedObject typeArray(__cx, JS_NewInt16ArrayWithBuffer(__cx, jsobj, offset, byteLength / 2)); + return Object::_createJSObject(nullptr, typeArray); + } + case TypedArrayType::INT32: { + JS::RootedObject typeArray(__cx, JS_NewInt32ArrayWithBuffer(__cx, jsobj, offset, byteLength / 4)); + return Object::_createJSObject(nullptr, typeArray); + } + case TypedArrayType::UINT8: { + JS::RootedObject typeArray(__cx, JS_NewUint8ArrayWithBuffer(__cx, jsobj, offset, byteLength)); + return Object::_createJSObject(nullptr, typeArray); + } + case TypedArrayType::UINT16: { + JS::RootedObject typeArray(__cx, JS_NewUint16ArrayWithBuffer(__cx, jsobj, offset, byteLength / 2)); + return Object::_createJSObject(nullptr, typeArray); + } + case TypedArrayType::UINT32: { + JS::RootedObject typeArray(__cx, JS_NewUint32ArrayWithBuffer(__cx, jsobj, offset, byteLength / 4)); + return Object::_createJSObject(nullptr, typeArray); + } + case TypedArrayType::FLOAT32: { + JS::RootedObject typeArray(__cx, JS_NewFloat32ArrayWithBuffer(__cx, jsobj, offset, byteLength / 4)); + return Object::_createJSObject(nullptr, typeArray); + } + case TypedArrayType::FLOAT64: { + JS::RootedObject typeArray(__cx, JS_NewFloat64ArrayWithBuffer(__cx, jsobj, offset, byteLength / 8)); + return Object::_createJSObject(nullptr, typeArray); + } + default: + assert(false); // Should never go here. + break; + } + + return nullptr; +} + +Object *Object::createUint8TypedArray(uint8_t *data, size_t dataCount) { + return createTypedArray(TypedArrayType::UINT8, data, dataCount); +} + +Object *Object::createJSONObject(const std::string &jsonStr) { + Value strVal(jsonStr); + JS::RootedValue jsStr(__cx); + internal::seToJsValue(__cx, strVal, &jsStr); + JS::RootedValue jsObj(__cx); + JS::RootedString rootedStr(__cx, jsStr.toString()); + Object *obj = nullptr; + if (JS_ParseJSON(__cx, rootedStr, &jsObj)) { + obj = Object::_createJSObject(nullptr, jsObj.toObjectOrNull()); + } + return obj; +} + +void Object::_setFinalizeCallback(JSFinalizeOp finalizeCb) { + _finalizeCb = finalizeCb; +} + +bool Object::getProperty(const char *name, Value *data, bool cachePropertyName) { + assert(data != nullptr); + data->setUndefined(); + + JSObject *jsobj = _getJSObject(); + if (jsobj == nullptr) + return false; + + JS::RootedObject object(__cx, jsobj); + + bool found = false; + bool ok = JS_HasProperty(__cx, object, name, &found); + + if (!ok || !found) { + return false; + } + + JS::RootedValue rcValue(__cx); + ok = JS_GetProperty(__cx, object, name, &rcValue); + + if (ok && data) { + internal::jsToSeValue(__cx, rcValue, data); + } + + return ok; +} + +bool Object::setProperty(const char *name, const Value &v) { + JS::RootedObject object(__cx, _getJSObject()); + + JS::RootedValue value(__cx); + internal::seToJsValue(__cx, v, &value); + return JS_SetProperty(__cx, object, name, value); +} + +bool Object::defineProperty(const char *name, JSNative getter, JSNative setter) { + JS::RootedObject jsObj(__cx, _getJSObject()); + return JS_DefineProperty(__cx, jsObj, name, getter, setter, JSPROP_ENUMERATE); +} + +bool Object::defineOwnProperty(const char *name, const se::Value &value, bool writable, bool enumerable, bool configurable) { + JS::RootedObject jsObj(__cx, _getJSObject()); + JS::RootedValue jsVal(__cx); + internal::seToJsValue(__cx, value, &jsVal); + + unsigned attrs = 0; + if (!writable) { + attrs |= JSPROP_READONLY; + } + if (enumerable) { + attrs |= JSPROP_ENUMERATE; + } + if (!configurable) { + attrs |= JSPROP_PERMANENT; + } + + return JS_DefineProperty(__cx, jsObj, name, jsVal, attrs); +} + +bool Object::call(const ValueArray &args, Object *thisObject, Value *rval /* = nullptr*/) { + assert(isFunction()); + + JS::RootedValueVector jsarr(__cx); + jsarr.reserve(args.size()); + internal::seToJsArgs(__cx, args, &jsarr); + + JS::RootedObject contextObject(__cx); + if (thisObject != nullptr) { + contextObject.set(thisObject->_getJSObject()); + } + + JSObject *funcObj = _getJSObject(); + JS::RootedValue func(__cx, JS::ObjectValue(*funcObj)); + JS::RootedValue rcValue(__cx); + + JSAutoRealm autoRealm(__cx, func.toObjectOrNull()); + bool ok = JS_CallFunctionValue(__cx, contextObject, func, jsarr, &rcValue); + + if (ok) { + if (rval != nullptr) + internal::jsToSeValue(__cx, rcValue, rval); + } else { + se::ScriptEngine::getInstance()->clearException(); + } + + return ok; +} + +bool Object::defineFunction(const char *funcName, JSNative func) { + JS::RootedObject object(__cx, _getJSObject()); + JSFunction *jsFunc = JS_DefineFunction(__cx, object, funcName, func, 0, JSPROP_ENUMERATE); + return jsFunc != nullptr; +} + +bool Object::getArrayLength(uint32_t *length) const { + assert(length != nullptr); + if (!isArray()) + return false; + + JS::RootedObject object(__cx, _getJSObject()); + if (JS::GetArrayLength(__cx, object, length)) + return true; + + *length = 0; + return false; +} + +bool Object::getArrayElement(uint32_t index, Value *data) const { + assert(data != nullptr); + if (!isArray()) + return false; + + JS::RootedObject object(__cx, _getJSObject()); + JS::RootedValue rcValue(__cx); + if (JS_GetElement(__cx, object, index, &rcValue)) { + internal::jsToSeValue(__cx, rcValue, data); + return true; + } + + data->setUndefined(); + return false; +} + +bool Object::setArrayElement(uint32_t index, const Value &data) { + if (!isArray()) + return false; + + JS::RootedValue jsval(__cx); + internal::seToJsValue(__cx, data, &jsval); + JS::RootedObject thisObj(__cx, _getJSObject()); + return JS_SetElement(__cx, thisObj, index, jsval); +} + +bool Object::isFunction() const { + return JS_ObjectIsFunction(_getJSObject()); +} + +bool Object::_isNativeFunction(JSNative func) const { + JSObject *obj = _getJSObject(); + return JS_ObjectIsFunction(obj) && JS_IsNativeFunction(obj, func); +} + +bool Object::isTypedArray() const { + return JS_IsTypedArrayObject(_getJSObject()); +} + +Object::TypedArrayType Object::getTypedArrayType() const { + TypedArrayType ret = TypedArrayType::NONE; + JSObject *obj = _getJSObject(); + if (JS_IsInt8Array(obj)) + ret = TypedArrayType::INT8; + else if (JS_IsInt16Array(obj)) + ret = TypedArrayType::INT16; + else if (JS_IsInt32Array(obj)) + ret = TypedArrayType::INT32; + else if (JS_IsUint8Array(obj)) + ret = TypedArrayType::UINT8; + else if (JS_IsUint8ClampedArray(obj)) + ret = TypedArrayType::UINT8_CLAMPED; + else if (JS_IsUint16Array(obj)) + ret = TypedArrayType::UINT16; + else if (JS_IsUint32Array(obj)) + ret = TypedArrayType::UINT32; + else if (JS_IsFloat32Array(obj)) + ret = TypedArrayType::FLOAT32; + else if (JS_IsFloat64Array(obj)) + ret = TypedArrayType::FLOAT64; + + return ret; +} + +bool Object::getTypedArrayData(uint8_t **ptr, size_t *length) const { + assert(JS_IsArrayBufferViewObject(_getJSObject())); + bool isShared = false; + + JS::AutoCheckCannotGC nogc; + if (ptr != nullptr) { + *ptr = (uint8_t *)JS_GetArrayBufferViewData(_getJSObject(), &isShared, nogc); + } + + if (length != nullptr) { + *length = JS_GetArrayBufferViewByteLength(_getJSObject()); + } + return (*ptr != nullptr); +} + +bool Object::isArray() const { + JS::RootedValue value(__cx, JS::ObjectValue(*_getJSObject())); + bool isArray = false; + return JS::IsArrayObject(__cx, value, &isArray) && isArray; +} + +bool Object::isArrayBuffer() const { + return JS::IsArrayBufferObject(_getJSObject()); +} + +bool Object::getArrayBufferData(uint8_t **ptr, size_t *length) const { + assert(isArrayBuffer()); + + bool isShared = false; + JS::AutoCheckCannotGC nogc; + if (ptr != nullptr) { + *ptr = (uint8_t *)JS::GetArrayBufferData(_getJSObject(), &isShared, nogc); + } + + if (length != nullptr) { + *length = JS::GetArrayBufferByteLength(_getJSObject()); + } + return (*ptr != nullptr); +} + +bool Object::getAllKeys(ccstd::vector *allKeys) const { + assert(allKeys != nullptr); + JS::RootedObject jsobj(__cx, _getJSObject()); + JS::Rooted props(__cx, JS::IdVector(__cx)); + if (!JS_Enumerate(__cx, jsobj, &props)) + return false; + + ccstd::vector keys; + for (size_t i = 0, length = props.length(); i < length; ++i) { + JS::RootedId id(__cx, props[i]); + JS::RootedValue keyVal(__cx); + JS_IdToValue(__cx, id, &keyVal); + + if (JSID_IS_STRING(id)) { + JS::RootedString rootedKeyVal(__cx, keyVal.toString()); + allKeys->push_back(internal::jsToStdString(__cx, rootedKeyVal)); + } else if (JSID_IS_INT(id)) { + char buf[50] = {0}; + snprintf(buf, sizeof(buf), "%d", keyVal.toInt32()); + allKeys->push_back(buf); + } else { + assert(false); + } + } + + return true; +} + +void Object::setPrivateObject(PrivateObjectBase *data) { + assert(_privateObject == nullptr); + #if CC_DEBUG + //assert(NativePtrToObjectMap::find(data->getRaw()) == NativePtrToObjectMap::end()); + auto it = NativePtrToObjectMap::find(data->getRaw()); + if (it != NativePtrToObjectMap::end()) { + auto *pri = it->second->getPrivateObject(); + SE_LOGE("Already exists object %s/[%s], trying to add %s/[%s]\n", pri->getName(), typeid(*pri).name(), data->getName(), typeid(*data).name()); + #if JSB_TRACK_OBJECT_CREATION + SE_LOGE(" previous object created at %s\n", it->second->_objectCreationStackFrame.c_str()); + #endif + assert(false); + } + #endif + JS::RootedObject obj(__cx, _getJSObject()); + internal::setPrivate(__cx, obj, data, this, &_internalData, _finalizeCb); //TODO(cjh): how to use _internalData? + NativePtrToObjectMap::emplace(data->getRaw(), this); + _privateObject = data; +} + +PrivateObjectBase *Object::getPrivateObject() const { + if (_privateObject == nullptr) { + JS::RootedObject obj(__cx, _getJSObject()); + const_cast(this)->_privateObject = static_cast(internal::getPrivate(__cx, obj, 0)); + } + return _privateObject; +} + +void Object::clearPrivateData(bool clearMapping) { + if (_privateObject != nullptr) { + if (clearMapping) { + NativePtrToObjectMap::erase(_privateObject->getRaw()); + } + JS::RootedObject obj(__cx, _getJSObject()); + internal::clearPrivate(__cx, obj); + delete _privateObject; + _privateObject = nullptr; + } +} + +void Object::setContext(JSContext *cx) { + __cx = cx; +} + +// static +void Object::cleanup() { + for (const auto &e : __objectMap) { + e.first->reset(); + } + + ScriptEngine::getInstance()->addAfterCleanupHook([]() { + __objectMap.clear(); + const auto &instance = NativePtrToObjectMap::instance(); + for (const auto &e : instance) { + e.second->decRef(); + } + NativePtrToObjectMap::clear(); + __cx = nullptr; + }); +} + +JSObject *Object::_getJSObject() const { + return isRooted() ? _root.get() : _heap.get(); +} + +void Object::root() { + if (_rootCount == 0) { + protect(); + } + ++_rootCount; +} + +void Object::unroot() { + if (_rootCount > 0) { + --_rootCount; + if (_rootCount == 0) { + unprotect(); + } + } +} + +void Object::protect() { + assert(_root == nullptr); + assert(_heap != JS::SafelyInitialized::create()); + + _root.init(__cx, _heap); + _heap.set(JS::SafelyInitialized::create()); +} + +void Object::unprotect() { + if (!_root.initialized()) + return; + + assert(_currentVMId == ScriptEngine::getInstance()->getVMId()); + assert(_heap == JS::SafelyInitialized::create()); + _heap = _root.get(); + _root.reset(); +} + +void Object::reset() { + _root.reset(); + _heap = JS::SafelyInitialized::create(); +} + +void Object::onTraceCallback(JSTracer *trc, void *data) { + auto *thiz = reinterpret_cast(data); + thiz->trace(trc); +} + +/* Tracing makes no sense in the rooted case, because JS::PersistentRooted + * already takes care of that. */ +void Object::trace(JSTracer *tracer) { + assert(!isRooted()); + JS::TraceEdge(tracer, &_heap, "seobj tracing"); +} + +/* If not tracing, then you must call this method during GC in order to + * update the object's location if it was moved, or null it out if it was + * finalized. If the object was finalized, returns true. */ +bool Object::updateAfterGC(JSTracer *trc, void *data) { + assert(!isRooted()); + bool isGarbageCollected = false; + internal::PrivateData *internalData = nullptr; + + JSObject *oldPtr = _heap.unbarrieredGet(); + if (_heap.unbarrieredGet() != nullptr) + JS_UpdateWeakPointerAfterGC(trc, &_heap); + + JSObject *newPtr = _heap.unbarrieredGet(); + + // IDEA: test to see ggc + if (oldPtr != nullptr && newPtr != nullptr) { + assert(oldPtr == newPtr); + } + isGarbageCollected = (newPtr == nullptr); + if (isGarbageCollected && internalData != nullptr) { + free(internalData); + } + return isGarbageCollected; +} + +bool Object::isRooted() const { + return _rootCount > 0; +} + +bool Object::strictEquals(Object *o) const { + JSObject *thisObj = _getJSObject(); + JSObject *oThisObj = o->_getJSObject(); + if ((thisObj == nullptr || oThisObj == nullptr) && thisObj != oThisObj) + return false; + + assert(thisObj); + assert(oThisObj); + JS::RootedValue v1(__cx, JS::ObjectValue(*_getJSObject())); + JS::RootedValue v2(__cx, JS::ObjectValue(*o->_getJSObject())); + bool same = false; + bool ok = JS::SameValue(__cx, v1, v2, &same); + return ok && same; +} + +bool Object::attachObject(Object *obj) { + assert(obj); + + Object *global = ScriptEngine::getInstance()->getGlobalObject(); + Value jsbVal; + if (!global->getProperty("jsb", &jsbVal)) + return false; + Object *jsbObj = jsbVal.toObject(); + + Value func; + + if (!jsbObj->getProperty("registerNativeRef", &func)) + return false; + + ValueArray args; + args.push_back(Value(this)); + args.push_back(Value(obj)); + func.toObject()->call(args, global); + return true; +} + +bool Object::detachObject(Object *obj) { + assert(obj); + Object *global = ScriptEngine::getInstance()->getGlobalObject(); + Value jsbVal; + if (!global->getProperty("jsb", &jsbVal)) + return false; + Object *jsbObj = jsbVal.toObject(); + + Value func; + + if (!jsbObj->getProperty("unregisterNativeRef", &func)) + return false; + + ValueArray args; + args.push_back(Value(this)); + args.push_back(Value(obj)); + func.toObject()->call(args, global); + return true; +} + +std::string Object::toString() const { + std::string ret; + if (isFunction() || isArray() || isTypedArray()) { + JS::RootedValue val(__cx, JS::ObjectOrNullValue(_getJSObject())); + internal::forceConvertJsValueToStdString(__cx, val, &ret); + } else if (isArrayBuffer()) { + ret = "[object ArrayBuffer]"; + } else { + ret = "[object Object]"; + } + return ret; +} + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM diff --git a/cocos/bindings/jswrapper/sm/Object.h b/cocos/bindings/jswrapper/sm/Object.h new file mode 100644 index 0000000..56e7b13 --- /dev/null +++ b/cocos/bindings/jswrapper/sm/Object.h @@ -0,0 +1,458 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../config.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + + #include "../RefCounter.h" + #include "../Value.h" + #include "Base.h" + +namespace se { + +class Class; + +namespace internal { +struct PrivateData; +} + +/** + * se::Object represents JavaScript Object. + */ +class Object final : public RefCounter { +public: + /** + * @brief Creates a JavaScript Object like `{} or new Object()`. + * @return A JavaScript Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createPlainObject(); + + /** + * @brief Creates a JavaScript Array Object like `[] or new Array()`. + * @param[in] length The initical length of array. + * @return A JavaScript Array Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createArrayObject(size_t length); + + /** + * @brief Creates a JavaScript Typed Array Object with uint8 format from an existing pointer. + * @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object. + * @param[in] byteLength The number of bytes pointed to by the parameter bytes. + * @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + * @deprecated This method is deprecated, please use `se::Object::createTypedArray` instead. + */ + SE_DEPRECATED_ATTRIBUTE static Object *createUint8TypedArray(uint8_t *data, size_t byteLength); + + enum class TypedArrayType { + NONE, + INT8, + INT16, + INT32, + UINT8, + UINT8_CLAMPED, + UINT16, + UINT32, + FLOAT32, + FLOAT64 + }; + + /** + * @brief Creates a JavaScript Typed Array Object with specified format from an existing pointer, + if provide a null pointer,then will create a empty JavaScript Typed Array Object. + * @param[in] type The format of typed array. + * @param[in] data A pointer to the byte buffer to be used as the backing store of the Typed Array object. + * @param[in] byteLength The number of bytes pointed to by the parameter bytes. + * @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createTypedArray(TypedArrayType type, const void *data, size_t byteLength); + + /** + * @brief Creates a JavaScript Typed Array Object with a se::Object, which is a ArrayBuffer, + if provide a null pointer,then will create a empty JavaScript Typed Array Object. + * @param[in] type The format of typed array. + * @param[in] obj A ArrayBuffer to TypedArray. + * @param[in] offset Offset of ArrayBuffer to create with. + * @param[in] byteLength The number of bytes pointed to by the parameter bytes. + * @return A JavaScript Typed Array Object which refers to the ArrayBuffer Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj); + static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset); + static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength); + + /** + * @brief Creates a JavaScript Array Buffer object from an existing pointer. + * @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object. + * @param[in] byteLength The number of bytes pointed to by the parameter bytes. + * @return A Array Buffer Object whose backing store is the same as the one pointed to data, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createArrayBufferObject(const void *data, size_t byteLength); + + using BufferContentsFreeFunc = void (*)(void *contents, size_t byteLength, void *userData); + static Object *createExternalArrayBufferObject(void *contents, size_t nbytes, BufferContentsFreeFunc freeFunc, void *freeUserData = nullptr); + + /** + * @brief Creates a JavaScript Object from a JSON formatted string. + * @param[in] jsonStr The utf-8 string containing the JSON string to be parsed. + * @return A JavaScript Object containing the parsed value, or nullptr if the input is invalid. + * @note The return value (non-null) has to be released manually. + */ + static Object *createJSONObject(const std::string &jsonStr); + + /** + * @brief Creates a JavaScript Native Binding Object from an existing se::Class instance. + * @param[in] cls The se::Class instance which stores native callback informations. + * @return A JavaScript Native Binding Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createObjectWithClass(Class *cls); + + /** + * @brief Gets a se::Object from an existing native object pointer. + * @param[in] ptr The native object pointer associated with the se::Object + * @return A JavaScript Native Binding Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *getObjectWithPtr(void *ptr); + + /** + * @brief Gets a property from an object. + * @param[in] name A utf-8 string containing the property's name. + * @param[out] value The property's value if object has the property, otherwise the undefined value. + * @return true if object has the property, otherwise false. + */ + inline bool getProperty(const char *name, Value *data) { + return getProperty(name, data, false); + } + + bool getProperty(const char *name, Value *data, bool cachePropertyName); + + inline bool getProperty(const std::string &name, Value *value) { + return getProperty(name.c_str(), value); + } + + /** + * @brief Sets a property to an object. + * @param[in] name A utf-8 string containing the property's name. + * @param[in] value A value to be used as the property's value. + * @return true if the property is set successfully, otherwise false. + */ + bool setProperty(const char *name, const Value &value); + + inline bool setProperty(const std::string &name, const Value &value) { + return setProperty(name.c_str(), value); + } + + /** + * @brief Defines a property with native accessor callbacks for an object. + * @param[in] name A utf-8 string containing the property's name. + * @param[in] getter The native callback for getter. + * @param[in] setter The native callback for setter. + * @return true if succeed, otherwise false. + */ + bool defineProperty(const char *name, JSNative getter, JSNative setter); + + bool defineOwnProperty(const char *name, const se::Value &value, bool writable = true, bool enumerable = true, bool configurable = true); + + /** + * @brief Defines a function with a native callback for an object. + * @param[in] funcName A utf-8 string containing the function name. + * @param[in] func The native callback triggered by JavaScript code. + * @return true if succeed, otherwise false. + */ + bool defineFunction(const char *funcName, JSNative func); + + /** + * @brief Tests whether an object can be called as a function. + * @return true if object can be called as a function, otherwise false. + */ + bool isFunction() const; + + /** + * @brief Calls an object as a function. + * @param[in] args A se::Value array of arguments to pass to the function. Pass se::EmptyValueArray if argumentCount is 0. + * @param[in] thisObject The object to use as "this," or NULL to use the global object as "this." + * @param[out] rval The se::Value that results from calling object as a function, passing nullptr if return value is ignored. + * @return true if object is a function and there isn't any errors, otherwise false. + */ + bool call(const ValueArray &args, Object *thisObject, Value *rval = nullptr); + + /** + * @brief Tests whether an object is an array. + * @return true if object is an array, otherwise false. + */ + bool isArray() const; + + /** + * @brief Gets array length of an array object. + * @param[out] length The array length to be stored. It's set to 0 if there is an error. + * @return true if succeed, otherwise false. + */ + bool getArrayLength(uint32_t *length) const; + + /** + * @brief Gets an element from an array object by numeric index. + * @param[in] index An integer value for index. + * @param[out] data The se::Value to be stored for the element in certain index. + * @return true if succeed, otherwise false. + */ + bool getArrayElement(uint32_t index, Value *data) const; + + /** + * @brief Sets an element to an array object by numeric index. + * @param[in] index An integer value for index. + * @param[in] data The se::Value to be set to array with certain index. + * @return true if succeed, otherwise false. + */ + bool setArrayElement(uint32_t index, const Value &data); + + /** @brief Tests whether an object is a typed array. + * @return true if object is a typed array, otherwise false. + */ + bool isTypedArray() const; + + /** + * @brief Gets the type of a typed array object. + * @return The type of a typed array object. + */ + TypedArrayType getTypedArrayType() const; + + /** + * @brief Gets backing store of a typed array object. + * @param[out] ptr A temporary pointer to the backing store of a JavaScript Typed Array object. + * @param[out] length The byte length of a JavaScript Typed Array object. + * @return true if succeed, otherwise false. + */ + bool getTypedArrayData(uint8_t **ptr, size_t *length) const; + + /** + * @brief Tests whether an object is an array buffer object. + * @return true if object is an array buffer object, otherwise false. + */ + bool isArrayBuffer() const; + + /** + * @brief Gets buffer data of an array buffer object. + * @param[out] ptr A pointer to the data buffer that serves as the backing store for a JavaScript Typed Array object. + * @param[out] length The number of bytes in a JavaScript data object. + * @return true if succeed, otherwise false. + */ + bool getArrayBufferData(uint8_t **ptr, size_t *length) const; + + /** + * @brief Gets all property names of an object. + * @param[out] allKeys A string vector to store all property names. + * @return true if succeed, otherwise false. + */ + bool getAllKeys(ccstd::vector *allKeys) const; + + void setPrivateObject(PrivateObjectBase *data); + PrivateObjectBase *getPrivateObject() const; + + /** + * @brief Gets an object's private data. + * @return A void* that is the object's private data, if the object has private data, otherwise nullptr. + */ + inline void *getPrivateData() const { + return _privateObject ? _privateObject->getRaw() : nullptr; + } + + /** + * @brief Sets a pointer to private data on an object. + * @param[in] data A void* to set as the object's private data. + * @note This method will associate private data with se::Object by std::unordered_map::emplace. + * It's used for search a se::Object via a void* private data. + */ + template + inline void setPrivateData(T *data) { + static_assert(!std::is_void::value, "void * is not allowed for private data"); + setPrivateObject(se::make_shared_private_object(data)); + } + + template + inline T *getTypedPrivateData() const { + return reinterpret_cast(getPrivateData()); + } + + /** + * @brief Clears private data of an object. + * @param clearMapping Whether to clear the mapping of native object & se::Object. + */ + void clearPrivateData(bool clearMapping = true); + + /** + * @brief Sets whether to clear the mapping of native object & se::Object in finalizer + */ + inline void setClearMappingInFinalizer(bool v) { _clearMappingInFinalizer = v; } + inline bool isClearMappingInFinalizer() const { return _clearMappingInFinalizer; } + + /** + * @brief Roots an object from garbage collection. + * @note Use this method when you want to store an object in a global or on the heap, where the garbage collector will not be able to discover your reference to it. + * An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection. + */ + void root(); + + /** + * @brief Unroots an object from garbage collection. + * @note An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection. + */ + void unroot(); + + /** + * @brief Tests whether an object is rooted. + * @return true if it has been already rooted, otherwise false. + */ + bool isRooted() const; + + /** + * @brief Tests whether two objects are strict equal, as compared by the JS === operator. + * @param[in] o The object to be tested with this object. + * @return true if the two values are strict equal, otherwise false. + */ + bool strictEquals(Object *o) const; + + /** + * @brief Attaches an object to current object. + * @param[in] obj The object to be attached. + * @return true if succeed, otherwise false. + * @note This method will set `obj` as a property of current object, therefore the lifecycle of child object will depend on current object, + * which means `obj` may be garbage collected only after current object is garbage collected. + * It's normally used in binding a native callback method. For example: + + ```javascript + var self = this; + someObject.setCallback(function(){}, self); + ``` + + ```c++ + static bool SomeObject_setCallback(se::State& s) + { + SomeObject* cobj = (SomeObject*)s.nativeThisObject(); + const auto& args = s.args(); + size_t argc = args.size(); + if (argc == 2) { + std::function arg0; + do { + if (args[0].isObject() && args[0].toObject()->isFunction()) + { + se::Value jsThis(args[1]); + se::Value jsFunc(args[0]); + + jsThis.toObject()->attachObject(jsFunc.toObject()); + + auto lambda = [=]() -> void { + ... + // Call jsFunc stuff... + ... + }; + arg0 = lambda; + } + else + { + arg0 = nullptr; + } + } while(false); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->setCallback(arg0); + return true; + } + + return false; + } + ``` + */ + bool attachObject(Object *obj); + + /** + * @brief Detaches an object from current object. + * @param[in] obj The object to be detached. + * @return true if succeed, otherwise false. + * @note The attached object will not be released if current object is not garbage collected. + */ + bool detachObject(Object *obj); + + /** + * @brief Returns the string for describing current object. + * @return The string for describing current object. + */ + std::string toString() const; + + // Private API used in wrapper + static Object *_createJSObject(Class *cls, JSObject *obj); + static Object *_createJSObjectForConstructor(Class *cls, const JS::CallArgs &args); + void _setFinalizeCallback(JSFinalizeOp finalizeCb); + bool _isNativeFunction(JSNative func) const; + JSObject *_getJSObject() const; + Class *_getClass() const { return _cls; } + // + +private: + Object(); + bool init(Class *cls, JSObject *obj); + virtual ~Object(); + + static void setContext(JSContext *cx); + static void cleanup(); + static void onTraceCallback(JSTracer *trc, void *data); + + void trace(JSTracer *tracer); + bool updateAfterGC(JSTracer *trc, void *data); + + void protect(); + void unprotect(); + void reset(); + + JS::Heap _heap; /* should be untouched if in rooted mode */ + JS::PersistentRootedObject _root; /* should be null if not in rooted mode */ + + PrivateObjectBase *_privateObject{nullptr}; + internal::PrivateData *_internalData{nullptr}; + + Class *_cls{nullptr}; + JSFinalizeOp _finalizeCb{nullptr}; + + uint32_t _rootCount{0}; + uint32_t _currentVMId{0}; + + bool _clearMappingInFinalizer{true}; + + friend class ScriptEngine; + friend class Class; +}; + +extern std::unordered_map __objectMap; // Currently, the value `void*` is always nullptr + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM diff --git a/cocos/bindings/jswrapper/sm/ScriptEngine.cpp b/cocos/bindings/jswrapper/sm/ScriptEngine.cpp new file mode 100644 index 0000000..5e3cb63 --- /dev/null +++ b/cocos/bindings/jswrapper/sm/ScriptEngine.cpp @@ -0,0 +1,1150 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "ScriptEngine.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + + #include "../MappingUtils.h" + #include "../State.h" + #include "Class.h" + #include "Object.h" + #include "Utils.h" + + // for debug socket + #ifdef _WIN32 + #include + #include + #else + #include + #include + #include + #endif + + #include + #include + #include + + #if SE_DEBUG + #define TRACE_DEBUGGER_SERVER(...) SE_LOGD(__VA_ARGS__) + #else + #define TRACE_DEBUGGER_SERVER(...) + #endif // #if CC_DEBUG + +uint32_t __jsbInvocationCount = 0; + +namespace se { + +namespace { + +const char *BYTE_CODE_FILE_EXT = ".jsc"; + +ScriptEngine *__instance = nullptr; + +const JSClassOps global_classOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + nullptr, // finalize + nullptr, // call + nullptr, // hasInstance + nullptr, // construct + JS_GlobalObjectTraceHook, // trace +}; + +JSClass __globalClass = { + "global", + JSCLASS_GLOBAL_FLAGS, + &global_classOps}; + +void reportWarning(JSContext *cx, JSErrorReport *report) { + MOZ_RELEASE_ASSERT(report); + // MOZ_RELEASE_ASSERT(report->isWarning()); + + SE_LOGE("%s:%u:%s\n", report->filename ? report->filename : "", + (unsigned int)report->lineno, + report->message().c_str()); +} + +void SetStandardCompartmentOptions(JS::RealmOptions &options) { + bool enableSharedMemory = true; + options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory); +} + +bool __forceGC(JSContext *cx, uint32_t argc, JS::Value *vp) { + JS_GC(cx); + return true; +} + +bool __log(JSContext *cx, uint32_t argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (argc > 0) { + JSString *string = JS::ToString(cx, args[0]); + if (string) { + JS::RootedString jsstr(cx, string); + JS::UniqueChars buffer = JS_EncodeStringToUTF8(cx, jsstr); + + SE_LOGD("JS: %s\n", buffer.get()); + } + } + args.rval().setUndefined(); + return true; +} + +// Private data class +bool privateDataContructor(JSContext *cx, uint32_t argc, JS::Value *vp) { + return true; +} + +void privateDataFinalize(JSFreeOp *fop, JSObject *obj) { + internal::PrivateData *p = (internal::PrivateData *)internal::SE_JS_GetPrivate(obj, 0); + if (p == nullptr) + return; + + internal::SE_JS_SetPrivate(obj, 0, p->data); + if (p->finalizeCb != nullptr) + p->finalizeCb(fop, obj); + free(p); +} + +// ------------------------------------------------------- ScriptEngine + +void on_garbage_collect(JSContext *cx, JSGCStatus status, JS::GCReason reason, void *data) { + /* We finalize any pending toggle refs before doing any garbage collection, + * so that we can collect the JS wrapper objects, and in order to minimize + * the chances of objects having a pending toggle up queued when they are + * garbage collected. */ + if (status == JSGC_BEGIN) { + ScriptEngine::getInstance()->_setGarbageCollecting(true); + SE_LOGD("on_garbage_collect: begin, Native -> JS map count: %d, all objects: %d\n", (int)NativePtrToObjectMap::size(), (int)__objectMap.size()); + } else if (status == JSGC_END) { + SE_LOGD("on_garbage_collect: end, Native -> JS map count: %d, all objects: %d\n", (int)NativePtrToObjectMap::size(), (int)__objectMap.size()); + ScriptEngine::getInstance()->_setGarbageCollecting(false); + } +} + +std::string removeFileExt(const std::string &filePath) { + size_t pos = filePath.rfind('.'); + if (0 < pos) { + return filePath.substr(0, pos); + } + return filePath; +} + +bool getBytecodeBuildId(JS::BuildIdCharVector *buildId) { + // The browser embeds the date into the buildid and the buildid is embedded + // in the binary, so every 'make' necessarily builds a new firefox binary. + // Fortunately, the actual firefox executable is tiny -- all the code is in + // libxul.so and other shared modules -- so this isn't a big deal. Not so + // for the statically-linked JS shell. To avoid recompiling js.cpp and + // re-linking 'js' on every 'make', we use a constant buildid and rely on + // the shell user to manually clear any caches between cache-breaking updates. + const char buildid[] = "cocos_xdr"; + return buildId->append(buildid, sizeof(buildid)); +} + +// For console stuff +bool JSB_console_format_log(State &s, const char *prefix, int msgIndex = 0) { + if (msgIndex < 0) + return false; + + const auto &args = s.args(); + int argc = (int)args.size(); + if ((argc - msgIndex) == 1) { + std::string msg = args[msgIndex].toStringForce(); + SE_LOGD("JS: %s%s\n", prefix, msg.c_str()); + } else if (argc > 1) { + std::string msg = args[msgIndex].toStringForce(); + size_t pos; + for (int i = (msgIndex + 1); i < argc; ++i) { + pos = msg.find("%"); + if (pos != std::string::npos && pos != (msg.length() - 1) && (msg[pos + 1] == 'd' || msg[pos + 1] == 's' || msg[pos + 1] == 'f')) { + msg.replace(pos, 2, args[i].toStringForce()); + } else { + msg += " " + args[i].toStringForce(); + } + } + + SE_LOGD("JS: %s%s\n", prefix, msg.c_str()); + } + + return true; +} + +bool JSB_console_log(State &s) { + JSB_console_format_log(s, ""); + return true; +} +SE_BIND_FUNC(JSB_console_log) + +bool JSB_console_debug(State &s) { + JSB_console_format_log(s, "[DEBUG]: "); + return true; +} +SE_BIND_FUNC(JSB_console_debug) + +bool JSB_console_info(State &s) { + JSB_console_format_log(s, "[INFO]: "); + return true; +} +SE_BIND_FUNC(JSB_console_info) + +bool JSB_console_warn(State &s) { + JSB_console_format_log(s, "[WARN]: "); + return true; +} +SE_BIND_FUNC(JSB_console_warn) + +bool JSB_console_error(State &s) { + JSB_console_format_log(s, "[ERROR]: "); + return true; +} +SE_BIND_FUNC(JSB_console_error) + +bool JSB_console_assert(State &s) { + const auto &args = s.args(); + if (!args.empty()) { + if (args[0].isBoolean() && !args[0].toBoolean()) { + JSB_console_format_log(s, "[ASSERT]: ", 1); + } + } + return true; +} +SE_BIND_FUNC(JSB_console_assert) + +bool JSB_console_time(State &s) { + return true; //TODO(cjh) +} +SE_BIND_FUNC(JSB_console_time) + +bool JSB_console_timeEnd(State &s) { + return true; //TODO(cjh) +} +SE_BIND_FUNC(JSB_console_timeEnd) + +} // namespace + +AutoHandleScope::AutoHandleScope() { +} + +AutoHandleScope::~AutoHandleScope() { + js::RunJobs(se::ScriptEngine::getInstance()->_getContext()); +} + +ScriptEngine *ScriptEngine::getInstance() { + if (__instance == nullptr) { + __instance = ccnew ScriptEngine(); + } + + return __instance; +} + +void ScriptEngine::destroyInstance() { + delete __instance; + __instance = nullptr; +} + +ScriptEngine::ScriptEngine() +: _cx(nullptr), + _globalObj(nullptr), + _debugGlobalObj(nullptr), + _oldCompartment(nullptr), + _exceptionCallback(nullptr), + _debuggerServerPort(0), + _vmId(0), + _isGarbageCollecting(false), + _isValid(false), + _isInCleanup(false), + _isErrorHandleWorking(false) { + bool ok = JS_Init(); + assert(ok); +} + +/* static */ +void ScriptEngine::onWeakPointerCompartmentCallback(JSTracer *trc, JS::Compartment *comp, void *data) { + onWeakPointerZoneGroupCallback(trc, data); +} + +/* static */ +void ScriptEngine::onWeakPointerZoneGroupCallback(JSTracer *trc, void *data) { + bool isInCleanup = getInstance()->isInCleanup(); + bool isIterUpdated = false; + Object *obj = nullptr; + auto iter = NativePtrToObjectMap::begin(); + while (iter != NativePtrToObjectMap::end()) { + obj = iter->second; + isIterUpdated = false; + if (!obj->isRooted()) { + if (obj->updateAfterGC(trc, data)) { + obj->decRef(); + iter = NativePtrToObjectMap::erase(iter); + isIterUpdated = true; + } + } else if (isInCleanup) // Rooted and in cleanup step + { + obj->unprotect(); + obj->decRef(); + iter = NativePtrToObjectMap::erase(iter); + isIterUpdated = true; + } + + if (!isIterUpdated) + ++iter; + } +} + +bool ScriptEngine::init() { + cleanup(); + SE_LOGD("Initializing SpiderMonkey, version: %s\n", JS_GetImplementationVersion()); + ++_vmId; + + for (const auto &hook : _beforeInitHookArray) { + hook(); + } + _beforeInitHookArray.clear(); + + _cx = JS_NewContext(JS::DefaultHeapMaxBytes); + + if (nullptr == _cx) + return false; + + NativePtrToObjectMap::init(); + + Class::setContext(_cx); + Object::setContext(_cx); + + js::UseInternalJobQueues(_cx); + + JS_SetGCParameter(_cx, JSGC_MAX_BYTES, 0xffffffff); + JS_SetGCParameter(_cx, JSGC_INCREMENTAL_GC_ENABLED, 1); + JS_SetGCParameter(_cx, JSGC_PER_ZONE_GC_ENABLED, 1); + JS_SetGCParameter(_cx, JSGC_COMPACTING_ENABLED, 1); + + JS_SetNativeStackQuota(_cx, 5000000); + + JS_SetOffthreadIonCompilationEnabled(_cx, true); + JS_SetGlobalJitCompilerOption(_cx, JSJITCOMPILER_ION_ENABLE, 1); + JS_SetGlobalJitCompilerOption(_cx, JSJITCOMPILER_BASELINE_ENABLE, 1); + + JS_SetGCCallback(_cx, on_garbage_collect, nullptr); + + if (!JS::InitSelfHostedCode(_cx)) + return false; + + // Waiting is allowed on the shell's main thread, for now. + JS_SetFutexCanWait(_cx); + + JS::SetWarningReporter(_cx, reportWarning); + + #if defined(JS_GC_ZEAL) && defined(DEBUG) + // JS_SetGCZeal(_cx, 2, JS_DEFAULT_ZEAL_FREQ); + #endif + + JS::RealmOptions options; + SetStandardCompartmentOptions(options); + + #ifdef DEBUG + JS::ContextOptionsRef(_cx) + .setDisableIon() + .setWasmBaseline(false) + .setAsmJS(false) + .setWasm(false) + .setWasmIon(false); + #else + JS::ContextOptionsRef(_cx) + .setAsmJS(true) + .setWasm(true) + .setWasmBaseline(true) + .setWasmIon(true); + #endif + + JS::RootedObject globalObj(_cx, JS_NewGlobalObject(_cx, &__globalClass, nullptr, JS::DontFireOnNewGlobalHook, options)); + + if (nullptr == globalObj) + return false; + + _globalObj = Object::_createJSObject(nullptr, globalObj); + _globalObj->root(); + + JS::RootedObject rootedGlobalObj(_cx, _globalObj->_getJSObject()); + + _oldCompartment = JS::EnterRealm(_cx, rootedGlobalObj); + JS::InitRealmStandardClasses(_cx); + + _globalObj->setProperty("window", Value(_globalObj)); + + // SpiderMonkey isn't shipped with a console variable. Make a fake one. + Value consoleVal; + bool hasConsole = _globalObj->getProperty("console", &consoleVal) && consoleVal.isObject(); + assert(!hasConsole); + + HandleObject consoleObj(Object::createPlainObject()); + consoleObj->defineFunction("log", _SE(JSB_console_log)); + consoleObj->defineFunction("debug", _SE(JSB_console_debug)); + consoleObj->defineFunction("info", _SE(JSB_console_info)); + consoleObj->defineFunction("warn", _SE(JSB_console_warn)); + consoleObj->defineFunction("error", _SE(JSB_console_error)); + consoleObj->defineFunction("assert", _SE(JSB_console_assert)); + consoleObj->defineFunction("time", _SE(JSB_console_info)); //TODO(cjh) + consoleObj->defineFunction("timeEnd", _SE(JSB_console_info)); //TODO(cjh) + + _globalObj->setProperty("console", Value(consoleObj)); + + _globalObj->setProperty("scriptEngineType", Value("SpiderMonkey")); + + JS_DefineFunction(_cx, rootedGlobalObj, "log", __log, 0, JSPROP_PERMANENT); + JS_DefineFunction(_cx, rootedGlobalObj, "forceGC", __forceGC, 0, JSPROP_READONLY | JSPROP_PERMANENT); + + JS_FireOnNewGlobalObject(_cx, rootedGlobalObj); + JS::SetProcessBuildIdOp(getBytecodeBuildId); + + _isValid = true; + + for (const auto &hook : _afterInitHookArray) { + hook(); + } + _afterInitHookArray.clear(); + + return true; +} + +ScriptEngine::~ScriptEngine() { + cleanup(); + JS_ShutDown(); +} + +void ScriptEngine::cleanup() { + if (!_isValid) + return; + + _isInCleanup = true; + for (const auto &hook : _beforeCleanupHookArray) { + hook(); + } + _beforeCleanupHookArray.clear(); + + SAFE_DEC_REF(_globalObj); + Class::cleanup(); + Object::cleanup(); + + JS::LeaveRealm(_cx, _oldCompartment); + + JS_DestroyContext(_cx); + + _cx = nullptr; + _globalObj = nullptr; + _oldCompartment = nullptr; + _isValid = false; + + _registerCallbackArray.clear(); + + _filenameScriptMap.clear(); + + for (const auto &hook : _afterCleanupHookArray) { + hook(); + } + _afterCleanupHookArray.clear(); + _isInCleanup = false; + + NativePtrToObjectMap::destroy(); +} + +void ScriptEngine::addBeforeCleanupHook(const std::function &hook) { + _beforeCleanupHookArray.push_back(hook); +} + +void ScriptEngine::addAfterCleanupHook(const std::function &hook) { + _afterCleanupHookArray.push_back(hook); +} + +void ScriptEngine::addBeforeInitHook(const std::function &hook) { + _beforeInitHookArray.push_back(hook); +} + +void ScriptEngine::addAfterInitHook(const std::function &hook) { + _afterInitHookArray.push_back(hook); +} + +bool ScriptEngine::isGarbageCollecting() { + return _isGarbageCollecting; +} + +void ScriptEngine::_setGarbageCollecting(bool isGarbageCollecting) { + _isGarbageCollecting = isGarbageCollecting; +} + +Object *ScriptEngine::getGlobalObject() { + return _globalObj; +} + +void ScriptEngine::addRegisterCallback(RegisterCallback cb) { + assert(std::find(_registerCallbackArray.begin(), _registerCallbackArray.end(), cb) == _registerCallbackArray.end()); + _registerCallbackArray.push_back(cb); +} + +void ScriptEngine::addPermanentRegisterCallback(RegisterCallback cb) { + if (std::find(_permRegisterCallbackArray.begin(), _permRegisterCallbackArray.end(), cb) == _permRegisterCallbackArray.end()) { + _permRegisterCallbackArray.push_back(cb); + } +} + + #pragma mark - Debug + +static std::string inData; +static std::string outData; +static ccstd::vector g_queue; +static std::mutex g_qMutex; +static std::mutex g_rwMutex; +static int clientSocket = -1; +static uint32_t s_nestedLoopLevel = 0; + +static void cc_closesocket(int fd) { + #ifdef _WIN32 + closesocket(fd); + #else + close(fd); + #endif +} + +void ScriptEngine::_debugProcessInput(const std::string &str) { + JS::RootedObject debugGlobal(_cx, _debugGlobalObj->_getJSObject()); + JS::Realm *globalCpt = JS::EnterRealm(_cx, debugGlobal); + + Value func; + if (_debugGlobalObj->getProperty("processInput", &func) && func.isObject() && func.toObject()->isFunction()) { + ValueArray args; + args.push_back(Value(str)); + func.toObject()->call(args, _debugGlobalObj); + } + + JS::LeaveRealm(_cx, globalCpt); +} + +static bool NS_ProcessNextEvent() { + std::string message; + size_t messageCount = 0; + while (true) { + g_qMutex.lock(); + messageCount = g_queue.size(); + if (messageCount > 0) { + auto first = g_queue.begin(); + message = *first; + g_queue.erase(first); + --messageCount; + } + g_qMutex.unlock(); + + if (!message.empty()) { + ScriptEngine::getInstance()->_debugProcessInput(message); + } + + if (messageCount == 0) + break; + } + // std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + return true; +} + +static bool JSBDebug_enterNestedEventLoop(State &s) { + enum { + NS_OK = 0, + NS_ERROR_UNEXPECTED + }; + + #define NS_SUCCEEDED(v) ((v) == NS_OK) + + int rv = NS_OK; + + uint32_t nestLevel = ++s_nestedLoopLevel; + + while (NS_SUCCEEDED(rv) && s_nestedLoopLevel >= nestLevel) { + js::RunJobs(se::ScriptEngine::getInstance()->_getContext()); + if (!NS_ProcessNextEvent()) + rv = NS_ERROR_UNEXPECTED; + } + + assert(s_nestedLoopLevel <= nestLevel); + + s.rval().setInt32(s_nestedLoopLevel); + return true; +} +SE_BIND_FUNC(JSBDebug_enterNestedEventLoop) + +static bool JSBDebug_exitNestedEventLoop(State &s) { + if (s_nestedLoopLevel > 0) { + --s_nestedLoopLevel; + } else { + s.rval().setInt32(0); + return true; + } + return true; +} +SE_BIND_FUNC(JSBDebug_exitNestedEventLoop) + +static bool JSBDebug_getEventLoopNestLevel(State &s) { + s.rval().setInt32(s_nestedLoopLevel); + return true; +} +SE_BIND_FUNC(JSBDebug_getEventLoopNestLevel) + +static void _clientSocketWriteAndClearString(std::string &s) { + ::send(clientSocket, s.c_str(), s.length(), 0); + s.clear(); +} + +static void processInput(const std::string &data) { + std::lock_guard lk(g_qMutex); + g_queue.push_back(data); +} + +static void clearBuffers() { + std::lock_guard lk(g_rwMutex); + // only process input if there's something and we're not locked + if (!inData.empty()) { + processInput(inData); + inData.clear(); + } + if (!outData.empty()) { + _clientSocketWriteAndClearString(outData); + } +} + +static void serverEntryPoint(uint32_t port) { + // start a server, accept the connection and keep reading data from it + struct addrinfo hints, *result = nullptr, *rp = nullptr; + int s = 0; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; // TCP stream sockets + hints.ai_flags = AI_PASSIVE; // fill in my IP for me + + std::stringstream portstr; + portstr << port; + + int err = 0; + + #ifdef _WIN32 + WSADATA wsaData; + err = WSAStartup(MAKEWORD(2, 2), &wsaData); + #endif + + if ((err = getaddrinfo(NULL, portstr.str().c_str(), &hints, &result)) != 0) { + SE_LOGD("getaddrinfo error : %s\n", gai_strerror(err)); + } + + for (rp = result; rp != NULL; rp = rp->ai_next) { + if ((s = socket(rp->ai_family, rp->ai_socktype, 0)) < 0) { + continue; + } + int optval = 1; + if ((setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(optval))) < 0) { + cc_closesocket(s); + TRACE_DEBUGGER_SERVER("debug server : error setting socket option SO_REUSEADDR\n"); + return; + } + + #ifdef __APPLE__ + if ((setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval))) < 0) { + close(s); + TRACE_DEBUGGER_SERVER("debug server : error setting socket option SO_NOSIGPIPE\n"); + return; + } + #endif + + if ((::bind(s, rp->ai_addr, rp->ai_addrlen)) == 0) { + break; + } + cc_closesocket(s); + s = -1; + } + if (s < 0 || rp == NULL) { + TRACE_DEBUGGER_SERVER("debug server : error creating/binding socket\n"); + return; + } + + freeaddrinfo(result); + + listen(s, 1); + + #define MAX_RECEIVED_SIZE 1024 + #define BUF_SIZE MAX_RECEIVED_SIZE + 1 + + char buf[BUF_SIZE] = {0}; + int readBytes = 0; + while (true) { + clientSocket = accept(s, NULL, NULL); + + if (clientSocket < 0) { + TRACE_DEBUGGER_SERVER("debug server : error on accept\n"); + return; + } else { + // read/write data + TRACE_DEBUGGER_SERVER("debug server : client connected\n"); + + inData = "connected"; + // process any input, send any output + clearBuffers(); + + while ((readBytes = (int)::recv(clientSocket, buf, MAX_RECEIVED_SIZE, 0)) > 0) { + buf[readBytes] = '\0'; + // TRACE_DEBUGGER_SERVER("debug server : received command >%s", buf); + + // no other thread is using this + inData.append(buf); + // process any input, send any output + clearBuffers(); + } // while(read) + + cc_closesocket(clientSocket); + } + } // while(true) + + #undef BUF_SIZE + #undef MAX_RECEIVED_SIZE +} + +static bool JSBDebug_require(State &s) { + const auto &args = s.args(); + int argc = (int)args.size(); + + if (argc >= 1) { + ScriptEngine::getInstance()->runScript(args[0].toString()); + return true; + } + + SE_REPORT_ERROR("Wrong number of arguments: %d, expected: %d", argc, 1); + return false; +} +SE_BIND_FUNC(JSBDebug_require) + +static bool JSBDebug_BufferWrite(State &s) { + const auto &args = s.args(); + int argc = (int)args.size(); + if (argc == 1) { + // this is safe because we're already inside a lock (from clearBuffers) + outData.append(args[0].toString()); + _clientSocketWriteAndClearString(outData); + } + return true; +} +SE_BIND_FUNC(JSBDebug_BufferWrite) + +bool ScriptEngine::start() { + if (!init()) + return false; + + if (isDebuggerEnabled() && _debugGlobalObj == nullptr) { + JS::RealmOptions options; + options.creationOptions().setSharedMemoryAndAtomicsEnabled(true); + + JS::RootedObject debugGlobal(_cx, JS_NewGlobalObject(_cx, &__globalClass, nullptr, JS::DontFireOnNewGlobalHook, options)); + _debugGlobalObj = Object::_createJSObject(nullptr, debugGlobal); + _debugGlobalObj->root(); + + JS::Realm *globalCpt = JS::EnterRealm(_cx, debugGlobal); + JS::InitRealmStandardClasses(_cx); + JS_FireOnNewGlobalObject(_cx, debugGlobal); + JS_DefineDebuggerObject(_cx, debugGlobal); + + // these are used in the debug program + JS_DefineFunction(_cx, debugGlobal, "log", __log, 0, JSPROP_PERMANENT); + _debugGlobalObj->defineFunction("require", _SE(JSBDebug_require)); + _debugGlobalObj->defineFunction("_bufferWrite", _SE(JSBDebug_BufferWrite)); + _debugGlobalObj->defineFunction("_enterNestedEventLoop", _SE(JSBDebug_enterNestedEventLoop)); + _debugGlobalObj->defineFunction("_exitNestedEventLoop", _SE(JSBDebug_exitNestedEventLoop)); + _debugGlobalObj->defineFunction("_getEventLoopNestLevel", _SE(JSBDebug_getEventLoopNestLevel)); + + JS::RootedObject globalObj(_cx, _globalObj->_getJSObject()); + JS_WrapObject(_cx, &globalObj); + + runScript("script/jsb_debugger.js"); + + // prepare the debugger + Value prepareDebuggerFunc; + assert(_debugGlobalObj->getProperty("_prepareDebugger", &prepareDebuggerFunc) && prepareDebuggerFunc.isObject() && prepareDebuggerFunc.toObject()->isFunction()); + + ValueArray args; + args.push_back(Value(_globalObj)); + prepareDebuggerFunc.toObject()->call(args, _debugGlobalObj); + + // start bg thread + auto t = std::thread(&serverEntryPoint, _debuggerServerPort); + t.detach(); + + JS::LeaveRealm(_cx, globalCpt); + } + + bool ok = false; + _startTime = std::chrono::steady_clock::now(); + + for (auto cb : _permRegisterCallbackArray) { + ok = cb(_globalObj); + assert(ok); + if (!ok) { + break; + } + } + + for (auto cb : _registerCallbackArray) { + ok = cb(_globalObj); + assert(ok); + if (!ok) + break; + } + + // After ScriptEngine is started, _registerCallbackArray isn't needed. Therefore, clear it here. + _registerCallbackArray.clear(); + return ok; +} + +bool ScriptEngine::getScript(const std::string &path, JS::MutableHandleScript script) { + std::string fullPath = _fileOperationDelegate.onGetFullPath(path); + auto iter = _filenameScriptMap.find(fullPath); + if (iter != _filenameScriptMap.end()) { + JS::PersistentRootedScript *rootedScript = iter->second; + script.set(rootedScript->get()); + return true; + } + + // // a) check jsc file first + // std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT; + // if (_filenameScriptMap.find(byteCodePath) != _filenameScriptMap.end()) + // { + // script.set(_filenameScriptMap[byteCodePath]->get()); + // return true; + // } + // + // // b) no jsc file, check js file + // if (_filenameScriptMap.find(path) != _filenameScriptMap.end()) + // { + // script.set(_filenameScriptMap[path]->get()); + // return true; + // } + + return false; +} + +bool ScriptEngine::compileScript(const std::string &path, JS::MutableHandleScript script) { + if (path.empty()) + return false; + + bool ok = getScript(path, script); + if (ok) + return true; + + assert(_fileOperationDelegate.isValid()); + + bool compileSucceed = false; + + // Creator v1.7 supports v8, spidermonkey, javascriptcore and chakracore as its script engine, + // jsc file isn't bytecode format anymore, it's a xxtea encrpted binary format instead. + // Therefore, for unifying the flow, ScriptEngine class will not support spidermonkey bytecode. + // // a) check jsc file first + // std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT; + // + // // Check whether '.jsc' files exist to avoid outputting log which says 'couldn't find .jsc file'. + // if (_fileOperationDelegate.onCheckFileExist(byteCodePath)) + // { + // _fileOperationDelegate.onGetDataFromFile(byteCodePath, [&](const uint8_t* data, size_t dataLen) { + // if (data != nullptr && dataLen > 0) + // { + // JS::TranscodeBuffer buffer; + // bool appended = buffer.append(data, dataLen); + // JS::TranscodeResult result = JS::DecodeScript(_cx, buffer, script); + // if (appended && result == JS::TranscodeResult::TranscodeResult_Ok) + // { + // compileSucceed = true; + // _filenameScriptMap[byteCodePath] = ccnew JS::PersistentRootedScript(_cx, script.get()); + // } + // assert(compileSucceed); + // } + // }); + // + // } + + // b) no jsc file, check js file + if (!compileSucceed) { + /* Clear any pending exception from previous failed decoding. */ + clearException(); + + std::string jsFileContent = _fileOperationDelegate.onGetStringFromFile(path); + if (!jsFileContent.empty()) { + JS::CompileOptions op(_cx); + op.setFileAndLine(path.c_str(), 1); + + JS::SourceText srcBuf; + bool succeed = srcBuf.init(_cx, jsFileContent.c_str(), jsFileContent.length(), + JS::SourceOwnership::Borrowed); + + assert(succeed); + JSScript *compiledScript = JS::Compile(_cx, op, srcBuf); + if (compiledScript != nullptr) { + compileSucceed = true; + script.set(compiledScript); + std::string fullPath = _fileOperationDelegate.onGetFullPath(path); + _filenameScriptMap[fullPath] = ccnew JS::PersistentRootedScript(_cx, script.get()); + } + assert(compileSucceed); + } + } + + clearException(); + + if (!compileSucceed) { + SE_LOGD("ScriptEngine::compileScript fail:%s\n", path.c_str()); + } + + return compileSucceed; +} + +bool ScriptEngine::evalString(const char *script, ssize_t length /* = -1 */, Value *ret /* = nullptr */, const char *fileName /* = nullptr */) { + assert(script != nullptr); + + if (length < 0) + length = strlen(script); + + if (fileName == nullptr) + fileName = "(no filename)"; + + JS::CompileOptions options(_cx); + options.setFile(fileName); + + JS::RootedValue rval(_cx); + + JS::SourceText srcBuf; + bool succeed = srcBuf.init(_cx, script, length, JS::SourceOwnership::Borrowed); + assert(succeed); + + bool ok = JS::Evaluate(_cx, options, srcBuf, &rval); + if (!ok) { + clearException(); + } + assert(ok); + + if (ok && ret != nullptr && !rval.isNullOrUndefined()) { + internal::jsToSeValue(_cx, rval, ret); + } + + if (!ok) { + SE_LOGE("ScriptEngine::evalString script %s, failed!\n", fileName); + } + return ok; +} + +void ScriptEngine::setFileOperationDelegate(const FileOperationDelegate &delegate) { + _fileOperationDelegate = delegate; +} + +const ScriptEngine::FileOperationDelegate &ScriptEngine::getFileOperationDelegate() const { + return _fileOperationDelegate; +} + +bool ScriptEngine::runScript(const std::string &path, Value *ret /* = nullptr */) { + assert(_fileOperationDelegate.isValid()); + + JS::RootedScript script(_cx); + bool ok = compileScript(path, &script); + if (ok) { + JS::RootedValue rval(_cx); + ok = JS_ExecuteScript(_cx, script, &rval); + if (!ok) { + SE_LOGE("Evaluating %s failed (evaluatedOK == JS_FALSE)\n", path.c_str()); + clearException(); + } + + if (ok && ret != nullptr && !rval.isNullOrUndefined()) { + internal::jsToSeValue(_cx, rval, ret); + } + } + + return ok; +} + +void ScriptEngine::clearException() { + if (_cx == nullptr) + return; + + if (JS_IsExceptionPending(_cx)) { + JS::RootedValue exceptionValue(_cx); + JS_GetPendingException(_cx, &exceptionValue); + JS_ClearPendingException(_cx); + + assert(exceptionValue.isObject()); + JS::RootedObject exceptionObj(_cx, exceptionValue.toObjectOrNull()); + JSErrorReport *report = JS_ErrorFromException(_cx, exceptionObj); + const char *message = report->message().c_str(); + const std::string filePath = report->filename != nullptr ? report->filename : "(no filename)"; + char line[50] = {0}; + snprintf(line, sizeof(line), "%u", report->lineno); + char column[50] = {0}; + snprintf(column, sizeof(column), "%u", report->column); + const std::string location = filePath + ":" + line + ":" + column; + + JS::UniqueChars stack; + + JS::RootedValue stackVal(_cx); + if (JS_GetProperty(_cx, exceptionObj, "stack", &stackVal) && stackVal.isString()) { + JS::RootedString jsstackStr(_cx, stackVal.toString()); + stack = JS_EncodeStringToUTF8(_cx, jsstackStr); + } + + std::string exceptionStr = message; + exceptionStr += ", location: " + location; + if (stack != nullptr) { + exceptionStr += "\nSTACK:\n"; + exceptionStr += stack.get(); + } + + SE_LOGE("ERROR: %s\n", exceptionStr.c_str()); + if (_exceptionCallback != nullptr) { + _exceptionCallback(location.c_str(), message, stack.get()); + } + + if (!_isErrorHandleWorking) { + _isErrorHandleWorking = true; + + Value errorHandler; + if (_globalObj->getProperty("__errorHandler", &errorHandler) && errorHandler.isObject() && errorHandler.toObject()->isFunction()) { + ValueArray args; + args.push_back(Value(filePath)); + args.push_back(Value(report->lineno)); + args.push_back(Value(message)); + if (stack) { + args.push_back(Value(stack.get())); + } else { + args.push_back(Value("")); + } + errorHandler.toObject()->call(args, _globalObj); + } + + _isErrorHandleWorking = false; + } else { + SE_LOGE("ERROR: __errorHandler has exception\n"); + } + } +} + +void ScriptEngine::setExceptionCallback(const ExceptionCallback &cb) { + _exceptionCallback = cb; +} + +void ScriptEngine::setJSExceptionCallback(const ExceptionCallback &cb) { //TODO(cjh) +} + +void ScriptEngine::enableDebugger(const std::string &serverAddr, uint32_t port, bool isWait) { + _debuggerServerAddr = serverAddr; + _debuggerServerPort = port; +} + +bool ScriptEngine::isDebuggerEnabled() const { + return !_debuggerServerAddr.empty() && _debuggerServerPort > 0; +} + +void ScriptEngine::mainLoopUpdate() { + js::RunJobs(_cx); + + if (!isDebuggerEnabled()) { + return; + } + std::string message; + size_t messageCount = 0; + while (true) { + g_qMutex.lock(); + messageCount = g_queue.size(); + if (messageCount > 0) { + auto first = g_queue.begin(); + message = *first; + g_queue.erase(first); + --messageCount; + } + g_qMutex.unlock(); + + if (!message.empty()) { + _debugProcessInput(message); + } + + if (messageCount == 0) + break; + } +} + +bool ScriptEngine::callFunction(Object *targetObj, const char *funcName, uint32_t argc, Value *args, Value *rval /* = nullptr*/) { + JS::RootedValueVector jsarr(_cx); + if (!jsarr.reserve(argc)) { + SE_LOGE("ScriptEngine::callFunction out of memory!"); + return false; + } + + for (size_t i = 0; i < argc; ++i) { + JS::RootedValue jsval(_cx); + internal::seToJsValue(_cx, args[i], &jsval); + jsarr.append(jsval); + } + + JS::RootedObject contextObject(_cx); + if (targetObj != nullptr) { + contextObject.set(targetObj->_getJSObject()); + } + + bool found = false; + bool ok = JS_HasProperty(_cx, contextObject, funcName, &found); + + if (!ok || !found) { + return false; + } + + JS::RootedValue funcValue(_cx); + ok = JS_GetProperty(_cx, contextObject, funcName, &funcValue); + + if (!ok) { + return false; + } + + JS::RootedValue rcValue(_cx); + JSAutoRealm autoRealm(_cx, funcValue.toObjectOrNull()); + ok = JS_CallFunctionValue(_cx, contextObject, funcValue, jsarr, &rcValue); + + if (ok) { + if (rval != nullptr) + internal::jsToSeValue(_cx, rcValue, rval); + } else { + se::ScriptEngine::getInstance()->clearException(); + } + + return ok; +} + +void ScriptEngine::handlePromiseExceptions() { + clearException(); +} //TODO(cjh) + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM diff --git a/cocos/bindings/jswrapper/sm/ScriptEngine.h b/cocos/bindings/jswrapper/sm/ScriptEngine.h new file mode 100644 index 0000000..a02eae9 --- /dev/null +++ b/cocos/bindings/jswrapper/sm/ScriptEngine.h @@ -0,0 +1,347 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../config.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + + #include "Base.h" + +namespace se { + +class Object; +class Class; +class Value; + +/** + * A stack-allocated class that governs a number of local handles. + * It's only implemented for v8 wrapper now. + * Other script engine wrappers have empty implementation for this class. + * It's used at the beginning of executing any wrapper API. + */ +class AutoHandleScope { +public: + AutoHandleScope(); + ~AutoHandleScope(); +}; + +/** + * ScriptEngine is a sington which represents a context of JavaScript VM. + */ +class ScriptEngine final { +public: + /** + * @brief Gets or creates the instance of script engine. + * @return The script engine instance. + */ + static ScriptEngine *getInstance(); + + /** + * @brief Destroys the instance of script engine. + */ + static void destroyInstance(); + + /** + * @brief Gets the global object of JavaScript VM. + * @return The se::Object stores the global JavaScript object. + */ + Object *getGlobalObject(); + + typedef bool (*RegisterCallback)(Object *); + + /** + * @brief Adds a callback for registering a native binding module. + * @param[in] cb A callback for registering a native binding module. + * @note This method just add a callback to a vector, callbacks is invoked in `start` method. + */ + void addRegisterCallback(RegisterCallback cb); + + /** + * @brief Adds a callback for registering a native binding module, which will not be removed by ScriptEngine::cleanup. + * @param[in] cb A callback for registering a native binding module. + * @note This method just add a callback to a vector, callbacks is invoked in `start` method. + */ + void addPermanentRegisterCallback(RegisterCallback cb); + + /** + * @brief Starts the script engine. + * @return true if succeed, otherwise false. + * @note This method will invoke all callbacks of native binding modules by the order of registration. + */ + bool start(); + + /** + * @brief Initializes script engine. + * @return true if succeed, otherwise false. + * @note This method will create JavaScript context and global object. + */ + bool init(); + + /** + * @brief Adds a hook function before initializing script engine. + * @param[in] hook A hook function to be invoked before initializing script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addBeforeInitHook(const std::function &hook); + + /** + * @brief Adds a hook function after initializing script engine. + * @param[in] hook A hook function to be invoked before initializing script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addAfterInitHook(const std::function &hook); + + /** + * @brief Cleanups script engine. + * @note This method will removes all objects in JavaScript VM even whose are rooted, then shutdown JavaScript VMf. + */ + void cleanup(); + + /** + * @brief Adds a hook function before cleanuping script engine. + * @param[in] hook A hook function to be invoked before cleanuping script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addBeforeCleanupHook(const std::function &hook); + + /** + * @brief Adds a hook function after cleanuping script engine. + * @param[in] hook A hook function to be invoked after cleanuping script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addAfterCleanupHook(const std::function &hook); + + /** + * @brief Executes a utf-8 string buffer which contains JavaScript code. + * @param[in] scriptStr A utf-8 string buffer, if it isn't null-terminated, parameter `length` should be assigned and > 0. + * @param[in] length The length of parameter `scriptStr`, it will be set to string length internally if passing < 0 and parameter `scriptStr` is null-terminated. + * @param[in] rval The se::Value that results from evaluating script. Passing nullptr if you don't care about the result. + * @param[in] fileName A string containing a URL for the script's source file. This is used by debuggers and when reporting exceptions. Pass NULL if you do not care to include source file information. + * @return true if succeed, otherwise false. + */ + bool evalString(const char *scriptStr, ssize_t length = -1, Value *rval = nullptr, const char *fileName = nullptr); + + /** + * @brief Compile script file into v8::ScriptCompiler::CachedData and save to file. + * @param[in] path The path of script file. + * @param[in] pathBc The location where bytecode file should be written to. The path should be ends with ".bc", which indicates a bytecode file. + * @return true if succeed, otherwise false. + */ + bool saveByteCodeToFile(const std::string &path, const std::string &pathBc) { + assert(false); + return false; + } //cjh + + /** + * @brief Grab a snapshot of the current JavaScript execution stack. + * @return current stack trace string + */ + std::string getCurrentStackTrace() { return ""; } //cjh + + /** + * Delegate class for file operation + */ + class FileOperationDelegate { + public: + FileOperationDelegate() + : onGetDataFromFile(nullptr), + onGetStringFromFile(nullptr), + onCheckFileExist(nullptr), + onGetFullPath(nullptr) {} + + bool isValid() const { + return onGetDataFromFile != nullptr && onGetStringFromFile != nullptr && onCheckFileExist != nullptr && onGetFullPath != nullptr; + } + + // path, buffer, buffer size + std::function &)> onGetDataFromFile; + // path, return file string content. + std::function onGetStringFromFile; + // path + std::function onCheckFileExist; + // path, return full path + std::function onGetFullPath; + }; + + /** + * @brief Sets the delegate for file operation. + * @param delegate[in] The delegate instance for file operation. + */ + void setFileOperationDelegate(const FileOperationDelegate &delegate); + + /** + * @brief Gets the delegate for file operation. + * @return The delegate for file operation + */ + const FileOperationDelegate &getFileOperationDelegate() const; + + /** + * @brief Executes a file which contains JavaScript code. + * @param[in] path Script file path. + * @param[in] rval The se::Value that results from evaluating script. Passing nullptr if you don't care about the result. + * @return true if succeed, otherwise false. + */ + bool runScript(const std::string &path, Value *rval = nullptr); + + /** + * @brief Tests whether script engine is doing garbage collection. + * @return true if it's in garbage collection, otherwise false. + */ + bool isGarbageCollecting(); + + /** + * @brief Performs a JavaScript garbage collection. + */ + void garbageCollect() { JS_GC(_cx); } + + /** + * @brief Tests whether script engine is being cleaned up. + * @return true if it's in cleaning up, otherwise false. + */ + bool isInCleanup() { return _isInCleanup; } + + /** + * @brief Tests whether script engine is valid. + * @return true if it's valid, otherwise false. + */ + bool isValid() { return _isValid; } + + /** + * @brief Throw JS exception + */ + void throwException(const std::string &errorMessage) { assert(false); } //cjh + + /** + * @brief Clears all exceptions. + */ + void clearException(); + + using ExceptionCallback = std::function; // location, message, stack + + /** + * @brief Sets the callback function while an exception is fired. + * @param[in] cb The callback function to notify that an exception is fired. + */ + void setExceptionCallback(const ExceptionCallback &cb); + + /** + * @brief Sets the callback function while an exception is fired in JS. + * @param[in] cb The callback function to notify that an exception is fired. + */ + void setJSExceptionCallback(const ExceptionCallback &cb); + + /** + * @brief Gets the start time of script engine. + * @return The start time of script engine. + */ + const std::chrono::steady_clock::time_point &getStartTime() const { return _startTime; } + + /** + * @brief Enables JavaScript debugger + * @param[in] serverAddr The address of debugger server. + * @param[in] port The port of debugger server will use. + * @param[in] isWait Whether wait debugger attach when loading. + */ + void enableDebugger(const std::string &serverAddr, uint32_t port, bool isWait = false); + + /** + * @brief Tests whether JavaScript debugger is enabled + * @return true if JavaScript debugger is enabled, otherwise false. + */ + bool isDebuggerEnabled() const; + + /** + * @brief Main loop update trigger, it's need to invoked in main thread every frame. + */ + void mainLoopUpdate(); + + /** + * @brief Gets script virtual machine instance ID. Default value is 1, increase by 1 if `init` is invoked. + */ + uint32_t getVMId() const { return _vmId; } + + /** + * @brief Fast version of call script function, faster than Object::call + */ + bool callFunction(Object *targetObj, const char *funcName, uint32_t argc, Value *args, Value *rval = nullptr); + + /** + * @brief Handle all exceptions throwed by promise + */ + void handlePromiseExceptions(); + + // Private API used in wrapper + JSContext *_getContext() { return _cx; } + void _setGarbageCollecting(bool isGarbageCollecting); + void _debugProcessInput(const std::string &str); + // +private: + ScriptEngine(); + ~ScriptEngine(); + + static void onWeakPointerCompartmentCallback(JSTracer *trc, JS::Compartment *comp, void *data); + static void onWeakPointerZoneGroupCallback(JSTracer *trc, void *data); + + bool getScript(const std::string &path, JS::MutableHandleScript script); + bool compileScript(const std::string &path, JS::MutableHandleScript script); + + JSContext *_cx; + JS::Realm *_oldCompartment; + + Object *_globalObj; + Object *_debugGlobalObj; + + FileOperationDelegate _fileOperationDelegate; + + ccstd::vector _registerCallbackArray; + ccstd::vector _permRegisterCallbackArray; + std::chrono::steady_clock::time_point _startTime; + + ccstd::vector> _beforeInitHookArray; + ccstd::vector> _afterInitHookArray; + + ccstd::vector> _beforeCleanupHookArray; + ccstd::vector> _afterCleanupHookArray; + + ExceptionCallback _exceptionCallback; + // name ~> JSScript map + std::unordered_map _filenameScriptMap; + + std::string _debuggerServerAddr; + uint32_t _debuggerServerPort; + + uint32_t _vmId; + + bool _isGarbageCollecting; + bool _isValid; + bool _isInCleanup; + bool _isErrorHandleWorking; +}; + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM diff --git a/cocos/bindings/jswrapper/sm/SeApi.h b/cocos/bindings/jswrapper/sm/SeApi.h new file mode 100644 index 0000000..3720219 --- /dev/null +++ b/cocos/bindings/jswrapper/sm/SeApi.h @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../State.h" +#include "../Value.h" +#include "Class.h" +#include "HelperMacros.h" +#include "Object.h" +#include "ScriptEngine.h" +#include "Utils.h" diff --git a/cocos/bindings/jswrapper/sm/Utils.cpp b/cocos/bindings/jswrapper/sm/Utils.cpp new file mode 100644 index 0000000..36cdca6 --- /dev/null +++ b/cocos/bindings/jswrapper/sm/Utils.cpp @@ -0,0 +1,207 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Utils.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + + #include "Class.h" + #include "Object.h" + #include "ScriptEngine.h" + +namespace se { + +namespace internal { + +void *SE_JS_GetPrivate(JSObject *obj, uint32_t slot) { + assert(slot >= 0 && slot < 2); + const auto &v = JS::GetReservedSlot(obj, slot); + return v.isNullOrUndefined() ? nullptr : v.toPrivate(); +} + +void SE_JS_SetPrivate(JSObject *obj, uint32_t slot, void *data) { + assert(slot >= 0 && slot < 2); + JS::SetReservedSlot(obj, slot, JS::PrivateValue(data)); +} + +bool isJSBClass(JSObject *obj) { + const JSClass *cls = JS::GetClass(obj); + return (cls->flags & (JSCLASS_HAS_RESERVED_SLOTS(2)) && (cls->flags & JSCLASS_USERBIT1)); +} + +void forceConvertJsValueToStdString(JSContext *cx, JS::HandleValue jsval, std::string *ret) { + assert(ret != nullptr); + JS::RootedString jsStr(cx, JS::ToString(cx, jsval)); + *ret = jsToStdString(cx, jsStr); +} + +std::string jsToStdString(JSContext *cx, JS::HandleString jsStr) { + JS::UniqueChars str = JS_EncodeStringToUTF8(cx, jsStr); + std::string ret(str.get()); + return ret; +} + +void jsToSeArgs(JSContext *cx, int argc, const JS::CallArgs &argv, ValueArray &outArr) { + for (int i = 0; i < argc; ++i) { + jsToSeValue(cx, argv[i], &outArr[i]); + } +} + +void seToJsArgs(JSContext *cx, const ValueArray &args, JS::RootedValueVector *outArr) { + for (const auto &arg : args) { + JS::RootedValue v(cx); + seToJsValue(cx, arg, &v); + outArr->append(v); + } +} + +void seToJsValue(JSContext *cx, const Value &arg, JS::MutableHandleValue outVal) { + switch (arg.getType()) { + case Value::Type::Number: { + JS::RootedValue value(cx); + value.setDouble(arg.toNumber()); + outVal.set(value); + } break; + + case Value::Type::String: { + JS::UTF8Chars utf8Str(arg.toString().c_str(), arg.toString().length()); + JSString *string = JS_NewStringCopyUTF8N(cx, utf8Str); + JS::RootedValue value(cx); + value.setString(string); + outVal.set(value); + } break; + + case Value::Type::Boolean: { + JS::RootedValue value(cx); + value.setBoolean(arg.toBoolean()); + outVal.set(value); + } break; + + case Value::Type::Object: { + JS::RootedValue value(cx, JS::ObjectValue(*arg.toObject()->_getJSObject())); + outVal.set(value); + } break; + + case Value::Type::Null: { + JS::RootedValue value(cx); + value.setNull(); + outVal.set(value); + } break; + + case Value::Type::Undefined: { + JS::RootedValue value(cx); + value.setUndefined(); + outVal.set(value); + } break; + case Value::Type::BigInt: { + JS::RootedValue value(cx); + JS::BigInt *bi = JS::NumberToBigInt(cx, arg.toUint64()); + outVal.setBigInt(bi); + } break; + default: + assert(false); + break; + } +} + +void jsToSeValue(JSContext *cx, JS::HandleValue jsval, Value *v) { + if (jsval.isNumber()) { + v->setNumber(jsval.toNumber()); + } else if (jsval.isString()) { + JS::RootedString jsstr(cx, jsval.toString()); + v->setString(jsToStdString(cx, jsstr)); + } else if (jsval.isBoolean()) { + v->setBoolean(jsval.toBoolean()); + } else if (jsval.isObject()) { + Object *object = nullptr; + + JS::RootedObject jsobj(cx, jsval.toObjectOrNull()); + PrivateObjectBase *privateObject = static_cast(internal::getPrivate(cx, jsobj, 0)); + void *nativeObj = privateObject ? privateObject->getRaw() : nullptr; + bool needRoot = false; + if (nativeObj != nullptr) { + object = Object::getObjectWithPtr(nativeObj); + } + + if (object == nullptr) { + object = Object::_createJSObject(nullptr, jsobj); + needRoot = true; + } + v->setObject(object, needRoot); + object->decRef(); + } else if (jsval.isNull()) { + v->setNull(); + } else if (jsval.isUndefined()) { + v->setUndefined(); + } else if (jsval.isBigInt()) { + v->setUint64(JS::ToBigUint64(jsval.toBigInt())); + } else { + assert(false); + } +} + +void setReturnValue(JSContext *cx, const Value &data, const JS::CallArgs &argv) { + JS::RootedValue rval(cx); + seToJsValue(cx, data, &rval); + argv.rval().set(rval); +} + +bool hasPrivate(JSContext *cx, JS::HandleObject obj) { + return isJSBClass(obj); +} + +void *getPrivate(JSContext *cx, JS::HandleObject obj, uint32_t slot) { + bool found = isJSBClass(obj); + if (found) { + return SE_JS_GetPrivate(obj, slot); + } + + return nullptr; +} + +void setPrivate(JSContext *cx, JS::HandleObject obj, PrivateObjectBase *data, Object *seObj, PrivateData **outInternalData, JSFinalizeOp finalizeCb) { + bool found = isJSBClass(obj); + assert(found); + if (found) { + SE_JS_SetPrivate(obj, 0, data); + SE_JS_SetPrivate(obj, 1, seObj); + if (outInternalData != nullptr) { + *outInternalData = nullptr; + } + } +} + +void clearPrivate(JSContext *cx, JS::HandleObject obj) { + bool found = isJSBClass(obj); + if (found) { + SE_JS_SetPrivate(obj, 0, nullptr); + SE_JS_SetPrivate(obj, 1, nullptr); + } +} + +} // namespace internal +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM diff --git a/cocos/bindings/jswrapper/sm/Utils.h b/cocos/bindings/jswrapper/sm/Utils.h new file mode 100644 index 0000000..ac4f150 --- /dev/null +++ b/cocos/bindings/jswrapper/sm/Utils.h @@ -0,0 +1,70 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../config.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM + + #include "Base.h" + + #include "../Value.h" + +namespace se { + +class Class; + +namespace internal { + +struct PrivateData { + PrivateObjectBase *data{nullptr}; + Object *seObj{nullptr}; + JSFinalizeOp finalizeCb{nullptr}; +}; + +void forceConvertJsValueToStdString(JSContext *cx, JS::HandleValue jsval, std::string *ret); +std::string jsToStdString(JSContext *cx, JS::HandleString jsStr); + +void jsToSeArgs(JSContext *cx, int argc, const JS::CallArgs &argv, ValueArray &outArr); +void jsToSeValue(JSContext *cx, JS::HandleValue jsval, Value *v); +void seToJsArgs(JSContext *cx, const ValueArray &args, JS::RootedValueVector *outArr); +void seToJsValue(JSContext *cx, const Value &v, JS::MutableHandleValue outVal); + +void setReturnValue(JSContext *cx, const Value &data, const JS::CallArgs &argv); + +bool hasPrivate(JSContext *cx, JS::HandleObject obj); +void *getPrivate(JSContext *cx, JS::HandleObject obj, uint32_t slot); +void setPrivate(JSContext *cx, JS::HandleObject obj, PrivateObjectBase *data, Object *seObj, PrivateData **outInternalData, JSFinalizeOp finalizeCb); +void clearPrivate(JSContext *cx, JS::HandleObject obj); + +void *SE_JS_GetPrivate(JSObject *obj, uint32_t slot); +void SE_JS_SetPrivate(JSObject *obj, uint32_t slot, void *data); + +} // namespace internal + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_SM diff --git a/cocos/bindings/jswrapper/v8/Base.h b/cocos/bindings/jswrapper/v8/Base.h new file mode 100644 index 0000000..c9853c0 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/Base.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "libplatform/libplatform.h" + +//#define V8_DEPRECATION_WARNINGS 1 +//#define V8_IMMINENT_DEPRECATION_WARNINGS 1 +//#define V8_HAS_ATTRIBUTE_DEPRECATED_MESSAGE 1 + +#include "v8.h" + +#include // Resolves that memset, memcpy aren't found while APP_PLATFORM >= 22 on Android +#include // for std::find +#include +#include +#include "../PrivateObject.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/unordered_set.h" + +#include "HelperMacros.h" + +namespace se { +using V8FinalizeFunc = void (*)(Object *seObj); +} // namespace se diff --git a/cocos/bindings/jswrapper/v8/Class.cpp b/cocos/bindings/jswrapper/v8/Class.cpp new file mode 100644 index 0000000..6c35362 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/Class.cpp @@ -0,0 +1,328 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Class.h" +#include +#include "Value.h" +#include "base/Macros.h" +#include "v8/HelperMacros.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + + #include "Object.h" + #include "ScriptEngine.h" + #include "Utils.h" + +namespace { +inline v8::Local createExternal(v8::Isolate *isolate, void *data) { + if (data) { + return v8::External::New(isolate, data); + } + return {}; +} +} // namespace + +namespace se { +// ------------------------------------------------------- Object + +namespace { +// ccstd::unordered_map __clsMap; +v8::Isolate *__isolate = nullptr; // NOLINT +ccstd::vector __allClasses; // NOLINT + +void invalidConstructor(const v8::FunctionCallbackInfo &args) { + v8::Local thisObj = args.This(); + v8::Local constructorName = thisObj->GetConstructorName(); + v8::String::Utf8Value strConstructorName{args.GetIsolate(), constructorName}; + SE_ASSERT(false, "%s 's constructor is not public!", *strConstructorName); // NOLINT(misc-static-assert) +} + +} // namespace + +Class::Class() { + __allClasses.push_back(this); +} + +Class::~Class() = default; + +/* static */ +Class *Class::create(const ccstd::string &clsName, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data) { + auto *cls = ccnew Class(); + if (cls != nullptr && !cls->init(clsName, parent, parentProto, ctor, data)) { + delete cls; + cls = nullptr; + } + return cls; +} + +Class *Class::create(const std::initializer_list &classPath, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data) { + se::AutoHandleScope scope; + se::Value currentParent{parent}; + for (auto i = 0; i < classPath.size() - 1; i++) { + se::Value tmp; + bool ok = currentParent.toObject()->getProperty(*(classPath.begin() + i), &tmp); + CC_ASSERT(ok); // class or namespace in path is not defined + currentParent = tmp; + } + return create(*(classPath.end() - 1), currentParent.toObject(), parentProto, ctor, data); +} + +bool Class::init(const ccstd::string &clsName, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data) { + _name = clsName; + _parent = parent; + if (_parent != nullptr) { + _parent->incRef(); + } + + _parentProto = parentProto; + if (_parentProto != nullptr) { + _parentProto->incRef(); + } + + _constructor = ctor; + + v8::FunctionCallback ctorToSet = _constructor != nullptr ? _constructor : invalidConstructor; + + _constructorTemplate.Reset(__isolate, v8::FunctionTemplate::New(__isolate, ctorToSet, createExternal(__isolate, data))); + v8::MaybeLocal jsNameVal = v8::String::NewFromUtf8(__isolate, _name.c_str(), v8::NewStringType::kNormal); + if (jsNameVal.IsEmpty()) { + return false; + } + + _constructorTemplate.Get(__isolate)->SetClassName(jsNameVal.ToLocalChecked()); + _constructorTemplate.Get(__isolate)->InstanceTemplate()->SetInternalFieldCount(1); + + return true; +} + +void Class::_setCtor(Object *obj) { + assert(!_ctor.has_value()); + _ctor = obj; + if (obj != nullptr) { + obj->root(); + obj->incRef(); + } +} + +void Class::destroy() { + SAFE_DEC_REF(_parent); + SAFE_DEC_REF(_proto); + SAFE_DEC_REF(_parentProto); + if (_ctor.has_value()) { + if (_ctor.value() != nullptr) { + _ctor.value()->unroot(); + _ctor.value()->decRef(); + } + _ctor.reset(); + } + _constructorTemplate.Reset(); +} + +void Class::cleanup() { + for (auto *cls : __allClasses) { + cls->destroy(); + } + + se::ScriptEngine::getInstance()->addAfterCleanupHook([]() { + for (auto *cls : __allClasses) { + delete cls; + } + __allClasses.clear(); + }); +} + +void Class::setCreateProto(bool createProto) { + _createProto = createProto; +} + +bool Class::install() { + // assert(__clsMap.find(_name) == __clsMap.end()); + // + // __clsMap.emplace(_name, this); + + if (_parentProto != nullptr) { + _constructorTemplate.Get(__isolate)->Inherit(_parentProto->_getClass()->_constructorTemplate.Get(__isolate)); + } + + v8::Local context = __isolate->GetCurrentContext(); + v8::MaybeLocal ctor = _constructorTemplate.Get(__isolate)->GetFunction(context); + if (ctor.IsEmpty()) { + return false; + } + + v8::Local ctorChecked = ctor.ToLocalChecked(); + v8::MaybeLocal name = v8::String::NewFromUtf8(__isolate, _name.c_str(), v8::NewStringType::kNormal); + if (name.IsEmpty()) { + return false; + } + + v8::Maybe result = _parent->_getJSObject()->Set(context, name.ToLocalChecked(), ctorChecked); + if (result.IsNothing()) { + return false; + } + + v8::MaybeLocal prototypeName = v8::String::NewFromUtf8(__isolate, "prototype", v8::NewStringType::kNormal); + if (prototypeName.IsEmpty()) { + return false; + } + + v8::MaybeLocal prototypeObj = ctorChecked->Get(context, prototypeName.ToLocalChecked()); + if (prototypeObj.IsEmpty()) { + return false; + } + + if (_createProto) { + // Proto object is released in Class::destroy. + _proto = Object::_createJSObject(this, v8::Local::Cast(prototypeObj.ToLocalChecked())); + _proto->root(); + } + return true; +} + +bool Class::defineFunction(const char *name, v8::FunctionCallback func, void *data) { + v8::MaybeLocal jsName = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal); + if (jsName.IsEmpty()) { + return false; + } + + _constructorTemplate.Get(__isolate)->PrototypeTemplate()->Set(jsName.ToLocalChecked(), v8::FunctionTemplate::New(__isolate, func, createExternal(__isolate, data))); + return true; +} + +bool Class::defineProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data) { + v8::MaybeLocal jsName = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal); + if (jsName.IsEmpty()) { + return false; + } + + auto prototypeTemplate = _constructorTemplate.Get(__isolate)->PrototypeTemplate(); + auto externalData = createExternal(__isolate, data); + + v8::Local getterTemplate = v8::Local(); + v8::Local setterTemplate = v8::Local(); + + if (getter != nullptr) { + getterTemplate = v8::FunctionTemplate::New(__isolate, getter, externalData); + } + + if (setter != nullptr) { + setterTemplate = v8::FunctionTemplate::New(__isolate, setter, externalData); + } + prototypeTemplate->SetAccessorProperty(jsName.ToLocalChecked(), getterTemplate, setterTemplate); + return true; +} + +bool Class::defineProperty(const std::initializer_list &names, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data) { + bool ret = true; + for (const auto *name : names) { + ret &= defineProperty(name, getter, setter, data); + } + return ret; +} + +bool Class::defineStaticFunction(const char *name, v8::FunctionCallback func, void *data) { + v8::MaybeLocal jsName = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal); + if (jsName.IsEmpty()) { + return false; + } + _constructorTemplate.Get(__isolate)->Set(jsName.ToLocalChecked(), v8::FunctionTemplate::New(__isolate, func, createExternal(__isolate, data))); + return true; +} + +bool Class::defineStaticProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data) { + v8::MaybeLocal jsName = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal); + if (jsName.IsEmpty()) { + return false; + } + + auto externalData = createExternal(__isolate, data); + v8::Local getterTemplate = v8::Local(); + v8::Local setterTemplate = v8::Local(); + + if (getter != nullptr) { + getterTemplate = v8::FunctionTemplate::New(__isolate, getter, externalData); + } + + if (setter != nullptr) { + setterTemplate = v8::FunctionTemplate::New(__isolate, setter, externalData); + } + + _constructorTemplate.Get(__isolate)->SetAccessorProperty(jsName.ToLocalChecked(), getterTemplate, setterTemplate); + return true; +} + +bool Class::defineStaticProperty(const char *name, const Value &value, PropertyAttribute attribute /* = PropertyAttribute::NONE */) { + v8::MaybeLocal jsName = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal); + if (jsName.IsEmpty()) { + return false; + } + + v8::Local v8Val; + internal::seToJsValue(__isolate, value, &v8Val); + _constructorTemplate.Get(__isolate)->Set(jsName.ToLocalChecked(), v8Val, static_cast(attribute)); + return true; +} + +bool Class::defineFinalizeFunction(V8FinalizeFunc finalizeFunc) { + CC_ASSERT_NOT_NULL(finalizeFunc); + _finalizeFunc = finalizeFunc; + return true; +} + +// v8::Local Class::_createJSObject(const ccstd::string &clsName, Class** outCls) +// { +// auto iter = __clsMap.find(clsName); +// if (iter == __clsMap.end()) +// { +// *outCls = nullptr; +// return v8::Local::Cast(v8::Undefined(__isolate)); +// } +// +// *outCls = iter->second; +// return _createJSObjectWithClass(iter->second); +// } + +v8::Local Class::_createJSObjectWithClass(Class *cls) { // NOLINT + v8::MaybeLocal ret = cls->_constructorTemplate.Get(__isolate)->InstanceTemplate()->NewInstance(__isolate->GetCurrentContext()); + CC_ASSERT(!ret.IsEmpty()); + return ret.ToLocalChecked(); +} + +Object *Class::getProto() const { + return _proto; +} + +V8FinalizeFunc Class::_getFinalizeFunction() const { // NOLINT + return _finalizeFunc; +} + +/* static */ +void Class::setIsolate(v8::Isolate *isolate) { + __isolate = isolate; +} + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/Class.h b/cocos/bindings/jswrapper/v8/Class.h new file mode 100644 index 0000000..96e714e --- /dev/null +++ b/cocos/bindings/jswrapper/v8/Class.h @@ -0,0 +1,173 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../config.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + + #include "../Define.h" + #include "../Value.h" + #include "Base.h" + #include "base/std/optional.h" + +namespace se { + +class Object; + +/** + * se::Class represents a definition of how to create a native binding object. + */ +class Class final { +public: + /** + * @brief Creates a class used for creating relevant native binding objects. + * @param[in] className A null-terminated UTF8 string containing the class's name. + * @param[in] obj The object that current class proto object attaches to. Should not be nullptr. + * @param[in] parentProto The parent proto object that current class inherits from. Passing nullptr means a new class has no parent. + * @param[in] ctor A callback to invoke when your constructor is used in a 'new' expression. Pass nullptr to use the default object constructor. + * @param[in] data A data pointer attach to the function callback. + * @return A class instance used for creating relevant native binding objects. + * @note Don't need to delete the pointer return by this method, it's managed internally. + */ + static Class *create(const ccstd::string &clsName, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data = nullptr); + + static Class *create(const std::initializer_list &classPath, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data = nullptr); + + /** + * @brief Defines a member function with a callback. Each objects created by class will have this function property. + * @param[in] name A null-terminated UTF8 string containing the function name. + * @param[in] func A callback to invoke when the property is called as a function. + * @param[in] data A data pointer attach to the function callback. + * @return true if succeed, otherwise false. + */ + bool defineFunction(const char *name, v8::FunctionCallback func, void *data = nullptr); + + /** + * @brief Defines a property with accessor callbacks. Each objects created by class will have this property. + * @param[in] name A null-terminated UTF8 string containing the property name. + * @param[in] getter A callback to invoke when the property is read. + * @param[in] setter A callback to invoke when the property is set. + * @param[in] data A data pointer attach to the property's callback + * @return true if succeed, otherwise false. + */ + bool defineProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data = nullptr); + + bool defineProperty(const std::initializer_list &names, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data = nullptr); + + /** + * @brief Defines a static function with a callback. Only JavaScript constructor object will have this function. + * @param[in] name A null-terminated UTF8 string containing the function name. + * @param[in] func A callback to invoke when the constructor's property is called as a function. + * @param[in] data A data pointer attach to static function callback + * @return true if succeed, otherwise false. + */ + bool defineStaticFunction(const char *name, v8::FunctionCallback func, void *data = nullptr); + + /** + * @brief Defines a static property with accessor callbacks. Only JavaScript constructor object will have this property. + * @param[in] name A null-terminated UTF8 string containing the property name. + * @param[in] getter A callback to invoke when the constructor's property is read. + * @param[in] setter A callback to invoke when the constructor's property is set. + * @param[in] data A data pointer attach to static property callback + * @return true if succeed, otherwise false. + */ + bool defineStaticProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter, void *data = nullptr); + + /** + * @brief Defines a static property with a value. Only JavaScript constructor object will have this property. + * @param[in] name A null-terminated UTF8 string containing the property name. + * @param[in] value A value to be set on the constructor. + * @param[in] attribute An attribute to describe the property. + * @return true if succeed, otherwise false. + */ + bool defineStaticProperty(const char *name, const Value &value, PropertyAttribute attribute = PropertyAttribute::NONE); + + /** + * @brief Defines the finalize function with a callback. + * @param[in] func The callback to invoke when a JavaScript object is garbage collected. + * @return true if succeed, otherwise false. + */ + bool defineFinalizeFunction(V8FinalizeFunc func); + + /** + * @brief Installs class to JavaScript VM. + * @return true if succeed, otherwise false. + * @note After this method, an object could be created by `var foo = new Foo();`. + */ + bool install(); + + /** + * @brief Gets the proto object of this class. + * @return The proto object of this class. + * @note Don't need to be released in user code. + */ + Object *getProto() const; + + /** + * @brief Gets the class name. + * @return The class name. + */ + const char *getName() const { return _name.c_str(); } + + // Private API used in wrapper + V8FinalizeFunc _getFinalizeFunction() const; // NOLINT(readability-identifier-naming) + void _setCtor(Object *obj); // NOLINT(readability-identifier-naming) + inline const ccstd::optional &_getCtor() const { return _ctor; } // NOLINT(readability-identifier-naming) + +private: + Class(); + ~Class(); + + void setCreateProto(bool createProto); + + bool init(const ccstd::string &clsName, Object *parent, Object *parentProto, v8::FunctionCallback ctor, void *data = nullptr); + void destroy(); + + static void cleanup(); + // static v8::Local _createJSObject(const ccstd::string &clsName, Class** outCls); + static v8::Local _createJSObjectWithClass(Class *cls); // NOLINT(readability-identifier-naming) + static void setIsolate(v8::Isolate *isolate); + + ccstd::string _name; + Object *_parent{nullptr}; + Object *_parentProto{nullptr}; + Object *_proto{nullptr}; + ccstd::optional _ctor; + + v8::FunctionCallback _constructor{nullptr}; + v8::UniquePersistent _constructorTemplate; + V8FinalizeFunc _finalizeFunc{nullptr}; + bool _createProto{true}; + + friend class ScriptEngine; + friend class Object; + friend class JSBPersistentHandleVisitor; +}; + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/HelperMacros.cpp b/cocos/bindings/jswrapper/v8/HelperMacros.cpp new file mode 100644 index 0000000..69287ed --- /dev/null +++ b/cocos/bindings/jswrapper/v8/HelperMacros.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "HelperMacros.h" +#include "../State.h" +#include "../ValueArrayPool.h" +#include "Class.h" +#include "Object.h" +#include "ScriptEngine.h" +#include "Utils.h" + +#if defined(RECORD_JSB_INVOKING) + +namespace { +bool cmp(const std::pair> &a, const std::pair> &b) { + return std::get<1>(a.second) > std::get<1>(b.second); +} +unsigned int __jsbInvocationCount; // NOLINT(readability-identifier-naming) +ccstd::unordered_map> __jsbFunctionInvokedRecords; // NOLINT(readability-identifier-naming) +} // namespace + +JsbInvokeScopeT::JsbInvokeScopeT(const char *functionName) : _functionName(functionName) { + _start = std::chrono::high_resolution_clock::now(); + __jsbInvocationCount++; +} +JsbInvokeScopeT::~JsbInvokeScopeT() { + auto &ref = __jsbFunctionInvokedRecords[_functionName]; + std::get<0>(ref) += 1; + std::get<1>(ref) += std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - _start).count(); +} + +#endif + +void printJSBInvokeAtFrame(int n) { +#if defined(RECORD_JSB_INVOKING) + static int cnt = 0; + cnt += 1; + if (cnt % n == 0) { + printJSBInvoke(); + } +#endif +} + +void clearRecordJSBInvoke() { +#if defined(RECORD_JSB_INVOKING) + __jsbInvocationCount = 0; + __jsbFunctionInvokedRecords.clear(); +#endif +} + +void printJSBInvoke() { +#if defined(RECORD_JSB_INVOKING) + static ccstd::vector>> pairs; + for (const auto &it : __jsbFunctionInvokedRecords) { + pairs.emplace_back(it); // NOLINT + } + + std::sort(pairs.begin(), pairs.end(), cmp); + cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "Start print JSB function record info....... %d times", __jsbInvocationCount); + for (const auto &pair : pairs) { + cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "\t%s takes %.3lf ms, invoked %u times,", pair.first, std::get<1>(pair.second) / 1000000.0, std::get<0>(pair.second)); + } + pairs.clear(); + cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG, "End print JSB function record info.......\n"); +#endif +} + +SE_HOT void jsbFunctionWrapper(const v8::FunctionCallbackInfo &v8args, se_function_ptr func, const char *funcName) { + bool ret = false; + v8::Isolate *isolate = v8args.GetIsolate(); + v8::HandleScope scope(isolate); + bool needDeleteValueArray{false}; + se::ValueArray &args = se::gValueArrayPool.get(v8args.Length(), needDeleteValueArray); + se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; + se::internal::jsToSeArgs(v8args, args); + se::Object *thisObject = se::internal::getPrivate(isolate, v8args.This()); + se::State state(thisObject, args); + ret = func(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s\n", funcName); + } + se::internal::setReturnValue(state.rval(), v8args); +} + +SE_HOT void jsbFinalizeWrapper(se::Object *thisObject, se_function_ptr func, const char *funcName) { + auto *engine = se::ScriptEngine::getInstance(); + engine->_setGarbageCollecting(true); + se::State state(thisObject); + bool ret = func(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s\n", funcName); + } + engine->_setGarbageCollecting(false); +} +SE_HOT void jsbConstructorWrapper(const v8::FunctionCallbackInfo &v8args, se_function_ptr func, se_finalize_ptr finalizeCb, se::Class *cls, const char *funcName) { + v8::Isolate *isolate = v8args.GetIsolate(); + v8::HandleScope scope(isolate); + bool ret = true; + bool needDeleteValueArray{false}; + se::ValueArray &args = se::gValueArrayPool.get(v8args.Length(), needDeleteValueArray); + se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; + se::internal::jsToSeArgs(v8args, args); + se::Object *thisObject = se::Object::_createJSObject(cls, v8args.This()); + thisObject->_setFinalizeCallback(finalizeCb); + se::State state(thisObject, args); + ret = func(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s\n", funcName); + } + se::Value property; + bool foundCtor = false; + if (!cls->_getCtor().has_value()) { + foundCtor = thisObject->getProperty("_ctor", &property, true); + if (foundCtor) { + cls->_setCtor(property.toObject()); + } else { + cls->_setCtor(nullptr); + } + } else { + auto *ctorObj = cls->_getCtor().value(); + if (ctorObj != nullptr) { + property.setObject(ctorObj); + foundCtor = true; + } + } + + if (foundCtor) { + property.toObject()->call(args, thisObject); + } +} + +SE_HOT void jsbGetterWrapper(const v8::FunctionCallbackInfo &v8args, se_function_ptr func, const char *funcName) { + v8::Isolate *isolate = v8args.GetIsolate(); + v8::HandleScope scope(isolate); + bool ret = true; + se::Object *thisObject = se::internal::getPrivate(isolate, v8args.This()); + se::State state(thisObject); + ret = func(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s\n", funcName); + } + se::internal::setReturnValue(state.rval(), v8args); +} + +SE_HOT void jsbSetterWrapper(const v8::FunctionCallbackInfo &v8args, se_function_ptr func, const char *funcName) { + v8::Isolate *isolate = v8args.GetIsolate(); + v8::HandleScope scope(isolate); + bool ret = true; + se::Object *thisObject = se::internal::getPrivate(isolate, v8args.This()); + bool needDeleteValueArray{false}; + se::ValueArray &args = se::gValueArrayPool.get(1, needDeleteValueArray); + se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; + se::Value &data{args[0]}; + se::internal::jsToSeValue(isolate, v8args[0], &data); + se::State state(thisObject, args); + ret = func(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s\n", funcName); + } +} diff --git a/cocos/bindings/jswrapper/v8/HelperMacros.h b/cocos/bindings/jswrapper/v8/HelperMacros.h new file mode 100644 index 0000000..4a62513 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/HelperMacros.h @@ -0,0 +1,211 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "v8.h" +#include +#include +#include +#include +#include +#include +#include "../config.h" +#include "base/Log.h" +#include "base/Macros.h" +#include "base/std/container/string.h" + +// #define RECORD_JSB_INVOKING + +#ifndef CC_DEBUG + #undef RECORD_JSB_INVOKING +#endif + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + + #if defined(RECORD_JSB_INVOKING) + +class JsbInvokeScopeT { +public: + JsbInvokeScopeT(const char *functionName); + ~JsbInvokeScopeT(); + +private: + const char *_functionName; + std::chrono::time_point _start; +}; + #define JsbInvokeScope(arg) JsbInvokeScopeT invokeScope(arg); // NOLINT(readability-identifier-naming) + #else + // NOLINTNEXTLINE(readability-identifier-naming) + #define JsbInvokeScope(arg) \ + do { \ + } while (0) + + #endif + +template +constexpr inline T *SE_THIS_OBJECT(STATE &s) { // NOLINT(readability-identifier-naming) + return reinterpret_cast(s.nativeThisObject()); +} + +template +constexpr typename std::enable_if::value, char *>::type SE_UNDERLYING_TYPE_NAME() { // NOLINT(readability-identifier-naming) + return typeid(std::underlying_type_t).name(); +} + +template +constexpr typename std::enable_if::value, char *>::type SE_UNDERLYING_TYPE_NAME() { // NOLINT(readability-identifier-naming) + return typeid(T).name(); +} + +void clearRecordJSBInvoke(); + +void printJSBInvoke(); + +void printJSBInvokeAtFrame(int n); + +namespace se { +class Class; +class Object; +class State; +} // namespace se +using se_function_ptr = bool (*)(se::State &state); +using se_finalize_ptr = void (*)(se::Object *seObj); + +void jsbFunctionWrapper(const v8::FunctionCallbackInfo &, + se_function_ptr, + const char *); +void jsbFinalizeWrapper(se::Object *thisObject, + se_function_ptr, + const char *); +void jsbConstructorWrapper(const v8::FunctionCallbackInfo &, + se_function_ptr, + se_finalize_ptr finalizeCb, + se::Class *, + const char *); +void jsbGetterWrapper(const v8::FunctionCallbackInfo &, + se_function_ptr, + const char *); +void jsbSetterWrapper(const v8::FunctionCallbackInfo &, + se_function_ptr, + const char *); + + #ifdef __GNUC__ + #define SE_UNUSED __attribute__((unused)) + #define SE_HOT __attribute__((hot)) + #else + #define SE_UNUSED + #define SE_HOT + #endif + + #define SAFE_INC_REF(obj) \ + if (obj != nullptr) obj->incRef() + #define SAFE_DEC_REF(obj) \ + if ((obj) != nullptr) { \ + (obj)->decRef(); \ + (obj) = nullptr; \ + } + + #define _SE(name) name##Registry // NOLINT(readability-identifier-naming, bugprone-reserved-identifier) + + #define SE_DECLARE_FUNC(funcName) \ + void funcName##Registry(const v8::FunctionCallbackInfo &v8args) + + #define SE_BIND_FUNC(funcName) \ + void funcName##Registry(const v8::FunctionCallbackInfo &_v8args) { \ + JsbInvokeScope(#funcName); \ + jsbFunctionWrapper(_v8args, funcName, #funcName); \ + } + + #define SE_BIND_FUNC_FAST(funcName) \ + void funcName##Registry(const v8::FunctionCallbackInfo &_v8args) { \ + JsbInvokeScope(#funcName); \ + auto *thisObject = static_cast(_v8args.This()->GetAlignedPointerFromInternalField(0)); \ + auto *nativeObject = thisObject != nullptr ? thisObject->getPrivateData() : nullptr; \ + funcName(nativeObject); \ + } + + #define SE_BIND_FINALIZE_FUNC(funcName) \ + void funcName##Registry(se::Object *thisObject) { \ + JsbInvokeScope(#funcName); \ + if (thisObject == nullptr) \ + return; \ + jsbFinalizeWrapper(thisObject, funcName, #funcName); \ + } + + #define SE_DECLARE_FINALIZE_FUNC(funcName) \ + void funcName##Registry(se::Object *thisObject); + + // v8 doesn't need to create a new JSObject in SE_BIND_CTOR while SpiderMonkey needs. + #define SE_BIND_CTOR(funcName, cls, finalizeCb) \ + void funcName##Registry(const v8::FunctionCallbackInfo &_v8args) { \ + JsbInvokeScope(#funcName); \ + jsbConstructorWrapper(_v8args, funcName, _SE(finalizeCb), cls, #funcName); \ + } + + #define SE_BIND_PROP_GET_IMPL(funcName, postFix) \ + void funcName##postFix##Registry(const v8::FunctionCallbackInfo &_v8args) { \ + JsbInvokeScope(#funcName); \ + jsbGetterWrapper(_v8args, funcName, #funcName); \ + } + + #define SE_BIND_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, ) + #define SE_BIND_FUNC_AS_PROP_GET(funcName) SE_BIND_PROP_GET_IMPL(funcName, _asGetter) + + #define SE_BIND_PROP_SET_IMPL(funcName, postFix) \ + void funcName##postFix##Registry(const v8::FunctionCallbackInfo &_v8args) { \ + JsbInvokeScope(#funcName); \ + jsbSetterWrapper(_v8args, funcName, #funcName); \ + } + + #define SE_BIND_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, ) + #define SE_BIND_FUNC_AS_PROP_SET(funcName) SE_BIND_PROP_SET_IMPL(funcName, _asSetter) + + #define SE_TYPE_NAME(t) typeid(t).name() + + #define SE_QUOTEME_(x) #x // NOLINT(readability-identifier-naming) + #define SE_QUOTEME(x) SE_QUOTEME_(x) + + // IDEA: implement this macro + // #define SE_REPORT_ERROR(fmt, ...) SE_LOGE(SE_STR_CONCAT3("[ERROR] ( %s, %d): ", fmt, "\n"), __FILE__, __LINE__, ##__VA_ARGS__) + #define SE_REPORT_ERROR(fmt, ...) selogMessage(cc::LogLevel::ERR, "[SE_ERROR]", (" (%s, %d): " fmt), __FILE__, __LINE__, ##__VA_ARGS__) + + #if CC_DEBUG > 0 + + #define SE_ASSERT(cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + selogMessage(cc::LogLevel::ERR, "[SE_ASSERT]", (" (%s, %d): " fmt), __FILE__, __LINE__, ##__VA_ARGS__); \ + CC_ABORT(); \ + } \ + } while (false) + + #else + + #define SE_ASSERT(cond, fmt, ...) + + #endif // #if CC_DEBUG > 0 + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/MissingSymbols.cpp b/cocos/bindings/jswrapper/v8/MissingSymbols.cpp new file mode 100644 index 0000000..e91f0af --- /dev/null +++ b/cocos/bindings/jswrapper/v8/MissingSymbols.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "MissingSymbols.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + + //TODO(PatriceJiang): modify this when OHOS llvm upgrade + #if CC_PLATFORM == CC_PLATFORM_OHOS + #include +extern "C" { +int local_bcmp(const void *cs, const void *ct, size_t count) { + return memcmp(cs, ct, count); +} +int bcmp(const void *cs, const void *ct, size_t count) __attribute__((weak, alias("local_bcmp"))); +} // extern "C" + #endif + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 \ No newline at end of file diff --git a/cocos/bindings/jswrapper/v8/MissingSymbols.h b/cocos/bindings/jswrapper/v8/MissingSymbols.h new file mode 100644 index 0000000..7ffea38 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/MissingSymbols.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include + +#include "../config.h" diff --git a/cocos/bindings/jswrapper/v8/Object.cpp b/cocos/bindings/jswrapper/v8/Object.cpp new file mode 100644 index 0000000..c062f4f --- /dev/null +++ b/cocos/bindings/jswrapper/v8/Object.cpp @@ -0,0 +1,1128 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Object.h" +#include "v8/HelperMacros.h" + +// Use node::Buffer to replace v8 api,to avoid link err in editor platform. +#if CC_EDITOR && CC_PLATFORM == CC_PLATFORM_WINDOWS + #include +#endif + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + #include "../MappingUtils.h" + #include "Class.h" + #include "ScriptEngine.h" + #include "Utils.h" + #include "base/std/container/unordered_map.h" + + #include + #include + #include "base/std/container/array.h" + + #define JSB_FUNC_DEFAULT_MAX_ARG_COUNT (10) + +namespace se { + +namespace { +v8::Isolate *__isolate = nullptr; // NOLINT + #if CC_DEBUG_JS_OBJECT_ID && CC_DEBUG +uint32_t nativeObjectId = 0; + #endif +} // namespace + +class JSBPersistentHandleVisitor : public v8::PersistentHandleVisitor { +public: + JSBPersistentHandleVisitor() = default; + + void VisitPersistentHandle(v8::Persistent *value, uint16_t classId) override { + if (value == nullptr || classId != ObjectWrap::MAGIC_CLASS_ID_JSB) { + return; + } + + auto &persistObj = v8::Persistent::Cast(*value); + const int fieldCount = v8::Object::InternalFieldCount(persistObj); + if (fieldCount != 1) { + return; + } + + void *ptr = v8::Object::GetAlignedPointerFromInternalField(persistObj, 0); + if (ptr == nullptr) { + return; + } + + auto *obj = reinterpret_cast(ptr); + auto *nativeObj = obj->getPrivateData(); + if (nativeObj == nullptr) { + // Not a JSB binding object + return; + } + + // Remove mapping + se::NativePtrToObjectMap::erase(nativeObj, obj); + + // Invoke finalize callback + if (obj->_finalizeCb != nullptr) { + obj->_finalizeCb(obj); + } else { + if (obj->_getClass() != nullptr) { + if (obj->_getClass()->_finalizeFunc != nullptr) { + obj->_getClass()->_finalizeFunc(obj); + } + } + } + + if (obj->getRefCount() != 1) { + CC_LOG_WARNING("se::Object (%p) reference count (%u) is not 1", obj, obj->getRefCount()); + } + obj->decRef(); + } +}; + +Object::Object() { // NOLINT + #if JSB_TRACK_OBJECT_CREATION + _objectCreationStackFrame = se::ScriptEngine::getInstance()->getCurrentStackTrace(); + #endif +} + +Object::~Object() { + if (_rootCount > 0) { + _obj.unref(); + } + + delete _privateObject; + _privateObject = nullptr; +} + +/* static */ +void Object::nativeObjectFinalizeHook(Object *seObj) { + if (seObj == nullptr) { + return; + } + + if (seObj->_clearMappingInFinalizer && seObj->_privateData != nullptr) { + void *nativeObj = seObj->_privateData; + NativePtrToObjectMap::erase(nativeObj, seObj); + } + + if (seObj->_finalizeCb != nullptr) { + seObj->_finalizeCb(seObj); + } else { + if (seObj->_getClass() != nullptr && seObj->_getClass()->_finalizeFunc != nullptr) { + seObj->_getClass()->_finalizeFunc(seObj); + } + } + + seObj->decRef(); +} + +/* static */ +void Object::setIsolate(v8::Isolate *isolate) { + __isolate = isolate; + ObjectWrap::setIsolateValid(__isolate != nullptr); +} + +void Object::cleanup() { + JSBPersistentHandleVisitor jsbVisitor; + __isolate->VisitHandlesWithClassIds(&jsbVisitor); + SE_ASSERT(NativePtrToObjectMap::size() == 0, "NativePtrToObjectMap should be empty!"); +} + +Object *Object::createProxyTarget(se::Object *proxy) { + SE_ASSERT(proxy->isProxy(), "parameter is not a Proxy object"); + v8::Local jsobj = proxy->getProxyTarget().As(); + Object *obj = Object::_createJSObject(nullptr, jsobj); + return obj; +} + +Object *Object::createPlainObject() { + v8::Local jsobj = v8::Object::New(__isolate); + Object *obj = _createJSObject(nullptr, jsobj); + return obj; +} + +Object *Object::createMapObject() { + v8::Local jsobj = v8::Map::New(__isolate); + return _createJSObject(nullptr, jsobj); +} + +Object *Object::createSetObject() { + v8::Local jsobj = v8::Set::New(__isolate); + return _createJSObject(nullptr, jsobj); +} + +Object *Object::getObjectWithPtr(void *ptr) { + Object *obj = nullptr; + NativePtrToObjectMap::forEach(ptr, [&obj](se::Object *foundObj) { + obj = foundObj; + obj->incRef(); + }); + return obj; +} + +Object *Object::_createJSObject(Class *cls, v8::Local obj) { // NOLINT(readability-identifier-naming) + auto *ret = ccnew Object(); + if (!ret->init(cls, obj)) { + delete ret; + ret = nullptr; + } + return ret; +} + +Object *Object::createObjectWithClass(Class *cls) { + v8::Local jsobj = Class::_createJSObjectWithClass(cls); + Object *obj = Object::_createJSObject(cls, jsobj); + return obj; +} + +/* static */ +Object *Object::createObjectWithConstructor(se::Object *constructor) { + auto jsVal = constructor->_getJSObject()->CallAsConstructor(__isolate->GetCurrentContext(), 0, nullptr); + if (jsVal.IsEmpty()) { + return nullptr; + } + + v8::Local jsobj = v8::Local::Cast(jsVal.ToLocalChecked()); + return Object::_createJSObject(nullptr, jsobj); +} + +/* static */ +Object *Object::createObjectWithConstructor(se::Object *constructor, const ValueArray &args) { + ccstd::vector> jsArgs(args.size()); + internal::seToJsArgs(__isolate, args, jsArgs.data()); + auto jsVal = constructor->_getJSObject()->CallAsConstructor(__isolate->GetCurrentContext(), static_cast(args.size()), jsArgs.data()); + if (jsVal.IsEmpty()) { + return nullptr; + } + + v8::Local jsobj = v8::Local::Cast(jsVal.ToLocalChecked()); + return Object::_createJSObject(nullptr, jsobj); +} + +Object *Object::createArrayObject(size_t length) { + v8::Local jsobj = v8::Array::New(__isolate, static_cast(length)); + Object *obj = Object::_createJSObject(nullptr, jsobj); + return obj; +} + +Object *Object::createArrayBufferObject(const void *data, size_t byteLength) { + #if CC_EDITOR && CC_PLATFORM == CC_PLATFORM_WINDOWS + auto nodeBuffer = node::Buffer::New(__isolate, byteLength); + auto *srcData = node::Buffer::Data(nodeBuffer.ToLocalChecked()); + v8::Local jsobj = nodeBuffer.ToLocalChecked().As()->Buffer(); + #else + v8::Local jsobj = v8::ArrayBuffer::New(__isolate, byteLength); + auto *srcData = jsobj->GetBackingStore()->Data(); + #endif + if (data) { + memcpy(srcData, data, byteLength); + } else { + memset(srcData, 0, byteLength); + } + Object *obj = Object::_createJSObject(nullptr, jsobj); + return obj; +} + +/* static */ +Object *Object::createExternalArrayBufferObject(void *contents, size_t byteLength, BufferContentsFreeFunc freeFunc, void *freeUserData /* = nullptr*/) { + Object *obj = nullptr; + #if CC_EDITOR && CC_PLATFORM == CC_PLATFORM_WINDOWS + auto nodeBuffer = node::Buffer::New( + __isolate, (char *)contents, byteLength, [](char *data, void *hint) {}, nullptr) + .ToLocalChecked() + .As(); + v8::Local jsobj = nodeBuffer.As()->Buffer(); + #else + std::shared_ptr backingStore = v8::ArrayBuffer::NewBackingStore(contents, byteLength, freeFunc, freeUserData); + v8::Local jsobj = v8::ArrayBuffer::New(__isolate, backingStore); + #endif + + if (!jsobj.IsEmpty()) { + obj = Object::_createJSObject(nullptr, jsobj); + } + return obj; +} + +Object *Object::createTypedArray(TypedArrayType type, const void *data, size_t byteLength) { + if (type == TypedArrayType::NONE) { + SE_LOGE("Don't pass se::Object::TypedArrayType::NONE to createTypedArray API!"); + return nullptr; + } + + if (type == TypedArrayType::UINT8_CLAMPED) { + SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!"); + return nullptr; + } + #if CC_EDITOR && CC_PLATFORM == CC_PLATFORM_WINDOWS + auto nodeBuffer = node::Buffer::New(__isolate, byteLength); + auto *srcData = node::Buffer::Data(nodeBuffer.ToLocalChecked()); + v8::Local jsobj = nodeBuffer.ToLocalChecked().As()->Buffer(); + #else + v8::Local jsobj = v8::ArrayBuffer::New(__isolate, byteLength); + auto *srcData = jsobj->GetBackingStore()->Data(); + #endif + + // If data has content,then will copy data into buffer,or will only clear buffer. + if (data) { + memcpy(srcData, data, byteLength); + } else { + memset(srcData, 0, byteLength); + } + + v8::Local arr; + switch (type) { + case TypedArrayType::INT8: + arr = v8::Int8Array::New(jsobj, 0, byteLength); + break; + case TypedArrayType::INT16: + arr = v8::Int16Array::New(jsobj, 0, byteLength / 2); + break; + case TypedArrayType::INT32: + arr = v8::Int32Array::New(jsobj, 0, byteLength / 4); + break; + case TypedArrayType::UINT8: + arr = v8::Uint8Array::New(jsobj, 0, byteLength); + break; + case TypedArrayType::UINT16: + arr = v8::Uint16Array::New(jsobj, 0, byteLength / 2); + break; + case TypedArrayType::UINT32: + arr = v8::Uint32Array::New(jsobj, 0, byteLength / 4); + break; + case TypedArrayType::FLOAT32: + arr = v8::Float32Array::New(jsobj, 0, byteLength / 4); + break; + case TypedArrayType::FLOAT64: + arr = v8::Float64Array::New(jsobj, 0, byteLength / 8); + break; + default: + CC_ABORT(); // Should never go here. + break; + } + + Object *obj = Object::_createJSObject(nullptr, arr); + return obj; +} + +Object *Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj) { + return Object::createTypedArrayWithBuffer(type, obj, 0); +} + +Object *Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset) { + size_t byteLength{0}; + uint8_t *skip{nullptr}; + obj->getArrayBufferData(&skip, &byteLength); + return Object::createTypedArrayWithBuffer(type, obj, offset, byteLength - offset); +} + +Object *Object::createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength) { + if (type == TypedArrayType::NONE) { + SE_LOGE("Don't pass se::Object::TypedArrayType::NONE to createTypedArray API!"); + return nullptr; + } + + if (type == TypedArrayType::UINT8_CLAMPED) { + SE_LOGE("Doesn't support to create Uint8ClampedArray with Object::createTypedArray API!"); + return nullptr; + } + + v8::Local typedArray; + CC_ASSERT(obj->isArrayBuffer()); + v8::Local jsobj = obj->_getJSObject().As(); + switch (type) { + case TypedArrayType::INT8: + typedArray = v8::Int8Array::New(jsobj, offset, byteLength); + break; + case TypedArrayType::INT16: + typedArray = v8::Int16Array::New(jsobj, offset, byteLength / 2); + break; + case TypedArrayType::INT32: + typedArray = v8::Int32Array::New(jsobj, offset, byteLength / 4); + break; + case TypedArrayType::UINT8: + typedArray = v8::Uint8Array::New(jsobj, offset, byteLength); + break; + case TypedArrayType::UINT16: + typedArray = v8::Uint16Array::New(jsobj, offset, byteLength / 2); + break; + case TypedArrayType::UINT32: + typedArray = v8::Uint32Array::New(jsobj, offset, byteLength / 4); + break; + case TypedArrayType::FLOAT32: + typedArray = v8::Float32Array::New(jsobj, offset, byteLength / 4); + break; + case TypedArrayType::FLOAT64: + typedArray = v8::Float64Array::New(jsobj, offset, byteLength / 8); + break; + default: + CC_ABORT(); // Should never go here. + break; + } + + return Object::_createJSObject(nullptr, typedArray); +} + +Object *Object::createUint8TypedArray(uint8_t *bytes, size_t byteLength) { + return createTypedArray(TypedArrayType::UINT8, bytes, byteLength); +} + +Object *Object::createJSONObject(const ccstd::string &jsonStr) { + v8::Local context = __isolate->GetCurrentContext(); + Value strVal(jsonStr); + v8::Local jsStr; + internal::seToJsValue(__isolate, strVal, &jsStr); + v8::Local v8Str = v8::Local::Cast(jsStr); + v8::MaybeLocal ret = v8::JSON::Parse(context, v8Str); + if (ret.IsEmpty()) { + return nullptr; + } + + v8::Local jsobj = v8::Local::Cast(ret.ToLocalChecked()); + return Object::_createJSObject(nullptr, jsobj); +} + +bool Object::init(Class *cls, v8::Local obj) { + _cls = cls; + + _obj.init(obj, this, _cls != nullptr); + _obj.setFinalizeCallback(nativeObjectFinalizeHook); + + #if CC_DEBUG && CC_DEBUG_JS_OBJECT_ID + // this->_objectId = ++nativeObjectId; + // defineOwnProperty("__object_id__", se::Value(this->_objectId), false, false, false); + // defineOwnProperty("__native_class_name__", se::Value(cls ? cls->getName() : "[noname]"), false, false, false); + #endif + + return true; +} + +bool Object::getProperty(const char *name, Value *data, bool cachePropertyName) { + CC_ASSERT_NOT_NULL(data); + data->setUndefined(); + + v8::HandleScope handleScope(__isolate); + + if (_obj.persistent().IsEmpty()) { + return false; + } + + v8::MaybeLocal nameValue; + + if (cachePropertyName) { + nameValue = ScriptEngine::getInstance()->_getStringPool().get(__isolate, name); + } else { + nameValue = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal); + } + + if (nameValue.IsEmpty()) { + return false; + } + + v8::Local nameValToLocal = nameValue.ToLocalChecked(); + v8::Local context = __isolate->GetCurrentContext(); + v8::Local localObj = _obj.handle(__isolate); + v8::Maybe maybeExist = localObj->Has(context, nameValToLocal); + if (maybeExist.IsNothing()) { + return false; + } + + if (!maybeExist.FromJust()) { + return false; + } + + v8::MaybeLocal result = localObj->Get(context, nameValToLocal); + if (result.IsEmpty()) { + return false; + } + + internal::jsToSeValue(__isolate, result.ToLocalChecked(), data); + + return true; +} + +bool Object::deleteProperty(const char *name) { + v8::HandleScope handleScope(__isolate); + + if (_obj.persistent().IsEmpty()) { + return false; + } + + v8::MaybeLocal nameValue = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal); + if (nameValue.IsEmpty()) { + return false; + } + + v8::Local nameValToLocal = nameValue.ToLocalChecked(); + v8::Local context = __isolate->GetCurrentContext(); + v8::Maybe maybeExist = _obj.handle(__isolate)->Delete(context, nameValToLocal); + if (maybeExist.IsNothing()) { + return false; + } + + if (!maybeExist.FromJust()) { + return false; + } + + return true; +} + +bool Object::setProperty(const char *name, const Value &data) { + v8::MaybeLocal nameValue = ScriptEngine::getInstance()->_getStringPool().get(__isolate, name); + if (nameValue.IsEmpty()) { + return false; + } + + v8::Local value; + internal::seToJsValue(__isolate, data, &value); + v8::Maybe ret = _obj.handle(__isolate)->Set(__isolate->GetCurrentContext(), nameValue.ToLocalChecked(), value); + if (ret.IsNothing()) { + SE_LOGD("ERROR: %s, Set return nothing ...\n", __FUNCTION__); + return false; + } + return true; +} + +bool Object::defineProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter) { + v8::MaybeLocal nameValue = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal); + if (nameValue.IsEmpty()) { + return false; + } + + v8::Local nameValChecked = nameValue.ToLocalChecked(); + v8::Local jsName = v8::Local::Cast(nameValChecked); + v8::Local currentContext = __isolate->GetCurrentContext(); + + v8::MaybeLocal v8Getter = v8::Function::New(currentContext, getter); + v8::MaybeLocal v8Setter = v8::Function::New(currentContext, setter); + if (!v8Getter.IsEmpty() && !v8Setter.IsEmpty()) { + _obj.handle(__isolate)->SetAccessorProperty(jsName, v8Getter.ToLocalChecked(), v8Setter.ToLocalChecked()); + } else if (v8Getter.IsEmpty()) { + _obj.handle(__isolate)->SetAccessorProperty(jsName, {}, v8Setter.ToLocalChecked()); + } else if (v8Setter.IsEmpty()) { + _obj.handle(__isolate)->SetAccessorProperty(jsName, v8Getter.ToLocalChecked(), {}); + } + + return true; +} + +bool Object::defineOwnProperty(const char *name, const se::Value &value, bool writable, bool enumerable, bool configurable) { + v8::MaybeLocal nameValue = v8::String::NewFromUtf8(__isolate, name, v8::NewStringType::kNormal); + if (nameValue.IsEmpty()) { + return false; + } + + int flag{v8::PropertyAttribute::None}; + if (!writable) { + flag |= v8::PropertyAttribute::ReadOnly; + } + if (!enumerable) { + flag |= v8::PropertyAttribute::DontEnum; + } + if (!configurable) { + flag |= v8::PropertyAttribute::DontDelete; + } + + v8::Local v8Value; + internal::seToJsValue(__isolate, value, &v8Value); + + v8::Local nameValChecked = nameValue.ToLocalChecked(); + v8::Local jsName = v8::Local::Cast(nameValChecked); + v8::Maybe ret = _obj.handle(__isolate)->DefineOwnProperty(__isolate->GetCurrentContext(), jsName, v8Value, static_cast(flag)); + return ret.IsJust() && ret.FromJust(); +} + +bool Object::isFunction() const { + return const_cast(this)->_obj.handle(__isolate)->IsCallable(); +} + +bool Object::_isNativeFunction() const { // NOLINT(readability-identifier-naming) + if (isFunction()) { + ccstd::string info = toString(); + if (info.find("[native code]") != ccstd::string::npos) { + return true; + } + } + return false; +} + +bool Object::isTypedArray() const { + return const_cast(this)->_obj.handle(__isolate)->IsTypedArray(); +} + +bool Object::isProxy() const { + return const_cast(this)->_obj.handle(__isolate)->IsProxy(); +} + +v8::Local Object::getProxyTarget() const { + v8::Local value = const_cast(this)->_obj.handle(__isolate); + CC_ASSERTF(value->IsProxy(), "Object is not a Proxy"); + v8::Proxy *proxy = v8::Proxy::Cast(*value); + return proxy->GetTarget(); +} + +Object::TypedArrayType Object::getTypedArrayType() const { + v8::Local value = const_cast(this)->_obj.handle(__isolate); + TypedArrayType ret = TypedArrayType::NONE; + if (value->IsFloat32Array()) { + ret = TypedArrayType::FLOAT32; + } else if (value->IsUint32Array()) { + ret = TypedArrayType::UINT32; + } else if (value->IsUint16Array()) { + ret = TypedArrayType::UINT16; + } else if (value->IsUint8Array()) { + ret = TypedArrayType::UINT8; + } else if (value->IsInt32Array()) { + ret = TypedArrayType::INT32; + } else if (value->IsInt16Array()) { + ret = TypedArrayType::INT16; + } else if (value->IsInt8Array()) { + ret = TypedArrayType::INT8; + } else if (value->IsUint8ClampedArray()) { + ret = TypedArrayType::UINT8_CLAMPED; + } else if (value->IsFloat64Array()) { + ret = TypedArrayType::FLOAT64; + } + + return ret; +} + +bool Object::getTypedArrayData(uint8_t **ptr, size_t *length) const { + CC_ASSERT(isTypedArray()); + v8::Local obj = const_cast(this)->_obj.handle(__isolate); + #if CC_EDITOR && CC_PLATFORM == CC_PLATFORM_WINDOWS + char *data = node::Buffer::Data(obj); + *ptr = reinterpret_cast(data); + if (length) { + *length = node::Buffer::Length(obj); + } + #else + v8::Local arr = v8::Local::Cast(obj); + const auto &backingStore = arr->Buffer()->GetBackingStore(); + *ptr = static_cast(backingStore->Data()) + arr->ByteOffset(); + if (length) { + *length = arr->ByteLength(); + } + #endif + + return true; +} + +bool Object::isArrayBuffer() const { + v8::Local obj = const_cast(this)->_obj.handle(__isolate); + return obj->IsArrayBuffer(); +} + +bool Object::getArrayBufferData(uint8_t **ptr, size_t *length) const { + CC_ASSERT(isArrayBuffer()); + #if CC_EDITOR && CC_PLATFORM == CC_PLATFORM_WINDOWS + v8::Local jsobj = _getJSObject().As(); + auto obj = v8::Int8Array::New(jsobj, 0, jsobj->ByteLength()); + char *data = node::Buffer::Data(obj.As()); + *ptr = reinterpret_cast(data); + if (length) { + *length = node::Buffer::Length(obj.As()); + } + #else + v8::Local obj = const_cast(this)->_obj.handle(__isolate); + v8::Local arrBuf = v8::Local::Cast(obj); + const auto &backingStore = arrBuf->GetBackingStore(); + *ptr = static_cast(backingStore->Data()); + if (length) { + *length = backingStore->ByteLength(); + } + #endif + return true; +} + +void Object::setPrivateObject(PrivateObjectBase *data) { + CC_ASSERT_NULL(_privateObject); + #if CC_DEBUG + // CC_ASSERT(!NativePtrToObjectMap::contains(data->getRaw())); + if (data != nullptr) { + NativePtrToObjectMap::filter(data->getRaw(), _getClass()) + .forEach([&](se::Object *seObj) { + auto *pri = seObj->getPrivateObject(); + SE_LOGE("Already exists object %s/[%s], trying to add %s/[%s]\n", pri->getName(), typeid(*pri).name(), data->getName(), typeid(*data).name()); + #if JSB_TRACK_OBJECT_CREATION + SE_LOGE(" previous object created at %s\n", it->second->_objectCreationStackFrame.c_str()); + #endif + CC_ABORT(); + }); + } + #endif + internal::setPrivate(__isolate, _obj, this); + _privateObject = data; + + if (data != nullptr) { + _privateData = data->getRaw(); + NativePtrToObjectMap::emplace(_privateData, this); + } else { + _privateData = nullptr; + } +} + +PrivateObjectBase *Object::getPrivateObject() const { + return _privateObject; +} + +void Object::clearPrivateData(bool clearMapping) { + if (_privateObject != nullptr) { + if (clearMapping) { + NativePtrToObjectMap::erase(_privateData, this); + } + internal::clearPrivate(__isolate, _obj); + delete _privateObject; + _privateObject = nullptr; + _privateData = nullptr; + } +} + +v8::Local Object::_getJSObject() const { // NOLINT(readability-identifier-naming) + return const_cast(this)->_obj.handle(__isolate); +} + +ObjectWrap &Object::_getWrap() { // NOLINT(readability-identifier-naming) + return _obj; +} + +bool Object::call(const ValueArray &args, Object *thisObject, Value *rval /* = nullptr*/) { + if (_obj.persistent().IsEmpty()) { + SE_LOGD("Function object is released!\n"); + return false; + } + size_t argc = args.size(); + + ccstd::array, JSB_FUNC_DEFAULT_MAX_ARG_COUNT> argv; + std::unique_ptr>> vecArgs; + v8::Local *pArgv = argv.data(); + + if (argc > JSB_FUNC_DEFAULT_MAX_ARG_COUNT) { + vecArgs = std::make_unique>>(); + vecArgs->resize(argc); + pArgv = vecArgs->data(); + } + + internal::seToJsArgs(__isolate, args, pArgv); + + v8::Local thiz = v8::Local::Cast(v8::Undefined(__isolate)); + if (thisObject != nullptr) { + if (thisObject->_obj.persistent().IsEmpty()) { + SE_LOGD("This object is released!\n"); + return false; + } + thiz = thisObject->_obj.handle(__isolate); + } + + for (size_t i = 0; i < argc; ++i) { + if (pArgv[i].IsEmpty()) { + SE_LOGD("%s argv[%d] is released!\n", __FUNCTION__, (int)i); + return false; + } + } + + v8::Local context = se::ScriptEngine::getInstance()->_getContext(); + #if CC_DEBUG + v8::TryCatch tryCatch(__isolate); + #endif + v8::MaybeLocal result = _obj.handle(__isolate)->CallAsFunction(context, thiz, static_cast(argc), pArgv); + + #if CC_DEBUG + if (tryCatch.HasCaught()) { + v8::String::Utf8Value stack(__isolate, tryCatch.StackTrace(__isolate->GetCurrentContext()).ToLocalChecked()); + SE_REPORT_ERROR("Invoking function failed, %s", *stack); + } + #endif + + if (!result.IsEmpty()) { + if (rval != nullptr) { + internal::jsToSeValue(__isolate, result.ToLocalChecked(), rval); + } + return true; + } + SE_REPORT_ERROR("Invoking function (%p) failed!", this); + se::ScriptEngine::getInstance()->clearException(); + + // CC_ABORT(); + + return false; +} + +bool Object::defineFunction(const char *funcName, void (*func)(const v8::FunctionCallbackInfo &args)) { + v8::MaybeLocal maybeFuncName = v8::String::NewFromUtf8(__isolate, funcName, v8::NewStringType::kNormal); + if (maybeFuncName.IsEmpty()) { + return false; + } + + v8::Local context = __isolate->GetCurrentContext(); + v8::MaybeLocal maybeFunc = v8::FunctionTemplate::New(__isolate, func)->GetFunction(context); + if (maybeFunc.IsEmpty()) { + return false; + } + + v8::Maybe ret = _obj.handle(__isolate)->Set(context, + v8::Local::Cast(maybeFuncName.ToLocalChecked()), + maybeFunc.ToLocalChecked()); + + return ret.IsJust() && ret.FromJust(); +} + +bool Object::isMap() const { + return const_cast(this)->_obj.handle(__isolate)->IsMap(); +} + +bool Object::isWeakMap() const { + return const_cast(this)->_obj.handle(__isolate)->IsWeakMap(); +} + +bool Object::isSet() const { + return const_cast(this)->_obj.handle(__isolate)->IsSet(); +} + +bool Object::isWeakSet() const { + return const_cast(this)->_obj.handle(__isolate)->IsWeakSet(); +} + +bool Object::isArray() const { + return const_cast(this)->_obj.handle(__isolate)->IsArray(); +} + +bool Object::getArrayLength(uint32_t *length) const { + CC_ASSERT(isArray()); + CC_ASSERT_NOT_NULL(length); + auto *thiz = const_cast(this); + auto v8Arr = v8::Local::Cast(thiz->_obj.handle(__isolate)); + *length = v8Arr->Length(); + return true; +} + +bool Object::getArrayElement(uint32_t index, Value *data) const { + CC_ASSERT(isArray()); + CC_ASSERT_NOT_NULL(data); + auto *thiz = const_cast(this); + v8::MaybeLocal result = thiz->_obj.handle(__isolate)->Get(__isolate->GetCurrentContext(), index); + + if (result.IsEmpty()) { + return false; + } + + internal::jsToSeValue(__isolate, result.ToLocalChecked(), data); + return true; +} + +bool Object::setArrayElement(uint32_t index, const Value &data) { + CC_ASSERT(isArray()); + + v8::Local jsval; + internal::seToJsValue(__isolate, data, &jsval); + v8::Maybe ret = _obj.handle(__isolate)->Set(__isolate->GetCurrentContext(), index, jsval); + + return ret.IsJust() && ret.FromJust(); +} + +bool Object::getAllKeys(ccstd::vector *allKeys) const { + CC_ASSERT_NOT_NULL(allKeys); + auto *thiz = const_cast(this); + v8::Local context = __isolate->GetCurrentContext(); + v8::MaybeLocal keys = thiz->_obj.handle(__isolate)->GetOwnPropertyNames(context); + if (keys.IsEmpty()) { + return false; + } + + v8::Local keysChecked = keys.ToLocalChecked(); + Value keyVal; + for (uint32_t i = 0, len = keysChecked->Length(); i < len; ++i) { + v8::MaybeLocal key = keysChecked->Get(context, i); + if (key.IsEmpty()) { + allKeys->clear(); + return false; + } + internal::jsToSeValue(__isolate, key.ToLocalChecked(), &keyVal); + if (keyVal.isString()) { + allKeys->push_back(keyVal.toString()); + } else if (keyVal.isNumber()) { + char buf[50] = {0}; + snprintf(buf, sizeof(buf), "%d", keyVal.toInt32()); + allKeys->push_back(buf); + } else { + assert(false); + } + } + return true; +} + +void Object::clearMap() { // NOLINT + CC_ASSERT(isMap()); + v8::Map::Cast(*_getJSObject())->Clear(); +} + +bool Object::removeMapElement(const Value &key) { // NOLINT + CC_ASSERT(isMap()); + v8::Local v8Key; + internal::seToJsValue(__isolate, key, &v8Key); + v8::Maybe ret = v8::Map::Cast(*_getJSObject())->Delete(__isolate->GetCurrentContext(), v8Key); + return ret.IsJust() && ret.FromJust(); +} + +bool Object::getMapElement(const Value &key, Value *outValue) const { + CC_ASSERT(isMap()); + if (outValue == nullptr) { + return false; + } + + v8::Local v8Key; + internal::seToJsValue(__isolate, key, &v8Key); + v8::MaybeLocal ret = v8::Map::Cast(*_getJSObject())->Get(__isolate->GetCurrentContext(), v8Key); + if (ret.IsEmpty()) { + outValue->setUndefined(); + return false; + } + + internal::jsToSeValue(__isolate, ret.ToLocalChecked(), outValue); + return true; +} + +bool Object::setMapElement(const Value &key, const Value &value) { // NOLINT + CC_ASSERT(isMap()); + + v8::Local v8Key; + internal::seToJsValue(__isolate, key, &v8Key); + + v8::Local v8Value; + internal::seToJsValue(__isolate, value, &v8Value); + + v8::MaybeLocal ret = v8::Map::Cast(*_getJSObject())->Set(__isolate->GetCurrentContext(), v8Key, v8Value); + return !ret.IsEmpty(); +} + +uint32_t Object::getMapSize() const { + CC_ASSERT(isMap()); + return static_cast(v8::Map::Cast(*_getJSObject())->Size()); +} + +ccstd::vector> Object::getAllElementsInMap() const { + CC_ASSERT(isMap()); + auto *v8Map = v8::Map::Cast(*_getJSObject()); + if (v8Map->Size() == 0) { + return {}; + } + + v8::Local keyValueArray = v8Map->AsArray(); + uint32_t length = keyValueArray->Length(); + if (length == 0) { + return {}; + } + + SE_ASSERT(length % 2 == 0, "should be multiple of 2"); + + ccstd::vector> ret; + ret.reserve(length / 2); + v8::Local currentContext = __isolate->GetCurrentContext(); + for (uint32_t i = 0; i < length; i += 2) { + v8::MaybeLocal key = keyValueArray->Get(currentContext, i); + v8::MaybeLocal value = keyValueArray->Get(currentContext, i + 1); + if (key.IsEmpty() || value.IsEmpty()) { + continue; + } + Value seKey; + Value seValue; + internal::jsToSeValue(__isolate, key.ToLocalChecked(), &seKey); + internal::jsToSeValue(__isolate, value.ToLocalChecked(), &seValue); + + ret.emplace_back(seKey, seValue); + } + + return ret; +} + +void Object::clearSet() { // NOLINT + CC_ASSERT(isSet()); + v8::Set::Cast(*_getJSObject())->Clear(); +} + +bool Object::removeSetElement(const Value &value) { // NOLINT + CC_ASSERT(isSet()); + v8::Local v8Value; + internal::seToJsValue(__isolate, value, &v8Value); + v8::Maybe ret = v8::Set::Cast(*_getJSObject())->Delete(__isolate->GetCurrentContext(), v8Value); + return ret.IsJust() && ret.FromJust(); +} + +bool Object::addSetElement(const Value &value) { // NOLINT + CC_ASSERT(isSet()); + v8::Local v8Value; + internal::seToJsValue(__isolate, value, &v8Value); + v8::MaybeLocal ret = v8::Set::Cast(*_getJSObject())->Add(__isolate->GetCurrentContext(), v8Value); + return !ret.IsEmpty(); +} + +bool Object::isElementInSet(const Value &value) const { + CC_ASSERT(isSet()); + v8::Local v8Value; + internal::seToJsValue(__isolate, value, &v8Value); + v8::Maybe ret = v8::Set::Cast(*_getJSObject())->Has(__isolate->GetCurrentContext(), v8Value); + return ret.IsJust() && ret.FromJust(); +} + +uint32_t Object::getSetSize() const { + CC_ASSERT(isSet()); + return static_cast(v8::Set::Cast(*_getJSObject())->Size()); +} + +ValueArray Object::getAllElementsInSet() const { + CC_ASSERT(isSet()); + auto *v8Set = v8::Set::Cast(*_getJSObject()); + if (v8Set->Size() == 0) { + return {}; + } + + v8::Local keyValueArray = v8Set->AsArray(); + uint32_t length = keyValueArray->Length(); + if (length == 0) { + return {}; + } + + ValueArray ret; + ret.reserve(length); + v8::Local currentContext = __isolate->GetCurrentContext(); + for (uint32_t i = 0; i < length; ++i) { + v8::MaybeLocal value = keyValueArray->Get(currentContext, i); + if (value.IsEmpty()) { + continue; + } + Value seValue; + internal::jsToSeValue(__isolate, value.ToLocalChecked(), &seValue); + + ret.emplace_back(seValue); + } + + return ret; +} + +Class *Object::_getClass() const { // NOLINT(readability-identifier-naming) + return _cls; +} + +void Object::_setFinalizeCallback(V8FinalizeFunc finalizeCb) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(finalizeCb); + _finalizeCb = finalizeCb; +} + +void Object::root() { + if (_rootCount == 0) { + _obj.ref(); + } + ++_rootCount; +} + +void Object::unroot() { + if (_rootCount > 0) { + --_rootCount; + if (_rootCount == 0) { + _obj.unref(); + } + } +} + +bool Object::isRooted() const { + return _rootCount > 0; +} + +bool Object::strictEquals(Object *o) const { + auto *a = const_cast(this); + return a->_obj.handle(__isolate) == o->_obj.handle(__isolate); +} + +bool Object::attachObject(Object *obj) { + CC_ASSERT(obj); + + Object *global = ScriptEngine::getInstance()->getGlobalObject(); + Value jsbVal; + if (!global->getProperty("jsb", &jsbVal)) { + return false; + } + Object *jsbObj = jsbVal.toObject(); + + Value func; + + if (!jsbObj->getProperty("registerNativeRef", &func)) { + return false; + } + + ValueArray args; + args.push_back(Value(this)); + args.push_back(Value(obj)); + func.toObject()->call(args, global); + return true; +} + +bool Object::detachObject(Object *obj) { + CC_ASSERT(obj); + + Object *global = ScriptEngine::getInstance()->getGlobalObject(); + Value jsbVal; + if (!global->getProperty("jsb", &jsbVal)) { + return false; + } + Object *jsbObj = jsbVal.toObject(); + + Value func; + + if (!jsbObj->getProperty("unregisterNativeRef", &func)) { + return false; + } + + ValueArray args; + args.push_back(Value(this)); + args.push_back(Value(obj)); + func.toObject()->call(args, global); + return true; +} + +ccstd::string Object::toString() const { + v8::Local v8Obj = const_cast(this)->_obj.handle(__isolate); + v8::String::Utf8Value utf8(__isolate, v8Obj); + return *utf8; +} + +ccstd::string Object::toStringExt() const { + if (isFunction()) return "[function]"; + if (isArray()) return "[array]"; + if (isArrayBuffer()) return "[arraybuffer]"; + if (isTypedArray()) return "[typedarray]"; + + ccstd::vector keys; + getAllKeys(&keys); + std::stringstream ss; + ss << "{"; + for (auto &k : keys) { + ss << k << ", "; + } + ss << "}"; + return ss.str(); +} + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/Object.h b/cocos/bindings/jswrapper/v8/Object.h new file mode 100644 index 0000000..a35d705 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/Object.h @@ -0,0 +1,706 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../config.h" +#include "bindings/jswrapper/PrivateObject.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + + #include "../RefCounter.h" + #include "../Value.h" + #include "Base.h" + #include "ObjectWrap.h" + #include "base/HasMemberFunction.h" + + #include + + // DEBUG ONLY: + // Set `__object_id__` && `__native_class_name__` for js object + #ifndef CC_DEBUG_JS_OBJECT_ID + #define CC_DEBUG_JS_OBJECT_ID 0 + #endif + + #define JSB_TRACK_OBJECT_CREATION 0 + +namespace se { + +class Class; +class ScriptEngine; + +/** + * se::Object represents JavaScript Object. + */ +class Object final : public RefCounter { +public: + /** + * @brief Creates a JavaScript Object like `{} or new Object()`. + * @return A JavaScript Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createPlainObject(); + + /** + * @brief Creates a ES6 Map Object like `new Map()` in JS. + * @return A JavaScript Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createMapObject(); + + /** + * @brief Creates a ES6 Set Object like `new Set()` in JS. + * @return A JavaScript Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createSetObject(); + + /** + * @brief Creates a JavaScript Array Object like `[] or new Array()`. + * @param[in] length The initical length of array. + * @return A JavaScript Array Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createArrayObject(size_t length); + + /** + * @brief Creates a JavaScript Typed Array Object with uint8 format from an existing pointer. + * @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object. + * @param[in] byteLength The number of bytes pointed to by the parameter bytes. + * @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + * @deprecated This method is deprecated, please use `se::Object::createTypedArray` instead. + */ + SE_DEPRECATED_ATTRIBUTE static Object *createUint8TypedArray(uint8_t *bytes, size_t byteLength); + + enum class TypedArrayType { + NONE, + INT8, + INT16, + INT32, + UINT8, + UINT8_CLAMPED, + UINT16, + UINT32, + FLOAT32, + FLOAT64 + }; + + /** + * @brief Creates a JavaScript Typed Array Object with specified format from an existing pointer, + if provide a null pointer,then will create a empty JavaScript Typed Array Object. + * @param[in] type The format of typed array. + * @param[in] data A pointer to the byte buffer to be used as the backing store of the Typed Array object. + * @param[in] byteLength The number of bytes pointed to by the parameter bytes. + * @return A JavaScript Typed Array Object whose backing store is the same as the one pointed data, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createTypedArray(TypedArrayType type, const void *data, size_t byteLength); + + /** + * @brief Creates a JavaScript Typed Array Object with a se::Object, which is a ArrayBuffer, + if provide a null pointer,then will create a empty JavaScript Typed Array Object. + * @param[in] type The format of typed array. + * @param[in] obj A ArrayBuffer to TypedArray. + * @param[in] offset Offset of ArrayBuffer to create with. + * @param[in] byteLength The number of bytes pointed to by the parameter bytes. + * @return A JavaScript Typed Array Object which refers to the ArrayBuffer Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj); + static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset); + static Object *createTypedArrayWithBuffer(TypedArrayType type, const Object *obj, size_t offset, size_t byteLength); + + /** + * @brief Creates a JavaScript Array Buffer object from an existing pointer. + * @param[in] bytes A pointer to the byte buffer to be used as the backing store of the Typed Array object. + * @param[in] byteLength The number of bytes pointed to by the parameter bytes. + * @return A Array Buffer Object whose backing store is the same as the one pointed to data, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createArrayBufferObject(const void *data, size_t byteLength); + + using BufferContentsFreeFunc = void (*)(void *contents, size_t byteLength, void *userData); + static Object *createExternalArrayBufferObject(void *contents, size_t byteLength, BufferContentsFreeFunc freeFunc, void *freeUserData = nullptr); + + /** + * @brief Creates a JavaScript Object from a JSON formatted string. + * @param[in] jsonStr The utf-8 string containing the JSON string to be parsed. + * @return A JavaScript Object containing the parsed value, or nullptr if the input is invalid. + * @note The return value (non-null) has to be released manually. + */ + static Object *createJSONObject(const ccstd::string &jsonStr); + + /** + * @brief Creates a JavaScript Native Binding Object from an existing se::Class instance. + * @param[in] cls The se::Class instance which stores native callback informations. + * @return A JavaScript Native Binding Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createObjectWithClass(Class *cls); + + /** + * @brief Creates a JavaScript Native Binding Object from an JS constructor with no arguments, which behaves as `new MyClass();` in JS. + * @param[in] constructor The JS constructor + * @return A JavaScript object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createObjectWithConstructor(se::Object *constructor); + + /** + * @brief Creates a JavaScript Native Binding Object from an JS constructor with arguments, which behaves as `new MyClass(arg0, arg1, arg2, ...);` in JS. + * @param[in] constructor The JS constructor + * @param[in] args The arguments passed to the JS constructor + * @return A JavaScript object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + */ + static Object *createObjectWithConstructor(se::Object *constructor, const ValueArray &args); + + /** + * Gets the Proxy Target object + * @param proxy The JavaScript Proxy object. + * @return The target JavaScript object of the parameter. + */ + static Object *createProxyTarget(se::Object *proxy); + + /** + * @brief Gets a se::Object from an existing native object pointer. + * @param[in] ptr The native object pointer associated with the se::Object + * @return A JavaScript Native Binding Object, or nullptr if there is an error. + * @note The return value (non-null) has to be released manually. + * @deprecated Use NativePtrToObjectMap to query the native object. + */ + CC_DEPRECATED(3.7) + static Object *getObjectWithPtr(void *ptr); + + /** + * @brief Gets a property from an object. + * @param[in] name A utf-8 string containing the property's name. + * @param[out] value The property's value if object has the property, otherwise the undefined value. + * @return true if object has the property, otherwise false. + */ + inline bool getProperty(const char *name, Value *data) { + return getProperty(name, data, false); + } + + bool getProperty(const char *name, Value *data, bool cachePropertyName); + + inline bool getProperty(const ccstd::string &name, Value *value) { + return getProperty(name.c_str(), value); + } + + /** + * @brief Sets a property to an object. + * @param[in] name A utf-8 string containing the property's name. + * @param[in] value A value to be used as the property's value. + * @return true if the property is set successfully, otherwise false. + */ + bool setProperty(const char *name, const Value &data); + + inline bool setProperty(const ccstd::string &name, const Value &value) { + return setProperty(name.c_str(), value); + } + + /** + * @brief Delete a property of an object. + * @param[in] name A utf-8 string containing the property's name. + * @return true if the property is deleted successfully, otherwise false. + */ + bool deleteProperty(const char *name); + + /** + * @brief Defines a property with native accessor callbacks for an object. + * @param[in] name A utf-8 string containing the property's name. + * @param[in] getter The native callback for getter. + * @param[in] setter The native callback for setter. + * @return true if succeed, otherwise false. + */ + bool defineProperty(const char *name, v8::FunctionCallback getter, v8::FunctionCallback setter); + + bool defineOwnProperty(const char *name, const se::Value &value, bool writable = true, bool enumerable = true, bool configurable = true); + + /** + * @brief Defines a function with a native callback for an object. + * @param[in] funcName A utf-8 string containing the function name. + * @param[in] func The native callback triggered by JavaScript code. + * @return true if succeed, otherwise false. + */ + bool defineFunction(const char *funcName, v8::FunctionCallback func); + + /** + * @brief Tests whether an object can be called as a function. + * @return true if object can be called as a function, otherwise false. + */ + bool isFunction() const; + + /** + * @brief Calls an object as a function. + * @param[in] args A se::Value array of arguments to pass to the function. Pass se::EmptyValueArray if argumentCount is 0. + * @param[in] thisObject The object to use as "this," or NULL to use the global object as "this." + * @param[out] rval The se::Value that results from calling object as a function, passing nullptr if return value is ignored. + * @return true if object is a function and there isn't any errors, otherwise false. + */ + bool call(const ValueArray &args, Object *thisObject, Value *rval = nullptr); + + /** + * @brief Tests whether an object is a ES6 Map. + * @return true if object is a Map, otherwise false. + */ + bool isMap() const; + + /** + * @brief Tests whether an object is a ES6 WeakMap. + * @return true if object is a WeakMap, otherwise false. + */ + bool isWeakMap() const; + + /** + * @brief Tests whether an object is a ES6 Set. + * @return true if object is a Set, otherwise false. + */ + bool isSet() const; + + /** + * @brief Tests whether an object is a ES6 WeakSet. + * @return true if object is a WeakSet, otherwise false. + */ + bool isWeakSet() const; + + /** + * @brief Tests whether an object is an array. + * @return true if object is an array, otherwise false. + */ + bool isArray() const; + + /** + * @brief Gets array length of an array object. + * @param[out] length The array length to be stored. It's set to 0 if there is an error. + * @return true if succeed, otherwise false. + */ + bool getArrayLength(uint32_t *length) const; + + /** + * @brief Gets an element from an array object by numeric index. + * @param[in] index An integer value for index. + * @param[out] data The se::Value to be stored for the element in certain index. + * @return true if succeed, otherwise false. + */ + bool getArrayElement(uint32_t index, Value *data) const; + + /** + * @brief Sets an element to an array object by numeric index. + * @param[in] index An integer value for index. + * @param[in] data The se::Value to be set to array with certain index. + * @return true if succeed, otherwise false. + */ + bool setArrayElement(uint32_t index, const Value &data); + + /** @brief Tests whether an object is a typed array. + * @return true if object is a typed array, otherwise false. + */ + bool isTypedArray() const; + + /** @brief Tests whether an object is a proxy object. + * @return true if object is a proxy object, otherwise false. + */ + bool isProxy() const; + + /** + * @brief Gets the type of a typed array object. + * @return The type of a typed array object. + */ + TypedArrayType getTypedArrayType() const; + + /** + * @brief Gets backing store of a typed array object. + * @param[out] ptr A temporary pointer to the backing store of a JavaScript Typed Array object. + * @param[out] length The byte length of a JavaScript Typed Array object. + * @return true if succeed, otherwise false. + */ + bool getTypedArrayData(uint8_t **ptr, size_t *length) const; + + /** + * @brief Tests whether an object is an array buffer object. + * @return true if object is an array buffer object, otherwise false. + */ + bool isArrayBuffer() const; + + /** + * @brief Gets buffer data of an array buffer object. + * @param[out] ptr A pointer to the data buffer that serves as the backing store for a JavaScript Typed Array object. + * @param[out] length The number of bytes in a JavaScript data object. + * @return true if succeed, otherwise false. + */ + bool getArrayBufferData(uint8_t **ptr, size_t *length) const; + + /** + * @brief Gets all property names of an object. + * @param[out] allKeys A string vector to store all property names. + * @return true if succeed, otherwise false. + */ + bool getAllKeys(ccstd::vector *allKeys) const; + + // ES6 Map operations + + /** + * @brief Clear all elements in a ES6 map object. + */ + void clearMap(); + + /** + * @brief Remove an element in a ES6 map by a key. + * @param[in] key The key of the element to remove, it could be any type that se::Value supports. + * @return true if succeed, otherwise false. + */ + bool removeMapElement(const Value &key); + + /** + * @brief Get an element in a ES6 map by a key. + * @param[in] key Key of the element to get, it could be any type that se::Value supports. + * @param[out] outValue Out parameter, On success, *outValue receives the current value of the map element, or nullptr if no such element is found + * @return true if succeed, otherwise false. + */ + bool getMapElement(const Value &key, Value *outValue) const; + + /** + * @brief Set an element in a ES6 map by a key. + * @param[in] key Key of the element to get, it could be any type that se::Value supports. + * @param[in] value The value to set the map. + * @return true if succeed, otherwise false. + */ + bool setMapElement(const Value &key, const Value &value); + + /** + * @brief Get the size of a ES6 map + * @return The size of a ES6 map + */ + uint32_t getMapSize() const; + + /** + * @brief Get all elements in a ES6 map + * @return All elements in a ES6 map, they're stored in a std::vector instead of `std::map/unordered_map` since we don't know how to compare or make a hash for `se::Value`s. + */ + ccstd::vector> getAllElementsInMap() const; + + // ES6 Set operations + + /** + * @brief Clear all elements in a ES6 set object. + */ + void clearSet(); + + /** + * @brief Remove an element in a ES6 set. + * @param[in] value The value to remove. + * @return true if succeed, otherwise false. + */ + bool removeSetElement(const Value &value); + + /** + * @brief Add an element to a ES6 set. + * @param[in] value The value to set the set. + * @return true if succeed, otherwise false. + */ + bool addSetElement(const Value &value); + + /** + * @brief Check whether the value is in a ES6 set. + * @return true if the value is in the ES6 set, otherwise false. + */ + bool isElementInSet(const Value &value) const; + + /** + * @brief Get the size of a ES6 set. + * @return The size of a ES6 set. + */ + uint32_t getSetSize() const; + + /** + * @brief Get all elements in a ES6 set. + * @return All elements in a ES6 set. + */ + ValueArray getAllElementsInSet() const; + + void setPrivateObject(PrivateObjectBase *data); + + template + inline void setPrivateObject(TypedPrivateObject *data) { + setPrivateObject(static_cast(data)); + if constexpr (cc::has_setScriptObject::value) { + data->template get()->setScriptObject(this); + } + } + + PrivateObjectBase *getPrivateObject() const; + + /* + * @brief Gets an object's private data. + * @return A void* that is the object's private data, if the object has private data, otherwise nullptr. + */ + inline void *getPrivateData() const { + return _privateData; + } + + /** + * @brief Sets a pointer to private data on an object and use smart pointer to hold it. + * + * If the pointer is an instance of `cc::RefCounted`, an `cc::IntrusivePtr` will be created to hold + * the reference to the object, otherwise a `std::shared_ptr` object will be used. + * When the JS object is freed by GC, the corresponding smart pointer `IntrusivePtr/shared_ptr` will also be destroyed. + * + * If you do not want the pointer to be released by GC, you can call `setRawPrivateData`. + * + * @param[in] data A void* to set as the object's private data. + * @note This method will associate private data with se::Object by ccstd::unordered_map::emplace. + * It's used for search a se::Object via a void* private data. + */ + template + inline void setPrivateData(T *data) { + static_assert(!std::is_void::value, "void * is not allowed for private data"); + setPrivateObject(se::make_shared_private_object(data)); + } + + /** + * @brief Use a InstrusivePtr to hold private data on the se::Object. + * + * @tparam T + * @param data A intrusive pointer object + */ + template + inline void setPrivateData(const cc::IntrusivePtr &data) { + setPrivateObject(se::ccintrusive_ptr_private_object(data)); + } + + /** + * @brief Use a std::shared_ptr to hold private data on the se::Object. + * + * @tparam T + * @param data A shared_ptr object + */ + template + inline void setPrivateData(const std::shared_ptr &data) { + setPrivateObject(se::shared_ptr_private_object(data)); + } + + /** + * @brief Set pointer to the private data on an object and will not use smart pointer to hold it. + * + * @tparam T + * @param data + * @param tryDestroyInGC When GCing the JS object, whether to `delete` the `data` pointer. + */ + template + inline void setRawPrivateData(T *data, bool tryDestroyInGC = false) { + static_assert(!std::is_void::value, "void * is not allowed for private data"); + auto *privateObject = se::rawref_private_object(data); + if (tryDestroyInGC) { + privateObject->tryAllowDestroyInGC(); + } + setPrivateObject(privateObject); + } + + /** + * @brief Get the underlying private data as std::shared_ptr + * + * @tparam T + * @return std::shared_ptr + */ + template + inline std::shared_ptr getPrivateSharedPtr() const { + assert(_privateObject->isSharedPtr()); + return static_cast *>(_privateObject)->getData(); + } + + /** + * @brief Get the underlying private data as InstrusivePtr + * + * @tparam T + * @return cc::IntrusivePtr + */ + template + inline cc::IntrusivePtr getPrivateInstrusivePtr() const { + assert(_privateObject->isCCIntrusivePtr()); + return static_cast *>(_privateObject)->getData(); + } + + template + inline T *getTypedPrivateData() const { + return reinterpret_cast(getPrivateData()); + } + + /** + * @brief Clears private data of an object. + * @param clearMapping Whether to clear the mapping of native object & se::Object. + */ + void clearPrivateData(bool clearMapping = true); + + /** + * @brief Sets whether to clear the mapping of native object & se::Object in finalizer + */ + void setClearMappingInFinalizer(bool v) { _clearMappingInFinalizer = v; } + + /** + * @brief Roots an object from garbage collection. + * @note Use this method when you want to store an object in a global or on the heap, where the garbage collector will not be able to discover your reference to it. + * An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection. + */ + void root(); + + /** + * @brief Unroots an object from garbage collection. + * @note An object may be rooted multiple times and must be unrooted an equal number of times before becoming eligible for garbage collection. + */ + void unroot(); + + /** + * @brief Tests whether an object is rooted. + * @return true if it has been already rooted, otherwise false. + */ + bool isRooted() const; + + /** + * @brief Tests whether two objects are strict equal, as compared by the JS === operator. + * @param[in] o The object to be tested with this object. + * @return true if the two values are strict equal, otherwise false. + */ + bool strictEquals(Object *o) const; + + /** + * @brief Attaches an object to current object. + * @param[in] obj The object to be attached. + * @return true if succeed, otherwise false. + * @note This method will set `obj` as a property of current object, therefore the lifecycle of child object will depend on current object, + * which means `obj` may be garbage collected only after current object is garbage collected. + * It's normally used in binding a native callback method. For example: + + ```javascript + var self = this; + someObject.setCallback(function(){}, self); + ``` + + ```c++ + static bool SomeObject_setCallback(se::State& s) + { + SomeObject* cobj = (SomeObject*)s.nativeThisObject(); + const auto& args = s.args(); + size_t argc = args.size(); + if (argc == 2) { + std::function arg0; + do { + if (args[0].isObject() && args[0].toObject()->isFunction()) + { + se::Value jsThis(args[1]); + se::Value jsFunc(args[0]); + + jsThis.toObject()->attachObject(jsFunc.toObject()); + + auto lambda = [=]() -> void { + ... + // Call jsFunc stuff... + ... + }; + arg0 = lambda; + } + else + { + arg0 = nullptr; + } + } while(false); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->setCallback(arg0); + return true; + } + + return false; + } + ``` + */ + bool attachObject(Object *obj); + + /** + * @brief Detaches an object from current object. + * @param[in] obj The object to be detached. + * @return true if succeed, otherwise false. + * @note The attached object will not be released if current object is not garbage collected. + */ + bool detachObject(Object *obj); + + /** + * @brief Returns the string for describing current object. + * @return The string for describing current object. + */ + ccstd::string toString() const; + + ccstd::string toStringExt() const; + + // Private API used in wrapper + static Object *_createJSObject(Class *cls, v8::Local obj); // NOLINT(readability-identifier-naming) + v8::Local _getJSObject() const; // NOLINT(readability-identifier-naming) + ObjectWrap &_getWrap(); // NOLINT(readability-identifier-naming) + Class *_getClass() const; // NOLINT(readability-identifier-naming) + + void _setFinalizeCallback(V8FinalizeFunc finalizeCb); // NOLINT(readability-identifier-naming) + bool _isNativeFunction() const; // NOLINT(readability-identifier-naming) + // + + #if CC_DEBUG && CC_DEBUG_JS_OBJECT_ID + uint32_t getObjectId() const { return _objectId; } + #endif + +private: + static void nativeObjectFinalizeHook(Object *seObj); + static void setIsolate(v8::Isolate *isolate); + static void cleanup(); + + Object(); + ~Object() override; + + bool init(Class *cls, v8::Local obj); + v8::Local getProxyTarget() const; + + Class *_cls{nullptr}; + ObjectWrap _obj; + uint32_t _rootCount{0}; + + PrivateObjectBase *_privateObject{nullptr}; + void *_privateData{nullptr}; + V8FinalizeFunc _finalizeCb{nullptr}; + + bool _clearMappingInFinalizer{true}; + + #if CC_DEBUG && CC_DEBUG_JS_OBJECT_ID + uint32_t _objectId = 0; + #endif + #if JSB_TRACK_OBJECT_CREATION + ccstd::string _objectCreationStackFrame; + #endif + + friend class ScriptEngine; + friend class JSBPersistentHandleVisitor; +}; + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/ObjectWrap.cpp b/cocos/bindings/jswrapper/v8/ObjectWrap.cpp new file mode 100644 index 0000000..75a2863 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/ObjectWrap.cpp @@ -0,0 +1,151 @@ +/**************************************************************************** + Copyright Joyent, Inc. and other Node contributors. + Copyright (c) 2021 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "ObjectWrap.h" +#include "Object.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + +namespace { +bool gIsIsolateValid = false; +} + +namespace se { + +/* static */ +void ObjectWrap::setIsolateValid(bool valid) { + gIsIsolateValid = valid; +} + +ObjectWrap::ObjectWrap() = default; + +ObjectWrap::~ObjectWrap() { + if (!gIsIsolateValid || persistent().IsEmpty()) { + return; + } + //cjh CC_ASSERT(persistent().IsNearDeath()); + persistent().ClearWeak(); + persistent().Reset(); +} + +bool ObjectWrap::init(v8::Local handle, Object *parent, bool registerWeak) { + CC_ASSERT(persistent().IsEmpty()); + _parent = parent; + _registerWeakCallback = registerWeak; + persistent().Reset(v8::Isolate::GetCurrent(), handle); + makeWeak(); + return true; +} + +void ObjectWrap::setFinalizeCallback(FinalizeFunc finalizeCb) { + _finalizeCb = finalizeCb; +} + +/*static*/ +void *ObjectWrap::unwrap(v8::Local handle, uint32_t fieldIndex) { + CC_ASSERT(!handle.IsEmpty()); + CC_ASSERT(handle->InternalFieldCount() > 0); + CC_ASSERT(fieldIndex >= 0 && fieldIndex < 1); + return handle->GetAlignedPointerFromInternalField(static_cast(fieldIndex)); +} +void ObjectWrap::wrap(void *nativeObj, uint32_t fieldIndex) { + CC_ASSERT(handle()->InternalFieldCount() > 0); + CC_ASSERT(fieldIndex >= 0 && fieldIndex < 1); + handle()->SetAlignedPointerInInternalField(static_cast(fieldIndex), nativeObj); + if (nativeObj) { + persistent().SetWrapperClassId(MAGIC_CLASS_ID_JSB); + } else { + persistent().SetWrapperClassId(0); + } +} + +v8::Local ObjectWrap::handle() { + return handle(v8::Isolate::GetCurrent()); +} + +v8::Local ObjectWrap::handle(v8::Isolate *isolate) { + return v8::Local::New(isolate, persistent()); +} + +v8::Persistent &ObjectWrap::persistent() { + return _handle; +} + +void ObjectWrap::makeWeak() { + // V8 offical documentation said that: + // kParameter will pass a void* parameter back to the callback, kInternalFields + // will pass the first two internal fields back to the callback, + // kFinalizer will pass a void* parameter back, but is invoked before the object is + // actually collected, so it can be resurrected. In the last case, it is not + // possible to request a second pass callback. + // enum class WeakCallbackType { kParameter, kInternalFields, kFinalizer }; + // + // NOTE: We get random crashes while previewing material in editor's inspector window, + // the reason is that kFinalizer will trigger weak callback when some assets are + // still being used, jsbinding code will get a dead se::Object pointer that was + // freed by weak callback. According V8 documentation, kParameter is a better option. + if (_registerWeakCallback) { + persistent().SetWeak(_parent, weakCallback, v8::WeakCallbackType::kParameter); + } else { + persistent().SetWeak(); + } + // persistent().MarkIndependent(); +} + +void ObjectWrap::ref() { + CC_ASSERT(!persistent().IsEmpty()); + persistent().ClearWeak(); + _refs++; +} + +void ObjectWrap::unref() { + if (!gIsIsolateValid) { + return; + } + CC_ASSERT(!persistent().IsEmpty()); + CC_ASSERT(!persistent().IsWeak()); + CC_ASSERT_GT(_refs, 0); + if (--_refs == 0) { + makeWeak(); + } +} + +/*static*/ +void ObjectWrap::weakCallback(const v8::WeakCallbackInfo &data) { + Object *seObj = data.GetParameter(); + ObjectWrap *wrap = &seObj->_getWrap(); + + CC_ASSERT(wrap->_refs == 0); + wrap->_handle.Reset(); + if (wrap->_finalizeCb != nullptr) { + wrap->_finalizeCb(seObj); + } else { + CC_ABORT(); + } +} + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/ObjectWrap.h b/cocos/bindings/jswrapper/v8/ObjectWrap.h new file mode 100644 index 0000000..485cb82 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/ObjectWrap.h @@ -0,0 +1,88 @@ +/**************************************************************************** + Copyright Joyent, Inc. and other Node contributors. + Copyright (c) 2021 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../config.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + + #include "../PrivateObject.h" + #include "Base.h" + +namespace se { + +class ObjectWrap { +public: + static constexpr uint16_t MAGIC_CLASS_ID_JSB = 0x1234; + + ObjectWrap(); + ~ObjectWrap(); + + bool init(v8::Local handle, Object *parent, bool registerWeak); + using FinalizeFunc = void (*)(Object *seObj); + void setFinalizeCallback(FinalizeFunc finalizeCb); + + v8::Local handle(); + v8::Local handle(v8::Isolate *isolate); + v8::Persistent &persistent(); + + void wrap(void *nativeObj, uint32_t fieldIndex); + static void *unwrap(v8::Local handle, uint32_t fieldIndex); + /* Ref() marks the object as being attached to an event loop. + * Refed objects will not be garbage collected, even if + * all references are lost. + */ + void ref(); + + /* Unref() marks an object as detached from the event loop. This is its + * default state. When an object with a "weak" reference changes from + * attached to detached state it will be freed. Be careful not to access + * the object after making this call as it might be gone! + * (A "weak reference" means an object that only has a + * persistent handle.) + * + * DO NOT CALL THIS FROM DESTRUCTOR + */ + void unref(); + + static void setIsolateValid(bool valid); + +private: + static void weakCallback(const v8::WeakCallbackInfo &data); + + void makeWeak(); + + int _refs{0}; // ro + v8::Persistent _handle; + FinalizeFunc _finalizeCb{nullptr}; + Object *_parent{nullptr}; + + bool _registerWeakCallback{false}; +}; + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/README.md b/cocos/bindings/jswrapper/v8/README.md new file mode 100644 index 0000000..67652a4 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/README.md @@ -0,0 +1,2 @@ +V8 inspector from node commit (4e8bc7181c1f2491e187477798d433a4488f43d4) + diff --git a/cocos/bindings/jswrapper/v8/ScriptEngine.cpp b/cocos/bindings/jswrapper/v8/ScriptEngine.cpp new file mode 100644 index 0000000..3c71875 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/ScriptEngine.cpp @@ -0,0 +1,1254 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "ScriptEngine.h" +#include "engine/EngineEvents.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + + #include "../MappingUtils.h" + #include "../State.h" + #include "Class.h" + #include "Object.h" + #include "Utils.h" + #include "base/Log.h" + #include "base/std/container/unordered_map.h" + #include "platform/FileUtils.h" + #include "plugins/bus/EventBus.h" + + #include + + #if SE_ENABLE_INSPECTOR + #include "debugger/env.h" + #include "debugger/inspector_agent.h" + #include "debugger/node.h" + + #endif + + #include "base/std/container/array.h" + + #define EXPOSE_GC "__jsb_gc__" + +const unsigned int JSB_STACK_FRAME_LIMIT = 20; + + #ifdef CC_DEBUG +unsigned int jsbInvocationCount = 0; +ccstd::unordered_map jsbFunctionInvokedRecords; + #endif + + #define RETRUN_VAL_IF_FAIL(cond, val) \ + if (!(cond)) return val + +namespace se { +AutoHandleScope::AutoHandleScope() +: _handleScope(v8::Isolate::GetCurrent()) { + #if CC_EDITOR + ScriptEngine::getInstance()->_getContext()->Enter(); + #endif +} + +AutoHandleScope::~AutoHandleScope() { // NOLINT + #if CC_EDITOR + ScriptEngine::getInstance()->_getContext()->Exit(); + #endif +} + +namespace { + +void seLogCallback(const v8::FunctionCallbackInfo &info) { + if (info[0]->IsString()) { + v8::String::Utf8Value utf8(v8::Isolate::GetCurrent(), info[0]); + cc::Log::logMessage(cc::LogType::KERNEL, cc::LogLevel::LEVEL_DEBUG + , "JS: %s", *utf8); + } +} + +void seForceGC(const v8::FunctionCallbackInfo & /*info*/) { + ScriptEngine::getInstance()->garbageCollect(); +} + +ccstd::string stackTraceToString(v8::Local stack) { + ccstd::string stackStr; + if (stack.IsEmpty()) { + return stackStr; + } + + char tmp[100] = {0}; + for (int i = 0, e = stack->GetFrameCount(); i < e; ++i) { + v8::Local frame = stack->GetFrame(v8::Isolate::GetCurrent(), i); + v8::Local script = frame->GetScriptName(); + ccstd::string scriptName; + if (!script.IsEmpty()) { + scriptName = *v8::String::Utf8Value(v8::Isolate::GetCurrent(), script); + } + + v8::Local func = frame->GetFunctionName(); + ccstd::string funcName; + if (!func.IsEmpty()) { + funcName = *v8::String::Utf8Value(v8::Isolate::GetCurrent(), func); + } + + stackStr += " - ["; + snprintf(tmp, sizeof(tmp), "%d", i); + stackStr += tmp; + stackStr += "]"; + stackStr += (funcName.empty() ? "anonymous" : funcName.c_str()); + stackStr += "@"; + stackStr += (scriptName.empty() ? "(no filename)" : scriptName.c_str()); + stackStr += ":"; + snprintf(tmp, sizeof(tmp), "%d", frame->GetLineNumber()); + stackStr += tmp; + + if (i < (e - 1)) { + stackStr += "\n"; + } + } + + return stackStr; +} + +se::Value oldConsoleLog; +se::Value oldConsoleDebug; +se::Value oldConsoleInfo; +se::Value oldConsoleWarn; +se::Value oldConsoleError; +se::Value oldConsoleAssert; + +bool jsbConsoleFormatLog(State &state, cc::LogLevel level, int msgIndex = 0) { + if (msgIndex < 0) { + return false; + } + + const auto &args = state.args(); + int argc = static_cast(args.size()); + if ((argc - msgIndex) == 1) { + ccstd::string msg = args[msgIndex].toStringForce(); + cc::Log::logMessage(cc::LogType::KERNEL, level + ,"JS: %s", msg.c_str()); + } else if (argc > 1) { + ccstd::string msg = args[msgIndex].toStringForce(); + size_t pos; + for (int i = (msgIndex + 1); i < argc; ++i) { + pos = msg.find('%'); + if (pos != ccstd::string::npos && pos != (msg.length() - 1) && (msg[pos + 1] == 'd' || msg[pos + 1] == 's' || msg[pos + 1] == 'f')) { + msg.replace(pos, 2, args[i].toStringForce()); + } else { + msg += " " + args[i].toStringForce(); + } + } + cc::Log::logMessage(cc::LogType::KERNEL, level + ,"JS: %s", msg.c_str()); + } + + return true; +} + +bool jsbConsoleLog(State &s) { + jsbConsoleFormatLog(s, cc::LogLevel::LEVEL_DEBUG); + oldConsoleLog.toObject()->call(s.args(), s.thisObject()); + return true; +} +SE_BIND_FUNC(jsbConsoleLog) + +bool jsbConsoleDebug(State &s) { + jsbConsoleFormatLog(s, cc::LogLevel::LEVEL_DEBUG); + oldConsoleDebug.toObject()->call(s.args(), s.thisObject()); + return true; +} +SE_BIND_FUNC(jsbConsoleDebug) + +bool jsbConsoleInfo(State &s) { + jsbConsoleFormatLog(s, cc::LogLevel::INFO); + oldConsoleInfo.toObject()->call(s.args(), s.thisObject()); + return true; +} +SE_BIND_FUNC(jsbConsoleInfo) + +bool jsbConsoleWarn(State &s) { + jsbConsoleFormatLog(s, cc::LogLevel::WARN); + oldConsoleWarn.toObject()->call(s.args(), s.thisObject()); + return true; +} +SE_BIND_FUNC(jsbConsoleWarn) + +bool jsbConsoleError(State &s) { + jsbConsoleFormatLog(s, cc::LogLevel::ERR); + oldConsoleError.toObject()->call(s.args(), s.thisObject()); + return true; +} +SE_BIND_FUNC(jsbConsoleError) + +bool jsbConsoleAssert(State &s) { + const auto &args = s.args(); + if (!args.empty()) { + if (args[0].isBoolean() && !args[0].toBoolean()) { + jsbConsoleFormatLog(s, cc::LogLevel::WARN, 1); + oldConsoleAssert.toObject()->call(s.args(), s.thisObject()); + } + } + return true; +} +SE_BIND_FUNC(jsbConsoleAssert) + + /* + * The unique V8 platform instance + */ + #if !CC_EDITOR +class ScriptEngineV8Context { +public: + ScriptEngineV8Context() { + platform = v8::platform::NewDefaultPlatform().release(); + v8::V8::InitializePlatform(platform); + ccstd::string flags; + // NOTICE: spaces are required between flags + flags.append(" --expose-gc-as=" EXPOSE_GC); + // for bytecode support + flags.append(" --no-flush-bytecode --no-lazy"); + // v8 trace gc + // flags.append(" --trace-gc"); + + // NOTICE: should be remove flag --no-turbo-escape after upgrade v8 to 10.x + // https://github.com/cocos/cocos-engine/issues/13342 + flags.append(" --no-turbo-escape"); + + #if (CC_PLATFORM == CC_PLATFORM_IOS) + flags.append(" --jitless"); + #endif + if (!flags.empty()) { + v8::V8::SetFlagsFromString(flags.c_str(), static_cast(flags.length())); + } + + bool ok = v8::V8::Initialize(); + CC_ASSERT(ok); + } + + ~ScriptEngineV8Context() { + v8::V8::Dispose(); +#if V8_MAJOR_VERSION > 9 || ( V8_MAJOR_VERSION == 9 && V8_MINOR_VERSION > 7) + v8::V8::DisposePlatform(); +#else + v8::V8::ShutdownPlatform(); +#endif + delete platform; + } + v8::Platform *platform = nullptr; +}; + +ScriptEngineV8Context *gSharedV8 = nullptr; + #endif // CC_EDITOR +} // namespace + +ScriptEngine *ScriptEngine::instance = nullptr; +ScriptEngine::DebuggerInfo ScriptEngine::debuggerInfo; + +void ScriptEngine::callExceptionCallback(const char *location, const char *message, const char *stack) { + if (_nativeExceptionCallback) { + _nativeExceptionCallback(location, message, stack); + } + if (_jsExceptionCallback) { + _jsExceptionCallback(location, message, stack); + } +} + +void ScriptEngine::onFatalErrorCallback(const char *location, const char *message) { + ccstd::string errorStr = "[FATAL ERROR] location: "; + errorStr += location; + errorStr += ", message: "; + errorStr += message; + + SE_LOGE("%s\n", errorStr.c_str()); + + getInstance()->callExceptionCallback(location, message, "(no stack information)"); +} + +void ScriptEngine::onOOMErrorCallback(const char *location, +#if V8_MAJOR_VERSION > 10 || (V8_MAJOR_VERSION == 10 && V8_MINOR_VERSION > 4) + const v8::OOMDetails& details +#else + bool isHeapOom +#endif + ) { + ccstd::string errorStr = "[OOM ERROR] location: "; + errorStr += location; + ccstd::string message; + message = "is heap out of memory: "; +#if V8_MAJOR_VERSION > 10 || (V8_MAJOR_VERSION == 10 && V8_MINOR_VERSION > 4) + if (details.is_heap_oom) { +#else + if (isHeapOom) { +#endif + message += "true"; + } else { + message += "false"; + } + + errorStr += ", " + message; + SE_LOGE("%s\n", errorStr.c_str()); + getInstance()->callExceptionCallback(location, message.c_str(), "(no stack information)"); +} + +void ScriptEngine::onMessageCallback(v8::Local message, v8::Local /*data*/) { + ScriptEngine *thiz = getInstance(); + v8::Local msg = message->Get(); + Value msgVal; + internal::jsToSeValue(v8::Isolate::GetCurrent(), msg, &msgVal); + CC_ASSERT(msgVal.isString()); + v8::ScriptOrigin origin = message->GetScriptOrigin(); + Value resouceNameVal; + internal::jsToSeValue(v8::Isolate::GetCurrent(), origin.ResourceName(), &resouceNameVal); + Value line(origin.LineOffset()); + Value column(origin.ColumnOffset()); + + ccstd::string location = resouceNameVal.toStringForce() + ":" + line.toStringForce() + ":" + column.toStringForce(); + + ccstd::string errorStr = msgVal.toString() + ", location: " + location; + ccstd::string stackStr = stackTraceToString(message->GetStackTrace()); + if (!stackStr.empty()) { + if (line.toInt32() == 0) { + location = "(see stack)"; + } + errorStr += "\nSTACK:\n" + stackStr; + } + SE_LOGE("ERROR: %s\n", errorStr.c_str()); + + thiz->callExceptionCallback(location.c_str(), msgVal.toString().c_str(), stackStr.c_str()); + + if (!thiz->_isErrorHandleWorking) { + thiz->_isErrorHandleWorking = true; + + Value errorHandler; + if (thiz->_globalObj && thiz->_globalObj->getProperty("__errorHandler", &errorHandler) && errorHandler.isObject() && errorHandler.toObject()->isFunction()) { + ValueArray args; + args.push_back(resouceNameVal); + args.push_back(line); + args.push_back(msgVal); + args.push_back(Value(stackStr)); + errorHandler.toObject()->call(args, thiz->_globalObj); + } + + thiz->_isErrorHandleWorking = false; + } else { + SE_LOGE("ERROR: __errorHandler has exception\n"); + } +} +/** + * Bug in v8 stacktrace: + * "handlerAddedAfterPromiseRejected" event is triggered if a resolve handler is added. + * But if no reject handler is added, then "unhandledRejectedPromise" exception will be called again, but the stacktrace this time become empty + * LastStackTrace is used to store it. + */ +void ScriptEngine::pushPromiseExeception(const v8::Local &promise, const char *event, const char *stackTrace) { + using element_type = decltype(_promiseArray)::value_type; + element_type *current; + + auto itr = std::find_if(_promiseArray.begin(), _promiseArray.end(), [&](const auto &e) -> bool { + return std::get<0>(e)->Get(_isolate) == promise; + }); + + if (itr == _promiseArray.end()) { // Not found, create one + _promiseArray.emplace_back(std::make_unique>(), ccstd::vector{}); + std::get<0>(_promiseArray.back())->Reset(_isolate, promise); + current = &_promiseArray.back(); + } else { + current = &(*itr); + } + + auto &exceptions = std::get<1>(*current); + if (strcmp(event, "handlerAddedAfterPromiseRejected") == 0) { + for (int i = 0; i < exceptions.size(); i++) { + if (exceptions[i].event == "unhandledRejectedPromise") { + _lastStackTrace = exceptions[i].stackTrace; + exceptions.erase(exceptions.begin() + i); + return; + } + } + } + exceptions.push_back(PromiseExceptionMsg{event, stackTrace}); +} + +void ScriptEngine::handlePromiseExceptions() { + if (_promiseArray.empty()) { + return; + } + for (auto &exceptionsPair : _promiseArray) { + auto &exceptionVector = std::get<1>(exceptionsPair); + for (const auto &exceptions : exceptionVector) { + getInstance()->callExceptionCallback("", exceptions.event.c_str(), exceptions.stackTrace.c_str()); + } + std::get<0>(exceptionsPair).get()->Reset(); + } + _promiseArray.clear(); + _lastStackTrace.clear(); +} + +void ScriptEngine::onPromiseRejectCallback(v8::PromiseRejectMessage msg) { + /* Reject message contains different types, yet not every type will lead to the exception in the end. + * A detection is needed: if the reject handler is added after the promise is triggered, it's actually valid.*/ + v8::Isolate *isolate = getInstance()->_isolate; + v8::HandleScope scope(isolate); + v8::TryCatch tryCatch(isolate); + std::stringstream ss; + auto event = msg.GetEvent(); + v8::Local value = msg.GetValue(); + auto promiseName = msg.GetPromise()->GetConstructorName(); + + if (!value.IsEmpty()) { + // prepend error object to stack message + // v8::MaybeLocal maybeStr = value->ToString(isolate->GetCurrentContext()); + if (value->IsString()) { + v8::Local str = value->ToString(isolate->GetCurrentContext()).ToLocalChecked(); + + v8::String::Utf8Value valueUtf8(isolate, str); + auto *strp = *valueUtf8; + if (strp == nullptr) { + ss << "value: null" << std::endl; + auto tn = value->TypeOf(isolate); + v8::String::Utf8Value tnUtf8(isolate, tn); + strp = *tnUtf8; + ss << " type: " << strp << std::endl; + } + + } else if (value->IsObject()) { + v8::MaybeLocal json = v8::JSON::Stringify(isolate->GetCurrentContext(), value); + if (!json.IsEmpty()) { + v8::String::Utf8Value jsonStr(isolate, json.ToLocalChecked()); + auto *strp = *jsonStr; + if (strp) { + ss << " obj: " << strp << std::endl; + } else { + ss << " obj: null" << std::endl; + } + } else { + v8::Local obj = value->ToObject(isolate->GetCurrentContext()).ToLocalChecked(); + v8::Local attrNames = obj->GetOwnPropertyNames(isolate->GetCurrentContext()).ToLocalChecked(); + + if (!attrNames.IsEmpty()) { + uint32_t size = attrNames->Length(); + for (uint32_t i = 0; i < size; i++) { + se::Value e; + v8::Local attrName = attrNames->Get(isolate->GetCurrentContext(), i) + .ToLocalChecked() + ->ToString(isolate->GetCurrentContext()) + .ToLocalChecked(); + v8::String::Utf8Value attrUtf8(isolate, attrName); + auto *strp = *attrUtf8; + ss << " obj.property " << strp << std::endl; + } + ss << " obj: JSON.parse failed!" << std::endl; + } + } + } + v8::String::Utf8Value valuePromiseConstructor(isolate, promiseName); + auto *strp = *valuePromiseConstructor; + if (strp) { + ss << "PromiseConstructor " << strp; + } + } + auto stackStr = getInstance()->getCurrentStackTrace(); + ss << "stacktrace: " << std::endl; + if (stackStr.empty()) { + ss << getInstance()->_lastStackTrace << std::endl; + } else { + ss << stackStr << std::endl; + } + // Check event immediately, for certain case throw exception. + switch (event) { + case v8::kPromiseRejectWithNoHandler: + getInstance()->pushPromiseExeception(msg.GetPromise(), "unhandledRejectedPromise", ss.str().c_str()); + break; + case v8::kPromiseHandlerAddedAfterReject: + getInstance()->pushPromiseExeception(msg.GetPromise(), "handlerAddedAfterPromiseRejected", ss.str().c_str()); + break; + case v8::kPromiseRejectAfterResolved: + getInstance()->callExceptionCallback("", "rejectAfterPromiseResolved", stackStr.c_str()); + break; + case v8::kPromiseResolveAfterResolved: + getInstance()->callExceptionCallback("", "resolveAfterPromiseResolved", stackStr.c_str()); + break; + } +} + +ScriptEngine *ScriptEngine::getInstance() { + return ScriptEngine::instance; +} + +void ScriptEngine::destroyInstance() { +} + +ScriptEngine::ScriptEngine() +: _isolate(nullptr), + _handleScope(nullptr), + _globalObj(nullptr) + #if SE_ENABLE_INSPECTOR + , + _env(nullptr), + _isolateData(nullptr) + #endif + , + _debuggerServerPort(0), + _vmId(0), + _isValid(false), + _isGarbageCollecting(false), + _isInCleanup(false), + _isErrorHandleWorking(false) { + #if !CC_EDITOR + if (!gSharedV8) { + gSharedV8 = ccnew ScriptEngineV8Context(); + } + #endif + + ScriptEngine::instance = this; +} + +ScriptEngine::~ScriptEngine() { // NOLINT(bugprone-exception-escape) + cleanup(); + /** + * v8::V8::Initialize() can only be called once for a process. + * Engine::restart() will delete ScriptEngine and re-create it. + * So gSharedV8 variable should not be released and it will be re-used. + */ + // if (gSharedV8) { + // delete gSharedV8; + // gSharedV8 = nullptr; + // } + ScriptEngine::instance = nullptr; +} + +bool ScriptEngine::postInit() { + v8::HandleScope hs(_isolate); + // editor has it's own isolate,no need to enter and set callback. + #if !CC_EDITOR + _isolate->Enter(); + _isolate->SetCaptureStackTraceForUncaughtExceptions(true, JSB_STACK_FRAME_LIMIT, v8::StackTrace::kOverview); + _isolate->SetFatalErrorHandler(onFatalErrorCallback); + _isolate->SetOOMErrorHandler(onOOMErrorCallback); + _isolate->AddMessageListener(onMessageCallback); + _isolate->SetPromiseRejectCallback(onPromiseRejectCallback); + #endif + NativePtrToObjectMap::init(); + + Class::setIsolate(_isolate); + Object::setIsolate(_isolate); + + _globalObj = Object::_createJSObject(nullptr, _isolate->GetCurrentContext()->Global()); + _globalObj->root(); + _globalObj->setProperty("window", Value(_globalObj)); + + #if !CC_EDITOR + se::Value consoleVal; + if (_globalObj->getProperty("console", &consoleVal) && consoleVal.isObject()) { + consoleVal.toObject()->getProperty("log", &oldConsoleLog); + consoleVal.toObject()->defineFunction("log", _SE(jsbConsoleLog)); + + consoleVal.toObject()->getProperty("debug", &oldConsoleDebug); + consoleVal.toObject()->defineFunction("debug", _SE(jsbConsoleDebug)); + + consoleVal.toObject()->getProperty("info", &oldConsoleInfo); + consoleVal.toObject()->defineFunction("info", _SE(jsbConsoleInfo)); + + consoleVal.toObject()->getProperty("warn", &oldConsoleWarn); + consoleVal.toObject()->defineFunction("warn", _SE(jsbConsoleWarn)); + + consoleVal.toObject()->getProperty("error", &oldConsoleError); + consoleVal.toObject()->defineFunction("error", _SE(jsbConsoleError)); + + consoleVal.toObject()->getProperty("assert", &oldConsoleAssert); + consoleVal.toObject()->defineFunction("assert", _SE(jsbConsoleAssert)); + } + #endif + _globalObj->setProperty("scriptEngineType", se::Value("V8")); + + _globalObj->defineFunction("log", seLogCallback); + _globalObj->defineFunction("forceGC", seForceGC); + + _globalObj->getProperty(EXPOSE_GC, &_gcFuncValue); + if (_gcFuncValue.isObject() && _gcFuncValue.toObject()->isFunction()) { + _gcFunc = _gcFuncValue.toObject(); + } else { + _gcFunc = nullptr; + } + + _isValid = true; + + // @deprecated since 3.7.0 + cc::plugin::send(cc::plugin::BusType::SCRIPT_ENGINE, cc::plugin::ScriptEngineEvent::POST_INIT); + + cc::events::ScriptEngine::broadcast(cc::ScriptEngineEvent::AFTER_INIT); + + for (const auto &hook : _afterInitHookArray) { + hook(); + } + _afterInitHookArray.clear(); + + return _isValid; +} + +bool ScriptEngine::init() { + return init(nullptr); +} + +bool ScriptEngine::init(v8::Isolate *isolate) { + cleanup(); + SE_LOGD("Initializing V8, version: %s\n", v8::V8::GetVersion()); + ++_vmId; + + _engineThreadId = std::this_thread::get_id(); + + cc::events::ScriptEngine::broadcast(cc::ScriptEngineEvent::BEFORE_INIT); + + for (const auto &hook : _beforeInitHookArray) { + hook(); + } + _beforeInitHookArray.clear(); + + if (isolate != nullptr) { + _isolate = isolate; + v8::Local context = _isolate->GetCurrentContext(); + _context.Reset(_isolate, context); + } else { + static v8::ArrayBuffer::Allocator *arrayBufferAllocator{nullptr}; + if (arrayBufferAllocator == nullptr) { + arrayBufferAllocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); + } + v8::Isolate::CreateParams createParams; + createParams.array_buffer_allocator = arrayBufferAllocator; + _isolate = v8::Isolate::New(createParams); + v8::HandleScope hs(_isolate); + _context.Reset(_isolate, v8::Context::New(_isolate)); + _context.Get(_isolate)->Enter(); + } + + return postInit(); +} + +void ScriptEngine::cleanup() { + if (!_isValid) { + return; + } + + SE_LOGD("ScriptEngine::cleanup begin ...\n"); + _isInCleanup = true; + + cc::events::ScriptEngine::broadcast(cc::ScriptEngineEvent::BEFORE_CLEANUP); + + { + AutoHandleScope hs; + for (const auto &hook : _beforeCleanupHookArray) { + hook(); + } + _beforeCleanupHookArray.clear(); + + _stringPool.clear(); + + SAFE_DEC_REF(_globalObj); + Object::cleanup(); + Class::cleanup(); + garbageCollect(); + + oldConsoleLog.setUndefined(); + oldConsoleDebug.setUndefined(); + oldConsoleInfo.setUndefined(); + oldConsoleWarn.setUndefined(); + oldConsoleError.setUndefined(); + oldConsoleAssert.setUndefined(); + + #if SE_ENABLE_INSPECTOR + + if (_env != nullptr) { + _env->inspector_agent()->Disconnect(); + _env->inspector_agent()->Stop(); + } + + if (_isolateData != nullptr) { + node::FreeIsolateData(_isolateData); + _isolateData = nullptr; + } + + if (_env != nullptr) { + _env->CleanupHandles(); + node::FreeEnvironment(_env); + _env = nullptr; + } + #endif + + _context.Get(_isolate)->Exit(); + _context.Reset(); + _isolate->Exit(); + } + _isolate->Dispose(); + _isolate = nullptr; + Object::setIsolate(nullptr); + + _globalObj = nullptr; + _isValid = false; + + _registerCallbackArray.clear(); + + for (const auto &hook : _afterCleanupHookArray) { + hook(); + } + + // Cleanup all hooks + _beforeInitHookArray.clear(); + _afterInitHookArray.clear(); + _beforeCleanupHookArray.clear(); + _afterCleanupHookArray.clear(); + + _isInCleanup = false; + NativePtrToObjectMap::destroy(); + _gcFuncValue.setUndefined(); + _gcFunc = nullptr; + cc::events::ScriptEngine::broadcast(cc::ScriptEngineEvent::AFTER_CLEANUP); + SE_LOGD("ScriptEngine::cleanup end ...\n"); +} + +Object *ScriptEngine::getGlobalObject() const { + return _globalObj; +} + +void ScriptEngine::addBeforeInitHook(const std::function &hook) { + _beforeInitHookArray.push_back(hook); +} + +void ScriptEngine::addAfterInitHook(const std::function &hook) { + _afterInitHookArray.push_back(hook); +} + +void ScriptEngine::addBeforeCleanupHook(const std::function &hook) { + _beforeCleanupHookArray.push_back(hook); +} + +void ScriptEngine::addAfterCleanupHook(const std::function &hook) { + _afterCleanupHookArray.push_back(hook); +} + +void ScriptEngine::addRegisterCallback(RegisterCallback cb) { + CC_ASSERT(std::find(_registerCallbackArray.begin(), _registerCallbackArray.end(), cb) == _registerCallbackArray.end()); + _registerCallbackArray.push_back(cb); +} + +void ScriptEngine::addPermanentRegisterCallback(RegisterCallback cb) { + if (std::find(_permRegisterCallbackArray.begin(), _permRegisterCallbackArray.end(), cb) == _permRegisterCallbackArray.end()) { + _permRegisterCallbackArray.push_back(cb); + } +} + +bool ScriptEngine::callRegisteredCallback() { + se::AutoHandleScope hs; + bool ok = false; + _startTime = std::chrono::steady_clock::now(); + + for (auto cb : _permRegisterCallbackArray) { + ok = cb(_globalObj); + CC_ASSERT(ok); + if (!ok) { + break; + } + } + + for (auto cb : _registerCallbackArray) { + ok = cb(_globalObj); + CC_ASSERT(ok); + if (!ok) { + break; + } + } + + // After ScriptEngine is started, _registerCallbackArray isn't needed. Therefore, clear it here. + _registerCallbackArray.clear(); + + return ok; +} + +bool ScriptEngine::start() { + if (!init()) { + return false; + } + se::AutoHandleScope hs; + + // Check the cache of debuggerInfo. Enable debugger if it's valid. + if (debuggerInfo.isValid()) { + enableDebugger(debuggerInfo.serverAddr, debuggerInfo.port, debuggerInfo.isWait); + debuggerInfo.reset(); + } + + // debugger + if (isDebuggerEnabled()) { + #if SE_ENABLE_INSPECTOR && !CC_EDITOR + // V8 inspector stuff, most code are taken from NodeJS. + _isolateData = node::CreateIsolateData(_isolate, uv_default_loop()); + _env = node::CreateEnvironment(_isolateData, _context.Get(_isolate), 0, nullptr, 0, nullptr); + + node::DebugOptions options; + options.set_wait_for_connect(_isWaitForConnect); // the program will be hung up until debug attach if _isWaitForConnect = true + options.set_inspector_enabled(true); + options.set_port(static_cast(_debuggerServerPort)); + options.set_host_name(_debuggerServerAddr); + bool ok = _env->inspector_agent()->Start(gSharedV8->platform, "", options); + CC_ASSERT(ok); + #endif + } + + return callRegisteredCallback(); +} + +bool ScriptEngine::start(v8::Isolate *isolate) { + if (!init(isolate)) { + return false; + } + return callRegisteredCallback(); +} + +void ScriptEngine::garbageCollect() { + SE_LOGD("GC begin ..., (js->native map) size: %d\n", (int)NativePtrToObjectMap::size()); + _gcFunc->call({}, nullptr); + SE_LOGD("GC end ..., (js->native map) size: %d\n", (int)NativePtrToObjectMap::size()); +} + +bool ScriptEngine::isGarbageCollecting() const { + return _isGarbageCollecting; +} + +void ScriptEngine::_setGarbageCollecting(bool isGarbageCollecting) { // NOLINT(readability-identifier-naming) + _isGarbageCollecting = isGarbageCollecting; +} + +/* static */ +void ScriptEngine::_setDebuggerInfo(const DebuggerInfo &info) { + debuggerInfo = info; +} + +bool ScriptEngine::isValid() const { + return ScriptEngine::instance != nullptr && _isValid; +} + +bool ScriptEngine::evalString(const char *script, uint32_t length /* = 0 */, Value *ret /* = nullptr */, const char *fileName /* = nullptr */) { + if (_engineThreadId != std::this_thread::get_id()) { + // `evalString` should run in main thread + CC_ABORT(); + return false; + } + + CC_ASSERT_NOT_NULL(script); + if (length == 0) { + length = static_cast(strlen(script)); + } + + if (fileName == nullptr) { + fileName = "(no filename)"; + } + + // Fix the source url is too long displayed in Chrome debugger. + ccstd::string sourceUrl = fileName; + static const ccstd::string PREFIX_KEY = "/temp/quick-scripts/"; + size_t prefixPos = sourceUrl.find(PREFIX_KEY); + if (prefixPos != ccstd::string::npos) { + sourceUrl = sourceUrl.substr(prefixPos + PREFIX_KEY.length()); + } + + // It is needed, or will crash if invoked from non C++ context, such as invoked from objective-c context(for example, handler of UIKit). + v8::HandleScope handleScope(_isolate); + + ccstd::string scriptStr(script, length); + v8::MaybeLocal source = v8::String::NewFromUtf8(_isolate, scriptStr.c_str(), v8::NewStringType::kNormal); + if (source.IsEmpty()) { + return false; + } + + v8::MaybeLocal originStr = v8::String::NewFromUtf8(_isolate, sourceUrl.c_str(), v8::NewStringType::kNormal); + if (originStr.IsEmpty()) { + return false; + } + + v8::ScriptOrigin origin(_isolate, originStr.ToLocalChecked()); + v8::MaybeLocal maybeScript = v8::Script::Compile(_context.Get(_isolate), source.ToLocalChecked(), &origin); + + bool success = false; + + if (!maybeScript.IsEmpty()) { + v8::TryCatch block(_isolate); + + v8::Local v8Script = maybeScript.ToLocalChecked(); + v8::MaybeLocal maybeResult = v8Script->Run(_context.Get(_isolate)); + + if (!maybeResult.IsEmpty()) { + v8::Local result = maybeResult.ToLocalChecked(); + + if (!result->IsUndefined() && ret != nullptr) { + internal::jsToSeValue(_isolate, result, ret); + } + + success = true; + } + + if (block.HasCaught()) { + v8::Local message = block.Message(); + SE_LOGE("ScriptEngine::evalString catch exception:\n"); + onMessageCallback(message, v8::Undefined(_isolate)); + } + } + + if (!success) { + SE_LOGE("ScriptEngine::evalString script %s, failed!\n", fileName); + } + + return success; +} + +ccstd::string ScriptEngine::getCurrentStackTrace() { + if (!_isValid) { + return ccstd::string(); + } + + v8::HandleScope hs(_isolate); + v8::Local stack = v8::StackTrace::CurrentStackTrace(_isolate, JSB_STACK_FRAME_LIMIT, v8::StackTrace::kOverview); + return stackTraceToString(stack); +} + +void ScriptEngine::setFileOperationDelegate(const FileOperationDelegate &delegate) { + _fileOperationDelegate = delegate; +} + +const ScriptEngine::FileOperationDelegate &ScriptEngine::getFileOperationDelegate() const { + return _fileOperationDelegate; +} + +bool ScriptEngine::saveByteCodeToFile(const ccstd::string &path, const ccstd::string &pathBc) { + bool success = false; + auto *fu = cc::FileUtils::getInstance(); + + if (pathBc.length() > 3 && pathBc.substr(pathBc.length() - 3) != ".bc") { + SE_LOGE("ScriptEngine::generateByteCode bytecode file path should endwith \".bc\"\n"); + ; + return false; + } + + if (fu->isFileExist(pathBc)) { + SE_LOGE("ScriptEngine::generateByteCode file already exists, it will be rewrite!\n"); + } + + // create directory for .bc file + { + auto lastSep = static_cast(pathBc.size()) - 1; + while (lastSep >= 0 && pathBc[lastSep] != '/') { + lastSep -= 1; + } + + if (lastSep == 0) { + SE_LOGE("ScriptEngine::generateByteCode no directory component found in path %s\n", path.c_str()); + return false; + } + ccstd::string pathBcDir = pathBc.substr(0, lastSep); + success = fu->createDirectory(pathBcDir); + if (!success) { + SE_LOGE("ScriptEngine::generateByteCode failed to create bytecode for %s\n", path.c_str()); + return success; + } + } + + // load script file + ccstd::string scriptBuffer = _fileOperationDelegate.onGetStringFromFile(path); + v8::Local code = v8::String::NewFromUtf8(_isolate, scriptBuffer.c_str(), v8::NewStringType::kNormal, static_cast(scriptBuffer.length())).ToLocalChecked(); + v8::Local scriptPath = v8::String::NewFromUtf8(_isolate, path.data(), v8::NewStringType::kNormal).ToLocalChecked(); + // create unbound script + v8::ScriptOrigin origin(_isolate, scriptPath); + v8::ScriptCompiler::Source source(code, origin); + v8::Local parsingContext = v8::Local::New(_isolate, _context); + v8::Context::Scope parsingScope(parsingContext); + v8::TryCatch tryCatch(_isolate); + v8::Local v8Script = v8::ScriptCompiler::CompileUnboundScript(_isolate, &source, v8::ScriptCompiler::kEagerCompile) + .ToLocalChecked(); + // create CachedData + v8::ScriptCompiler::CachedData *cd = v8::ScriptCompiler::CreateCodeCache(v8Script); + + if (cd != nullptr) { + // save to file + cc::Data writeData; + writeData.copy(cd->data, cd->length); + success = fu->writeDataToFile(writeData, pathBc); + if (!success) { + SE_LOGE("ScriptEngine::generateByteCode write %s\n", pathBc.c_str()); + } + + // TODO(PatriceJiang): v8 on windows is built with dynamic library (dll), + // Invoking `delete` for the memory allocated in v8.dll will cause crash. + // Need to modify v8 source code and add v8::ScriptCompiler::DestroyCodeCache(v8::ScriptCompiler::CachedData *cd). + #if CC_PLATFORM != CC_PLATFORM_WINDOWS + delete cd; + #endif + } else { + success = false; + } + + return success; +} + +bool ScriptEngine::runByteCodeFile(const ccstd::string &pathBc, Value *ret /* = nullptr */) { + auto *fu = cc::FileUtils::getInstance(); + + cc::Data cachedData; + fu->getContents(pathBc, &cachedData); + + // read origin source file length from .bc file + uint8_t *p = cachedData.getBytes() + 8; + int filesize = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24); + + { + // fix bytecode + v8::HandleScope scope(_isolate); + v8::Local dummyBytecodeSource = v8::String::NewFromUtf8(_isolate, "\" \"", v8::NewStringType::kNormal).ToLocalChecked(); + v8::ScriptCompiler::Source dummySource(dummyBytecodeSource); + v8::Local dummyFunction = v8::ScriptCompiler::CompileUnboundScript(_isolate, &dummySource, v8::ScriptCompiler::kEagerCompile).ToLocalChecked(); + v8::ScriptCompiler::CachedData *dummyData = v8::ScriptCompiler::CreateCodeCache(dummyFunction); + memcpy(p + 4, dummyData->data + 12, 4); + + // TODO(PatriceJiang): v8 on windows is built with dynamic library (dll), + // Invoking `delete` for the memory allocated in v8.dll will cause crash. + // Need to modify v8 source code and add v8::ScriptCompiler::DestroyCodeCache(v8::ScriptCompiler::CachedData *cd). + #if CC_PLATFORM != CC_PLATFORM_WINDOWS + delete dummyData; + #endif + } + + // setup ScriptOrigin + v8::Local scriptPath = v8::String::NewFromUtf8(_isolate, pathBc.data(), v8::NewStringType::kNormal).ToLocalChecked(); + v8::ScriptOrigin origin(_isolate, scriptPath, 0, 0, true); + + // restore CacheData + auto *v8CacheData = ccnew v8::ScriptCompiler::CachedData(cachedData.getBytes(), static_cast(cachedData.getSize())); + v8::Local dummyCode; + + // generate dummy code + if (filesize > 0) { + ccstd::vector codeBuffer; + codeBuffer.resize(filesize + 1); + std::fill(codeBuffer.begin(), codeBuffer.end(), ' '); + codeBuffer[0] = '\"'; + codeBuffer[filesize - 1] = '\"'; + codeBuffer[filesize] = '\0'; + dummyCode = v8::String::NewFromUtf8(_isolate, codeBuffer.data(), v8::NewStringType::kNormal, filesize).ToLocalChecked(); + + CC_ASSERT(dummyCode->Length() == filesize); + } + + v8::ScriptCompiler::Source source(dummyCode, origin, v8CacheData); + + if (source.GetCachedData() == nullptr) { + SE_LOGE("ScriptEngine::runByteCodeFile can not load cacheData for %s", pathBc.c_str()); + return false; + } + + v8::TryCatch tryCatch(_isolate); + v8::Local v8Script = v8::ScriptCompiler::CompileUnboundScript(_isolate, &source, v8::ScriptCompiler::kConsumeCodeCache) + .ToLocalChecked(); + + if (v8Script.IsEmpty()) { + SE_LOGE("ScriptEngine::runByteCodeFile can not compile %s!\n", pathBc.c_str()); + return false; + } + + if (source.GetCachedData()->rejected) { + SE_LOGE("ScriptEngine::runByteCodeFile cache rejected %s!\n", pathBc.c_str()); + return false; + } + + v8::Local runnableScript = v8Script->BindToCurrentContext(); + v8::MaybeLocal result = runnableScript->Run(_context.Get(_isolate)); + + if (result.IsEmpty()) { + SE_LOGE("ScriptEngine::runByteCodeFile script %s, failed!\n", pathBc.c_str()); + return false; + } + + if (!result.ToLocalChecked()->IsUndefined() && ret != nullptr) { + internal::jsToSeValue(_isolate, result.ToLocalChecked(), ret); + } + + SE_LOGE("ScriptEngine::runByteCodeFile success %s!\n", pathBc.c_str()); + + return true; +} + +bool ScriptEngine::runScript(const ccstd::string &path, Value *ret /* = nullptr */) { + CC_ASSERT(!path.empty()); + CC_ASSERT(_fileOperationDelegate.isValid()); + + if (!cc::FileUtils::getInstance()->isFileExist(path)) { + std::stringstream ss; + ss << "throw new Error(\"Failed to require file '" + << path << "', not found!\");"; + evalString(ss.str().c_str()); + return false; + } + + if (path.length() > 3 && path.substr(path.length() - 3) == ".bc") { + return runByteCodeFile(path, ret); + } + + ccstd::string scriptBuffer = _fileOperationDelegate.onGetStringFromFile(path); + + if (!scriptBuffer.empty()) { + return evalString(scriptBuffer.c_str(), static_cast(scriptBuffer.length()), ret, path.c_str()); + } + + SE_LOGE("ScriptEngine::runScript script %s, buffer is empty!\n", path.c_str()); + return false; +} + +void ScriptEngine::clearException() { + // IDEA: +} + +void ScriptEngine::throwException(const ccstd::string &errorMessage) { + v8::HandleScope scope(_isolate); + v8::Local message = v8::String::NewFromUtf8(_isolate, errorMessage.data()).ToLocalChecked(); + v8::Local error = v8::Exception::Error(message); + _isolate->ThrowException(error); +} + +void ScriptEngine::setExceptionCallback(const ExceptionCallback &cb) { + _nativeExceptionCallback = cb; +} + +void ScriptEngine::setJSExceptionCallback(const ExceptionCallback &cb) { + _jsExceptionCallback = cb; +} + +v8::Local ScriptEngine::_getContext() const { // NOLINT(readability-identifier-naming) + return _context.Get(_isolate); +} + +void ScriptEngine::enableDebugger(const ccstd::string &serverAddr, uint32_t port, bool isWait) { + _debuggerServerAddr = serverAddr; + _debuggerServerPort = port; + _isWaitForConnect = isWait; +} + +bool ScriptEngine::isDebuggerEnabled() const { + return !_debuggerServerAddr.empty() && _debuggerServerPort > 0; +} + +void ScriptEngine::mainLoopUpdate() { + // empty implementation +} + +bool ScriptEngine::callFunction(Object *targetObj, const char *funcName, uint32_t argc, Value *args, Value *rval /* = nullptr*/) { + v8::HandleScope handleScope(_isolate); + + v8::MaybeLocal nameValue = _getStringPool().get(_isolate, funcName); + + if (nameValue.IsEmpty()) { + if (rval != nullptr) { + rval->setUndefined(); + } + return false; + } + + v8::Local nameValToLocal = nameValue.ToLocalChecked(); + v8::Local context = _isolate->GetCurrentContext(); + v8::Local localObj = targetObj->_obj.handle(_isolate); + + v8::MaybeLocal funcVal = localObj->Get(context, nameValToLocal); + if (funcVal.IsEmpty()) { + if (rval != nullptr) { + rval->setUndefined(); + } + return false; + } + + SE_ASSERT(argc < 11, "Only support argument count that less than 11"); // NOLINT + ccstd::array, 10> argv; + + for (size_t i = 0; i < argc; ++i) { + internal::seToJsValue(_isolate, args[i], &argv[i]); + } + + #if CC_DEBUG + v8::TryCatch tryCatch(_isolate); + #endif + + CC_ASSERT(!funcVal.IsEmpty()); + if (!funcVal.ToLocalChecked()->IsFunction()) { + v8::String::Utf8Value funcStr(_isolate, funcVal.ToLocalChecked()); + SE_REPORT_ERROR("%s is not a function: %s", funcName, *funcStr); + return false; + } + + v8::MaybeLocal funcObj = funcVal.ToLocalChecked()->ToObject(context); + v8::MaybeLocal result = funcObj.ToLocalChecked()->CallAsFunction(_getContext(), localObj, argc, argv.data()); + + #if CC_DEBUG + if (tryCatch.HasCaught()) { + v8::String::Utf8Value stack(_isolate, tryCatch.StackTrace(context).ToLocalChecked()); + SE_REPORT_ERROR("Invoking function failed, %s", *stack); + if (rval != nullptr) { + rval->setUndefined(); + } + tryCatch.ReThrow(); + return false; + } + #endif + + if (!result.IsEmpty()) { + if (rval != nullptr) { + internal::jsToSeValue(_isolate, result.ToLocalChecked(), rval); + } + } + return true; +} + +// VMStringPool +ScriptEngine::VMStringPool::VMStringPool() = default; + +ScriptEngine::VMStringPool::~VMStringPool() = default; + +v8::MaybeLocal ScriptEngine::VMStringPool::get(v8::Isolate *isolate, const char *name) { + v8::Local ret; + auto iter = _vmStringPoolMap.find(name); + if (iter == _vmStringPoolMap.end()) { + v8::MaybeLocal nameValue = v8::String::NewFromUtf8(isolate, name, v8::NewStringType::kNormal); + if (!nameValue.IsEmpty()) { + auto *persistentName = ccnew v8::Persistent(); + persistentName->Reset(isolate, nameValue.ToLocalChecked()); + _vmStringPoolMap.emplace(name, persistentName); + ret = v8::Local::New(isolate, *persistentName); + } + } else { + ret = v8::Local::New(isolate, *iter->second); + } + + return ret; +} + +void ScriptEngine::VMStringPool::clear() { + for (auto &e : _vmStringPoolMap) { + e.second->Reset(); + delete e.second; + } + _vmStringPoolMap.clear(); +} + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/ScriptEngine.h b/cocos/bindings/jswrapper/v8/ScriptEngine.h new file mode 100644 index 0000000..c1ada9b --- /dev/null +++ b/cocos/bindings/jswrapper/v8/ScriptEngine.h @@ -0,0 +1,441 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../config.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + + #include "../Value.h" + #include "Base.h" + + #include + + #if SE_ENABLE_INSPECTOR +namespace node { +namespace inspector { +class Agent; +} + +class Environment; +class IsolateData; +} // namespace node + #endif + +namespace se { + +class Object; +class Class; +class Value; + +/** + * A stack-allocated class that governs a number of local handles. + * It's only implemented for v8 wrapper now. + * Other script engine wrappers have empty implementation for this class. + * It's used at the beginning of executing any wrapper API. + */ +class AutoHandleScope { +public: + AutoHandleScope(); + ~AutoHandleScope(); + +private: + v8::HandleScope _handleScope; +}; + +/** + * ScriptEngine is a sington which represents a context of JavaScript VM. + */ +class ScriptEngine final { +public: + /** + * @brief Gets or creates the instance of script engine. + * @return The script engine instance. + */ + static ScriptEngine *getInstance(); + + /** + * @brief Destroys the instance of script engine. + */ + CC_DEPRECATED(3.6.0) + static void destroyInstance(); + + ScriptEngine(); + ~ScriptEngine(); + + /** + * @brief Gets the global object of JavaScript VM. + * @return The se::Object stores the global JavaScript object. + */ + Object *getGlobalObject() const; + + using RegisterCallback = bool (*)(Object *); + + /** + * @brief Adds a callback for registering a native binding module. + * @param[in] cb A callback for registering a native binding module. + * @note This method just add a callback to a vector, callbacks is invoked in `start` method. + */ + void addRegisterCallback(RegisterCallback cb); + + /** + * @brief Adds a callback for registering a native binding module, which will not be removed by ScriptEngine::cleanup. + * @param[in] cb A callback for registering a native binding module. + * @note This method just add a callback to a vector, callbacks is invoked in `start` method. + */ + void addPermanentRegisterCallback(RegisterCallback cb); + + /** + * @brief Starts the script engine. + * @return true if succeed, otherwise false. + * @note This method will invoke all callbacks of native binding modules by the order of registration. + */ + bool start(); + + /** + * @brief Starts the script engine with isolate. + * @return true if succeed, otherwise false. + * @note This method will invoke all callbacks of native binding modules by the order of registration. + */ + bool start(v8::Isolate *isolate); + + /** + * @brief Initializes script engine. + * @return true if succeed, otherwise false. + * @note This method will create JavaScript context and global object. + */ + bool init(); + + /** + * @brief Initializes script engine with isolate. + * @return true if succeed, otherwise false. + * @note This method will create JavaScript context and global object. + */ + bool init(v8::Isolate *isolate); + /** + * @brief Adds a hook function before initializing script engine. + * @param[in] hook A hook function to be invoked before initializing script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addBeforeInitHook(const std::function &hook); + + /** + * @brief Adds a hook function after initializing script engine. + * @param[in] hook A hook function to be invoked before initializing script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addAfterInitHook(const std::function &hook); + + /** + * @brief Cleanups script engine. + * @note This method will removes all objects in JavaScript VM even whose are rooted, then shutdown JavaScript VMf. + */ + void cleanup(); + + /** + * @brief Adds a hook function before cleanuping script engine. + * @param[in] hook A hook function to be invoked before cleanuping script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addBeforeCleanupHook(const std::function &hook); + + /** + * @brief Adds a hook function after cleanuping script engine. + * @param[in] hook A hook function to be invoked after cleanuping script engine. + * @note Multiple hook functions could be added, they will be invoked by the order of adding. + */ + void addAfterCleanupHook(const std::function &hook); + + /** + * @brief Executes a utf-8 string buffer which contains JavaScript code. + * @param[in] script A utf-8 string buffer, if it isn't null-terminated, parameter `length` should be assigned and > 0. + * @param[in] length The length of parameter `scriptStr`, it will be set to string length internally if passing 0 and parameter `scriptStr` is null-terminated. + * @param[in] ret The se::Value that results from evaluating script. Passing nullptr if you don't care about the result. + * @param[in] fileName A string containing a URL for the script's source file. This is used by debuggers and when reporting exceptions. Pass NULL if you do not care to include source file information. + * @return true if succeed, otherwise false. + */ + bool evalString(const char *script, uint32_t length = 0, Value *ret = nullptr, const char *fileName = nullptr); + + /** + * @brief Compile script file into v8::ScriptCompiler::CachedData and save to file. + * @param[in] path The path of script file. + * @param[in] pathBc The location where bytecode file should be written to. The path should be ends with ".bc", which indicates a bytecode file. + * @return true if succeed, otherwise false. + */ + bool saveByteCodeToFile(const ccstd::string &path, const ccstd::string &pathBc); + + /** + * @brief Grab a snapshot of the current JavaScript execution stack. + * @return current stack trace string + */ + ccstd::string getCurrentStackTrace(); + + /** + * Delegate class for file operation + */ + class FileOperationDelegate { + public: + FileOperationDelegate() + : onGetDataFromFile(nullptr), + onGetStringFromFile(nullptr), + onCheckFileExist(nullptr), + onGetFullPath(nullptr) {} + + /** + * @brief Tests whether delegate is valid. + */ + bool isValid() const { + return onGetDataFromFile != nullptr && onGetStringFromFile != nullptr && onCheckFileExist != nullptr && onGetFullPath != nullptr; + } + + // path, buffer, buffer size + std::function &)> onGetDataFromFile; + // path, return file string content. + std::function onGetStringFromFile; + // path + std::function onCheckFileExist; + // path, return full path + std::function onGetFullPath; + }; + + /** + * @brief Sets the delegate for file operation. + * @param delegate[in] The delegate instance for file operation. + */ + void setFileOperationDelegate(const FileOperationDelegate &delegate); + + /** + * @brief Gets the delegate for file operation. + * @return The delegate for file operation + */ + const FileOperationDelegate &getFileOperationDelegate() const; + + /** + * @brief Executes a file which contains JavaScript code. + * @param[in] path Script file path. + * @param[in] ret The se::Value that results from evaluating script. Passing nullptr if you don't care about the result. + * @return true if succeed, otherwise false. + */ + bool runScript(const ccstd::string &path, Value *ret = nullptr); + + /** + * @brief Tests whether script engine is doing garbage collection. + * @return true if it's in garbage collection, otherwise false. + */ + bool isGarbageCollecting() const; + + /** + * @brief Performs a JavaScript garbage collection. + */ + void garbageCollect(); + + /** + * @brief Tests whether script engine is being cleaned up. + * @return true if it's in cleaning up, otherwise false. + */ + bool isInCleanup() const { return _isInCleanup; } + + /** + * @brief Tests whether script engine is valid. + * @return true if it's valid, otherwise false. + */ + bool isValid() const; + + /** + * @brief Throw JS exception + */ + void throwException(const ccstd::string &errorMessage); + + /** + * @brief Clears all exceptions. + */ + void clearException(); + + using ExceptionCallback = std::function; // location, message, stack + + /** + * @brief Sets the callback function while an exception is fired. + * @param[in] cb The callback function to notify that an exception is fired. + */ + void setExceptionCallback(const ExceptionCallback &cb); + + /** + * @brief Sets the callback function while an exception is fired in JS. + * @param[in] cb The callback function to notify that an exception is fired. + */ + void setJSExceptionCallback(const ExceptionCallback &cb); + + /** + * @brief Gets the start time of script engine. + * @return The start time of script engine. + */ + const std::chrono::steady_clock::time_point &getStartTime() const { return _startTime; } + + /** + * @brief Enables JavaScript debugger + * @param[in] serverAddr The address of debugger server. + * @param[in] isWait Whether wait debugger attach when loading. + */ + void enableDebugger(const ccstd::string &serverAddr, uint32_t port, bool isWait = false); + + /** + * @brief Tests whether JavaScript debugger is enabled + * @return true if JavaScript debugger is enabled, otherwise false. + */ + bool isDebuggerEnabled() const; + + /** + * @brief Main loop update trigger, it's need to invoked in main thread every frame. + */ + void mainLoopUpdate(); + + /** + * @brief Gets script virtual machine instance ID. Default value is 1, increase by 1 if `init` is invoked. + */ + uint32_t getVMId() const { return _vmId; } + + /** + * @brief Fast version of call script function, faster than Object::call + */ + bool callFunction(Object *targetObj, const char *funcName, uint32_t argc, Value *args, Value *rval = nullptr); + + /** + * @brief Handle all exceptions throwed by promise + */ + void handlePromiseExceptions(); + + // Private API used in wrapper + class VMStringPool final { + public: + VMStringPool(); + ~VMStringPool(); + v8::MaybeLocal get(v8::Isolate *isolate, const char *name); + void clear(); + + private: + ccstd::unordered_map *> _vmStringPoolMap; + }; + + inline VMStringPool &_getStringPool() { return _stringPool; } // NOLINT(readability-identifier-naming) + void _retainScriptObject(void *owner, void *target); // NOLINT(readability-identifier-naming) + void _releaseScriptObject(void *owner, void *target); // NOLINT(readability-identifier-naming) + v8::Local _getContext() const; // NOLINT(readability-identifier-naming) + void _setGarbageCollecting(bool isGarbageCollecting); // NOLINT(readability-identifier-naming) + + struct DebuggerInfo { + ccstd::string serverAddr; + uint32_t port{0}; + bool isWait{false}; + inline bool isValid() const { return !serverAddr.empty() && port != 0; } + inline void reset() { + serverAddr.clear(); + port = 0; + isWait = false; + } + }; + static void _setDebuggerInfo(const DebuggerInfo &info); // NOLINT(readability-identifier-naming) + // +private: + static void privateDataFinalize(PrivateObjectBase *privateObj); + static void onFatalErrorCallback(const char *location, const char *message); + + static void onOOMErrorCallback(const char *location, +#if V8_MAJOR_VERSION > 10 || (V8_MAJOR_VERSION == 10 && V8_MINOR_VERSION > 4) + const v8::OOMDetails& details +#else + bool isHeapOom +#endif + ); + static void onMessageCallback(v8::Local message, v8::Local data); + static void onPromiseRejectCallback(v8::PromiseRejectMessage msg); + + /** + * @brief Load the bytecode file and set the return value + * @param[in] path_bc The path of bytecode file. + * @param[in] ret The se::Value that results from evaluating script. Passing nullptr if you don't care about the result. + * @return true if succeed, otherwise false. + */ + bool runByteCodeFile(const ccstd::string &pathBc, Value *ret /* = nullptr */); + void callExceptionCallback(const char *, const char *, const char *); + bool callRegisteredCallback(); + bool postInit(); + // Struct to save exception info + struct PromiseExceptionMsg { + ccstd::string event; + ccstd::string stackTrace; + }; + // Push promise and exception msg to _promiseArray + void pushPromiseExeception(const v8::Local &promise, const char *event, const char *stackTrace); + + static ScriptEngine *instance; + + static DebuggerInfo debuggerInfo; + + ccstd::vector>, ccstd::vector>> _promiseArray; + + std::chrono::steady_clock::time_point _startTime; + ccstd::vector _registerCallbackArray; + ccstd::vector _permRegisterCallbackArray; + ccstd::vector> _beforeInitHookArray; + ccstd::vector> _afterInitHookArray; + ccstd::vector> _beforeCleanupHookArray; + ccstd::vector> _afterCleanupHookArray; + + v8::Persistent _context; + + v8::Isolate *_isolate; + v8::HandleScope *_handleScope; + Object *_globalObj; + Value _gcFuncValue; + Object *_gcFunc = nullptr; + + FileOperationDelegate _fileOperationDelegate; + ExceptionCallback _nativeExceptionCallback = nullptr; + ExceptionCallback _jsExceptionCallback = nullptr; + + #if SE_ENABLE_INSPECTOR + node::Environment *_env; + node::IsolateData *_isolateData; + #endif + VMStringPool _stringPool; + + std::thread::id _engineThreadId; + ccstd::string _lastStackTrace; + ccstd::string _debuggerServerAddr; + uint32_t _debuggerServerPort; + bool _isWaitForConnect; + + uint32_t _vmId; + + bool _isValid; + bool _isGarbageCollecting; + bool _isInCleanup; + bool _isErrorHandleWorking; +}; + +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/SeApi.h b/cocos/bindings/jswrapper/v8/SeApi.h new file mode 100644 index 0000000..f7fc9f6 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/SeApi.h @@ -0,0 +1,32 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "Class.h" +#include "HelperMacros.h" +#include "Object.h" +#include "ScriptEngine.h" +#include "Utils.h" diff --git a/cocos/bindings/jswrapper/v8/Utils.cpp b/cocos/bindings/jswrapper/v8/Utils.cpp new file mode 100644 index 0000000..6e53342 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/Utils.cpp @@ -0,0 +1,249 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Utils.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + + #include + #include "Class.h" + #include "Object.h" + #include "ScriptEngine.h" + #include "base/Log.h" + #include "base/Macros.h" + +namespace se { + +namespace internal { + +void jsToSeArgs(const v8::FunctionCallbackInfo &v8args, ValueArray &outArr) { + v8::Isolate *isolate = v8args.GetIsolate(); + for (int i = 0; i < v8args.Length(); i++) { + jsToSeValue(isolate, v8args[i], &outArr[i]); + } +} + +void seToJsArgs(v8::Isolate *isolate, const ValueArray &args, v8::Local *outArr) { + CC_ASSERT_NOT_NULL(outArr); + uint32_t i = 0; + for (const auto &data : args) { + v8::Local &jsval = outArr[i]; + seToJsValue(isolate, data, &jsval); + ++i; + } +} + +void seToJsValue(v8::Isolate *isolate, const Value &v, v8::Local *outJsVal) { + CC_ASSERT_NOT_NULL(outJsVal); + switch (v.getType()) { + case Value::Type::Number: + *outJsVal = v8::Number::New(isolate, v.toDouble()); + break; + case Value::Type::String: { + v8::MaybeLocal str = v8::String::NewFromUtf8(isolate, v.toString().data(), v8::NewStringType::kNormal, static_cast(v.toString().length())); + if (!str.IsEmpty()) { + *outJsVal = str.ToLocalChecked(); + } else { + outJsVal->Clear(); + } + } break; + case Value::Type::Boolean: + *outJsVal = v8::Boolean::New(isolate, v.toBoolean()); + break; + case Value::Type::Object: + *outJsVal = v.toObject()->_getJSObject(); + break; + case Value::Type::Null: + *outJsVal = v8::Null(isolate); + break; + case Value::Type::Undefined: + *outJsVal = v8::Undefined(isolate); + break; + case Value::Type::BigInt: + *outJsVal = v8::BigInt::New(isolate, v.toInt64()); + break; + default: + CC_ABORT(); + break; + } +} + +void jsToSeValue(v8::Isolate *isolate, v8::Local jsval, Value *v) { + CC_ASSERT_NOT_NULL(v); + v8::HandleScope handleScope(isolate); + + if (jsval->IsUndefined()) { + v->setUndefined(); + } else if (jsval->IsNull()) { + v->setNull(); + } else if (jsval->IsNumber()) { + v8::MaybeLocal jsNumber = jsval->ToNumber(isolate->GetCurrentContext()); + if (!jsNumber.IsEmpty()) { + v->setDouble(jsNumber.ToLocalChecked()->Value()); + } else { + v->setUndefined(); + } + } else if (jsval->IsBigInt()) { + v8::MaybeLocal jsBigInt = jsval->ToBigInt(isolate->GetCurrentContext()); + if (!jsBigInt.IsEmpty()) { + auto bigInt = jsBigInt.ToLocalChecked(); + v->setInt64(bigInt->Int64Value()); + } else { + v->setUndefined(); + } + } else if (jsval->IsString()) { + v8::String::Utf8Value utf8(isolate, jsval); + const char *utf8Str = *utf8; + v->setString(utf8Str); + } else if (jsval->IsBoolean()) { + v8::MaybeLocal jsBoolean = jsval->ToBoolean(isolate); + if (!jsBoolean.IsEmpty()) { + v->setBoolean(jsBoolean.ToLocalChecked()->Value()); + } else { + v->setUndefined(); + } + } else if (jsval->IsObject()) { + v8::MaybeLocal jsObj = jsval->ToObject(isolate->GetCurrentContext()); + if (!jsObj.IsEmpty()) { + auto *obj = internal::getPrivate(isolate, jsObj.ToLocalChecked()); + if (obj == nullptr) { + obj = Object::_createJSObject(nullptr, jsObj.ToLocalChecked()); + } else { + obj->incRef(); + } + v->setObject(obj, true); + obj->decRef(); + } else { + v->setUndefined(); + } + } +} + +template +static void warnWithinTimesInReleaseMode(const char *msg) { + static int timesLimit = N; + #if CC_DEBUG + CC_LOG_DEBUG(msg); + #else + if (timesLimit > 0) { + CC_LOG_WARNING(msg); + timesLimit--; + } + #endif +} + +template +void setReturnValueTemplate(const Value &data, const T &argv) { + switch (data.getType()) { + case Value::Type::Undefined: { + argv.GetReturnValue().Set(v8::Undefined(argv.GetIsolate())); + break; + } + case Value::Type::Null: { + argv.GetReturnValue().Set(v8::Null(argv.GetIsolate())); + break; + } + case Value::Type::Number: { + argv.GetReturnValue().Set(v8::Number::New(argv.GetIsolate(), data.toDouble())); + break; + } + case Value::Type::BigInt: { + constexpr int64_t maxSafeInt = 9007199254740991LL; // value refer to JS Number.MAX_SAFE_INTEGER + constexpr int64_t minSafeInt = -9007199254740991LL; // value refer to JS Number.MIN_SAFE_INTEGER + if (data.toInt64() > maxSafeInt || data.toInt64() < minSafeInt) { + // NOTICE: Precision loss will happend here. + warnWithinTimesInReleaseMode<100>("int64 value is out of range for double"); + CC_ABORT(); // should be fixed in debug mode. + } + argv.GetReturnValue().Set(v8::Number::New(argv.GetIsolate(), static_cast(data.toInt64()))); + break; + } + case Value::Type::String: { + v8::MaybeLocal value = v8::String::NewFromUtf8(argv.GetIsolate(), data.toString().c_str(), v8::NewStringType::kNormal); + CC_ASSERT(!value.IsEmpty()); + argv.GetReturnValue().Set(value.ToLocalChecked()); + break; + } + case Value::Type::Boolean: { + argv.GetReturnValue().Set(v8::Boolean::New(argv.GetIsolate(), data.toBoolean())); + break; + } + case Value::Type::Object: { + argv.GetReturnValue().Set(data.toObject()->_getJSObject()); + break; + } + } +} + +void setReturnValue(const Value &data, const v8::FunctionCallbackInfo &argv) { + setReturnValueTemplate(data, argv); +} + +void setReturnValue(const Value &data, const v8::PropertyCallbackInfo &argv) { + setReturnValueTemplate(data, argv); +} + +bool hasPrivate(v8::Isolate * /*isolate*/, v8::Local value) { + v8::Local obj = v8::Local::Cast(value); + return obj->InternalFieldCount() > 0; +} + +void setPrivate(v8::Isolate *isolate, ObjectWrap &wrap, Object *thizObj) { + v8::Local obj = wrap.handle(isolate); + int c = obj->InternalFieldCount(); + CC_ASSERT_GT(c, 0); + if (c == 1) { + wrap.wrap(thizObj, 0); + } +} + +Object *getPrivate(v8::Isolate *isolate, v8::Local value) { + v8::Local context = isolate->GetCurrentContext(); + v8::MaybeLocal obj = value->ToObject(context); + if (obj.IsEmpty()) { + return nullptr; + } + + v8::Local objChecked = obj.ToLocalChecked(); + int c = objChecked->InternalFieldCount(); + if (c == 1) { + return static_cast(ObjectWrap::unwrap(objChecked, 0)); + } + + return nullptr; +} + +void clearPrivate(v8::Isolate *isolate, ObjectWrap &wrap) { + v8::Local obj = wrap.handle(isolate); + int c = obj->InternalFieldCount(); + if (c == 1) { + wrap.wrap(nullptr, 0); + } +} + +} // namespace internal +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/Utils.h b/cocos/bindings/jswrapper/v8/Utils.h new file mode 100644 index 0000000..dcd7591 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/Utils.h @@ -0,0 +1,56 @@ +/**************************************************************************** + Copyright (c) 2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "../config.h" + +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + + #include "../Value.h" + #include "Base.h" + #include "ObjectWrap.h" + +namespace se { + +namespace internal { + +void jsToSeArgs(const v8::FunctionCallbackInfo &v8args, ValueArray &outArr); +void jsToSeValue(v8::Isolate *isolate, v8::Local jsval, Value *v); +void seToJsArgs(v8::Isolate *isolate, const ValueArray &args, v8::Local *outArr); +void seToJsValue(v8::Isolate *isolate, const Value &v, v8::Local *outJsVal); + +void setReturnValue(const Value &data, const v8::FunctionCallbackInfo &argv); +void setReturnValue(const Value &data, const v8::PropertyCallbackInfo &argv); + +bool hasPrivate(v8::Isolate *isolate, v8::Local value); +void setPrivate(v8::Isolate *isolate, ObjectWrap &wrap, Object *obj); +Object *getPrivate(v8::Isolate *isolate, v8::Local value); +void clearPrivate(v8::Isolate *isolate, ObjectWrap &wrap); + +} // namespace internal +} // namespace se + +#endif // #if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 diff --git a/cocos/bindings/jswrapper/v8/debugger/SHA1.cpp b/cocos/bindings/jswrapper/v8/debugger/SHA1.cpp new file mode 100644 index 0000000..2918631 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/SHA1.cpp @@ -0,0 +1,597 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//#include "mozilla/Assertions.h" +//#include "mozilla/EndianUtils.h" +#include "SHA1.h" + +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include + #include + + #if defined(_MSC_VER) + #include + #pragma intrinsic(_byteswap_ushort) + #pragma intrinsic(_byteswap_ulong) + #pragma intrinsic(_byteswap_uint64) + #endif + + #define MOZ_ASSERT(cond, ...) assert(cond) + +//using se::NativeEndian; +using se::SHA1Sum; + +namespace { + + #if defined(_WIN64) + #if defined(_M_X64) || defined(_M_AMD64) || defined(_AMD64_) + #define MOZ_LITTLE_ENDIAN 1 + #else + #error "CPU type is unknown" + #endif + #elif defined(_WIN32) + #if defined(_M_IX86) + #define MOZ_LITTLE_ENDIAN 1 + #elif defined(_M_ARM) + #define MOZ_LITTLE_ENDIAN 1 + #else + #error "CPU type is unknown" + #endif + #elif defined(__APPLE__) || defined(__powerpc__) || defined(__ppc__) + #if __LITTLE_ENDIAN__ + #define MOZ_LITTLE_ENDIAN 1 + #elif __BIG_ENDIAN__ + #define MOZ_BIG_ENDIAN 1 + #endif + #elif defined(__GNUC__) && \ + defined(__BYTE_ORDER__) && \ + defined(__ORDER_LITTLE_ENDIAN__) && \ + defined(__ORDER_BIG_ENDIAN__) + /* + * Some versions of GCC provide architecture-independent macros for + * this. Yes, there are more than two values for __BYTE_ORDER__. + */ + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + #define MOZ_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + #define MOZ_BIG_ENDIAN 1 + #else + #error "Can't handle mixed-endian architectures" + #endif + /* + * We can't include useful headers like or + * here because they're not present on all platforms. Instead we have + * this big conditional that ideally will catch all the interesting + * cases. + */ + #elif defined(__sparc) || defined(__sparc__) || \ + defined(_POWER) || defined(__hppa) || \ + defined(_MIPSEB) || defined(__ARMEB__) || \ + defined(__s390__) || defined(__AARCH64EB__) || \ + (defined(__sh__) && defined(__LITTLE_ENDIAN__)) || \ + (defined(__ia64) && defined(__BIG_ENDIAN__)) + #define MOZ_BIG_ENDIAN 1 + #elif defined(__i386) || defined(__i386__) || \ + defined(__x86_64) || defined(__x86_64__) || \ + defined(_MIPSEL) || defined(__ARMEL__) || \ + defined(__alpha__) || defined(__AARCH64EL__) || \ + (defined(__sh__) && defined(__BIG_ENDIAN__)) || \ + (defined(__ia64) && !defined(__BIG_ENDIAN__)) + #define MOZ_LITTLE_ENDIAN 1 + #endif + + #if MOZ_BIG_ENDIAN + #define MOZ_LITTLE_ENDIAN 0 + #elif MOZ_LITTLE_ENDIAN + #define MOZ_BIG_ENDIAN 0 + #else + #error "Cannot determine endianness" + #endif + + #if defined(__clang__) + #if __has_builtin(__builtin_bswap16) + #define MOZ_HAVE_BUILTIN_BYTESWAP16 __builtin_bswap16 + #endif + #elif defined(__GNUC__) + #define MOZ_HAVE_BUILTIN_BYTESWAP16 __builtin_bswap16 + #elif defined(_MSC_VER) + #define MOZ_HAVE_BUILTIN_BYTESWAP16 _byteswap_ushort + #endif + +enum Endianness { Little, + Big }; + + #if MOZ_BIG_ENDIAN + #define MOZ_NATIVE_ENDIANNESS Big + #else + #define MOZ_NATIVE_ENDIANNESS Little + #endif + +/* + * We need wrappers here because free functions with default template + * arguments and/or partial specialization of function templates are not + * supported by all the compilers we use. + */ +template +struct Swapper; + +template +struct Swapper { + static T swap(T aValue) { + #if defined(MOZ_HAVE_BUILTIN_BYTESWAP16) + return MOZ_HAVE_BUILTIN_BYTESWAP16(aValue); + #else + return T(((aValue & 0x00ff) << 8) | ((aValue & 0xff00) >> 8)); + #endif + } +}; + +template +struct Swapper { + static T swap(T aValue) { + #if defined(__clang__) || defined(__GNUC__) + return T(__builtin_bswap32(aValue)); + #elif defined(_MSC_VER) + return T(_byteswap_ulong(aValue)); + #else + return T(((aValue & 0x000000ffU) << 24) | + ((aValue & 0x0000ff00U) << 8) | + ((aValue & 0x00ff0000U) >> 8) | + ((aValue & 0xff000000U) >> 24)); + #endif + } +}; + +template +struct Swapper { + static inline T swap(T aValue) { + #if defined(__clang__) || defined(__GNUC__) + return T(__builtin_bswap64(aValue)); + #elif defined(_MSC_VER) + return T(_byteswap_uint64(aValue)); + #else + return T(((aValue & 0x00000000000000ffULL) << 56) | + ((aValue & 0x000000000000ff00ULL) << 40) | + ((aValue & 0x0000000000ff0000ULL) << 24) | + ((aValue & 0x00000000ff000000ULL) << 8) | + ((aValue & 0x000000ff00000000ULL) >> 8) | + ((aValue & 0x0000ff0000000000ULL) >> 24) | + ((aValue & 0x00ff000000000000ULL) >> 40) | + ((aValue & 0xff00000000000000ULL) >> 56)); + #endif + } +}; + +template +class Endian { +public: + template + static T swapToBigEndian(T aValue) { + return maybeSwap(aValue); + } + +private: + /** + * Return |aValue| converted from SourceEndian encoding to DestEndian + * encoding. + */ + template + static inline T maybeSwap(T aValue) { + if (SourceEndian == DestEndian) { + return aValue; + } + return Swapper::swap(aValue); + } +}; + +class NativeEndian final : public Endian { +private: + typedef Endian super; + +public: + using super::swapToBigEndian; +}; + +} // namespace + +static inline uint32_t +SHA_ROTL(uint32_t aT, uint32_t aN) { + MOZ_ASSERT(aN < 32); + return (aT << aN) | (aT >> (32 - aN)); +} + +static void +shaCompress(volatile unsigned *aX, const uint32_t *aBuf); + + #define SHA_F1(X, Y, Z) ((((Y) ^ (Z)) & (X)) ^ (Z)) + #define SHA_F2(X, Y, Z) ((X) ^ (Y) ^ (Z)) + #define SHA_F3(X, Y, Z) (((X) & (Y)) | ((Z) & ((X) | (Y)))) + #define SHA_F4(X, Y, Z) ((X) ^ (Y) ^ (Z)) + + #define SHA_MIX(n, a, b, c) XW(n) = SHA_ROTL(XW(a) ^ XW(b) ^ XW(c) ^ XW(n), 1) + +SHA1Sum::SHA1Sum() +: mSize(0), + mDone(false) { + // Initialize H with constants from FIPS180-1. + mH[0] = 0x67452301L; + mH[1] = 0xefcdab89L; + mH[2] = 0x98badcfeL; + mH[3] = 0x10325476L; + mH[4] = 0xc3d2e1f0L; +} + + /* + * Explanation of H array and index values: + * + * The context's H array is actually the concatenation of two arrays + * defined by SHA1, the H array of state variables (5 elements), + * and the W array of intermediate values, of which there are 16 elements. + * The W array starts at H[5], that is W[0] is H[5]. + * Although these values are defined as 32-bit values, we use 64-bit + * variables to hold them because the AMD64 stores 64 bit values in + * memory MUCH faster than it stores any smaller values. + * + * Rather than passing the context structure to shaCompress, we pass + * this combined array of H and W values. We do not pass the address + * of the first element of this array, but rather pass the address of an + * element in the middle of the array, element X. Presently X[0] is H[11]. + * So we pass the address of H[11] as the address of array X to shaCompress. + * Then shaCompress accesses the members of the array using positive AND + * negative indexes. + * + * Pictorially: (each element is 8 bytes) + * H | H0 H1 H2 H3 H4 W0 W1 W2 W3 W4 W5 W6 W7 W8 W9 Wa Wb Wc Wd We Wf | + * X |-11-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 | + * + * The byte offset from X[0] to any member of H and W is always + * representable in a signed 8-bit value, which will be encoded + * as a single byte offset in the X86-64 instruction set. + * If we didn't pass the address of H[11], and instead passed the + * address of H[0], the offsets to elements H[16] and above would be + * greater than 127, not representable in a signed 8-bit value, and the + * x86-64 instruction set would encode every such offset as a 32-bit + * signed number in each instruction that accessed element H[16] or + * higher. This results in much bigger and slower code. + */ + #define H2X 11 /* X[0] is H[11], and H[0] is X[-11] */ + #define W2X 6 /* X[0] is W[6], and W[0] is X[-6] */ + +/* + * SHA: Add data to context. + */ +void SHA1Sum::update(const void *aData, uint32_t aLen) { + MOZ_ASSERT(!mDone, "SHA1Sum can only be used to compute a single hash."); + + const uint8_t *data = static_cast(aData); + + if (aLen == 0) { + return; + } + + /* Accumulate the byte count. */ + unsigned int lenB = static_cast(mSize) & 63U; + + mSize += aLen; + + /* Read the data into W and process blocks as they get full. */ + unsigned int togo; + if (lenB > 0) { + togo = 64U - lenB; + if (aLen < togo) { + togo = aLen; + } + memcpy(mU.mB + lenB, data, togo); + aLen -= togo; + data += togo; + lenB = (lenB + togo) & 63U; + if (!lenB) { + shaCompress(&mH[H2X], mU.mW); + } + } + + while (aLen >= 64U) { + aLen -= 64U; + shaCompress(&mH[H2X], reinterpret_cast(data)); + data += 64U; + } + + if (aLen > 0) { + memcpy(mU.mB, data, aLen); + } +} + +/* + * SHA: Generate hash value + */ +void SHA1Sum::finish(SHA1Sum::Hash &aHashOut) { + MOZ_ASSERT(!mDone, "SHA1Sum can only be used to compute a single hash."); + + uint64_t size = mSize; + uint32_t lenB = uint32_t(size) & 63; + + static const uint8_t bulk_pad[64] = + {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + /* Pad with a binary 1 (e.g. 0x80), then zeroes, then length in bits. */ + update(bulk_pad, (((55 + 64) - lenB) & 63) + 1); + MOZ_ASSERT((uint32_t(mSize) & 63) == 56); + + /* Convert size from bytes to bits. */ + size <<= 3; + mU.mW[14] = NativeEndian::swapToBigEndian(uint32_t(size >> 32)); + mU.mW[15] = NativeEndian::swapToBigEndian(uint32_t(size)); + shaCompress(&mH[H2X], mU.mW); + + /* Output hash. */ + mU.mW[0] = NativeEndian::swapToBigEndian(mH[0]); + mU.mW[1] = NativeEndian::swapToBigEndian(mH[1]); + mU.mW[2] = NativeEndian::swapToBigEndian(mH[2]); + mU.mW[3] = NativeEndian::swapToBigEndian(mH[3]); + mU.mW[4] = NativeEndian::swapToBigEndian(mH[4]); + memcpy(aHashOut, mU.mW, 20); + mDone = true; +} + +/* + * SHA: Compression function, unrolled. + * + * Some operations in shaCompress are done as 5 groups of 16 operations. + * Others are done as 4 groups of 20 operations. + * The code below shows that structure. + * + * The functions that compute the new values of the 5 state variables + * A-E are done in 4 groups of 20 operations (or you may also think + * of them as being done in 16 groups of 5 operations). They are + * done by the SHA_RNDx macros below, in the right column. + * + * The functions that set the 16 values of the W array are done in + * 5 groups of 16 operations. The first group is done by the + * LOAD macros below, the latter 4 groups are done by SHA_MIX below, + * in the left column. + * + * gcc's optimizer observes that each member of the W array is assigned + * a value 5 times in this code. It reduces the number of store + * operations done to the W array in the context (that is, in the X array) + * by creating a W array on the stack, and storing the W values there for + * the first 4 groups of operations on W, and storing the values in the + * context's W array only in the fifth group. This is undesirable. + * It is MUCH bigger code than simply using the context's W array, because + * all the offsets to the W array in the stack are 32-bit signed offsets, + * and it is no faster than storing the values in the context's W array. + * + * The original code for sha_fast.c prevented this creation of a separate + * W array in the stack by creating a W array of 80 members, each of + * whose elements is assigned only once. It also separated the computations + * of the W array values and the computations of the values for the 5 + * state variables into two separate passes, W's, then A-E's so that the + * second pass could be done all in registers (except for accessing the W + * array) on machines with fewer registers. The method is suboptimal + * for machines with enough registers to do it all in one pass, and it + * necessitates using many instructions with 32-bit offsets. + * + * This code eliminates the separate W array on the stack by a completely + * different means: by declaring the X array volatile. This prevents + * the optimizer from trying to reduce the use of the X array by the + * creation of a MORE expensive W array on the stack. The result is + * that all instructions use signed 8-bit offsets and not 32-bit offsets. + * + * The combination of this code and the -O3 optimizer flag on GCC 3.4.3 + * results in code that is 3 times faster than the previous NSS sha_fast + * code on AMD64. + */ +static void +shaCompress(volatile unsigned *aX, const uint32_t *aBuf) { + unsigned A, B, C, D, E; + + #define XH(n) aX[n - H2X] + #define XW(n) aX[n - W2X] + + #define K0 0x5a827999L + #define K1 0x6ed9eba1L + #define K2 0x8f1bbcdcL + #define K3 0xca62c1d6L + + #define SHA_RND1(a, b, c, d, e, n) \ + a = SHA_ROTL(b, 5) + SHA_F1(c, d, e) + a + XW(n) + K0; \ + c = SHA_ROTL(c, 30) + #define SHA_RND2(a, b, c, d, e, n) \ + a = SHA_ROTL(b, 5) + SHA_F2(c, d, e) + a + XW(n) + K1; \ + c = SHA_ROTL(c, 30) + #define SHA_RND3(a, b, c, d, e, n) \ + a = SHA_ROTL(b, 5) + SHA_F3(c, d, e) + a + XW(n) + K2; \ + c = SHA_ROTL(c, 30) + #define SHA_RND4(a, b, c, d, e, n) \ + a = SHA_ROTL(b, 5) + SHA_F4(c, d, e) + a + XW(n) + K3; \ + c = SHA_ROTL(c, 30) + + #define LOAD(n) XW(n) = NativeEndian::swapToBigEndian(aBuf[n]) + + A = XH(0); + B = XH(1); + C = XH(2); + D = XH(3); + E = XH(4); + + LOAD(0); + SHA_RND1(E, A, B, C, D, 0); + LOAD(1); + SHA_RND1(D, E, A, B, C, 1); + LOAD(2); + SHA_RND1(C, D, E, A, B, 2); + LOAD(3); + SHA_RND1(B, C, D, E, A, 3); + LOAD(4); + SHA_RND1(A, B, C, D, E, 4); + LOAD(5); + SHA_RND1(E, A, B, C, D, 5); + LOAD(6); + SHA_RND1(D, E, A, B, C, 6); + LOAD(7); + SHA_RND1(C, D, E, A, B, 7); + LOAD(8); + SHA_RND1(B, C, D, E, A, 8); + LOAD(9); + SHA_RND1(A, B, C, D, E, 9); + LOAD(10); + SHA_RND1(E, A, B, C, D, 10); + LOAD(11); + SHA_RND1(D, E, A, B, C, 11); + LOAD(12); + SHA_RND1(C, D, E, A, B, 12); + LOAD(13); + SHA_RND1(B, C, D, E, A, 13); + LOAD(14); + SHA_RND1(A, B, C, D, E, 14); + LOAD(15); + SHA_RND1(E, A, B, C, D, 15); + + SHA_MIX(0, 13, 8, 2); + SHA_RND1(D, E, A, B, C, 0); + SHA_MIX(1, 14, 9, 3); + SHA_RND1(C, D, E, A, B, 1); + SHA_MIX(2, 15, 10, 4); + SHA_RND1(B, C, D, E, A, 2); + SHA_MIX(3, 0, 11, 5); + SHA_RND1(A, B, C, D, E, 3); + + SHA_MIX(4, 1, 12, 6); + SHA_RND2(E, A, B, C, D, 4); + SHA_MIX(5, 2, 13, 7); + SHA_RND2(D, E, A, B, C, 5); + SHA_MIX(6, 3, 14, 8); + SHA_RND2(C, D, E, A, B, 6); + SHA_MIX(7, 4, 15, 9); + SHA_RND2(B, C, D, E, A, 7); + SHA_MIX(8, 5, 0, 10); + SHA_RND2(A, B, C, D, E, 8); + SHA_MIX(9, 6, 1, 11); + SHA_RND2(E, A, B, C, D, 9); + SHA_MIX(10, 7, 2, 12); + SHA_RND2(D, E, A, B, C, 10); + SHA_MIX(11, 8, 3, 13); + SHA_RND2(C, D, E, A, B, 11); + SHA_MIX(12, 9, 4, 14); + SHA_RND2(B, C, D, E, A, 12); + SHA_MIX(13, 10, 5, 15); + SHA_RND2(A, B, C, D, E, 13); + SHA_MIX(14, 11, 6, 0); + SHA_RND2(E, A, B, C, D, 14); + SHA_MIX(15, 12, 7, 1); + SHA_RND2(D, E, A, B, C, 15); + + SHA_MIX(0, 13, 8, 2); + SHA_RND2(C, D, E, A, B, 0); + SHA_MIX(1, 14, 9, 3); + SHA_RND2(B, C, D, E, A, 1); + SHA_MIX(2, 15, 10, 4); + SHA_RND2(A, B, C, D, E, 2); + SHA_MIX(3, 0, 11, 5); + SHA_RND2(E, A, B, C, D, 3); + SHA_MIX(4, 1, 12, 6); + SHA_RND2(D, E, A, B, C, 4); + SHA_MIX(5, 2, 13, 7); + SHA_RND2(C, D, E, A, B, 5); + SHA_MIX(6, 3, 14, 8); + SHA_RND2(B, C, D, E, A, 6); + SHA_MIX(7, 4, 15, 9); + SHA_RND2(A, B, C, D, E, 7); + + SHA_MIX(8, 5, 0, 10); + SHA_RND3(E, A, B, C, D, 8); + SHA_MIX(9, 6, 1, 11); + SHA_RND3(D, E, A, B, C, 9); + SHA_MIX(10, 7, 2, 12); + SHA_RND3(C, D, E, A, B, 10); + SHA_MIX(11, 8, 3, 13); + SHA_RND3(B, C, D, E, A, 11); + SHA_MIX(12, 9, 4, 14); + SHA_RND3(A, B, C, D, E, 12); + SHA_MIX(13, 10, 5, 15); + SHA_RND3(E, A, B, C, D, 13); + SHA_MIX(14, 11, 6, 0); + SHA_RND3(D, E, A, B, C, 14); + SHA_MIX(15, 12, 7, 1); + SHA_RND3(C, D, E, A, B, 15); + + SHA_MIX(0, 13, 8, 2); + SHA_RND3(B, C, D, E, A, 0); + SHA_MIX(1, 14, 9, 3); + SHA_RND3(A, B, C, D, E, 1); + SHA_MIX(2, 15, 10, 4); + SHA_RND3(E, A, B, C, D, 2); + SHA_MIX(3, 0, 11, 5); + SHA_RND3(D, E, A, B, C, 3); + SHA_MIX(4, 1, 12, 6); + SHA_RND3(C, D, E, A, B, 4); + SHA_MIX(5, 2, 13, 7); + SHA_RND3(B, C, D, E, A, 5); + SHA_MIX(6, 3, 14, 8); + SHA_RND3(A, B, C, D, E, 6); + SHA_MIX(7, 4, 15, 9); + SHA_RND3(E, A, B, C, D, 7); + SHA_MIX(8, 5, 0, 10); + SHA_RND3(D, E, A, B, C, 8); + SHA_MIX(9, 6, 1, 11); + SHA_RND3(C, D, E, A, B, 9); + SHA_MIX(10, 7, 2, 12); + SHA_RND3(B, C, D, E, A, 10); + SHA_MIX(11, 8, 3, 13); + SHA_RND3(A, B, C, D, E, 11); + + SHA_MIX(12, 9, 4, 14); + SHA_RND4(E, A, B, C, D, 12); + SHA_MIX(13, 10, 5, 15); + SHA_RND4(D, E, A, B, C, 13); + SHA_MIX(14, 11, 6, 0); + SHA_RND4(C, D, E, A, B, 14); + SHA_MIX(15, 12, 7, 1); + SHA_RND4(B, C, D, E, A, 15); + + SHA_MIX(0, 13, 8, 2); + SHA_RND4(A, B, C, D, E, 0); + SHA_MIX(1, 14, 9, 3); + SHA_RND4(E, A, B, C, D, 1); + SHA_MIX(2, 15, 10, 4); + SHA_RND4(D, E, A, B, C, 2); + SHA_MIX(3, 0, 11, 5); + SHA_RND4(C, D, E, A, B, 3); + SHA_MIX(4, 1, 12, 6); + SHA_RND4(B, C, D, E, A, 4); + SHA_MIX(5, 2, 13, 7); + SHA_RND4(A, B, C, D, E, 5); + SHA_MIX(6, 3, 14, 8); + SHA_RND4(E, A, B, C, D, 6); + SHA_MIX(7, 4, 15, 9); + SHA_RND4(D, E, A, B, C, 7); + SHA_MIX(8, 5, 0, 10); + SHA_RND4(C, D, E, A, B, 8); + SHA_MIX(9, 6, 1, 11); + SHA_RND4(B, C, D, E, A, 9); + SHA_MIX(10, 7, 2, 12); + SHA_RND4(A, B, C, D, E, 10); + SHA_MIX(11, 8, 3, 13); + SHA_RND4(E, A, B, C, D, 11); + SHA_MIX(12, 9, 4, 14); + SHA_RND4(D, E, A, B, C, 12); + SHA_MIX(13, 10, 5, 15); + SHA_RND4(C, D, E, A, B, 13); + SHA_MIX(14, 11, 6, 0); + SHA_RND4(B, C, D, E, A, 14); + SHA_MIX(15, 12, 7, 1); + SHA_RND4(A, B, C, D, E, 15); + + XH(0) += A; + XH(1) += B; + XH(2) += C; + XH(3) += D; + XH(4) += E; +} + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/SHA1.h b/cocos/bindings/jswrapper/v8/debugger/SHA1.h new file mode 100644 index 0000000..c35b172 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/SHA1.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Simple class for computing SHA1. */ + +#pragma once + +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + +//#include "mozilla/Types.h" + + #include + #include + + #ifdef MFBT_API + #undef MFBT_API + #endif + + #define MFBT_API + +namespace se { + +/** + * This class computes the SHA1 hash of a byte sequence, or of the concatenation + * of multiple sequences. For example, computing the SHA1 of two sequences of + * bytes could be done as follows: + * + * void SHA1(const uint8_t* buf1, uint32_t size1, + * const uint8_t* buf2, uint32_t size2, + * SHA1Sum::Hash& hash) + * { + * SHA1Sum s; + * s.update(buf1, size1); + * s.update(buf2, size2); + * s.finish(hash); + * } + * + * The finish method may only be called once and cannot be followed by calls + * to update. + */ +class SHA1Sum { + union { + uint32_t mW[16]; /* input buffer */ + uint8_t mB[64]; + } mU; + uint64_t mSize; /* count of hashed bytes. */ + unsigned mH[22]; /* 5 state variables, 16 tmp values, 1 extra */ + bool mDone; + +public: + MFBT_API SHA1Sum(); + + static const size_t kHashSize = 20; + typedef uint8_t Hash[kHashSize]; + + /* Add len bytes of dataIn to the data sequence being hashed. */ + MFBT_API void update(const void *aData, uint32_t aLength); + + /* Compute the final hash of all data into hashOut. */ + MFBT_API void finish(SHA1Sum::Hash &aHashOut); +}; + +} /* namespace se */ + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/base64.h b/cocos/bindings/jswrapper/v8/debugger/base64.h new file mode 100644 index 0000000..37753d8 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/base64.h @@ -0,0 +1,188 @@ +#ifndef SRC_BASE64_H_ +#define SRC_BASE64_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + + #include "util.h" + + #include + +namespace node { + //// Base 64 //// + #define base64_encoded_size(size) ((size + 2 - ((size + 2) % 3)) / 3 * 4) + +// Doesn't check for padding at the end. Can be 1-2 bytes over. +static inline size_t base64_decoded_size_fast(size_t size) { + size_t remainder = size % 4; + + size = (size / 4) * 3; + if (remainder) { + if (size == 0 && remainder == 1) { + // special case: 1-byte input cannot be decoded + size = 0; + } else { + // non-padded input, add 1 or 2 extra bytes + size += 1 + (remainder == 3); + } + } + + return size; +} + +template +size_t base64_decoded_size(const TypeName *src, size_t size) { + if (size == 0) + return 0; + + if (src[size - 1] == '=') + size--; + if (size > 0 && src[size - 1] == '=') + size--; + + return base64_decoded_size_fast(size); +} + +extern const int8_t unbase64_table[256]; + + #define unbase64(x) \ + static_cast(unbase64_table[static_cast(x)]) + +template +bool base64_decode_group_slow(char *const dst, const size_t dstlen, + const TypeName *const src, const size_t srclen, + size_t *const i, size_t *const k) { + uint8_t hi; + uint8_t lo; + #define V(expr) \ + for (;;) { \ + const uint8_t c = src[*i]; \ + lo = unbase64(c); \ + *i += 1; \ + if (lo < 64) \ + break; /* Legal character. */ \ + if (c == '=' || *i >= srclen) \ + return false; /* Stop decoding. */ \ + } \ + expr; \ + if (*i >= srclen) \ + return false; \ + if (*k >= dstlen) \ + return false; \ + hi = lo; + V(do {} while (false) /* empty*/); + V(dst[(*k)++] = ((hi & 0x3F) << 2) | ((lo & 0x30) >> 4)); + V(dst[(*k)++] = ((hi & 0x0F) << 4) | ((lo & 0x3C) >> 2)); + V(dst[(*k)++] = ((hi & 0x03) << 6) | ((lo & 0x3F) >> 0)); + #undef V + return true; // Continue decoding. +} + +template +size_t base64_decode_fast(char *const dst, const size_t dstlen, + const TypeName *const src, const size_t srclen, + const size_t decoded_size) { + const size_t available = dstlen < decoded_size ? dstlen : decoded_size; + const size_t max_k = available / 3 * 3; + size_t max_i = srclen / 4 * 4; + size_t i = 0; + size_t k = 0; + while (i < max_i && k < max_k) { + const uint32_t v = + unbase64(src[i + 0]) << 24 | + unbase64(src[i + 1]) << 16 | + unbase64(src[i + 2]) << 8 | + unbase64(src[i + 3]); + // If MSB is set, input contains whitespace or is not valid base64. + if (v & 0x80808080) { + if (!base64_decode_group_slow(dst, dstlen, src, srclen, &i, &k)) + return k; + max_i = i + (srclen - i) / 4 * 4; // Align max_i again. + } else { + dst[k + 0] = ((v >> 22) & 0xFC) | ((v >> 20) & 0x03); + dst[k + 1] = ((v >> 12) & 0xF0) | ((v >> 10) & 0x0F); + dst[k + 2] = ((v >> 2) & 0xC0) | ((v >> 0) & 0x3F); + i += 4; + k += 3; + } + } + if (i < srclen && k < dstlen) { + base64_decode_group_slow(dst, dstlen, src, srclen, &i, &k); + } + return k; +} + +template +size_t base64_decode(char *const dst, const size_t dstlen, + const TypeName *const src, const size_t srclen) { + const size_t decoded_size = base64_decoded_size(src, srclen); + return base64_decode_fast(dst, dstlen, src, srclen, decoded_size); +} + +static size_t base64_encode(const char *src, + size_t slen, + char *dst, + size_t dlen) { + // We know how much we'll write, just make sure that there's space. + CHECK(dlen >= base64_encoded_size(slen) && + "not enough space provided for base64 encode"); + + dlen = base64_encoded_size(slen); + + unsigned a; + unsigned b; + unsigned c; + unsigned i; + unsigned k; + unsigned n; + + static const char table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + i = 0; + k = 0; + n = static_cast(slen / 3 * 3); + + while (i < n) { + a = src[i + 0] & 0xff; + b = src[i + 1] & 0xff; + c = src[i + 2] & 0xff; + + dst[k + 0] = table[a >> 2]; + dst[k + 1] = table[((a & 3) << 4) | (b >> 4)]; + dst[k + 2] = table[((b & 0x0f) << 2) | (c >> 6)]; + dst[k + 3] = table[c & 0x3f]; + + i += 3; + k += 4; + } + + if (n != slen) { + switch (slen - n) { + case 1: + a = src[i + 0] & 0xff; + dst[k + 0] = table[a >> 2]; + dst[k + 1] = table[(a & 3) << 4]; + dst[k + 2] = '='; + dst[k + 3] = '='; + break; + + case 2: + a = src[i + 0] & 0xff; + b = src[i + 1] & 0xff; + dst[k + 0] = table[a >> 2]; + dst[k + 1] = table[((a & 3) << 4) | (b >> 4)]; + dst[k + 2] = table[(b & 0x0f) << 2]; + dst[k + 3] = '='; + break; + } + } + + return dlen; +} +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_BASE64_H_ diff --git a/cocos/bindings/jswrapper/v8/debugger/env.cpp b/cocos/bindings/jswrapper/v8/debugger/env.cpp new file mode 100644 index 0000000..fc460e6 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/env.cpp @@ -0,0 +1,117 @@ +#include "env.h" + +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "uv.h" + +using namespace v8; + +namespace node { + +void Environment::Start(int argc, + const char *const *argv, + int exec_argc, + const char *const *exec_argv, + bool start_profiler_idle_notifier) { + HandleScope handle_scope(isolate()); + Context::Scope context_scope(context()); + + // uv_check_init(event_loop(), immediate_check_handle()); + // uv_unref(reinterpret_cast(immediate_check_handle())); + // + // uv_idle_init(event_loop(), immediate_idle_handle()); + // + // // Inform V8's CPU profiler when we're idle. The profiler is sampling-based + // // but not all samples are created equal; mark the wall clock time spent in + // // epoll_wait() and friends so profiling tools can filter it out. The samples + // // still end up in v8.log but with state=IDLE rather than state=EXTERNAL. + // // REFINE(bnoordhuis) Depends on a libuv implementation detail that we should + // // probably fortify in the API contract, namely that the last started prepare + // // or check watcher runs first. It's not 100% foolproof; if an add-on starts + // // a prepare or check watcher after us, any samples attributed to its callback + // // will be recorded with state=IDLE. + // uv_prepare_init(event_loop(), &idle_prepare_handle_); + // uv_check_init(event_loop(), &idle_check_handle_); + // uv_unref(reinterpret_cast(&idle_prepare_handle_)); + // uv_unref(reinterpret_cast(&idle_check_handle_)); + // + // uv_timer_init(event_loop(), destroy_ids_timer_handle()); + // + // auto close_and_finish = [](Environment* env, uv_handle_t* handle, void* arg) { + // handle->data = env; + // + // uv_close(handle, [](uv_handle_t* handle) { + // static_cast(handle->data)->FinishHandleCleanup(handle); + // }); + // }; + // + // RegisterHandleCleanup( + // reinterpret_cast(immediate_check_handle()), + // close_and_finish, + // nullptr); + // RegisterHandleCleanup( + // reinterpret_cast(immediate_idle_handle()), + // close_and_finish, + // nullptr); + // RegisterHandleCleanup( + // reinterpret_cast(&idle_prepare_handle_), + // close_and_finish, + // nullptr); + // RegisterHandleCleanup( + // reinterpret_cast(&idle_check_handle_), + // close_and_finish, + // nullptr); + + if (start_profiler_idle_notifier) { + StartProfilerIdleNotifier(); + } + + auto process_template = FunctionTemplate::New(isolate()); + process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate(), "process")); + + auto process_object = + process_template->GetFunction(context()).ToLocalChecked()->NewInstance(context()).ToLocalChecked(); + set_process_object(process_object); + + SetupProcessObject(this, argc, argv, exec_argc, exec_argv); + // LoadAsyncWrapperInfo(this); +} + +void Environment::AssignToContext(v8::Local context) { + context->SetAlignedPointerInEmbedderData(kContextEmbedderDataIndex, this); +} + +void Environment::CleanupHandles() { + // while (HandleCleanup* hc = handle_cleanup_queue_.PopFront()) { + // handle_cleanup_waiting_++; + // hc->cb_(this, hc->handle_, hc->arg_); + // delete hc; + // } + // + // while (handle_cleanup_waiting_ != 0) + // uv_run(event_loop(), UV_RUN_ONCE); + // + // while (handle_cleanup_waiting_ != 0) + // uv_run(event_loop(), UV_RUN_ONCE); +} + +void Environment::StartProfilerIdleNotifier() { + // uv_prepare_start(&idle_prepare_handle_, [](uv_prepare_t* handle) { + // Environment* env = ContainerOf(&Environment::idle_prepare_handle_, handle); + // env->isolate()->GetCpuProfiler()->SetIdle(true); + // }); + // + // uv_check_start(&idle_check_handle_, [](uv_check_t* handle) { + // Environment* env = ContainerOf(&Environment::idle_check_handle_, handle); + // env->isolate()->GetCpuProfiler()->SetIdle(false); + // }); +} + +void Environment::StopProfilerIdleNotifier() { + // uv_prepare_stop(&idle_prepare_handle_); + // uv_check_stop(&idle_check_handle_); +} + +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/env.h b/cocos/bindings/jswrapper/v8/debugger/env.h new file mode 100644 index 0000000..302dfc7 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/env.h @@ -0,0 +1,618 @@ +#pragma once +// clang-format off +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "inspector_agent.h" + #include "v8.h" + #include "uv.h" + #include "util.h" + #include "node.h" + +namespace node { + +// Pick an index that's hopefully out of the way when we're embedded inside +// another application. Performance-wise or memory-wise it doesn't matter: +// Context::SetAlignedPointerInEmbedderData() is backed by a FixedArray, +// worst case we pay a one-time penalty for resizing the array. + #ifndef NODE_CONTEXT_EMBEDDER_DATA_INDEX + #define NODE_CONTEXT_EMBEDDER_DATA_INDEX 32 + #endif + +// PER_ISOLATE_* macros: We have a lot of per-isolate properties +// and adding and maintaining their getters and setters by hand would be +// difficult so let's make the preprocessor generate them for us. +// +// In each macro, `V` is expected to be the name of a macro or function which +// accepts the number of arguments provided in each tuple in the macro body, +// typically two. The named function will be invoked against each tuple. +// +// Make sure that any macro V defined for use with the PER_ISOLATE_* macros is +// undefined again after use. + +// Private symbols are per-isolate primitives but Environment proxies them +// for the sake of convenience. Strings should be ASCII-only and have a +// "node:" prefix to avoid name clashes with third-party code. + #define PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V) \ + V(alpn_buffer_private_symbol, "node:alpnBuffer") \ + V(arrow_message_private_symbol, "node:arrowMessage") \ + V(contextify_context_private_symbol, "node:contextify:context") \ + V(contextify_global_private_symbol, "node:contextify:global") \ + V(inspector_delegate_private_symbol, "node:inspector:delegate") \ + V(decorated_private_symbol, "node:decorated") \ + V(npn_buffer_private_symbol, "node:npnBuffer") \ + V(processed_private_symbol, "node:processed") \ + V(selected_npn_buffer_private_symbol, "node:selectedNpnBuffer") + +// Strings are per-isolate primitives but Environment proxies them +// for the sake of convenience. Strings should be ASCII-only. + #define PER_ISOLATE_STRING_PROPERTIES(V) \ + V(address_string, "address") \ + V(args_string, "args") \ + V(async, "async") \ + V(buffer_string, "buffer") \ + V(bytes_string, "bytes") \ + V(bytes_parsed_string, "bytesParsed") \ + V(bytes_read_string, "bytesRead") \ + V(cached_data_string, "cachedData") \ + V(cached_data_produced_string, "cachedDataProduced") \ + V(cached_data_rejected_string, "cachedDataRejected") \ + V(callback_string, "callback") \ + V(change_string, "change") \ + V(channel_string, "channel") \ + V(oncertcb_string, "oncertcb") \ + V(onclose_string, "_onclose") \ + V(code_string, "code") \ + V(configurable_string, "configurable") \ + V(cwd_string, "cwd") \ + V(dest_string, "dest") \ + V(detached_string, "detached") \ + V(disposed_string, "_disposed") \ + V(dns_a_string, "A") \ + V(dns_aaaa_string, "AAAA") \ + V(dns_cname_string, "CNAME") \ + V(dns_mx_string, "MX") \ + V(dns_naptr_string, "NAPTR") \ + V(dns_ns_string, "NS") \ + V(dns_ptr_string, "PTR") \ + V(dns_soa_string, "SOA") \ + V(dns_srv_string, "SRV") \ + V(dns_txt_string, "TXT") \ + V(domain_string, "domain") \ + V(emitting_top_level_domain_error_string, "_emittingTopLevelDomainError") \ + V(exchange_string, "exchange") \ + V(enumerable_string, "enumerable") \ + V(idle_string, "idle") \ + V(irq_string, "irq") \ + V(encoding_string, "encoding") \ + V(enter_string, "enter") \ + V(entries_string, "entries") \ + V(env_pairs_string, "envPairs") \ + V(errno_string, "errno") \ + V(error_string, "error") \ + V(events_string, "_events") \ + V(exiting_string, "_exiting") \ + V(exit_code_string, "exitCode") \ + V(exit_string, "exit") \ + V(expire_string, "expire") \ + V(exponent_string, "exponent") \ + V(exports_string, "exports") \ + V(ext_key_usage_string, "ext_key_usage") \ + V(external_stream_string, "_externalStream") \ + V(family_string, "family") \ + V(fatal_exception_string, "_fatalException") \ + V(fd_string, "fd") \ + V(file_string, "file") \ + V(fingerprint_string, "fingerprint") \ + V(flags_string, "flags") \ + V(get_string, "get") \ + V(get_data_clone_error_string, "_getDataCloneError") \ + V(get_shared_array_buffer_id_string, "_getSharedArrayBufferId") \ + V(gid_string, "gid") \ + V(handle_string, "handle") \ + V(homedir_string, "homedir") \ + V(hostmaster_string, "hostmaster") \ + V(ignore_string, "ignore") \ + V(immediate_callback_string, "_immediateCallback") \ + V(infoaccess_string, "infoAccess") \ + V(inherit_string, "inherit") \ + V(input_string, "input") \ + V(internal_string, "internal") \ + V(ipv4_string, "IPv4") \ + V(ipv6_string, "IPv6") \ + V(isalive_string, "isAlive") \ + V(isclosing_string, "isClosing") \ + V(issuer_string, "issuer") \ + V(issuercert_string, "issuerCertificate") \ + V(kill_signal_string, "killSignal") \ + V(length_string, "length") \ + V(mac_string, "mac") \ + V(max_buffer_string, "maxBuffer") \ + V(message_string, "message") \ + V(minttl_string, "minttl") \ + V(model_string, "model") \ + V(modulus_string, "modulus") \ + V(name_string, "name") \ + V(netmask_string, "netmask") \ + V(nice_string, "nice") \ + V(nsname_string, "nsname") \ + V(ocsp_request_string, "OCSPRequest") \ + V(onchange_string, "onchange") \ + V(onclienthello_string, "onclienthello") \ + V(oncomplete_string, "oncomplete") \ + V(onconnection_string, "onconnection") \ + V(ondone_string, "ondone") \ + V(onerror_string, "onerror") \ + V(onexit_string, "onexit") \ + V(onhandshakedone_string, "onhandshakedone") \ + V(onhandshakestart_string, "onhandshakestart") \ + V(onmessage_string, "onmessage") \ + V(onnewsession_string, "onnewsession") \ + V(onnewsessiondone_string, "onnewsessiondone") \ + V(onocspresponse_string, "onocspresponse") \ + V(onread_string, "onread") \ + V(onreadstart_string, "onreadstart") \ + V(onreadstop_string, "onreadstop") \ + V(onselect_string, "onselect") \ + V(onshutdown_string, "onshutdown") \ + V(onsignal_string, "onsignal") \ + V(onstop_string, "onstop") \ + V(onwrite_string, "onwrite") \ + V(output_string, "output") \ + V(order_string, "order") \ + V(owner_string, "owner") \ + V(parse_error_string, "Parse Error") \ + V(path_string, "path") \ + V(pbkdf2_error_string, "PBKDF2 Error") \ + V(pid_string, "pid") \ + V(pipe_string, "pipe") \ + V(port_string, "port") \ + V(preference_string, "preference") \ + V(priority_string, "priority") \ + V(produce_cached_data_string, "produceCachedData") \ + V(raw_string, "raw") \ + V(read_host_object_string, "_readHostObject") \ + V(readable_string, "readable") \ + V(received_shutdown_string, "receivedShutdown") \ + V(refresh_string, "refresh") \ + V(regexp_string, "regexp") \ + V(rename_string, "rename") \ + V(replacement_string, "replacement") \ + V(retry_string, "retry") \ + V(serial_string, "serial") \ + V(scopeid_string, "scopeid") \ + V(sent_shutdown_string, "sentShutdown") \ + V(serial_number_string, "serialNumber") \ + V(service_string, "service") \ + V(servername_string, "servername") \ + V(session_id_string, "sessionId") \ + V(set_string, "set") \ + V(shell_string, "shell") \ + V(signal_string, "signal") \ + V(size_string, "size") \ + V(sni_context_err_string, "Invalid SNI context") \ + V(sni_context_string, "sni_context") \ + V(speed_string, "speed") \ + V(stack_string, "stack") \ + V(status_string, "status") \ + V(stdio_string, "stdio") \ + V(subject_string, "subject") \ + V(subjectaltname_string, "subjectaltname") \ + V(sys_string, "sys") \ + V(syscall_string, "syscall") \ + V(tick_callback_string, "_tickCallback") \ + V(tick_domain_cb_string, "_tickDomainCallback") \ + V(ticketkeycallback_string, "onticketkeycallback") \ + V(timeout_string, "timeout") \ + V(times_string, "times") \ + V(tls_ticket_string, "tlsTicket") \ + V(ttl_string, "ttl") \ + V(type_string, "type") \ + V(uid_string, "uid") \ + V(unknown_string, "") \ + V(user_string, "user") \ + V(username_string, "username") \ + V(valid_from_string, "valid_from") \ + V(valid_to_string, "valid_to") \ + V(value_string, "value") \ + V(verify_error_string, "verifyError") \ + V(version_string, "version") \ + V(weight_string, "weight") \ + V(windows_verbatim_arguments_string, "windowsVerbatimArguments") \ + V(wrap_string, "wrap") \ + V(writable_string, "writable") \ + V(write_host_object_string, "_writeHostObject") \ + V(write_queue_size_string, "writeQueueSize") \ + V(x_forwarded_string, "x-forwarded-for") \ + V(zero_return_string, "ZERO_RETURN") + + #define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \ + V(as_external, v8::External) \ + V(async_hooks_destroy_function, v8::Function) \ + V(async_hooks_init_function, v8::Function) \ + V(async_hooks_before_function, v8::Function) \ + V(async_hooks_after_function, v8::Function) \ + V(binding_cache_object, v8::Object) \ + V(buffer_constructor_function, v8::Function) \ + V(buffer_prototype_object, v8::Object) \ + V(context, v8::Context) \ + V(domain_array, v8::Array) \ + V(domains_stack_array, v8::Array) \ + V(jsstream_constructor_template, v8::FunctionTemplate) \ + V(module_load_list_array, v8::Array) \ + V(pbkdf2_constructor_template, v8::ObjectTemplate) \ + V(pipe_constructor_template, v8::FunctionTemplate) \ + V(process_object, v8::Object) \ + V(promise_reject_function, v8::Function) \ + V(promise_wrap_template, v8::ObjectTemplate) \ + V(push_values_to_array_function, v8::Function) \ + V(randombytes_constructor_template, v8::ObjectTemplate) \ + V(script_context_constructor_template, v8::FunctionTemplate) \ + V(script_data_constructor_function, v8::Function) \ + V(secure_context_constructor_template, v8::FunctionTemplate) \ + V(tcp_constructor_template, v8::FunctionTemplate) \ + V(tick_callback_function, v8::Function) \ + V(tls_wrap_constructor_function, v8::Function) \ + V(tls_wrap_constructor_template, v8::FunctionTemplate) \ + V(tty_constructor_template, v8::FunctionTemplate) \ + V(udp_constructor_function, v8::Function) \ + V(url_constructor_function, v8::Function) \ + V(write_wrap_constructor_function, v8::Function) + +class IsolateData { +public: + inline IsolateData(v8::Isolate *isolate, uv_loop_t *event_loop, + uint32_t *zero_fill_field = nullptr); + inline uv_loop_t *event_loop() const; + inline uint32_t *zero_fill_field() const; + + #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) + #define VS(PropertyName, StringValue) V(v8::String, PropertyName) + #define V(TypeName, PropertyName) \ + inline v8::Local PropertyName(v8::Isolate *isolate) const; + PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) + PER_ISOLATE_STRING_PROPERTIES(VS) + #undef V + #undef VS + #undef VP + +private: + #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) + #define VS(PropertyName, StringValue) V(v8::String, PropertyName) + #define V(TypeName, PropertyName) \ + v8::Eternal PropertyName##_; + PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) + PER_ISOLATE_STRING_PROPERTIES(VS) + #undef V + #undef VS + #undef VP + + uv_loop_t *const event_loop_; + uint32_t *const zero_fill_field_; + + NODE_DISALLOW_COPY_AND_ASSIGN(IsolateData); +}; + +class Environment { +public: + static const int kContextEmbedderDataIndex = NODE_CONTEXT_EMBEDDER_DATA_INDEX; + + static inline Environment *GetCurrent(v8::Isolate *isolate); + static inline Environment *GetCurrent(v8::Local context); + static inline Environment *GetCurrent(const v8::FunctionCallbackInfo &info); + + template + static inline Environment *GetCurrent(const v8::PropertyCallbackInfo &info); + + inline Environment(IsolateData *isolate_data, v8::Local context); + inline ~Environment(); + + void Start(int argc, + const char *const *argv, + int exec_argc, + const char *const *exec_argv, + bool start_profiler_idle_notifier); + void AssignToContext(v8::Local context); + void CleanupHandles(); + + void StartProfilerIdleNotifier(); + void StopProfilerIdleNotifier(); + + inline v8::Isolate *isolate() const { + return isolate_; + } + + inline IsolateData *isolate_data() const { + return isolate_data_; + } + + inline uv_loop_t *event_loop() const { + return isolate_data()->event_loop(); + } + + inline inspector::Agent *inspector_agent() { + return &inspector_agent_; + } + + // Strings and private symbols are shared across shared contexts + // The getters simply proxy to the per-isolate primitive. + #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) + #define VS(PropertyName, StringValue) V(v8::String, PropertyName) + #define V(TypeName, PropertyName) \ + inline v8::Local PropertyName() const; + PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) + PER_ISOLATE_STRING_PROPERTIES(VS) + #undef V + #undef VS + #undef VP + + #define V(PropertyName, TypeName) \ + inline v8::Local PropertyName() const; \ + inline void set_##PropertyName(v8::Local value); + ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) + #undef V + + inline void ThrowError(const char *errmsg); + inline void ThrowTypeError(const char *errmsg); + inline void ThrowRangeError(const char *errmsg); + inline void ThrowErrnoException(int errorno, + const char *syscall = nullptr, + const char *message = nullptr, + const char *path = nullptr); + inline void ThrowUVException(int errorno, + const char *syscall = nullptr, + const char *message = nullptr, + const char *path = nullptr, + const char *dest = nullptr); + + inline v8::Local + NewFunctionTemplate(v8::FunctionCallback callback, + v8::Local signature = + v8::Local()); + + // Convenience methods for NewFunctionTemplate(). + inline void SetMethod(v8::Local that, + const char *name, + v8::FunctionCallback callback); + + class AsyncCallbackScope { + public: + AsyncCallbackScope() = delete; + explicit AsyncCallbackScope(Environment *env); + ~AsyncCallbackScope(); + inline bool in_makecallback(); + + private: + Environment *env_; + + NODE_DISALLOW_COPY_AND_ASSIGN(AsyncCallbackScope); + }; + +private: + inline void ThrowError(v8::Local (*fun)(v8::Local), + const char *errmsg); + + v8::Isolate *const isolate_; + IsolateData *const isolate_data_; + + #define V(PropertyName, TypeName) \ + v8::Persistent PropertyName##_; + ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) + #undef V + inspector::Agent inspector_agent_; + size_t makecallback_cntr_; +}; + +inline IsolateData::IsolateData(v8::Isolate *isolate, uv_loop_t *event_loop, + uint32_t *zero_fill_field) : + // Create string and private symbol properties as internalized one byte strings. + // + // Internalized because it makes property lookups a little faster and because + // the string is created in the old space straight away. It's going to end up + // in the old space sooner or later anyway but now it doesn't go through + // v8::Eternal's new space handling first. + // + // One byte because our strings are ASCII and we can safely skip V8's UTF-8 + // decoding step. It's a one-time cost, but why pay it when you don't have to? + #define V(PropertyName, StringValue) \ + PropertyName##_( \ + isolate, \ + v8::Private::New( \ + isolate, \ + v8::String::NewFromOneByte( \ + isolate, \ + reinterpret_cast(StringValue), \ + v8::NewStringType::kInternalized, \ + sizeof(StringValue) - 1) \ + .ToLocalChecked())), + PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V) + #undef V + #define V(PropertyName, StringValue) \ + PropertyName##_( \ + isolate, \ + v8::String::NewFromOneByte( \ + isolate, \ + reinterpret_cast(StringValue), \ + v8::NewStringType::kInternalized, \ + sizeof(StringValue) - 1) \ + .ToLocalChecked()), + PER_ISOLATE_STRING_PROPERTIES(V) + #undef V + event_loop_(event_loop), + zero_fill_field_(zero_fill_field) { +} + +inline uv_loop_t *IsolateData::event_loop() const { + return event_loop_; +} + +inline uint32_t *IsolateData::zero_fill_field() const { + return zero_fill_field_; +} + +inline Environment *Environment::GetCurrent(v8::Isolate *isolate) { + return GetCurrent(isolate->GetCurrentContext()); +} + +inline Environment *Environment::GetCurrent(v8::Local context) { + return static_cast( + context->GetAlignedPointerFromEmbedderData(kContextEmbedderDataIndex)); +} + +inline Environment *Environment::GetCurrent( + const v8::FunctionCallbackInfo &info) { + CHECK(info.Data()->IsExternal()); + return static_cast(info.Data().As()->Value()); +} + +template +inline Environment *Environment::GetCurrent( + const v8::PropertyCallbackInfo &info) { + CHECK(info.Data()->IsExternal()); + // XXX(bnoordhuis) Work around a g++ 4.9.2 template type inferrer bug + // when the expression is written as info.Data().As(). + v8::Local data = info.Data(); + return static_cast(data.As()->Value()); +} + +inline Environment::Environment(IsolateData *isolate_data, v8::Local context) +: isolate_(context->GetIsolate()), + isolate_data_(isolate_data), + inspector_agent_(this), + makecallback_cntr_(0), + context_(context->GetIsolate(), context) { + // We'll be creating new objects so make sure we've entered the context. + v8::HandleScope handle_scope(isolate()); + v8::Context::Scope context_scope(context); + set_as_external(v8::External::New(isolate(), this)); + set_binding_cache_object(v8::Object::New(isolate())); + set_module_load_list_array(v8::Array::New(isolate())); + + AssignToContext(context); + + //cjh destroy_ids_list_.reserve(512); +} + +inline Environment::~Environment() { + v8::HandleScope handle_scope(isolate()); + + context()->SetAlignedPointerInEmbedderData(kContextEmbedderDataIndex, + nullptr); + #define V(PropertyName, TypeName) PropertyName##_.Reset(); + ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) + #undef V + + // delete[] heap_statistics_buffer_; + // delete[] heap_space_statistics_buffer_; + // delete[] http_parser_buffer_; +} + +inline void Environment::SetMethod(v8::Local that, + const char *name, + v8::FunctionCallback callback) { + v8::Local function = + NewFunctionTemplate(callback)->GetFunction(context()).ToLocalChecked(); + // kInternalized strings are created in the old space. + const v8::NewStringType type = v8::NewStringType::kInternalized; + v8::Local name_string = + v8::String::NewFromUtf8(isolate(), name, type).ToLocalChecked(); + that->Set(isolate()->GetCurrentContext(), name_string, function).Check(); + function->SetName(name_string); // NODE_SET_METHOD() compatibility. +} + +inline void Environment::ThrowError(const char *errmsg) { + ThrowError(v8::Exception::Error, errmsg); +} + +inline void Environment::ThrowTypeError(const char *errmsg) { + ThrowError(v8::Exception::TypeError, errmsg); +} + +inline void Environment::ThrowRangeError(const char *errmsg) { + ThrowError(v8::Exception::RangeError, errmsg); +} + +inline void Environment::ThrowError( + v8::Local (*fun)(v8::Local), + const char *errmsg) { + v8::HandleScope handle_scope(isolate()); + isolate()->ThrowException(fun(OneByteString(isolate(), errmsg))); +} + +inline void Environment::ThrowErrnoException(int errorno, + const char *syscall, + const char *message, + const char *path) { + isolate()->ThrowException( + ErrnoException(isolate(), errorno, syscall, message, path)); +} + +inline void Environment::ThrowUVException(int errorno, + const char *syscall, + const char *message, + const char *path, + const char *dest) { + isolate()->ThrowException( + UVException(isolate(), errorno, syscall, message, path, dest)); +} + +inline v8::Local +Environment::NewFunctionTemplate(v8::FunctionCallback callback, + v8::Local signature) { + v8::Local external = as_external(); + return v8::FunctionTemplate::New(isolate(), callback, external, signature); +} + + #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) + #define VS(PropertyName, StringValue) V(v8::String, PropertyName) + #define V(TypeName, PropertyName) \ + inline v8::Local IsolateData::PropertyName(v8::Isolate *isolate) const { \ + /* Strings are immutable so casting away const-ness here is okay. */ \ + return const_cast(this)->PropertyName##_.Get(isolate); \ + } +PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) +PER_ISOLATE_STRING_PROPERTIES(VS) + #undef V + #undef VS + #undef VP + + #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) + #define VS(PropertyName, StringValue) V(v8::String, PropertyName) + #define V(TypeName, PropertyName) \ + inline v8::Local Environment::PropertyName() const { \ + return isolate_data()->PropertyName(isolate()); \ + } +PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) +PER_ISOLATE_STRING_PROPERTIES(VS) + #undef V + #undef VS + #undef VP + + #define V(PropertyName, TypeName) \ + inline v8::Local Environment::PropertyName() const { \ + return StrongPersistentToLocal(PropertyName##_); \ + } \ + inline void Environment::set_##PropertyName(v8::Local value) { \ + PropertyName##_.Reset(isolate(), value); \ + } +ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) + #undef V + +inline Environment::AsyncCallbackScope::AsyncCallbackScope(Environment *env) +: env_(env) { + env_->makecallback_cntr_++; +} + +inline Environment::AsyncCallbackScope::~AsyncCallbackScope() { + env_->makecallback_cntr_--; +} + +inline bool Environment::AsyncCallbackScope::in_makecallback() { + return env_->makecallback_cntr_ > 1; +} + +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + +// clang-format on \ No newline at end of file diff --git a/cocos/bindings/jswrapper/v8/debugger/http_parser.cpp b/cocos/bindings/jswrapper/v8/debugger/http_parser.cpp new file mode 100644 index 0000000..b1ccaa2 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/http_parser.cpp @@ -0,0 +1,2409 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" + +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include + #include + #include + #include + #include + #include + + #ifndef ULLONG_MAX + #define ULLONG_MAX ((uint64_t)-1) /* 2^64-1 */ + #endif + + #ifndef MIN + #define MIN(a, b) ((a) < (b) ? (a) : (b)) + #endif + + #ifndef ARRAY_SIZE + #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + #endif + + #ifndef BIT_AT + #define BIT_AT(a, i) \ + (!!((unsigned int)(a)[(unsigned int)(i) >> 3] & \ + (1 << ((unsigned int)(i)&7)))) + #endif + + #ifndef ELEM_AT + #define ELEM_AT(a, i, v) ((unsigned int)(i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) + #endif + + #define SET_ERRNO(e) \ + do { \ + parser->http_errno = (e); \ + } while (0) + + #define CURRENT_STATE() p_state + #define UPDATE_STATE(V) p_state = (enum state)(V); + #define RETURN(V) \ + do { \ + parser->state = CURRENT_STATE(); \ + return (V); \ + } while (0); + #define REEXECUTE() \ + goto reexecute; + + #ifdef __GNUC__ + #define LIKELY(X) __builtin_expect(!!(X), 1) + #define UNLIKELY(X) __builtin_expect(!!(X), 0) + #else + #define LIKELY(X) (X) + #define UNLIKELY(X) (X) + #endif + + /* Run the notify callback FOR, returning ER if it fails */ + #define CALLBACK_NOTIFY_(FOR, ER) \ + do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + UPDATE_STATE(parser->state); \ + \ + /* We either errored above or got paused; get out */ \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ + return (ER); \ + } \ + } \ + } while (0) + + /* Run the notify callback FOR and consume the current byte */ + #define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) + + /* Run the notify callback FOR and don't consume the current byte */ + #define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) + + /* Run data callback FOR with LEN bytes, returning ER if it fails */ + #define CALLBACK_DATA_(FOR, LEN, ER) \ + do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (FOR##_mark) { \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != \ + settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + UPDATE_STATE(parser->state); \ + \ + /* We either errored above or got paused; get out */ \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ + return (ER); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ + } while (0) + + /* Run the data callback FOR and consume the current byte */ + #define CALLBACK_DATA(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + + /* Run the data callback FOR and don't consume the current byte */ + #define CALLBACK_DATA_NOADVANCE(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + + /* Set the mark FOR; non-destructive if mark is already set */ + #define MARK(FOR) \ + do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ + } while (0) + + /* Don't allow the total size of the HTTP headers (including the status + * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect + * embedders against denial-of-service attacks where the attacker feeds + * us a never-ending header that the embedder keeps buffering. + * + * This check is arguably the responsibility of embedders but we're doing + * it on the embedder's behalf because most won't bother and this way we + * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger + * than any reasonable request or response so this should never affect + * day-to-day operation. + */ + #define COUNT_HEADER_SIZE(V) \ + do { \ + parser->nread += (V); \ + if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ + SET_ERRNO(HPE_HEADER_OVERFLOW); \ + goto error; \ + } \ + } while (0) + + #define PROXY_CONNECTION "proxy-connection" + #define CONNECTION "connection" + #define CONTENT_LENGTH "content-length" + #define TRANSFER_ENCODING "transfer-encoding" + #define UPGRADE "upgrade" + #define CHUNKED "chunked" + #define KEEP_ALIVE "keep-alive" + #define CLOSE "close" + +static const char *method_strings[] = //NOLINT(readability-identifier-naming) + { + #define XX(num, name, string) #string, + HTTP_METHOD_MAP(XX) + #undef XX +}; + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { + /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, + /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, + /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, + /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, + /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', + /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0, + /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', + /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, + /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', + /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', + /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', 0, '~', 0}; + +//NOLINTNEXTLINE(readability-identifier-naming) +static const int8_t unhex[256] = + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + + #if HTTP_PARSER_STRICT + #define T(v) 0 + #else + #define T(v) v + #endif + +//NOLINTNEXTLINE(readability-identifier-naming) +static const uint8_t normal_url_char[32] = { + /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, + /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, + /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, + /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +}; + + #undef T +//NOLINTNEXTLINE(readability-identifier-naming) +enum state { s_dead = 1 /* important that this is > 0 */ + + , + s_start_req_or_res, //NOLINT + s_res_or_resp_H, //NOLINT + s_start_res, //NOLINT + s_res_H, //NOLINT + s_res_HT, //NOLINT + s_res_HTT, //NOLINT + s_res_HTTP, //NOLINT + s_res_first_http_major, //NOLINT + s_res_http_major, //NOLINT + s_res_first_http_minor, //NOLINT + s_res_http_minor, //NOLINT + s_res_first_status_code, //NOLINT + s_res_status_code, //NOLINT + s_res_status_start, //NOLINT + s_res_status, //NOLINT + s_res_line_almost_done, //NOLINT + s_start_req, //NOLINT + s_req_method, //NOLINT + s_req_spaces_before_url, //NOLINT + s_req_schema, //NOLINT + s_req_schema_slash, //NOLINT + s_req_schema_slash_slash, //NOLINT + s_req_server_start, //NOLINT + s_req_server, //NOLINT + s_req_server_with_at, //NOLINT + s_req_path, //NOLINT + s_req_query_string_start, //NOLINT + s_req_query_string, //NOLINT + s_req_fragment_start, //NOLINT + s_req_fragment, //NOLINT + s_req_http_start, //NOLINT + s_req_http_H, //NOLINT + s_req_http_HT, //NOLINT + s_req_http_HTT, //NOLINT + s_req_http_HTTP, //NOLINT + s_req_first_http_major, //NOLINT + s_req_http_major, //NOLINT + s_req_first_http_minor, //NOLINT + s_req_http_minor, //NOLINT + s_req_line_almost_done //NOLINT + + , + s_header_field_start, //NOLINT + s_header_field, //NOLINT + s_header_value_discard_ws, //NOLINT + s_header_value_discard_ws_almost_done, //NOLINT + s_header_value_discard_lws, //NOLINT + s_header_value_start, //NOLINT + s_header_value, //NOLINT + s_header_value_lws, //NOLINT + s_header_almost_done, //NOLINT + s_chunk_size_start, //NOLINT + s_chunk_size, //NOLINT + s_chunk_parameters, //NOLINT + s_chunk_size_almost_done, //NOLINT + s_headers_almost_done, //NOLINT + s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , + s_chunk_data, //NOLINT + s_chunk_data_almost_done, //NOLINT + s_chunk_data_done //NOLINT + + , + s_body_identity, //NOLINT + s_body_identity_eof //NOLINT + , + s_message_done //NOLINT +}; + + #define PARSING_HEADER(state) (state <= s_headers_done) + +enum header_states { h_general = 0, //NOLINT + h_C, //NOLINT + h_CO, //NOLINT + h_CON //NOLINT + + , + h_matching_connection, //NOLINT + h_matching_proxy_connection, //NOLINT + h_matching_content_length, //NOLINT + h_matching_transfer_encoding, //NOLINT + h_matching_upgrade //NOLINT + , + h_connection, //NOLINT + h_content_length, //NOLINT + h_transfer_encoding, //NOLINT + h_upgrade //NOLINT + + , + h_matching_transfer_encoding_chunked, //NOLINT + h_matching_connection_token_start, //NOLINT + h_matching_connection_keep_alive, //NOLINT + h_matching_connection_close, //NOLINT + h_matching_connection_upgrade, //NOLINT + h_matching_connection_token //NOLINT + + , + h_transfer_encoding_chunked, //NOLINT + h_connection_keep_alive, //NOLINT + h_connection_close, //NOLINT + h_connection_upgrade //NOLINT +}; + +enum http_host_state { //NOLINT + s_http_host_dead = 1, //NOLINT + s_http_userinfo_start, //NOLINT + s_http_userinfo, //NOLINT + s_http_host_start, //NOLINT + s_http_host_v6_start, //NOLINT + s_http_host, //NOLINT + s_http_host_v6, //NOLINT + s_http_host_v6_end, //NOLINT + s_http_host_v6_zone_start, //NOLINT + s_http_host_v6_zone, //NOLINT + s_http_host_port_start, //NOLINT + s_http_host_port //NOLINT +}; + + /* Macros for character classes; depends on strict-mode */ + #define CR '\r' + #define LF '\n' + #define LOWER(c) (unsigned char)(c | 0x20) + #define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') + #define IS_NUM(c) ((c) >= '0' && (c) <= '9') + #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) + #define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) + #define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') + #define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + + #define STRICT_TOKEN(c) (tokens[(unsigned char)c]) + + #if HTTP_PARSER_STRICT + #define TOKEN(c) (tokens[(unsigned char)c]) + #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) + #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') + #else + #define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) + #define IS_URL_CHAR(c) \ + (BIT_AT(normal_url_char, (unsigned char)c) || ((c)&0x80)) + #define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') + #endif + + /** + * Verify that a char is a valid visible (printable) US-ASCII + * character or %x80-FF + **/ + #define IS_HEADER_CHAR(ch) \ + (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127)) + + #define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + #if HTTP_PARSER_STRICT + #define STRICT_CHECK(cond) \ + do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ + } while (0) + #define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) + #else + #define STRICT_CHECK(cond) + #define NEW_MESSAGE() start_state + #endif + + /* Map errno values to strings for human-readable output */ + #define HTTP_STRERROR_GEN(n, s) {"HPE_" #n, s}, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { //NOLINT + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)}; + #undef HTTP_STRERROR_GEN + +int http_message_needs_eof(const http_parser *parser); //NOLINT + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) { //NOLINT + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + + #if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } + #endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} +//NOLINTNEXTLINE +size_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) { + char c, ch; //NOLINT + int8_t unhex_val; //NOLINT + const char *p = data; + const char *header_field_mark = nullptr; + const char *header_value_mark = nullptr; + const char *url_mark = nullptr; + const char *body_mark = nullptr; + const char *status_mark = nullptr; + auto p_state = static_cast(parser->state); + const unsigned int lenient = parser->lenient_http_headers; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + if (len == 0) { + switch (CURRENT_STATE()) { + case s_body_identity_eof: + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + if (CURRENT_STATE() == s_header_field) { + header_field_mark = data; + } + if (CURRENT_STATE() == s_header_value) { + header_value_mark = data; + } + switch (CURRENT_STATE()) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_server: + case s_req_server_with_at: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + url_mark = data; + break; + case s_res_status: + status_mark = data; + break; + default: + break; + } + + for (p = data; p != data + len; p++) { + ch = *p; + + if (PARSING_HEADER(CURRENT_STATE())) { + COUNT_HEADER_SIZE(1); + } + + reexecute: + switch (CURRENT_STATE()) { + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (LIKELY(ch == CR || ch == LF)) { + break; + } + + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: { + if (ch == CR || ch == LF) { + break; + } + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (ch == 'H') { + UPDATE_STATE(s_res_or_resp_H); + + CALLBACK_NOTIFY(message_begin); + } else { + parser->type = HTTP_REQUEST; + UPDATE_STATE(s_start_req); + REEXECUTE(); + } + + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + UPDATE_STATE(s_res_HT); + } else { + if (UNLIKELY(ch != 'E')) { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + parser->index = 2; + UPDATE_STATE(s_req_method); + } + break; + + case s_start_res: { + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + switch (ch) { + case 'H': + UPDATE_STATE(s_res_H); + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + CALLBACK_NOTIFY(message_begin); + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HT); + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HTT); + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_res_HTTP); + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_res_first_http_major); + break; + + case s_res_first_http_major: + if (UNLIKELY(ch < '0' || ch > '9')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_res_http_major); + break; + + /* major HTTP version or dot */ + case s_res_http_major: { + if (ch == '.') { + UPDATE_STATE(s_res_first_http_minor); + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (UNLIKELY(parser->http_major > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_res_http_minor); + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: { + if (ch == ' ') { + UPDATE_STATE(s_res_first_status_code); + break; + } + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (UNLIKELY(parser->http_minor > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + UPDATE_STATE(s_res_status_code); + break; + } + + case s_res_status_code: { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + UPDATE_STATE(s_res_status_start); + break; + case CR: + UPDATE_STATE(s_res_line_almost_done); + break; + case LF: + UPDATE_STATE(s_header_field_start); + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (UNLIKELY(parser->status_code > 999)) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status_start: { + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + MARK(status); + UPDATE_STATE(s_res_status); + parser->index = 0; + break; + } + + case s_res_status: + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + CALLBACK_DATA(status); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA(status); + break; + } + + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_field_start); + break; + + case s_start_req: { + if (ch == CR || ch == LF) { + break; + } + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (UNLIKELY(!IS_ALPHA(ch))) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = static_cast(0); + parser->index = 1; + switch (ch) { + case 'A': parser->method = HTTP_ACL; break; + case 'B': parser->method = HTTP_BIND; break; + case 'C': + parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ + break; + case 'D': parser->method = HTTP_DELETE; break; + case 'G': parser->method = HTTP_GET; break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': + parser->method = HTTP_LOCK; /* or LINK */ + break; + case 'M': + parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ + break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': + parser->method = HTTP_POST; + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + break; + case 'R': + parser->method = HTTP_REPORT; /* or REBIND */ + break; + case 'S': + parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ + break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': + parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ + break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + UPDATE_STATE(s_req_method); + + CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: { + const char *matcher; + if (UNLIKELY(ch == '\0')) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + UPDATE_STATE(s_req_spaces_before_url); + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if (IS_ALPHA(ch)) { + switch (parser->method << 16 | parser->index << 8 | ch) { //NOLINT + #define XX(meth, pos, ch, new_meth) \ + case (HTTP_##meth << 16 | pos << 8 | ch): \ + parser->method = HTTP_##new_meth; \ + break; + + XX(POST, 1, 'U', PUT) + XX(POST, 1, 'A', PATCH) + XX(CONNECT, 1, 'H', CHECKOUT) + XX(CONNECT, 2, 'P', COPY) + XX(MKCOL, 1, 'O', MOVE) + XX(MKCOL, 1, 'E', MERGE) + XX(MKCOL, 2, 'A', MKACTIVITY) + XX(MKCOL, 3, 'A', MKCALENDAR) + XX(SUBSCRIBE, 1, 'E', SEARCH) + XX(REPORT, 2, 'B', REBIND) + XX(POST, 1, 'R', PROPFIND) + XX(PROPFIND, 4, 'P', PROPPATCH) + XX(PUT, 2, 'R', PURGE) + XX(LOCK, 1, 'I', LINK) + XX(UNLOCK, 2, 'S', UNSUBSCRIBE) + XX(UNLOCK, 2, 'B', UNBIND) + XX(UNLOCK, 3, 'I', UNLINK) + #undef XX + + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (ch == '-' && + parser->index == 1 && //NOLINT + parser->method == HTTP_MKCOL) { //NOLINT + parser->method = HTTP_MSEARCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++parser->index; + break; + } + + case s_req_spaces_before_url: { + if (ch == ' ') break; + + MARK(url); + if (parser->method == HTTP_CONNECT) { //NOLINT + UPDATE_STATE(s_req_server_start); + } + + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + break; + } + + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: { + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case CR: + case LF: + SET_ERRNO(HPE_INVALID_URL); + goto error; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + + break; + } + + case s_req_server: + case s_req_server_with_at: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: { + switch (ch) { + case ' ': + UPDATE_STATE(s_req_http_start); + CALLBACK_DATA(url); + break; + case CR: + case LF: + parser->http_major = 0; + parser->http_minor = 9; + UPDATE_STATE((ch == CR) ? s_req_line_almost_done : s_header_field_start); + CALLBACK_DATA(url); + break; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + UPDATE_STATE(s_req_http_H); + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HT); + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HTT); + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_req_http_HTTP); + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_req_first_http_major); + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (UNLIKELY(ch < '1' || ch > '9')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_req_http_major); + break; + + /* major HTTP version or dot */ + case s_req_http_major: { + if (ch == '.') { + UPDATE_STATE(s_req_first_http_minor); + break; + } + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (UNLIKELY(parser->http_major > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_req_http_minor); + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: { + if (ch == CR) { + UPDATE_STATE(s_req_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + /* XXX allow spaces after digit? */ + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (UNLIKELY(parser->http_minor > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + UPDATE_STATE(s_header_field_start); + break; + } + + case s_header_field_start: { + if (ch == CR) { + UPDATE_STATE(s_headers_almost_done); + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + UPDATE_STATE(s_headers_almost_done); + REEXECUTE(); + } + + c = TOKEN(ch); + + if (UNLIKELY(!c)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + parser->index = 0; + UPDATE_STATE(s_header_field); + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: { + const char *start = p; + for (; p != data + len; p++) { + ch = *p; + c = TOKEN(ch); + + if (!c) { + break; + } + + switch (parser->header_state) { //NOLINT + case h_general: + break; + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CONNECTION) - 1 || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION) - 2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION) - 1 || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION) - 2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH) - 1 || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH) - 2) { + if (parser->flags & F_CONTENTLENGTH) { //NOLINT + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + parser->header_state = h_content_length; + parser->flags |= F_CONTENTLENGTH; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING) - 1 || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING) - 2) { + parser->header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE) - 1 || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE) - 2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + } + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) { + --p; + break; + } + + if (ch == ':') { + UPDATE_STATE(s_header_value_discard_ws); + CALLBACK_DATA(header_field); + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_discard_ws: + if (ch == ' ' || ch == '\t') break; + + if (ch == CR) { + UPDATE_STATE(s_header_value_discard_ws_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + /* FALLTHROUGH */ + + case s_header_value_start: { + MARK(header_value); + + UPDATE_STATE(s_header_value); + parser->index = 0; + + c = LOWER(ch); + + switch (parser->header_state) { //NOLINT + case h_upgrade: + parser->flags |= F_UPGRADE; + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_general; + } + break; + + case h_content_length: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else if (c == 'u') { + parser->header_state = h_matching_connection_upgrade; + } else { + parser->header_state = h_matching_connection_token; + } + break; + + /* Multi-value `Connection` header */ + case h_matching_connection_token_start: + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: { + const char *start = p; + auto hState = static_cast(parser->header_state); + for (; p != data + len; p++) { + ch = *p; + if (ch == CR) { + UPDATE_STATE(s_header_almost_done); + parser->header_state = hState; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_almost_done); + COUNT_HEADER_SIZE(p - start); + parser->header_state = hState; + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + + if (!lenient && !IS_HEADER_CHAR(ch)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + c = LOWER(ch); + + switch (hState) { + case h_general: { + const char *pCr; + const char *pLf; + size_t limit = data + len - p; + + limit = MIN(limit, HTTP_MAX_HEADER_SIZE); + + pCr = static_cast(memchr(p, CR, limit)); + pLf = static_cast(memchr(p, LF, limit)); + if (pCr != nullptr) { + if (pLf != nullptr && pCr >= pLf) { + p = pLf; + } else { + p = pCr; + } + } else if (UNLIKELY(pLf != nullptr)) { + p = pLf; + } else { + p = data + len; + } + --p; + + break; + } + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: { + uint64_t t; + + if (ch == ' ') break; + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = hState; + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = hState; + goto error; + } + + parser->content_length = t; + break; + } + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED) - 1 || c != CHUNKED[parser->index]) { + hState = h_general; + } else if (parser->index == sizeof(CHUNKED) - 2) { + hState = h_transfer_encoding_chunked; + } + break; + + case h_matching_connection_token_start: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + hState = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + hState = h_matching_connection_close; + } else if (c == 'u') { + hState = h_matching_connection_upgrade; + } else if (STRICT_TOKEN(c)) { + hState = h_matching_connection_token; + } else if (c == ' ' || c == '\t') { + /* Skip lws */ + } else { + hState = h_general; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE) - 1 || c != KEEP_ALIVE[parser->index]) { + hState = h_matching_connection_token; + } else if (parser->index == sizeof(KEEP_ALIVE) - 2) { + hState = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE) - 1 || c != CLOSE[parser->index]) { + hState = h_matching_connection_token; + } else if (parser->index == sizeof(CLOSE) - 2) { + hState = h_connection_close; + } + break; + + /* looking for 'Connection: upgrade' */ + case h_matching_connection_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE) - 1 || + c != UPGRADE[parser->index]) { + hState = h_matching_connection_token; + } else if (parser->index == sizeof(UPGRADE) - 2) { + hState = h_connection_upgrade; + } + break; + + case h_matching_connection_token: + if (ch == ',') { + hState = h_matching_connection_token_start; + parser->index = 0; + } + break; + + case h_transfer_encoding_chunked: + if (ch != ' ') hState = h_general; + break; + + case h_connection_keep_alive: + case h_connection_close: + case h_connection_upgrade: + if (ch == ',') { + if (hState == h_connection_keep_alive) { + parser->flags |= F_CONNECTION_KEEP_ALIVE; + } else if (hState == h_connection_close) { + parser->flags |= F_CONNECTION_CLOSE; + } else if (hState == h_connection_upgrade) { + parser->flags |= F_CONNECTION_UPGRADE; + } + hState = h_matching_connection_token_start; + parser->index = 0; + } else if (ch != ' ') { + hState = h_matching_connection_token; + } + break; + + default: + UPDATE_STATE(s_header_value); + hState = h_general; + break; + } + } + parser->header_state = hState; + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) { + --p; + } + break; + } + + case s_header_almost_done: { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + UPDATE_STATE(s_header_value_lws); + break; + } + + case s_header_value_lws: { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_start); + REEXECUTE(); + } + + /* finished the header */ + switch (parser->header_state) { //NOLINT + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + default: + break; + } + + UPDATE_STATE(s_header_field_start); + REEXECUTE(); + } + + case s_header_value_discard_ws_almost_done: { + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + case s_header_value_discard_lws: { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_discard_ws); + break; + } + switch (parser->header_state) { //NOLINT + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + /* header value was empty */ + MARK(header_value); + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + + case s_headers_almost_done: { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { //NOLINT + /* End of a chunked request */ + UPDATE_STATE(s_message_done); + CALLBACK_NOTIFY_NOADVANCE(chunk_complete); + REEXECUTE(); + } + + /* Cannot use chunked encoding and a content-length header together + per the HTTP specification. */ + if ((parser->flags & F_CHUNKED) && //NOLINT + (parser->flags & F_CONTENTLENGTH)) { //NOLINT + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + + UPDATE_STATE(s_headers_done); + + /* Set this here so that on_headers_complete() callbacks can see it */ + parser->upgrade = + ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == //NOLINT + (F_UPGRADE | F_CONNECTION_UPGRADE) || + parser->method == HTTP_CONNECT); //NOLINT + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 2: + parser->upgrade = 1; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + SET_ERRNO(HPE_CB_headers_complete); + RETURN(p - data); /* Error */ + } + } + + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + RETURN(p - data); + } + + REEXECUTE(); + } + + case s_headers_done: { + int hasBody; + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + hasBody = parser->flags & F_CHUNKED || //NOLINT + (parser->content_length > 0 && parser->content_length != ULLONG_MAX); + if (parser->upgrade && (parser->method == HTTP_CONNECT || //NOLINT + (parser->flags & F_SKIPBODY) || !hasBody)) { //NOLINT + /* Exit, the rest of the message is in a different protocol. */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + RETURN((p - data) + 1); + } + + if (parser->flags & F_SKIPBODY) { //NOLINT + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { //NOLINT + /* chunked encoding - ignore Content-Length header */ + UPDATE_STATE(s_chunk_size_start); + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { + /* Content-Length header given and non-zero */ + UPDATE_STATE(s_body_identity); + } else { + if (!http_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else { + /* Read body until EOF */ + UPDATE_STATE(s_body_identity_eof); + } + } + } + + break; + } + + case s_body_identity: { + uint64_t toRead = MIN(parser->content_length, + (uint64_t)((data + len) - p)); + + assert(parser->content_length != 0 && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= toRead; + p += toRead - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_message_done); + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + REEXECUTE(); + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + if (parser->upgrade) { + /* Exit, the rest of the message is in a different protocol. */ + RETURN((p - data) + 1); + } + break; + + case s_chunk_size_start: { + assert(parser->nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[static_cast(ch)]; + if (UNLIKELY(unhex_val == -1)) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + UPDATE_STATE(s_chunk_size); + break; + } + + case s_chunk_size: { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + + unhex_val = unhex[static_cast(ch)]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + UPDATE_STATE(s_chunk_parameters); + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. REFINE: check for overflow */ + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + break; + } + + case s_chunk_size_almost_done: { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + UPDATE_STATE(s_header_field_start); + } else { + UPDATE_STATE(s_chunk_data); + } + CALLBACK_NOTIFY(chunk_header); + break; + } + + case s_chunk_data: { + uint64_t toRead = MIN(parser->content_length, + (uint64_t)((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 && parser->content_length != ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= toRead; + p += toRead - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_chunk_data_almost_done); + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + STRICT_CHECK(ch != CR); + UPDATE_STATE(s_chunk_data_done); + CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + parser->nread = 0; + UPDATE_STATE(s_chunk_size_start); + CALLBACK_NOTIFY(chunk_complete); + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0) + + (status_mark ? 1 : 0)) <= 1); + + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); + CALLBACK_DATA_NOADVANCE(status); + + RETURN(len); + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + RETURN(p - data); +} + +/* Does the parser need to see an EOF to find the end of the message? */ +int http_message_needs_eof(const http_parser *parser) { //NOLINT + if (parser->type == HTTP_REQUEST) { //NOLINT + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue NOLINT */ + parser->status_code == 204 || /* No Content NOLINT*/ + parser->status_code == 304 || /* Not Modified/ NOLINT*/ + parser->flags & F_SKIPBODY) { /* response to a HEAD request NOLINT */ + return 0; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { //NOLINT + return 0; + } + + return 1; +} + +int http_should_keep_alive(const http_parser *parser) { //NOLINT + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { //NOLINT + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { //NOLINT + return 0; + } + } + + return !http_message_needs_eof(parser); +} + +const char * +http_method_str(enum http_method m) { //NOLINT + return ELEM_AT(method_strings, m, ""); +} + +void http_parser_init(http_parser *parser, enum http_parser_type t) { //NOLINT + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->http_errno = HPE_OK; +} + +void http_parser_settings_init(http_parser_settings *settings) { //NOLINT + memset(settings, 0, sizeof(*settings)); +} + +const char * +http_errno_name(enum http_errno err) { //NOLINT + assert(((size_t)err) < ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { //NOLINT + assert(((size_t)err) < ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].description; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { //NOLINT + switch (s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + if (s == s_http_host_v6 && ch == '%') { + return s_http_host_v6_zone_start; + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || + ch == '~') { + return s_http_host_v6_zone; + } + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char *buf, struct http_parser_url *u, int found_at) { //NOLINT + assert(u->field_set & (1 << UF_HOST)); + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state newS = http_parse_host_char(s, *p); + + if (newS == s_http_host_dead) { + return 1; + } + + switch (newS) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = p - buf; + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = newS; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +void http_parser_url_init(struct http_parser_url *u) { //NOLINT + memset(u, 0, sizeof(*u)); +} + +int http_parser_parse_url(const char *buf, size_t buflen, int is_connect, //NOLINT + struct http_parser_url *u) { + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; //NOLINT + int found_at = 0; //NOLINT + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + + /* FALLTROUGH */ + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & (1 << UF_SCHEMA)) && + (u->field_set & (1 << UF_HOST)) == 0) { + return 1; + } + + if (u->field_set & (1 << UF_HOST)) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST) | (1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + auto v = strtoul(buf + u->field_data[UF_PORT].off, nullptr, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t)v; //NOLINT + } + + return 0; +} + +void http_parser_pause(http_parser *parser, int paused) { //NOLINT + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} + +int http_body_is_final(const struct http_parser *parser) { //NOLINT + return parser->state == s_message_done; //NOLINT +} + +unsigned long //NOLINT +http_parser_version(void) { //NOLINT + return HTTP_PARSER_VERSION_MAJOR * 0x10000 | + HTTP_PARSER_VERSION_MINOR * 0x00100 | + HTTP_PARSER_VERSION_PATCH * 0x00001; +} + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/http_parser.h b/cocos/bindings/jswrapper/v8/debugger/http_parser.h new file mode 100644 index 0000000..33cb4bd --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/http_parser.h @@ -0,0 +1,353 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef http_parser_h +#define http_parser_h + +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #ifdef __cplusplus +extern "C" { + #endif + + /* Also update SONAME in the Makefile whenever you change these. */ + #define HTTP_PARSER_VERSION_MAJOR 2 + #define HTTP_PARSER_VERSION_MINOR 7 + #define HTTP_PARSER_VERSION_PATCH 0 + + #include + #if defined(_WIN32) && !defined(__MINGW32__) && \ + (!defined(_MSC_VER) || _MSC_VER < 1600) && !defined(__WINE__) + #include + #include +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + #else + #include + #endif + + /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ + #ifndef HTTP_PARSER_STRICT + #define HTTP_PARSER_STRICT 1 + #endif + + /* Maximium header size allowed. If the macro is not defined + * before including this header then the default is used. To + * change the maximum header size, define the macro in the build + * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove + * the effective limit on the size of the header, define the macro + * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) + */ + #ifndef HTTP_MAX_HEADER_SIZE + #define HTTP_MAX_HEADER_SIZE (80 * 1024) + #endif + +using http_parser = struct http_parser; +using http_parser_settings = struct http_parser_settings; + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * Returning `2` from on_headers_complete will tell parser that it should not + * expect neither a body nor any futher responses on this connection. This is + * useful for handling responses to a CONNECT request which may not contain + * `Upgrade` or `Connection: upgrade` headers. + * + * http_data_cb does not return data chunks. It will be called arbitrarily + * many times for each string. E.G. you might get 10 callbacks for "on_url" + * each providing just a few characters more data. + */ +using http_data_cb = int (*)(http_parser *, const char *, size_t); +using http_cb = int (*)(http_parser *); + + /* Request Methods */ + #define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + /* pathological */ \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + /* WebDAV */ \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + /* subversion */ \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + /* upnp */ \ + XX(24, MSEARCH, M - SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + /* RFC-5789 */ \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + /* CalDAV */ \ + XX(30, MKCALENDAR, MKCALENDAR) \ + /* RFC-2068, section 19.6.1.2 */ \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) + +enum http_method { //NOLINT(readability-identifier-naming) + #define XX(num, name, string) HTTP_##name = num, + HTTP_METHOD_MAP(XX) + #undef XX +}; + +enum http_parser_type { HTTP_REQUEST, //NOLINT(readability-identifier-naming) + HTTP_RESPONSE, + HTTP_BOTH }; + +/* Flag values for http_parser.flags field */ +enum flags { F_CHUNKED = 1 << 0, //NOLINT(readability-identifier-naming) + F_CONNECTION_KEEP_ALIVE = 1 << 1, + F_CONNECTION_CLOSE = 1 << 2, + F_CONNECTION_UPGRADE = 1 << 3, + F_TRAILING = 1 << 4, + F_UPGRADE = 1 << 5, + F_SKIPBODY = 1 << 6, + F_CONTENTLENGTH = 1 << 7 +}; + + /* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ + #define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + XX(CB_status, "the on_status callback failed") \ + XX(CB_chunk_header, "the on_chunk_header callback failed") \ + XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(UNEXPECTED_CONTENT_LENGTH, \ + "unexpected content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state") \ + XX(STRICT, "strict mode assertion failed") \ + XX(PAUSED, "parser is paused") \ + XX(UNKNOWN, "an unknown error occurred") + + /* Define HPE_* values for each errno value above */ + #define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { //NOLINT(readability-identifier-naming) + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; + #undef HTTP_ERRNO_GEN + + /* Get an http_errno value from an http_parser */ + #define HTTP_PARSER_ERRNO(p) ((enum http_errno)(p)->http_errno) + +struct http_parser { + /** PRIVATE **/ + unsigned int type : 2; /* enum http_parser_type */ + unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ + unsigned int state : 7; /* enum state from http_parser.c */ + unsigned int header_state : 7; /* enum header_state from http_parser.c */ + unsigned int index : 7; /* index into current matcher */ + unsigned int lenient_http_headers : 1; + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ + + /** READ-ONLY **/ + unsigned short http_major; //NOLINT(google-runtime-int) + unsigned short http_minor; //NOLINT(google-runtime-int) + unsigned int status_code : 16; /* responses only */ + unsigned int method : 8; /* requests only */ + unsigned int http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned int upgrade : 1; + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_data_cb on_status; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; + /* When on_chunk_header is called, the current chunk length is stored + * in parser->content_length. + */ + http_cb on_chunk_header; + http_cb on_chunk_complete; +}; + +enum http_parser_url_fields { UF_SCHEMA = 0, //NOLINT(readability-identifier-naming) + UF_HOST = 1, + UF_PORT = 2, + UF_PATH = 3, + UF_QUERY = 4, + UF_FRAGMENT = 5, + UF_USERINFO = 6, + UF_MAX = 7 +}; + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { //NOLINT(readability-identifier-naming) + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + +/* Returns the library version. Bits 16-23 contain the major version number, + * bits 8-15 the minor version number and bits 0-7 the patch level. + * Usage example: + * + * unsigned long version = http_parser_version(); + * unsigned major = (version >> 16) & 255; + * unsigned minor = (version >> 8) & 255; + * unsigned patch = version & 255; + * printf("http_parser v%u.%u.%u\n", major, minor, patch); + */ +unsigned long http_parser_version(void); //NOLINT(readability-identifier-naming,google-runtime-int) + +void http_parser_init(http_parser *parser, enum http_parser_type type); //NOLINT(readability-identifier-naming) + +/* Initialize http_parser_settings members to 0 + */ +void http_parser_settings_init(http_parser_settings *settings); //NOLINT(readability-identifier-naming) + +/* Executes the parser. Returns number of parsed bytes. Sets + * `parser->http_errno` on error. */ +size_t http_parser_execute(http_parser *parser, //NOLINT(readability-identifier-naming) + const http_parser_settings *settings, + const char *data, + size_t len); + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns 0, then this should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(const http_parser *parser); //NOLINT(readability-identifier-naming) + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); //NOLINT(readability-identifier-naming) + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); //NOLINT(readability-identifier-naming) + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); //NOLINT(readability-identifier-naming) + +/* Initialize all http_parser_url members to 0 */ +void http_parser_url_init(struct http_parser_url *u); //NOLINT(readability-identifier-naming) + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, //NOLINT(readability-identifier-naming) + int is_connect, //NOLINT(readability-identifier-naming) + struct http_parser_url *u); + +/* Pause or un-pause the parser; a nonzero value pauses */ +void http_parser_pause(http_parser *parser, int paused); //NOLINT(readability-identifier-naming) + +/* Checks if this is the final chunk of the body. */ +int http_body_is_final(const http_parser *parser); //NOLINT(readability-identifier-naming) + + #ifdef __cplusplus +} + #endif + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + +#endif diff --git a/cocos/bindings/jswrapper/v8/debugger/inspector_agent.cpp b/cocos/bindings/jswrapper/v8/debugger/inspector_agent.cpp new file mode 100644 index 0000000..6f06a12 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/inspector_agent.cpp @@ -0,0 +1,822 @@ +#include "inspector_agent.h" + +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "env.h" + #include "inspector_io.h" + #include "node.h" + #include "util.h" + #include "v8-inspector.h" + #include "v8-platform.h" + #include "zlib.h" + + #include "libplatform/libplatform.h" + + #include + #include + #include + + #ifdef __POSIX__ + #include // setuid, getuid + #endif // __POSIX__ + +namespace node { +namespace inspector { +namespace { +using v8::Context; +using v8::External; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Maybe; +using v8::MaybeLocal; +using v8::NewStringType; +using v8::Object; +using v8::Persistent; +using v8::String; +using v8::Value; + +using v8_inspector::StringBuffer; +using v8_inspector::StringView; +using v8_inspector::V8Inspector; +using v8_inspector::V8InspectorClient; + +static uv_sem_t start_io_thread_semaphore; +static uv_async_t start_io_thread_async; + +class StartIoTask : public v8::Task { +public: + explicit StartIoTask(Agent *agent) : agent(agent) {} + + void Run() override { + agent->StartIoThread(false); + } + +private: + Agent *agent; +}; + +std::unique_ptr ToProtocolString(Isolate *isolate, + Local value) { + TwoByteValue buffer(isolate, value); + return StringBuffer::create(StringView(*buffer, buffer.length())); +} + +// Called on the main thread. +void StartIoThreadAsyncCallback(uv_async_t *handle) { + static_cast(handle->data)->StartIoThread(false); +} + +void StartIoInterrupt(Isolate *isolate, void *agent) { + static_cast(agent)->StartIoThread(false); +} + + #ifdef __POSIX__ + +static void StartIoThreadWakeup(int signo) { + uv_sem_post(&start_io_thread_semaphore); +} + +inline void *StartIoThreadMain(void *unused) { + for (;;) { + uv_sem_wait(&start_io_thread_semaphore); + Agent *agent = static_cast(start_io_thread_async.data); + if (agent != nullptr) + agent->RequestIoThreadStart(); + } + return nullptr; +} + +static int StartDebugSignalHandler() { + // Start a watchdog thread for calling v8::Debug::DebugBreak() because + // it's not safe to call directly from the signal handler, it can + // deadlock with the thread it interrupts. + CHECK_EQ(0, uv_sem_init(&start_io_thread_semaphore, 0)); + pthread_attr_t attr; + CHECK_EQ(0, pthread_attr_init(&attr)); + // Don't shrink the thread's stack on FreeBSD. Said platform decided to + // follow the pthreads specification to the letter rather than in spirit: + // https://lists.freebsd.org/pipermail/freebsd-current/2014-March/048885.html + #ifndef __FreeBSD__ + CHECK_EQ(0, pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN)); + #endif // __FreeBSD__ + CHECK_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); + sigset_t sigmask; + // Mask all signals. + sigfillset(&sigmask); + CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask)); + pthread_t thread; + const int err = pthread_create(&thread, &attr, + StartIoThreadMain, nullptr); + // Restore original mask + CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr)); + CHECK_EQ(0, pthread_attr_destroy(&attr)); + if (err != 0) { + SE_LOGE("node[%d]: pthread_create: %s\n", getpid(), strerror(err)); + + // Leave SIGUSR1 blocked. We don't install a signal handler, + // receiving the signal would terminate the process. + return -err; + } + RegisterSignalHandler(SIGUSR1, StartIoThreadWakeup); + // Unblock SIGUSR1. A pending SIGUSR1 signal will now be delivered. + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGUSR1); + CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sigmask, nullptr)); + return 0; +} + #endif // __POSIX__ + + #ifdef _WIN32 +DWORD WINAPI StartIoThreadProc(void *arg) { + Agent *agent = static_cast(start_io_thread_async.data); + if (agent != nullptr) + agent->RequestIoThreadStart(); + return 0; +} + +static int GetDebugSignalHandlerMappingName(DWORD pid, wchar_t *buf, + size_t buf_len) { + return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid); +} + +static int StartDebugSignalHandler() { + wchar_t mapping_name[32]; + HANDLE mapping_handle; + DWORD pid; + LPTHREAD_START_ROUTINE *handler; + + pid = GetCurrentProcessId(); + + if (GetDebugSignalHandlerMappingName(pid, + mapping_name, + arraysize(mapping_name)) < 0) { + return -1; + } + + mapping_handle = CreateFileMappingW(INVALID_HANDLE_VALUE, + nullptr, + PAGE_READWRITE, + 0, + sizeof *handler, + mapping_name); + if (mapping_handle == nullptr) { + return -1; + } + + handler = reinterpret_cast( + MapViewOfFile(mapping_handle, + FILE_MAP_ALL_ACCESS, + 0, + 0, + sizeof *handler)); + if (handler == nullptr) { + CloseHandle(mapping_handle); + return -1; + } + + *handler = StartIoThreadProc; + + UnmapViewOfFile(static_cast(handler)); + + return 0; +} + #endif // _WIN32 + +class JsBindingsSessionDelegate : public InspectorSessionDelegate { +public: + JsBindingsSessionDelegate(Environment *env, + Local session, + Local receiver, + Local callback) + : env_(env), + session_(env->isolate(), session), + receiver_(env->isolate(), receiver), + callback_(env->isolate(), callback) { + session_.SetWeak(this, JsBindingsSessionDelegate::Release, + v8::WeakCallbackType::kParameter); + } + + ~JsBindingsSessionDelegate() override { + session_.Reset(); + receiver_.Reset(); + callback_.Reset(); + } + + bool WaitForFrontendMessageWhilePaused() override { + return false; + } + + void SendMessageToFrontend(const v8_inspector::StringView &message) override { + Isolate *isolate = env_->isolate(); + v8::HandleScope handle_scope(isolate); + Context::Scope context_scope(env_->context()); + MaybeLocal v8string = + String::NewFromTwoByte(isolate, message.characters16(), + NewStringType::kNormal, static_cast(message.length())); + Local argument = v8string.ToLocalChecked().As(); + Local callback = callback_.Get(isolate); + Local receiver = receiver_.Get(isolate); + callback->Call(env_->context(), receiver, 1, &argument) + .FromMaybe(Local()); + } + + void Disconnect() { + Agent *agent = env_->inspector_agent(); + if (agent->delegate() == this) + agent->Disconnect(); + } + +private: + static void Release( + const v8::WeakCallbackInfo &info) { + info.SetSecondPassCallback(ReleaseSecondPass); + info.GetParameter()->session_.Reset(); + } + + static void ReleaseSecondPass( + const v8::WeakCallbackInfo &info) { + JsBindingsSessionDelegate *delegate = info.GetParameter(); + delegate->Disconnect(); + delete delegate; + } + + Environment *env_; + Persistent session_; + Persistent receiver_; + Persistent callback_; +}; + +void SetDelegate(Environment *env, Local inspector, + JsBindingsSessionDelegate *delegate) { + inspector->SetPrivate(env->context(), + env->inspector_delegate_private_symbol(), + v8::External::New(env->isolate(), delegate)); +} + +Maybe GetDelegate( + const FunctionCallbackInfo &info) { + Environment *env = Environment::GetCurrent(info); + Local delegate; + MaybeLocal maybe_delegate = + info.This()->GetPrivate(env->context(), + env->inspector_delegate_private_symbol()); + + if (maybe_delegate.ToLocal(&delegate)) { + CHECK(delegate->IsExternal()); + void *value = delegate.As()->Value(); + if (value != nullptr) { + return v8::Just(static_cast(value)); + } + } + env->ThrowError("Inspector is not connected"); + return v8::Nothing(); +} + +void Dispatch(const FunctionCallbackInfo &info) { + Environment *env = Environment::GetCurrent(info); + if (!info[0]->IsString()) { + env->ThrowError("Inspector message must be a string"); + return; + } + Maybe maybe_delegate = GetDelegate(info); + if (maybe_delegate.IsNothing()) + return; + Agent *inspector = env->inspector_agent(); + CHECK_EQ(maybe_delegate.ToChecked(), inspector->delegate()); + inspector->Dispatch(ToProtocolString(env->isolate(), info[0])->string()); +} + +void Disconnect(const FunctionCallbackInfo &info) { + Environment *env = Environment::GetCurrent(info); + Maybe delegate = GetDelegate(info); + if (delegate.IsNothing()) { + return; + } + delegate.ToChecked()->Disconnect(); + SetDelegate(env, info.This(), nullptr); + delete delegate.ToChecked(); +} + +void ConnectJSBindingsSession(const FunctionCallbackInfo &info) { + Environment *env = Environment::GetCurrent(info); + if (!info[0]->IsFunction()) { + env->ThrowError("Message callback is required"); + return; + } + Agent *inspector = env->inspector_agent(); + if (inspector->delegate() != nullptr) { + env->ThrowError("Session is already attached"); + return; + } + Local session = Object::New(env->isolate()); + env->SetMethod(session, "dispatch", Dispatch); + env->SetMethod(session, "disconnect", Disconnect); + info.GetReturnValue().Set(session); + + JsBindingsSessionDelegate *delegate = + new JsBindingsSessionDelegate(env, session, info.Holder(), + info[0].As()); + inspector->Connect(delegate); + SetDelegate(env, session, delegate); +} + +void InspectorConsoleCall(const v8::FunctionCallbackInfo &info) { + Isolate *isolate = info.GetIsolate(); + HandleScope handle_scope(isolate); + Local context = isolate->GetCurrentContext(); + CHECK_LT(2, info.Length()); + std::vector> call_args; + for (int i = 3; i < info.Length(); ++i) { + call_args.push_back(info[i]); + } + Environment *env = Environment::GetCurrent(isolate); + if (env->inspector_agent()->enabled()) { + Local inspector_method = info[0]; + CHECK(inspector_method->IsFunction()); + Local config_value = info[2]; + CHECK(config_value->IsObject()); + Local config_object = config_value.As(); + Local in_call_key = FIXED_ONE_BYTE_STRING(isolate, "in_call"); + if (!config_object->Has(context, in_call_key).FromMaybe(false)) { + CHECK(config_object->Set(context, + in_call_key, + v8::True(isolate)) + .FromJust()); + CHECK(!inspector_method.As()->Call(context, + info.Holder(), + static_cast(call_args.size()), + call_args.data()) + .IsEmpty()); + } + CHECK(config_object->Delete(context, in_call_key).FromJust()); + } + + Local node_method = info[1]; + CHECK(node_method->IsFunction()); + node_method.As()->Call(context, + info.Holder(), + static_cast(call_args.size()), + call_args.data()) + .FromMaybe(Local()); +} + +void CallAndPauseOnStart( + const v8::FunctionCallbackInfo &args) { + Environment *env = Environment::GetCurrent(args); + CHECK_GT(args.Length(), 1); + CHECK(args[0]->IsFunction()); + std::vector> call_args; + for (int i = 2; i < args.Length(); i++) { + call_args.push_back(args[i]); + } + + env->inspector_agent()->PauseOnNextJavascriptStatement("Break on start"); + v8::MaybeLocal retval = + args[0].As()->Call(env->context(), args[1], + static_cast(call_args.size()), call_args.data()); + if (!retval.IsEmpty()) { + args.GetReturnValue().Set(retval.ToLocalChecked()); + } +} + +// Used in NodeInspectorClient::currentTimeMS() below. +const int NANOS_PER_MSEC = 1000000; +const int CONTEXT_GROUP_ID = 1; + +class ChannelImpl final : public v8_inspector::V8Inspector::Channel { +public: + explicit ChannelImpl(V8Inspector *inspector, + InspectorSessionDelegate *delegate) + : delegate_(delegate) { +#if V8_MAJOR_VERSION > 10 || (V8_MAJOR_VERSION == 10 && V8_MINOR_VERSION > 3) + session_ = inspector->connect(1, this, StringView(), v8_inspector::V8Inspector::ClientTrustLevel::kFullyTrusted); +#else + session_ = inspector->connect(1, this, StringView()); +#endif + } + + virtual ~ChannelImpl() {} + + void dispatchProtocolMessage(const StringView &message) { + session_->dispatchProtocolMessage(message); + } + + bool waitForFrontendMessage() { + return delegate_->WaitForFrontendMessageWhilePaused(); + } + + void schedulePauseOnNextStatement(const std::string &reason) { + std::unique_ptr buffer = Utf8ToStringView(reason); + session_->schedulePauseOnNextStatement(buffer->string(), buffer->string()); + } + + InspectorSessionDelegate *delegate() { + return delegate_; + } + +private: + void sendResponse( + int callId, + std::unique_ptr message) override { + sendMessageToFrontend(message->string()); + } + + void sendNotification( + std::unique_ptr message) override { + sendMessageToFrontend(message->string()); + } + + void flushProtocolNotifications() override {} + + void sendMessageToFrontend(const StringView &message) { + delegate_->SendMessageToFrontend(message); + } + + InspectorSessionDelegate *const delegate_; + std::unique_ptr session_; +}; + +class InspectorTimer { +public: + InspectorTimer(uv_loop_t *loop, + double interval_s, + V8InspectorClient::TimerCallback callback, + void *data) : timer_(), + callback_(callback), + data_(data) { + uv_timer_init(loop, &timer_); + int64_t interval_ms = 1000 * interval_s; + uv_timer_start(&timer_, OnTimer, interval_ms, interval_ms); + } + + InspectorTimer(const InspectorTimer &) = delete; + + void Stop() { + uv_timer_stop(&timer_); + uv_close(reinterpret_cast(&timer_), TimerClosedCb); + } + +private: + static void OnTimer(uv_timer_t *uvtimer) { + InspectorTimer *timer = node::ContainerOf(&InspectorTimer::timer_, uvtimer); + timer->callback_(timer->data_); + } + + static void TimerClosedCb(uv_handle_t *uvtimer) { + InspectorTimer *timer = + node::ContainerOf(&InspectorTimer::timer_, + reinterpret_cast(uvtimer)); + delete timer; + } + + ~InspectorTimer() {} + + uv_timer_t timer_; + V8InspectorClient::TimerCallback callback_; + void *data_; +}; + +class InspectorTimerHandle { +public: + InspectorTimerHandle(uv_loop_t *loop, double interval_s, + V8InspectorClient::TimerCallback callback, void *data) { + timer_ = new InspectorTimer(loop, interval_s, callback, data); + } + + InspectorTimerHandle(const InspectorTimerHandle &) = delete; + + ~InspectorTimerHandle() { + CHECK_NE(timer_, nullptr); + timer_->Stop(); + timer_ = nullptr; + } + +private: + InspectorTimer *timer_; +}; +} // namespace + +class NodeInspectorClient : public V8InspectorClient { +public: + NodeInspectorClient(node::Environment *env, + v8::Platform *platform) : env_(env), + platform_(platform), + terminated_(false), + running_nested_loop_(false) { + client_ = V8Inspector::create(env->isolate(), this); + } + + void runMessageLoopOnPause(int context_group_id) override { + CHECK_NE(channel_, nullptr); + if (running_nested_loop_) + return; + terminated_ = false; + running_nested_loop_ = true; + while (!terminated_ && channel_->waitForFrontendMessage()) { + while (v8::platform::PumpMessageLoop(platform_, env_->isolate())) { + } + } + terminated_ = false; + running_nested_loop_ = false; + } + + double currentTimeMS() override { + return uv_hrtime() * 1.0 / NANOS_PER_MSEC; + } + + void contextCreated(Local context, const std::string &name) { + std::unique_ptr name_buffer = Utf8ToStringView(name); + v8_inspector::V8ContextInfo info(context, CONTEXT_GROUP_ID, + name_buffer->string()); + client_->contextCreated(info); + } + + void contextDestroyed(Local context) { + client_->contextDestroyed(context); + } + + void quitMessageLoopOnPause() override { + terminated_ = true; + } + + void connectFrontend(InspectorSessionDelegate *delegate) { + CHECK_EQ(channel_, nullptr); + channel_ = std::unique_ptr( + new ChannelImpl(client_.get(), delegate)); + } + + void disconnectFrontend() { + quitMessageLoopOnPause(); + channel_.reset(); + } + + void dispatchMessageFromFrontend(const StringView &message) { + if (channel_ == nullptr) return; + CHECK_NE(channel_, nullptr); + channel_->dispatchProtocolMessage(message); + } + + Local ensureDefaultContextInGroup(int contextGroupId) override { + return env_->context(); + } + + void FatalException(Local error, Local message) { + Local context = env_->context(); + + int script_id = message->GetScriptOrigin().ScriptId(); + + Local stack_trace = message->GetStackTrace(); + + if (!stack_trace.IsEmpty() && + stack_trace->GetFrameCount() > 0 && + script_id == stack_trace->GetFrame(env_->isolate(), 0)->GetScriptId()) { + script_id = 0; + } + + const uint8_t DETAILS[] = "Uncaught"; + + Isolate *isolate = context->GetIsolate(); + + client_->exceptionThrown( + context, + StringView(DETAILS, sizeof(DETAILS) - 1), + error, + ToProtocolString(isolate, message->Get())->string(), + ToProtocolString(isolate, message->GetScriptResourceName())->string(), + message->GetLineNumber(context).FromMaybe(0), + message->GetStartColumn(context).FromMaybe(0), + client_->createStackTrace(stack_trace), + script_id); + } + + ChannelImpl *channel() { + return channel_.get(); + } + + void startRepeatingTimer(double interval_s, + TimerCallback callback, + void *data) override { + timers_.emplace(std::piecewise_construct, std::make_tuple(data), + std::make_tuple(env_->event_loop(), interval_s, callback, + data)); + } + + void cancelTimer(void *data) override { + timers_.erase(data); + } + +private: + node::Environment *env_; + v8::Platform *platform_; + bool terminated_; + bool running_nested_loop_; + std::unique_ptr client_; + std::unique_ptr channel_; + std::unordered_map timers_; +}; + +Agent::Agent(Environment *env) : parent_env_(env), + client_(nullptr), + platform_(nullptr), + enabled_(false) {} + +// Destructor needs to be defined here in implementation file as the header +// does not have full definition of some classes. +Agent::~Agent() { +} + +bool Agent::Start(v8::Platform *platform, const char *path, + const DebugOptions &options) { + path_ = path == nullptr ? "" : path; + debug_options_ = options; + client_ = + std::unique_ptr( + new NodeInspectorClient(parent_env_, platform)); + client_->contextCreated(parent_env_->context(), "Node.js Main Context"); + platform_ = platform; + CHECK_EQ(0, uv_async_init(uv_default_loop(), + &start_io_thread_async, + StartIoThreadAsyncCallback)); + start_io_thread_async.data = this; + uv_unref(reinterpret_cast(&start_io_thread_async)); + + // Ignore failure, SIGUSR1 won't work, but that should not block node start. + StartDebugSignalHandler(); + if (options.inspector_enabled()) { + // This will return false if listen failed on the inspector port. + return StartIoThread(options.wait_for_connect()); + } + return true; +} + +bool Agent::StartIoThread(bool wait_for_connect) { + if (io_ != nullptr) + return true; + + CHECK_NE(client_, nullptr); + + enabled_ = true; + io_ = std::unique_ptr( + new InspectorIo(parent_env_, platform_, path_, debug_options_, + wait_for_connect)); + if (!io_->Start()) { + client_.reset(); + return false; + } + + v8::Isolate *isolate = parent_env_->isolate(); + + // Send message to enable debug in workers + HandleScope handle_scope(isolate); + Local process_object = parent_env_->process_object(); + Local emit_fn = + process_object->Get(parent_env_->context(), FIXED_ONE_BYTE_STRING(isolate, "emit")).ToLocalChecked(); + // In case the thread started early during the startup + if (!emit_fn->IsFunction()) + return true; + + Local message = Object::New(isolate); + message->Set(parent_env_->context(), FIXED_ONE_BYTE_STRING(isolate, "cmd"), + FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED")) + .Check(); + Local argv[] = { + FIXED_ONE_BYTE_STRING(isolate, "internalMessage"), + message}; + MakeCallback(parent_env_->isolate(), process_object, emit_fn.As(), + arraysize(argv), argv, {0, 0}); + + return true; +} + +void Agent::Stop() { + if (io_ != nullptr) { + io_->Stop(); + io_.reset(); + } +} + +void Agent::Connect(InspectorSessionDelegate *delegate) { + enabled_ = true; + client_->connectFrontend(delegate); +} + +bool Agent::IsConnected() { + return io_ && io_->IsConnected(); +} + +void Agent::WaitForDisconnect() { + CHECK_NE(client_, nullptr); + client_->contextDestroyed(parent_env_->context()); + if (io_ != nullptr) { + io_->WaitForDisconnect(); + } +} + +void Agent::FatalException(Local error, Local message) { + if (!IsStarted()) + return; + client_->FatalException(error, message); + WaitForDisconnect(); +} + +void Agent::Dispatch(const StringView &message) { + if (client_ == nullptr) return; + CHECK_NE(client_, nullptr); + client_->dispatchMessageFromFrontend(message); +} + +void Agent::Disconnect() { + CHECK_NE(client_, nullptr); + client_->disconnectFrontend(); +} + +void Agent::RunMessageLoop() { + CHECK_NE(client_, nullptr); + client_->runMessageLoopOnPause(CONTEXT_GROUP_ID); +} + +InspectorSessionDelegate *Agent::delegate() { + CHECK_NE(client_, nullptr); + ChannelImpl *channel = client_->channel(); + if (channel == nullptr) + return nullptr; + return channel->delegate(); +} + +void Agent::PauseOnNextJavascriptStatement(const std::string &reason) { + ChannelImpl *channel = client_->channel(); + if (channel != nullptr) + channel->schedulePauseOnNextStatement(reason); +} + +void Open(const FunctionCallbackInfo &args) { + Environment *env = Environment::GetCurrent(args); + inspector::Agent *agent = env->inspector_agent(); + bool wait_for_connect = false; + + if (args.Length() > 0 && args[0]->IsUint32()) { + uint32_t port = args[0]->Uint32Value(env->context()).ToChecked(); + agent->options().set_port(static_cast(port)); + } + + if (args.Length() > 1 && args[1]->IsString()) { + node::Utf8Value host(env->isolate(), args[1].As()); + agent->options().set_host_name(*host); + } + + if (args.Length() > 2 && args[2]->IsBoolean()) { + wait_for_connect = args[2]->BooleanValue(env->isolate()); + } + + agent->StartIoThread(wait_for_connect); +} + +void Url(const FunctionCallbackInfo &args) { + Environment *env = Environment::GetCurrent(args); + inspector::Agent *agent = env->inspector_agent(); + inspector::InspectorIo *io = agent->io(); + + if (!io) return; + + std::vector ids = io->GetTargetIds(); + + if (ids.empty()) return; + + std::string url = FormatWsAddress(io->host(), io->port(), ids[0], true); + args.GetReturnValue().Set(OneByteString(env->isolate(), url.c_str())); +} + +// static +void Agent::InitInspector(Local target, Local unused, + Local context, void *priv) { + Environment *env = Environment::GetCurrent(context); + Agent *agent = env->inspector_agent(); + env->SetMethod(target, "consoleCall", InspectorConsoleCall); + if (agent->debug_options_.wait_for_connect()) + env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart); + env->SetMethod(target, "connect", ConnectJSBindingsSession); + env->SetMethod(target, "open", Open); + env->SetMethod(target, "url", Url); +} + +void Agent::RequestIoThreadStart() { + // We need to attempt to interrupt V8 flow (in case Node is running + // continuous JS code) and to wake up libuv thread (in case Node is waiting + // for IO events) + uv_async_send(&start_io_thread_async); + v8::Isolate *isolate = parent_env_->isolate(); + platform_->GetForegroundTaskRunner(isolate)->PostTask(std::make_unique(this)); + isolate->RequestInterrupt(StartIoInterrupt, this); + uv_async_send(&start_io_thread_async); +} + +} // namespace inspector +} // namespace node + +//cjh NODE_MODULE_CONTEXT_AWARE_BUILTIN(inspector, +// node::inspector::Agent::InitInspector); + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/inspector_agent.h b/cocos/bindings/jswrapper/v8/debugger/inspector_agent.h new file mode 100644 index 0000000..fbf20d8 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/inspector_agent.h @@ -0,0 +1,116 @@ +#ifndef SRC_INSPECTOR_AGENT_H_ +#define SRC_INSPECTOR_AGENT_H_ + +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include + + #include + + #if !HAVE_INSPECTOR + #error("This header can only be used when inspector is enabled") + #endif + + #include "node_debug_options.h" + +// Forward declaration to break recursive dependency chain with src/env.h. +namespace node { +class Environment; +} // namespace node + +namespace v8 { +class Context; +template +class FunctionCallbackInfo; +template +class Local; +class Message; +class Object; +class Platform; +class Value; +} // namespace v8 + +namespace v8_inspector { +class StringView; +} // namespace v8_inspector + +namespace node { +namespace inspector { + +class InspectorSessionDelegate { +public: + virtual ~InspectorSessionDelegate() = default; + virtual bool WaitForFrontendMessageWhilePaused() = 0; + virtual void SendMessageToFrontend(const v8_inspector::StringView &message) = 0; +}; + +class InspectorIo; +class NodeInspectorClient; + +class Agent { +public: + explicit Agent(node::Environment *env); + ~Agent(); + + // Create client_, may create io_ if option enabled + bool Start(v8::Platform *platform, const char *path, + const DebugOptions &options); + // Stop and destroy io_ + void Stop(); + + bool IsStarted() { return !!client_; } + + // IO thread started, and client connected + bool IsConnected(); + + void WaitForDisconnect(); + void FatalException(v8::Local error, + v8::Local message); + + // These methods are called by the WS protocol and JS binding to create + // inspector sessions. The inspector responds by using the delegate to send + // messages back. + void Connect(InspectorSessionDelegate *delegate); + void Disconnect(); + void Dispatch(const v8_inspector::StringView &message); + InspectorSessionDelegate *delegate(); + + void RunMessageLoop(); + bool enabled() { return enabled_; } + void PauseOnNextJavascriptStatement(const std::string &reason); + + // Initialize 'inspector' module bindings + static void InitInspector(v8::Local target, + v8::Local unused, + v8::Local context, + void *priv); + + InspectorIo *io() { + return io_.get(); + } + + // Can only be called from the the main thread. + bool StartIoThread(bool wait_for_connect); + + // Calls StartIoThread() from off the main thread. + void RequestIoThreadStart(); + + DebugOptions &options() { return debug_options_; } + +private: + node::Environment *parent_env_; + std::unique_ptr client_; + std::unique_ptr io_; + v8::Platform *platform_; + bool enabled_; + std::string path_; + DebugOptions debug_options_; +}; + +} // namespace inspector +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + +#endif // SRC_INSPECTOR_AGENT_H_ diff --git a/cocos/bindings/jswrapper/v8/debugger/inspector_io.cpp b/cocos/bindings/jswrapper/v8/debugger/inspector_io.cpp new file mode 100644 index 0000000..bb60666 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/inspector_io.cpp @@ -0,0 +1,533 @@ +#include "inspector_io.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "inspector_socket_server.h" + #include "env.h" + #include "node.h" + //cjh #include "node_crypto.h" + #include "node_mutex.h" + #include "v8-inspector.h" + #include "util.h" + #include "zlib.h" + + #include "libplatform/libplatform.h" + + #include +//cjh #include + + #include + #include + + #include "base/UTF8.h" //cjh added + +namespace node { +namespace inspector { +namespace { +using AsyncAndAgent = std::pair; +using v8_inspector::StringBuffer; +using v8_inspector::StringView; + +template +using TransportAndIo = std::pair; + +std::string GetProcessTitle() { + char title[2048]; + int err = uv_get_process_title(title, sizeof(title)); + if (err == 0) { + return title; + } else { + // Title is too long, or could not be retrieved. + return "Node.js"; + } +} + +std::string ScriptPath(uv_loop_t *loop, const std::string &script_name) { + std::string script_path; + + if (!script_name.empty()) { + uv_fs_t req; + req.ptr = nullptr; + if (0 == uv_fs_realpath(loop, &req, script_name.c_str(), nullptr)) { + CHECK_NE(req.ptr, nullptr); + script_path = std::string(static_cast(req.ptr)); + } + uv_fs_req_cleanup(&req); + } + + return script_path; +} + +// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt +// Used ver 4 - with numbers +std::string GenerateID() { + // uint16_t buffer[8]; + + //cjh same uuid + uint16_t buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + + //cjh + //cjh CHECK(crypto::EntropySource(reinterpret_cast(buffer), + // sizeof(buffer))); + + char uuid[256]; + snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x", + buffer[0], // time_low + buffer[1], // time_mid + buffer[2], // time_low + (buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version + (buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low + buffer[5], // node + buffer[6], + buffer[7]); + return uuid; +} + +std::string StringViewToUtf8(const StringView &view) { + if (view.is8Bit()) { + return std::string(reinterpret_cast(view.characters8()), + view.length()); + } + const uint16_t *source = view.characters16(); + + std::u16string u16Str((char16_t *)source); + std::string ret; + cc::StringUtils::UTF16ToUTF8(u16Str, ret); + return ret; + // const UChar* unicodeSource = reinterpret_cast(source); + // static_assert(sizeof(*source) == sizeof(*unicodeSource), + // "sizeof(*source) == sizeof(*unicodeSource)"); + // + // size_t result_length = view.length() * sizeof(*source); + // std::string result(result_length, '\0'); + //cjh UnicodeString utf16(unicodeSource, view.length()); + // // ICU components for std::string compatibility are not enabled in build... + // bool done = false; + // while (!done) { + // CheckedArrayByteSink sink(&result[0], result_length); + // utf16.toUTF8(sink); + // result_length = sink.NumberOfBytesAppended(); + // result.resize(result_length); + // done = !sink.Overflowed(); + // } + // return result; + + return ""; +} + +void HandleSyncCloseCb(uv_handle_t *handle) { + *static_cast(handle->data) = true; +} + +int CloseAsyncAndLoop(uv_async_t *async) { + bool is_closed = false; + async->data = &is_closed; + uv_close(reinterpret_cast(async), HandleSyncCloseCb); + while (!is_closed) + uv_run(async->loop, UV_RUN_ONCE); + async->data = nullptr; + return uv_loop_close(async->loop); +} + +// Delete main_thread_req_ on async handle close +void ReleasePairOnAsyncClose(uv_handle_t *async) { + AsyncAndAgent *pair = node::ContainerOf(&AsyncAndAgent::first, + reinterpret_cast(async)); + delete pair; +} + +} // namespace + +std::unique_ptr Utf8ToStringView(const std::string &message) { + //cjh UnicodeString utf16 = + // UnicodeString::fromUTF8(StringPiece(message.data(), message.length())); + + std::u16string u16Str; + cc::StringUtils::UTF8ToUTF16(message, u16Str); + + // StringView view(reinterpret_cast(utf16.getBuffer()), + // utf16.length()); + + StringView view(reinterpret_cast(u16Str.c_str()), + u16Str.length()); + return StringBuffer::create(view); +} + +class IoSessionDelegate : public InspectorSessionDelegate { +public: + explicit IoSessionDelegate(InspectorIo *io) : io_(io) {} + bool WaitForFrontendMessageWhilePaused() override; + void SendMessageToFrontend(const v8_inspector::StringView &message) override; + +private: + InspectorIo *io_; +}; + +// Passed to InspectorSocketServer to handle WS inspector protocol events, +// mostly session start, message received, and session end. +class InspectorIoDelegate : public node::inspector::SocketServerDelegate { +public: + InspectorIoDelegate(InspectorIo *io, const std::string &script_path, + const std::string &script_name, bool wait); + // Calls PostIncomingMessage() with appropriate InspectorAction: + // kStartSession + bool StartSession(int session_id, const std::string &target_id) override; + // kSendMessage + void MessageReceived(int session_id, const std::string &message) override; + // kEndSession + void EndSession(int session_id) override; + + std::vector GetTargetIds() override; + std::string GetTargetTitle(const std::string &id) override; + std::string GetTargetUrl(const std::string &id) override; + bool IsConnected() { return connected_; } + void ServerDone() override { + io_->ServerDone(); + } + +private: + InspectorIo *io_; + bool connected_; + int session_id_; + const std::string script_name_; + const std::string script_path_; + const std::string target_id_; + bool waiting_; +}; + +void InterruptCallback(v8::Isolate *, void *agent) { + InspectorIo *io = static_cast(agent)->io(); + if (io != nullptr) + io->DispatchMessages(); +} + +class DispatchMessagesTask : public v8::Task { +public: + explicit DispatchMessagesTask(Agent *agent) : agent_(agent) {} + + void Run() override { + InspectorIo *io = agent_->io(); + if (io != nullptr) + io->DispatchMessages(); + } + +private: + Agent *agent_; +}; + +InspectorIo::InspectorIo(Environment *env, v8::Platform *platform, + const std::string &path, const DebugOptions &options, + bool wait_for_connect) +: options_(options), + thread_(), + delegate_(nullptr), + state_(State::kNew), + parent_env_(env), + thread_req_(), + platform_(platform), + dispatching_messages_(false), + session_id_(0), + script_name_(path), + wait_for_connect_(wait_for_connect), + port_(-1) { + main_thread_req_ = new AsyncAndAgent({uv_async_t(), env->inspector_agent()}); + CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_->first, + InspectorIo::MainThreadReqAsyncCb)); + uv_unref(reinterpret_cast(&main_thread_req_->first)); + CHECK_EQ(0, uv_sem_init(&thread_start_sem_, 0)); +} + +InspectorIo::~InspectorIo() { + uv_sem_destroy(&thread_start_sem_); + uv_close(reinterpret_cast(&main_thread_req_->first), + ReleasePairOnAsyncClose); +} + +bool InspectorIo::Start() { + CHECK_EQ(state_, State::kNew); + CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadMain, this), 0); + uv_sem_wait(&thread_start_sem_); + + if (state_ == State::kError) { + return false; + } + state_ = State::kAccepting; + if (wait_for_connect_) { + DispatchMessages(); + } + return true; +} + +void InspectorIo::Stop() { + CHECK(state_ == State::kAccepting || state_ == State::kConnected); + Write(TransportAction::kKill, 0, StringView()); + int err = uv_thread_join(&thread_); + CHECK_EQ(err, 0); + state_ = State::kShutDown; + DispatchMessages(); +} + +bool InspectorIo::IsConnected() { + return delegate_ != nullptr && delegate_->IsConnected(); +} + +bool InspectorIo::IsStarted() { + return platform_ != nullptr; +} + +void InspectorIo::WaitForDisconnect() { + if (state_ == State::kAccepting) + state_ = State::kDone; + if (state_ == State::kConnected) { + state_ = State::kShutDown; + Write(TransportAction::kStop, 0, StringView()); + SE_LOGD("Waiting for the debugger to disconnect...\n"); + parent_env_->inspector_agent()->RunMessageLoop(); + } +} + +// static +void InspectorIo::ThreadMain(void *io) { + static_cast(io)->ThreadMain(); +} + +// static +template +void InspectorIo::IoThreadAsyncCb(uv_async_t *async) { + TransportAndIo *transport_and_io = + static_cast *>(async->data); + if (transport_and_io == nullptr) { + return; + } + Transport *transport = transport_and_io->first; + InspectorIo *io = transport_and_io->second; + MessageQueue outgoing_message_queue; + io->SwapBehindLock(&io->outgoing_message_queue_, &outgoing_message_queue); + for (const auto &outgoing : outgoing_message_queue) { + switch (std::get<0>(outgoing)) { + case TransportAction::kKill: + transport->TerminateConnections(); + // Fallthrough + case TransportAction::kStop: + transport->Stop(nullptr); + break; + case TransportAction::kSendMessage: + std::string message = StringViewToUtf8(std::get<2>(outgoing)->string()); + transport->Send(std::get<1>(outgoing), message); + break; + } + } +} + +template +void InspectorIo::ThreadMain() { + uv_loop_t loop; + loop.data = nullptr; + int err = uv_loop_init(&loop); + CHECK_EQ(err, 0); + thread_req_.data = nullptr; + err = uv_async_init(&loop, &thread_req_, IoThreadAsyncCb); + CHECK_EQ(err, 0); + std::string script_path = ScriptPath(&loop, script_name_); + InspectorIoDelegate delegate(this, script_path, script_name_, + wait_for_connect_); + delegate_ = &delegate; + Transport server(&delegate, &loop, options_.host_name(), options_.port()); + TransportAndIo queue_transport(&server, this); + thread_req_.data = &queue_transport; + if (!server.Start()) { + state_ = State::kError; // Safe, main thread is waiting on semaphore + CHECK_EQ(0, CloseAsyncAndLoop(&thread_req_)); + uv_sem_post(&thread_start_sem_); + return; + } + port_ = server.Port(); // Safe, main thread is waiting on semaphore. + if (!wait_for_connect_) { + uv_sem_post(&thread_start_sem_); + } + uv_run(&loop, UV_RUN_DEFAULT); + thread_req_.data = nullptr; + CHECK_EQ(uv_loop_close(&loop), 0); + delegate_ = nullptr; +} + +template +bool InspectorIo::AppendMessage(MessageQueue *queue, + ActionType action, int session_id, + std::unique_ptr buffer) { + Mutex::ScopedLock scoped_lock(state_lock_); + bool trigger_pumping = queue->empty(); + queue->push_back(std::make_tuple(action, session_id, std::move(buffer))); + return trigger_pumping; +} + +template +void InspectorIo::SwapBehindLock(MessageQueue *vector1, + MessageQueue *vector2) { + Mutex::ScopedLock scoped_lock(state_lock_); + vector1->swap(*vector2); +} + +void InspectorIo::PostIncomingMessage(InspectorAction action, int session_id, + const std::string &message) { + if (AppendMessage(&incoming_message_queue_, action, session_id, + Utf8ToStringView(message))) { + Agent *agent = main_thread_req_->second; + v8::Isolate *isolate = parent_env_->isolate(); + platform_->GetForegroundTaskRunner(isolate)->PostTask(std::make_unique(agent)); + isolate->RequestInterrupt(InterruptCallback, agent); + CHECK_EQ(0, uv_async_send(&main_thread_req_->first)); + } + NotifyMessageReceived(); +} + +std::vector InspectorIo::GetTargetIds() const { + return delegate_ ? delegate_->GetTargetIds() : std::vector(); +} + +void InspectorIo::WaitForFrontendMessageWhilePaused() { + dispatching_messages_ = false; + Mutex::ScopedLock scoped_lock(state_lock_); + if (incoming_message_queue_.empty()) + incoming_message_cond_.Wait(scoped_lock); +} + +void InspectorIo::NotifyMessageReceived() { + Mutex::ScopedLock scoped_lock(state_lock_); + incoming_message_cond_.Broadcast(scoped_lock); +} + +void InspectorIo::DispatchMessages() { + // This function can be reentered if there was an incoming message while + // V8 was processing another inspector request (e.g. if the user is + // evaluating a long-running JS code snippet). This can happen only at + // specific points (e.g. the lines that call inspector_ methods) + if (dispatching_messages_) + return; + dispatching_messages_ = true; + bool had_messages = false; + do { + if (dispatching_message_queue_.empty()) + SwapBehindLock(&incoming_message_queue_, &dispatching_message_queue_); + had_messages = !dispatching_message_queue_.empty(); + while (!dispatching_message_queue_.empty()) { + MessageQueue::value_type task; + std::swap(dispatching_message_queue_.front(), task); + dispatching_message_queue_.pop_front(); + StringView message = std::get<2>(task)->string(); + switch (std::get<0>(task)) { + case InspectorAction::kStartSession: + CHECK_EQ(session_delegate_, nullptr); + session_id_ = std::get<1>(task); + state_ = State::kConnected; + SE_LOGD("Debugger attached.\n"); + session_delegate_ = std::unique_ptr( + new IoSessionDelegate(this)); + parent_env_->inspector_agent()->Connect(session_delegate_.get()); + break; + case InspectorAction::kEndSession: + CHECK_NE(session_delegate_, nullptr); + if (state_ == State::kShutDown) { + state_ = State::kDone; + } else { + state_ = State::kAccepting; + } + parent_env_->inspector_agent()->Disconnect(); + session_delegate_.reset(); + break; + case InspectorAction::kSendMessage: + parent_env_->inspector_agent()->Dispatch(message); + break; + } + } + } while (had_messages); + dispatching_messages_ = false; +} + +// static +void InspectorIo::MainThreadReqAsyncCb(uv_async_t *req) { + AsyncAndAgent *pair = node::ContainerOf(&AsyncAndAgent::first, req); + // Note that this may be called after io was closed or even after a new + // one was created and ran. + InspectorIo *io = pair->second->io(); + if (io != nullptr) + io->DispatchMessages(); +} + +void InspectorIo::Write(TransportAction action, int session_id, + const StringView &inspector_message) { + AppendMessage(&outgoing_message_queue_, action, session_id, + StringBuffer::create(inspector_message)); + int err = uv_async_send(&thread_req_); + CHECK_EQ(0, err); +} + +InspectorIoDelegate::InspectorIoDelegate(InspectorIo *io, + const std::string &script_path, + const std::string &script_name, + bool wait) +: io_(io), + connected_(false), + session_id_(0), + script_name_(script_name), + script_path_(script_path), + target_id_(GenerateID()), + waiting_(wait) {} + +bool InspectorIoDelegate::StartSession(int session_id, + const std::string &target_id) { + if (connected_) + return false; + connected_ = true; + session_id_++; + io_->PostIncomingMessage(InspectorAction::kStartSession, session_id, ""); + return true; +} + +void InspectorIoDelegate::MessageReceived(int session_id, + const std::string &message) { + // REFINE(pfeldman): Instead of blocking execution while debugger + // engages, node should wait for the run callback from the remote client + // and initiate its startup. This is a change to node.cc that should be + // upstreamed separately. + if (waiting_) { + if (message.find("\"Runtime.runIfWaitingForDebugger\"") != + std::string::npos) { + waiting_ = false; + io_->ResumeStartup(); + } + } + io_->PostIncomingMessage(InspectorAction::kSendMessage, session_id, + message); +} + +void InspectorIoDelegate::EndSession(int session_id) { + connected_ = false; + io_->PostIncomingMessage(InspectorAction::kEndSession, session_id, ""); +} + +std::vector InspectorIoDelegate::GetTargetIds() { + return {target_id_}; +} + +std::string InspectorIoDelegate::GetTargetTitle(const std::string &id) { + return script_name_.empty() ? GetProcessTitle() : script_name_; +} + +std::string InspectorIoDelegate::GetTargetUrl(const std::string &id) { + return "file://" + script_path_; +} + +bool IoSessionDelegate::WaitForFrontendMessageWhilePaused() { + io_->WaitForFrontendMessageWhilePaused(); + return true; +} + +void IoSessionDelegate::SendMessageToFrontend( + const v8_inspector::StringView &message) { + io_->Write(TransportAction::kSendMessage, io_->session_id_, message); +} + +} // namespace inspector +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/inspector_io.h b/cocos/bindings/jswrapper/v8/debugger/inspector_io.h new file mode 100644 index 0000000..62ac031 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/inspector_io.h @@ -0,0 +1,181 @@ +#ifndef SRC_INSPECTOR_IO_H_ +#define SRC_INSPECTOR_IO_H_ + +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "inspector_socket_server.h" + #include "node_debug_options.h" + #include "node_mutex.h" + #include "uv.h" + + #include + #include + #include + + #if !HAVE_INSPECTOR + #error("This header can only be used when inspector is enabled") + #endif + +// Forward declaration to break recursive dependency chain with src/env.h. +namespace node { +class Environment; +} // namespace node + +namespace v8_inspector { +class StringBuffer; +class StringView; +} // namespace v8_inspector + +namespace node { +namespace inspector { + +std::string FormatWsAddress(const std::string &host, int port, + const std::string &target_id, + bool include_protocol); + +class InspectorIoDelegate; + +enum class InspectorAction { + kStartSession, + kEndSession, + kSendMessage +}; + +// kKill closes connections and stops the server, kStop only stops the server +enum class TransportAction { + kKill, + kSendMessage, + kStop +}; + +class InspectorIo { +public: + InspectorIo(node::Environment *env, v8::Platform *platform, + const std::string &path, const DebugOptions &options, + bool wait_for_connect); + + ~InspectorIo(); + // Start the inspector agent thread, waiting for it to initialize, + // and waiting as well for a connection if wait_for_connect. + bool Start(); + // Stop the inspector agent thread. + void Stop(); + + bool IsStarted(); + bool IsConnected(); + + void WaitForDisconnect(); + // Called from thread to queue an incoming message and trigger + // DispatchMessages() on the main thread. + void PostIncomingMessage(InspectorAction action, int session_id, + const std::string &message); + void ResumeStartup() { + uv_sem_post(&thread_start_sem_); + } + void ServerDone() { + uv_close(reinterpret_cast(&thread_req_), nullptr); + } + + int port() const { return port_; } + std::string host() const { return options_.host_name(); } + std::vector GetTargetIds() const; + +private: + template + using MessageQueue = + std::deque>>; + enum class State { + kNew, + kAccepting, + kConnected, + kDone, + kError, + kShutDown + }; + + // Callback for main_thread_req_'s uv_async_t + static void MainThreadReqAsyncCb(uv_async_t *req); + + // Wrapper for agent->ThreadMain() + static void ThreadMain(void *agent); + + // Runs a uv_loop_t + template + void ThreadMain(); + // Called by ThreadMain's loop when triggered by thread_req_, writes + // messages from outgoing_message_queue to the InspectorSockerServer + template + static void IoThreadAsyncCb(uv_async_t *async); + + void SetConnected(bool connected); + void DispatchMessages(); + // Write action to outgoing_message_queue, and wake the thread + void Write(TransportAction action, int session_id, + const v8_inspector::StringView &message); + // Thread-safe append of message to a queue. Return true if the queue + // used to be empty. + template + bool AppendMessage(MessageQueue *vector, ActionType action, + int session_id, + std::unique_ptr buffer); + // Used as equivalent of a thread-safe "pop" of an entire queue's content. + template + void SwapBehindLock(MessageQueue *vector1, + MessageQueue *vector2); + // Wait on incoming_message_cond_ + void WaitForFrontendMessageWhilePaused(); + // Broadcast incoming_message_cond_ + void NotifyMessageReceived(); + + const DebugOptions options_; + + // The IO thread runs its own uv_loop to implement the TCP server off + // the main thread. + uv_thread_t thread_; + // Used by Start() to wait for thread to initialize, or for it to initialize + // and receive a connection if wait_for_connect was requested. + uv_sem_t thread_start_sem_; + + InspectorIoDelegate *delegate_; + State state_; + node::Environment *parent_env_; + + // Attached to the uv_loop in ThreadMain() + uv_async_t thread_req_; + // Note that this will live while the async is being closed - likely, past + // the parent object lifespan + std::pair *main_thread_req_; + std::unique_ptr session_delegate_; + v8::Platform *platform_; + + // Message queues + ConditionVariable incoming_message_cond_; + Mutex state_lock_; // Locked before mutating either queue. + MessageQueue incoming_message_queue_; + MessageQueue outgoing_message_queue_; + MessageQueue dispatching_message_queue_; + + bool dispatching_messages_; + int session_id_; + + std::string script_name_; + std::string script_path_; + const bool wait_for_connect_; + int port_; + + friend class DispatchMessagesTask; + friend class IoSessionDelegate; + friend void InterruptCallback(v8::Isolate *, void *agent); +}; + +std::unique_ptr Utf8ToStringView( + const std::string &message); + +} // namespace inspector +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + +#endif // SRC_INSPECTOR_IO_H_ diff --git a/cocos/bindings/jswrapper/v8/debugger/inspector_socket.cpp b/cocos/bindings/jswrapper/v8/debugger/inspector_socket.cpp new file mode 100644 index 0000000..564e2b6 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/inspector_socket.cpp @@ -0,0 +1,643 @@ +#include "inspector_socket.h" + +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "util.h" + + #include "base64.h" + + //#include "openssl/sha.h" // Sha-1 hash + #include "SHA1.h" + + #include + #include + + #define ACCEPT_KEY_LENGTH base64_encoded_size(20) + #define BUFFER_GROWTH_CHUNK_SIZE 1024 + + #define DUMP_READS 0 + #define DUMP_WRITES 0 + +namespace node { +namespace inspector { + +static const char CLOSE_FRAME[] = {'\x88', '\x00'}; + +enum ws_decode_result { + FRAME_OK, + FRAME_INCOMPLETE, + FRAME_CLOSE, + FRAME_ERROR +}; + + #if DUMP_READS || DUMP_WRITES +static void dump_hex(const char *buf, size_t len) { + const char *ptr = buf; + const char *end = ptr + len; + const char *cptr; + char c; + int i; + + while (ptr < end) { + cptr = ptr; + for (i = 0; i < 16 && ptr < end; i++) { + printf("%2.2X ", static_cast(*(ptr++))); + } + for (i = 72 - (i * 4); i > 0; i--) { + printf(" "); + } + for (i = 0; i < 16 && cptr < end; i++) { + c = *(cptr++); + printf("%c", (c > 0x19) ? c : '.'); + } + printf("\n"); + } + printf("\n\n"); +} + #endif + +static void remove_from_beginning(std::vector *buffer, size_t count) { + buffer->erase(buffer->begin(), buffer->begin() + count); +} + +static void dispose_inspector(uv_handle_t *handle) { + InspectorSocket *inspector = inspector_from_stream(handle); + inspector_cb close = + inspector->ws_mode ? inspector->ws_state->close_cb : nullptr; + inspector->buffer.clear(); + delete inspector->ws_state; + inspector->ws_state = nullptr; + if (close) { + close(inspector, 0); + } +} + +static void close_connection(InspectorSocket *inspector) { + uv_handle_t *socket = reinterpret_cast(&inspector->tcp); + if (!uv_is_closing(socket)) { + uv_read_stop(reinterpret_cast(socket)); + uv_close(socket, dispose_inspector); + } +} + +struct WriteRequest { + WriteRequest(InspectorSocket *inspector, const char *data, size_t size) + : inspector(inspector), + storage(data, data + size), + buf(uv_buf_init(&storage[0], (unsigned int)storage.size())) {} + + static WriteRequest *from_write_req(uv_write_t *req) { + return node::ContainerOf(&WriteRequest::req, req); + } + + InspectorSocket *const inspector; + std::vector storage; + uv_write_t req; + uv_buf_t buf; +}; + +// Cleanup +static void write_request_cleanup(uv_write_t *req, int status) { + delete WriteRequest::from_write_req(req); +} + +static int write_to_client(InspectorSocket *inspector, + const char *msg, + size_t len, + uv_write_cb write_cb = write_request_cleanup) { + #if DUMP_WRITES + printf("%s (%ld bytes):\n", __FUNCTION__, len); + dump_hex(msg, len); + #endif + + // Freed in write_request_cleanup + WriteRequest *wr = new WriteRequest(inspector, msg, len); + uv_stream_t *stream = reinterpret_cast(&inspector->tcp); + return uv_write(&wr->req, stream, &wr->buf, 1, write_cb) < 0; +} + +// Constants for hybi-10 frame format. + +typedef int OpCode; + +const OpCode kOpCodeContinuation = 0x0; +const OpCode kOpCodeText = 0x1; +const OpCode kOpCodeBinary = 0x2; +const OpCode kOpCodeClose = 0x8; +const OpCode kOpCodePing = 0x9; +const OpCode kOpCodePong = 0xA; + +const unsigned char kFinalBit = 0x80; +const unsigned char kReserved1Bit = 0x40; +const unsigned char kReserved2Bit = 0x20; +const unsigned char kReserved3Bit = 0x10; +const unsigned char kOpCodeMask = 0xF; +const unsigned char kMaskBit = 0x80; +const unsigned char kPayloadLengthMask = 0x7F; + +const size_t kMaxSingleBytePayloadLength = 125; +const size_t kTwoBytePayloadLengthField = 126; +const size_t kEightBytePayloadLengthField = 127; +const size_t kMaskingKeyWidthInBytes = 4; + +static std::vector encode_frame_hybi17(const char *message, + size_t data_length) { + std::vector frame; + OpCode op_code = kOpCodeText; + frame.push_back(kFinalBit | op_code); + if (data_length <= kMaxSingleBytePayloadLength) { + frame.push_back(static_cast(data_length)); + } else if (data_length <= 0xFFFF) { + frame.push_back(kTwoBytePayloadLengthField); + frame.push_back((data_length & 0xFF00) >> 8); + frame.push_back(data_length & 0xFF); + } else { + frame.push_back(kEightBytePayloadLengthField); + char extended_payload_length[8]; + size_t remaining = data_length; + // Fill the length into extended_payload_length in the network byte order. + for (int i = 0; i < 8; ++i) { + extended_payload_length[7 - i] = remaining & 0xFF; + remaining >>= 8; + } + frame.insert(frame.end(), extended_payload_length, + extended_payload_length + 8); + CHECK_EQ(0, remaining); + } + frame.insert(frame.end(), message, message + data_length); + return frame; +} + +static ws_decode_result decode_frame_hybi17(const std::vector &buffer, + bool client_frame, + int *bytes_consumed, + std::vector *output, + bool *compressed) { + *bytes_consumed = 0; + if (buffer.size() < 2) + return FRAME_INCOMPLETE; + + auto it = buffer.begin(); + + unsigned char first_byte = *it++; + unsigned char second_byte = *it++; + + bool final = (first_byte & kFinalBit) != 0; + bool reserved1 = (first_byte & kReserved1Bit) != 0; + bool reserved2 = (first_byte & kReserved2Bit) != 0; + bool reserved3 = (first_byte & kReserved3Bit) != 0; + int op_code = first_byte & kOpCodeMask; + bool masked = (second_byte & kMaskBit) != 0; + *compressed = reserved1; + if (!final || reserved2 || reserved3) + return FRAME_ERROR; // Only compression extension is supported. + + bool closed = false; + switch (op_code) { + case kOpCodeClose: + closed = true; + break; + case kOpCodeText: + break; + case kOpCodeBinary: // We don't support binary frames yet. + case kOpCodeContinuation: // We don't support binary frames yet. + case kOpCodePing: // We don't support binary frames yet. + case kOpCodePong: // We don't support binary frames yet. + default: + return FRAME_ERROR; + } + + // In Hybi-17 spec client MUST mask its frame. + if (client_frame && !masked) { + return FRAME_ERROR; + } + + uint64_t payload_length64 = second_byte & kPayloadLengthMask; + if (payload_length64 > kMaxSingleBytePayloadLength) { + int extended_payload_length_size; + if (payload_length64 == kTwoBytePayloadLengthField) { + extended_payload_length_size = 2; + } else if (payload_length64 == kEightBytePayloadLengthField) { + extended_payload_length_size = 8; + } else { + return FRAME_ERROR; + } + if ((buffer.end() - it) < extended_payload_length_size) + return FRAME_INCOMPLETE; + payload_length64 = 0; + for (int i = 0; i < extended_payload_length_size; ++i) { + payload_length64 <<= 8; + payload_length64 |= static_cast(*it++); + } + } + + static const uint64_t max_payload_length = 0x7FFFFFFFFFFFFFFFull; + static const size_t max_length = SIZE_MAX; + if (payload_length64 > max_payload_length || + payload_length64 > max_length - kMaskingKeyWidthInBytes) { + // WebSocket frame length too large. + return FRAME_ERROR; + } + size_t payload_length = static_cast(payload_length64); + + if (buffer.size() - kMaskingKeyWidthInBytes < payload_length) + return FRAME_INCOMPLETE; + + std::vector::const_iterator masking_key = it; + std::vector::const_iterator payload = it + kMaskingKeyWidthInBytes; + for (size_t i = 0; i < payload_length; ++i) // Unmask the payload. + output->insert(output->end(), + payload[i] ^ masking_key[i % kMaskingKeyWidthInBytes]); + + size_t pos = it + kMaskingKeyWidthInBytes + payload_length - buffer.begin(); + *bytes_consumed = (int)pos; + return closed ? FRAME_CLOSE : FRAME_OK; +} + +static void invoke_read_callback(InspectorSocket *inspector, + int status, const uv_buf_t *buf) { + if (inspector->ws_state->read_cb) { + inspector->ws_state->read_cb( + reinterpret_cast(&inspector->tcp), status, buf); + } +} + +static void shutdown_complete(InspectorSocket *inspector) { + close_connection(inspector); +} + +static void on_close_frame_written(uv_write_t *req, int status) { + WriteRequest *wr = WriteRequest::from_write_req(req); + InspectorSocket *inspector = wr->inspector; + delete wr; + inspector->ws_state->close_sent = true; + if (inspector->ws_state->received_close) { + shutdown_complete(inspector); + } +} + +static void close_frame_received(InspectorSocket *inspector) { + inspector->ws_state->received_close = true; + if (!inspector->ws_state->close_sent) { + invoke_read_callback(inspector, 0, 0); + write_to_client(inspector, CLOSE_FRAME, sizeof(CLOSE_FRAME), + on_close_frame_written); + } else { + shutdown_complete(inspector); + } +} + +static int parse_ws_frames(InspectorSocket *inspector) { + int bytes_consumed = 0; + std::vector output; + bool compressed = false; + + ws_decode_result r = decode_frame_hybi17(inspector->buffer, + true /* client_frame */, + &bytes_consumed, &output, + &compressed); + // Compressed frame means client is ignoring the headers and misbehaves + if (compressed || r == FRAME_ERROR) { + invoke_read_callback(inspector, UV_EPROTO, nullptr); + close_connection(inspector); + bytes_consumed = 0; + } else if (r == FRAME_CLOSE) { + close_frame_received(inspector); + bytes_consumed = 0; + } else if (r == FRAME_OK && inspector->ws_state->alloc_cb && inspector->ws_state->read_cb) { + uv_buf_t buffer; + size_t len = output.size(); + inspector->ws_state->alloc_cb( + reinterpret_cast(&inspector->tcp), + len, &buffer); + CHECK_GE(buffer.len, len); + memcpy(buffer.base, &output[0], len); + invoke_read_callback(inspector, (int)len, &buffer); + } + return bytes_consumed; +} + +static void prepare_buffer(uv_handle_t *stream, size_t len, uv_buf_t *buf) { + *buf = uv_buf_init(new char[len], (unsigned int)len); +} + +static void reclaim_uv_buf(InspectorSocket *inspector, const uv_buf_t *buf, + ssize_t read) { + if (read > 0) { + std::vector &buffer = inspector->buffer; + buffer.insert(buffer.end(), buf->base, buf->base + read); + } + delete[] buf->base; +} + +static void websockets_data_cb(uv_stream_t *stream, ssize_t nread, + const uv_buf_t *buf) { + InspectorSocket *inspector = inspector_from_stream(stream); + reclaim_uv_buf(inspector, buf, nread); + if (nread < 0 || nread == UV_EOF) { + inspector->connection_eof = true; + if (!inspector->shutting_down && inspector->ws_state->read_cb) { + inspector->ws_state->read_cb(stream, nread, nullptr); + } + if (inspector->ws_state->close_sent && + !inspector->ws_state->received_close) { + shutdown_complete(inspector); // invoke callback + } + } else { + #if DUMP_READS + printf("%s read %ld bytes\n", __FUNCTION__, nread); + if (nread > 0) { + dump_hex(inspector->buffer.data() + inspector->buffer.size() - nread, + nread); + } + #endif + // 2. Parse. + int processed = 0; + do { + processed = parse_ws_frames(inspector); + // 3. Fix the buffer size & length + if (processed > 0) { + remove_from_beginning(&inspector->buffer, processed); + } + } while (processed > 0 && !inspector->buffer.empty()); + } +} + +int inspector_read_start(InspectorSocket *inspector, + uv_alloc_cb alloc_cb, uv_read_cb read_cb) { + CHECK(inspector->ws_mode); + CHECK(!inspector->shutting_down || read_cb == nullptr); + inspector->ws_state->close_sent = false; + inspector->ws_state->alloc_cb = alloc_cb; + inspector->ws_state->read_cb = read_cb; + int err = + uv_read_start(reinterpret_cast(&inspector->tcp), + prepare_buffer, + websockets_data_cb); + if (err < 0) { + close_connection(inspector); + } + return err; +} + +void inspector_read_stop(InspectorSocket *inspector) { + uv_read_stop(reinterpret_cast(&inspector->tcp)); + inspector->ws_state->alloc_cb = nullptr; + inspector->ws_state->read_cb = nullptr; +} + +static void generate_accept_string(const std::string &client_key, + char (*buffer)[ACCEPT_KEY_LENGTH]) { + // Magic string from websockets spec. + static const char ws_magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + std::string input(client_key + ws_magic); + // char hash[SHA_DIGEST_LENGTH]; + // SHA1(reinterpret_cast(&input[0]), input.size(), + // reinterpret_cast(hash)); + + se::SHA1Sum::Hash hash = {0}; + se::SHA1Sum s; + s.update(reinterpret_cast(&input[0]), (uint32_t)input.size()); + s.finish(hash); + + node::base64_encode((char *)hash, sizeof(hash), *buffer, sizeof(*buffer)); +} + +static int header_value_cb(http_parser *parser, const char *at, size_t length) { + static const char SEC_WEBSOCKET_KEY_HEADER[] = "Sec-WebSocket-Key"; + auto inspector = static_cast(parser->data); + auto state = inspector->http_parsing_state; + state->parsing_value = true; + if (state->current_header.size() == sizeof(SEC_WEBSOCKET_KEY_HEADER) - 1 && + node::StringEqualNoCaseN(state->current_header.data(), + SEC_WEBSOCKET_KEY_HEADER, + sizeof(SEC_WEBSOCKET_KEY_HEADER) - 1)) { + state->ws_key.append(at, length); + } + return 0; +} + +static int header_field_cb(http_parser *parser, const char *at, size_t length) { + auto inspector = static_cast(parser->data); + auto state = inspector->http_parsing_state; + if (state->parsing_value) { + state->parsing_value = false; + state->current_header.clear(); + } + state->current_header.append(at, length); + return 0; +} + +static int path_cb(http_parser *parser, const char *at, size_t length) { + auto inspector = static_cast(parser->data); + auto state = inspector->http_parsing_state; + state->path.append(at, length); + return 0; +} + +static void handshake_complete(InspectorSocket *inspector) { + uv_read_stop(reinterpret_cast(&inspector->tcp)); + handshake_cb callback = inspector->http_parsing_state->callback; + inspector->ws_state = new ws_state_s(); + inspector->ws_mode = true; + callback(inspector, kInspectorHandshakeUpgraded, + inspector->http_parsing_state->path); +} + +static void cleanup_http_parsing_state(InspectorSocket *inspector) { + delete inspector->http_parsing_state; + inspector->http_parsing_state = nullptr; +} + +static void report_handshake_failure_cb(uv_handle_t *handle) { + dispose_inspector(handle); + InspectorSocket *inspector = inspector_from_stream(handle); + handshake_cb cb = inspector->http_parsing_state->callback; + cleanup_http_parsing_state(inspector); + cb(inspector, kInspectorHandshakeFailed, std::string()); +} + +static void close_and_report_handshake_failure(InspectorSocket *inspector) { + uv_handle_t *socket = reinterpret_cast(&inspector->tcp); + if (uv_is_closing(socket)) { + report_handshake_failure_cb(socket); + } else { + uv_read_stop(reinterpret_cast(socket)); + uv_close(socket, report_handshake_failure_cb); + } +} + +static void then_close_and_report_failure(uv_write_t *req, int status) { + InspectorSocket *inspector = WriteRequest::from_write_req(req)->inspector; + write_request_cleanup(req, status); + close_and_report_handshake_failure(inspector); +} + +static void handshake_failed(InspectorSocket *inspector) { + const char HANDSHAKE_FAILED_RESPONSE[] = + "HTTP/1.0 400 Bad Request\r\n" + "Content-Type: text/html; charset=UTF-8\r\n\r\n" + "WebSockets request was expected\r\n"; + write_to_client(inspector, HANDSHAKE_FAILED_RESPONSE, + sizeof(HANDSHAKE_FAILED_RESPONSE) - 1, + then_close_and_report_failure); +} + +// init_handshake references message_complete_cb +static void init_handshake(InspectorSocket *socket); + +static int message_complete_cb(http_parser *parser) { + InspectorSocket *inspector = static_cast(parser->data); + struct http_parsing_state_s *state = inspector->http_parsing_state; + if (parser->method != HTTP_GET) { + handshake_failed(inspector); + } else if (!parser->upgrade) { + if (state->callback(inspector, kInspectorHandshakeHttpGet, state->path)) { + init_handshake(inspector); + } else { + handshake_failed(inspector); + } + } else if (state->ws_key.empty()) { + handshake_failed(inspector); + } else if (state->callback(inspector, kInspectorHandshakeUpgrading, + state->path)) { + char accept_string[ACCEPT_KEY_LENGTH]; + generate_accept_string(state->ws_key, &accept_string); + const char accept_ws_prefix[] = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: "; + const char accept_ws_suffix[] = "\r\n\r\n"; + std::string reply(accept_ws_prefix, sizeof(accept_ws_prefix) - 1); + reply.append(accept_string, sizeof(accept_string)); + reply.append(accept_ws_suffix, sizeof(accept_ws_suffix) - 1); + if (write_to_client(inspector, &reply[0], reply.size()) >= 0) { + handshake_complete(inspector); + inspector->http_parsing_state->done = true; + } else { + close_and_report_handshake_failure(inspector); + } + } else { + handshake_failed(inspector); + } + return 0; +} + +static void data_received_cb(uv_stream_s *tcp, ssize_t nread, + const uv_buf_t *buf) { + #if DUMP_READS + if (nread >= 0) { + printf("%s (%ld bytes)\n", __FUNCTION__, nread); + dump_hex(buf->base, nread); + } else { + printf("[%s:%d] %s\n", __FUNCTION__, __LINE__, uv_err_name(nread)); + } + #endif + InspectorSocket *inspector = inspector_from_stream(tcp); + reclaim_uv_buf(inspector, buf, nread); + if (nread < 0 || nread == UV_EOF) { + close_and_report_handshake_failure(inspector); + } else { + http_parsing_state_s *state = inspector->http_parsing_state; + http_parser *parser = &state->parser; + http_parser_execute(parser, &state->parser_settings, + inspector->buffer.data(), nread); + remove_from_beginning(&inspector->buffer, nread); + if (parser->http_errno != HPE_OK) { + handshake_failed(inspector); + } + if (inspector->http_parsing_state->done) { + cleanup_http_parsing_state(inspector); + } + } +} + +static void init_handshake(InspectorSocket *socket) { + http_parsing_state_s *state = socket->http_parsing_state; + CHECK_NE(state, nullptr); + state->current_header.clear(); + state->ws_key.clear(); + state->path.clear(); + state->done = false; + http_parser_init(&state->parser, HTTP_REQUEST); + state->parser.data = socket; + http_parser_settings *settings = &state->parser_settings; + http_parser_settings_init(settings); + settings->on_header_field = header_field_cb; + settings->on_header_value = header_value_cb; + settings->on_message_complete = message_complete_cb; + settings->on_url = path_cb; +} + +int inspector_accept(uv_stream_t *server, InspectorSocket *socket, + handshake_cb callback) { + CHECK_NE(callback, nullptr); + CHECK_EQ(socket->http_parsing_state, nullptr); + + socket->http_parsing_state = new http_parsing_state_s(); + uv_stream_t *tcp = reinterpret_cast(&socket->tcp); + int err = uv_tcp_init(server->loop, &socket->tcp); + + if (err == 0) { + err = uv_accept(server, tcp); + } + if (err == 0) { + init_handshake(socket); + socket->http_parsing_state->callback = callback; + err = uv_read_start(tcp, prepare_buffer, + data_received_cb); + } + if (err != 0) { + uv_close(reinterpret_cast(tcp), NULL); + } + return err; +} + +void inspector_write(InspectorSocket *inspector, const char *data, + size_t len) { + if (inspector->ws_mode) { + std::vector output = encode_frame_hybi17(data, len); + write_to_client(inspector, &output[0], output.size()); + } else { + write_to_client(inspector, data, len); + } +} + +void inspector_close(InspectorSocket *inspector, + inspector_cb callback) { + // libuv throws assertions when closing stream that's already closed - we + // need to do the same. + CHECK(!uv_is_closing(reinterpret_cast(&inspector->tcp))); + CHECK(!inspector->shutting_down); + inspector->shutting_down = true; + inspector->ws_state->close_cb = callback; + if (inspector->connection_eof) { + close_connection(inspector); + } else { + inspector_read_stop(inspector); + write_to_client(inspector, CLOSE_FRAME, sizeof(CLOSE_FRAME), + on_close_frame_written); + inspector_read_start(inspector, nullptr, nullptr); + } +} + +bool inspector_is_active(const InspectorSocket *inspector) { + const uv_handle_t *tcp = + reinterpret_cast(&inspector->tcp); + return !inspector->shutting_down && !uv_is_closing(tcp); +} + +void InspectorSocket::reinit() { + http_parsing_state = nullptr; + ws_state = nullptr; + buffer.clear(); + ws_mode = false; + shutting_down = false; + connection_eof = false; +} + +} // namespace inspector +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/inspector_socket.h b/cocos/bindings/jswrapper/v8/debugger/inspector_socket.h new file mode 100644 index 0000000..12ac527 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/inspector_socket.h @@ -0,0 +1,109 @@ +#ifndef SRC_INSPECTOR_SOCKET_H_ +#define SRC_INSPECTOR_SOCKET_H_ + +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "http_parser.h" + #include "util.h" + #include "uv.h" + + #include + #include + +namespace node { +namespace inspector { + +enum inspector_handshake_event { + kInspectorHandshakeUpgrading, + kInspectorHandshakeUpgraded, + kInspectorHandshakeHttpGet, + kInspectorHandshakeFailed +}; + +class InspectorSocket; + +typedef void (*inspector_cb)(InspectorSocket *, int); +// Notifies as handshake is progressing. Returning false as a response to +// kInspectorHandshakeUpgrading or kInspectorHandshakeHttpGet event will abort +// the connection. inspector_write can be used from the callback. +typedef bool (*handshake_cb)(InspectorSocket *, + enum inspector_handshake_event state, + const std::string &path); + +struct http_parsing_state_s { + http_parser parser; + http_parser_settings parser_settings; + handshake_cb callback; + bool done; + bool parsing_value; + std::string ws_key; + std::string path; + std::string current_header; +}; + +struct ws_state_s { + uv_alloc_cb alloc_cb; + uv_read_cb read_cb; + inspector_cb close_cb; + bool close_sent; + bool received_close; +}; + +// HTTP Wrapper around a uv_tcp_t +class InspectorSocket { +public: + InspectorSocket() : data(nullptr), + http_parsing_state(nullptr), + ws_state(nullptr), + buffer(0), + ws_mode(false), + shutting_down(false), + connection_eof(false) {} + void reinit(); + void *data; + struct http_parsing_state_s *http_parsing_state; + struct ws_state_s *ws_state; + std::vector buffer; + uv_tcp_t tcp; + bool ws_mode; + bool shutting_down; + bool connection_eof; + +private: + NODE_DISALLOW_COPY_AND_ASSIGN(InspectorSocket); +}; + +int inspector_accept(uv_stream_t *server, InspectorSocket *inspector, + handshake_cb callback); + +void inspector_close(InspectorSocket *inspector, + inspector_cb callback); + +// Callbacks will receive stream handles. Use inspector_from_stream to get +// InspectorSocket* from the stream handle. +int inspector_read_start(InspectorSocket *inspector, uv_alloc_cb, + uv_read_cb); +void inspector_read_stop(InspectorSocket *inspector); +void inspector_write(InspectorSocket *inspector, + const char *data, size_t len); +bool inspector_is_active(const InspectorSocket *inspector); + +inline InspectorSocket *inspector_from_stream(uv_tcp_t *stream) { + return node::ContainerOf(&InspectorSocket::tcp, stream); +} + +inline InspectorSocket *inspector_from_stream(uv_stream_t *stream) { + return inspector_from_stream(reinterpret_cast(stream)); +} + +inline InspectorSocket *inspector_from_stream(uv_handle_t *stream) { + return inspector_from_stream(reinterpret_cast(stream)); +} + +} // namespace inspector +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + +#endif // SRC_INSPECTOR_SOCKET_H_ diff --git a/cocos/bindings/jswrapper/v8/debugger/inspector_socket_server.cpp b/cocos/bindings/jswrapper/v8/debugger/inspector_socket_server.cpp new file mode 100644 index 0000000..1f40c60 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/inspector_socket_server.cpp @@ -0,0 +1,700 @@ +#include "inspector_socket_server.h" + +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "node.h" + #include "uv.h" + #include "zlib.h" + + #include + #include + #include + #include + +namespace node { +namespace inspector { + +// Function is declared in inspector_io.h so the rest of the node does not +// depend on inspector_socket_server.h +std::string FormatWsAddress(const std::string &host, int port, + const std::string &target_id, + bool include_protocol) { + // Host is valid (socket was bound) so colon means it's a v6 IP address + bool v6 = host.find(':') != std::string::npos; + std::ostringstream url; + if (include_protocol) + url << "ws://"; + if (v6) { + url << '['; + } + url << host; + if (v6) { + url << ']'; + } + url << ':' << port << '/' << target_id; + return url.str(); +} + +namespace { + +static const uint8_t PROTOCOL_JSON[] = { + #include "v8_inspector_protocol_json.h" // NOLINT(build/include_order) +}; + +template +static std::string to_string(T arg) { + std::stringstream ss; + ss << arg; + return ss.str(); +} + +void Escape(std::string *string) { + for (char &c : *string) { + c = (c == '\"' || c == '\\') ? '_' : c; + } +} + +std::string MapToString(const std::map &object) { + bool first = true; + std::ostringstream json; + json << "{\n"; + for (const auto &name_value : object) { + if (!first) + json << ",\n"; + first = false; + json << " \"" << name_value.first << "\": \""; + json << name_value.second << "\""; + } + json << "\n} "; + return json.str(); +} + +std::string MapsToString( + const std::vector> &array) { + bool first = true; + std::ostringstream json; + json << "[ "; + for (const auto &object : array) { + if (!first) + json << ", "; + first = false; + json << MapToString(object); + } + json << "]\n\n"; + return json.str(); +} + +const char *MatchPathSegment(const char *path, const char *expected) { + size_t len = strlen(expected); + if (StringEqualNoCaseN(path, expected, len)) { + if (path[len] == '/') return path + len + 1; + if (path[len] == '\0') return path + len; + } + return nullptr; +} + +void OnBufferAlloc(uv_handle_t *handle, size_t len, uv_buf_t *buf) { + buf->base = new char[len]; + buf->len = len; +} + +void PrintDebuggerReadyMessage(const std::string &host, + int port, + const std::vector &ids, + FILE *out) { + if (out == NULL) { + return; + } + + std::vector> ipList; + + { + char buf[512]; + uv_interface_address_t *info = nullptr; + int count = 0; + int i = 0; + + uv_interface_addresses(&info, &count); + i = count; + + if (errno) { + SE_LOGE("failed to get addresses %s", strerror(errno)); + } + + SE_LOGD("Number of interfaces: %d", count); + while (i--) { + auto &networkInterface = info[i]; + + if (networkInterface.address.address4.sin_family == AF_INET) { + uv_ip4_name(&networkInterface.address.address4, buf, sizeof(buf)); + ipList.emplace_back(networkInterface.name, networkInterface.is_internal, buf); + } + } + uv_free_interface_addresses(info, count); + } + // failed to query device interfaces, + if (ipList.empty()) { + #if ANDROID + SE_LOGD("Please query IP by running the following command in terminal: adb shell ip -4 -br addr"); + #endif + ipList.emplace_back("none", false, "IP_ADDR_OF_THIS_DEVICE"); + } + + for (const std::string &id : ids) { + if (host != "0.0.0.0") { + SE_LOGD("Debugger listening..., visit [ devtools://devtools/bundled/js_app.html?v8only=true&ws=%s ] in chrome browser to debug!", + FormatWsAddress(host, port, id, false).c_str()); + } else { + SE_LOGD("Debugger listening..., visit ["); + for (auto &nif : ipList) { + SE_LOGD(" devtools://devtools/bundled/js_app.html?v8only=true&ws=%s", + FormatWsAddress(std::get<2>(nif), port, id, false).c_str()); + } + SE_LOGD(" ] in chrome browser to debug!"); + } + } + SE_LOGD("For help see %s", + "https://nodejs.org/en/docs/inspector"); +} + +void SendHttpResponse(InspectorSocket *socket, const std::string &response) { + const char HEADERS[] = + "HTTP/1.0 200 OK\r\n" + "Content-Type: application/json; charset=UTF-8\r\n" + "Cache-Control: no-cache\r\n" + "Content-Length: %zu\r\n" + "\r\n"; + char header[sizeof(HEADERS) + 20]; + int header_len = snprintf(header, sizeof(header), HEADERS, response.size()); + inspector_write(socket, header, header_len); + inspector_write(socket, response.data(), response.size()); +} + +void SendVersionResponse(InspectorSocket *socket) { + std::map response; + response["Browser"] = "Cocos Games"; //cjh + response["Protocol-Version"] = "1.1"; + SendHttpResponse(socket, MapToString(response)); +} + +void SendProtocolJson(InspectorSocket *socket) { + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + CHECK_EQ(Z_OK, inflateInit(&strm)); + static const size_t kDecompressedSize = + PROTOCOL_JSON[0] * 0x10000u + + PROTOCOL_JSON[1] * 0x100u + + PROTOCOL_JSON[2]; + strm.next_in = const_cast(PROTOCOL_JSON + 3); + strm.avail_in = sizeof(PROTOCOL_JSON) - 3; + std::string data(kDecompressedSize, '\0'); + strm.next_out = reinterpret_cast(&data[0]); + strm.avail_out = static_cast(data.size()); + CHECK_EQ(Z_STREAM_END, inflate(&strm, Z_FINISH)); + CHECK_EQ(0, strm.avail_out); + CHECK_EQ(Z_OK, inflateEnd(&strm)); + SendHttpResponse(socket, data); +} + +int GetSocketHost(uv_tcp_t *socket, std::string *out_host) { + char ip[INET6_ADDRSTRLEN]; + sockaddr_storage addr; + int len = sizeof(addr); + int err = uv_tcp_getsockname(socket, + reinterpret_cast(&addr), + &len); + if (err != 0) + return err; + if (addr.ss_family == AF_INET6) { + const sockaddr_in6 *v6 = reinterpret_cast(&addr); + err = uv_ip6_name(v6, ip, sizeof(ip)); + } else { + const sockaddr_in *v4 = reinterpret_cast(&addr); + err = uv_ip4_name(v4, ip, sizeof(ip)); + } + if (err != 0) + return err; + *out_host = ip; + return err; +} +} // namespace + +class Closer { +public: + explicit Closer(InspectorSocketServer *server) : server_(server), + close_count_(0) {} + + void AddCallback(InspectorSocketServer::ServerCallback callback) { + if (callback == nullptr) + return; + callbacks_.insert(callback); + } + + void DecreaseExpectedCount() { + --close_count_; + NotifyIfDone(); + } + + void IncreaseExpectedCount() { + ++close_count_; + } + + void NotifyIfDone() { + if (close_count_ == 0) { + for (auto callback : callbacks_) { + callback(server_); + } + InspectorSocketServer *server = server_; + delete server->closer_; + server->closer_ = nullptr; + } + } + +private: + InspectorSocketServer *server_; + std::set callbacks_; + int close_count_; +}; + +class SocketSession { +public: + static int Accept(InspectorSocketServer *server, int server_port, + uv_stream_t *server_socket); + void Send(const std::string &message); + void Close(); + + int id() const { return id_; } + bool IsForTarget(const std::string &target_id) const { + return target_id_ == target_id; + } + static int ServerPortForClient(InspectorSocket *client) { + return From(client)->server_port_; + } + +private: + SocketSession(InspectorSocketServer *server, int server_port); + static SocketSession *From(InspectorSocket *socket) { + return node::ContainerOf(&SocketSession::socket_, socket); + } + + enum class State { kHttp, + kWebSocket, + kClosing, + kEOF, + kDeclined }; + static bool HandshakeCallback(InspectorSocket *socket, + enum inspector_handshake_event state, + const std::string &path); + static void ReadCallback(uv_stream_t *stream, ssize_t read, + const uv_buf_t *buf); + static void CloseCallback(InspectorSocket *socket, int code); + + void FrontendConnected(); + void SetDeclined() { state_ = State::kDeclined; } + void SetTargetId(const std::string &target_id) { + CHECK(target_id_.empty()); + target_id_ = target_id; + } + + const int id_; + InspectorSocket socket_; + InspectorSocketServer *server_; + std::string target_id_; + State state_; + const int server_port_; +}; + +class ServerSocket { +public: + static int Listen(InspectorSocketServer *inspector_server, + sockaddr *addr, uv_loop_t *loop); + void Close() { + uv_close(reinterpret_cast(&tcp_socket_), + SocketClosedCallback); + } + int port() const { return port_; } + +private: + explicit ServerSocket(InspectorSocketServer *server) + : tcp_socket_(uv_tcp_t()), + server_(server), + port_(-1) {} + template + static ServerSocket *FromTcpSocket(UvHandle *socket) { + return node::ContainerOf(&ServerSocket::tcp_socket_, + reinterpret_cast(socket)); + } + + static void SocketConnectedCallback(uv_stream_t *tcp_socket, int status); + static void SocketClosedCallback(uv_handle_t *tcp_socket); + static void FreeOnCloseCallback(uv_handle_t *tcp_socket_) { + delete FromTcpSocket(tcp_socket_); + } + int DetectPort(); + + uv_tcp_t tcp_socket_; + InspectorSocketServer *server_; + int port_; +}; + +InspectorSocketServer::InspectorSocketServer(SocketServerDelegate *delegate, + uv_loop_t *loop, + const std::string &host, + int port, + FILE *out) : loop_(loop), + delegate_(delegate), + host_(host), + port_(port), + closer_(nullptr), + next_session_id_(0), + out_(out) { + state_ = ServerState::kNew; +} + +bool InspectorSocketServer::SessionStarted(SocketSession *session, + const std::string &id) { + if (TargetExists(id) && delegate_->StartSession(session->id(), id)) { + connected_sessions_[session->id()] = session; + return true; + } else { + return false; + } +} + +void InspectorSocketServer::SessionTerminated(SocketSession *session) { + int id = session->id(); + if (connected_sessions_.erase(id) != 0) { + delegate_->EndSession(id); + if (connected_sessions_.empty()) { + if (state_ == ServerState::kRunning && !server_sockets_.empty()) { + PrintDebuggerReadyMessage(host_, server_sockets_[0]->port(), + delegate_->GetTargetIds(), out_); + } + if (state_ == ServerState::kStopped) { + delegate_->ServerDone(); + } + } + } + delete session; +} + +bool InspectorSocketServer::HandleGetRequest(InspectorSocket *socket, + const std::string &path) { + const char *command = MatchPathSegment(path.c_str(), "/json"); + if (command == nullptr) + return false; + + if (MatchPathSegment(command, "list") || command[0] == '\0') { + SendListResponse(socket); + return true; + } else if (MatchPathSegment(command, "protocol")) { + SendProtocolJson(socket); + return true; + } else if (MatchPathSegment(command, "version")) { + SendVersionResponse(socket); + return true; + } else if (const char *target_id = MatchPathSegment(command, "activate")) { + if (TargetExists(target_id)) { + SendHttpResponse(socket, "Target activated"); + return true; + } + return false; + } + return false; +} + +void InspectorSocketServer::SendListResponse(InspectorSocket *socket) { + std::vector> response; + for (const std::string &id : delegate_->GetTargetIds()) { + response.push_back(std::map()); + std::map &target_map = response.back(); + target_map["description"] = "node.js instance"; + target_map["faviconUrl"] = "https://nodejs.org/static/favicon.ico"; + target_map["id"] = id; + target_map["title"] = delegate_->GetTargetTitle(id); + Escape(&target_map["title"]); + target_map["type"] = "node"; + // This attribute value is a "best effort" URL that is passed as a JSON + // string. It is not guaranteed to resolve to a valid resource. + target_map["url"] = delegate_->GetTargetUrl(id); + Escape(&target_map["url"]); + + bool connected = false; + for (const auto &session : connected_sessions_) { + if (session.second->IsForTarget(id)) { + connected = true; + break; + } + } + if (!connected) { + std::string host; + int port = SocketSession::ServerPortForClient(socket); + GetSocketHost(&socket->tcp, &host); + std::ostringstream frontend_url; + frontend_url << "devtools://devtools/bundled"; + frontend_url << "/js_app.html?experiments=true&v8only=true&ws="; + frontend_url << FormatWsAddress(host, port, id, false); + target_map["devtoolsFrontendUrl"] += frontend_url.str(); + target_map["webSocketDebuggerUrl"] = + FormatWsAddress(host, port, id, true); + } + } + SendHttpResponse(socket, MapsToString(response)); +} + +bool InspectorSocketServer::Start() { + CHECK_EQ(state_, ServerState::kNew); + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICSERV; + hints.ai_socktype = SOCK_STREAM; + uv_getaddrinfo_t req; + const std::string port_string = to_string(port_); + int err = uv_getaddrinfo(loop_, &req, nullptr, host_.c_str(), + port_string.c_str(), &hints); + if (err < 0) { + SE_LOGE("Unable to resolve \"%s\": %s\n", host_.c_str(), + uv_strerror(err)); + return false; + } + for (addrinfo *address = req.addrinfo; address != nullptr; + address = address->ai_next) { + err = ServerSocket::Listen(this, address->ai_addr, loop_); + } + uv_freeaddrinfo(req.addrinfo); + + if (!connected_sessions_.empty()) { + return true; + } + // We only show error if we failed to start server on all addresses. We only + // show one error, for the last address. + if (server_sockets_.empty()) { + SE_LOGE("Starting inspector on %s:%d failed: %s\n", + host_.c_str(), port_, uv_strerror(err)); + if (err == UV_EADDRINUSE) { + SE_LOGE("[FATAL ERROR]: Port [:%s] is occupied by other processes, try to kill the previous debug process or change the port number in `jsb_enable_debugger`.\n", port_string.c_str()); + } else { + SE_LOGE("[FATAL ERROR]: Failed to bind port [%s], error code: %d.\n", port_string.c_str(), err); + } + assert(false); //failed to start socket server for chrome debugger + return false; + } + state_ = ServerState::kRunning; + // getaddrinfo sorts the addresses, so the first port is most relevant. + PrintDebuggerReadyMessage(host_, server_sockets_[0]->port(), + delegate_->GetTargetIds(), out_); + return true; +} + +void InspectorSocketServer::Stop(ServerCallback cb) { + CHECK_EQ(state_, ServerState::kRunning); + if (closer_ == nullptr) { + closer_ = new Closer(this); + } + closer_->AddCallback(cb); + closer_->IncreaseExpectedCount(); + state_ = ServerState::kStopping; + for (ServerSocket *server_socket : server_sockets_) + server_socket->Close(); + closer_->NotifyIfDone(); +} + +void InspectorSocketServer::TerminateConnections() { + for (const auto &session : connected_sessions_) { + session.second->Close(); + } +} + +bool InspectorSocketServer::TargetExists(const std::string &id) { + const std::vector &target_ids = delegate_->GetTargetIds(); + const auto &found = std::find(target_ids.begin(), target_ids.end(), id); + return found != target_ids.end(); +} + +void InspectorSocketServer::Send(int session_id, const std::string &message) { + auto session_iterator = connected_sessions_.find(session_id); + if (session_iterator != connected_sessions_.end()) { + session_iterator->second->Send(message); + } +} + +void InspectorSocketServer::ServerSocketListening(ServerSocket *server_socket) { + server_sockets_.push_back(server_socket); +} + +void InspectorSocketServer::ServerSocketClosed(ServerSocket *server_socket) { + CHECK_EQ(state_, ServerState::kStopping); + + server_sockets_.erase(std::remove(server_sockets_.begin(), + server_sockets_.end(), server_socket), + server_sockets_.end()); + if (!server_sockets_.empty()) + return; + + if (closer_ != nullptr) { + closer_->DecreaseExpectedCount(); + } + if (connected_sessions_.empty()) { + delegate_->ServerDone(); + } + state_ = ServerState::kStopped; +} + +int InspectorSocketServer::Port() const { + if (!server_sockets_.empty()) { + return server_sockets_[0]->port(); + } + return port_; +} + +// InspectorSession tracking +SocketSession::SocketSession(InspectorSocketServer *server, int server_port) +: id_(server->GenerateSessionId()), + server_(server), + state_(State::kHttp), + server_port_(server_port) {} + +void SocketSession::Close() { + CHECK_NE(state_, State::kClosing); + state_ = State::kClosing; + inspector_close(&socket_, CloseCallback); +} + +// static +int SocketSession::Accept(InspectorSocketServer *server, int server_port, + uv_stream_t *server_socket) { + // Memory is freed when the socket closes. + SocketSession *session = new SocketSession(server, server_port); + int err = inspector_accept(server_socket, &session->socket_, + HandshakeCallback); + if (err != 0) { + delete session; + } + return err; +} + +// static +bool SocketSession::HandshakeCallback(InspectorSocket *socket, + inspector_handshake_event event, + const std::string &path) { + SocketSession *session = SocketSession::From(socket); + InspectorSocketServer *server = session->server_; + const std::string &id = path.empty() ? path : path.substr(1); + switch (event) { + case kInspectorHandshakeHttpGet: + return server->HandleGetRequest(socket, path); + case kInspectorHandshakeUpgrading: + if (server->SessionStarted(session, id)) { + session->SetTargetId(id); + return true; + } else { + session->SetDeclined(); + return false; + } + case kInspectorHandshakeUpgraded: + session->FrontendConnected(); + return true; + case kInspectorHandshakeFailed: + server->SessionTerminated(session); + return false; + default: + UNREACHABLE(); + return false; + } +} + +// static +void SocketSession::CloseCallback(InspectorSocket *socket, int code) { + SocketSession *session = SocketSession::From(socket); + CHECK_EQ(State::kClosing, session->state_); + session->server_->SessionTerminated(session); +} + +void SocketSession::FrontendConnected() { + CHECK_EQ(State::kHttp, state_); + state_ = State::kWebSocket; + inspector_read_start(&socket_, OnBufferAlloc, ReadCallback); +} + +// static +void SocketSession::ReadCallback(uv_stream_t *stream, ssize_t read, + const uv_buf_t *buf) { + InspectorSocket *socket = inspector_from_stream(stream); + SocketSession *session = SocketSession::From(socket); + if (read > 0) { + session->server_->MessageReceived(session->id_, + std::string(buf->base, read)); + } else { + session->Close(); + } + if (buf != nullptr && buf->base != nullptr) + delete[] buf->base; +} + +void SocketSession::Send(const std::string &message) { + inspector_write(&socket_, message.data(), message.length()); +} + +// ServerSocket implementation +int ServerSocket::DetectPort() { + sockaddr_storage addr; + int len = sizeof(addr); + int err = uv_tcp_getsockname(&tcp_socket_, + reinterpret_cast(&addr), &len); + if (err != 0) + return err; + int port; + if (addr.ss_family == AF_INET6) + port = reinterpret_cast(&addr)->sin6_port; + else + port = reinterpret_cast(&addr)->sin_port; + port_ = ntohs(port); + return err; +} + +// static +int ServerSocket::Listen(InspectorSocketServer *inspector_server, + sockaddr *addr, uv_loop_t *loop) { + ServerSocket *server_socket = new ServerSocket(inspector_server); + uv_tcp_t *server = &server_socket->tcp_socket_; + CHECK_EQ(0, uv_tcp_init(loop, server)); + int err = uv_tcp_bind(server, addr, 0); + if (err == 0) { + err = uv_listen(reinterpret_cast(server), 1, + ServerSocket::SocketConnectedCallback); + } + if (err == 0) { + err = server_socket->DetectPort(); + } + if (err == 0) { + inspector_server->ServerSocketListening(server_socket); + } else { + uv_close(reinterpret_cast(server), FreeOnCloseCallback); + } + return err; +} + +// static +void ServerSocket::SocketConnectedCallback(uv_stream_t *tcp_socket, + int status) { + if (status == 0) { + ServerSocket *server_socket = ServerSocket::FromTcpSocket(tcp_socket); + // Memory is freed when the socket closes. + SocketSession::Accept(server_socket->server_, server_socket->port_, + tcp_socket); + } +} + +// static +void ServerSocket::SocketClosedCallback(uv_handle_t *tcp_socket) { + ServerSocket *server_socket = ServerSocket::FromTcpSocket(tcp_socket); + server_socket->server_->ServerSocketClosed(server_socket); + delete server_socket; +} + +} // namespace inspector +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/inspector_socket_server.h b/cocos/bindings/jswrapper/v8/debugger/inspector_socket_server.h new file mode 100644 index 0000000..63618fc --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/inspector_socket_server.h @@ -0,0 +1,105 @@ +#ifndef SRC_INSPECTOR_SOCKET_SERVER_H_ +#define SRC_INSPECTOR_SOCKET_SERVER_H_ + +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "inspector_agent.h" + #include "inspector_socket.h" + #include "uv.h" + + #include + #include + #include + + #if !HAVE_INSPECTOR + #error("This header can only be used when inspector is enabled") + #endif + +namespace node { +namespace inspector { + +class Closer; +class SocketSession; +class ServerSocket; + +class SocketServerDelegate { +public: + virtual bool StartSession(int session_id, const std::string &target_id) = 0; + virtual void EndSession(int session_id) = 0; + virtual void MessageReceived(int session_id, const std::string &message) = 0; + virtual std::vector GetTargetIds() = 0; + virtual std::string GetTargetTitle(const std::string &id) = 0; + virtual std::string GetTargetUrl(const std::string &id) = 0; + virtual void ServerDone() = 0; +}; + +// HTTP Server, writes messages requested as TransportActions, and responds +// to HTTP requests and WS upgrades. + +class InspectorSocketServer { +public: + using ServerCallback = void (*)(InspectorSocketServer *); + InspectorSocketServer(SocketServerDelegate *delegate, + uv_loop_t *loop, + const std::string &host, + int port, + FILE *out = stderr); + // Start listening on host/port + bool Start(); + + // Called by the TransportAction sent with InspectorIo::Write(): + // kKill and kStop + void Stop(ServerCallback callback); + // kSendMessage + void Send(int session_id, const std::string &message); + // kKill + void TerminateConnections(); + + int Port() const; + + // Server socket lifecycle. There may be multiple sockets + void ServerSocketListening(ServerSocket *server_socket); + void ServerSocketClosed(ServerSocket *server_socket); + + // Session connection lifecycle + bool HandleGetRequest(InspectorSocket *socket, const std::string &path); + bool SessionStarted(SocketSession *session, const std::string &id); + void SessionTerminated(SocketSession *session); + void MessageReceived(int session_id, const std::string &message) { + delegate_->MessageReceived(session_id, message); + } + + int GenerateSessionId() { + return next_session_id_++; + } + +private: + void SendListResponse(InspectorSocket *socket); + bool TargetExists(const std::string &id); + + enum class ServerState { kNew, + kRunning, + kStopping, + kStopped }; + uv_loop_t *loop_; + SocketServerDelegate *const delegate_; + const std::string host_; + int port_; + std::string path_; + std::vector server_sockets_; + Closer *closer_; + std::map connected_sessions_; + int next_session_id_; + FILE *out_; + ServerState state_; + + friend class Closer; +}; + +} // namespace inspector +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + +#endif // SRC_INSPECTOR_SOCKET_SERVER_H_ diff --git a/cocos/bindings/jswrapper/v8/debugger/node.cpp b/cocos/bindings/jswrapper/v8/debugger/node.cpp new file mode 100644 index 0000000..523519d --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/node.cpp @@ -0,0 +1,1118 @@ +#include "node.h" + +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "env.h" + #include "http_parser.h" + #include "util.h" + + #include + #include + #include + #include + + #ifdef __POSIX__ + #include + #endif + + #define NODE_VERSION "JSB2.0" //cjh added + +static inline const char *errno_string(int errorno) { + #define ERRNO_CASE(e) \ + case e: return #e; + switch (errorno) { + #ifdef EACCES + ERRNO_CASE(EACCES); + #endif + + #ifdef EADDRINUSE + ERRNO_CASE(EADDRINUSE); + #endif + + #ifdef EADDRNOTAVAIL + ERRNO_CASE(EADDRNOTAVAIL); + #endif + + #ifdef EAFNOSUPPORT + ERRNO_CASE(EAFNOSUPPORT); + #endif + + #ifdef EAGAIN + ERRNO_CASE(EAGAIN); + #endif + + #ifdef EWOULDBLOCK + #if EAGAIN != EWOULDBLOCK + ERRNO_CASE(EWOULDBLOCK); + #endif + #endif + + #ifdef EALREADY + ERRNO_CASE(EALREADY); + #endif + + #ifdef EBADF + ERRNO_CASE(EBADF); + #endif + + #ifdef EBADMSG + ERRNO_CASE(EBADMSG); + #endif + + #ifdef EBUSY + ERRNO_CASE(EBUSY); + #endif + + #ifdef ECANCELED + ERRNO_CASE(ECANCELED); + #endif + + #ifdef ECHILD + ERRNO_CASE(ECHILD); + #endif + + #ifdef ECONNABORTED + ERRNO_CASE(ECONNABORTED); + #endif + + #ifdef ECONNREFUSED + ERRNO_CASE(ECONNREFUSED); + #endif + + #ifdef ECONNRESET + ERRNO_CASE(ECONNRESET); + #endif + + #ifdef EDEADLK + ERRNO_CASE(EDEADLK); + #endif + + #ifdef EDESTADDRREQ + ERRNO_CASE(EDESTADDRREQ); + #endif + + #ifdef EDOM + ERRNO_CASE(EDOM); + #endif + + #ifdef EDQUOT + ERRNO_CASE(EDQUOT); + #endif + + #ifdef EEXIST + ERRNO_CASE(EEXIST); + #endif + + #ifdef EFAULT + ERRNO_CASE(EFAULT); + #endif + + #ifdef EFBIG + ERRNO_CASE(EFBIG); + #endif + + #ifdef EHOSTUNREACH + ERRNO_CASE(EHOSTUNREACH); + #endif + + #ifdef EIDRM + ERRNO_CASE(EIDRM); + #endif + + #ifdef EILSEQ + ERRNO_CASE(EILSEQ); + #endif + + #ifdef EINPROGRESS + ERRNO_CASE(EINPROGRESS); + #endif + + #ifdef EINTR + ERRNO_CASE(EINTR); + #endif + + #ifdef EINVAL + ERRNO_CASE(EINVAL); + #endif + + #ifdef EIO + ERRNO_CASE(EIO); + #endif + + #ifdef EISCONN + ERRNO_CASE(EISCONN); + #endif + + #ifdef EISDIR + ERRNO_CASE(EISDIR); + #endif + + #ifdef ELOOP + ERRNO_CASE(ELOOP); + #endif + + #ifdef EMFILE + ERRNO_CASE(EMFILE); + #endif + + #ifdef EMLINK + ERRNO_CASE(EMLINK); + #endif + + #ifdef EMSGSIZE + ERRNO_CASE(EMSGSIZE); + #endif + + #ifdef EMULTIHOP + ERRNO_CASE(EMULTIHOP); + #endif + + #ifdef ENAMETOOLONG + ERRNO_CASE(ENAMETOOLONG); + #endif + + #ifdef ENETDOWN + ERRNO_CASE(ENETDOWN); + #endif + + #ifdef ENETRESET + ERRNO_CASE(ENETRESET); + #endif + + #ifdef ENETUNREACH + ERRNO_CASE(ENETUNREACH); + #endif + + #ifdef ENFILE + ERRNO_CASE(ENFILE); + #endif + + #ifdef ENOBUFS + ERRNO_CASE(ENOBUFS); + #endif + + #ifdef ENODATA + ERRNO_CASE(ENODATA); + #endif + + #ifdef ENODEV + ERRNO_CASE(ENODEV); + #endif + + #ifdef ENOENT + ERRNO_CASE(ENOENT); + #endif + + #ifdef ENOEXEC + ERRNO_CASE(ENOEXEC); + #endif + + #ifdef ENOLINK + ERRNO_CASE(ENOLINK); + #endif + + #ifdef ENOLCK + #if ENOLINK != ENOLCK + ERRNO_CASE(ENOLCK); + #endif + #endif + + #ifdef ENOMEM + ERRNO_CASE(ENOMEM); + #endif + + #ifdef ENOMSG + ERRNO_CASE(ENOMSG); + #endif + + #ifdef ENOPROTOOPT + ERRNO_CASE(ENOPROTOOPT); + #endif + + #ifdef ENOSPC + ERRNO_CASE(ENOSPC); + #endif + + #ifdef ENOSR + ERRNO_CASE(ENOSR); + #endif + + #ifdef ENOSTR + ERRNO_CASE(ENOSTR); + #endif + + #ifdef ENOSYS + ERRNO_CASE(ENOSYS); + #endif + + #ifdef ENOTCONN + ERRNO_CASE(ENOTCONN); + #endif + + #ifdef ENOTDIR + ERRNO_CASE(ENOTDIR); + #endif + + #ifdef ENOTEMPTY + #if ENOTEMPTY != EEXIST + ERRNO_CASE(ENOTEMPTY); + #endif + #endif + + #ifdef ENOTSOCK + ERRNO_CASE(ENOTSOCK); + #endif + + #ifdef ENOTSUP + ERRNO_CASE(ENOTSUP); + #else + #ifdef EOPNOTSUPP + ERRNO_CASE(EOPNOTSUPP); + #endif + #endif + + #ifdef ENOTTY + ERRNO_CASE(ENOTTY); + #endif + + #ifdef ENXIO + ERRNO_CASE(ENXIO); + #endif + + #ifdef EOVERFLOW + ERRNO_CASE(EOVERFLOW); + #endif + + #ifdef EPERM + ERRNO_CASE(EPERM); + #endif + + #ifdef EPIPE + ERRNO_CASE(EPIPE); + #endif + + #ifdef EPROTO + ERRNO_CASE(EPROTO); + #endif + + #ifdef EPROTONOSUPPORT + ERRNO_CASE(EPROTONOSUPPORT); + #endif + + #ifdef EPROTOTYPE + ERRNO_CASE(EPROTOTYPE); + #endif + + #ifdef ERANGE + ERRNO_CASE(ERANGE); + #endif + + #ifdef EROFS + ERRNO_CASE(EROFS); + #endif + + #ifdef ESPIPE + ERRNO_CASE(ESPIPE); + #endif + + #ifdef ESRCH + ERRNO_CASE(ESRCH); + #endif + + #ifdef ESTALE + ERRNO_CASE(ESTALE); + #endif + + #ifdef ETIME + ERRNO_CASE(ETIME); + #endif + + #ifdef ETIMEDOUT + ERRNO_CASE(ETIMEDOUT); + #endif + + #ifdef ETXTBSY + ERRNO_CASE(ETXTBSY); + #endif + + #ifdef EXDEV + ERRNO_CASE(EXDEV); + #endif + + default: return ""; + } +} + + #ifdef __POSIX__ +void RegisterSignalHandler(int signal, + void (*handler)(int signal), + bool reset_handler) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + #ifndef __FreeBSD__ + // FreeBSD has a nasty bug with SA_RESETHAND reseting the SA_SIGINFO, that is + // in turn set for a libthr wrapper. This leads to a crash. + // Work around the issue by manually setting SIG_DFL in the signal handler + sa.sa_flags = reset_handler ? SA_RESETHAND : 0; + #endif + sigfillset(&sa.sa_mask); + CHECK_EQ(sigaction(signal, &sa, nullptr), 0); +} + #endif // __POSIX__ + +using namespace v8; + +namespace node { + +static bool v8_is_profiling = false; + +Local ErrnoException(Isolate *isolate, + int errorno, + const char *syscall, + const char *msg, + const char *path) { + Environment *env = Environment::GetCurrent(isolate); + + Local e; + Local estring = OneByteString(env->isolate(), errno_string(errorno)); + if (msg == nullptr || msg[0] == '\0') { + msg = strerror(errorno); + } + Local message = OneByteString(env->isolate(), msg); + + Local cons = + String::Concat(env->isolate(), estring, FIXED_ONE_BYTE_STRING(env->isolate(), ", ")); + cons = String::Concat(env->isolate(), cons, message); + + Local path_string; + if (path != nullptr) { + // IDEA(bnoordhuis) It's questionable to interpret the file path as UTF-8. + path_string = String::NewFromUtf8(env->isolate(), path, v8::NewStringType::kNormal).ToLocalChecked(); + } + + if (path_string.IsEmpty() == false) { + cons = String::Concat(env->isolate(), cons, FIXED_ONE_BYTE_STRING(env->isolate(), " '")); + cons = String::Concat(env->isolate(), cons, path_string); + cons = String::Concat(env->isolate(), cons, FIXED_ONE_BYTE_STRING(env->isolate(), "'")); + } + e = Exception::Error(cons); + + Local obj = e->ToObject(env->context()).ToLocalChecked(); + obj->Set(env->context(), env->errno_string(), Integer::New(env->isolate(), errorno)).Check(); + obj->Set(env->context(), env->code_string(), estring).Check(); + + if (path_string.IsEmpty() == false) { + obj->Set(env->context(), env->path_string(), path_string).Check(); + } + + if (syscall != nullptr) { + obj->Set(env->context(), env->syscall_string(), OneByteString(env->isolate(), syscall)).Check(); + } + + return e; +} + +static Local StringFromPath(Isolate *isolate, const char *path) { + #ifdef _WIN32 + if (strncmp(path, "\\\\?\\UNC\\", 8) == 0) { + return String::Concat(isolate, FIXED_ONE_BYTE_STRING(isolate, "\\\\"), + String::NewFromUtf8(isolate, path + 8).ToLocalChecked()); + } else if (strncmp(path, "\\\\?\\", 4) == 0) { + return String::NewFromUtf8(isolate, path + 4).ToLocalChecked(); + } + #endif + + return String::NewFromUtf8(isolate, path, v8::NewStringType::kNormal).ToLocalChecked(); +} + +Local UVException(Isolate *isolate, + int errorno, + const char *syscall, + const char *msg, + const char *path) { + return UVException(isolate, errorno, syscall, msg, path, nullptr); +} + +Local UVException(Isolate *isolate, + int errorno, + const char *syscall, + const char *msg, + const char *path, + const char *dest) { + Environment *env = Environment::GetCurrent(isolate); + + if (!msg || !msg[0]) + msg = uv_strerror(errorno); + + Local js_code = OneByteString(isolate, uv_err_name(errorno)); + Local js_syscall = OneByteString(isolate, syscall); + Local js_path; + Local js_dest; + + Local js_msg = js_code; + js_msg = String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, ": ")); + js_msg = String::Concat(isolate, js_msg, OneByteString(isolate, msg)); + js_msg = String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, ", ")); + js_msg = String::Concat(isolate, js_msg, js_syscall); + + if (path != nullptr) { + js_path = StringFromPath(isolate, path); + + js_msg = String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, " '")); + js_msg = String::Concat(isolate, js_msg, js_path); + js_msg = String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, "'")); + } + + if (dest != nullptr) { + js_dest = StringFromPath(isolate, dest); + + js_msg = String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, " -> '")); + js_msg = String::Concat(isolate, js_msg, js_dest); + js_msg = String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, "'")); + } + + Local e = Exception::Error(js_msg)->ToObject(isolate->GetCurrentContext()).ToLocalChecked(); + + e->Set(isolate->GetCurrentContext(), env->errno_string(), Integer::New(isolate, errorno)).Check(); + e->Set(isolate->GetCurrentContext(), env->code_string(), js_code).Check(); + e->Set(isolate->GetCurrentContext(), env->syscall_string(), js_syscall).Check(); + if (!js_path.IsEmpty()) + e->Set(isolate->GetCurrentContext(), env->path_string(), js_path).Check(); + if (!js_dest.IsEmpty()) + e->Set(isolate->GetCurrentContext(), env->dest_string(), js_dest).Check(); + + return e; +} + +MaybeLocal MakeCallback(Environment *env, + Local recv, + const Local callback, + int argc, + Local argv[], + async_context asyncContext) { + // If you hit this assertion, you forgot to enter the v8::Context first. + CHECK_EQ(env->context(), env->isolate()->GetCurrentContext()); + + Local object; + + // Environment::AsyncCallbackScope callback_scope(env); + // bool disposed_domain = false; + // + // if (recv->IsObject()) { + // object = recv.As(); + // } + // + // if (env->using_domains()) { + // CHECK(recv->IsObject()); + // disposed_domain = DomainEnter(env, object); + // if (disposed_domain) return Undefined(env->isolate()); + // } + + MaybeLocal ret; + + // { + // AsyncHooks::ExecScope exec_scope(env, asyncContext.async_id, + // asyncContext.trigger_async_id); + // + // if (asyncContext.async_id != 0) { + // if (!AsyncWrap::EmitBefore(env, asyncContext.async_id)) + // return Local(); + // } + // + // ret = callback->Call(env->context(), recv, argc, argv); + // + // if (ret.IsEmpty()) { + // // NOTE: For backwards compatibility with public API we return Undefined() + // // if the top level call threw. + // return callback_scope.in_makecallback() ? + // ret : Undefined(env->isolate()); + // } + // + // if (asyncContext.async_id != 0) { + // if (!AsyncWrap::EmitAfter(env, asyncContext.async_id)) + // return Local(); + // } + // } + // + // if (env->using_domains()) { + // disposed_domain = DomainExit(env, object); + // if (disposed_domain) return Undefined(env->isolate()); + // } + // + // if (callback_scope.in_makecallback()) { + // return ret; + // } + // + // Environment::TickInfo* tick_info = env->tick_info(); + // + // if (tick_info->length() == 0) { + // env->isolate()->RunMicrotasks(); + // } + // + // // Make sure the stack unwound properly. If there are nested MakeCallback's + // // then it should return early and not reach this code. + // CHECK_EQ(env->current_async_id(), asyncContext.async_id); + // CHECK_EQ(env->trigger_id(), asyncContext.trigger_async_id); + // + // Local process = env->process_object(); + // + // if (tick_info->length() == 0) { + // tick_info->set_index(0); + // return ret; + // } + // + // if (env->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) { + // return Undefined(env->isolate()); + // } + + return ret; +} + +// Public MakeCallback()s + +MaybeLocal MakeCallback(Isolate *isolate, + Local recv, + const char *method, + int argc, + Local argv[], + async_context asyncContext) { + Local method_string = + String::NewFromUtf8(isolate, method, v8::NewStringType::kNormal) + .ToLocalChecked(); + return MakeCallback(isolate, recv, method_string, argc, argv, asyncContext); +} + +MaybeLocal MakeCallback(Isolate *isolate, + Local recv, + Local symbol, + int argc, + Local argv[], + async_context asyncContext) { + Local callback_v = recv->Get(isolate->GetCurrentContext(), symbol).ToLocalChecked(); + if (callback_v.IsEmpty()) return Local(); + if (!callback_v->IsFunction()) return Local(); + Local callback = callback_v.As(); + return MakeCallback(isolate, recv, callback, argc, argv, asyncContext); +} + +MaybeLocal MakeCallback(Isolate *isolate, + Local recv, + Local callback, + int argc, + Local argv[], + async_context asyncContext) { + // Observe the following two subtleties: + // + // 1. The environment is retrieved from the callback function's context. + // 2. The context to enter is retrieved from the environment. + // + // Because of the AssignToContext() call in src/node_contextify.cc, + // the two contexts need not be the same. + Environment *env = Environment::GetCurrent(callback->GetCreationContext().ToLocalChecked()); + Context::Scope context_scope(env->context()); + return MakeCallback(env, recv.As(), callback, argc, argv, + asyncContext); +} + +// Legacy MakeCallback()s + +Local MakeCallback(Isolate *isolate, + Local recv, + const char *method, + int argc, + Local *argv) { + EscapableHandleScope handle_scope(isolate); + return handle_scope.Escape( + MakeCallback(isolate, recv, method, argc, argv, {0, 0}) + .FromMaybe(Local())); +} + +Local MakeCallback(Isolate *isolate, + Local recv, + Local symbol, + int argc, + Local *argv) { + EscapableHandleScope handle_scope(isolate); + return handle_scope.Escape( + MakeCallback(isolate, recv, symbol, argc, argv, {0, 0}) + .FromMaybe(Local())); +} + +Local MakeCallback(Isolate *isolate, + Local recv, + Local callback, + int argc, + Local *argv) { + EscapableHandleScope handle_scope(isolate); + return handle_scope.Escape( + MakeCallback(isolate, recv, callback, argc, argv, {0, 0}) + .FromMaybe(Local())); +} + +IsolateData *CreateIsolateData(Isolate *isolate, uv_loop_t *loop) { + return new IsolateData(isolate, loop); +} + +void FreeIsolateData(IsolateData *isolate_data) { + delete isolate_data; +} + +Environment *CreateEnvironment(IsolateData *isolate_data, + Local context, + int argc, + const char *const *argv, + int exec_argc, + const char *const *exec_argv) { + Isolate *isolate = context->GetIsolate(); + HandleScope handle_scope(isolate); + Context::Scope context_scope(context); + auto env = new Environment(isolate_data, context); + env->Start(argc, argv, exec_argc, exec_argv, v8_is_profiling); + return env; +} + +void FreeEnvironment(Environment *env) { + delete env; +} + +NO_RETURN void Abort() { + DumpBacktrace(stderr); + fflush(stderr); + ABORT_NO_BACKTRACE(); +} + +NO_RETURN void Assert(const char *const (*args)[4]) { + auto filename = (*args)[0]; + auto linenum = (*args)[1]; + auto message = (*args)[2]; + auto function = (*args)[3]; + + char exepath[256]; + size_t exepath_size = sizeof(exepath); + if (uv_exepath(exepath, &exepath_size)) + snprintf(exepath, sizeof(exepath), "node"); + + char pid[12] = {0}; + #ifndef _WIN32 + snprintf(pid, sizeof(pid), "[%u]", getpid()); + #endif + + SE_LOGE("%s%s: %s:%s:%s%s Assertion `%s' failed.\n", + exepath, pid, filename, linenum, + function, *function ? ":" : "", message); + + Abort(); +} + +static void Abort(const FunctionCallbackInfo &args) { + Abort(); +} + + #define READONLY_PROPERTY(obj, str, var) \ + do { \ + obj->DefineOwnProperty(env->context(), \ + OneByteString(env->isolate(), str), \ + var, \ + v8::ReadOnly) \ + .FromJust(); \ + } while (0) + + #define READONLY_DONT_ENUM_PROPERTY(obj, str, var) \ + do { \ + obj->DefineOwnProperty(env->context(), \ + OneByteString(env->isolate(), str), \ + var, \ + static_cast(v8::ReadOnly | \ + v8::DontEnum)) \ + .FromJust(); \ + } while (0) + +static void ProcessTitleGetter(Local property, + const PropertyCallbackInfo &info) { + char buffer[512]; + uv_get_process_title(buffer, sizeof(buffer)); + info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), buffer, v8::NewStringType::kNormal).ToLocalChecked()); +} + +static void ProcessTitleSetter(Local property, + Local value, + const PropertyCallbackInfo &info) { + node::Utf8Value title(info.GetIsolate(), value); + // REFINE(piscisaureus): protect with a lock + uv_set_process_title(*title); +} + +void SetupProcessObject(Environment *env, + int argc, + const char *const *argv, + int exec_argc, + const char *const *exec_argv) { + HandleScope scope(env->isolate()); + + Local process = env->process_object(); + + auto title_string = FIXED_ONE_BYTE_STRING(env->isolate(), "title"); + CHECK(process->SetAccessor(env->context(), + title_string, + ProcessTitleGetter, + ProcessTitleSetter, + env->as_external()) + .FromJust()); + + // process.version + READONLY_PROPERTY(process, + "version", + FIXED_ONE_BYTE_STRING(env->isolate(), NODE_VERSION)); + + // process.moduleLoadList + READONLY_PROPERTY(process, + "moduleLoadList", + env->module_load_list_array()); + + // process.versions + Local versions = Object::New(env->isolate()); + READONLY_PROPERTY(process, "versions", versions); + + const char http_parser_version[] = NODE_STRINGIFY(HTTP_PARSER_VERSION_MAJOR) "." NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR) "." NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH); + READONLY_PROPERTY(versions, + "http_parser", + FIXED_ONE_BYTE_STRING(env->isolate(), http_parser_version)); + + // +1 to get rid of the leading 'v' + READONLY_PROPERTY(versions, + "node", + OneByteString(env->isolate(), NODE_VERSION)); + READONLY_PROPERTY(versions, + "v8", + OneByteString(env->isolate(), V8::GetVersion())); + READONLY_PROPERTY(versions, + "uv", + OneByteString(env->isolate(), uv_version_string())); + + SE_LOGD("libuv version: %s\n", uv_version_string()); + // READONLY_PROPERTY(versions, + // "zlib", + // FIXED_ONE_BYTE_STRING(env->isolate(), ZLIB_VERSION)); + // READONLY_PROPERTY(versions, + // "ares", + // FIXED_ONE_BYTE_STRING(env->isolate(), ARES_VERSION_STR)); + + // const char node_modules_version[] = NODE_STRINGIFY(NODE_MODULE_VERSION); + // READONLY_PROPERTY( + // versions, + // "modules", + // FIXED_ONE_BYTE_STRING(env->isolate(), node_modules_version)); + + // process._promiseRejectEvent + Local promiseRejectEvent = Object::New(env->isolate()); + READONLY_DONT_ENUM_PROPERTY(process, + "_promiseRejectEvent", + promiseRejectEvent); + READONLY_PROPERTY(promiseRejectEvent, + "unhandled", + Integer::New(env->isolate(), + v8::kPromiseRejectWithNoHandler)); + READONLY_PROPERTY(promiseRejectEvent, + "handled", + Integer::New(env->isolate(), + v8::kPromiseHandlerAddedAfterReject)); + + //#if HAVE_OPENSSL + // // Stupid code to slice out the version string. + // { // NOLINT(whitespace/braces) + // size_t i, j, k; + // int c; + // for (i = j = 0, k = sizeof(OPENSSL_VERSION_TEXT) - 1; i < k; ++i) { + // c = OPENSSL_VERSION_TEXT[i]; + // if ('0' <= c && c <= '9') { + // for (j = i + 1; j < k; ++j) { + // c = OPENSSL_VERSION_TEXT[j]; + // if (c == ' ') + // break; + // } + // break; + // } + // } + // READONLY_PROPERTY( + // versions, + // "openssl", + // OneByteString(env->isolate(), &OPENSSL_VERSION_TEXT[i], j - i)); + // } + //#endif + + // process.arch + READONLY_PROPERTY(process, "arch", OneByteString(env->isolate(), "x64")); //IDEA: cjh + + // process.platform + READONLY_PROPERTY(process, + "platform", + OneByteString(env->isolate(), "macOS")); //IDEA: cjh + + // process.release + Local release = Object::New(env->isolate()); + READONLY_PROPERTY(process, "release", release); + READONLY_PROPERTY(release, "name", OneByteString(env->isolate(), "node")); + + // if this is a release build and no explicit base has been set + // substitute the standard release download URL + #ifndef NODE_RELEASE_URLBASE + #if NODE_VERSION_IS_RELEASE + #define NODE_RELEASE_URLBASE "https://nodejs.org/download/release/" + #endif + #endif + + #if defined(NODE_RELEASE_URLBASE) + #define NODE_RELEASE_URLPFX NODE_RELEASE_URLBASE "v" NODE_VERSION_STRING "/" + #define NODE_RELEASE_URLFPFX NODE_RELEASE_URLPFX "node-v" NODE_VERSION_STRING + + READONLY_PROPERTY(release, + "sourceUrl", + OneByteString(env->isolate(), + NODE_RELEASE_URLFPFX ".tar.gz")); + READONLY_PROPERTY(release, + "headersUrl", + OneByteString(env->isolate(), + NODE_RELEASE_URLFPFX "-headers.tar.gz")); + #ifdef _WIN32 + READONLY_PROPERTY(release, + "libUrl", + OneByteString(env->isolate(), + strcmp(NODE_ARCH, "ia32") ? NODE_RELEASE_URLPFX "win-" NODE_ARCH "/node.lib" + : NODE_RELEASE_URLPFX + "win-x86/node.lib")); + #endif + #endif + + // process.argv + Local arguments = Array::New(env->isolate(), argc); + for (int i = 0; i < argc; ++i) { + arguments->Set(env->context(), i, String::NewFromUtf8(env->isolate(), argv[i], v8::NewStringType::kNormal).ToLocalChecked()).Check(); + } + process->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "argv"), arguments).Check(); + + // process.execArgv + Local exec_arguments = Array::New(env->isolate(), exec_argc); + for (int i = 0; i < exec_argc; ++i) { + exec_arguments->Set(env->context(), i, String::NewFromUtf8(env->isolate(), exec_argv[i], v8::NewStringType::kNormal).ToLocalChecked()).Check(); + } + process->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "execArgv"), + exec_arguments) + .Check(); + + // create process.env + // Local process_env_template = + // ObjectTemplate::New(env->isolate()); + // process_env_template->SetHandler(NamedPropertyHandlerConfiguration( + // EnvGetter, + // EnvSetter, + // EnvQuery, + // EnvDeleter, + // EnvEnumerator, + // env->as_external())); + // + // Local process_env = + // process_env_template->NewInstance(env->context()).ToLocalChecked(); + // process->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "env"), process_env); + // + // READONLY_PROPERTY(process, "pid", Integer::New(env->isolate(), getpid())); + // READONLY_PROPERTY(process, "features", GetFeatures(env)); + // + // auto need_immediate_callback_string = + // FIXED_ONE_BYTE_STRING(env->isolate(), "_needImmediateCallback"); + // CHECK(process->SetAccessor(env->context(), need_immediate_callback_string, + // NeedImmediateCallbackGetter, + // NeedImmediateCallbackSetter, + // env->as_external()).FromJust()); + // + // // -e, --eval + // if (eval_string) { + // READONLY_PROPERTY(process, + // "_eval", + // String::NewFromUtf8(env->isolate(), eval_string)); + // } + // + // // -p, --print + // if (print_eval) { + // READONLY_PROPERTY(process, "_print_eval", True(env->isolate())); + // } + // + // // -c, --check + // if (syntax_check_only) { + // READONLY_PROPERTY(process, "_syntax_check_only", True(env->isolate())); + // } + // + // // -i, --interactive + // if (force_repl) { + // READONLY_PROPERTY(process, "_forceRepl", True(env->isolate())); + // } + + // // -r, --require + // if (!preload_modules.empty()) { + // Local array = Array::New(env->isolate()); + // for (unsigned int i = 0; i < preload_modules.size(); ++i) { + // Local module = String::NewFromUtf8(env->isolate(), + // preload_modules[i].c_str()); + // array->Set(i, module); + // } + // READONLY_PROPERTY(process, + // "_preload_modules", + // array); + // + // preload_modules.clear(); + // } + // + // // --no-deprecation + // if (no_deprecation) { + // READONLY_PROPERTY(process, "noDeprecation", True(env->isolate())); + // } + // + // // --no-warnings + // if (no_process_warnings) { + // READONLY_PROPERTY(process, "noProcessWarnings", True(env->isolate())); + // } + // + // // --trace-warnings + // if (trace_warnings) { + // READONLY_PROPERTY(process, "traceProcessWarnings", True(env->isolate())); + // } + // + // // --throw-deprecation + // if (throw_deprecation) { + // READONLY_PROPERTY(process, "throwDeprecation", True(env->isolate())); + // } + // + //#ifdef NODE_NO_BROWSER_GLOBALS + // // configure --no-browser-globals + // READONLY_PROPERTY(process, "_noBrowserGlobals", True(env->isolate())); + //#endif // NODE_NO_BROWSER_GLOBALS + // + // // --prof-process + // if (prof_process) { + // READONLY_PROPERTY(process, "profProcess", True(env->isolate())); + // } + // + // // --trace-deprecation + // if (trace_deprecation) { + // READONLY_PROPERTY(process, "traceDeprecation", True(env->isolate())); + // } + // + // // REFINE(refack): move the following 3 to `node_config` + // // --inspect-brk + // if (debug_options.wait_for_connect()) { + // READONLY_DONT_ENUM_PROPERTY(process, + // "_breakFirstLine", True(env->isolate())); + // } + // + // // --inspect --debug-brk + // if (debug_options.deprecated_invocation()) { + // READONLY_DONT_ENUM_PROPERTY(process, + // "_deprecatedDebugBrk", True(env->isolate())); + // } + // + // // --debug or, --debug-brk without --inspect + // if (debug_options.invalid_invocation()) { + // READONLY_DONT_ENUM_PROPERTY(process, + // "_invalidDebug", True(env->isolate())); + // } + // + // // --security-revert flags + //#define V(code, _, __) \ +//do { \ +//if (IsReverted(REVERT_ ## code)) { \ +//READONLY_PROPERTY(process, "REVERT_" #code, True(env->isolate())); \ +//} \ +//} while (0); + // REVERSIONS(V) + //#undef V + // + // size_t exec_path_len = 2 * PATH_MAX; + // char* exec_path = new char[exec_path_len]; + // Local exec_path_value; + // if (uv_exepath(exec_path, &exec_path_len) == 0) { + // exec_path_value = String::NewFromUtf8(env->isolate(), + // exec_path, + // String::kNormalString, + // exec_path_len); + // } else { + // exec_path_value = String::NewFromUtf8(env->isolate(), argv[0]); + // } + // process->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "execPath"), + // exec_path_value); + // delete[] exec_path; + // + // auto debug_port_string = FIXED_ONE_BYTE_STRING(env->isolate(), "debugPort"); + // CHECK(process->SetAccessor(env->context(), + // debug_port_string, + // DebugPortGetter, + // DebugPortSetter, + // env->as_external()).FromJust()); + // + // // define various internal methods + // env->SetMethod(process, + // "_startProfilerIdleNotifier", + // StartProfilerIdleNotifier); + // env->SetMethod(process, + // "_stopProfilerIdleNotifier", + // StopProfilerIdleNotifier); + // env->SetMethod(process, "_getActiveRequests", GetActiveRequests); + // env->SetMethod(process, "_getActiveHandles", GetActiveHandles); + // env->SetMethod(process, "reallyExit", Exit); + // env->SetMethod(process, "abort", Abort); + // env->SetMethod(process, "chdir", Chdir); + // env->SetMethod(process, "cwd", Cwd); + // + // env->SetMethod(process, "umask", Umask); + // + //#if defined(__POSIX__) && !defined(__ANDROID__) + // env->SetMethod(process, "getuid", GetUid); + // env->SetMethod(process, "geteuid", GetEUid); + // env->SetMethod(process, "setuid", SetUid); + // env->SetMethod(process, "seteuid", SetEUid); + // + // env->SetMethod(process, "setgid", SetGid); + // env->SetMethod(process, "setegid", SetEGid); + // env->SetMethod(process, "getgid", GetGid); + // env->SetMethod(process, "getegid", GetEGid); + // + // env->SetMethod(process, "getgroups", GetGroups); + // env->SetMethod(process, "setgroups", SetGroups); + // env->SetMethod(process, "initgroups", InitGroups); + //#endif // __POSIX__ && !defined(__ANDROID__) + // + // env->SetMethod(process, "_kill", Kill); + // + // env->SetMethod(process, "_debugProcess", DebugProcess); + // env->SetMethod(process, "_debugPause", DebugPause); + // env->SetMethod(process, "_debugEnd", DebugEnd); + // + // env->SetMethod(process, "hrtime", Hrtime); + // + // env->SetMethod(process, "cpuUsage", CPUUsage); + // + // env->SetMethod(process, "dlopen", DLOpen); + // + // env->SetMethod(process, "uptime", Uptime); + // env->SetMethod(process, "memoryUsage", MemoryUsage); + // + // env->SetMethod(process, "binding", Binding); + // env->SetMethod(process, "_linkedBinding", LinkedBinding); + // + // env->SetMethod(process, "_setupProcessObject", SetupProcessObject); + // env->SetMethod(process, "_setupNextTick", SetupNextTick); + // env->SetMethod(process, "_setupPromises", SetupPromises); + // env->SetMethod(process, "_setupDomainUse", SetupDomainUse); + + // pre-set _events object for faster emit checks + Local events_obj = Object::New(env->isolate()); + CHECK(events_obj->SetPrototype(env->context(), + Null(env->isolate())) + .FromJust()); + process->Set(env->context(), env->events_string(), events_obj).Check(); +} + + #undef READONLY_PROPERTY + +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/node.h b/cocos/bindings/jswrapper/v8/debugger/node.h new file mode 100644 index 0000000..e12fdc9 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/node.h @@ -0,0 +1,160 @@ +#pragma once + +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "v8.h" + + #include + + #ifdef _WIN32 + #ifndef BUILDING_NODE_EXTENSION + #define NODE_EXTERN __declspec(dllexport) + #else + #define NODE_EXTERN __declspec(dllimport) + #endif + #else + #define NODE_EXTERN /* nothing */ + #endif + + #include + #include + + #ifndef NODE_STRINGIFY + #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n) + #define NODE_STRINGIFY_HELPER(n) #n + #endif + + // The arraysize(arr) macro returns the # of elements in an array arr. + // The expression is a compile-time constant, and therefore can be + // used in defining new arrays, for example. If you use arraysize on + // a pointer by mistake, you will get a compile-time error. + #define arraysize(array) (sizeof(ArraySizeHelper(array))) + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template +char (&ArraySizeHelper(T (&array)[N]))[N]; + + #if !V8_CC_MSVC +// That gcc wants both of these prototypes seems mysterious. VC, for +// its part, can't decide which to use (another mystery). Matching of +// template overloads: the final frontier. +template +char (&ArraySizeHelper(const T (&array)[N]))[N]; + #endif + + #ifdef __POSIX__ +void RegisterSignalHandler(int signal, + void (*handler)(int signal), + bool reset_handler = false); + #endif // __POSIX__ + +namespace node { + +NODE_EXTERN v8::Local ErrnoException(v8::Isolate *isolate, + int errorno, + const char *syscall = NULL, + const char *message = NULL, + const char *path = NULL); +NODE_EXTERN v8::Local UVException(v8::Isolate *isolate, + int errorno, + const char *syscall = NULL, + const char *message = NULL, + const char *path = NULL); +NODE_EXTERN v8::Local UVException(v8::Isolate *isolate, + int errorno, + const char *syscall, + const char *message, + const char *path, + const char *dest); + +typedef double async_id; +struct async_context { + ::node::async_id async_id; + ::node::async_id trigger_async_id; +}; + +/* An API specific to emit before/after callbacks is unnecessary because + * MakeCallback will automatically call them for you. + * + * These methods may create handles on their own, so run them inside a + * HandleScope. + * + * `asyncId` and `triggerAsyncId` should correspond to the values returned by + * `EmitAsyncInit()` and `AsyncHooksGetTriggerAsyncId()`, respectively, when the + * invoking resource was created. If these values are unknown, 0 can be passed. + * */ + +v8::MaybeLocal MakeCallback(v8::Isolate *isolate, + v8::Local recv, + v8::Local callback, + int argc, + v8::Local *argv, + async_context asyncContext); + +v8::MaybeLocal MakeCallback(v8::Isolate *isolate, + v8::Local recv, + const char *method, + int argc, + v8::Local *argv, + async_context asyncContext); + +v8::MaybeLocal MakeCallback(v8::Isolate *isolate, + v8::Local recv, + v8::Local symbol, + int argc, + v8::Local *argv, + async_context asyncContext); + +/* + * These methods need to be called in a HandleScope. + * + * It is preferred that you use the `MakeCallback` overloads taking + * `async_id` arguments. + */ + +v8::Local MakeCallback( + v8::Isolate *isolate, + v8::Local recv, + const char *method, + int argc, + v8::Local *argv); +v8::Local MakeCallback( + v8::Isolate *isolate, + v8::Local recv, + v8::Local symbol, + int argc, + v8::Local *argv); +v8::Local MakeCallback( + v8::Isolate *isolate, + v8::Local recv, + v8::Local callback, + int argc, + v8::Local *argv); + +class IsolateData; +class Environment; + +NODE_EXTERN IsolateData *CreateIsolateData(v8::Isolate *isolate, + struct uv_loop_s *loop); +NODE_EXTERN void FreeIsolateData(IsolateData *isolate_data); + +NODE_EXTERN Environment *CreateEnvironment(IsolateData *isolate_data, + v8::Local context, + int argc, + const char *const *argv, + int exec_argc, + const char *const *exec_argv); +NODE_EXTERN void FreeEnvironment(Environment *env); + +void SetupProcessObject(Environment *env, + int argc, + const char *const *argv, + int exec_argc, + const char *const *exec_argv); + +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/node_debug_options.cpp b/cocos/bindings/jswrapper/v8/debugger/node_debug_options.cpp new file mode 100644 index 0000000..760c1f3 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/node_debug_options.cpp @@ -0,0 +1,141 @@ +#include "node_debug_options.h" + +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include + #include + #include + #include "util.h" + +namespace node { + +namespace { +const int default_inspector_port = 9229; + +inline std::string remove_brackets(const std::string &host) { + if (!host.empty() && host.front() == '[' && host.back() == ']') + return host.substr(1, host.size() - 2); + else + return host; +} + +int parse_and_validate_port(const std::string &port) { + char *endptr; + errno = 0; + const long result = strtol(port.c_str(), &endptr, 10); // NOLINT(runtime/int) + if (errno != 0 || *endptr != '\0' || + (result != 0 && result < 1024) || result > 65535) { + SE_LOGE("Debug port must be 0 or in range 1024 to 65535.\n"); + exit(12); + } + return static_cast(result); +} + +std::pair split_host_port(const std::string &arg) { + // remove_brackets only works if no port is specified + // so if it has an effect only an IPv6 address was specified + std::string host = remove_brackets(arg); + if (host.length() < arg.length()) + return {host, -1}; + + size_t colon = arg.rfind(':'); + if (colon == std::string::npos) { + // Either a port number or a host name. Assume that + // if it's not all decimal digits, it's a host name. + for (char c : arg) { + if (c < '0' || c > '9') { + return {arg, -1}; + } + } + return {"", parse_and_validate_port(arg)}; + } + // host and port found + return std::make_pair(remove_brackets(arg.substr(0, colon)), + parse_and_validate_port(arg.substr(colon + 1))); +} + +} // namespace + +DebugOptions::DebugOptions() : inspector_enabled_(false), + deprecated_debug_(false), + break_first_line_(false), + host_name_("127.0.0.1"), + port_(-1) {} + +bool DebugOptions::ParseOption(const char *argv0, const std::string &option) { + bool has_argument = false; + std::string option_name; + std::string argument; + + auto pos = option.find("="); + if (pos == std::string::npos) { + option_name = option; + } else { + option_name = option.substr(0, pos); + argument = option.substr(pos + 1); + + if (argument.length() > 0) + has_argument = true; + else + argument.clear(); + } + + // Note that --debug-port and --debug-brk in conjunction with --inspect + // work but are undocumented. + // --debug is no longer valid. + // Ref: https://github.com/nodejs/node/issues/12630 + // Ref: https://github.com/nodejs/node/pull/12949 + if (option_name == "--inspect") { + inspector_enabled_ = true; + } else if (option_name == "--debug") { + deprecated_debug_ = true; + } else if (option_name == "--inspect-brk") { + inspector_enabled_ = true; + break_first_line_ = true; + } else if (option_name == "--debug-brk") { + break_first_line_ = true; + deprecated_debug_ = true; + } else if (option_name == "--debug-port" || + option_name == "--inspect-port") { + if (!has_argument) { + SE_LOGE("%s: %s requires an argument\n", + argv0, option.c_str()); + exit(9); + } + } else { + return false; + } + + #if !HAVE_INSPECTOR + if (inspector_enabled_) { + SE_LOGE("Inspector support is not available with this Node.js build\n"); + } + inspector_enabled_ = false; + return false; + #endif + + // argument can be specified for *any* option to specify host:port + if (has_argument) { + std::pair host_port = split_host_port(argument); + if (!host_port.first.empty()) { + host_name_ = host_port.first; + } + if (host_port.second >= 0) { + port_ = host_port.second; + } + } + + return true; +} + +int DebugOptions::port() const { + int port = port_; + if (port < 0) { + port = default_inspector_port; + } + return port; +} + +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/node_debug_options.h b/cocos/bindings/jswrapper/v8/debugger/node_debug_options.h new file mode 100644 index 0000000..4a3245c --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/node_debug_options.h @@ -0,0 +1,45 @@ +#ifndef SRC_NODE_DEBUG_OPTIONS_H_ +#define SRC_NODE_DEBUG_OPTIONS_H_ + +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include + +// Forward declaration to break recursive dependency chain with src/env.h. +namespace node { + +class DebugOptions { +public: + DebugOptions(); + bool ParseOption(const char *argv0, const std::string &option); + void set_inspector_enabled(bool enabled) { inspector_enabled_ = enabled; } + bool inspector_enabled() const { return inspector_enabled_; } + bool deprecated_invocation() const { + return deprecated_debug_ && + inspector_enabled_ && + break_first_line_; + } + bool invalid_invocation() const { + return deprecated_debug_ && !inspector_enabled_; + } + void set_wait_for_connect(bool wait) { break_first_line_ = wait; } + bool wait_for_connect() const { return break_first_line_; } + std::string host_name() const { return host_name_; } + void set_host_name(std::string host_name) { host_name_ = host_name; } + int port() const; + void set_port(int port) { port_ = port; } + +private: + bool inspector_enabled_; + bool deprecated_debug_; + bool break_first_line_; + std::string host_name_; + int port_; +}; + +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + +#endif // SRC_NODE_DEBUG_OPTIONS_H_ diff --git a/cocos/bindings/jswrapper/v8/debugger/node_mutex.h b/cocos/bindings/jswrapper/v8/debugger/node_mutex.h new file mode 100644 index 0000000..0a56dbb --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/node_mutex.h @@ -0,0 +1,196 @@ +#ifndef SRC_NODE_MUTEX_H_ +#define SRC_NODE_MUTEX_H_ + +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #include "util.h" + #include "uv.h" + +namespace node { + +template +class ConditionVariableBase; +template +class MutexBase; +struct LibuvMutexTraits; + +using ConditionVariable = ConditionVariableBase; +using Mutex = MutexBase; + +template +class MutexBase { +public: + inline MutexBase(); + inline ~MutexBase(); + inline void Lock(); + inline void Unlock(); + + class ScopedLock; + class ScopedUnlock; + + class ScopedLock { + public: + inline explicit ScopedLock(const MutexBase &mutex); + inline explicit ScopedLock(const ScopedUnlock &scoped_unlock); + inline ~ScopedLock(); + + private: + template + friend class ConditionVariableBase; + friend class ScopedUnlock; + const MutexBase &mutex_; + NODE_DISALLOW_COPY_AND_ASSIGN(ScopedLock); + }; + + class ScopedUnlock { + public: + inline explicit ScopedUnlock(const ScopedLock &scoped_lock); + inline ~ScopedUnlock(); + + private: + friend class ScopedLock; + const MutexBase &mutex_; + NODE_DISALLOW_COPY_AND_ASSIGN(ScopedUnlock); + }; + +private: + template + friend class ConditionVariableBase; + mutable typename Traits::MutexT mutex_; + NODE_DISALLOW_COPY_AND_ASSIGN(MutexBase); +}; + +template +class ConditionVariableBase { +public: + using ScopedLock = typename MutexBase::ScopedLock; + + inline ConditionVariableBase(); + inline ~ConditionVariableBase(); + inline void Broadcast(const ScopedLock &); + inline void Signal(const ScopedLock &); + inline void Wait(const ScopedLock &scoped_lock); + +private: + typename Traits::CondT cond_; + NODE_DISALLOW_COPY_AND_ASSIGN(ConditionVariableBase); +}; + +struct LibuvMutexTraits { + using CondT = uv_cond_t; + using MutexT = uv_mutex_t; + + static inline int cond_init(CondT *cond) { + return uv_cond_init(cond); + } + + static inline int mutex_init(MutexT *mutex) { + return uv_mutex_init(mutex); + } + + static inline void cond_broadcast(CondT *cond) { + uv_cond_broadcast(cond); + } + + static inline void cond_destroy(CondT *cond) { + uv_cond_destroy(cond); + } + + static inline void cond_signal(CondT *cond) { + uv_cond_signal(cond); + } + + static inline void cond_wait(CondT *cond, MutexT *mutex) { + uv_cond_wait(cond, mutex); + } + + static inline void mutex_destroy(MutexT *mutex) { + uv_mutex_destroy(mutex); + } + + static inline void mutex_lock(MutexT *mutex) { + uv_mutex_lock(mutex); + } + + static inline void mutex_unlock(MutexT *mutex) { + uv_mutex_unlock(mutex); + } +}; + +template +ConditionVariableBase::ConditionVariableBase() { + CHECK_EQ(0, Traits::cond_init(&cond_)); +} + +template +ConditionVariableBase::~ConditionVariableBase() { + Traits::cond_destroy(&cond_); +} + +template +void ConditionVariableBase::Broadcast(const ScopedLock &) { + Traits::cond_broadcast(&cond_); +} + +template +void ConditionVariableBase::Signal(const ScopedLock &) { + Traits::cond_signal(&cond_); +} + +template +void ConditionVariableBase::Wait(const ScopedLock &scoped_lock) { + Traits::cond_wait(&cond_, &scoped_lock.mutex_.mutex_); +} + +template +MutexBase::MutexBase() { + CHECK_EQ(0, Traits::mutex_init(&mutex_)); +} + +template +MutexBase::~MutexBase() { + Traits::mutex_destroy(&mutex_); +} + +template +void MutexBase::Lock() { + Traits::mutex_lock(&mutex_); +} + +template +void MutexBase::Unlock() { + Traits::mutex_unlock(&mutex_); +} + +template +MutexBase::ScopedLock::ScopedLock(const MutexBase &mutex) +: mutex_(mutex) { + Traits::mutex_lock(&mutex_.mutex_); +} + +template +MutexBase::ScopedLock::ScopedLock(const ScopedUnlock &scoped_unlock) +: MutexBase(scoped_unlock.mutex_) {} + +template +MutexBase::ScopedLock::~ScopedLock() { + Traits::mutex_unlock(&mutex_.mutex_); +} + +template +MutexBase::ScopedUnlock::ScopedUnlock(const ScopedLock &scoped_lock) +: mutex_(scoped_lock.mutex_) { + Traits::mutex_unlock(&mutex_.mutex_); +} + +template +MutexBase::ScopedUnlock::~ScopedUnlock() { + Traits::mutex_lock(&mutex_.mutex_); +} + +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + +#endif // SRC_NODE_MUTEX_H_ diff --git a/cocos/bindings/jswrapper/v8/debugger/util-inl.h b/cocos/bindings/jswrapper/v8/debugger/util-inl.h new file mode 100644 index 0000000..c52c738 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/util-inl.h @@ -0,0 +1,432 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef SRC_UTIL_INL_H_ +#define SRC_UTIL_INL_H_ + +#ifndef NODE_UTIL_H_INCLUDE + #error "util-inl.h could only be included in util.h" +#endif + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + + #include "util.h" + #include + + #if defined(_MSC_VER) + #include + #define BSWAP_2(x) _byteswap_ushort(x) + #define BSWAP_4(x) _byteswap_ulong(x) + #define BSWAP_8(x) _byteswap_uint64(x) + #else + #define BSWAP_2(x) ((x) << 8) | ((x) >> 8) + #define BSWAP_4(x) \ + (((x)&0xFF) << 24) | \ + (((x)&0xFF00) << 8) | \ + (((x) >> 8) & 0xFF00) | \ + (((x) >> 24) & 0xFF) + #define BSWAP_8(x) \ + (((x)&0xFF00000000000000ull) >> 56) | \ + (((x)&0x00FF000000000000ull) >> 40) | \ + (((x)&0x0000FF0000000000ull) >> 24) | \ + (((x)&0x000000FF00000000ull) >> 8) | \ + (((x)&0x00000000FF000000ull) << 8) | \ + (((x)&0x0000000000FF0000ull) << 24) | \ + (((x)&0x000000000000FF00ull) << 40) | \ + (((x)&0x00000000000000FFull) << 56) + #endif + +namespace node { + +template +ListNode::ListNode() : prev_(this), + next_(this) {} + +template +ListNode::~ListNode() { + Remove(); +} + +template +void ListNode::Remove() { + prev_->next_ = next_; + next_->prev_ = prev_; + prev_ = this; + next_ = this; +} + +template +bool ListNode::IsEmpty() const { + return prev_ == this; +} + +template (T::*M)> +ListHead::Iterator::Iterator(ListNode *node) : node_(node) {} + +template (T::*M)> +T *ListHead::Iterator::operator*() const { + return ContainerOf(M, node_); +} + +template (T::*M)> +const typename ListHead::Iterator & +ListHead::Iterator::operator++() { + node_ = node_->next_; + return *this; +} + +template (T::*M)> +bool ListHead::Iterator::operator!=(const Iterator &that) const { + return node_ != that.node_; +} + +template (T::*M)> +ListHead::~ListHead() { + while (IsEmpty() == false) + head_.next_->Remove(); +} + +template (T::*M)> +void ListHead::MoveBack(ListHead *that) { + if (IsEmpty()) + return; + ListNode *to = &that->head_; + head_.next_->prev_ = to->prev_; + to->prev_->next_ = head_.next_; + head_.prev_->next_ = to; + to->prev_ = head_.prev_; + head_.prev_ = &head_; + head_.next_ = &head_; +} + +template (T::*M)> +void ListHead::PushBack(T *element) { + ListNode *that = &(element->*M); + head_.prev_->next_ = that; + that->prev_ = head_.prev_; + that->next_ = &head_; + head_.prev_ = that; +} + +template (T::*M)> +void ListHead::PushFront(T *element) { + ListNode *that = &(element->*M); + head_.next_->prev_ = that; + that->prev_ = &head_; + that->next_ = head_.next_; + head_.next_ = that; +} + +template (T::*M)> +bool ListHead::IsEmpty() const { + return head_.IsEmpty(); +} + +template (T::*M)> +T *ListHead::PopFront() { + if (IsEmpty()) + return nullptr; + ListNode *node = head_.next_; + node->Remove(); + return ContainerOf(M, node); +} + +template (T::*M)> +typename ListHead::Iterator ListHead::begin() const { + return Iterator(head_.next_); +} + +template (T::*M)> +typename ListHead::Iterator ListHead::end() const { + return Iterator(const_cast *>(&head_)); +} + +template +ContainerOfHelper::ContainerOfHelper(Inner Outer::*field, + Inner *pointer) +: pointer_(reinterpret_cast( + reinterpret_cast(pointer) - + reinterpret_cast(&(static_cast(0)->*field)))) { +} + +template +template +ContainerOfHelper::operator TypeName *() const { + return static_cast(pointer_); +} + +template +inline ContainerOfHelper ContainerOf(Inner Outer::*field, + Inner *pointer) { + return ContainerOfHelper(field, pointer); +} + +template +inline v8::Local PersistentToLocal( + v8::Isolate *isolate, + const v8::Persistent &persistent) { + if (persistent.IsWeak()) { + return WeakPersistentToLocal(isolate, persistent); + } else { + return StrongPersistentToLocal(persistent); + } +} + +template +inline v8::Local StrongPersistentToLocal( + const v8::Persistent &persistent) { + return *reinterpret_cast *>( + const_cast *>(&persistent)); +} + +template +inline v8::Local WeakPersistentToLocal( + v8::Isolate *isolate, + const v8::Persistent &persistent) { + return v8::Local::New(isolate, persistent); +} + +inline v8::Local OneByteString(v8::Isolate *isolate, + const char *data, + int length) { + return v8::String::NewFromOneByte(isolate, + reinterpret_cast(data), + v8::NewStringType::kNormal, + length) + .ToLocalChecked(); +} + +inline v8::Local OneByteString(v8::Isolate *isolate, + const signed char *data, + int length) { + return v8::String::NewFromOneByte(isolate, + reinterpret_cast(data), + v8::NewStringType::kNormal, + length) + .ToLocalChecked(); +} + +inline v8::Local OneByteString(v8::Isolate *isolate, + const unsigned char *data, + int length) { + return v8::String::NewFromOneByte(isolate, + reinterpret_cast(data), + v8::NewStringType::kNormal, + length) + .ToLocalChecked(); +} + +template +void Wrap(v8::Local object, TypeName *pointer) { + CHECK_EQ(false, object.IsEmpty()); + CHECK_GT(object->InternalFieldCount(), 0); + object->SetAlignedPointerInInternalField(0, pointer); +} + +void ClearWrap(v8::Local object) { + Wrap(object, nullptr); +} + +template +TypeName *Unwrap(v8::Local object) { + CHECK_EQ(false, object.IsEmpty()); + CHECK_GT(object->InternalFieldCount(), 0); + void *pointer = object->GetAlignedPointerFromInternalField(0); + return static_cast(pointer); +} + +void SwapBytes16(char *data, size_t nbytes) { + CHECK_EQ(nbytes % 2, 0); + + #if defined(_MSC_VER) + int align = reinterpret_cast(data) % sizeof(uint16_t); + if (align == 0) { + // MSVC has no strict aliasing, and is able to highly optimize this case. + uint16_t *data16 = reinterpret_cast(data); + size_t len16 = nbytes / sizeof(*data16); + for (size_t i = 0; i < len16; i++) { + data16[i] = BSWAP_2(data16[i]); + } + return; + } + #endif + + uint16_t temp; + for (size_t i = 0; i < nbytes; i += sizeof(temp)) { + memcpy(&temp, &data[i], sizeof(temp)); + temp = BSWAP_2(temp); + memcpy(&data[i], &temp, sizeof(temp)); + } +} + +void SwapBytes32(char *data, size_t nbytes) { + CHECK_EQ(nbytes % 4, 0); + + #if defined(_MSC_VER) + int align = reinterpret_cast(data) % sizeof(uint32_t); + // MSVC has no strict aliasing, and is able to highly optimize this case. + if (align == 0) { + uint32_t *data32 = reinterpret_cast(data); + size_t len32 = nbytes / sizeof(*data32); + for (size_t i = 0; i < len32; i++) { + data32[i] = BSWAP_4(data32[i]); + } + return; + } + #endif + + uint32_t temp; + for (size_t i = 0; i < nbytes; i += sizeof(temp)) { + memcpy(&temp, &data[i], sizeof(temp)); + temp = BSWAP_4(temp); + memcpy(&data[i], &temp, sizeof(temp)); + } +} + +void SwapBytes64(char *data, size_t nbytes) { + CHECK_EQ(nbytes % 8, 0); + + #if defined(_MSC_VER) + int align = reinterpret_cast(data) % sizeof(uint64_t); + if (align == 0) { + // MSVC has no strict aliasing, and is able to highly optimize this case. + uint64_t *data64 = reinterpret_cast(data); + size_t len64 = nbytes / sizeof(*data64); + for (size_t i = 0; i < len64; i++) { + data64[i] = BSWAP_8(data64[i]); + } + return; + } + #endif + + uint64_t temp; + for (size_t i = 0; i < nbytes; i += sizeof(temp)) { + memcpy(&temp, &data[i], sizeof(temp)); + temp = BSWAP_8(temp); + memcpy(&data[i], &temp, sizeof(temp)); + } +} + +char ToLower(char c) { + return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; +} + +bool StringEqualNoCase(const char *a, const char *b) { + do { + if (*a == '\0') + return *b == '\0'; + if (*b == '\0') + return *a == '\0'; + } while (ToLower(*a++) == ToLower(*b++)); + return false; +} + +bool StringEqualNoCaseN(const char *a, const char *b, size_t length) { + for (size_t i = 0; i < length; i++) { + if (ToLower(a[i]) != ToLower(b[i])) + return false; + if (a[i] == '\0') + return true; + } + return true; +} + +inline size_t MultiplyWithOverflowCheck(size_t a, size_t b) { + size_t ret = a * b; + if (a != 0) + CHECK_EQ(b, ret / a); + + return ret; +} + +// These should be used in our code as opposed to the native +// versions as they abstract out some platform and or +// compiler version specific functionality. +// malloc(0) and realloc(ptr, 0) have implementation-defined behavior in +// that the standard allows them to either return a unique pointer or a +// nullptr for zero-sized allocation requests. Normalize by always using +// a nullptr. +template +T *UncheckedRealloc(T *pointer, size_t n) { + size_t full_size = MultiplyWithOverflowCheck(sizeof(T), n); + + if (full_size == 0) { + free(pointer); + return nullptr; + } + + void *allocated = realloc(pointer, full_size); + + if (UNLIKELY(allocated == nullptr)) { + // Tell V8 that memory is low and retry. + LowMemoryNotification(); + allocated = realloc(pointer, full_size); + } + + return static_cast(allocated); +} + +// As per spec realloc behaves like malloc if passed nullptr. +template +inline T *UncheckedMalloc(size_t n) { + if (n == 0) n = 1; + return UncheckedRealloc(nullptr, n); +} + +template +inline T *UncheckedCalloc(size_t n) { + if (n == 0) n = 1; + MultiplyWithOverflowCheck(sizeof(T), n); + return static_cast(calloc(n, sizeof(T))); +} + +template +inline T *Realloc(T *pointer, size_t n) { + T *ret = UncheckedRealloc(pointer, n); + if (n > 0) CHECK_NE(ret, nullptr); + return ret; +} + +template +inline T *Malloc(size_t n) { + T *ret = UncheckedMalloc(n); + if (n > 0) CHECK_NE(ret, nullptr); + return ret; +} + +template +inline T *Calloc(size_t n) { + T *ret = UncheckedCalloc(n); + if (n > 0) CHECK_NE(ret, nullptr); + return ret; +} + +// Shortcuts for char*. +inline char *Malloc(size_t n) { return Malloc(n); } +inline char *Calloc(size_t n) { return Calloc(n); } +inline char *UncheckedMalloc(size_t n) { return UncheckedMalloc(n); } +inline char *UncheckedCalloc(size_t n) { return UncheckedCalloc(n); } + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_UTIL_INL_H_ diff --git a/cocos/bindings/jswrapper/v8/debugger/util.cpp b/cocos/bindings/jswrapper/v8/debugger/util.cpp new file mode 100644 index 0000000..3ef3013 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/util.cpp @@ -0,0 +1,117 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "util.h" + +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + //cjh #include "string_bytes.h" + //#include "node_buffer.h" + //#include "node_internals.h" + #include + +namespace node { + +using v8::Isolate; +using v8::Local; +using v8::String; +using v8::Value; + +template +static void MakeUtf8String(Isolate *isolate, + Local value, + T *target) { + Local string; + if (!value->ToString(isolate->GetCurrentContext()).ToLocal(&string)) + return; + + const size_t storage = 3 * string->Length() + 1; + target->AllocateSufficientStorage(storage); + const int flags = + String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8; + const int length = string->WriteUtf8(isolate, target->out(), (int)storage, 0, flags); + target->SetLengthAndZeroTerminate(length); +} + +Utf8Value::Utf8Value(Isolate *isolate, Local value) { + if (value.IsEmpty()) + return; + + MakeUtf8String(isolate, value, this); +} + +TwoByteValue::TwoByteValue(Isolate *isolate, Local value) { + if (value.IsEmpty()) { + return; + } + + Local string; + if (!value->ToString(isolate->GetCurrentContext()).ToLocal(&string)) + if (string.IsEmpty()) + return; + + // Allocate enough space to include the null terminator + const size_t storage = string->Length() + 1; + AllocateSufficientStorage(storage); + + const int flags = String::NO_NULL_TERMINATION; + const int length = string->Write(isolate, out(), 0, (int)storage, flags); + SetLengthAndZeroTerminate(length); +} + +BufferValue::BufferValue(Isolate *isolate, Local value) { + // Slightly different take on Utf8Value. If value is a String, + // it will return a Utf8 encoded string. If value is a Buffer, + // it will copy the data out of the Buffer as is. + if (value.IsEmpty()) { + // Dereferencing this object will return nullptr. + Invalidate(); + return; + } + + if (value->IsString()) { + MakeUtf8String(isolate, value, this); + //cjh } else if (Buffer::HasInstance(value)) { + // const size_t len = Buffer::Length(value); + // // Leave place for the terminating '\0' byte. + // AllocateSufficientStorage(len + 1); + // memcpy(out(), Buffer::Data(value), len); + // SetLengthAndZeroTerminate(len); + } else { + Invalidate(); + } +} + +void LowMemoryNotification() { + // if (v8_initialized) { + auto isolate = v8::Isolate::GetCurrent(); + if (isolate != nullptr) { + isolate->LowMemoryNotification(); + } + // } +} + +void DumpBacktrace(FILE *fp) { +} + +} // namespace node + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR diff --git a/cocos/bindings/jswrapper/v8/debugger/util.h b/cocos/bindings/jswrapper/v8/debugger/util.h new file mode 100644 index 0000000..8987033 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/util.h @@ -0,0 +1,449 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef SRC_UTIL_H_ +#define SRC_UTIL_H_ + +#include "../../config.h" +#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + + #define NODE_WANT_INTERNALS 1 //cjh added + + #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + + #include "v8.h" + + #include + #include + #include + #include + #include + #include + + #include // std::remove_reference + +namespace node { + +// These should be used in our code as opposed to the native +// versions as they abstract out some platform and or +// compiler version specific functionality +// malloc(0) and realloc(ptr, 0) have implementation-defined behavior in +// that the standard allows them to either return a unique pointer or a +// nullptr for zero-sized allocation requests. Normalize by always using +// a nullptr. +template +inline T *UncheckedRealloc(T *pointer, size_t n); +template +inline T *UncheckedMalloc(size_t n); +template +inline T *UncheckedCalloc(size_t n); + +// Same things, but aborts immediately instead of returning nullptr when +// no memory is available. +template +inline T *Realloc(T *pointer, size_t n); +template +inline T *Malloc(size_t n); +template +inline T *Calloc(size_t n); + +inline char *Malloc(size_t n); +inline char *Calloc(size_t n); +inline char *UncheckedMalloc(size_t n); +inline char *UncheckedCalloc(size_t n); + +// Used by the allocation functions when allocation fails. +// Thin wrapper around v8::Isolate::LowMemoryNotification() that checks +// whether V8 is initialized. +void LowMemoryNotification(); + + #ifdef __GNUC__ + #define NO_RETURN __attribute__((noreturn)) + #else + #define NO_RETURN + #endif + +// The slightly odd function signature for Assert() is to ease +// instruction cache pressure in calls from CHECK. +NO_RETURN void Abort(); +NO_RETURN void Assert(const char *const (*args)[4]); +void DumpBacktrace(FILE *fp); + +template +using remove_reference = std::remove_reference; + + #define FIXED_ONE_BYTE_STRING(isolate, string) \ + (node::OneByteString((isolate), (string), sizeof(string) - 1)) + + #define NODE_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + void operator=(const TypeName &) = delete; \ + void operator=(TypeName &&) = delete; \ + TypeName(const TypeName &) = delete; \ + TypeName(TypeName &&) = delete + + // Windows 8+ does not like abort() in Release mode + #ifdef _WIN32 + #define ABORT_NO_BACKTRACE() raise(SIGABRT) + #else + #define ABORT_NO_BACKTRACE() abort() + #endif + + #define ABORT() node::Abort() + + #ifdef __GNUC__ + #define LIKELY(expr) __builtin_expect(!!(expr), 1) + #define UNLIKELY(expr) __builtin_expect(!!(expr), 0) + #define PRETTY_FUNCTION_NAME __PRETTY_FUNCTION__ + #else + #define LIKELY(expr) expr + #define UNLIKELY(expr) expr + #define PRETTY_FUNCTION_NAME "" + #endif + + #define STRINGIFY_(x) #x + #define STRINGIFY(x) STRINGIFY_(x) + + #define CHECK(expr) \ + do { \ + if (UNLIKELY(!(expr))) { \ + static const char *const args[] = {__FILE__, STRINGIFY(__LINE__), \ + #expr, PRETTY_FUNCTION_NAME}; \ + node::Assert(&args); \ + } \ + } while (0) + + #define CHECK_EQ(a, b) CHECK((a) == (b)) + #define CHECK_GE(a, b) CHECK((a) >= (b)) + #define CHECK_GT(a, b) CHECK((a) > (b)) + #define CHECK_LE(a, b) CHECK((a) <= (b)) + #define CHECK_LT(a, b) CHECK((a) < (b)) + #define CHECK_NE(a, b) CHECK((a) != (b)) + + #define UNREACHABLE() ABORT() + + #define ASSIGN_OR_RETURN_UNWRAP(ptr, obj, ...) \ + do { \ + *ptr = \ + Unwrap::type>(obj); \ + if (*ptr == nullptr) \ + return __VA_ARGS__; \ + } while (0) + +// TAILQ-style intrusive list node. +template +class ListNode; + +// TAILQ-style intrusive list head. +template (T::*M)> +class ListHead; + +template +class ListNode { +public: + inline ListNode(); + inline ~ListNode(); + inline void Remove(); + inline bool IsEmpty() const; + +private: + template (U::*M)> + friend class ListHead; + ListNode *prev_; + ListNode *next_; + NODE_DISALLOW_COPY_AND_ASSIGN(ListNode); +}; + +template (T::*M)> +class ListHead { +public: + class Iterator { + public: + inline T *operator*() const; + inline const Iterator &operator++(); + inline bool operator!=(const Iterator &that) const; + + private: + friend class ListHead; + inline explicit Iterator(ListNode *node); + ListNode *node_; + }; + + inline ListHead() = default; + inline ~ListHead(); + inline void MoveBack(ListHead *that); + inline void PushBack(T *element); + inline void PushFront(T *element); + inline bool IsEmpty() const; + inline T *PopFront(); + inline Iterator begin() const; + inline Iterator end() const; + +private: + ListNode head_; + NODE_DISALLOW_COPY_AND_ASSIGN(ListHead); +}; + +// The helper is for doing safe downcasts from base types to derived types. +template +class ContainerOfHelper { +public: + inline ContainerOfHelper(Inner Outer::*field, Inner *pointer); + template + inline operator TypeName *() const; + +private: + Outer *const pointer_; +}; + +// Calculate the address of the outer (i.e. embedding) struct from +// the interior pointer to a data member. +template +inline ContainerOfHelper ContainerOf(Inner Outer::*field, + Inner *pointer); + +// If persistent.IsWeak() == false, then do not call persistent.Reset() +// while the returned Local is still in scope, it will destroy the +// reference to the object. +template +inline v8::Local PersistentToLocal( + v8::Isolate *isolate, + const v8::Persistent &persistent); + +// Unchecked conversion from a non-weak Persistent to Local, +// use with care! +// +// Do not call persistent.Reset() while the returned Local is still in +// scope, it will destroy the reference to the object. +template +inline v8::Local StrongPersistentToLocal( + const v8::Persistent &persistent); + +template +inline v8::Local WeakPersistentToLocal( + v8::Isolate *isolate, + const v8::Persistent &persistent); + +// Convenience wrapper around v8::String::NewFromOneByte(). +inline v8::Local OneByteString(v8::Isolate *isolate, + const char *data, + int length = -1); + +// For the people that compile with -funsigned-char. +inline v8::Local OneByteString(v8::Isolate *isolate, + const signed char *data, + int length = -1); + +inline v8::Local OneByteString(v8::Isolate *isolate, + const unsigned char *data, + int length = -1); + +inline void Wrap(v8::Local object, void *pointer); + +inline void ClearWrap(v8::Local object); + +template +inline TypeName *Unwrap(v8::Local object); + +// Swaps bytes in place. nbytes is the number of bytes to swap and must be a +// multiple of the word size (checked by function). +inline void SwapBytes16(char *data, size_t nbytes); +inline void SwapBytes32(char *data, size_t nbytes); +inline void SwapBytes64(char *data, size_t nbytes); + +// tolower() is locale-sensitive. Use ToLower() instead. +inline char ToLower(char c); + +// strcasecmp() is locale-sensitive. Use StringEqualNoCase() instead. +inline bool StringEqualNoCase(const char *a, const char *b); + +// strncasecmp() is locale-sensitive. Use StringEqualNoCaseN() instead. +inline bool StringEqualNoCaseN(const char *a, const char *b, size_t length); + +// Allocates an array of member type T. For up to kStackStorageSize items, +// the stack is used, otherwise malloc(). +template +class MaybeStackBuffer { +public: + const T *out() const { + return buf_; + } + + T *out() { + return buf_; + } + + // operator* for compatibility with `v8::String::(Utf8)Value` + T *operator*() { + return buf_; + } + + const T *operator*() const { + return buf_; + } + + T &operator[](size_t index) { + CHECK_LT(index, length()); + return buf_[index]; + } + + const T &operator[](size_t index) const { + CHECK_LT(index, length()); + return buf_[index]; + } + + size_t length() const { + return length_; + } + + // Current maximum capacity of the buffer with which SetLength() can be used + // without first calling AllocateSufficientStorage(). + size_t capacity() const { + return IsAllocated() ? capacity_ : IsInvalidated() ? 0 : kStackStorageSize; + } + + // Make sure enough space for `storage` entries is available. + // This method can be called multiple times throughout the lifetime of the + // buffer, but once this has been called Invalidate() cannot be used. + // Content of the buffer in the range [0, length()) is preserved. + void AllocateSufficientStorage(size_t storage) { + CHECK(!IsInvalidated()); + if (storage > capacity()) { + bool was_allocated = IsAllocated(); + T *allocated_ptr = was_allocated ? buf_ : nullptr; + buf_ = Realloc(allocated_ptr, storage); + capacity_ = storage; + if (!was_allocated && length_ > 0) + memcpy(buf_, buf_st_, length_ * sizeof(buf_[0])); + } + + length_ = storage; + } + + void SetLength(size_t length) { + // capacity() returns how much memory is actually available. + CHECK_LE(length, capacity()); + length_ = length; + } + + void SetLengthAndZeroTerminate(size_t length) { + // capacity() returns how much memory is actually available. + CHECK_LE(length + 1, capacity()); + SetLength(length); + + // T() is 0 for integer types, nullptr for pointers, etc. + buf_[length] = T(); + } + + // Make derefencing this object return nullptr. + // This method can be called multiple times throughout the lifetime of the + // buffer, but once this has been called AllocateSufficientStorage() cannot + // be used. + void Invalidate() { + CHECK(!IsAllocated()); + length_ = 0; + buf_ = nullptr; + } + + // If the buffer is stored in the heap rather than on the stack. + bool IsAllocated() const { + return !IsInvalidated() && buf_ != buf_st_; + } + + // If Invalidate() has been called. + bool IsInvalidated() const { + return buf_ == nullptr; + } + + // Release ownership of the malloc'd buffer. + // Note: This does not free the buffer. + void Release() { + CHECK(IsAllocated()); + buf_ = buf_st_; + length_ = 0; + capacity_ = 0; + } + + MaybeStackBuffer() : length_(0), + capacity_(0), + buf_(buf_st_) { + // Default to a zero-length, null-terminated buffer. + buf_[0] = T(); + } + + explicit MaybeStackBuffer(size_t storage) : MaybeStackBuffer() { + AllocateSufficientStorage(storage); + } + + ~MaybeStackBuffer() { + if (IsAllocated()) + free(buf_); + } + +private: + size_t length_; + // capacity of the malloc'ed buf_ + size_t capacity_; + T *buf_; + T buf_st_[kStackStorageSize]; +}; + +class Utf8Value : public MaybeStackBuffer { +public: + explicit Utf8Value(v8::Isolate *isolate, v8::Local value); +}; + +class TwoByteValue : public MaybeStackBuffer { +public: + explicit TwoByteValue(v8::Isolate *isolate, v8::Local value); +}; + +class BufferValue : public MaybeStackBuffer { +public: + explicit BufferValue(v8::Isolate *isolate, v8::Local value); +}; + + #define THROW_AND_RETURN_UNLESS_BUFFER(env, obj) \ + do { \ + if (!Buffer::HasInstance(obj)) \ + return env->ThrowTypeError("argument should be a Buffer"); \ + } while (0) + + #define SPREAD_BUFFER_ARG(val, name) \ + CHECK((val)->IsArrayBufferView()); \ + v8::Local name = (val).As(); \ + v8::ArrayBuffer::Contents name##_c = name->Buffer()->GetContents(); \ + const size_t name##_offset = name->ByteOffset(); \ + const size_t name##_length = name->ByteLength(); \ + char *const name##_data = \ + static_cast(name##_c.Data()) + name##_offset; \ + if (name##_length > 0) \ + CHECK_NE(name##_data, nullptr); + +} // namespace node + + #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + + #define NODE_UTIL_H_INCLUDE + #include "util-inl.h" + +#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR + +#endif // SRC_UTIL_H_ diff --git a/cocos/bindings/jswrapper/v8/debugger/v8_inspector_protocol_json.h b/cocos/bindings/jswrapper/v8/debugger/v8_inspector_protocol_json.h new file mode 100644 index 0000000..2b4fc46 --- /dev/null +++ b/cocos/bindings/jswrapper/v8/debugger/v8_inspector_protocol_json.h @@ -0,0 +1,501 @@ +0, 205, 180, 120, 218, 237, 125, 107, 111, 220, 56, 182, 224, 95, 33, 140, 11, 196, 1, 42, + 158, 153, 187, 192, 221, 197, 220, 139, 5, 18, 39, 153, 246, 69, 30, 134, 157, 204, 44, 176, + 200, 7, 149, 196, 114, 177, 163, 146, 106, 69, 201, 142, 103, 208, 255, 125, 207, 139, 15, 73, + 148, 74, 101, 187, 95, 51, 1, 26, 105, 151, 68, 145, 135, 228, 225, 121, 159, 195, 127, 156, + 20, 245, 46, 51, 149, 61, 249, 243, 255, 253, 199, 73, 94, 239, 118, 89, 85, 240, 143, 70, + 183, 93, 35, 47, 76, 171, 119, 240, 215, 63, 78, 254, 173, 209, 155, 147, 63, 159, 188, 166, + 143, 78, 126, 90, 157, 180, 247, 123, 13, 15, 178, 166, 201, 238, 79, 86, 39, 85, 182, 195, + 159, 174, 211, 213, 73, 161, 109, 222, 152, 125, 107, 234, 10, 158, 191, 51, 182, 85, 245, 70, + 217, 110, 191, 175, 155, 86, 23, 74, 90, 158, 157, 252, 244, 101, 117, 178, 133, 193, 75, 221, + 224, 152, 39, 235, 166, 190, 179, 186, 129, 46, 26, 93, 21, 186, 129, 63, 191, 248, 254, 111, + 116, 251, 122, 98, 136, 43, 6, 123, 106, 8, 254, 1, 237, 174, 243, 173, 222, 101, 163, 207, + 47, 155, 250, 214, 192, 35, 101, 170, 77, 221, 236, 50, 124, 172, 178, 117, 221, 181, 170, 221, + 106, 181, 111, 234, 182, 206, 235, 82, 89, 250, 252, 236, 132, 87, 128, 87, 9, 94, 238, 117, + 211, 26, 249, 41, 75, 99, 219, 198, 84, 55, 97, 109, 232, 127, 195, 97, 121, 54, 10, 223, + 1, 156, 171, 201, 143, 111, 97, 117, 240, 139, 137, 239, 229, 53, 79, 85, 186, 168, 215, 63, + 234, 188, 133, 47, 76, 17, 118, 110, 244, 125, 248, 133, 251, 211, 155, 42, 47, 25, 246, 137, + 128, 77, 226, 72, 191, 195, 55, 183, 89, 217, 241, 234, 53, 218, 118, 101, 123, 22, 38, 193, + 15, 224, 183, 96, 211, 149, 222, 213, 173, 254, 200, 112, 226, 24, 131, 174, 190, 229, 154, 33, + 43, 116, 155, 153, 210, 98, 87, 53, 61, 201, 202, 147, 63, 183, 77, 167, 125, 215, 218, 181, + 125, 205, 77, 195, 32, 111, 134, 111, 126, 10, 232, 164, 25, 88, 220, 150, 125, 214, 192, 179, + 150, 145, 112, 114, 27, 244, 183, 61, 76, 34, 185, 19, 111, 252, 43, 213, 214, 202, 245, 204, + 155, 58, 4, 122, 162, 119, 222, 177, 191, 52, 117, 183, 31, 117, 127, 125, 191, 91, 215, 165, + 201, 213, 13, 190, 38, 132, 129, 221, 202, 90, 149, 103, 149, 90, 107, 213, 89, 192, 120, 24, + 184, 209, 165, 206, 172, 86, 59, 88, 105, 179, 47, 181, 226, 78, 237, 44, 32, 235, 186, 134, + 175, 170, 0, 137, 169, 242, 178, 43, 244, 57, 239, 249, 59, 83, 233, 151, 151, 23, 9, 228, + 129, 245, 218, 193, 75, 171, 238, 182, 26, 112, 167, 81, 242, 133, 194, 79, 20, 124, 163, 236, + 182, 238, 202, 2, 33, 204, 110, 97, 249, 179, 53, 128, 84, 116, 56, 111, 66, 54, 237, 209, + 229, 56, 0, 173, 41, 117, 213, 142, 32, 186, 168, 20, 191, 81, 187, 186, 128, 222, 221, 222, + 91, 24, 12, 232, 74, 229, 134, 14, 195, 170, 172, 209, 170, 170, 91, 88, 56, 33, 27, 8, + 126, 81, 211, 179, 125, 6, 203, 10, 189, 232, 188, 35, 16, 213, 71, 56, 104, 13, 145, 137, + 255, 202, 97, 128, 255, 109, 117, 123, 137, 109, 62, 86, 30, 205, 254, 235, 15, 244, 70, 217, + 214, 111, 255, 96, 39, 247, 58, 55, 27, 67, 164, 6, 214, 205, 228, 219, 48, 130, 202, 235, + 170, 213, 223, 90, 220, 73, 32, 41, 72, 137, 226, 37, 82, 23, 114, 68, 29, 178, 42, 99, + 85, 189, 51, 45, 194, 221, 95, 78, 117, 103, 202, 18, 151, 93, 186, 129, 6, 48, 28, 182, + 113, 67, 200, 113, 7, 10, 9, 0, 97, 7, 251, 236, 70, 207, 28, 49, 249, 238, 162, 136, + 207, 150, 192, 125, 238, 223, 29, 179, 137, 76, 69, 94, 221, 255, 21, 128, 30, 147, 198, 191, + 9, 74, 33, 144, 76, 57, 112, 182, 112, 2, 25, 90, 88, 33, 68, 42, 245, 223, 215, 31, + 63, 8, 146, 243, 129, 8, 24, 103, 17, 17, 214, 247, 10, 23, 69, 31, 135, 95, 55, 186, + 210, 13, 108, 224, 101, 163, 111, 141, 190, 131, 23, 56, 112, 99, 118, 208, 101, 248, 58, 13, + 240, 158, 191, 137, 0, 113, 189, 21, 10, 182, 34, 154, 208, 113, 48, 1, 158, 53, 127, 209, + 22, 214, 76, 31, 5, 79, 192, 174, 0, 81, 219, 104, 130, 39, 67, 44, 52, 173, 161, 31, + 176, 84, 56, 134, 67, 148, 207, 23, 199, 193, 151, 221, 101, 166, 5, 62, 186, 51, 118, 122, + 55, 71, 192, 224, 71, 180, 44, 123, 254, 82, 54, 22, 22, 168, 46, 111, 117, 225, 113, 94, + 80, 0, 176, 54, 66, 114, 64, 8, 60, 167, 153, 146, 97, 87, 202, 180, 207, 44, 162, 184, + 53, 40, 59, 120, 52, 169, 20, 28, 220, 186, 17, 89, 32, 197, 177, 52, 225, 150, 35, 225, + 240, 223, 77, 89, 175, 179, 82, 80, 139, 87, 98, 154, 237, 201, 248, 110, 95, 213, 223, 240, + 244, 225, 137, 65, 238, 220, 232, 31, 25, 103, 9, 17, 149, 217, 248, 185, 222, 193, 250, 187, + 183, 79, 200, 40, 113, 8, 160, 63, 249, 87, 248, 183, 201, 114, 141, 235, 228, 201, 239, 147, + 115, 209, 193, 190, 15, 56, 233, 128, 64, 23, 128, 176, 72, 253, 154, 72, 220, 192, 15, 163, + 217, 203, 19, 158, 110, 76, 110, 226, 101, 248, 103, 38, 53, 79, 64, 86, 70, 88, 254, 178, + 40, 148, 136, 217, 196, 92, 28, 2, 154, 118, 171, 110, 204, 173, 174, 252, 35, 153, 160, 41, + 14, 225, 252, 121, 6, 56, 254, 219, 23, 242, 114, 0, 243, 109, 87, 229, 248, 250, 99, 117, + 60, 130, 186, 13, 175, 21, 246, 164, 54, 210, 149, 66, 145, 101, 32, 186, 29, 64, 214, 9, + 177, 207, 245, 248, 90, 231, 37, 128, 214, 38, 229, 252, 240, 206, 193, 229, 1, 17, 200, 120, + 187, 188, 198, 150, 216, 171, 172, 185, 233, 144, 97, 156, 5, 40, 241, 249, 75, 121, 60, 173, + 214, 185, 15, 109, 98, 111, 102, 198, 177, 103, 234, 37, 146, 193, 222, 51, 144, 76, 65, 23, + 92, 235, 178, 70, 17, 176, 166, 169, 88, 148, 102, 255, 27, 40, 212, 53, 117, 165, 238, 234, + 166, 36, 222, 132, 47, 91, 248, 84, 183, 61, 58, 252, 79, 41, 37, 254, 106, 164, 140, 101, + 208, 239, 98, 211, 119, 177, 233, 24, 177, 9, 79, 186, 13, 68, 40, 98, 36, 69, 76, 171, + 120, 77, 248, 133, 28, 97, 197, 68, 81, 52, 217, 122, 51, 192, 85, 83, 193, 132, 13, 237, + 58, 64, 55, 69, 2, 230, 172, 84, 151, 108, 143, 185, 119, 198, 141, 186, 153, 38, 109, 158, + 93, 245, 103, 39, 32, 6, 203, 78, 207, 58, 227, 250, 24, 146, 21, 224, 41, 149, 23, 25, + 163, 143, 213, 105, 93, 149, 247, 110, 170, 160, 161, 35, 254, 193, 154, 91, 93, 110, 158, 207, + 48, 60, 35, 61, 94, 6, 3, 211, 106, 52, 219, 139, 126, 163, 222, 172, 127, 13, 126, 11, + 59, 213, 131, 247, 193, 236, 150, 183, 56, 94, 198, 13, 162, 226, 131, 120, 110, 56, 117, 19, + 115, 5, 178, 223, 131, 122, 0, 38, 192, 134, 173, 5, 38, 27, 3, 197, 172, 12, 249, 5, + 109, 178, 176, 180, 254, 38, 175, 232, 188, 193, 43, 248, 201, 6, 54, 132, 75, 229, 91, 54, + 176, 29, 1, 103, 150, 231, 160, 162, 212, 77, 0, 246, 35, 140, 186, 136, 176, 141, 38, 225, + 250, 234, 97, 42, 31, 100, 221, 194, 134, 253, 193, 210, 255, 158, 211, 196, 254, 83, 57, 108, + 140, 155, 7, 190, 136, 93, 194, 153, 213, 6, 233, 213, 111, 145, 107, 216, 20, 21, 187, 26, + 239, 39, 224, 97, 246, 51, 16, 44, 79, 111, 200, 62, 247, 209, 25, 103, 31, 113, 58, 168, + 163, 163, 206, 67, 98, 250, 212, 9, 42, 161, 216, 210, 75, 4, 129, 152, 59, 53, 32, 5, + 190, 179, 84, 46, 179, 157, 46, 178, 110, 214, 241, 74, 139, 85, 124, 26, 106, 86, 64, 34, + 200, 45, 235, 101, 65, 186, 116, 59, 73, 29, 246, 103, 210, 85, 23, 155, 191, 1, 23, 5, + 32, 223, 214, 205, 107, 189, 238, 110, 110, 200, 239, 208, 31, 236, 147, 70, 62, 23, 172, 100, + 240, 87, 155, 85, 57, 49, 92, 232, 3, 85, 109, 224, 196, 168, 200, 223, 113, 103, 132, 118, + 133, 116, 71, 48, 180, 160, 138, 111, 123, 131, 235, 10, 53, 241, 177, 13, 153, 30, 91, 145, + 50, 137, 164, 108, 198, 198, 65, 96, 212, 40, 134, 224, 19, 16, 61, 118, 112, 148, 8, 105, + 89, 214, 212, 3, 147, 220, 57, 75, 44, 78, 222, 212, 183, 40, 255, 43, 56, 61, 149, 96, + 177, 27, 9, 240, 21, 228, 68, 2, 192, 217, 18, 145, 126, 57, 51, 34, 9, 133, 102, 183, + 211, 5, 10, 61, 64, 231, 112, 150, 58, 35, 227, 165, 177, 212, 195, 8, 208, 222, 148, 11, + 99, 147, 115, 126, 205, 207, 151, 79, 122, 216, 107, 158, 53, 5, 76, 22, 228, 30, 253, 166, + 2, 156, 75, 144, 239, 215, 220, 10, 37, 156, 178, 228, 109, 140, 132, 126, 20, 229, 115, 238, + 128, 76, 214, 168, 175, 8, 191, 79, 209, 33, 167, 91, 232, 246, 28, 52, 153, 122, 199, 103, + 225, 45, 57, 141, 224, 12, 240, 30, 22, 147, 199, 98, 68, 253, 100, 205, 197, 205, 50, 173, + 110, 95, 20, 142, 8, 240, 211, 25, 198, 205, 13, 98, 74, 112, 237, 158, 252, 42, 26, 120, + 189, 219, 131, 190, 197, 48, 60, 189, 175, 69, 186, 159, 245, 159, 217, 186, 107, 114, 253, 249, + 234, 221, 152, 244, 208, 27, 213, 53, 165, 8, 189, 176, 177, 158, 115, 184, 149, 78, 114, 104, + 103, 174, 66, 223, 155, 109, 253, 236, 166, 172, 253, 119, 145, 102, 38, 32, 23, 50, 64, 196, + 189, 164, 55, 93, 60, 133, 235, 64, 122, 7, 50, 245, 235, 186, 14, 244, 216, 79, 48, 235, + 67, 24, 43, 27, 188, 94, 177, 137, 246, 144, 117, 234, 170, 251, 61, 120, 32, 97, 107, 166, + 142, 197, 252, 233, 23, 246, 19, 205, 237, 168, 67, 255, 175, 130, 73, 191, 71, 231, 235, 63, + 165, 213, 234, 55, 239, 81, 254, 125, 153, 213, 126, 89, 19, 218, 239, 212, 68, 5, 44, 192, + 58, 202, 213, 83, 105, 144, 10, 57, 205, 32, 200, 170, 113, 148, 16, 124, 218, 154, 68, 188, + 142, 60, 151, 216, 24, 68, 128, 26, 21, 145, 200, 118, 221, 72, 139, 88, 52, 23, 29, 37, + 62, 153, 128, 209, 59, 131, 128, 123, 26, 161, 70, 161, 51, 172, 94, 123, 213, 26, 84, 140, + 222, 39, 76, 130, 24, 4, 255, 8, 118, 112, 165, 152, 182, 225, 177, 135, 142, 80, 120, 245, + 99, 118, 149, 249, 127, 232, 130, 12, 138, 229, 136, 142, 225, 142, 109, 186, 134, 246, 84, 122, + 5, 250, 10, 75, 14, 74, 15, 80, 137, 198, 220, 152, 96, 228, 98, 16, 113, 45, 208, 211, + 201, 244, 125, 7, 179, 109, 238, 97, 40, 96, 216, 100, 194, 191, 167, 70, 108, 25, 64, 120, + 129, 146, 154, 22, 84, 8, 33, 151, 192, 217, 26, 153, 169, 252, 206, 72, 125, 163, 45, 35, + 165, 55, 130, 197, 10, 7, 49, 77, 79, 87, 236, 7, 100, 13, 73, 60, 69, 64, 93, 7, + 254, 216, 223, 211, 207, 188, 38, 130, 40, 97, 105, 210, 2, 37, 245, 53, 80, 172, 39, 122, + 244, 158, 180, 126, 143, 186, 234, 118, 24, 233, 118, 81, 109, 208, 136, 140, 198, 155, 15, 217, + 7, 248, 247, 69, 244, 228, 197, 31, 79, 190, 172, 210, 131, 127, 174, 44, 40, 37, 89, 105, + 254, 142, 180, 47, 77, 175, 46, 65, 107, 1, 109, 244, 86, 139, 207, 153, 137, 17, 108, 51, + 30, 42, 216, 105, 36, 84, 47, 184, 91, 132, 76, 164, 205, 65, 32, 155, 3, 212, 71, 146, + 57, 107, 47, 252, 217, 85, 133, 222, 224, 142, 195, 223, 129, 149, 118, 187, 53, 169, 208, 129, + 130, 88, 98, 157, 169, 185, 8, 109, 161, 199, 19, 230, 87, 124, 151, 166, 84, 14, 52, 111, + 205, 237, 202, 18, 255, 7, 236, 137, 34, 7, 111, 0, 205, 176, 87, 14, 238, 218, 101, 248, + 3, 24, 26, 252, 123, 167, 179, 175, 252, 27, 255, 226, 103, 166, 69, 34, 89, 35, 228, 66, + 48, 233, 111, 34, 44, 40, 152, 53, 245, 183, 123, 254, 191, 80, 64, 4, 172, 224, 177, 167, + 103, 102, 187, 245, 220, 228, 228, 181, 218, 26, 212, 200, 157, 64, 198, 231, 143, 57, 48, 175, + 187, 99, 186, 212, 152, 118, 211, 146, 65, 238, 168, 208, 178, 188, 204, 172, 253, 144, 138, 64, + 20, 104, 168, 129, 58, 69, 250, 10, 29, 229, 176, 0, 207, 217, 254, 242, 212, 144, 101, 85, + 100, 125, 191, 77, 34, 239, 85, 207, 24, 37, 65, 19, 64, 169, 81, 180, 170, 49, 120, 162, + 135, 219, 22, 201, 7, 49, 94, 249, 121, 26, 76, 50, 141, 134, 115, 136, 218, 212, 243, 148, + 58, 53, 121, 72, 212, 196, 41, 1, 178, 175, 153, 47, 109, 51, 248, 138, 215, 130, 190, 149, + 165, 88, 169, 117, 215, 178, 41, 165, 221, 26, 111, 86, 188, 159, 145, 104, 187, 228, 105, 22, + 145, 54, 117, 212, 143, 216, 245, 120, 186, 35, 153, 54, 201, 35, 122, 102, 198, 212, 154, 77, + 145, 54, 117, 138, 216, 81, 213, 213, 139, 225, 246, 204, 57, 56, 22, 153, 240, 135, 155, 198, + 50, 142, 4, 215, 224, 28, 178, 245, 26, 31, 146, 152, 227, 86, 92, 6, 127, 24, 250, 78, + 194, 187, 159, 181, 80, 203, 20, 24, 120, 39, 171, 165, 182, 203, 29, 74, 178, 31, 93, 46, + 233, 243, 188, 215, 116, 50, 202, 183, 167, 207, 14, 119, 252, 125, 79, 120, 112, 60, 157, 12, + 110, 142, 169, 71, 114, 76, 140, 2, 201, 177, 206, 23, 64, 191, 44, 50, 122, 171, 51, 16, + 233, 102, 141, 44, 219, 204, 190, 170, 139, 251, 216, 244, 183, 113, 22, 183, 69, 129, 66, 242, + 209, 218, 84, 69, 252, 214, 5, 134, 28, 25, 102, 20, 66, 34, 55, 230, 230, 240, 248, 95, + 190, 51, 215, 9, 230, 250, 251, 224, 158, 79, 66, 71, 167, 80, 187, 6, 189, 123, 83, 214, + 99, 173, 238, 83, 67, 193, 130, 27, 101, 235, 157, 142, 194, 229, 188, 163, 10, 132, 105, 54, + 120, 251, 225, 250, 194, 185, 42, 64, 213, 65, 110, 181, 49, 237, 32, 58, 104, 224, 41, 15, + 196, 106, 194, 77, 190, 159, 246, 141, 186, 220, 142, 62, 116, 233, 241, 208, 64, 127, 120, 48, + 237, 205, 248, 243, 193, 70, 241, 200, 242, 77, 26, 21, 0, 37, 29, 30, 160, 18, 228, 237, + 27, 222, 158, 33, 200, 212, 199, 143, 41, 34, 219, 167, 239, 75, 156, 149, 78, 198, 74, 179, + 172, 102, 44, 238, 204, 144, 221, 225, 150, 61, 134, 240, 38, 83, 82, 220, 0, 81, 82, 202, + 19, 210, 170, 149, 247, 96, 63, 134, 108, 169, 151, 206, 117, 205, 58, 54, 41, 177, 17, 2, + 222, 139, 223, 157, 2, 95, 171, 145, 163, 251, 184, 211, 159, 22, 82, 63, 131, 100, 246, 98, + 3, 56, 87, 21, 160, 202, 246, 197, 14, 81, 192, 83, 242, 211, 7, 18, 71, 165, 153, 136, + 19, 51, 2, 7, 181, 11, 91, 189, 88, 194, 248, 23, 33, 225, 95, 38, 143, 73, 143, 210, + 44, 58, 35, 105, 49, 83, 200, 203, 87, 125, 63, 4, 17, 22, 233, 69, 105, 190, 106, 231, + 84, 68, 178, 239, 168, 208, 244, 134, 66, 71, 115, 251, 56, 11, 132, 144, 134, 17, 98, 166, + 123, 75, 9, 29, 15, 166, 3, 200, 108, 248, 4, 171, 168, 85, 10, 193, 63, 57, 64, 21, + 40, 148, 117, 206, 36, 206, 155, 114, 22, 40, 68, 131, 89, 141, 92, 52, 75, 141, 150, 119, + 141, 105, 147, 30, 103, 225, 171, 97, 73, 103, 33, 133, 125, 190, 71, 93, 48, 223, 102, 213, + 13, 52, 56, 133, 51, 147, 249, 69, 168, 27, 198, 197, 164, 126, 249, 50, 10, 143, 99, 99, + 176, 110, 110, 49, 108, 193, 162, 245, 145, 98, 107, 188, 89, 214, 13, 183, 82, 30, 251, 61, + 101, 117, 7, 128, 97, 110, 52, 27, 71, 93, 15, 167, 158, 186, 37, 128, 154, 92, 229, 27, + 189, 220, 13, 54, 59, 15, 251, 232, 121, 216, 199, 204, 195, 206, 206, 99, 10, 55, 88, 112, + 239, 154, 131, 248, 65, 52, 135, 142, 95, 164, 205, 71, 0, 14, 177, 3, 197, 11, 179, 73, + 34, 80, 161, 75, 221, 11, 18, 202, 235, 6, 68, 198, 125, 93, 21, 164, 131, 45, 16, 21, + 145, 170, 235, 67, 64, 199, 128, 218, 109, 125, 103, 85, 183, 247, 190, 33, 238, 96, 144, 238, + 233, 5, 202, 234, 0, 92, 139, 143, 94, 102, 63, 145, 79, 106, 118, 109, 197, 238, 127, 151, + 13, 61, 88, 41, 55, 206, 210, 248, 56, 99, 63, 30, 24, 54, 136, 8, 48, 227, 187, 42, + 114, 141, 76, 91, 61, 60, 45, 20, 26, 200, 45, 87, 201, 46, 249, 145, 200, 152, 212, 60, + 102, 96, 115, 168, 236, 36, 164, 36, 54, 79, 73, 163, 137, 176, 207, 249, 96, 214, 24, 125, + 147, 102, 223, 99, 152, 196, 121, 93, 97, 152, 16, 77, 39, 12, 16, 36, 199, 95, 146, 55, + 76, 173, 208, 76, 128, 236, 196, 74, 13, 35, 29, 123, 75, 166, 62, 245, 142, 152, 177, 213, + 179, 22, 200, 88, 179, 203, 74, 16, 4, 111, 141, 53, 232, 145, 52, 85, 108, 76, 193, 253, + 79, 174, 245, 195, 141, 164, 3, 227, 229, 147, 216, 55, 159, 218, 76, 57, 103, 214, 229, 100, + 164, 71, 217, 6, 167, 54, 188, 151, 212, 50, 54, 46, 139, 162, 30, 5, 175, 247, 50, 84, + 206, 212, 27, 246, 84, 245, 245, 50, 83, 244, 132, 210, 139, 194, 155, 124, 131, 193, 51, 101, + 16, 238, 47, 220, 208, 120, 141, 92, 178, 18, 207, 24, 80, 14, 50, 162, 122, 126, 249, 28, + 143, 197, 46, 118, 33, 59, 1, 180, 71, 19, 17, 91, 57, 114, 145, 37, 223, 84, 136, 68, + 42, 152, 4, 93, 167, 233, 144, 189, 89, 201, 248, 179, 115, 33, 122, 213, 123, 212, 137, 186, + 24, 133, 70, 48, 232, 247, 115, 225, 38, 226, 128, 139, 188, 164, 189, 176, 40, 14, 32, 137, + 196, 95, 115, 56, 246, 99, 42, 214, 131, 236, 36, 137, 176, 178, 33, 72, 220, 112, 54, 178, + 44, 73, 22, 127, 232, 118, 25, 122, 113, 179, 130, 54, 157, 36, 104, 110, 177, 166, 88, 203, + 129, 231, 121, 154, 181, 122, 196, 118, 158, 248, 238, 219, 107, 144, 63, 199, 160, 131, 190, 93, + 20, 160, 145, 10, 138, 228, 10, 90, 154, 210, 100, 13, 208, 46, 248, 96, 198, 156, 49, 92, + 186, 215, 51, 198, 174, 65, 185, 136, 12, 253, 244, 117, 201, 100, 28, 115, 182, 230, 248, 73, + 64, 211, 97, 196, 84, 2, 67, 67, 228, 149, 41, 102, 151, 31, 33, 158, 249, 26, 95, 175, + 70, 161, 24, 130, 148, 55, 28, 184, 64, 12, 200, 3, 19, 194, 55, 96, 139, 162, 164, 217, + 228, 129, 19, 32, 74, 56, 173, 31, 156, 201, 99, 104, 163, 170, 96, 255, 233, 93, 56, 47, + 110, 168, 178, 206, 25, 209, 79, 255, 248, 98, 141, 158, 239, 231, 243, 227, 128, 166, 217, 237, + 170, 137, 145, 206, 233, 229, 209, 99, 13, 172, 153, 124, 6, 47, 94, 79, 119, 240, 240, 184, + 211, 133, 246, 150, 174, 41, 199, 214, 150, 171, 119, 211, 16, 173, 36, 22, 132, 182, 245, 206, + 133, 54, 187, 192, 143, 204, 246, 98, 160, 82, 179, 142, 56, 53, 167, 77, 75, 214, 244, 102, + 81, 214, 52, 125, 242, 9, 191, 136, 230, 29, 158, 205, 133, 21, 58, 222, 178, 57, 46, 61, + 123, 177, 230, 54, 78, 32, 112, 148, 237, 142, 20, 176, 176, 150, 219, 108, 191, 215, 213, 172, + 8, 240, 128, 40, 206, 9, 130, 51, 138, 148, 28, 69, 129, 101, 20, 29, 59, 174, 197, 19, + 0, 62, 69, 243, 55, 26, 160, 158, 179, 245, 111, 172, 70, 88, 39, 123, 97, 236, 168, 232, + 60, 81, 152, 82, 239, 168, 121, 131, 37, 65, 248, 201, 236, 52, 236, 234, 110, 28, 9, 248, + 193, 159, 174, 157, 41, 75, 99, 53, 172, 103, 97, 149, 53, 152, 17, 0, 24, 230, 226, 252, + 151, 137, 212, 78, 0, 73, 58, 230, 35, 164, 244, 130, 202, 148, 96, 29, 35, 176, 11, 99, + 89, 30, 26, 122, 196, 57, 28, 15, 228, 172, 67, 208, 250, 193, 68, 114, 220, 107, 25, 145, + 205, 167, 32, 142, 227, 17, 242, 30, 185, 140, 199, 152, 19, 43, 223, 54, 169, 173, 162, 211, + 78, 198, 63, 206, 139, 112, 49, 96, 132, 159, 156, 96, 0, 10, 15, 226, 67, 93, 217, 99, + 244, 128, 7, 56, 162, 128, 136, 232, 210, 155, 44, 34, 114, 118, 166, 222, 98, 156, 149, 189, + 175, 114, 126, 32, 49, 10, 98, 158, 200, 100, 39, 135, 201, 229, 120, 182, 66, 174, 43, 190, + 227, 46, 18, 41, 231, 81, 78, 57, 175, 211, 164, 135, 39, 119, 77, 236, 163, 208, 254, 37, + 66, 2, 103, 190, 170, 187, 94, 12, 94, 76, 196, 105, 2, 32, 242, 231, 186, 32, 248, 221, + 162, 172, 150, 82, 221, 125, 214, 176, 30, 177, 132, 180, 159, 187, 204, 156, 77, 19, 173, 166, + 171, 14, 194, 114, 8, 108, 125, 209, 229, 178, 152, 21, 73, 190, 209, 52, 24, 106, 226, 98, + 46, 82, 114, 57, 168, 60, 142, 3, 194, 33, 235, 92, 92, 65, 216, 171, 41, 180, 191, 142, + 217, 91, 34, 219, 159, 102, 74, 185, 153, 17, 146, 43, 71, 157, 21, 188, 179, 217, 141, 150, + 236, 59, 74, 42, 98, 148, 159, 224, 42, 146, 176, 116, 40, 30, 254, 37, 172, 220, 29, 232, + 219, 185, 100, 100, 39, 52, 153, 97, 229, 162, 105, 110, 21, 139, 188, 227, 48, 210, 11, 107, + 59, 39, 88, 192, 160, 9, 205, 197, 88, 7, 71, 63, 203, 107, 60, 76, 219, 212, 247, 135, + 39, 23, 130, 253, 11, 247, 137, 10, 147, 120, 170, 164, 138, 120, 94, 201, 57, 249, 193, 103, + 103, 101, 207, 75, 13, 71, 36, 161, 101, 70, 253, 35, 162, 140, 62, 84, 119, 40, 131, 228, + 252, 57, 106, 134, 174, 246, 95, 111, 52, 97, 248, 222, 160, 56, 187, 114, 158, 115, 143, 164, + 197, 8, 31, 218, 136, 189, 203, 130, 5, 142, 159, 26, 123, 89, 74, 199, 220, 234, 58, 169, + 37, 146, 82, 56, 28, 151, 205, 32, 195, 21, 150, 230, 87, 250, 182, 254, 58, 147, 75, 54, + 100, 15, 128, 131, 54, 193, 25, 174, 232, 113, 172, 128, 222, 109, 239, 7, 64, 53, 60, 212, + 60, 139, 157, 211, 216, 208, 182, 199, 54, 1, 233, 42, 12, 176, 82, 153, 13, 57, 8, 176, + 207, 46, 85, 81, 222, 127, 118, 171, 32, 70, 147, 179, 3, 203, 233, 87, 109, 110, 14, 225, + 236, 99, 98, 223, 203, 203, 11, 36, 86, 169, 181, 116, 174, 210, 178, 190, 161, 73, 173, 59, + 138, 180, 5, 209, 51, 10, 1, 189, 203, 154, 138, 151, 186, 48, 13, 255, 251, 109, 135, 226, + 145, 115, 49, 181, 66, 32, 9, 155, 201, 21, 158, 53, 62, 155, 36, 252, 0, 37, 173, 204, + 246, 150, 224, 208, 85, 225, 26, 48, 225, 228, 16, 211, 141, 161, 14, 229, 175, 55, 21, 54, + 205, 235, 142, 88, 16, 98, 46, 62, 57, 210, 117, 254, 201, 123, 49, 244, 12, 223, 30, 232, + 18, 211, 181, 96, 236, 201, 129, 178, 47, 15, 208, 68, 28, 104, 180, 145, 187, 172, 136, 253, + 156, 15, 72, 250, 73, 128, 231, 79, 253, 49, 148, 32, 37, 231, 49, 135, 206, 179, 61, 150, + 20, 137, 20, 206, 17, 248, 15, 212, 23, 103, 113, 63, 78, 84, 197, 177, 114, 194, 234, 30, + 202, 75, 50, 213, 149, 139, 125, 29, 163, 124, 207, 194, 122, 216, 125, 54, 52, 66, 161, 195, + 254, 16, 201, 19, 205, 54, 216, 92, 66, 138, 23, 25, 57, 245, 55, 88, 226, 82, 175, 216, + 137, 24, 82, 64, 164, 217, 233, 115, 37, 5, 71, 89, 21, 112, 121, 185, 207, 147, 229, 72, + 151, 101, 85, 251, 156, 108, 231, 239, 145, 186, 87, 152, 113, 166, 206, 75, 67, 54, 97, 1, + 152, 114, 79, 96, 58, 46, 209, 139, 121, 49, 126, 143, 228, 115, 155, 97, 217, 7, 36, 236, + 146, 47, 141, 242, 126, 25, 59, 181, 120, 8, 148, 11, 100, 22, 6, 201, 83, 174, 205, 237, + 96, 179, 14, 166, 69, 247, 160, 142, 32, 142, 59, 177, 186, 125, 5, 180, 255, 235, 190, 198, + 157, 121, 153, 163, 125, 121, 121, 2, 114, 230, 218, 15, 35, 85, 238, 196, 70, 141, 35, 175, + 67, 255, 138, 63, 240, 89, 96, 227, 90, 99, 248, 158, 42, 234, 253, 1, 192, 207, 252, 47, + 60, 31, 113, 63, 226, 111, 76, 77, 231, 250, 171, 217, 191, 44, 75, 74, 64, 179, 203, 167, + 98, 225, 179, 3, 19, 193, 38, 156, 252, 102, 167, 103, 240, 62, 251, 10, 175, 17, 48, 194, + 4, 242, 7, 53, 29, 70, 197, 34, 223, 190, 119, 223, 159, 134, 217, 172, 98, 118, 87, 212, + 187, 136, 53, 233, 54, 127, 126, 40, 91, 53, 72, 124, 78, 160, 13, 93, 247, 50, 114, 66, + 42, 78, 152, 117, 104, 26, 83, 198, 87, 241, 211, 20, 189, 127, 39, 230, 179, 105, 90, 239, + 12, 108, 243, 65, 127, 190, 21, 99, 124, 4, 184, 83, 96, 112, 1, 107, 213, 237, 113, 245, + 138, 194, 180, 190, 84, 113, 10, 127, 95, 221, 127, 38, 195, 67, 122, 203, 31, 106, 123, 109, + 41, 206, 32, 6, 46, 107, 207, 158, 208, 42, 137, 83, 197, 68, 114, 155, 24, 233, 64, 42, + 98, 98, 164, 43, 12, 208, 74, 200, 113, 240, 20, 112, 15, 195, 37, 42, 79, 197, 0, 8, + 187, 0, 10, 60, 110, 222, 181, 37, 129, 25, 141, 119, 77, 215, 209, 51, 26, 198, 189, 144, + 106, 109, 67, 215, 211, 196, 84, 142, 51, 202, 124, 220, 108, 16, 70, 73, 9, 38, 58, 255, + 232, 109, 66, 123, 156, 105, 15, 23, 13, 192, 228, 87, 98, 61, 209, 80, 254, 91, 41, 144, + 225, 231, 188, 10, 84, 152, 50, 154, 169, 208, 143, 109, 235, 189, 35, 97, 81, 39, 46, 6, + 35, 42, 38, 170, 125, 133, 81, 44, 14, 212, 116, 73, 146, 115, 141, 73, 35, 145, 13, 163, + 183, 4, 66, 248, 189, 13, 223, 67, 230, 178, 234, 214, 247, 138, 144, 177, 161, 255, 81, 124, + 223, 153, 250, 72, 101, 74, 6, 92, 200, 16, 147, 94, 137, 70, 38, 165, 59, 224, 172, 89, + 95, 141, 192, 242, 36, 41, 197, 37, 198, 31, 127, 154, 177, 39, 159, 149, 232, 197, 121, 79, + 6, 28, 234, 120, 127, 190, 122, 43, 196, 107, 151, 181, 249, 54, 50, 205, 226, 176, 164, 142, + 24, 95, 73, 18, 251, 179, 221, 218, 162, 240, 2, 83, 231, 174, 3, 20, 87, 2, 67, 175, + 158, 137, 155, 148, 184, 230, 65, 144, 55, 32, 43, 196, 43, 72, 3, 216, 174, 185, 69, 190, + 69, 100, 189, 209, 101, 157, 21, 246, 183, 64, 151, 7, 68, 203, 237, 241, 44, 49, 61, 235, + 177, 239, 46, 43, 61, 49, 95, 141, 201, 251, 4, 161, 61, 164, 75, 7, 72, 70, 135, 210, + 196, 234, 116, 57, 51, 244, 191, 252, 193, 205, 6, 71, 119, 80, 77, 105, 87, 223, 234, 185, + 29, 57, 2, 147, 190, 164, 210, 232, 110, 245, 4, 108, 211, 245, 109, 230, 42, 235, 209, 136, + 79, 37, 55, 236, 107, 203, 177, 50, 209, 138, 249, 47, 207, 134, 37, 229, 164, 113, 36, 231, + 30, 194, 223, 107, 212, 186, 201, 32, 129, 177, 121, 140, 197, 89, 131, 6, 216, 185, 129, 251, + 184, 77, 154, 251, 4, 98, 15, 181, 140, 226, 33, 99, 169, 83, 16, 20, 203, 14, 67, 237, + 158, 11, 246, 162, 196, 25, 97, 176, 230, 142, 29, 109, 6, 12, 37, 63, 103, 102, 221, 27, + 26, 114, 206, 111, 87, 21, 199, 29, 205, 68, 109, 4, 60, 173, 121, 251, 169, 126, 27, 2, + 255, 7, 188, 28, 79, 150, 203, 206, 143, 102, 200, 38, 111, 204, 238, 22, 54, 79, 181, 94, + 79, 49, 81, 176, 226, 204, 204, 96, 230, 207, 72, 34, 111, 218, 217, 66, 109, 110, 61, 195, + 16, 125, 245, 228, 76, 57, 111, 23, 177, 18, 194, 1, 100, 88, 184, 88, 188, 57, 225, 203, + 168, 176, 167, 64, 54, 180, 29, 1, 119, 236, 244, 167, 58, 162, 174, 139, 73, 166, 251, 90, + 245, 168, 245, 145, 196, 210, 199, 241, 83, 64, 88, 222, 53, 228, 139, 152, 49, 255, 80, 225, + 185, 243, 216, 189, 50, 62, 227, 169, 250, 58, 12, 171, 141, 12, 193, 172, 211, 250, 120, 18, + 47, 123, 144, 38, 155, 229, 219, 129, 34, 11, 123, 185, 199, 18, 34, 9, 191, 148, 222, 131, + 240, 121, 43, 213, 51, 72, 231, 226, 42, 196, 131, 175, 47, 128, 175, 77, 124, 77, 250, 67, + 207, 47, 21, 204, 88, 241, 248, 93, 59, 53, 124, 215, 142, 92, 91, 163, 46, 72, 171, 75, + 116, 80, 239, 189, 174, 74, 238, 154, 190, 167, 41, 53, 27, 92, 157, 174, 212, 215, 50, 43, + 242, 83, 45, 202, 20, 138, 166, 203, 158, 33, 233, 169, 112, 142, 187, 204, 126, 37, 111, 80, + 69, 193, 166, 225, 245, 90, 195, 49, 16, 248, 104, 30, 103, 42, 220, 136, 67, 129, 213, 98, + 136, 143, 186, 33, 77, 190, 163, 176, 69, 223, 81, 168, 14, 201, 238, 27, 24, 171, 170, 185, + 61, 89, 235, 195, 136, 232, 239, 169, 184, 52, 67, 114, 190, 83, 230, 41, 148, 243, 82, 149, + 53, 232, 113, 143, 91, 13, 92, 245, 199, 50, 171, 107, 162, 192, 239, 81, 230, 60, 186, 254, + 171, 191, 176, 136, 169, 56, 9, 174, 218, 14, 148, 86, 124, 117, 193, 134, 199, 234, 65, 5, + 156, 164, 119, 51, 95, 198, 73, 106, 142, 156, 45, 241, 217, 131, 212, 220, 220, 79, 185, 134, + 195, 136, 155, 250, 200, 18, 157, 152, 123, 127, 173, 129, 186, 39, 77, 69, 190, 178, 168, 155, + 144, 229, 100, 125, 235, 190, 56, 178, 86, 144, 77, 235, 191, 126, 24, 42, 195, 108, 93, 153, + 147, 80, 136, 138, 124, 0, 168, 250, 36, 197, 52, 4, 77, 28, 149, 44, 147, 201, 247, 200, + 38, 92, 76, 0, 109, 229, 88, 41, 152, 207, 67, 68, 3, 83, 47, 110, 72, 106, 21, 232, + 130, 11, 66, 162, 185, 208, 197, 215, 32, 67, 4, 238, 245, 215, 247, 116, 60, 80, 20, 221, + 115, 208, 205, 2, 71, 249, 140, 163, 253, 168, 250, 82, 8, 234, 57, 39, 44, 76, 86, 243, + 17, 86, 195, 70, 109, 158, 28, 219, 182, 235, 130, 21, 207, 108, 67, 43, 190, 223, 151, 247, + 46, 102, 159, 147, 32, 236, 164, 167, 62, 94, 164, 149, 80, 177, 25, 185, 133, 72, 213, 117, + 194, 88, 238, 15, 196, 178, 32, 171, 232, 82, 140, 249, 1, 167, 253, 125, 110, 196, 185, 82, + 110, 104, 181, 164, 241, 185, 162, 224, 67, 232, 1, 34, 204, 147, 145, 2, 219, 7, 102, 140, + 178, 130, 237, 195, 170, 146, 199, 160, 82, 209, 220, 3, 72, 163, 222, 149, 156, 212, 8, 43, + 88, 255, 34, 179, 186, 227, 57, 24, 116, 2, 232, 131, 54, 37, 245, 186, 185, 167, 106, 170, + 18, 140, 226, 162, 134, 177, 136, 174, 216, 5, 162, 17, 40, 98, 19, 57, 186, 239, 138, 176, + 50, 224, 33, 71, 219, 143, 111, 122, 41, 76, 95, 85, 99, 99, 153, 42, 61, 133, 154, 99, + 39, 143, 11, 107, 25, 208, 136, 95, 229, 140, 124, 233, 9, 244, 32, 24, 187, 0, 145, 89, + 68, 13, 65, 30, 189, 178, 81, 225, 142, 179, 254, 77, 24, 126, 13, 98, 188, 61, 143, 30, + 166, 100, 123, 130, 6, 13, 238, 77, 107, 242, 174, 204, 26, 185, 107, 131, 70, 245, 9, 83, + 107, 125, 99, 170, 202, 39, 214, 198, 155, 245, 144, 51, 208, 67, 130, 161, 190, 249, 216, 163, + 140, 152, 43, 232, 213, 175, 90, 190, 228, 80, 79, 106, 63, 161, 199, 94, 136, 235, 100, 185, + 230, 68, 13, 63, 59, 237, 1, 7, 165, 76, 83, 70, 119, 158, 117, 55, 91, 148, 104, 96, + 19, 230, 242, 123, 91, 78, 31, 30, 100, 157, 80, 97, 193, 186, 138, 43, 20, 238, 38, 78, + 228, 107, 74, 116, 176, 82, 140, 176, 255, 13, 59, 103, 212, 57, 231, 17, 160, 1, 10, 229, + 23, 177, 221, 176, 245, 210, 181, 197, 44, 11, 134, 57, 238, 128, 234, 209, 68, 15, 206, 212, + 5, 197, 177, 149, 211, 195, 161, 228, 194, 38, 71, 92, 139, 16, 142, 48, 107, 35, 148, 164, + 161, 187, 6, 89, 124, 240, 46, 234, 99, 110, 66, 20, 20, 248, 69, 234, 145, 46, 97, 102, + 238, 108, 127, 172, 226, 168, 199, 95, 150, 78, 172, 126, 139, 215, 48, 178, 200, 56, 42, 83, + 78, 181, 82, 187, 54, 24, 177, 225, 247, 41, 76, 6, 211, 47, 1, 47, 76, 33, 197, 237, + 184, 84, 8, 182, 137, 186, 225, 104, 22, 180, 235, 224, 67, 198, 191, 113, 161, 117, 193, 198, + 231, 63, 71, 221, 205, 113, 149, 226, 145, 247, 61, 85, 118, 211, 221, 117, 32, 171, 92, 68, + 182, 83, 180, 188, 110, 50, 42, 98, 8, 173, 54, 89, 105, 245, 247, 42, 168, 223, 111, 60, + 60, 14, 38, 218, 210, 143, 213, 53, 236, 199, 155, 205, 134, 99, 80, 150, 67, 69, 216, 9, + 29, 112, 162, 155, 207, 39, 2, 165, 222, 32, 230, 80, 135, 81, 73, 196, 134, 173, 26, 112, + 136, 71, 40, 116, 204, 181, 129, 190, 174, 168, 39, 135, 67, 142, 252, 215, 172, 49, 113, 18, + 229, 66, 71, 183, 205, 235, 253, 148, 167, 91, 162, 215, 163, 228, 31, 106, 173, 232, 86, 2, + 11, 135, 216, 74, 228, 29, 63, 230, 123, 71, 20, 217, 108, 159, 161, 97, 175, 124, 182, 82, + 207, 242, 178, 182, 93, 163, 159, 209, 249, 120, 150, 163, 201, 227, 153, 124, 64, 165, 53, 233, + 32, 17, 77, 67, 137, 253, 163, 88, 126, 106, 124, 147, 187, 125, 7, 154, 97, 246, 29, 103, + 132, 193, 223, 36, 157, 207, 95, 108, 44, 139, 145, 76, 129, 112, 43, 53, 25, 0, 206, 161, + 29, 210, 104, 88, 30, 163, 210, 119, 131, 60, 85, 199, 249, 6, 183, 158, 165, 229, 57, 220, + 64, 102, 103, 116, 200, 182, 117, 89, 88, 63, 216, 227, 4, 94, 86, 190, 173, 75, 1, 221, + 132, 57, 80, 97, 90, 63, 178, 187, 121, 68, 118, 87, 22, 219, 209, 179, 112, 211, 53, 85, + 147, 21, 71, 253, 174, 107, 19, 171, 31, 176, 143, 180, 12, 4, 143, 52, 131, 215, 112, 42, + 182, 203, 113, 112, 151, 125, 115, 95, 12, 3, 102, 190, 153, 93, 183, 3, 162, 15, 111, 41, + 53, 208, 39, 9, 176, 70, 131, 149, 153, 116, 219, 138, 25, 138, 105, 235, 31, 29, 37, 37, + 221, 144, 15, 36, 156, 29, 64, 74, 137, 138, 242, 165, 85, 176, 98, 210, 176, 67, 117, 42, + 28, 230, 121, 242, 120, 74, 208, 23, 150, 147, 112, 161, 84, 227, 46, 80, 205, 250, 234, 21, + 138, 249, 219, 30, 94, 149, 208, 118, 93, 127, 187, 228, 248, 139, 132, 28, 237, 53, 198, 62, + 174, 79, 23, 213, 10, 61, 13, 52, 65, 108, 199, 161, 180, 88, 168, 71, 234, 27, 185, 98, + 234, 78, 57, 206, 183, 26, 111, 248, 100, 29, 0, 239, 47, 32, 71, 136, 64, 57, 29, 219, + 116, 165, 247, 37, 90, 169, 136, 104, 99, 198, 129, 255, 196, 193, 195, 10, 197, 30, 131, 82, + 225, 36, 84, 88, 85, 235, 109, 77, 81, 37, 107, 104, 137, 142, 20, 148, 196, 49, 142, 10, + 141, 238, 123, 152, 227, 31, 144, 143, 246, 108, 105, 210, 9, 130, 229, 189, 254, 208, 149, 247, + 253, 201, 80, 103, 100, 9, 195, 137, 97, 238, 12, 116, 11, 12, 0, 195, 14, 4, 164, 112, + 67, 2, 48, 53, 201, 1, 198, 158, 158, 225, 192, 48, 24, 208, 39, 13, 12, 59, 147, 232, + 206, 149, 218, 96, 249, 51, 42, 45, 108, 229, 50, 17, 232, 147, 91, 3, 101, 127, 134, 212, + 191, 171, 196, 32, 190, 233, 202, 99, 246, 93, 23, 87, 116, 106, 143, 213, 6, 143, 55, 232, + 140, 12, 217, 244, 242, 178, 182, 102, 222, 237, 186, 151, 22, 118, 50, 162, 205, 237, 159, 223, + 60, 235, 221, 114, 146, 54, 86, 197, 107, 223, 176, 61, 175, 183, 71, 209, 6, 9, 91, 113, + 27, 254, 51, 109, 145, 114, 19, 71, 202, 135, 231, 66, 202, 168, 145, 47, 208, 56, 47, 35, + 250, 38, 250, 184, 79, 86, 104, 182, 114, 2, 254, 154, 198, 74, 28, 31, 16, 93, 41, 218, + 16, 102, 122, 166, 248, 200, 69, 146, 148, 75, 22, 77, 232, 172, 18, 81, 48, 93, 3, 60, + 68, 138, 230, 217, 62, 91, 155, 210, 80, 73, 60, 76, 139, 23, 157, 192, 10, 45, 228, 80, + 154, 93, 125, 139, 63, 162, 96, 155, 149, 63, 90, 36, 195, 128, 106, 27, 132, 212, 21, 213, + 208, 174, 57, 199, 49, 88, 139, 224, 27, 221, 230, 136, 109, 190, 138, 121, 239, 82, 163, 61, + 108, 60, 214, 251, 164, 140, 51, 95, 224, 60, 153, 147, 195, 211, 189, 164, 160, 160, 227, 239, + 168, 138, 130, 123, 116, 241, 100, 230, 204, 201, 248, 187, 166, 151, 184, 214, 27, 156, 74, 1, + 103, 213, 253, 129, 204, 65, 178, 62, 161, 106, 148, 142, 32, 172, 57, 76, 173, 63, 0, 82, + 55, 83, 245, 130, 238, 98, 59, 12, 66, 70, 113, 206, 206, 34, 148, 221, 216, 37, 80, 112, + 18, 247, 84, 110, 247, 3, 33, 57, 144, 212, 193, 106, 225, 120, 238, 153, 149, 60, 204, 132, + 129, 120, 166, 179, 137, 41, 188, 211, 213, 13, 139, 6, 20, 236, 55, 215, 249, 148, 110, 234, + 188, 52, 46, 199, 110, 156, 233, 53, 151, 41, 16, 204, 30, 199, 148, 138, 216, 102, 118, 155, + 170, 122, 67, 150, 115, 124, 121, 132, 249, 124, 24, 71, 63, 132, 246, 229, 195, 139, 59, 28, + 229, 93, 123, 7, 136, 129, 182, 240, 69, 218, 212, 39, 242, 184, 185, 224, 40, 199, 38, 108, + 164, 225, 13, 34, 248, 57, 150, 243, 150, 221, 95, 10, 67, 0, 15, 223, 198, 145, 190, 174, + 233, 125, 182, 79, 221, 216, 36, 81, 183, 130, 234, 187, 108, 63, 170, 38, 36, 80, 246, 79, + 255, 210, 245, 129, 77, 189, 142, 110, 139, 122, 224, 10, 161, 207, 207, 95, 58, 117, 236, 6, + 189, 175, 209, 155, 254, 168, 237, 121, 115, 253, 31, 104, 33, 233, 74, 125, 92, 192, 110, 73, + 199, 116, 217, 208, 209, 136, 252, 217, 129, 212, 116, 180, 221, 50, 195, 242, 217, 177, 33, 249, + 167, 79, 184, 229, 242, 57, 204, 150, 107, 12, 178, 48, 93, 60, 168, 28, 195, 76, 234, 235, + 132, 135, 164, 15, 61, 200, 13, 46, 163, 229, 214, 52, 232, 109, 2, 132, 67, 105, 86, 51, + 164, 110, 1, 36, 244, 148, 193, 198, 192, 138, 210, 214, 32, 233, 52, 98, 0, 65, 149, 227, + 107, 21, 178, 254, 194, 61, 116, 78, 120, 162, 224, 120, 74, 39, 65, 182, 238, 162, 22, 7, + 81, 37, 216, 244, 45, 149, 70, 248, 84, 19, 119, 254, 206, 156, 191, 51, 231, 239, 204, 249, + 247, 203, 156, 191, 243, 189, 239, 124, 239, 247, 198, 247, 54, 20, 76, 131, 254, 46, 4, 121, + 116, 188, 70, 17, 235, 46, 101, 227, 16, 175, 10, 145, 221, 227, 91, 170, 30, 155, 84, 241, + 146, 2, 69, 82, 49, 216, 11, 67, 115, 231, 22, 40, 206, 33, 136, 178, 100, 240, 230, 223, + 74, 66, 84, 220, 46, 147, 71, 45, 149, 9, 64, 126, 168, 98, 198, 164, 248, 36, 65, 40, + 231, 33, 152, 139, 138, 31, 15, 118, 86, 162, 209, 124, 190, 154, 139, 11, 248, 63, 63, 92, + 65, 87, 175, 63, 190, 135, 127, 223, 32, 130, 98, 116, 36, 138, 255, 132, 104, 161, 38, 149, + 207, 98, 39, 241, 69, 92, 142, 225, 222, 164, 43, 253, 163, 118, 193, 227, 31, 169, 51, 138, + 30, 197, 15, 119, 107, 115, 211, 213, 157, 157, 9, 54, 152, 168, 109, 192, 209, 6, 252, 242, + 40, 250, 94, 164, 136, 249, 184, 80, 63, 109, 110, 138, 182, 15, 175, 58, 152, 15, 17, 252, + 193, 244, 115, 241, 46, 94, 219, 201, 152, 191, 173, 105, 251, 89, 14, 83, 118, 229, 95, 39, + 132, 104, 242, 28, 204, 163, 84, 47, 33, 178, 137, 235, 237, 53, 20, 205, 44, 145, 196, 72, + 43, 115, 188, 77, 189, 49, 89, 34, 108, 120, 28, 176, 120, 0, 0, 249, 172, 23, 75, 252, + 229, 224, 93, 113, 175, 250, 68, 102, 146, 80, 45, 186, 44, 238, 188, 231, 32, 90, 16, 52, + 113, 54, 151, 22, 230, 236, 181, 44, 5, 112, 168, 219, 68, 229, 158, 56, 107, 97, 174, 162, + 231, 245, 240, 230, 187, 97, 189, 142, 80, 208, 216, 89, 20, 207, 98, 19, 161, 143, 147, 121, + 160, 54, 241, 208, 156, 222, 190, 233, 122, 80, 38, 236, 73, 50, 84, 251, 53, 21, 167, 199, + 251, 242, 52, 27, 54, 48, 244, 31, 115, 181, 199, 220, 26, 46, 174, 154, 150, 154, 71, 92, + 197, 56, 184, 180, 149, 119, 81, 229, 253, 26, 80, 187, 80, 10, 131, 200, 208, 228, 92, 123, + 113, 69, 115, 216, 153, 62, 34, 172, 111, 71, 40, 107, 184, 86, 60, 250, 84, 141, 139, 178, + 78, 17, 4, 35, 209, 102, 197, 19, 197, 34, 205, 214, 240, 251, 16, 41, 191, 169, 194, 102, + 156, 30, 161, 92, 182, 231, 48, 102, 224, 40, 140, 154, 32, 239, 110, 172, 136, 24, 204, 8, + 135, 51, 9, 110, 7, 134, 95, 150, 223, 52, 118, 109, 193, 206, 79, 203, 50, 228, 241, 62, + 199, 104, 133, 68, 244, 166, 143, 100, 136, 170, 109, 204, 46, 32, 147, 48, 108, 232, 211, 222, + 153, 223, 39, 191, 143, 162, 80, 140, 61, 58, 84, 47, 148, 58, 95, 107, 14, 250, 226, 124, + 105, 95, 51, 222, 227, 0, 90, 139, 90, 121, 175, 36, 55, 115, 114, 55, 185, 217, 68, 88, + 195, 160, 28, 122, 170, 186, 43, 174, 89, 239, 2, 143, 73, 74, 84, 239, 19, 39, 211, 73, + 132, 55, 101, 189, 206, 208, 228, 67, 193, 35, 88, 31, 201, 144, 242, 36, 33, 36, 248, 23, + 165, 221, 172, 78, 214, 208, 226, 43, 150, 64, 114, 183, 156, 99, 84, 13, 94, 57, 195, 170, + 222, 145, 133, 140, 174, 125, 52, 74, 106, 127, 63, 186, 155, 237, 164, 194, 183, 139, 7, 39, + 44, 226, 242, 135, 140, 2, 12, 254, 248, 74, 40, 156, 133, 143, 238, 226, 96, 11, 19, 245, + 103, 185, 252, 33, 75, 244, 188, 94, 255, 25, 199, 55, 69, 198, 31, 252, 22, 175, 44, 166, + 221, 109, 144, 68, 229, 24, 120, 10, 50, 89, 101, 13, 5, 221, 51, 172, 254, 66, 6, 74, + 138, 175, 233, 130, 27, 14, 5, 161, 235, 53, 48, 94, 61, 22, 52, 87, 147, 165, 123, 166, + 176, 114, 161, 225, 161, 98, 149, 226, 136, 51, 47, 42, 44, 3, 205, 241, 219, 179, 186, 105, + 211, 190, 155, 39, 13, 15, 27, 89, 99, 13, 160, 185, 212, 215, 217, 196, 248, 233, 82, 173, + 190, 32, 236, 209, 34, 137, 183, 243, 245, 178, 137, 38, 214, 29, 187, 13, 9, 100, 137, 126, + 201, 174, 67, 17, 20, 81, 127, 147, 165, 26, 163, 140, 183, 69, 217, 134, 81, 118, 27, 23, + 46, 213, 62, 26, 126, 234, 98, 165, 126, 10, 248, 3, 110, 86, 250, 46, 109, 62, 86, 218, + 156, 201, 19, 118, 142, 131, 107, 151, 151, 74, 164, 152, 238, 254, 98, 214, 113, 136, 226, 194, + 153, 120, 104, 125, 45, 87, 154, 140, 195, 15, 48, 25, 16, 75, 35, 227, 28, 92, 13, 80, + 21, 249, 61, 48, 40, 184, 113, 17, 196, 57, 85, 223, 234, 221, 153, 30, 176, 64, 190, 126, + 89, 20, 161, 238, 71, 85, 19, 77, 29, 27, 82, 14, 214, 211, 26, 66, 137, 97, 80, 114, + 27, 68, 35, 145, 207, 220, 192, 3, 77, 249, 39, 142, 139, 11, 154, 246, 224, 238, 39, 141, + 99, 1, 192, 247, 242, 237, 24, 12, 185, 64, 120, 203, 65, 103, 95, 146, 182, 69, 9, 44, + 161, 10, 156, 88, 185, 150, 12, 188, 47, 168, 242, 133, 187, 127, 30, 206, 234, 187, 26, 195, + 173, 108, 171, 51, 18, 101, 67, 83, 127, 214, 93, 24, 200, 57, 207, 232, 216, 40, 144, 120, + 221, 15, 6, 255, 247, 23, 77, 66, 38, 93, 153, 180, 12, 187, 136, 14, 172, 180, 138, 100, + 109, 254, 92, 150, 109, 65, 13, 214, 193, 38, 17, 155, 229, 65, 250, 218, 253, 132, 252, 194, + 181, 27, 127, 4, 137, 220, 203, 37, 149, 110, 239, 234, 230, 43, 21, 91, 164, 190, 95, 100, + 123, 67, 181, 27, 235, 134, 97, 205, 246, 251, 28, 243, 222, 233, 44, 193, 42, 202, 233, 177, + 58, 239, 26, 190, 180, 221, 217, 179, 220, 94, 48, 133, 196, 110, 245, 236, 61, 121, 233, 252, + 36, 89, 13, 21, 147, 228, 126, 177, 202, 80, 149, 210, 213, 169, 236, 149, 175, 156, 30, 177, + 132, 221, 46, 167, 7, 196, 128, 45, 211, 222, 31, 127, 241, 130, 235, 225, 224, 189, 22, 71, + 86, 215, 114, 219, 28, 223, 199, 177, 216, 178, 63, 233, 59, 236, 19, 90, 207, 183, 9, 119, + 67, 120, 3, 23, 201, 22, 0, 78, 255, 244, 8, 178, 191, 140, 224, 31, 13, 199, 100, 189, + 242, 254, 161, 90, 205, 31, 89, 111, 197, 112, 84, 227, 146, 11, 144, 206, 5, 143, 173, 66, + 132, 217, 151, 213, 28, 203, 72, 16, 232, 65, 189, 63, 172, 6, 9, 24, 113, 33, 17, 122, + 203, 67, 146, 77, 248, 34, 145, 113, 41, 221, 70, 145, 127, 149, 218, 153, 188, 169, 165, 114, + 255, 217, 76, 116, 246, 249, 229, 103, 37, 85, 88, 155, 113, 79, 103, 234, 189, 68, 92, 139, + 18, 47, 21, 24, 162, 175, 168, 228, 99, 221, 20, 18, 168, 215, 180, 163, 146, 25, 88, 92, + 102, 62, 185, 236, 138, 122, 224, 139, 205, 177, 207, 179, 222, 157, 188, 82, 43, 54, 220, 230, + 75, 15, 226, 164, 228, 182, 222, 31, 140, 113, 69, 48, 46, 1, 84, 44, 29, 142, 117, 58, + 24, 91, 6, 235, 191, 188, 82, 64, 89, 158, 75, 185, 218, 17, 182, 35, 243, 199, 219, 88, + 59, 68, 105, 214, 182, 169, 180, 45, 242, 137, 251, 26, 3, 83, 13, 86, 5, 85, 207, 114, + 132, 67, 23, 207, 144, 209, 61, 195, 96, 119, 247, 96, 58, 214, 155, 138, 188, 99, 149, 117, + 210, 17, 114, 153, 200, 153, 114, 83, 34, 111, 44, 73, 185, 163, 18, 19, 97, 247, 124, 4, + 70, 178, 51, 151, 158, 108, 42, 188, 84, 2, 175, 243, 59, 83, 111, 162, 47, 88, 144, 104, + 58, 74, 82, 85, 184, 98, 59, 243, 119, 170, 221, 13, 157, 112, 168, 167, 197, 106, 81, 113, + 181, 237, 14, 241, 201, 14, 240, 162, 222, 143, 247, 227, 176, 56, 47, 50, 206, 212, 66, 240, + 107, 22, 99, 40, 185, 204, 42, 128, 84, 99, 156, 45, 58, 52, 6, 64, 9, 238, 202, 237, + 5, 28, 181, 42, 77, 70, 115, 59, 156, 60, 205, 203, 237, 103, 115, 108, 57, 142, 241, 30, + 146, 244, 37, 53, 10, 228, 14, 162, 94, 10, 111, 155, 125, 213, 15, 89, 67, 135, 164, 249, + 146, 17, 87, 7, 54, 85, 93, 38, 177, 168, 210, 186, 32, 15, 42, 149, 216, 235, 81, 134, + 223, 212, 26, 222, 232, 246, 149, 182, 237, 155, 13, 52, 110, 127, 190, 85, 68, 91, 174, 30, + 52, 29, 29, 52, 85, 80, 37, 37, 117, 147, 53, 107, 108, 21, 46, 221, 61, 32, 242, 77, + 242, 141, 177, 51, 102, 116, 179, 89, 229, 113, 123, 224, 115, 114, 182, 187, 132, 23, 57, 143, + 236, 218, 137, 180, 35, 231, 188, 93, 46, 59, 108, 77, 235, 232, 233, 225, 133, 15, 247, 211, + 16, 195, 210, 214, 71, 26, 208, 93, 171, 104, 60, 201, 184, 152, 81, 189, 143, 43, 130, 59, + 99, 125, 207, 70, 59, 128, 104, 218, 227, 188, 53, 101, 1, 59, 122, 240, 194, 246, 115, 108, + 200, 96, 152, 194, 30, 37, 32, 22, 26, 26, 94, 165, 125, 192, 159, 182, 206, 3, 140, 83, + 98, 77, 13, 121, 134, 39, 82, 140, 97, 222, 226, 234, 47, 100, 13, 84, 140, 110, 114, 109, + 190, 114, 52, 107, 81, 99, 118, 128, 123, 217, 247, 211, 139, 111, 230, 147, 201, 191, 166, 74, + 178, 92, 70, 239, 47, 80, 6, 255, 105, 53, 121, 199, 163, 44, 226, 225, 77, 125, 89, 73, + 230, 67, 136, 254, 113, 112, 168, 22, 1, 57, 155, 189, 30, 20, 101, 130, 15, 124, 241, 247, + 232, 86, 83, 124, 199, 104, 174, 126, 160, 4, 55, 68, 6, 232, 90, 199, 119, 47, 173, 34, + 218, 134, 169, 21, 88, 52, 52, 103, 198, 144, 251, 29, 77, 223, 107, 51, 90, 159, 8, 156, + 73, 124, 162, 238, 146, 187, 92, 74, 89, 166, 125, 4, 185, 245, 217, 29, 48, 22, 42, 130, + 36, 68, 215, 117, 27, 29, 223, 9, 163, 30, 145, 222, 79, 102, 55, 181, 52, 94, 112, 11, + 197, 227, 19, 2, 228, 116, 255, 32, 52, 31, 232, 157, 146, 169, 22, 245, 237, 214, 104, 24, + 224, 105, 227, 195, 142, 167, 90, 54, 99, 218, 22, 203, 109, 19, 113, 5, 254, 160, 31, 28, + 22, 103, 229, 37, 97, 20, 222, 218, 59, 214, 243, 127, 204, 114, 164, 236, 14, 158, 225, 132, + 248, 28, 210, 110, 21, 186, 4, 58, 79, 1, 51, 120, 57, 215, 173, 207, 38, 119, 123, 235, + 55, 103, 102, 46, 184, 116, 175, 177, 163, 249, 233, 28, 56, 28, 83, 7, 227, 168, 219, 12, + 147, 106, 230, 181, 43, 48, 19, 221, 102, 245, 167, 69, 183, 89, 181, 66, 96, 14, 145, 247, + 172, 5, 66, 185, 238, 34, 155, 148, 13, 99, 206, 209, 133, 33, 161, 90, 100, 47, 246, 209, + 158, 89, 156, 106, 156, 4, 37, 83, 57, 172, 26, 218, 177, 6, 36, 235, 168, 85, 37, 36, + 224, 58, 209, 11, 238, 244, 146, 145, 36, 18, 215, 123, 102, 168, 254, 144, 171, 44, 57, 31, + 31, 251, 36, 67, 65, 63, 135, 174, 42, 155, 209, 143, 6, 151, 25, 117, 81, 133, 38, 30, + 83, 234, 125, 206, 232, 253, 44, 73, 81, 186, 226, 66, 113, 109, 40, 166, 101, 195, 193, 126, + 137, 59, 237, 70, 193, 110, 189, 137, 76, 203, 187, 46, 45, 51, 121, 246, 248, 45, 90, 74, + 49, 235, 191, 231, 121, 37, 223, 74, 79, 238, 156, 89, 85, 39, 243, 29, 41, 7, 143, 23, + 54, 177, 6, 11, 110, 221, 61, 250, 134, 191, 199, 134, 239, 47, 184, 233, 111, 184, 95, 163, + 37, 154, 220, 50, 55, 241, 241, 174, 185, 62, 172, 11, 194, 11, 62, 33, 23, 173, 239, 76, + 203, 75, 119, 110, 160, 55, 61, 193, 190, 249, 120, 215, 164, 221, 92, 236, 199, 194, 68, 174, + 89, 189, 91, 124, 135, 147, 153, 45, 10, 142, 1, 55, 220, 253, 153, 240, 201, 211, 231, 179, + 209, 23, 222, 133, 246, 144, 154, 220, 173, 105, 167, 185, 163, 162, 183, 46, 77, 28, 147, 192, + 42, 127, 5, 144, 212, 191, 29, 194, 153, 170, 202, 88, 181, 193, 170, 239, 88, 127, 48, 155, + 25, 235, 244, 99, 169, 142, 51, 234, 117, 92, 198, 181, 191, 254, 111, 77, 101, 236, 246, 103, + 220, 128, 55, 85, 241, 160, 61, 56, 104, 197, 251, 53, 247, 233, 160, 19, 240, 128, 69, 151, + 100, 55, 41, 174, 240, 131, 206, 246, 28, 145, 96, 31, 110, 86, 164, 74, 13, 47, 203, 80, + 89, 156, 33, 140, 44, 102, 79, 58, 28, 59, 250, 96, 5, 111, 176, 180, 203, 84, 37, 82, + 245, 140, 219, 225, 144, 215, 85, 182, 183, 219, 218, 127, 243, 204, 221, 77, 224, 202, 54, 4, + 75, 62, 7, 167, 89, 249, 0, 113, 156, 117, 86, 180, 89, 85, 33, 148, 213, 21, 167, 224, + 67, 32, 133, 66, 123, 211, 198, 15, 226, 177, 127, 143, 243, 29, 76, 73, 236, 58, 127, 97, + 51, 207, 1, 227, 248, 155, 71, 151, 88, 235, 25, 187, 248, 225, 171, 251, 128, 65, 23, 135, + 46, 207, 138, 57, 110, 188, 42, 254, 243, 167, 170, 59, 118, 191, 91, 215, 165, 201, 123, 37, + 199, 144, 17, 230, 92, 30, 207, 213, 4, 17, 203, 174, 218, 97, 133, 49, 180, 164, 115, 167, + 118, 176, 202, 89, 81, 92, 184, 251, 184, 194, 108, 15, 57, 121, 177, 101, 216, 70, 119, 119, + 181, 47, 76, 69, 21, 27, 184, 214, 125, 228, 199, 255, 183, 111, 163, 90, 98, 209, 46, 109, + 251, 75, 61, 187, 146, 95, 14, 5, 30, 208, 252, 55, 218, 71, 21, 176, 189, 43, 46, 142, + 168, 110, 77, 134, 16, 157, 90, 173, 149, 36, 40, 168, 119, 174, 196, 25, 242, 249, 29, 122, + 1, 92, 197, 88, 104, 233, 37, 149, 35, 174, 112, 218, 166, 214, 9, 56, 26, 160, 228, 30, + 111, 170, 224, 146, 23, 92, 6, 133, 136, 114, 67, 56, 25, 86, 116, 176, 64, 163, 165, 56, + 188, 80, 1, 169, 231, 145, 249, 80, 186, 168, 43, 51, 198, 5, 45, 105, 98, 97, 223, 251, + 133, 45, 199, 7, 34, 117, 228, 8, 190, 33, 167, 112, 222, 191, 197, 244, 107, 100, 200, 25, + 187, 15, 7, 38, 52, 145, 231, 88, 87, 237, 185, 3, 215, 247, 45, 218, 144, 46, 107, 99, + 233, 150, 76, 99, 89, 141, 149, 216, 76, 58, 89, 78, 191, 243, 38, 15, 182, 100, 72, 1, + 34, 9, 244, 132, 198, 255, 227, 223, 255, 231, 127, 252, 47, 233, 81, 230, 185, 192, 181, 231, + 125, 139, 180, 190, 11, 28, 125, 110, 189, 112, 111, 39, 156, 126, 126, 73, 233, 216, 56, 159, + 110, 244, 193, 161, 162, 32, 9, 25, 121, 44, 234, 2, 29, 137, 81, 240, 124, 219, 85, 95, + 23, 11, 90, 57, 181, 238, 97, 3, 185, 82, 4, 64, 219, 207, 254, 152, 226, 59, 203, 61, + 197, 5, 150, 55, 157, 181, 185, 212, 56, 217, 99, 82, 22, 55, 78, 184, 236, 205, 2, 243, + 103, 175, 181, 174, 166, 143, 221, 164, 17, 105, 248, 229, 140, 113, 49, 220, 93, 153, 8, 151, + 217, 196, 103, 53, 212, 185, 10, 33, 57, 78, 170, 110, 41, 131, 77, 202, 2, 53, 250, 6, + 139, 239, 226, 125, 0, 20, 187, 149, 121, 87, 77, 184, 63, 143, 146, 131, 173, 14, 87, 60, + 26, 174, 63, 54, 160, 111, 254, 178, 77, 170, 5, 13, 103, 133, 239, 250, 221, 58, 109, 60, + 34, 148, 6, 175, 169, 162, 110, 57, 251, 145, 64, 162, 90, 81, 66, 142, 137, 6, 182, 89, + 107, 63, 239, 11, 244, 27, 15, 4, 13, 46, 122, 200, 254, 219, 140, 244, 136, 225, 50, 242, + 23, 61, 37, 97, 208, 231, 225, 98, 94, 7, 189, 32, 182, 215, 219, 180, 5, 31, 78, 1, + 122, 180, 128, 132, 188, 201, 242, 173, 251, 233, 110, 231, 37, 219, 218, 6, 104, 21, 93, 42, + 17, 217, 75, 101, 124, 103, 224, 118, 77, 224, 121, 161, 191, 173, 88, 65, 38, 43, 107, 220, + 50, 83, 132, 211, 193, 140, 228, 16, 194, 17, 52, 215, 13, 119, 208, 110, 77, 147, 252, 222, + 154, 191, 235, 62, 75, 24, 119, 113, 246, 116, 120, 136, 14, 26, 196, 64, 213, 241, 134, 227, + 80, 49, 66, 184, 33, 237, 146, 92, 174, 31, 210, 28, 116, 145, 104, 179, 192, 48, 243, 36, + 190, 192, 41, 182, 166, 203, 205, 53, 44, 253, 24, 159, 202, 232, 18, 27, 220, 27, 199, 199, + 252, 174, 84, 82, 29, 149, 175, 22, 82, 206, 69, 151, 54, 220, 36, 248, 201, 188, 135, 38, + 242, 248, 77, 121, 248, 230, 156, 82, 83, 227, 141, 164, 94, 199, 24, 105, 123, 150, 187, 171, + 178, 50, 220, 148, 119, 156, 191, 42, 80, 135, 98, 150, 221, 242, 242, 28, 49, 191, 25, 31, + 195, 23, 236, 7, 228, 19, 170, 103, 12, 155, 178, 203, 126, 172, 27, 104, 241, 39, 76, 147, + 48, 21, 253, 253, 239, 39, 63, 253, 244, 255, 1, 17, 146, 119, 172 diff --git a/cocos/bindings/manual/JavaScriptJavaBridge.cpp b/cocos/bindings/manual/JavaScriptJavaBridge.cpp new file mode 100644 index 0000000..f2779af --- /dev/null +++ b/cocos/bindings/manual/JavaScriptJavaBridge.cpp @@ -0,0 +1,719 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "cocos/bindings/manual/JavaScriptJavaBridge.h" +#include "cocos/application/ApplicationManager.h" +#include "cocos/base/UTF8.h" +#include "cocos/bindings/manual/jsb_conversions.h" + +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include + +#elif CC_PLATFORM == CC_PLATFORM_OHOS + #include +#endif + +#ifdef LOG_TAG + #undef LOG_TAG +#endif + +#define LOG_TAG "JavaScriptJavaBridge" + +#ifndef ORG_JAVABRIDGE_CLASS_NAME + #define ORG_JAVABRIDGE_CLASS_NAME com_cocos_lib_CocosJavascriptJavaBridge +#endif +#define JNI_JSJAVABRIDGE(FUNC) JNI_METHOD1(ORG_JAVABRIDGE_CLASS_NAME, FUNC) +#define JSJ_ERR_OK (0) +#define JSJ_ERR_TYPE_NOT_SUPPORT (-1) +#define JSJ_ERR_INVALID_SIGNATURES (-2) +#define JSJ_ERR_METHOD_NOT_FOUND (-3) +#define JSJ_ERR_EXCEPTION_OCCURRED (-4) +#define JSJ_ERR_VM_THREAD_DETACHED (-5) +#define JSJ_ERR_VM_FAILURE (-6) +#define JSJ_ERR_CLASS_NOT_FOUND (-7) + +class JavaScriptJavaBridge { +public: + enum class ValueType : char { + INVALID, + VOID, + INTEGER, + LONG, + FLOAT, + BOOLEAN, + STRING, + VECTOR, + FUNCTION + }; + + using ValueTypes = ccstd::vector; + + using ReturnValue = union { + int intValue; + int64_t longValue; + float floatValue; + int boolValue; + ccstd::string *stringValue; + }; + + class CallInfo { + public: + CallInfo(const char *className, const char *methodName, const char *methodSig) + : _mClassName(className), + _mMethodName(methodName), + _mMethodSig(methodSig) { + memset(&_mRet, 0, sizeof(_mRet)); + _mValid = validateMethodSig() && getMethodInfo(); + } + ~CallInfo(); + + bool isValid() const { + return _mValid; + } + + int getErrorCode() const { + return _mError; + } + + void tryThrowJSException() const { + if (_mError != JSJ_ERR_OK) { + se::ScriptEngine::getInstance()->throwException(getErrorMessage()); + } + } + + const char *getErrorMessage() const { + switch (_mError) { + case JSJ_ERR_TYPE_NOT_SUPPORT: + return "argument type is not supported"; + case JSJ_ERR_INVALID_SIGNATURES: + return "invalid signature"; + case JSJ_ERR_METHOD_NOT_FOUND: + return "method not found"; + case JSJ_ERR_EXCEPTION_OCCURRED: + return "excpected occurred"; + case JSJ_ERR_VM_THREAD_DETACHED: + return "vm thread detached"; + case JSJ_ERR_VM_FAILURE: + return "vm failure"; + case JSJ_ERR_CLASS_NOT_FOUND: + return "class not found"; + case JSJ_ERR_OK: + default: + return "NOERROR"; + } + } + + JNIEnv *getEnv() const { + return _mEnv; + } + + ValueType argumentTypeAtIndex(size_t index) { + return _mArgumentsType.at(index); + } + + int getArgumentsCount() const { + return _mArgumentsCount; + } + + ValueType getReturnValueType() { + return _mReturnType; + } + + ReturnValue getReturnValue() { + return _mRet; + } + + bool execute(); + bool executeWithArgs(jvalue *args); + + private: + bool _mValid{false}; + int _mError{JSJ_ERR_OK}; + + ccstd::string _mClassName; + ccstd::string _mMethodName; + ccstd::string _mMethodSig; + int _mArgumentsCount{0}; + ValueTypes _mArgumentsType; + ValueType _mReturnType{ValueType::VOID}; + + ReturnValue _mRet; + jstring _mRetjstring{nullptr}; + + JNIEnv *_mEnv{nullptr}; + jclass _mClassID{nullptr}; + jmethodID _mMethodID{nullptr}; + + bool validateMethodSig(); + bool getMethodInfo(); + ValueType checkType(const ccstd::string &sig, size_t *pos); + }; + + static bool convertReturnValue(ReturnValue retValue, ValueType type, se::Value *ret); +}; +using JsCallback = std::function; +class ScriptNativeBridge { +public: + void callByNative(const ccstd::string &arg0, const ccstd::string &arg1); + inline void setCallback(const JsCallback &cb) { + _callback = cb; + } + static ScriptNativeBridge *bridgeCxxInstance; + se::Value jsCb; + +private: + JsCallback _callback{nullptr}; // NOLINT(readability-identifier-naming) +}; +extern "C" { + +JNIEXPORT jint JNICALL JNI_JSJAVABRIDGE(evalString)(JNIEnv *env, jclass /*cls*/, jstring value) { + if (!se::ScriptEngine::getInstance()->isValid()) { + CC_LOG_DEBUG("ScriptEngine has not been initialized"); + return 0; + } + + se::AutoHandleScope hs; + bool strFlag = false; + ccstd::string strValue = cc::StringUtils::getStringUTFCharsJNI(env, value, &strFlag); + if (!strFlag) { + CC_LOG_DEBUG("JavaScriptJavaBridge_evalString error, invalid string code"); + return 0; + } + se::ScriptEngine::getInstance()->evalString(strValue.c_str()); + return 1; +} +JNIEXPORT void JNICALL +Java_com_cocos_lib_JsbBridge_nativeSendToScript(JNIEnv *env, jclass clazz, jstring arg0, jstring arg1) { // NOLINT + ccstd::string cArg0{cc::JniHelper::jstring2string(arg0)}; + ccstd::string cArg1{cc::JniHelper::jstring2string(arg1)}; + + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() { + ScriptNativeBridge::bridgeCxxInstance->callByNative(cArg0, cArg1); + }); +} +} // extern "C" + +ScriptNativeBridge *ScriptNativeBridge::bridgeCxxInstance{nullptr}; + +JavaScriptJavaBridge::CallInfo::~CallInfo() { + _mEnv->DeleteLocalRef(_mClassID); + if (_mReturnType == ValueType::STRING && _mRet.stringValue) { + _mEnv->DeleteLocalRef(_mRetjstring); + delete _mRet.stringValue; + } +} + +bool JavaScriptJavaBridge::CallInfo::execute() { + switch (_mReturnType) { + case JavaScriptJavaBridge::ValueType::VOID: + _mEnv->CallStaticVoidMethod(_mClassID, _mMethodID); + break; + + case JavaScriptJavaBridge::ValueType::INTEGER: + _mRet.intValue = _mEnv->CallStaticIntMethod(_mClassID, _mMethodID); + break; + + case JavaScriptJavaBridge::ValueType::LONG: + _mRet.longValue = _mEnv->CallStaticLongMethod(_mClassID, _mMethodID); + break; + + case JavaScriptJavaBridge::ValueType::FLOAT: + _mRet.floatValue = _mEnv->CallStaticFloatMethod(_mClassID, _mMethodID); + break; + + case JavaScriptJavaBridge::ValueType::BOOLEAN: + _mRet.boolValue = _mEnv->CallStaticBooleanMethod(_mClassID, _mMethodID); + break; + + case JavaScriptJavaBridge::ValueType::STRING: { + _mRetjstring = static_cast(_mEnv->CallStaticObjectMethod(_mClassID, _mMethodID)); + if (_mRetjstring) { + ccstd::string strValue = cc::StringUtils::getStringUTFCharsJNI(_mEnv, _mRetjstring); + _mRet.stringValue = ccnew ccstd::string(strValue); + } else { + _mRet.stringValue = nullptr; + } + + break; + } + + default: + _mError = JSJ_ERR_TYPE_NOT_SUPPORT; + SE_LOGD("Return type '%d' is not supported", static_cast(_mReturnType)); + return false; + } + + if (_mEnv->ExceptionCheck() == JNI_TRUE) { + _mEnv->ExceptionDescribe(); + _mEnv->ExceptionClear(); + _mError = JSJ_ERR_EXCEPTION_OCCURRED; + return false; + } + + return true; +} + +bool JavaScriptJavaBridge::CallInfo::executeWithArgs(jvalue *args) { + switch (_mReturnType) { + case JavaScriptJavaBridge::ValueType::VOID: + _mEnv->CallStaticVoidMethodA(_mClassID, _mMethodID, args); + break; + + case JavaScriptJavaBridge::ValueType::INTEGER: + _mRet.intValue = _mEnv->CallStaticIntMethodA(_mClassID, _mMethodID, args); + break; + + case JavaScriptJavaBridge::ValueType::LONG: + _mRet.longValue = _mEnv->CallStaticLongMethodA(_mClassID, _mMethodID, args); + break; + + case JavaScriptJavaBridge::ValueType::FLOAT: + _mRet.floatValue = _mEnv->CallStaticFloatMethodA(_mClassID, _mMethodID, args); + break; + + case JavaScriptJavaBridge::ValueType::BOOLEAN: + _mRet.boolValue = _mEnv->CallStaticBooleanMethodA(_mClassID, _mMethodID, args); + break; + + case JavaScriptJavaBridge::ValueType::STRING: { + _mRetjstring = static_cast(_mEnv->CallStaticObjectMethodA(_mClassID, _mMethodID, args)); + if (_mRetjstring) { + ccstd::string strValue = cc::StringUtils::getStringUTFCharsJNI(_mEnv, _mRetjstring); + _mRet.stringValue = ccnew ccstd::string(strValue); + } else { + _mRet.stringValue = nullptr; + } + break; + } + + default: + _mError = JSJ_ERR_TYPE_NOT_SUPPORT; + SE_LOGD("Return type '%d' is not supported", static_cast(_mReturnType)); + return false; + } + + if (_mEnv->ExceptionCheck() == JNI_TRUE) { + _mEnv->ExceptionDescribe(); + _mEnv->ExceptionClear(); + _mError = JSJ_ERR_EXCEPTION_OCCURRED; + return false; + } + + return true; +} + +bool JavaScriptJavaBridge::CallInfo::validateMethodSig() { + size_t len = _mMethodSig.length(); + if (len < 3 || _mMethodSig[0] != '(') // min sig is "()V" + { + _mError = JSJ_ERR_INVALID_SIGNATURES; + return false; + } + + size_t pos = 1; + while (pos < len && _mMethodSig[pos] != ')') { + JavaScriptJavaBridge::ValueType type = checkType(_mMethodSig, &pos); + if (type == ValueType::INVALID) return false; + + _mArgumentsCount++; + _mArgumentsType.push_back(type); + pos++; + } + + if (pos >= len || _mMethodSig[pos] != ')') { + _mError = JSJ_ERR_INVALID_SIGNATURES; + return false; + } + + pos++; + _mReturnType = checkType(_mMethodSig, &pos); + return true; +} + +JavaScriptJavaBridge::ValueType JavaScriptJavaBridge::CallInfo::checkType(const ccstd::string &sig, size_t *pos) { + switch (sig[*pos]) { + case 'I': + return JavaScriptJavaBridge::ValueType::INTEGER; + case 'J': + return JavaScriptJavaBridge::ValueType::LONG; + case 'F': + return JavaScriptJavaBridge::ValueType::FLOAT; + case 'Z': + return JavaScriptJavaBridge::ValueType::BOOLEAN; + case 'V': + return JavaScriptJavaBridge::ValueType::VOID; + case 'L': + size_t pos2 = sig.find_first_of(';', *pos + 1); + if (pos2 == ccstd::string::npos) { + _mError = JSJ_ERR_INVALID_SIGNATURES; + return ValueType::INVALID; + } + + const ccstd::string t = sig.substr(*pos, pos2 - *pos + 1); + if (t == "Ljava/lang/String;") { + *pos = pos2; + return ValueType::STRING; + } + + if (t == "Ljava/util/Vector;") { + *pos = pos2; + return ValueType::VECTOR; + } + + _mError = JSJ_ERR_TYPE_NOT_SUPPORT; + return ValueType::INVALID; + } + + _mError = JSJ_ERR_TYPE_NOT_SUPPORT; + return ValueType::INVALID; +} + +bool JavaScriptJavaBridge::CallInfo::getMethodInfo() { + _mMethodID = nullptr; + _mEnv = nullptr; + + JavaVM *jvm = cc::JniHelper::getJavaVM(); + jint ret = jvm->GetEnv(reinterpret_cast(&_mEnv), JNI_VERSION_1_4); + switch (ret) { + case JNI_OK: + break; + + case JNI_EDETACHED: +#if CC_PLATFORM == CC_PLATFORM_ANDROID + if (jvm->AttachCurrentThread(&_mEnv, nullptr) < 0) { +#else + if (jvm->AttachCurrentThread(reinterpret_cast(&_mEnv), nullptr) < 0) { +#endif + SE_LOGD("%s", "Failed to get the environment using AttachCurrentThread()"); + _mError = JSJ_ERR_VM_THREAD_DETACHED; + return false; + } + break; + + case JNI_EVERSION: + default: + SE_LOGD("%s", "Failed to get the environment using GetEnv()"); + _mError = JSJ_ERR_VM_FAILURE; + return false; + } + jstring jstrClassName = _mEnv->NewStringUTF(_mClassName.c_str()); + _mClassID = static_cast(_mEnv->CallObjectMethod(cc::JniHelper::classloader, + cc::JniHelper::loadclassMethodMethodId, + jstrClassName)); + + if (nullptr == _mClassID) { + SE_LOGD("Classloader failed to find class of %s", _mClassName.c_str()); + ccDeleteLocalRef(_mEnv, jstrClassName); + _mEnv->ExceptionClear(); + _mError = JSJ_ERR_CLASS_NOT_FOUND; + return false; + } + + ccDeleteLocalRef(_mEnv, jstrClassName); + _mMethodID = _mEnv->GetStaticMethodID(_mClassID, _mMethodName.c_str(), _mMethodSig.c_str()); + if (!_mMethodID) { + _mEnv->ExceptionClear(); + SE_LOGD("Failed to find method id of %s.%s %s", + _mClassName.c_str(), + _mMethodName.c_str(), + _mMethodSig.c_str()); + _mError = JSJ_ERR_METHOD_NOT_FOUND; + return false; + } + + return true; +} + +bool JavaScriptJavaBridge::convertReturnValue(ReturnValue retValue, ValueType type, se::Value *ret) { + CC_ASSERT_NOT_NULL(ret); + switch (type) { + case JavaScriptJavaBridge::ValueType::INTEGER: + ret->setInt32(retValue.intValue); + break; + case JavaScriptJavaBridge::ValueType::LONG: + ret->setDouble(static_cast(retValue.longValue)); + break; + case JavaScriptJavaBridge::ValueType::FLOAT: + ret->setFloat(retValue.floatValue); + break; + case JavaScriptJavaBridge::ValueType::BOOLEAN: + ret->setBoolean(retValue.boolValue); + break; + case JavaScriptJavaBridge::ValueType::STRING: + if (retValue.stringValue) { + ret->setString(*retValue.stringValue); + } else { + ret->setNull(); + } + break; + default: + ret->setUndefined(); + break; + } + + return true; +} + +se::Class *__jsb_JavaScriptJavaBridge_class = nullptr; // NOLINT + +static bool JavaScriptJavaBridge_finalize(se::State &s) { //NOLINT(readability-identifier-naming, misc-unused-parameters) + return true; +} +SE_BIND_FINALIZE_FUNC(JavaScriptJavaBridge_finalize) + +static bool JavaScriptJavaBridge_constructor(se::State &s) { //NOLINT(readability-identifier-naming) + auto *cobj = ccnew JavaScriptJavaBridge(); + s.thisObject()->setPrivateData(cobj); + return true; +} +SE_BIND_CTOR(JavaScriptJavaBridge_constructor, __jsb_JavaScriptJavaBridge_class, JavaScriptJavaBridge_finalize) + +static bool JavaScriptJavaBridge_callStaticMethod(se::State &s) { //NOLINT(readability-identifier-naming) + const auto &args = s.args(); + auto argc = static_cast(args.size()); + + if (argc == 3) { + bool ok = false; + ccstd::string clsName; + ccstd::string methodName; + ccstd::string methodSig; + ok = sevalue_to_native(args[0], &clsName); + SE_PRECONDITION2(ok, false, "Converting class name failed!"); + + ok = sevalue_to_native(args[1], &methodName); + SE_PRECONDITION2(ok, false, "Converting method name failed!"); + + ok = sevalue_to_native(args[2], &methodSig); + SE_PRECONDITION2(ok, false, "Converting method signature failed!"); + + JavaScriptJavaBridge::CallInfo call(clsName.c_str(), methodName.c_str(), methodSig.c_str()); + if (call.isValid()) { + ok = call.execute(); + int errorCode = call.getErrorCode(); + if (!ok || errorCode < 0) { + call.tryThrowJSException(); + SE_REPORT_ERROR("call result code: %d", call.getErrorCode()); + return false; + } + JavaScriptJavaBridge::convertReturnValue(call.getReturnValue(), call.getReturnValueType(), &s.rval()); + return true; + } + call.tryThrowJSException(); + SE_REPORT_ERROR("JavaScriptJavaBridge::CallInfo isn't valid!"); + return false; + } + + if (argc > 3) { + bool ok = false; + ccstd::string clsName; + ccstd::string methodName; + ccstd::string methodSig; + ok = sevalue_to_native(args[0], &clsName); + SE_PRECONDITION2(ok, false, "Converting class name failed!"); + + ok = sevalue_to_native(args[1], &methodName); + SE_PRECONDITION2(ok, false, "Converting method name failed!"); + + ok = sevalue_to_native(args[2], &methodSig); + SE_PRECONDITION2(ok, false, "Converting method signature failed!"); + + JavaScriptJavaBridge::CallInfo call(clsName.c_str(), methodName.c_str(), methodSig.c_str()); + if (call.isValid() && call.getArgumentsCount() == (argc - 3)) { + int count = argc - 3; + auto *jargs = ccnew jvalue[count]; + ccstd::vector toReleaseObjects; + for (int i = 0; i < count; ++i) { + int index = i + 3; + switch (call.argumentTypeAtIndex(i)) { + case JavaScriptJavaBridge::ValueType::INTEGER: { + int integer = 0; + sevalue_to_native(args[index], &integer); + jargs[i].i = integer; + break; + } + case JavaScriptJavaBridge::ValueType::LONG: { + int64_t longVal = 0L; + sevalue_to_native(args[index], &longVal, nullptr); + jargs[i].j = longVal; + break; + } + case JavaScriptJavaBridge::ValueType::FLOAT: { + float floatNumber = 0.0F; + sevalue_to_native(args[index], &floatNumber); + jargs[i].f = floatNumber; + break; + } + case JavaScriptJavaBridge::ValueType::BOOLEAN: { + jargs[i].z = args[index].isBoolean() && args[index].toBoolean() ? JNI_TRUE : JNI_FALSE; + break; + } + case JavaScriptJavaBridge::ValueType::STRING: { + const auto &arg = args[index]; + if (arg.isNull() || arg.isUndefined()) { + jargs[i].l = nullptr; + } else { + ccstd::string str; + seval_to_std_string(args[index], &str); + jargs[i].l = call.getEnv()->NewStringUTF(str.c_str()); + toReleaseObjects.push_back(jargs[i].l); + } + + break; + } + default: + SE_REPORT_ERROR("Unsupport type of parameter %d", i); + break; + } + } + ok = call.executeWithArgs(jargs); + for (const auto &obj : toReleaseObjects) { + ccDeleteLocalRef(call.getEnv(), obj); + } + delete[] jargs; + int errorCode = call.getErrorCode(); + if (!ok || errorCode < 0) { + call.tryThrowJSException(); + SE_REPORT_ERROR("js_JSJavaBridge : call result code: %d", errorCode); + return false; + } + + JavaScriptJavaBridge::convertReturnValue(call.getReturnValue(), call.getReturnValueType(), &s.rval()); + return true; + } + call.tryThrowJSException(); + SE_REPORT_ERROR("call valid: %d, call.getArgumentsCount()= %d", call.isValid(), call.getArgumentsCount()); + return false; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting >=3", argc); + return false; +} +SE_BIND_FUNC(JavaScriptJavaBridge_callStaticMethod) + +static bool ScriptNativeBridge_getCallback(se::State &s) { //NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + CC_ASSERT_EQ(cobj, ScriptNativeBridge::bridgeCxxInstance); + s.rval() = cobj->jsCb; + SE_HOLD_RETURN_VALUE(cobj->jsCb, s.thisObject(), s.rval()); + return true; +} +SE_BIND_PROP_GET(ScriptNativeBridge_getCallback) + +static bool ScriptNativeBridge_setCallback(se::State &s) { //NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + CC_ASSERT_EQ(cobj, ScriptNativeBridge::bridgeCxxInstance); + const auto &args = s.args(); + se::Value jsFunc = args[0]; + cobj->jsCb = jsFunc; + if (jsFunc.isNullOrUndefined()) { + cobj->setCallback(nullptr); + } else { + CC_ASSERT(jsFunc.isObject() && jsFunc.toObject()->isFunction()); + s.thisObject()->attachObject(jsFunc.toObject()); + cobj->setCallback([jsFunc](const ccstd::string &arg0, const ccstd::string &arg1) { + se::AutoHandleScope hs; + se::ValueArray args; + args.push_back(se::Value(arg0)); + if (!arg1.empty()) { + args.push_back(se::Value(arg1)); + } + jsFunc.toObject()->call(args, nullptr); + }); + } + return true; +} +SE_BIND_PROP_SET(ScriptNativeBridge_setCallback) + +static bool ScriptNativeBridge_sendToNative(se::State &s) { //NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + if (argc >= 1 && argc < 3) { + bool ok = false; + ccstd::string arg0; + ok = sevalue_to_native(args[0], &arg0); + SE_PRECONDITION2(ok, false, "Converting arg0 failed!"); + ccstd::string arg1; + if (argc == 2) { + ok = sevalue_to_native(args[1], &arg1); + SE_PRECONDITION2(ok, false, "Converting arg1 failed!"); + } + callPlatformStringMethod(arg0, arg1); + SE_PRECONDITION2(ok, false, "call java method failed!"); + return ok; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting at least %d and less than %d", static_cast(argc), 1, 3); + return false; +} +SE_BIND_FUNC(ScriptNativeBridge_sendToNative) + +bool register_javascript_java_bridge(se::Object *obj) { //NOLINT(readability-identifier-naming) + se::Class *cls = se::Class::create("JavascriptJavaBridge", obj, nullptr, _SE(JavaScriptJavaBridge_constructor)); + cls->defineFinalizeFunction(_SE(JavaScriptJavaBridge_finalize)); + + cls->defineFunction("callStaticMethod", _SE(JavaScriptJavaBridge_callStaticMethod)); + + cls->install(); + __jsb_JavaScriptJavaBridge_class = cls; + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} + +se::Class *__jsb_ScriptNativeBridge_class = nullptr; // NOLINT + +static bool ScriptNativeBridge_finalize(se::State &s) { //NOLINT + //bridgeCxxInstance is an se object which will be deleted in se holder. + ScriptNativeBridge::bridgeCxxInstance = nullptr; + return true; +} +SE_BIND_FINALIZE_FUNC(ScriptNativeBridge_finalize) + +static bool ScriptNativeBridge_constructor(se::State &s) { //NOLINT(readability-identifier-naming) + auto *cobj = ccnew ScriptNativeBridge(); + s.thisObject()->setPrivateData(cobj); + ScriptNativeBridge::bridgeCxxInstance = cobj; + return true; +} +SE_BIND_CTOR(ScriptNativeBridge_constructor, __jsb_ScriptNativeBridge_class, ScriptNativeBridge_finalize) +bool register_script_native_bridge(se::Object *obj) { //NOLINT(readability-identifier-naming) + se::Class *cls = se::Class::create("ScriptNativeBridge", obj, nullptr, _SE(ScriptNativeBridge_constructor)); + cls->defineFinalizeFunction(_SE(ScriptNativeBridge_finalize)); + cls->defineFunction("sendToNative", _SE(ScriptNativeBridge_sendToNative)); + cls->defineProperty("onNative", _SE(ScriptNativeBridge_getCallback), _SE(ScriptNativeBridge_setCallback)); + + cls->install(); + __jsb_ScriptNativeBridge_class = cls; + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} +void callPlatformStringMethod(const ccstd::string &arg0, const ccstd::string &arg1) { + cc::JniHelper::callStaticVoidMethod( + "com/cocos/lib/JsbBridge", "callByScript", arg0, arg1); +} + +void ScriptNativeBridge::callByNative(const ccstd::string &arg0, const ccstd::string &arg1) { + _callback(arg0, arg1); +} diff --git a/cocos/bindings/manual/JavaScriptJavaBridge.h b/cocos/bindings/manual/JavaScriptJavaBridge.h new file mode 100644 index 0000000..6cf2c86 --- /dev/null +++ b/cocos/bindings/manual/JavaScriptJavaBridge.h @@ -0,0 +1,35 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "base/std/container/string.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "platform/java/jni/JniHelper.h" +namespace se { +class Object; +} + +bool register_javascript_java_bridge(se::Object *obj); // NOLINT(readability-identifier-naming) +bool register_script_native_bridge(se::Object *obj); // NOLINT(readability-identifier-naming) +void callPlatformStringMethod(const ccstd::string &arg0, const ccstd::string &arg1); // NOLINT(readability-identifier-naming) diff --git a/cocos/bindings/manual/JavaScriptObjCBridge.h b/cocos/bindings/manual/JavaScriptObjCBridge.h new file mode 100644 index 0000000..2f0674d --- /dev/null +++ b/cocos/bindings/manual/JavaScriptObjCBridge.h @@ -0,0 +1,35 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "base/std/container/string.h" +#include "cocos/bindings/jswrapper/SeApi.h" +namespace se { +class Object; +} + +bool register_javascript_objc_bridge(se::Object *obj); +bool register_script_native_bridge(se::Object *obj); // NOLINT(readability-identifier-naming) +bool callPlatformStringMethod(const ccstd::string &arg0, const ccstd::string &arg1); +void callScript(const ccstd::string &arg0, const ccstd::string &arg1); diff --git a/cocos/bindings/manual/JavaScriptObjCBridge.mm b/cocos/bindings/manual/JavaScriptObjCBridge.mm new file mode 100644 index 0000000..2ded066 --- /dev/null +++ b/cocos/bindings/manual/JavaScriptObjCBridge.mm @@ -0,0 +1,437 @@ +/**************************************************************************** + Copyright (c) 2018-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "JavaScriptObjCBridge.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" + +#include "base/std/container/string.h" +#include + +#import + +se::Value static objc_to_seval(id objcVal) { + se::Value ret; + if (objcVal == nil) + return ret; + + if ([objcVal isKindOfClass:[NSNumber class]]) { + NSNumber *number = (NSNumber *)objcVal; + ccstd::string numberType = [number objCType]; + if (numberType == @encode(BOOL) || numberType == @encode(bool)) { + ret.setBoolean([number boolValue]); + } else if (numberType == @encode(int) || numberType == @encode(long) || numberType == @encode(short) || numberType == @encode(unsigned int) || numberType == @encode(unsigned long) || numberType == @encode(unsigned short) || numberType == @encode(float) || numberType == @encode(double) || numberType == @encode(char) || numberType == @encode(unsigned char)) { + ret.setDouble([number doubleValue]); + } else { + CC_LOG_ERROR("Unknown number type: %s", numberType.c_str()); + } + } else if ([objcVal isKindOfClass:[NSString class]]) { + const char *content = [objcVal cStringUsingEncoding:NSUTF8StringEncoding]; + ret.setString(content); + } else if ([objcVal isKindOfClass:[NSDictionary class]]) { + CC_LOG_ERROR("JavaScriptObjCBridge doesn't support to bind NSDictionary!"); + } else { + const char *content = [[NSString stringWithFormat:@"%@", objcVal] cStringUsingEncoding:NSUTF8StringEncoding]; + ret.setString(content); + } + + return ret; +} +#define JSO_ERR_OK (0) +#define JSO_ERR_TYPE_NOT_SUPPORT (-1) +#define JSO_ERR_INVALID_ARGUMENTS (-2) +#define JSO_ERR_METHOD_NOT_FOUND (-3) +#define JSO_ERR_EXCEPTION_OCCURRED (-4) +#define JSO_ERR_CLASS_NOT_FOUND (-5) +#define JSO_ERR_VM_FAILURE (-6) + +class JavaScriptObjCBridge { +public: + class CallInfo { + public: + CallInfo(const char *className, const char *methodName) + : _className(className), + _methodName(methodName) {} + + ~CallInfo() {} + + int getErrorCode() const { + return _error; + } + + bool execute(const se::ValueArray &argv, se::Value &rval); + + private: + int _error{JSO_ERR_OK}; + ccstd::string _className; + ccstd::string _methodName; + }; +}; + +using JsCallback = std::function; + +class ScriptNativeBridge { +public: + void callByNative(const ccstd::string &arg0, const ccstd::string &arg1); + inline void setCallback(const JsCallback &cb) { + _callback = cb; + } + static ScriptNativeBridge *bridgeCxxInstance; + se::Value jsCb; + +private: + JsCallback _callback{nullptr}; // NOLINT(readability-identifier-naming) +}; +ScriptNativeBridge *ScriptNativeBridge::bridgeCxxInstance{nullptr}; +bool JavaScriptObjCBridge::CallInfo::execute(const se::ValueArray &argv, se::Value &rval) { + NSString *className = [NSString stringWithCString:_className.c_str() encoding:NSUTF8StringEncoding]; + NSString *methodName = [NSString stringWithCString:_methodName.c_str() encoding:NSUTF8StringEncoding]; + + if (!className || !methodName) { + _error = JSO_ERR_INVALID_ARGUMENTS; + return false; + } + + Class targetClass = NSClassFromString(className); + if (!targetClass) { + _error = JSO_ERR_CLASS_NOT_FOUND; + return false; + } + SEL methodSel; + methodSel = NSSelectorFromString(methodName); + if (!methodSel) { + _error = JSO_ERR_METHOD_NOT_FOUND; + return false; + } + methodSel = NSSelectorFromString(methodName); + NSMethodSignature *methodSig = [targetClass methodSignatureForSelector:(SEL)methodSel]; + if (methodSig == nil) { + _error = JSO_ERR_METHOD_NOT_FOUND; + NSLog(@"%@.%@ method isn't found!", className, methodName); + return false; + } + @try { + int argc = (int)argv.size(); + NSUInteger argumentCount = [methodSig numberOfArguments]; + if (argumentCount != argc) { + _error = JSO_ERR_INVALID_ARGUMENTS; + return false; + } + + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig]; + [invocation setTarget:targetClass]; + [invocation setSelector:methodSel]; + + for (int i = 2; i < argc; ++i) { + ccstd::string argumentType = [methodSig getArgumentTypeAtIndex:i]; + const se::Value &arg = argv[i]; + + /* - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx; + * + * Refer to https://developer.apple.com/documentation/foundation/nsinvocation/1437834-setargument?language=objc + * + * This method copies the contents of buffer as the argument at index. The number of bytes copied is determined by the argument size. + * When the argument value is an object, pass a pointer to the variable (or memory) from which the object should be copied: + */ + + if (arg.isString()) { + NSString *str = [NSString stringWithCString:arg.toString().c_str() encoding:NSUTF8StringEncoding]; + [invocation setArgument:&str atIndex:i]; + } else if (arg.isNumber()) { + if (argumentType == @encode(int)) { + int val = arg.toInt32(); + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == @encode(long)) { + long val = static_cast(arg.toDouble()); + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == @encode(short)) { + short val = arg.toInt16(); + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == @encode(unsigned int)) { + unsigned int val = arg.toUint32(); + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == @encode(unsigned long)) { + unsigned long val = static_cast(arg.toDouble()); + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == @encode(unsigned short)) { + unsigned short val = arg.toUint16(); + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == @encode(float)) { + float val = arg.toFloat(); + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == @encode(double)) { + double val = arg.toDouble(); + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == @encode(char)) { + char val = arg.toInt8(); + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == @encode(unsigned char)) { + unsigned char val = arg.toUint8(); + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == "@") { // NSNumber* + NSNumber *number = [NSNumber numberWithDouble:arg.toDouble()]; + [invocation setArgument:&number atIndex:i]; + } else { + NSLog(@"Unsupported argument type: %s", argumentType.c_str()); + _error = JSO_ERR_TYPE_NOT_SUPPORT; + return false; + } + } else if (arg.isBoolean()) { + if (argumentType == @encode(BOOL)) { + BOOL val = arg.toBoolean() ? YES : NO; + [invocation setArgument:&val atIndex:i]; + } else if (argumentType == @encode(bool)) { + bool val = arg.toBoolean(); + [invocation setArgument:&val atIndex:i]; + } else { + NSLog(@"Unsupported argument type: %s", argumentType.c_str()); + _error = JSO_ERR_TYPE_NOT_SUPPORT; + return false; + } + } else if (arg.isNullOrUndefined()) { + // Don't call [invocation setArgument] will pass nil to relevant Objective-C argument. + } else { + NSLog(@"Unsupported argument type, se::Value::Type: %d", (int)arg.getType()); + _error = JSO_ERR_TYPE_NOT_SUPPORT; + return false; + } + } + + NSUInteger returnLength = [methodSig methodReturnLength]; + ccstd::string returnType = [methodSig methodReturnType]; + [invocation invoke]; + + if (returnLength > 0) { + if (returnType == "@") { + id ret; + [invocation getReturnValue:&ret]; + rval = objc_to_seval(ret); + } else if (returnType == @encode(BOOL) || returnType == @encode(bool)) { + bool ret; + [invocation getReturnValue:&ret]; + rval.setBoolean(ret); + } else if (returnType == @encode(int)) { + int ret; + [invocation getReturnValue:&ret]; + rval.setInt32(ret); + } else if (returnType == @encode(long)) { + long ret; + [invocation getReturnValue:&ret]; + rval.setDouble(static_cast(ret)); + } else if (returnType == @encode(short)) { + short ret; + [invocation getReturnValue:&ret]; + rval.setInt16(ret); + } else if (returnType == @encode(unsigned int)) { + unsigned int ret; + [invocation getReturnValue:&ret]; + rval.setUint32(ret); + } else if (returnType == @encode(unsigned long)) { + unsigned long ret; + [invocation getReturnValue:&ret]; + rval.setDouble(static_cast(ret)); + } else if (returnType == @encode(unsigned short)) { + unsigned short ret; + [invocation getReturnValue:&ret]; + rval.setUint16(ret); + } else if (returnType == @encode(float)) { + float ret; + [invocation getReturnValue:&ret]; + rval.setFloat(ret); + } else if (returnType == @encode(double)) { + double ret; + [invocation getReturnValue:&ret]; + rval.setDouble(ret); + } else if (returnType == @encode(char)) { + int8_t ret; + [invocation getReturnValue:&ret]; + rval.setInt8(ret); + } else if (returnType == @encode(unsigned char)) { + uint8_t ret; + [invocation getReturnValue:&ret]; + rval.setUint8(ret); + } else { + _error = JSO_ERR_TYPE_NOT_SUPPORT; + NSLog(@"not support return type = %s", returnType.c_str()); + return false; + } + } + } @catch (NSException *exception) { + NSLog(@"EXCEPTION THROW: %@", exception); + _error = JSO_ERR_EXCEPTION_OCCURRED; + return false; + } + + return true; +} + +void ScriptNativeBridge::callByNative(const ccstd::string &arg0, const ccstd::string &arg1) { + _callback(arg0, arg1); +} + +se::Class *__jsb_JavaScriptObjCBridge_class = nullptr; + +static bool JavaScriptObjCBridge_finalize(se::State &s) { + return true; +} +SE_BIND_FINALIZE_FUNC(JavaScriptObjCBridge_finalize) + +static bool JavaScriptObjCBridge_constructor(se::State &s) { + JavaScriptObjCBridge *cobj = ccnew JavaScriptObjCBridge(); + s.thisObject()->setPrivateData(cobj); + return true; +} +SE_BIND_CTOR(JavaScriptObjCBridge_constructor, __jsb_JavaScriptObjCBridge_class, JavaScriptObjCBridge_finalize) + +static bool JavaScriptObjCBridge_callStaticMethod(se::State &s) { + const auto &args = s.args(); + int argc = (int)args.size(); + + if (argc >= 2) { + bool ok = false; + ccstd::string clsName, methodName; + ok = sevalue_to_native(args[0], &clsName); + SE_PRECONDITION2(ok, false, "Converting class name failed!"); + + ok = sevalue_to_native(args[1], &methodName); + SE_PRECONDITION2(ok, false, "Converting method name failed!"); + + JavaScriptObjCBridge::CallInfo call(clsName.c_str(), methodName.c_str()); + ok = call.execute(args, s.rval()); + if (!ok) { + s.rval().setUndefined(); + SE_REPORT_ERROR("call (%s.%s) failed, result code: %d", clsName.c_str(), methodName.c_str(), call.getErrorCode()); + return false; + } + + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting >=2", argc); + return false; +} +SE_BIND_FUNC(JavaScriptObjCBridge_callStaticMethod) + +static bool ScriptNativeBridge_getCallback(se::State &s) { + ScriptNativeBridge *cobj = (ScriptNativeBridge *)s.nativeThisObject(); + CC_ASSERT_EQ(cobj, ScriptNativeBridge::bridgeCxxInstance); + s.rval() = cobj->jsCb; + SE_HOLD_RETURN_VALUE(cobj->jsCb, s.thisObject(), s.rval()); + return true; +} +SE_BIND_PROP_GET(ScriptNativeBridge_getCallback) + +static bool ScriptNativeBridge_setCallback(se::State &s) { //NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + CC_ASSERT_EQ(cobj, ScriptNativeBridge::bridgeCxxInstance); + const auto &args = s.args(); + se::Value jsFunc = args[0]; + cobj->jsCb = jsFunc; + if (jsFunc.isNullOrUndefined()) { + cobj->setCallback(nullptr); + } else { + CC_ASSERT(jsFunc.isObject() && jsFunc.toObject()->isFunction()); + s.thisObject()->attachObject(jsFunc.toObject()); + cobj->setCallback([jsFunc](const ccstd::string &arg0, const ccstd::string &arg1) { + se::AutoHandleScope hs; + se::ValueArray args; + args.push_back(se::Value(arg0)); + if (!arg1.empty()) { + args.push_back(se::Value(arg1)); + } + jsFunc.toObject()->call(args, nullptr); + }); + } + return true; +} +SE_BIND_PROP_SET(ScriptNativeBridge_setCallback) + +static bool ScriptNativeBridge_sendToNative(se::State &s) { //NOLINT + const auto &args = s.args(); + int argc = (int)args.size(); + if (argc >= 1 && argc < 3) { + bool ok = false; + ccstd::string arg0; + ok = sevalue_to_native(args[0], &arg0); + SE_PRECONDITION2(ok, false, "Converting first argument failed!"); + ccstd::string arg1; + if (argc == 2) { + ok = sevalue_to_native(args[1], &arg1); + SE_PRECONDITION2(ok, false, "Converting second argument failed!"); + } + ok = callPlatformStringMethod(arg0, arg1); + SE_PRECONDITION2(ok, false, "call platform event failed!"); + return ok; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting at least %d and less than %d", argc, 1, 3); + return false; +} +SE_BIND_FUNC(ScriptNativeBridge_sendToNative) + +bool register_javascript_objc_bridge(se::Object *obj) { + se::Class *cls = se::Class::create("JavaScriptObjCBridge", obj, nullptr, _SE(JavaScriptObjCBridge_constructor)); + cls->defineFinalizeFunction(_SE(JavaScriptObjCBridge_finalize)); + + cls->defineFunction("callStaticMethod", _SE(JavaScriptObjCBridge_callStaticMethod)); + cls->install(); + __jsb_JavaScriptObjCBridge_class = cls; + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} + +se::Class *__jsb_ScriptNativeBridge_class = nullptr; // NOLINT + +static bool ScriptNativeBridge_finalize(se::State &s) { //NOLINT(readability-identifier-naming) + //bridgeCxxInstance is an se object which will be deleted in se holder. + ScriptNativeBridge::bridgeCxxInstance = nullptr; + return true; +} +SE_BIND_FINALIZE_FUNC(ScriptNativeBridge_finalize) + +static bool ScriptNativeBridge_constructor(se::State &s) { //NOLINT(readability-identifier-naming) + auto *cobj = ccnew ScriptNativeBridge(); + s.thisObject()->setPrivateData(cobj); + ScriptNativeBridge::bridgeCxxInstance = cobj; + return true; +} +SE_BIND_CTOR(ScriptNativeBridge_constructor, __jsb_ScriptNativeBridge_class, ScriptNativeBridge_finalize) +bool register_script_native_bridge(se::Object *obj) { //NOLINT(readability-identifier-naming) + se::Class *cls = se::Class::create("ScriptNativeBridge", obj, nullptr, _SE(ScriptNativeBridge_constructor)); + cls->defineFinalizeFunction(_SE(ScriptNativeBridge_finalize)); + cls->defineFunction("sendToNative", _SE(ScriptNativeBridge_sendToNative)); + cls->defineProperty("onNative", _SE(ScriptNativeBridge_getCallback), _SE(ScriptNativeBridge_setCallback)); + + cls->install(); + __jsb_ScriptNativeBridge_class = cls; + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} +void callScript(const ccstd::string &arg0, const ccstd::string &arg1) { + ScriptNativeBridge::bridgeCxxInstance->callByNative(arg0, arg1); +} diff --git a/cocos/bindings/manual/jsb_adpf.cpp b/cocos/bindings/manual/jsb_adpf.cpp new file mode 100644 index 0000000..108d5a7 --- /dev/null +++ b/cocos/bindings/manual/jsb_adpf.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*****************************************************************************/ + +#include "application/ApplicationManager.h" +#include "bindings/jswrapper/SeApi.h" + +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include "platform/android/adpf_manager.h" +#endif + +#if CC_PLATFORM == CC_PLATFORM_ANDROID && CC_SUPPORT_ADPF + +struct VmCallback { + uint32_t vmId{0xFEFEFEFE}; + se::Value *cbFn{nullptr}; + void reset() { + vmId = 0xFEFEFEFE; + delete cbFn; + cbFn = nullptr; + } +}; +static VmCallback vmCallback; +static bool jsb_adpf_onThermalStatusChanged_set(se::State &state) { // NOLINT + + auto fn = state.args()[0]; + vmCallback.reset(); + if (fn.isNullOrUndefined()) { + return true; + } + CC_ASSERT_TRUE(fn.toObject()->isFunction()); + auto *scriptEngine = se::ScriptEngine::getInstance(); + if (vmCallback.vmId != scriptEngine->getVMId()) { + vmCallback.vmId = scriptEngine->getVMId(); + scriptEngine->addBeforeCleanupHook([]() { + vmCallback.reset(); + }); + } + + vmCallback.cbFn = new se::Value(fn.toObject(), true); + // NOLINTNEXTLINE + ADPFManager::getInstance().SetThermalListener(+[](int prevStatus, int currentStatus) { + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() { + se::AutoHandleScope scope; + se::ValueArray args; + args.push_back(se::Value(prevStatus)); + args.push_back(se::Value(currentStatus)); + args.push_back(se::Value(ATHERMAL_STATUS_NONE)); + args.push_back(se::Value(ATHERMAL_STATUS_SHUTDOWN)); + CC_ASSERT_EQ(vmCallback.vmId, se::ScriptEngine::getInstance()->getVMId()); + if (vmCallback.cbFn && vmCallback.cbFn->isObject() && vmCallback.cbFn->toObject()->isFunction()) { + vmCallback.cbFn->toObject()->call(args, nullptr); + } + }); + }); + return true; +} +SE_BIND_PROP_SET(jsb_adpf_onThermalStatusChanged_set) + +static bool jsb_adpf_onThermalStatusChanged_get(se::State &state) { // NOLINT + if (!vmCallback.cbFn) { + state.rval().setUndefined(); + } else { + state.rval().setObject(vmCallback.cbFn->toObject()); + } + return true; +} +SE_BIND_PROP_GET(jsb_adpf_onThermalStatusChanged_get) + +static bool jsb_adpf_getThermalStatus(se::State &state) { // NOLINT + int statusInt = ADPFManager::getInstance().GetThermalStatus(); + state.rval().setUint32(statusInt); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalStatus) + +static bool jsb_adpf_getThermalStatusMin(se::State &state) { // NOLINT + state.rval().setUint32(ATHERMAL_STATUS_NONE); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalStatusMin) +static bool jsb_adpf_getThermalStatusMax(se::State &state) { // NOLINT + state.rval().setUint32(ATHERMAL_STATUS_SHUTDOWN); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalStatusMax) + +static bool jsb_adpf_getThermalStatusNormalized(se::State &state) { // NOLINT + float statusNormalized = ADPFManager::getInstance().GetThermalStatusNormalized(); + state.rval().setFloat(statusNormalized); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalStatusNormalized) + +static bool jsb_adpf_getThermalHeadroom(se::State &state) { // NOLINT + float headroom = ADPFManager::getInstance().GetThermalHeadroom(); + state.rval().setFloat(headroom); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalHeadroom) + +void jsb_register_ADPF(se::Object *ns) { // NOLINT + se::Value adpfObj{se::Object::createPlainObject()}; + adpfObj.toObject()->defineProperty("thermalHeadroom", _SE(jsb_adpf_getThermalHeadroom), nullptr); + adpfObj.toObject()->defineProperty("thermalStatus", _SE(jsb_adpf_getThermalStatus), nullptr); + adpfObj.toObject()->defineProperty("thermalStatusMin", _SE(jsb_adpf_getThermalStatusMin), nullptr); + adpfObj.toObject()->defineProperty("thermalStatusMax", _SE(jsb_adpf_getThermalStatusMax), nullptr); + adpfObj.toObject()->defineProperty("thermalStatusNormalized", _SE(jsb_adpf_getThermalStatusNormalized), nullptr); + adpfObj.toObject()->defineProperty("thermalHeadroom", _SE(jsb_adpf_getThermalHeadroom), nullptr); + adpfObj.toObject()->defineProperty("onThermalStatusChanged", _SE(jsb_adpf_onThermalStatusChanged_get), _SE(jsb_adpf_onThermalStatusChanged_set)); + ns->setProperty("adpf", adpfObj); +} +#else +void jsb_register_ADPF(se::Object *ns) {} // NOLINT +#endif // CC_PLATFORM_ANDROID diff --git a/cocos/bindings/manual/jsb_assets_manual.cpp b/cocos/bindings/manual/jsb_assets_manual.cpp new file mode 100644 index 0000000..41db639 --- /dev/null +++ b/cocos/bindings/manual/jsb_assets_manual.cpp @@ -0,0 +1,139 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "bindings/auto/jsb_assets_auto.h" +#include "core/assets/Material.h" +#include "core/assets/SimpleTexture.h" +#include "core/assets/TextureBase.h" +#include "core/data/JSBNativeDataHolder.h" +#include "jsb_scene_manual.h" + +#ifndef JSB_ALLOC + #define JSB_ALLOC(kls, ...) new (std::nothrow) kls(__VA_ARGS__) +#endif + +#ifndef JSB_FREE + #define JSB_FREE(ptr) delete ptr +#endif + +static bool js_assets_ImageAsset_setData(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + uint8_t *data{nullptr}; + if (args[0].isObject()) { + if (args[0].toObject()->isTypedArray()) { + args[0].toObject()->getTypedArrayData(&data, nullptr); + } else if (args[0].toObject()->isArrayBuffer()) { + args[0].toObject()->getArrayBufferData(&data, nullptr); + } else { + auto *dataHolder = static_cast(args[0].toObject()->getPrivateData()); + CC_ASSERT_NOT_NULL(dataHolder); + data = dataHolder->getData(); + } + } else { + CC_ABORTF("setData with '%s'", args[0].toStringForce().c_str()); + } + cobj->setData(data); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 0); + return false; +} +SE_BIND_FUNC(js_assets_ImageAsset_setData) // NOLINT(readability-identifier-naming) + +static bool js_assets_SimpleTexture_registerListeners(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + auto *thisObj = s.thisObject(); + + cobj->on([thisObj](cc::SimpleTexture * /*emitter*/, cc::gfx::Texture *texture) { + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(texture, arg0, nullptr); + se::ScriptEngine::getInstance()->callFunction(thisObj, "_onGFXTextureUpdated", 1, &arg0); + }); + + cobj->on([thisObj](cc::SimpleTexture * /*emitter*/, cc::ImageAsset *image) { + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(image, arg0, nullptr); + se::ScriptEngine::getInstance()->callFunction(thisObj, "_onAfterAssignImage", 1, &arg0); + }); + + return true; +} +SE_BIND_FUNC(js_assets_SimpleTexture_registerListeners) // NOLINT(readability-identifier-naming) + +static bool js_assets_TextureBase_registerGFXSamplerUpdatedListener(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + auto *thisObj = s.thisObject(); + cobj->on([thisObj](cc::TextureBase * /*emitter*/, cc::gfx::Sampler *sampler) { + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(sampler, arg0, nullptr); + se::ScriptEngine::getInstance()->callFunction(thisObj, "_onGFXSamplerUpdated", 1, &arg0); + }); + + return true; +} +SE_BIND_FUNC(js_assets_TextureBase_registerGFXSamplerUpdatedListener) // NOLINT(readability-identifier-naming) + +static bool js_assets_Material_registerPassesUpdatedListener(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + auto *thisObj = s.thisObject(); + cobj->on([thisObj](cc::Material * /*emitter*/) { + se::AutoHandleScope hs; + se::ScriptEngine::getInstance()->callFunction(thisObj, "_onPassesUpdated", 0, nullptr); + }); + + return true; +} +SE_BIND_FUNC(js_assets_Material_registerPassesUpdatedListener) // NOLINT(readability-identifier-naming) + +bool register_all_assets_manual(se::Object *obj) // NOLINT(readability-identifier-naming) +{ + // Get the ns + se::Value nsVal; + if (!obj->getProperty("jsb", &nsVal)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + obj->setProperty("jsb", nsVal); + } + + __jsb_cc_ImageAsset_proto->defineFunction("setData", _SE(js_assets_ImageAsset_setData)); + __jsb_cc_SimpleTexture_proto->defineFunction("_registerListeners", _SE(js_assets_SimpleTexture_registerListeners)); + __jsb_cc_TextureBase_proto->defineFunction("_registerGFXSamplerUpdatedListener", _SE(js_assets_TextureBase_registerGFXSamplerUpdatedListener)); + __jsb_cc_Material_proto->defineFunction("_registerPassesUpdatedListener", _SE(js_assets_Material_registerPassesUpdatedListener)); + + return true; +} diff --git a/cocos/bindings/manual/jsb_assets_manual.h b/cocos/bindings/manual/jsb_assets_manual.h new file mode 100644 index 0000000..d4ced57 --- /dev/null +++ b/cocos/bindings/manual/jsb_assets_manual.h @@ -0,0 +1,33 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "cocos/bindings/jswrapper/SeApi.h" + +namespace se { +class Object; +} + +bool register_all_assets_manual(se::Object *obj); // NOLINT(readability-identifier-naming) diff --git a/cocos/bindings/manual/jsb_audio_manual.cpp b/cocos/bindings/manual/jsb_audio_manual.cpp new file mode 100644 index 0000000..42745da --- /dev/null +++ b/cocos/bindings/manual/jsb_audio_manual.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_audio_manual.h" +#include +#include "State.h" +#include "audio/include/AudioDef.h" +#include "audio/include/AudioEngine.h" +#include "bindings/auto/jsb_audio_auto.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" + +// NOLINTNEXTLINE(readability-identifier-naming) +static bool PCMHeader_to_seval(PCMHeader& header, se::Value* ret) { + CC_ASSERT_NOT_NULL(ret); + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty("totalFrames", se::Value(header.totalFrames)); + obj->setProperty("sampleRate", se::Value(header.sampleRate)); + obj->setProperty("bytesPerFrame", se::Value(header.bytesPerFrame)); + obj->setProperty("channelCount", se::Value(header.channelCount)); + obj->setProperty("audioFormat", se::Value(static_cast(header.dataFormat))); + ret->setObject(obj); + + return true; +} +static bool js_audio_AudioEngine_getPCMHeader(se::State& s) // NOLINT +{ + const auto& args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + ccstd::string arg0; + ok &= sevalue_to_native(args[0], &arg0, nullptr); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + PCMHeader header = cc::AudioEngine::getPCMHeader(arg0.c_str()); + ok &= PCMHeader_to_seval(header, &s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_audio_AudioEngine_getPCMHeader) + +static bool js_audio_AudioEngine_getOriginalPCMBuffer(se::State& s) // NOLINT +{ + const auto& args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 2) { + ccstd::string arg0; // url of audio + ok &= sevalue_to_native(args[0], &arg0, nullptr); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + uint32_t arg1{0}; + ok &= sevalue_to_native(args[1], &arg1, nullptr); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + ccstd::vector buffer = cc::AudioEngine::getOriginalPCMBuffer(arg0.c_str(), arg1); + se::HandleObject obj(se::Object::createArrayBufferObject(buffer.data(), buffer.size())); + s.rval().setObject(obj); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_HOLD_RETURN_VALUE(result, s.thisObject(), s.rval()); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_audio_AudioEngine_getOriginalPCMBuffer) + +bool register_all_audio_manual(se::Object* obj) // NOLINT +{ + se::Value jsbVal; + obj->getProperty("jsb", &jsbVal); + se::Value audioEngineVal; + jsbVal.toObject()->getProperty("AudioEngine", &audioEngineVal); + + audioEngineVal.toObject()->defineFunction("getPCMHeader", _SE(js_audio_AudioEngine_getPCMHeader)); + audioEngineVal.toObject()->defineFunction("getOriginalPCMBuffer", _SE(js_audio_AudioEngine_getOriginalPCMBuffer)); + return true; +} diff --git a/cocos/bindings/manual/jsb_audio_manual.h b/cocos/bindings/manual/jsb_audio_manual.h new file mode 100644 index 0000000..d873877 --- /dev/null +++ b/cocos/bindings/manual/jsb_audio_manual.h @@ -0,0 +1,35 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +// clang-format off +#pragma once + +#include +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/audio/include/AudioEngine.h" + +bool register_all_audio_manual(se::Object *obj); // NOLINT + + diff --git a/cocos/bindings/manual/jsb_classtype.cpp b/cocos/bindings/manual/jsb_classtype.cpp new file mode 100644 index 0000000..00fcf26 --- /dev/null +++ b/cocos/bindings/manual/jsb_classtype.cpp @@ -0,0 +1,27 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_classtype.h" + +ccstd::unordered_map JSBClassType::jsbClassTypeMap; diff --git a/cocos/bindings/manual/jsb_classtype.h b/cocos/bindings/manual/jsb_classtype.h new file mode 100644 index 0000000..81c5762 --- /dev/null +++ b/cocos/bindings/manual/jsb_classtype.h @@ -0,0 +1,64 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "bindings/jswrapper/SeApi.h" + +#include + +class JSBClassType { +public: + template + static void registerClass(se::Class *cls) { + const char *typeName = typeid(T).name(); + CC_ASSERT(jsbClassTypeMap.find(typeName) == jsbClassTypeMap.end()); + jsbClassTypeMap.emplace(typeName, cls); + } + + template + static se::Class *findClass(const T *nativeObj) { + bool found = false; + const char *typeNameFromValue = typeid(*nativeObj).name(); + auto iter = jsbClassTypeMap.find(typeNameFromValue); + if (iter == jsbClassTypeMap.end()) { + const char *typeNameFromType = typeid(T).name(); + iter = jsbClassTypeMap.find(typeNameFromType); + if (iter != jsbClassTypeMap.end()) { + found = true; + jsbClassTypeMap.emplace(typeNameFromValue, iter->second); + } + } else { + found = true; + } + return found ? iter->second : nullptr; + } + + static void cleanup() { + jsbClassTypeMap.clear(); + } + +private: + static ccstd::unordered_map jsbClassTypeMap; +}; diff --git a/cocos/bindings/manual/jsb_cocos_manual.cpp b/cocos/bindings/manual/jsb_cocos_manual.cpp new file mode 100644 index 0000000..30ba3d6 --- /dev/null +++ b/cocos/bindings/manual/jsb_cocos_manual.cpp @@ -0,0 +1,813 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_cocos_manual.h" + +#include "bindings/manual/jsb_global.h" +#include "cocos/bindings/auto/jsb_cocos_auto.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global_init.h" + +#include "application/ApplicationManager.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" +#include "storage/local-storage/LocalStorage.h" + +extern se::Object *__jsb_cc_FileUtils_proto; // NOLINT(readability-redundant-declaration, readability-identifier-naming) + +static bool jsb_ccx_empty_func(se::State & /*s*/) { // NOLINT(readability-identifier-naming) + return true; +} +SE_BIND_FUNC(jsb_ccx_empty_func) // NOLINT(readability-identifier-naming) + +class JSPlistDelegator : public cc::SAXDelegator { +public: + static JSPlistDelegator *getInstance() { + static JSPlistDelegator *pInstance = nullptr; + if (pInstance == nullptr) { + pInstance = ccnew JSPlistDelegator(); + } + return pInstance; + }; + + ~JSPlistDelegator() override; + + cc::SAXParser *getParser(); + + ccstd::string parse(const ccstd::string &path); + ccstd::string parseText(const ccstd::string &text); + + // implement pure virtual methods of SAXDelegator + void startElement(void *ctx, const char *name, const char **atts) override; + void endElement(void *ctx, const char *name) override; + void textHandler(void *ctx, const char *ch, int len) override; + +private: + cc::SAXParser _parser; + ccstd::string _result; + bool _isStoringCharacters; + ccstd::string _currentValue; +}; + +// cc.PlistParser.getInstance() +static bool js_PlistParser_getInstance(se::State &s) { // NOLINT(readability-identifier-naming) + JSPlistDelegator *delegator = JSPlistDelegator::getInstance(); + cc::SAXParser *parser = delegator->getParser(); + + if (parser) { + native_ptr_to_seval(parser, __jsb_cc_SAXParser_class, &s.rval()); + s.rval().toObject()->root(); + return true; + } + return false; +} +SE_BIND_FUNC(js_PlistParser_getInstance) // NOLINT(readability-identifier-naming) + +// cc.PlistParser.getInstance().parse(text) +static bool js_PlistParser_parse(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + JSPlistDelegator *delegator = JSPlistDelegator::getInstance(); + + bool ok = true; + if (argc == 1) { + ccstd::string arg0; + ok &= sevalue_to_native(args[0], &arg0); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + ccstd::string parsedStr = delegator->parseText(arg0); + std::replace(parsedStr.begin(), parsedStr.end(), '\n', ' '); + + se::Value strVal; + nativevalue_to_se(parsedStr, strVal); + + se::HandleObject robj(se::Object::createJSONObject(strVal.toString())); + s.rval().setObject(robj); + return true; + } + SE_REPORT_ERROR("js_PlistParser_parse : wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_PlistParser_parse) + +cc::SAXParser *JSPlistDelegator::getParser() { + return &_parser; +} + +ccstd::string JSPlistDelegator::parse(const ccstd::string &path) { + _result.clear(); + + cc::SAXParser parser; + if (parser.init("UTF-8")) { + parser.setDelegator(this); + parser.parse(cc::FileUtils::getInstance()->fullPathForFilename(path)); + } + + return _result; +} + +JSPlistDelegator::~JSPlistDelegator() { + CC_LOG_INFO("deallocing __JSSAXDelegator: %p", this); +} + +ccstd::string JSPlistDelegator::parseText(const ccstd::string &text) { + _result.clear(); + + cc::SAXParser parser; + if (parser.init("UTF-8")) { + parser.setDelegator(this); + parser.parse(text.c_str(), text.size()); + } + + return _result; +} + +void JSPlistDelegator::startElement(void * /*ctx*/, const char *name, const char ** /*atts*/) { + _isStoringCharacters = true; + _currentValue.clear(); + + ccstd::string elementName{name}; + + auto end = static_cast(_result.size()) - 1; + if (end >= 0 && _result[end] != '{' && _result[end] != '[' && _result[end] != ':') { + _result += ","; + } + + if (elementName == "dict") { + _result += "{"; + } else if (elementName == "array") { + _result += "["; + } +} + +void JSPlistDelegator::endElement(void * /*ctx*/, const char *name) { + _isStoringCharacters = false; + ccstd::string elementName{name}; + + if (elementName == "dict") { + _result += "}"; + } else if (elementName == "array") { + _result += "]"; + } else if (elementName == "key") { + _result += "\"" + _currentValue + "\":"; + } else if (elementName == "string") { + _result += "\"" + _currentValue + "\""; + } else if (elementName == "false" || elementName == "true") { + _result += elementName; + } else if (elementName == "real" || elementName == "integer") { + _result += _currentValue; + } +} + +void JSPlistDelegator::textHandler(void * /*unused*/, const char *ch, int len) { + ccstd::string text(ch, 0, len); + + if (_isStoringCharacters) { + _currentValue += text; + } +} + +static bool register_plist_parser(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) + se::Value v; + __jsbObj->getProperty("PlistParser", &v); + CC_ASSERT(v.isObject()); + v.toObject()->defineFunction("getInstance", _SE(js_PlistParser_getInstance)); + + __jsb_cc_SAXParser_proto->defineFunction("parse", _SE(js_PlistParser_parse)); + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} + +// cc.sys.localStorage + +static bool JSB_localStorageGetItem(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + bool ok = true; + ccstd::string key; + ok = sevalue_to_native(args[0], &key); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + ccstd::string value; + ok = localStorageGetItem(key, &value); + if (ok) { + s.rval().setString(value); + } else { + s.rval().setNull(); // Should return null to make JSB behavior same as Browser since returning undefined will make JSON.parse(undefined) trigger exception. + } + + return true; + } + + SE_REPORT_ERROR("Invalid number of arguments"); + return false; +} +SE_BIND_FUNC(JSB_localStorageGetItem) // NOLINT(readability-identifier-naming) + +static bool JSB_localStorageRemoveItem(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + bool ok = true; + ccstd::string key; + ok = sevalue_to_native(args[0], &key); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + localStorageRemoveItem(key); + return true; + } + + SE_REPORT_ERROR("Invalid number of arguments"); + return false; +} +SE_BIND_FUNC(JSB_localStorageRemoveItem) // NOLINT(readability-identifier-naming) + +static bool JSB_localStorageSetItem(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 2) { + bool ok = true; + ccstd::string key; + ok = sevalue_to_native(args[0], &key); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + ccstd::string value = args[1].toStringForce(); + localStorageSetItem(key, value); + return true; + } + + SE_REPORT_ERROR("Invalid number of arguments"); + return false; +} +SE_BIND_FUNC(JSB_localStorageSetItem) // NOLINT(readability-identifier-naming) + +static bool JSB_localStorageClear(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 0) { + localStorageClear(); + return true; + } + + SE_REPORT_ERROR("Invalid number of arguments"); + return false; +} +SE_BIND_FUNC(JSB_localStorageClear) // NOLINT(readability-identifier-naming) + +static bool JSB_localStorageKey(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + bool ok = true; + int nIndex = 0; + ok = sevalue_to_native(args[0], &nIndex); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + ccstd::string value; + localStorageGetKey(nIndex, &value); + s.rval().setString(value); + return true; + } + + SE_REPORT_ERROR("Invalid number of arguments"); + return false; +} +SE_BIND_FUNC(JSB_localStorageKey) // NOLINT(readability-identifier-naming) + +static bool JSB_localStorage_getLength(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 0) { + int nLength = 0; + + localStorageGetLength(nLength); + s.rval().setInt32(nLength); + return true; + } + + SE_REPORT_ERROR("Invalid number of arguments"); + return false; +} +SE_BIND_PROP_GET(JSB_localStorage_getLength); // NOLINT(readability-identifier-naming) + +static bool register_sys_localStorage(se::Object *obj) { // NOLINT(readability-identifier-naming) + se::Value sys; + if (!obj->getProperty("sys", &sys)) { + se::HandleObject sysObj(se::Object::createPlainObject()); + obj->setProperty("sys", se::Value(sysObj)); + sys.setObject(sysObj); + } + + se::HandleObject localStorageObj(se::Object::createPlainObject()); + sys.toObject()->setProperty("localStorage", se::Value(localStorageObj)); + + localStorageObj->defineFunction("getItem", _SE(JSB_localStorageGetItem)); + localStorageObj->defineFunction("removeItem", _SE(JSB_localStorageRemoveItem)); + localStorageObj->defineFunction("setItem", _SE(JSB_localStorageSetItem)); + localStorageObj->defineFunction("clear", _SE(JSB_localStorageClear)); + localStorageObj->defineFunction("key", _SE(JSB_localStorageKey)); + localStorageObj->defineProperty("length", _SE(JSB_localStorage_getLength), nullptr); + + ccstd::string strFilePath = cc::FileUtils::getInstance()->getWritablePath(); +#if defined(__QNX__) + // In the QNX environment, the execution of this statement will not take effect. + // Not sure why + // strFilePath += "/jsb.sqlite"; + + // Use another way + char path[256] = {0}; + sprintf(path, "%s/jsb.sqlite", strFilePath.c_str()); + localStorageInit(path); +#else + strFilePath += "/jsb.sqlite"; + localStorageInit(strFilePath); +#endif + + se::ScriptEngine::getInstance()->addBeforeCleanupHook([]() { + localStorageFree(); + }); + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} + +//IDEA: move to auto bindings. +static bool js_CanvasRenderingContext2D_setCanvasBufferUpdatedCallback(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + std::function arg0; + do { + if (args[0].isObject() && args[0].toObject()->isFunction()) { + se::Value jsThis(s.thisObject()); + se::Value jsFunc(args[0]); + jsThis.toObject()->attachObject(jsFunc.toObject()); + se::Object *thisObj = s.thisObject(); + auto lambda = [=](const cc::Data &larg0) -> void { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + CC_UNUSED bool ok = true; + se::ValueArray args; + args.resize(1); + ok &= Data_to_TypedArray(larg0, &args[0]); + se::Value rval; + se::Object *funcObj = jsFunc.toObject(); + bool succeed = funcObj->call(args, thisObj, &rval); + if (!succeed) { + se::ScriptEngine::getInstance()->clearException(); + } + }; + // Add an unroot to avoid the root of the copy constructor caused by the internal reference of Lambda. + if (thisObj) { + thisObj->unroot(); + } + jsFunc.toObject()->unroot(); + arg0 = lambda; + } else { + arg0 = nullptr; + } + } while (false); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->setCanvasBufferUpdatedCallback(arg0); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_CanvasRenderingContext2D_setCanvasBufferUpdatedCallback) // NOLINT(readability-identifier-naming) + +static void setCanvasRenderingContext2DProps(cc::ICanvasRenderingContext2D *context, const se::Value &val) { + se::Object *props = val.toObject(); + se::Value propVal; + + props->getProperty("lineWidth", &propVal); + if (!propVal.isUndefined()) context->setLineWidth(propVal.toFloat()); + + props->getProperty("lineJoin", &propVal); + if (!propVal.isUndefined()) context->setLineJoin(propVal.toString()); + + props->getProperty("fillStyle", &propVal); + if (!propVal.isUndefined()) context->setFillStyle(propVal.toString()); + + props->getProperty("font", &propVal); + if (!propVal.isUndefined()) context->setFont(propVal.toString()); + + props->getProperty("lineCap", &propVal); + if (!propVal.isUndefined()) context->setLineCap(propVal.toString()); + + props->getProperty("textAlign", &propVal); + if (!propVal.isUndefined()) context->setTextAlign(propVal.toString()); + + props->getProperty("textBaseline", &propVal); + if (!propVal.isUndefined()) context->setTextBaseline(propVal.toString()); + + props->getProperty("strokeStyle", &propVal); + if (!propVal.isUndefined()) context->setStrokeStyle(propVal.toString()); + + props->getProperty("globalCompositeOperation", &propVal); + if (!propVal.isUndefined()) context->setGlobalCompositeOperation(propVal.toString()); + + props->getProperty("shadowBlur", &propVal); + if (!propVal.isUndefined()) context->setShadowBlur(propVal.toFloat()); + + props->getProperty("shadowColor", &propVal); + if (!propVal.isUndefined()) context->setShadowColor(propVal.toString()); + + props->getProperty("shadowOffsetX", &propVal); + if (!propVal.isUndefined()) context->setShadowOffsetX(propVal.toFloat()); + + props->getProperty("shadowOffsetY", &propVal); + if (!propVal.isUndefined()) context->setShadowOffsetY(propVal.toFloat()); +} + +static bool js_engine_CanvasRenderingContext2D_measureText(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 2) { + ccstd::string arg0; + ok &= sevalue_to_native(args[0], &arg0); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + SE_PRECONDITION2(args[1].isObject(), false, "no attributes set."); + setCanvasRenderingContext2DProps(cobj, args[1]); + cc::Size result = cobj->measureText(arg0); + ok &= nativevalue_to_se(result, s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_engine_CanvasRenderingContext2D_measureText) // NOLINT(readability-identifier-naming) + +static bool js_engine_CanvasRenderingContext2D_fillRect(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 5) { + float arg0 = 0; + float arg1 = 0; + float arg2 = 0; + float arg3 = 0; + ok &= sevalue_to_native(args[0], &arg0); + ok &= sevalue_to_native(args[1], &arg1); + ok &= sevalue_to_native(args[2], &arg2); + ok &= sevalue_to_native(args[3], &arg3); + SE_PRECONDITION2(args[4].isObject(), false, "no attributes set."); + setCanvasRenderingContext2DProps(cobj, args[4]); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->fillRect(arg0, arg1, arg2, arg3); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 4); + return false; +} +SE_BIND_FUNC(js_engine_CanvasRenderingContext2D_fillRect) // NOLINT(readability-identifier-naming) + +static bool js_engine_CanvasRenderingContext2D_fillText(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 5) { + ccstd::string arg0; + float arg1 = 0; + float arg2 = 0; + float arg3 = 0; + ok &= sevalue_to_native(args[0], &arg0); + ok &= sevalue_to_native(args[1], &arg1); + ok &= sevalue_to_native(args[2], &arg2); + SE_PRECONDITION2(args[4].isObject(), false, "no attributes set."); + setCanvasRenderingContext2DProps(cobj, args[4]); + if (args[3].isUndefined()) { + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->fillText(arg0, arg1, arg2, -1.0F); + } else { + ok &= sevalue_to_native(args[3], &arg3); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->fillText(arg0, arg1, arg2, arg3); + } + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 4); + return false; +} +SE_BIND_FUNC(js_engine_CanvasRenderingContext2D_fillText) // NOLINT(readability-identifier-naming) + +static bool js_engine_CanvasRenderingContext2D_strokeText(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + bool ok = true; + if (argc == 5) { + ccstd::string arg0; + float arg1 = 0; + float arg2 = 0; + float arg3 = 0; + ok &= sevalue_to_native(args[0], &arg0); + ok &= sevalue_to_native(args[1], &arg1); + ok &= sevalue_to_native(args[2], &arg2); + SE_PRECONDITION2(args[4].isObject(), false, "no attributes set."); + setCanvasRenderingContext2DProps(cobj, args[4]); + if (!args[3].isUndefined()) { + ok &= sevalue_to_native(args[3], &arg3); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->strokeText(arg0, arg1, arg2, arg3); + } else { + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->strokeText(arg0, arg1, arg2, -1.0F); + } + + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 4); + return false; +} +SE_BIND_FUNC(js_engine_CanvasRenderingContext2D_strokeText) // NOLINT(readability-identifier-naming) + +static se::Object *deviceMotionObject = nullptr; +static bool JSB_getDeviceMotionValue(se::State &s) { // NOLINT(readability-identifier-naming) + if (deviceMotionObject == nullptr) { + deviceMotionObject = se::Object::createArrayObject(9); + deviceMotionObject->root(); + } + + const auto &v = cc::Device::getDeviceMotionValue(); + + deviceMotionObject->setArrayElement(0, se::Value(v.accelerationX)); + deviceMotionObject->setArrayElement(1, se::Value(v.accelerationY)); + deviceMotionObject->setArrayElement(2, se::Value(v.accelerationZ)); + deviceMotionObject->setArrayElement(3, se::Value(v.accelerationIncludingGravityX)); + deviceMotionObject->setArrayElement(4, se::Value(v.accelerationIncludingGravityY)); + deviceMotionObject->setArrayElement(5, se::Value(v.accelerationIncludingGravityZ)); + deviceMotionObject->setArrayElement(6, se::Value(v.rotationRateAlpha)); + deviceMotionObject->setArrayElement(7, se::Value(v.rotationRateBeta)); + deviceMotionObject->setArrayElement(8, se::Value(v.rotationRateGamma)); + + s.rval().setObject(deviceMotionObject); + return true; +} +SE_BIND_FUNC(JSB_getDeviceMotionValue) // NOLINT(readability-identifier-naming) + +static bool register_device(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) + se::Value device; + __jsbObj->getProperty("Device", &device); + + device.toObject()->defineFunction("getDeviceMotionValue", _SE(JSB_getDeviceMotionValue)); + + se::ScriptEngine::getInstance()->addBeforeCleanupHook([]() { + if (deviceMotionObject != nullptr) { + deviceMotionObject->unroot(); + deviceMotionObject->decRef(); + deviceMotionObject = nullptr; + } + }); + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +static bool register_canvas_context2d(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) + __jsb_cc_ICanvasRenderingContext2D_proto->defineFunction("_setCanvasBufferUpdatedCallback", _SE(js_CanvasRenderingContext2D_setCanvasBufferUpdatedCallback)); + __jsb_cc_ICanvasRenderingContext2D_proto->defineFunction("fillText", _SE(js_engine_CanvasRenderingContext2D_fillText)); + __jsb_cc_ICanvasRenderingContext2D_proto->defineFunction("strokeText", _SE(js_engine_CanvasRenderingContext2D_strokeText)); + __jsb_cc_ICanvasRenderingContext2D_proto->defineFunction("fillRect", _SE(js_engine_CanvasRenderingContext2D_fillRect)); + __jsb_cc_ICanvasRenderingContext2D_proto->defineFunction("measureText", _SE(js_engine_CanvasRenderingContext2D_measureText)); + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} + +static bool js_engine_FileUtils_listFilesRecursively(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 2) { + ccstd::string arg0; + ccstd::vector arg1; + ok &= sevalue_to_native(args[0], &arg0); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->listFilesRecursively(arg0, &arg1); + se::Object *list = args[1].toObject(); + SE_PRECONDITION2(args[1].isObject() && list->isArray(), false, "2nd argument should be an Array"); + for (uint32_t i = 0; i < static_cast(arg1.size()); i++) { + list->setArrayElement(i, se::Value(arg1[i])); + } + list->setProperty("length", se::Value(static_cast(arg1.size()))); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2); + return false; +} +SE_BIND_FUNC(js_engine_FileUtils_listFilesRecursively) // NOLINT(readability-identifier-naming) + +static bool js_se_setExceptionCallback(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + if (args.size() != 1 || !args[0].isObject() || !args[0].toObject()->isFunction()) { + SE_REPORT_ERROR("expect 1 arguments of Function type, %d provided", (int)args.size()); + return false; + } + + se::Object *objFunc = args[0].toObject(); + // se::Value::reset will invoke decRef() while destroying s.args() + // increase ref here + objFunc->incRef(); + if (s.thisObject()) { + s.thisObject()->attachObject(objFunc); // prevent GC + } else { + //prevent GC in C++ & JS + objFunc->root(); + } + + se::ScriptEngine::getInstance()->setJSExceptionCallback([objFunc](const char *location, const char *message, const char *stack) { + se::AutoHandleScope scope; + se::ValueArray jsArgs; + jsArgs.resize(3); + jsArgs[0] = se::Value(location); + jsArgs[1] = se::Value(message); + jsArgs[2] = se::Value(stack); + objFunc->call(jsArgs, nullptr); + }); + + se::ScriptEngine::getInstance()->addBeforeCleanupHook([objFunc] { + objFunc->decRef(); + }); + + return true; +} +SE_BIND_FUNC(js_se_setExceptionCallback) // NOLINT(readability-identifier-naming) + +static bool register_filetuils_ext(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) + __jsb_cc_FileUtils_proto->defineFunction("listFilesRecursively", _SE(js_engine_FileUtils_listFilesRecursively)); + return true; +} + +static bool register_se_setExceptionCallback(se::Object *obj) { // NOLINT(readability-identifier-naming) + se::Value jsb; + if (!obj->getProperty("jsb", &jsb)) { + jsb.setObject(se::Object::createPlainObject()); + obj->setProperty("jsb", jsb); + } + auto *jsbObj = jsb.toObject(); + jsbObj->defineFunction("onError", _SE(js_se_setExceptionCallback)); + + return true; +} + +static bool js_engine_Color_get_val(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + CC_UNUSED bool ok = true; + se::Value jsret; + auto r = static_cast(cobj->r); + auto g = static_cast(cobj->g); + auto b = static_cast(cobj->b); + auto a = static_cast(cobj->a); + uint32_t val = (a << 24) + (b << 16) + (g << 8) + r; + ok &= nativevalue_to_se(val, jsret, s.thisObject() /*ctx*/); + s.rval() = jsret; + return true; +} +SE_BIND_PROP_GET(js_engine_Color_get_val) + +static bool js_engine_Color_set_val(se::State &s) // NOLINT(readability-identifier-naming) +{ + const auto &args = s.args(); + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + CC_UNUSED bool ok = true; + uint32_t val{0}; + ok &= sevalue_to_native(args[0], &val, s.thisObject()); + cobj->r = val & 0x000000FF; + cobj->g = (val & 0x0000FF00) >> 8; + cobj->b = (val & 0x00FF0000) >> 16; + cobj->a = (val & 0xFF000000) >> 24; + SE_PRECONDITION2(ok, false, "Error processing new value"); + return true; +} +SE_BIND_PROP_SET(js_engine_Color_set_val) + +static bool register_engine_Color_manual(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) + __jsb_cc_Color_proto->defineProperty("_val", _SE(js_engine_Color_get_val), _SE(js_engine_Color_set_val)); + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} +static bool js_cc_ISystemWindowManager_getInstance_static(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + CC_UNUSED bool ok = true; + auto *instance = CC_GET_PLATFORM_INTERFACE(cc::ISystemWindowManager); + ok &= nativevalue_to_se(instance, s.rval(), s.thisObject()); + SE_PRECONDITION2(ok, false, "js_cc_ISystemWindowManager_getInstance Failed"); + + return true; +} +SE_BIND_FUNC(js_cc_ISystemWindowManager_getInstance_static) + +static bool register_platform(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) + se::Value constructor; + bool result = __jsb_cc_ISystemWindowManager_proto->getProperty("constructor", &constructor); + result &= constructor.toObject()->defineFunction("getInstance", _SE(js_cc_ISystemWindowManager_getInstance_static)); + return result; +} + +template +static bool bindAsExternalBuffer(se::State &s) { // NOLINT + auto *self = SE_THIS_OBJECT(s); + if (!self) { + return false; + } + // NOLINTNEXTLINE + se::HandleObject buffer(se::Object::createExternalArrayBufferObject(self, sizeof(*self), [](void *, size_t, void *) {})); + s.rval().setObject(buffer); + return true; +} + +static bool js_cc_Vec2_underlyingData(se::State &s) { // NOLINT + return bindAsExternalBuffer(s); +} +SE_BIND_FUNC(js_cc_Vec2_underlyingData) + +static bool js_cc_Vec3_underlyingData(se::State &s) { // NOLINT + return bindAsExternalBuffer(s); +} +SE_BIND_FUNC(js_cc_Vec3_underlyingData) + +static bool js_cc_Vec4_underlyingData(se::State &s) { // NOLINT + return bindAsExternalBuffer(s); +} +SE_BIND_FUNC(js_cc_Vec4_underlyingData) + +static bool js_cc_Mat3_underlyingData(se::State &s) { // NOLINT + return bindAsExternalBuffer(s); +} +SE_BIND_FUNC(js_cc_Mat3_underlyingData) + +static bool js_cc_Mat4_underlyingData(se::State &s) { // NOLINT + return bindAsExternalBuffer(s); +} +SE_BIND_FUNC(js_cc_Mat4_underlyingData) + +static bool js_cc_Quaternion_underlyingData(se::State &s) { // NOLINT + return bindAsExternalBuffer(s); +} +SE_BIND_FUNC(js_cc_Quaternion_underlyingData) + +bool register_all_cocos_manual(se::Object *obj) { // NOLINT(readability-identifier-naming) + + __jsb_cc_Vec2_proto->defineFunction("underlyingData", _SE(js_cc_Vec2_underlyingData)); + __jsb_cc_Vec3_proto->defineFunction("underlyingData", _SE(js_cc_Vec3_underlyingData)); + __jsb_cc_Vec4_proto->defineFunction("underlyingData", _SE(js_cc_Vec4_underlyingData)); + __jsb_cc_Mat3_proto->defineFunction("underlyingData", _SE(js_cc_Mat3_underlyingData)); + __jsb_cc_Mat4_proto->defineFunction("underlyingData", _SE(js_cc_Mat4_underlyingData)); + __jsb_cc_Quaternion_proto->defineFunction("underlyingData", _SE(js_cc_Quaternion_underlyingData)); + + register_plist_parser(obj); + register_sys_localStorage(obj); + register_device(obj); + register_canvas_context2d(obj); + register_filetuils_ext(obj); + register_engine_Color_manual(obj); + register_se_setExceptionCallback(obj); + register_platform(obj); + return true; +} diff --git a/cocos/bindings/manual/jsb_cocos_manual.h b/cocos/bindings/manual/jsb_cocos_manual.h new file mode 100644 index 0000000..bfb4fb0 --- /dev/null +++ b/cocos/bindings/manual/jsb_cocos_manual.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { +class Object; +} + +bool register_all_cocos_manual(se::Object *obj); diff --git a/cocos/bindings/manual/jsb_conversions.h b/cocos/bindings/manual/jsb_conversions.h new file mode 100644 index 0000000..4855aec --- /dev/null +++ b/cocos/bindings/manual/jsb_conversions.h @@ -0,0 +1,1579 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include "MappingUtils.h" +#include "base/Macros.h" +#include "base/Ptr.h" +#include "base/RefCounted.h" +#include "base/std/any.h" +#include "base/std/container/map.h" +#include "base/std/container/vector.h" +#include "base/std/optional.h" +#include "base/std/variant.h" +#include "bindings/jswrapper/HandleObject.h" +#include "bindings/jswrapper/SeApi.h" +#include "bindings/manual/jsb_classtype.h" +#include "jsb_conversions_spec.h" +#include "core/data/JSBNativeDataHolder.h" + +#if CC_USE_SPINE + #include "cocos/editor-support/spine-creator-support/spine-cocos2dx.h" +#endif + +#include "core/assets/EffectAsset.h" +#include "core/geometry/Geometry.h" +#include "math/Color.h" +#include "math/Math.h" +#include "renderer/gfx-base/states/GFXSampler.h" + +#include "base/HasMemberFunction.h" + +#define SE_PRECONDITION2_VOID(condition, ...) \ + do { \ + if (!(condition)) { \ + DO_CC_LOG_ERROR("jsb: ERROR: File %s: Line: %d, Function: %s\n", __FILE__, __LINE__, __FUNCTION__); \ + DO_CC_LOG_ERROR(__VA_ARGS__); \ + return; \ + } \ + } while (0) + +#define SE_PRECONDITION2_FUNCNAME_VOID(condition, funcName, ...) \ + do { \ + if (!(condition)) { \ + DO_CC_LOG_ERROR("jsb: ERROR: File %s: Line: %d, Function: %s\n", __FILE__, __LINE__, funcName); \ + DO_CC_LOG_ERROR(__VA_ARGS__); \ + return; \ + } \ + } while (0) + +#define SE_PRECONDITION2(condition, ret_value, ...) \ + do { \ + if (!(condition)) { \ + DO_CC_LOG_ERROR("jsb: ERROR: File %s: Line: %d, Function: %s\n", __FILE__, __LINE__, __FUNCTION__); \ + DO_CC_LOG_ERROR(__VA_ARGS__); \ + return (ret_value); \ + } \ + } while (0) + +#define SE_PRECONDITION3(condition, ret_value, failed_code) \ + do { \ + if (!(condition)) { \ + failed_code; \ + return (ret_value); \ + } \ + } while (0) + +#define SE_PRECONDITION4(condition, ret_value, errorCode) \ + do { \ + if (!(condition)) { \ + DO_CC_LOG_ERROR("jsb: ERROR: File %s: Line: %d, Function: %s\n", __FILE__, __LINE__, __FUNCTION__); \ + __glErrorCode = errorCode; \ + return (ret_value); \ + } \ + } while (0) + +#define SE_PRECONDITION_ERROR_BREAK(condition, ...) \ + if (!(condition)) { \ + DO_CC_LOG_ERROR("jsb: ERROR: File %s: Line: %d, Function: %s\n", __FILE__, __LINE__, __FUNCTION__); \ + DO_CC_LOG_ERROR(__VA_ARGS__); \ + break; \ + } + +#if CC_ENABLE_CACHE_JSB_FUNC_RESULT + #define SE_HOLD_RETURN_VALUE(retCXXValue, thisObject, jsValue) \ + if (is_jsb_object_v::type>) { \ + (thisObject)->setProperty(ccstd::string("__cache") + __FUNCTION__, (jsValue)); \ + } +#else + #define SE_HOLD_RETURN_VALUE(...) +#endif + +template +bool seval_to_native_ptr(const se::Value &v, T *ret) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(ret); + + if (v.isObject()) { + T ptr = static_cast(v.toObject()->getPrivateData()); + if (ptr == nullptr) { + // This should never happen, return 'false' to mark the conversion fails. + *ret = nullptr; + return false; + } + + *ret = ptr; + return true; + } + if (v.isNullOrUndefined()) { + // If js value is null or undefined, the convertion should be successful. + // So we should return 'true' to indicate the convertion succeeds and mark + // the out value to 'nullptr'. + *ret = nullptr; + return true; + } + + // If js value isn't null, undefined and Object, mark the convertion fails. + *ret = nullptr; + return false; +} + +template +typename std::enable_if::value && !std::is_same::value, T>::type +seval_to_type(const se::Value &v, bool &ok) { // NOLINT(readability-identifier-naming) + if (!v.isObject()) { + ok = false; + return T(); + } + T *nativeObj = static_cast(v.toObject()->getPrivateData()); + ok = true; + return *nativeObj; +} + +template +typename std::enable_if::value, T>::type +seval_to_type(const se::Value &v, bool &ok) { // NOLINT(readability-identifier-naming) + if (!v.isNumber()) { + ok = false; + return 0; + } + ok = true; + return v.toInt32(); +} + +template +typename std::enable_if::value, T>::type +seval_to_type(const se::Value &v, bool &ok) { // NOLINT(readability-identifier-naming) + if (!v.isNumber()) { + ok = false; + return static_cast(0); + } + ok = true; + return static_cast(v.toInt32()); +} + +template +typename std::enable_if::value, T>::type +seval_to_type(const se::Value &v, bool &ok) { // NOLINT(readability-identifier-naming) + if (!v.isNumber()) { + ok = false; + return 0; + } + ok = true; + return v.toFloat(); +} + +template +typename std::enable_if::value, T>::type +seval_to_type(const se::Value &v, bool &ok) { // NOLINT(readability-identifier-naming) + if (!v.isString()) { + ok = false; + return ""; + } + ok = true; + return v.toString(); +} + +inline se::HandleObject unwrapProxyObject(se::Object *obj) { + if (obj->isProxy()) { + return se::HandleObject(se::Object::createProxyTarget(obj)); + } + obj->incRef(); + return se::HandleObject(obj); +} + +template +typename std::enable_if::value && std::is_class::type>::value, bool>::type +seval_to_std_vector(const se::Value &v, ccstd::vector *ret) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(ret); + CC_ASSERT(v.isObject()); + se::HandleObject array(unwrapProxyObject(v.toObject())); + CC_ASSERT(array->isArray()); + + bool ok = true; + uint32_t len = 0; + ok = array->getArrayLength(&len); + if (!ok) { + ret->clear(); + return false; + } + + ret->resize(len); + + se::Value tmp; + for (uint32_t i = 0; i < len; ++i) { + ok = array->getArrayElement(i, &tmp); + if (!ok) { + ret->clear(); + return false; + } + + if (tmp.isObject()) { + T nativeObj = static_cast(tmp.toObject()->getPrivateData()); + (*ret)[i] = nativeObj; + } else if (tmp.isNullOrUndefined()) { + (*ret)[i] = nullptr; + } else { + ret->clear(); + return false; + } + } + + return true; +} + +template +typename std::enable_if::value, bool>::type +seval_to_std_vector(const se::Value &v, ccstd::vector *ret) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(ret); + CC_ASSERT(v.isObject()); + se::HandleObject array(unwrapProxyObject(v.toObject())); + CC_ASSERT(array->isArray()); + + bool ok = true; + uint32_t len = 0; + ok = array->getArrayLength(&len); + if (!ok) { + ret->clear(); + return false; + } + + ret->resize(len); + + se::Value tmp; + for (uint32_t i = 0; i < len; ++i) { + ok = array->getArrayElement(i, &tmp); + if (!ok) { + ret->clear(); + return false; + } + (*ret)[i] = seval_to_type(tmp, ok); + if (!ok) { + return false; + } + } + + return true; +} + +template +bool seval_to_Map_string_key(const se::Value &v, cc::RefMap *ret) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(ret); + CC_ASSERT(v.isObject()); + se::Object *obj = v.toObject(); + + ccstd::vector allKeys; + bool ok = obj->getAllKeys(&allKeys); + if (!ok) { + ret->clear(); + return false; + } + + se::Value tmp; + for (const auto &key : allKeys) { + ok = obj->getProperty(key.c_str(), &tmp); + if (!ok || !tmp.isObject()) { + ret->clear(); + return false; + } + + T nativeObj = static_cast(tmp.toObject()->getPrivateData()); + ret->insert(key, nativeObj); + } + + return true; +} + +template +void cc_tmp_set_private_data(se::Object *obj, T *v) { // NOLINT(readability-identifier-naming) + if constexpr (std::is_base_of_v) { + obj->setPrivateData(v); + } else { + obj->setRawPrivateData(v); + } +} + +// handle reference +template +typename std::enable_if::value, bool>::type +native_ptr_to_seval(T &v_ref, se::Value *ret, bool *isReturnCachedValue = nullptr) { // NOLINT(readability-identifier-naming) + using DecayT = typename std::decay::type>::type; + auto *v = const_cast(&v_ref); + + CC_ASSERT_NOT_NULL(ret); + if (v == nullptr) { + ret->setNull(); + return true; + } + + se::Class *cls = JSBClassType::findClass(v); + se::NativePtrToObjectMap::filter(v, cls) + .forEach([&](se::Object *foundObj) { + if (isReturnCachedValue != nullptr) { + *isReturnCachedValue = true; + } + ret->setObject(foundObj); + }) + .orElse([&]() { + // If we couldn't find native object in map, then the native object is created from native code. e.g. TMXLayer::getTileAt + // CC_LOG_DEBUGWARN("WARNING: non-Ref type: (%s) isn't catched!", typeid(*v).name()); + CC_ASSERT_NOT_NULL(cls); + se::Object *obj = se::Object::createObjectWithClass(cls); + ret->setObject(obj, true); + cc_tmp_set_private_data(obj, v); + + se::Value property; + bool foundCtor = false; + if (!cls->_getCtor().has_value()) { + foundCtor = obj->getProperty("_ctor", &property, true); + if (foundCtor) { + cls->_setCtor(property.toObject()); + } else { + cls->_setCtor(nullptr); + } + } else { + auto *ctorObj = cls->_getCtor().value(); + if (ctorObj != nullptr) { + property.setObject(ctorObj); + foundCtor = true; + } + } + + if (foundCtor) { + property.toObject()->call(se::EmptyValueArray, obj); + } + + if (isReturnCachedValue != nullptr) { + *isReturnCachedValue = false; + } + }); + + return true; +} + +template +bool native_ptr_to_seval(T *vp, se::Class *cls, se::Value *ret, bool *isReturnCachedValue = nullptr) { // NOLINT(readability-identifier-naming) + using DecayT = typename std::decay::type>::type; + auto *v = const_cast(vp); + CC_ASSERT_NOT_NULL(ret); + if (v == nullptr) { + ret->setNull(); + return true; + } + + if constexpr (cc::has_getScriptObject::value) { + if (v->getScriptObject() != nullptr) { + if (isReturnCachedValue != nullptr) { + *isReturnCachedValue = true; + } + ret->setObject(v->getScriptObject()); + return true; + } + } + + se::NativePtrToObjectMap::filter(v, cls) + .forEach( + [&](se::Object *foundObj) { + if (isReturnCachedValue != nullptr) { + *isReturnCachedValue = true; + } + ret->setObject(foundObj); + }) + .orElse([&]() { + // If we couldn't find native object in map, then the native object is created from native code. e.g. TMXLayer::getTileAt + // CC_LOG_DEBUGWARN("WARNING: Ref type: (%s) isn't catched!", typeid(*v).name()); + CC_ASSERT_NOT_NULL(cls); + auto *obj = se::Object::createObjectWithClass(cls); + ret->setObject(obj, true); + cc_tmp_set_private_data(obj, v); + + se::Value property; + bool foundCtor = false; + if (!cls->_getCtor().has_value()) { + foundCtor = obj->getProperty("_ctor", &property, true); + if (foundCtor) { + cls->_setCtor(property.toObject()); + } else { + cls->_setCtor(nullptr); + } + } else { + auto *ctorObj = cls->_getCtor().value(); + if (ctorObj != nullptr) { + property.setObject(ctorObj); + foundCtor = true; + } + } + + if (foundCtor) { + property.toObject()->call(se::EmptyValueArray, obj); + } + + if (isReturnCachedValue != nullptr) { + *isReturnCachedValue = false; + } + }); + + return true; +} + +template +bool native_ptr_to_seval(T *vp, se::Value *ret, bool *isReturnCachedValue = nullptr) { // NOLINT(readability-identifier-naming) + using DecayT = typename std::decay::type>::type; + auto *v = const_cast(vp); + CC_ASSERT_NOT_NULL(ret); + if (v == nullptr) { + ret->setNull(); + return true; + } + + se::Class *cls = JSBClassType::findClass(v); + return native_ptr_to_seval(vp, cls, ret, isReturnCachedValue); +} +template +bool std_vector_to_seval(const ccstd::vector &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(ret); + bool ok = true; + se::HandleObject obj(se::Object::createArrayObject(v.size())); + + uint32_t i = 0; + se::Value tmp; + for (const auto &e : v) { + native_ptr_to_seval(e, &tmp); + obj->setArrayElement(i, tmp); + ++i; + } + + ret->setObject(obj, true); + + return ok; +} + +template +bool seval_to_reference(const se::Value &v, T **ret) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(ret); + CC_ASSERT(v.isObject()); + *ret = static_cast(v.toObject()->getPrivateData()); + return true; +} + +/////////////////////////////////// helpers ////////////////////////////////////////////////////////// + +////////////////////////// is jsb object /////////////////////////// + +template +struct is_jsb_object : std::false_type {}; // NOLINT(readability-identifier-naming) + +template +constexpr bool is_jsb_object_v = is_jsb_object>>::value; // NOLINT + +#define JSB_REGISTER_OBJECT_TYPE(T) \ + template <> \ + struct is_jsb_object : std::true_type {} + +template +constexpr inline typename std::enable_if::value, Out>::type & +holder_convert_to(In &input) { // NOLINT(readability-identifier-naming) + return input; +} + +template +constexpr inline typename std::enable_if::value && std::is_same::type>::value, Out>::type & +holder_convert_to(In &input) { // NOLINT(readability-identifier-naming) + return static_cast(&input); +} + +template +constexpr inline typename std::enable_if::value && std::is_same::type>::value, Out>::type & +holder_convert_to(In &input) { // NOLINT(readability-identifier-naming) + return *input; +} + +template +struct HolderType { + using type = typename std::remove_const::type>::type; + using local_type = typename std::conditional_t, std::add_pointer_t, type>; + local_type data; + type *ptr = nullptr; + struct alignas(T) EmbedField { + uint8_t inlineObject[is_reference && is_jsb_object_v ? sizeof(T) : 1]; + } inlineObject; + + constexpr inline type &value() { + if (ptr) return *ptr; + return holder_convert_to(data); + } + ~HolderType() { + // delete ptr; + if (ptr) { + ptr->~type(); + } + } +}; + +template <> +struct HolderType { + using type = const char *; + using local_type = ccstd::string; + local_type data; + std::remove_const_t *ptr = nullptr; + inline type value() const { return data.c_str(); } +}; + +template <> +struct HolderType { + using type = const char *; + using local_type = ccstd::string; + local_type data; + std::remove_const_t *ptr = nullptr; + inline type value() const { return data.c_str(); } +}; + +///////////////////////////////////convertion////////////////////////////////////////////////////////// + +////////////////// optional +template +struct is_optional : std::false_type {}; // NOLINT + +template +struct is_optional> : std::true_type {}; // NOLINT + +template +struct is_variant : std::false_type {}; // NOLINT +template +struct is_variant> : std::true_type {}; // NOLINT + +template +inline typename std::enable_if_t::value && !std::is_pointer::value && !is_jsb_object_v, bool> +sevalue_to_native(const se::Value & /*from*/, T * /*to*/, se::Object * /*unused*/) { // NOLINT(readability-identifier-naming) + SE_LOGE("Missing conversion impl `sevalue_to_native` for type [[%s]]\n", typeid(T).name()); + static_assert(!is_variant::value, "should not match cc::variant"); + static_assert((std::is_same::value), "Type incorrect or implementation not found!"); + return false; +} + +template +inline typename std::enable_if_t::value && !std::is_pointer::value && is_jsb_object_v, bool> +sevalue_to_native(const se::Value &from, T *to, se::Object * /*unused*/) { // NOLINT(readability-identifier-naming) + auto *obj = from.toObject(); + if constexpr (std::is_copy_assignable::value) { + *to = *static_cast(obj->getPrivateData()); + } else { + CC_ABORT(); // can not copy + } + return true; +} + +template +inline typename std::enable_if_t::value, bool> +sevalue_to_native(const se::Value &from, T *to, se::Object *ctx) { // NOLINT(readability-identifier-naming) + typename std::underlying_type_t tmp; + bool ret = sevalue_to_native(from, &tmp, ctx); + if (ret) *to = static_cast(tmp); + return ret; +} + +//////////////////////////////// forward declaration : sevalue_to_native //////////////////////////////// + +// ccstd::variant<...>>ss +template +bool sevalue_to_native(const se::Value &from, ccstd::variant *to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +template +bool sevalue_to_native(const se::Value &from, ccstd::optional *to, se::Object *ctx); // NOLINT(readability-identifier-naming) +/// ccstd::unordered_map +template +bool sevalue_to_native(const se::Value &from, ccstd::unordered_map *to, se::Object *ctx); // NOLINT(readability-identifier-naming) +/// ccstd::map +template +bool sevalue_to_native(const se::Value &from, ccstd::map *to, se::Object *ctx); // NOLINT(readability-identifier-naming) +// std::tuple +template +bool sevalue_to_native(const se::Value &from, std::tuple *to, se::Object *ctx); // NOLINT(readability-identifier-naming) +// std::shared_ptr +template +bool sevalue_to_native(const se::Value &from, std::shared_ptr> *out, se::Object *ctx); // NOLINT(readability-identifier-naming) +template +bool sevalue_to_native(const se::Value &from, std::shared_ptr *out, se::Object *ctx); // NOLINT(readability-identifier-naming) +// ccstd::vector +template +bool sevalue_to_native(const se::Value &from, ccstd::vector *to, se::Object *ctx); // NOLINT(readability-identifier-naming) +// ccstd::vector +template +bool sevalue_to_native(const se::Value &from, ccstd::array *to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +template +bool sevalue_to_native(const se::Value &from, cc::IntrusivePtr *to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +//////////////////// ccstd::array + +template +bool sevalue_to_native(const se::Value &from, ccstd::array *to, se::Object *ctx) { // NOLINT(readability-identifier-naming) + CC_ASSERT(from.toObject()); + se::Object *array = from.toObject(); + CC_ASSERT(array->isArray()); + uint32_t len = 0; + array->getArrayLength(&len); + se::Value tmp; + CC_ASSERT_GE(len, CNT); + for (uint32_t i = 0; i < CNT; i++) { + array->getArrayElement(i, &tmp); + sevalue_to_native(tmp, &(*to)[i], ctx); + } + return true; +} + +template +bool sevalue_to_native(const se::Value &from, ccstd::array *to, se::Object *ctx) { // NOLINT(readability-identifier-naming) + CC_ASSERT(from.toObject()); + se::Object *array = from.toObject(); + CC_ASSERT(array->isArray() || array->isArrayBuffer() || array->isTypedArray()); + if (array->isTypedArray()) { + uint8_t *data = nullptr; + size_t size = 0; + array->getTypedArrayData(&data, &size); + for (size_t i = 0; i < std::min(size, CNT); i++) { + (*to)[i] = data[i]; + } + } else if (array->isArrayBuffer()) { + uint8_t *data = nullptr; + size_t size = 0; + array->getArrayBufferData(&data, &size); + for (size_t i = 0; i < std::min(size, CNT); i++) { + (*to)[i] = data[i]; + } + } else if (array->isArray()) { + uint32_t len = 0; + array->getArrayLength(&len); + se::Value tmp; + CC_ASSERT_GE(len, CNT); + for (size_t i = 0; i < CNT; i++) { + array->getArrayElement(static_cast(i), &tmp); + sevalue_to_native(tmp, &(*to)[i], ctx); + } + } else { + return false; + } + return true; +} + +template +bool sevalue_to_native(const se::Value &from, ccstd::variant> *to, se::Object *ctx) { // NOLINT + bool ok = false; + if (from.isObject() && from.toObject()->isArray()) { + ccstd::vector result; + ok = sevalue_to_native(from, &result, ctx); + if (ok) { + *to = std::move(result); + } + } else { + T result{}; + ok = sevalue_to_native(from, &result, ctx); + if (ok) { + *to = std::move(result); + } + } + return ok; +} + +////////////////// TypedArray + +template +inline typename std::enable_if::value, bool>::type +sevalue_to_native(const se::Value &from, cc::TypedArrayTemp *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to->setJSTypedArray(from.toObject()); + return true; +} + +////////////////// pointer types + +template +typename std::enable_if_t::value && is_jsb_object_v, bool> +sevalue_to_native(const se::Value &from, T **to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + if (from.isNullOrUndefined()) { + // const ccstd::string stack = se::ScriptEngine::getInstance()->getCurrentStackTrace(); + // SE_LOGE("[ERROR] sevalue_to_native jsval is null/undefined: %s\nstack: %s", typeid(T).name(), stack.c_str()); + *to = nullptr; + return true; + } + *to = static_cast(from.toObject()->getPrivateData()); + return true; +} + +// duplicate extern to resolve the circular reference jsb_cocos_auto.h +// see jsb_cocos_auto.h +extern se::Class *__jsb_cc_JSBNativeDataHolder_class; // NOLINT + +template +typename std::enable_if_t::value && std::is_arithmetic::value, bool> +sevalue_to_native(const se::Value &from, T **to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + se::Object *data = from.toObject(); + uint8_t *tmp; + if (data->isArrayBuffer()) { + data->getArrayBufferData(&tmp, nullptr); + } else if (data->isTypedArray()) { + data->getTypedArrayData(&tmp, nullptr); + } else { + void *privateData = data->getPrivateData(); + if (privateData != nullptr && data->_getClass() == __jsb_cc_JSBNativeDataHolder_class) { + auto *dataHolder = static_cast(privateData); + if (dataHolder != nullptr) { + tmp = dataHolder->getData(); + } else { + CC_ABORT(); // bad type + return false; + } + } else { + CC_ABORT(); // bad type + return false; + } + } + *to = reinterpret_cast(tmp); + return true; +} + +template +typename std::enable_if_t::value && is_jsb_object_v, bool> +sevalue_to_native(const se::Value &from, T ***to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + if (from.isNullOrUndefined()) { + // const ccstd::string stack = se::ScriptEngine::getInstance()->getCurrentStackTrace(); + // SE_LOGE("[ERROR] sevalue_to_native jsval is null/undefined: %s\nstack: %s", typeid(T).name(), stack.c_str()); + *to = nullptr; + return true; + } + **to = static_cast(from.toObject()->getPrivateData()); + return true; +} + +template +bool sevalue_to_native(const se::Value &from, ccstd::vector *to, se::Object *ctx) { // NOLINT(readability-identifier-naming) + + if (from.isNullOrUndefined()) { + to->clear(); + return true; + } + + CC_ASSERT(from.toObject()); + se::HandleObject array(unwrapProxyObject(from.toObject())); + + if (array->isArray()) { + uint32_t len = 0; + array->getArrayLength(&len); + to->resize(len); + se::Value tmp; + for (uint32_t i = 0; i < len; i++) { + array->getArrayElement(i, &tmp); + if (!sevalue_to_native(tmp, to->data() + i, ctx)) { + SE_LOGE("vector %s convert error at %d\n", typeid(T).name(), i); + } + } + return true; + } + + if (array->isTypedArray()) { + CC_ASSERT(std::is_arithmetic::value); + if constexpr (std::is_arithmetic::value) { + uint8_t *data = nullptr; + size_t dataLen = 0; + array->getTypedArrayData(&data, &dataLen); + to->assign(reinterpret_cast(data), reinterpret_cast(data + dataLen)); + return true; + } + } + + SE_LOGE("[warn] failed to convert to ccstd::vector\n"); + return false; +} + +template +bool sevalue_to_native(const se::Value &from, cc::StablePropertyMap *to, se::Object *ctx) { // NOLINT + // convert object to attribute/value list: [{"prop1", v1}, {"prop2", v2}... {"propN", vn}] + CC_ASSERT_NOT_NULL(to); + CC_ASSERT(from.isObject()); + auto *jsObj = from.toObject(); + ccstd::vector objectKeys; + jsObj->getAllKeys(&objectKeys); + to->clear(); + se::Value valueJS; + for (auto &attr : objectKeys) { + V value; + + if (!jsObj->getProperty(attr, &valueJS)) { + continue; + } + if (!sevalue_to_native(valueJS, &value, ctx)) { + continue; + } + to->emplace_back(std::make_pair(std::move(attr), std::move(value))); + } + return true; +} + +///////////////////// function +/// + +template +bool nativevalue_to_se_args_v(se::ValueArray &array, Args &&...args); // NOLINT(readability-identifier-naming) + +template +inline bool sevalue_to_native(const se::Value &from, std::function *func, se::Object *self) { // NOLINT(readability-identifier-naming) + if (from.isNullOrUndefined()) { + *func = nullptr; + return true; + } + + if (from.isObject() && from.toObject()->isFunction()) { + CC_ASSERT(from.toObject()->isRooted()); + *func = [from, self]() { + se::AutoHandleScope hs; + bool ok = true; + se::ValueArray args; + se::Value rval; + bool succeed = from.toObject()->call(se::EmptyValueArray, self, &rval); + if (!succeed) { + se::ScriptEngine::getInstance()->clearException(); + } + + R rawRet{}; + sevalue_to_native(rval, &rawRet, self); + return rawRet; + }; + } else { + return false; + } + return true; +} + +template +inline bool sevalue_to_native(const se::Value &from, std::function *func, se::Object *self) { // NOLINT(readability-identifier-naming) + if (from.isNullOrUndefined()) { + *func = nullptr; + return true; + } + + if (from.isObject() && from.toObject()->isFunction()) { + CC_ASSERT(from.toObject()->isRooted()); + *func = [from, self](Args... inargs) { + se::AutoHandleScope hs; + bool ok = true; + se::ValueArray args; + int idx = 0; + args.resize(sizeof...(Args)); + nativevalue_to_se_args_v(args, inargs...); + se::Value rval; + bool succeed = from.toObject()->call(args, self, &rval); + if (!succeed) { + se::ScriptEngine::getInstance()->clearException(); + } + if constexpr (!std::is_same::value) { + R rawRet = {}; + sevalue_to_native(rval, &rawRet, self); + return rawRet; + } + }; + } else { + return false; + } + return true; +} + +inline bool sevalue_to_native(const se::Value &from, std::function *func, se::Object *self) { // NOLINT(readability-identifier-naming) + if (from.isNullOrUndefined()) { + *func = nullptr; + return true; + } + + if (from.isObject() && from.toObject()->isFunction()) { + CC_ASSERT(from.toObject()->isRooted()); + *func = [from, self]() { + se::AutoHandleScope hs; + bool ok = true; + se::ValueArray args; + se::Value rval; + bool succeed = from.toObject()->call(se::EmptyValueArray, self, &rval); + if (!succeed) { + se::ScriptEngine::getInstance()->clearException(); + } + }; + } else { + return false; + } + return true; +} + +template +inline bool sevalue_to_native(const se::Value &from, std::function *func, se::Object *self) { // NOLINT(readability-identifier-naming) + if (from.isNullOrUndefined()) { + *func = nullptr; + return true; + } + + if (from.isObject() && from.toObject()->isFunction()) { + CC_ASSERT(from.toObject()->isRooted()); + *func = [from, self](Args... inargs) { + se::AutoHandleScope hs; + bool ok = true; + se::ValueArray args; + int idx = 0; + args.resize(sizeof...(Args)); + nativevalue_to_se_args_v(args, inargs...); + se::Value rval; + bool succeed = from.toObject()->call(args, self, &rval); + if (!succeed) { + se::ScriptEngine::getInstance()->clearException(); + } + }; + } else { + return false; + } + return true; +} + +//////////////////////// ccstd::variant + +template +inline bool sevalue_to_native(const se::Value & /*from*/, ccstd::variant * /*to*/, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + static_assert(sizeof...(Args) == 0, "should not pass variant from js -> native"); + CC_ABORT(); + return false; +} + +template +inline bool sevalue_to_native(const se::Value &from, HolderType *holder, se::Object *ctx) { // NOLINT(readability-identifier-naming) + if constexpr (is_reference && is_jsb_object_v) { + void *ptr = from.toObject()->getPrivateData(); + if (ptr) { + holder->data = static_cast(ptr); + return true; + } + if constexpr (std::is_constructible::value) { + holder->ptr = ccnew_placement(&holder->inlineObject) T; + } else { + CC_ABORT(); // default construtor not provided + } + return sevalue_to_native(from, holder->ptr, ctx); + } else if constexpr (is_jsb_object_v) { + void *ptr = from.toObject()->getPrivateData(); + if (ptr) { + holder->data = *static_cast(ptr); + return true; + } + return sevalue_to_native(from, &(holder->data), ctx); + } else { + return sevalue_to_native(from, &(holder->data), ctx); + } +} + +template +inline bool sevalue_to_native(const se::Value &from, HolderType, true> *holder, se::Object *ctx) { // NOLINT(readability-identifier-naming) + if constexpr (is_jsb_object_v && std::is_pointer::value) { + auto &vec = holder->data; + return sevalue_to_native(from, &vec, ctx); + } else if constexpr (is_jsb_object_v) { + return sevalue_to_native(from, static_cast *>(&(holder->data)), ctx); + } else { + return sevalue_to_native(from, &(holder->data), ctx); + } +} + +/////////////////// std::shared_ptr + +template +bool sevalue_to_native(const se::Value &from, std::shared_ptr> *out, se::Object *ctx) { + if (from.isNullOrUndefined()) { + out->reset(); + return true; + } + + *out = std::make_shared>(); + return sevalue_to_native(from, out->get(), ctx); +} + +template +bool sevalue_to_native(const se::Value &from, std::shared_ptr *out, se::Object * /*ctx*/) { + if (from.isNullOrUndefined()) { + out->reset(); + return true; + } + *out = from.toObject()->getPrivateSharedPtr(); + return true; +} + +template +inline typename std::enable_if::value, bool>::type +sevalue_to_native(const se::Value &from, cc::IntrusivePtr> *out, se::Object *ctx) { // NOLINT(readability-identifier-naming) + *out = ccnew cc::TypedArrayTemp(); + sevalue_to_native(from, out->get(), ctx); + return true; +} + +template +bool sevalue_to_native(const se::Value &from, cc::IntrusivePtr *to, se::Object *ctx) { + if (from.isNullOrUndefined()) { + to = nullptr; + return true; + } + *to = from.toObject()->getPrivateInstrusivePtr(); + return true; +} + +/////////////////// std::tuple +template +void se_for_each_tuple_impl(Tuple &&tuple, F &&f, std::index_sequence /*seq*/) { // NOLINT(readability-identifier-naming) + using swallow = int[]; + (void)swallow{1, + (f(Indices, std::get(std::forward(tuple))), void(), int{})...}; +} + +template +void se_for_each_tuple(Tuple &&tuple, F &&f) { // NOLINT(readability-identifier-naming) + constexpr std::size_t n = std::tuple_size>::value; + se_for_each_tuple_impl(std::forward(tuple), std::forward(f), + std::make_index_sequence{}); +} + +template +bool sevalue_to_native(const se::Value &from, std::tuple *to, se::Object *ctx) { // NOLINT + constexpr size_t argsize = std::tuple_size>::value; + bool result = true; + se_for_each_tuple(*to, [&](auto i, auto ¶m) { + se::Value tmp; + from.toObject()->getArrayElement(static_cast(i), &tmp); + result &= sevalue_to_native(tmp, ¶m, ctx); + }); + return result; +} + +////////////// ccstd::unordered_map +template +bool sevalue_to_native(const se::Value &from, ccstd::unordered_map *to, se::Object *ctx) { // NOLINT + se::Object *jsmap = from.toObject(); + ccstd::vector allKeys; + jsmap->getAllKeys(&allKeys); + bool ret = true; + se::Value property; + for (auto &it : allKeys) { + if (jsmap->getProperty(it.c_str(), &property)) { + auto &output = (*to)[it]; + ret &= sevalue_to_native(property, &output, jsmap); + } + } + return true; +} + +////////////// ccstd::map +template +bool sevalue_to_native(const se::Value &from, ccstd::map *to, se::Object *ctx) { // NOLINT + se::Object *jsmap = from.toObject(); + ccstd::vector allKeys; + jsmap->getAllKeys(&allKeys); + bool ret = true; + se::Value property; + for (auto &it : allKeys) { + if (jsmap->getProperty(it.c_str(), &property)) { + auto &output = (*to)[it]; + ret &= sevalue_to_native(property, &output, jsmap); + } + } + return true; +} + +///////////////// ccstd::optional +template +bool sevalue_to_native(const se::Value &from, ccstd::optional *to, se::Object *ctx) { // NOLINT + static_assert(!is_optional::value, "bad match ?"); + if (from.isNullOrUndefined()) { + to->reset(); + return true; + } + T tmp{}; + bool ret = sevalue_to_native(from, &tmp, ctx); + if constexpr (std::is_move_assignable::value) { + *to = std::move(tmp); + } else { + *to = tmp; + } + return ret; +} + +////////////////////// shoter form +template +inline bool sevalue_to_native(const se::Value &from, T &&to) { // NOLINT(readability-identifier-naming) + return sevalue_to_native(from, std::forward(to), static_cast(nullptr)); +} + +/////////////////////////////////////////////////////////////////// +////////////////// nativevalue_to_se /////////////////////////// +/////////////////////////////////////////////////////////////////// + +template +inline bool nativevalue_to_se(T &&from, se::Value &to); // NOLINT(readability-identifier-naming) + +template +bool nativevalue_to_se(const ccstd::variant &from, se::Value &to, se::Object *ctx); // NOLINT + +template +bool nativevalue_to_se(const ccstd::variant *from, se::Value &to, se::Object *ctx) { // NOLINT + if (from) { + return nativevalue_to_se(*from, to, ctx); + } + + to.setNull(); + return true; +} + +template +bool nativevalue_to_se(ccstd::variant *from, se::Value &to, se::Object *ctx) { // NOLINT + if (from != nullptr) { + return nativevalue_to_se(*from, to, ctx); + } + to.setNull(); + return true; +} + +template +inline bool nativevalue_to_se(const ccstd::vector &from, se::Value &to, se::Object *ctx); // NOLINT + +template +inline bool nativevalue_to_se(const ccstd::vector *from, se::Value &to, se::Object *ctx) { // NOLINT + return nativevalue_to_se(*from, to, ctx); +} + +template +inline bool nativevalue_to_se(ccstd::vector *const from, se::Value &to, se::Object *ctx) { // NOLINT + return nativevalue_to_se(*from, to, ctx); +} + +template +inline typename std::enable_if::value, bool>::type +nativevalue_to_se(const T &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT + to.setInt32(static_cast(from)); + return true; +} + +template +inline typename std::enable_if::value, bool>::type +nativevalue_to_se(const T &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT + return native_ptr_to_seval(from, &to); +} + +template +inline typename std::enable_if, bool>::type +nativevalue_to_se(const T &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT + return native_ptr_to_seval(from, &to); +} + +template +inline typename std::enable_if::value, bool>::type +nativevalue_to_se(const T &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setDouble(static_cast(from)); + return true; +} + +// #endif // HAS_CONSTEXPR + +//////////////////////////////// forward declaration: nativevalue_to_se //////////////////////////////// + +template +inline bool nativevalue_to_se(const std::shared_ptr &from, se::Value &to, se::Object *ctx); // NOLINT + +template +inline bool nativevalue_to_se(const cc::IntrusivePtr &from, se::Value &to, se::Object *ctx); // NOLINT + +template +bool nativevalue_to_se(const std::reference_wrapper ref, se::Value &to, se::Object *ctx); // NOLINT + +template +bool nativevalue_to_se(const std::tuple &from, se::Value &to, se::Object *ctx); // NOLINT + +template +inline bool nativevalue_to_se(const ccstd::vector &from, se::Value &to, se::Object *ctx); // NOLINT + +template +inline bool nativevalue_to_se(const ccstd::unordered_map &from, se::Value &to, se::Object *ctx); // NOLINT + +template +inline bool nativevalue_to_se(const ccstd::map &from, se::Value &to, se::Object *ctx); // NOLINT + +template +inline bool nativevalue_to_se(const cc::TypedArrayTemp &typedArray, se::Value &to, se::Object *ctx); // NOLINT + +template +bool nativevalue_to_se(const cc::StablePropertyMap &from, se::Value &to, se::Object *ctx); // NOLINT + +/// nativevalue_to_se ccstd::optional +template +bool nativevalue_to_se(const ccstd::optional &from, se::Value &to, se::Object *ctx) { // NOLINT + if (!from.has_value()) { + to.setUndefined(); + return true; + } + return nativevalue_to_se(from.value(), to, ctx); +} + +template +inline bool nativevalue_to_se(const cc::TypedArrayTemp &typedArray, se::Value &to, se::Object *ctx) { // NOLINT + to.setObject(typedArray.getJSTypedArray()); + return true; +} + +template +inline bool nativevalue_to_se(const ccstd::vector &from, se::Value &to, se::Object *ctx) { // NOLINT(readability-identifier-naming) + se::HandleObject array(se::Object::createArrayObject(from.size())); + se::Value tmp; + for (size_t i = 0; i < from.size(); i++) { + // If from[i] is on stack, then should create a new object, or + // JS will hold a freed object. + if constexpr (!std::is_pointer::value && is_jsb_object_v) { + auto *pFrom = ccnew T(from[i]); + nativevalue_to_se(pFrom, tmp, ctx); + tmp.toObject()->getPrivateObject()->tryAllowDestroyInGC(); + } else { + nativevalue_to_se(from[i], tmp, ctx); + } + + array->setArrayElement(static_cast(i), tmp); + } + to.setObject(array, true); + return true; +} + +inline bool nativevalue_to_se(const ccstd::vector &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT + se::HandleObject array(se::Object::createArrayObject(from.size())); + for (auto i = 0; i < from.size(); i++) { + array->setArrayElement(i, se::Value(from[i])); + } + to.setObject(array); + return true; +} + +template +void cc_tmp_set_property(se::Object *obj, T &key, se::Value &value) { // NOLINT(readability-identifier-naming) + if constexpr (std::is_convertible::value) { + obj->setProperty(key, value); + } else { + obj->setProperty(std::to_string(key), value); + } +} + +template +inline bool nativevalue_to_se(const ccstd::unordered_map &from, se::Value &to, se::Object *ctx) { // NOLINT + se::HandleObject ret{se::Object::createPlainObject()}; + se::Value value; + bool ok = true; + for (auto &it : from) { + ok &= nativevalue_to_se(it.second, value, ctx); + cc_tmp_set_property(ret, it.first, value); + } + to.setObject(ret); + return true; +} +template +inline bool nativevalue_to_se(const ccstd::map &from, se::Value &to, se::Object *ctx) { // NOLINT + se::HandleObject ret{se::Object::createPlainObject()}; + se::Value value; + bool ok = true; + for (auto &it : from) { + ok &= nativevalue_to_se(it.second, value, ctx); + cc_tmp_set_property(ret, it.first, value); + } + to.setObject(ret); + return true; +} + +template +inline bool nativevalue_to_se(const ccstd::array &from, se::Value &to, se::Object *ctx) { // NOLINT(readability-identifier-naming) + se::HandleObject array{se::Object::createArrayObject(N)}; + se::Value tmp; + for (size_t i = 0; i < N; i++) { + nativevalue_to_se(from[i], tmp, ctx); + array->setArrayElement(static_cast(i), tmp); + } + to.setObject(array); + return true; +} + +template +inline bool nativevalue_to_se(const ccstd::array &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + se::HandleObject array{se::Object::createTypedArray(se::Object::TypedArrayType::UINT8, from.data(), N)}; + to.setObject(array); + return true; +} + +template +inline bool nativevalue_to_se(const ccstd::array &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + se::HandleObject array{se::Object::createTypedArray(se::Object::TypedArrayType::INT16, from.data(), N * sizeof(uint16_t))}; + to.setObject(array); + return true; +} + +template +inline bool nativevalue_to_se(const ccstd::array &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + se::HandleObject array{se::Object::createTypedArray(se::Object::TypedArrayType::FLOAT32, from.data(), N * sizeof(float))}; + to.setObject(array); + return true; +} + +template +inline bool nativevalue_to_se(const std::function & /*from*/, se::Value & /*to*/, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + SE_LOGE("Can not convert C++ const lambda to JS object"); + return false; +} + +template +bool nativevalue_to_se(const cc::StablePropertyMap &from, se::Value &to, se::Object *ctx) { // NOLINT(readability-identifier-naming + // convert to object from attribute/value list: [{"prop1", v1}, {"prop2", v2}... {"propN", vn}] + se::HandleObject ret(se::Object::createPlainObject()); + for (const auto &ele : from) { + se::Value keyJS; + se::Value valueJS; + if (!nativevalue_to_se(ele.first, keyJS, ctx)) { + continue; + } + if (!nativevalue_to_se(ele.second, valueJS, ctx)) { + continue; + } + ret->setProperty(keyJS.toString(), valueJS); + } + to.setObject(ret); + return true; +} + +///////////////////////// function /////////////////////// + +template +bool nativevalue_to_se_args(se::ValueArray &array, T &&x) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(std::forward(x), array[i], nullptr); +} +template +bool nativevalue_to_se_args(se::ValueArray &array, T &&x, Args &&...args) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se_args(array, std::forward(x)) && nativevalue_to_se_args(array, std::forward(args)...); +} + +template +bool nativevalue_to_se_args_v(se::ValueArray &array, Args &&...args) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se_args<0, Args...>(array, std::forward(args)...); +} + +// Spine conversions +#if CC_USE_SPINE + +template +bool nativevalue_to_se(const spine::Vector &v, se::Value &ret, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + se::HandleObject obj(se::Object::createArrayObject(v.size())); + bool ok = true; + + spine::Vector tmpv = v; + + auto size = static_cast(tmpv.size()); + for (uint32_t i = 0; i < size; ++i) { + se::Value tmp; + ok = nativevalue_to_se(tmpv[i], tmp, nullptr); + if (!ok || !obj->setArrayElement(i, tmp)) { + ok = false; + ret.setUndefined(); + break; + } + } + + if (ok) { + ret.setObject(obj); + } + + return ok; +} + +template +bool nativevalue_to_se(const spine::Vector &v, se::Value &ret, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + se::HandleObject obj(se::Object::createArrayObject(v.size())); + bool ok = true; + + spine::Vector tmpv = v; + + auto size = static_cast(tmpv.size()); + for (uint32_t i = 0; i < size; ++i) { + se::Value tmp; + ok = native_ptr_to_seval(tmpv[i], &tmp); + if (!ok || !obj->setArrayElement(i, tmp)) { + ok = false; + ret.setUndefined(); + break; + } + } + + if (ok) ret.setObject(obj); + return ok; +} + +template +bool sevalue_to_native(const se::Value &v, spine::Vector *ret, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(ret); + CC_ASSERT(v.isObject()); + se::Object *obj = v.toObject(); + CC_ASSERT(obj->isArray()); + + bool ok = true; + uint32_t len = 0; + ok = obj->getArrayLength(&len); + if (!ok) { + ret->clear(); + return false; + } + + se::Value tmp; + for (uint32_t i = 0; i < len; ++i) { + ok = obj->getArrayElement(i, &tmp); + if (!ok || !tmp.isObject()) { + ret->clear(); + return false; + } + + T *nativeObj = static_cast(tmp.toObject()->getPrivateData()); + ret->add(nativeObj); + } + + return true; +} + +template >> +bool sevalue_to_native(const se::Value &v, spine::Vector *ret, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(ret); + CC_ASSERT(v.isObject()); + se::Object *obj = v.toObject(); + CC_ASSERT(obj->isArray()); + + bool ok = true; + uint32_t len = 0; + ok = obj->getArrayLength(&len); + if (!ok) { + ret->clear(); + return false; + } + + se::Value tmp; + for (uint32_t i = 0; i < len; ++i) { + ok = obj->getArrayElement(i, &tmp); + if (!ok || !tmp.isNumber()) { + ret->clear(); + return false; + } + + T nativeObj = static_cast(tmp.toDouble()); + ret->add(nativeObj); + } + + return true; +} +#endif // CC_USE_SPINE + +/////////////////// shorter form +template +inline bool nativevalue_to_se(T &&from, se::Value &to) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(std::forward::type>(from), to, nullptr); +} + +template +bool nativevalue_to_se(const ccstd::variant &from, se::Value &to, se::Object *ctx) { // NOLINT(readability-identifier-naming) + bool ok = false; + ccstd::visit( + [&](auto ¶m) { + ok = nativevalue_to_se(param, to, ctx); + }, + from); + return ok; +} + +template +inline bool nativevalue_to_se(const std::shared_ptr> &from, se::Value &to, se::Object *ctx) { // NOLINT + return nativevalue_to_se(*from, to, ctx); +} + +template +inline bool nativevalue_to_se(const std::shared_ptr &from, se::Value &to, se::Object *ctx) { // NOLINT + + auto *nativePtr = from.get(); + if (!nativePtr) { + to.setNull(); + return true; + } + se::Class *cls = JSBClassType::findClass(nativePtr); + se::NativePtrToObjectMap::filter(nativePtr, cls) + .forEach([&](se::Object *foundObj) { + to.setObject(foundObj); + }) + .orElse([&]() { + CC_ASSERT(cls); + se::Object *obj = se::Object::createObjectWithClass(cls); + to.setObject(obj, true); + obj->setPrivateData(from); + }); + return true; +} + +template +inline bool nativevalue_to_se(const cc::IntrusivePtr &from, se::Value &to, se::Object *ctx) { // NOLINT + + auto *nativePtr = from.get(); + if (!nativePtr) { + to.setNull(); + return true; + } + se::Class *cls = JSBClassType::findClass(nativePtr); + se::NativePtrToObjectMap::filter(nativePtr, cls) + .forEach([&](se::Object *foundObj) { + to.setObject(foundObj); + }) + .orElse([&]() { + CC_ASSERT(cls); + se::Object *obj = se::Object::createObjectWithClass(cls); + to.setObject(obj, true); + obj->setPrivateData(from); + }); + return true; +} + +template +bool nativevalue_to_se(const std::tuple &from, se::Value &to, se::Object *ctx) { // NOLINT(readability-identifier-naming) + bool ok = true; + se::Value tmp; + se::HandleObject array{se::Object::createArrayObject(sizeof...(ARGS))}; + se_for_each_tuple( + from, [&](auto i, auto ¶m) { + ok &= nativevalue_to_se(param, tmp, ctx); + array->setArrayElement(static_cast(i), tmp); + }); + to.setObject(array); + return ok; +} + +template +bool nativevalue_to_se(const std::reference_wrapper ref, se::Value &to, se::Object *ctx) { // NOLINT + return nativevalue_to_se(ref.get(), to, ctx); +} + +// Remove this in near future. +#include "jsb_conversions_deprecated.h" diff --git a/cocos/bindings/manual/jsb_conversions_deprecated.h b/cocos/bindings/manual/jsb_conversions_deprecated.h new file mode 100644 index 0000000..1cfb8d0 --- /dev/null +++ b/cocos/bindings/manual/jsb_conversions_deprecated.h @@ -0,0 +1,106 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + http://www.cocos.com + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +// deprecated conversion functions + +// native value -> se value +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool int8_to_seval(int8_t v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool uint8_to_seval(uint8_t v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool int32_to_seval(int32_t v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool uint32_to_seval(uint32_t v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool int16_to_seval(uint16_t v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool uint16_to_seval(uint16_t v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool boolean_to_seval(bool v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool float_to_seval(float v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool double_to_seval(double v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool long_to_seval(long v, se::Value *ret) { // NOLINT(readability-identifier-naming, google-runtime-int) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool ulong_to_seval(unsigned long v, se::Value *ret) { // NOLINT(readability-identifier-naming, google-runtime-int) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool longlong_to_seval(long long v, se::Value *ret) { // NOLINT(readability-identifier-naming, google-runtime-int) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool uintptr_t_to_seval(uintptr_t v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool size_to_seval(size_t v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool std_string_to_seval(const std::string &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} + +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool std_vector_string_to_seval(const std::vector &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool std_vector_int_to_seval(const std::vector &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool std_vector_uint16_to_seval(const std::vector &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool std_vector_float_to_seval(const std::vector &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} +CC_DEPRECATED(3.6, "use nativevalue_to_se instead") +inline bool std_map_string_string_to_seval(const std::map &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return nativevalue_to_se(v, *ret, nullptr); +} diff --git a/cocos/bindings/manual/jsb_conversions_spec.cpp b/cocos/bindings/manual/jsb_conversions_spec.cpp new file mode 100644 index 0000000..8af8ffc --- /dev/null +++ b/cocos/bindings/manual/jsb_conversions_spec.cpp @@ -0,0 +1,1782 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + http://www.cocos.com + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include +#include "base/DeferredReleasePool.h" +#include "base/TemplateUtils.h" +#include "base/Value.h" +#include "cocos/base/RefMap.h" +#include "cocos/base/RefVector.h" +#include "cocos/core/TypedArray.h" +#include "cocos/editor-support/middleware-adapter.h" +#include "cocos/math/Geometry.h" +#include "cocos/math/Quaternion.h" +#include "cocos/math/Vec2.h" +#include "cocos/math/Vec3.h" +#include "core/ArrayBuffer.h" +#include "core/assets/ImageAsset.h" +#include "core/assets/TextureCube.h" +#include "core/geometry/AABB.h" +#include "extensions/cocos-ext.h" +#include "jsb_conversions.h" +#include "network/Downloader.h" + +#include "bindings/auto/jsb_assets_auto.h" +#include "bindings/auto/jsb_cocos_auto.h" +#if CC_USE_PHYSICS_PHYSX +#include "bindings/auto/jsb_physics_auto.h" +#endif +#include "cocos/core/geometry/Geometry.h" +#include "scene/Fog.h" +#include "scene/Shadow.h" +#include "scene/Skybox.h" + +#if CC_USE_SPINE +#include "cocos/editor-support/spine-creator-support/Vector2.h" +#endif + +///////////////////////// utils ///////////////////////// + +#define CHECK_ASSIGN_PRVOBJ_RET(jsObj, nativeObj) \ + se::PrivateObjectBase *_privateObjL = jsObj->getPrivateObject(); \ + if (_privateObjL) { \ + using target_type = typename std::decay::type; \ + *nativeObj = *_privateObjL->get(); \ + return true; \ + } + +template +typename std::enable_if::value, bool>::type +set_member_field(se::Object *obj, T *to, const ccstd::string &property, F f, se::Value &tmp) { // NOLINT + bool ok = obj->getProperty(property.data(), &tmp, true); + SE_PRECONDITION2(ok, false, "Property '%s' is not set", property.data()); + + A m; + ok = sevalue_to_native(tmp, &m, obj); + SE_PRECONDITION2(ok, false, "Convert property '%s' failed", property.data()); + (to->*f)(m); + return true; +} + +template +typename std::enable_if::value, bool>::type +set_member_field(se::Object *obj, T *to, const ccstd::string &property, F f, se::Value &tmp) { // NOLINT + bool ok = obj->getProperty(property.data(), &tmp, true); + SE_PRECONDITION2(ok, false, "Property '%s' is not set", property.data()); + + ok = sevalue_to_native(tmp, &(to->*f), obj); + SE_PRECONDITION2(ok, false, "Convert property '%s' failed", property.data()); + return true; +} + +static bool isNumberString(const ccstd::string &str) { + for (const auto &c : str) { // NOLINT(readability-use-anyofallof) // remove after using c++20 + if (!isdigit(c)) { + return false; + } + } + return true; +} + +namespace { +enum class DataType { + INT, + FLOAT +}; +enum class MathType { + VEC2 = 0, + VEC3, + VEC4, + QUATERNION, + MAT3, + MAT4, + SIZE, + RECT, + COLOR, +}; +} // namespace + +bool Vec2_to_seval(const cc::Vec2 &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return ret ? nativevalue_to_se(v, *ret, nullptr) : false; +} + +bool Vec3_to_seval(const cc::Vec3 &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return ret ? nativevalue_to_se(v, *ret, nullptr) : false; +} + +bool Vec4_to_seval(const cc::Vec4 &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return ret ? nativevalue_to_se(v, *ret, nullptr) : false; +} + +bool Mat4_to_seval(const cc::Mat4 &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(ret); + se::HandleObject obj(se::Object::createArrayObject(16)); + + for (uint8_t i = 0; i < 16; ++i) { + obj->setArrayElement(i, se::Value(v.m[i])); + } + + obj->setProperty("type", se::Value(static_cast(MathType::MAT4))); + ret->setObject(obj); + return true; +} + +bool Size_to_seval(const cc::Size &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return ret ? nativevalue_to_se(v, *ret, nullptr) : false; +} + +bool Rect_to_seval(const cc::Rect &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + return ret ? nativevalue_to_se(v, *ret, nullptr) : false; +} +//////////////////////////////////////////////////////////////////////////// +/////////////////sevalue to native////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +// NOLINTNEXTLINE(readability-identifier-naming) +bool seval_to_ccvalue(const se::Value &v, cc::Value *ret) { // NOLINT + return sevalue_to_native(v, ret, nullptr); +} +bool sevalue_to_native(const se::Value &from, cc::Value *to, se::Object * /*ctx*/) { // NOLINT + CC_ASSERT_NOT_NULL(to); + bool ok = true; + if (from.isObject()) { + se::Object *jsobj = from.toObject(); + if (!jsobj->isArray()) { + // It's a normal js object. + cc::ValueMap dictVal; + ok = sevalue_to_native(from, &dictVal, nullptr); + SE_PRECONDITION3(ok, false, *to = cc::Value::VALUE_NULL); + *to = cc::Value(dictVal); + } else { + // It's a js array object. + cc::ValueVector arrVal; + ok = sevalue_to_native(from, &arrVal, nullptr); + SE_PRECONDITION3(ok, false, *to = cc::Value::VALUE_NULL); + *to = cc::Value(arrVal); + } + } else if (from.isString()) { + *to = from.toString(); + } else if (from.isNumber()) { + *to = from.toDouble(); + } else if (from.isBoolean()) { + *to = from.toBoolean(); + } else if (from.isNullOrUndefined()) { + *to = cc::Value::VALUE_NULL; + } else { + SE_PRECONDITION2(false, false, "type not supported!"); + } + + return ok; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool seval_to_ccvaluemap(const se::Value &v, cc::ValueMap *ret) { // NOLINT + return sevalue_to_native(v, ret, nullptr); +} + +bool sevalue_to_native(const se::Value &from, cc::ValueMap *to, se::Object * /*ctx*/) { // NOLINT + CC_ASSERT_NOT_NULL(to); + + if (from.isNullOrUndefined()) { + to->clear(); + return true; + } + + SE_PRECONDITION3(from.isObject(), false, to->clear()); + SE_PRECONDITION3(!from.isNullOrUndefined(), false, to->clear()); + + se::Object *obj = from.toObject(); + + cc::ValueMap &dict = *to; + + ccstd::vector allKeys; + SE_PRECONDITION3(obj->getAllKeys(&allKeys), false, to->clear()); + + bool ok = false; + se::Value value; + cc::Value ccvalue; + for (const auto &key : allKeys) { + SE_PRECONDITION3(obj->getProperty(key.c_str(), &value), false, to->clear()); + ok = sevalue_to_native(value, &ccvalue, nullptr); + SE_PRECONDITION3(ok, false, to->clear()); + dict.emplace(key, ccvalue); + } + + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool seval_to_ccvaluemapintkey(const se::Value &v, cc::ValueMapIntKey *ret) { + CC_ASSERT_NOT_NULL(ret); + if (v.isNullOrUndefined()) { + ret->clear(); + return true; + } + + SE_PRECONDITION3(v.isObject(), false, ret->clear()); + SE_PRECONDITION3(!v.isNullOrUndefined(), false, ret->clear()); + + se::Object *obj = v.toObject(); + + cc::ValueMapIntKey &dict = *ret; + + ccstd::vector allKeys; + SE_PRECONDITION3(obj->getAllKeys(&allKeys), false, ret->clear()); + + bool ok = false; + se::Value value; + cc::Value ccvalue; + for (const auto &key : allKeys) { + SE_PRECONDITION3(obj->getProperty(key.c_str(), &value), false, ret->clear()); + + if (!isNumberString(key)) { + SE_LOGD("seval_to_ccvaluemapintkey, found not numeric key: %s", key.c_str()); + continue; + } + + int intKey = atoi(key.c_str()); + + ok = seval_to_ccvalue(value, &ccvalue); + SE_PRECONDITION3(ok, false, ret->clear()); + dict.emplace(intKey, ccvalue); + } + + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool seval_to_ccvaluevector(const se::Value &v, cc::ValueVector *ret) { // NOLINT + CC_ASSERT_NOT_NULL(ret); + + SE_PRECONDITION3(v.isObject(), false, ret->clear()); + + se::Object *obj = v.toObject(); + SE_PRECONDITION3(obj->isArray(), false, ret->clear()); + + uint32_t len = 0; + obj->getArrayLength(&len); + + bool ok = false; + se::Value value; + cc::Value ccvalue; + for (uint32_t i = 0; i < len; ++i) { + if (obj->getArrayElement(i, &value)) { + ok = seval_to_ccvalue(value, &ccvalue); + SE_PRECONDITION3(ok, false, ret->clear()); + ret->push_back(ccvalue); + } + } + + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevals_variadic_to_ccvaluevector(const se::ValueArray &args, cc::ValueVector *ret) { + bool ok = false; + cc::Value ccvalue; + + for (const auto &arg : args) { + ok = seval_to_ccvalue(arg, &ccvalue); + SE_PRECONDITION3(ok, false, ret->clear()); + ret->push_back(ccvalue); + } + + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool seval_to_Data(const se::Value &v, cc::Data *ret) { + return sevalue_to_native(v, ret, nullptr); +} +bool sevalue_to_native(const se::Value &v, cc::Data *ret, se::Object * /*ctx*/) { // NOLINT + CC_ASSERT_NOT_NULL(ret); + SE_PRECONDITION2(v.isObject() && (v.toObject()->isTypedArray() || v.toObject()->isArrayBuffer()), false, "Convert parameter to Data failed!"); + uint8_t *ptr = nullptr; + size_t length = 0; + se::Object *buffer = v.toObject(); + bool ok = false; + if (buffer->isTypedArray()) { + ok = buffer->getTypedArrayData(&ptr, &length); + } else { + ok = buffer->getArrayBufferData(&ptr, &length); + } + if (ok) { + ret->copy(ptr, static_cast(length)); + } else { + ret->clear(); + } + return ok; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool seval_to_DownloaderHints(const se::Value &v, cc::network::DownloaderHints *ret) { + const static cc::network::DownloaderHints ZERO{0, 0, ""}; + CC_ASSERT_NOT_NULL(ret); + SE_PRECONDITION2(v.isObject(), false, "Convert parameter to DownloaderHints failed!"); + se::Value tmp; + se::Object *obj = v.toObject(); + bool ok = false; + + ok = obj->getProperty("countOfMaxProcessingTasks", &tmp); + SE_PRECONDITION3(ok && tmp.isNumber(), false, *ret = ZERO); + ret->countOfMaxProcessingTasks = tmp.toUint32(); + + ok = obj->getProperty("timeoutInSeconds", &tmp); + SE_PRECONDITION3(ok && tmp.isNumber(), false, *ret = ZERO); + ret->timeoutInSeconds = tmp.toUint32(); + + ok = obj->getProperty("tempFileNameSuffix", &tmp); + SE_PRECONDITION3(ok && tmp.isString(), false, *ret = ZERO); + ret->tempFileNameSuffix = tmp.toString(); + + return ok; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::Vec4 *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Vec4 failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "x", &cc::Vec4::x, tmp); + set_member_field(obj, to, "y", &cc::Vec4::y, tmp); + set_member_field(obj, to, "z", &cc::Vec4::z, tmp); + set_member_field(obj, to, "w", &cc::Vec4::w, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::gfx::Rect *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Rect failed!"); + se::Object *obj = from.toObject(); + se::Value tmp; + + set_member_field(obj, to, "x", &cc::gfx::Rect::x, tmp); + set_member_field(obj, to, "y", &cc::gfx::Rect::y, tmp); + set_member_field(obj, to, "width", &cc::gfx::Rect::width, tmp); + set_member_field(obj, to, "height", &cc::gfx::Rect::height, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::Rect *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Rect failed!"); + se::Object *obj = from.toObject(); + se::Value tmp; + + set_member_field(obj, to, "x", &cc::Rect::x, tmp); + set_member_field(obj, to, "y", &cc::Rect::y, tmp); + set_member_field(obj, to, "width", &cc::Rect::width, tmp); + set_member_field(obj, to, "height", &cc::Rect::height, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::Mat3 *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Matrix3 failed!"); + se::Object *obj = from.toObject(); + + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + + if (obj->isTypedArray()) { + // typed array + SE_PRECONDITION2(obj->isTypedArray(), false, "Convert parameter to Matrix3 failed!"); + size_t length = 0; + uint8_t *ptr = nullptr; + obj->getTypedArrayData(&ptr, &length); + + memcpy(to->m, ptr, length); + } else { + bool ok = false; + se::Value tmp; + ccstd::string prefix = "m"; + for (uint32_t i = 0; i < 9; ++i) { + ccstd::string name; + if (i < 10) { + name = prefix + "0" + std::to_string(i); + } else { + name = prefix + std::to_string(i); + } + ok = obj->getProperty(name.c_str(), &tmp, true); + SE_PRECONDITION3(ok, false, *to = cc::Mat3::IDENTITY); + + if (tmp.isNumber()) { + to->m[i] = tmp.toFloat(); + } else { + SE_REPORT_ERROR("%u, not supported type in matrix", i); + *to = cc::Mat3::IDENTITY; + return false; + } + + tmp.setUndefined(); + } + } + + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::Mat4 *to, se::Object * /*unused*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Matrix4 failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + + if (obj->isTypedArray()) { + // typed array + SE_PRECONDITION2(obj->isTypedArray(), false, "Convert parameter to Matrix4 failed!"); + + size_t length = 0; + uint8_t *ptr = nullptr; + obj->getTypedArrayData(&ptr, &length); + + memcpy(to->m, ptr, length); + } else { + bool ok = false; + se::Value tmp; + ccstd::string prefix = "m"; + for (uint32_t i = 0; i < 16; ++i) { + ccstd::string name; + if (i < 10) { + name = prefix + "0" + std::to_string(i); + } else { + name = prefix + std::to_string(i); + } + ok = obj->getProperty(name.c_str(), &tmp, true); + SE_PRECONDITION3(ok, false, *to = cc::Mat4::IDENTITY); + + if (tmp.isNumber()) { + to->m[i] = tmp.toFloat(); + } else { + SE_REPORT_ERROR("%u, not supported type in matrix", i); + *to = cc::Mat4::IDENTITY; + return false; + } + + tmp.setUndefined(); + } + } + + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::Vec3 *to, se::Object * /*unused*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Vec3 failed!"); + + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "x", &cc::Vec3::x, tmp); + set_member_field(obj, to, "y", &cc::Vec3::y, tmp); + set_member_field(obj, to, "z", &cc::Vec3::z, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::Color *to, se::Object * /*unused*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Color failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value t; + set_member_field(obj, to, "r", &cc::Color::r, t); + set_member_field(obj, to, "g", &cc::Color::g, t); + set_member_field(obj, to, "b", &cc::Color::b, t); + set_member_field(obj, to, "a", &cc::Color::a, t); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::Vec2 *to, se::Object * /*unused*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Vec2 failed!"); + + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "x", &cc::Vec2::x, tmp); + set_member_field(obj, to, "y", &cc::Vec2::y, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::Size *to, se::Object * /*unused*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Size failed!"); + + se::Object *obj = from.toObject(); + se::Value tmp; + set_member_field(obj, to, "width", &cc::Size::width, tmp); + set_member_field(obj, to, "height", &cc::Size::height, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::Quaternion *to, se::Object * /*unused*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Quaternion failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to); + se::Value tmp; + set_member_field(obj, to, "x", &cc::Quaternion::x, tmp); + set_member_field(obj, to, "y", &cc::Quaternion::y, tmp); + set_member_field(obj, to, "z", &cc::Quaternion::z, tmp); + set_member_field(obj, to, "w", &cc::Quaternion::w, tmp); + return true; +} + +//////////////////// geometry + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::AABB *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to AABB failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "halfExtents", &cc::geometry::AABB::halfExtents, tmp); + set_member_field(obj, to, "center", &cc::geometry::AABB::center, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Capsule *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Capsule failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "radius", &cc::geometry::Capsule::radius, tmp); + set_member_field(obj, to, "halfHeight", &cc::geometry::Capsule::halfHeight, tmp); + set_member_field(obj, to, "axis", &cc::geometry::Capsule::axis, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Line *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Line failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "s", &cc::geometry::Line::s, tmp); + set_member_field(obj, to, "e", &cc::geometry::Line::e, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Ray *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Ray failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "o", &cc::geometry::Ray::o, tmp); + set_member_field(obj, to, "d", &cc::geometry::Ray::d, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Sphere *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Sphere failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "radius", &cc::geometry::Sphere::setRadius, tmp); + set_member_field(obj, to, "center", &cc::geometry::Sphere::setCenter, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Triangle *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Plane failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "a", &cc::geometry::Triangle::a, tmp); + set_member_field(obj, to, "b", &cc::geometry::Triangle::b, tmp); + set_member_field(obj, to, "c", &cc::geometry::Triangle::c, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Plane *to, se::Object * /*unused*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Plane failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "n", &cc::geometry::Plane::n, tmp); + set_member_field(obj, to, "d", &cc::geometry::Plane::d, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Plane **to, se::Object *ctx) { + return sevalue_to_native(from, *to, ctx); +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Frustum *to, se::Object * /*unused*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Frustum failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "planes", &cc::geometry::Frustum::planes, tmp); + set_member_field(obj, to, "vertices", &cc::geometry::Frustum::vertices, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Spline *to, se::Object * /*unused*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Spline failed!"); + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "_mode", &cc::geometry::Spline::setMode, tmp); + set_member_field>(obj, to, "_knots", &cc::geometry::Spline::setKnots, tmp); + return true; +} + +////////////////////////// scene info + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::scene::FogInfo *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to FogInfo failed!"); + se::Object *obj = from.toObject(); + se::Value tmp; + set_member_field(obj, to, "type", &cc::scene::FogInfo::setType, tmp); + set_member_field(obj, to, "fogColor", &cc::scene::FogInfo::setFogColor, tmp); + set_member_field(obj, to, "enabled", &cc::scene::FogInfo::setEnabled, tmp); + set_member_field(obj, to, "fogDensity", &cc::scene::FogInfo::setFogDensity, tmp); + set_member_field(obj, to, "fogStart", &cc::scene::FogInfo::setFogStart, tmp); + set_member_field(obj, to, "fogEnd", &cc::scene::FogInfo::setFogEnd, tmp); + set_member_field(obj, to, "fogAtten", &cc::scene::FogInfo::setFogAtten, tmp); + set_member_field(obj, to, "fogTop", &cc::scene::FogInfo::setFogTop, tmp); + set_member_field(obj, to, "fogRange", &cc::scene::FogInfo::setFogRange, tmp); + set_member_field(obj, to, "accurate", &cc::scene::FogInfo::setAccurate, tmp); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::scene::ShadowsInfo *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to ShadowInfo failed!"); + se::Object *obj = from.toObject(); + se::Value tmp; + set_member_field(obj, to, "type", &cc::scene::ShadowsInfo::setType, tmp); + set_member_field(obj, to, "enabled", &cc::scene::ShadowsInfo::setEnabled, tmp); + set_member_field(obj, to, "planeDirection", &cc::scene::ShadowsInfo::setPlaneDirection, tmp); + set_member_field(obj, to, "planeHeight", &cc::scene::ShadowsInfo::setPlaneHeight, tmp); + set_member_field(obj, to, "shadowColor", &cc::scene::ShadowsInfo::setShadowColor, tmp); + set_member_field(obj, to, "maxReceived", &cc::scene::ShadowsInfo::setMaxReceived, tmp); + set_member_field(obj, to, "size", &cc::scene::ShadowsInfo::setShadowMapSize, tmp); + + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::scene::SkyboxInfo *to, se::Object * /*ctx*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to SkyboxInfo failed!"); + se::Object *obj = from.toObject(); + se::Value tmp; + set_member_field(obj, to, "envmap", &cc::scene::SkyboxInfo::setEnvmap, tmp); + set_member_field(obj, to, "diffuseMap", &cc::scene::SkyboxInfo::setDiffuseMap, tmp); + set_member_field(obj, to, "enabled", &cc::scene::SkyboxInfo::setEnabled, tmp); + set_member_field(obj, to, "useIBL", &cc::scene::SkyboxInfo::setUseIBL, tmp); + set_member_field(obj, to, "useHDR", &cc::scene::SkyboxInfo::setUseHDR, tmp); + set_member_field(obj, to, "applyDiffuseMap", &cc::scene::SkyboxInfo::setApplyDiffuseMap, tmp); + + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::MacroValue *to, se::Object * /*ctx*/) { + bool ret = true; + if (from.isBoolean()) { + *to = from.toBoolean(); + } else if (from.isNumber()) { + *to = from.toInt32(); // NOTE: We only support macro with int32_t type now. + } else if (from.isString()) { + *to = from.toString(); + } else { + ret = false; + } + + return ret; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, ccstd::vector *to, se::Object * /*ctx*/) { + if (from.isNullOrUndefined()) { + to->clear(); + return true; + } + + SE_PRECONDITION2(from.isObject(), false, "sevalue_to_native(ccstd::vector), not an object"); + auto *fromObj = from.toObject(); + CC_ASSERT(fromObj->isArray()); + uint32_t len = 0; + bool ok = fromObj->getArrayLength(&len); + if (ok) { + to->resize(len); + se::Value arrElement; + for (uint32_t i = 0; i < len; ++i) { + ok = fromObj->getArrayElement(i, &arrElement); + if (!ok || !arrElement.isObject()) { + continue; + } + cc::MacroRecord macroRecord; + ccstd::vector keys; + ok = arrElement.toObject()->getAllKeys(&keys); + if (ok) { + se::Value seMacroVal; + for (const auto &key : keys) { + ok = arrElement.toObject()->getProperty(key, &seMacroVal); + cc::MacroValue macroVal; + sevalue_to_native(seMacroVal, ¯oVal, nullptr); + macroRecord.emplace(key, std::move(macroVal)); + } + } + (*to)[i] = std::move(macroRecord); + } + } + + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::MaterialProperty *to, se::Object *ctx) { + if (from.isNullOrUndefined()) { + *to = ccstd::monostate(); + return true; + } + + // TODO(PatriceJiang): float/int32_t from js number + if (from.isNumber()) { + double v = from.toDouble(); + if (std::trunc(v) != v) { + *to = static_cast(v); + } else { + *to = static_cast(v); + } + return true; + } + + if (from.isObject()) { + auto *obj = const_cast(from.toObject()); + bool hasX; + bool hasY; + bool hasZ; + bool hasW; + bool hasEuler; + bool hasM01; + bool hasM08; + bool hasM15; + bool hasAssetID; + bool hasColorVal; + + se::Value tmp0; + se::Value tmp1; + se::Value tmp2; + se::Value tmp3; + se::Value tmp4; + + hasColorVal = obj->getProperty("_val", &tmp0, true); + if (hasColorVal) { + *to = cc::Color{tmp0.toUint32()}; + return true; + } + + hasX = obj->getProperty("x", &tmp0, true); + hasY = hasX && obj->getProperty("y", &tmp1, true); + hasZ = hasY && obj->getProperty("z", &tmp2, true); + hasW = hasZ && obj->getProperty("w", &tmp3, true); + hasEuler = hasW && obj->getProperty("getEulerAngles", &tmp4, true); + + if (hasW) { + if (hasEuler) { + *to = cc::Quaternion(tmp0.toFloat(), tmp1.toFloat(), tmp2.toFloat(), tmp3.toFloat()); + } else { + *to = cc::Vec4(tmp0.toFloat(), tmp1.toFloat(), tmp2.toFloat(), tmp3.toFloat()); + } + return true; + } + + if (hasZ) { + *to = cc::Vec3(tmp0.toFloat(), tmp1.toFloat(), tmp2.toFloat()); + return true; + } + + if (hasY) { + *to = cc::Vec2(tmp0.toFloat(), tmp1.toFloat()); + return true; + } + + hasM01 = obj->getProperty("m00", &tmp0, true); + hasM08 = hasM01 && obj->getProperty("m08", &tmp1, true); + hasM15 = hasM08 && obj->getProperty("m15", &tmp2, true); + + if (hasM15) { + cc::Mat4 m4; + sevalue_to_native(from, &m4, ctx); + *to = m4; + return true; + } + + if (hasM08) { + cc::Mat3 m3; + sevalue_to_native(from, &m3, ctx); + *to = m3; + return true; + } + + hasAssetID = obj->getProperty("_id", &tmp3, true); + if (hasAssetID) { + *to = reinterpret_cast(obj->getPrivateData()); + return true; + } + + if (obj->_getClass() != nullptr) { + const auto *name = obj->_getClass()->getName(); + if (0 == strcmp(name, "Texture2D")) { + *to = reinterpret_cast(obj->getPrivateData()); + return true; + } + + if (0 == strcmp(name, "TextureCube")) { + *to = reinterpret_cast(obj->getPrivateData()); + return true; + } + + if (0 == strcmp(name, "RenderTexture")) { + *to = reinterpret_cast(obj->getPrivateData()); + return true; + } + } + + // gfx::Texture? + *to = reinterpret_cast(obj->getPrivateData()); + return true; + } + + return false; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::IPreCompileInfoValueType *to, se::Object *ctx) { + se::Object *obj = from.toObject(); + SE_PRECONDITION2(obj->isArray(), false, "faild to convert to IPreCompileInfoValueType"); + + uint32_t len; + obj->getArrayLength(&len); + if (len == 0) { + // TODO(PatriceJiang): judge type of empty array? + *to = ccstd::vector{}; + return false; + } + + se::Value firstEle; + obj->getArrayElement(0, &firstEle); + if (firstEle.isBoolean()) { + ccstd::vector result; + sevalue_to_native(from, &result, ctx); + *to = result; + return true; + } + if (firstEle.isNumber()) { + ccstd::vector result; + sevalue_to_native(from, &result, ctx); + *to = result; + return true; + } + if (firstEle.isString()) { + ccstd::vector result; + sevalue_to_native(from, &result, ctx); + *to = result; + return true; + } + + return false; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::IPropertyEditorValueType *to, se::Object *ctx) { + bool ret = true; + switch (from.getType()) { + case se::Value::Type::String: { + ccstd::string str; + ret = sevalue_to_native(from, &str, ctx); + *to = std::move(str); + } break; + case se::Value::Type::Boolean: { + bool v{false}; + ret = sevalue_to_native(from, &v, ctx); + *to = v; + } break; + case se::Value::Type::Number: { + float v{0.F}; + ret = sevalue_to_native(from, &v, ctx); + *to = v; + } break; + case se::Value::Type::Object: { + CC_ASSERT_TRUE(from.toObject()->isArray()); + ccstd::vector v; + ret = sevalue_to_native(from, &v, ctx); + *to = std::move(v); + } break; + default: + *to = {}; + break; + } + + return ret; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::IPropertyValue *to, se::Object * /*ctx*/) { + if (from.isObject() && from.toObject()->isArray()) { + uint32_t len = 0; + bool ok = from.toObject()->getArrayLength(&len); + ccstd::vector arr; + arr.resize(len); + for (uint32_t i = 0; i < len; ++i) { + se::Value e; + ok = from.toObject()->getArrayElement(i, &e); + if (ok) { + if (e.isNumber()) { + arr[i] = e.toFloat(); + } + } + } + *to = std::move(arr); + } else if (from.isString()) { + *to = from.toString(); + } else { + CC_ABORT(); + } + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::ArrayBuffer *to, se::Object * /*ctx*/) { + CC_ASSERT(from.isObject()); + to->setJSArrayBuffer(from.toObject()); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::ArrayBuffer **to, se::Object * /*ctx*/) { + CC_ASSERT(from.isObject()); + auto *obj = from.toObject(); + CC_ASSERT((obj->isArrayBuffer() || obj->isTypedArray())); + + auto *ab = ccnew cc::ArrayBuffer(); + ab->addRef(); + if (obj->isArrayBuffer()) { + ab->setJSArrayBuffer(obj); + } else if (obj->isTypedArray()) { + se::Value bufferVal; + obj->getProperty("buffer", &bufferVal); + ab->setJSArrayBuffer(bufferVal.toObject()); + } else { + ab->release(); + return false; + } + + *to = ab; + cc::DeferredReleasePool::add(*to); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, ccstd::vector *to, se::Object * /*ctx*/) { + if (from.isNullOrUndefined()) { + to->clear(); + return true; + } + + se::Object *arr = from.toObject(); + uint32_t size; + se::Value tmp; + arr->getArrayLength(&size); + to->resize(size); + for (uint32_t i = 0; i < size; i++) { + arr->getArrayElement(i, &tmp); + (*to)[i] = tmp.toBoolean(); + } + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, ccstd::variant *to, se::Object * /*ctx*/) { + if (from.isBoolean()) { + *to = from.toBoolean(); + } else if (from.isString()) { + *to = from.toString(); + } else { + CC_ASSERT(false); + } + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, ccstd::vector *to, se::Object * /*ctx*/) { + if (from.isNullOrUndefined()) { + to->clear(); + return true; + } + + CC_ASSERT(from.isObject()); + se::Object *in = from.toObject(); + + if (in->isTypedArray()) { + uint8_t *data = nullptr; + size_t dataLen = 0; + in->getTypedArrayData(&data, &dataLen); + to->resize(dataLen); + to->assign(data, data + dataLen); + return true; + } + + if (in->isArrayBuffer()) { + uint8_t *data = nullptr; + size_t dataLen = 0; + in->getArrayBufferData(&data, &dataLen); + to->resize(dataLen); + to->assign(data, data + dataLen); + return true; + } + + if (in->isArray()) { + uint32_t len = 0; + in->getArrayLength(&len); + to->resize(len); + se::Value ele; + for (uint32_t i = 0; i < len; i++) { + in->getArrayElement(i, &ele); + (*to)[i] = ele.toUint8(); + } + return true; + } + + SE_LOGE("type error, ArrayBuffer/TypedArray/Array expected!"); + return false; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::TypedArray *to, se::Object * /*ctx*/) { + CC_ASSERT(from.isObject()); + CC_ASSERT(from.toObject()->isTypedArray()); + if (to->index() == 0) { + se::Object::TypedArrayType type = from.toObject()->getTypedArrayType(); + switch (type) { + case se::Object::TypedArrayType::FLOAT32: + *to = cc::Float32Array(); + break; + case se::Object::TypedArrayType::UINT16: + *to = cc::Uint16Array(); + break; + case se::Object::TypedArrayType::UINT32: + *to = cc::Uint32Array(); + break; + case se::Object::TypedArrayType::UINT8: + *to = cc::Uint8Array(); + break; + case se::Object::TypedArrayType::INT32: + *to = cc::Int32Array(); + break; + case se::Object::TypedArrayType::INT16: + *to = cc::Int16Array(); + break; + case se::Object::TypedArrayType::INT8: + *to = cc::Int8Array(); + break; + case se::Object::TypedArrayType::FLOAT64: + *to = cc::Float64Array(); + break; + default: + CC_ABORT(); + } + } + + ccstd::visit(cc::overloaded{ + [&](auto &typedArray) { + typedArray.setJSTypedArray(from.toObject()); + }, + [](ccstd::monostate & /*unused*/) {}}, + *to); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::IBArray *to, se::Object * /*ctx*/) { + ccstd::visit(cc::overloaded{ + [&](auto &typedArray) { + typedArray.setJSTypedArray(from.toObject()); + }, + [](ccstd::monostate & /*unused*/) {}}, + *to); + + return true; +} + +#if CC_USE_SPINE + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &val, spine::String *obj, se::Object * /*unused*/) { + *obj = val.toString().data(); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool sevalue_to_native(const se::Value &v, spine::Vector *ret, se::Object * /*unused*/) { + CC_ASSERT(v.isObject()); + se::Object *obj = v.toObject(); + CC_ASSERT(obj->isArray()); + + bool ok = true; + uint32_t len = 0; + ok = obj->getArrayLength(&len); + if (!ok) { + ret->clear(); + return false; + } + + se::Value tmp; + for (uint32_t i = 0; i < len; ++i) { + ok = obj->getArrayElement(i, &tmp); + if (!ok || !tmp.isObject()) { + ret->clear(); + return false; + } + + const char *str = tmp.toString().c_str(); + ret->add(str); + } + + return true; +} + +bool sevalue_to_native(const se::Value &from, spine::Vector2 *to, se::Object * /*unused*/) { + SE_PRECONDITION2(from.isObject(), false, "Convert parameter to Vec2 failed!"); + + se::Object *obj = from.toObject(); + CHECK_ASSIGN_PRVOBJ_RET(obj, to) + se::Value tmp; + set_member_field(obj, to, "x", &spine::Vector2::x, tmp); + set_member_field(obj, to, "y", &spine::Vector2::y, tmp); + return true; +} + +bool nativevalue_to_se(const spine::Vector2 &from, se::Value &to, se::Object * /*unused*/) { + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty("x", se::Value(from.x)); + obj->setProperty("y", se::Value(from.y)); + to.setObject(obj); + return true; +} +#endif + +#if CC_USE_MIDDLEWARE +// NOLINTNEXTLINE(readability-identifier-naming) +bool seval_to_Map_string_key(const se::Value &v, cc::RefMap *ret) { + CC_ASSERT_NOT_NULL(ret); + CC_ASSERT(v.isObject()); + se::Object *obj = v.toObject(); + + ccstd::vector allKeys; + bool ok = obj->getAllKeys(&allKeys); + if (!ok) { + ret->clear(); + return false; + } + + se::Value tmp; + for (const auto &key : allKeys) { + auto pngPos = key.find(".png"); + if (pngPos == ccstd::string::npos) { + continue; + } + + ok = obj->getProperty(key.c_str(), &tmp); + if (!ok || !tmp.isObject()) { + ret->clear(); + return false; + } + auto *nativeObj = static_cast(tmp.toObject()->getPrivateData()); + ret->insert(key, nativeObj); + } + + return true; +} + +#endif + +//////////////////////////////////////////////////////////////////////////// +/////////////////nativevalue_to_se////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +// native to seval + +// NOLINTNEXTLINE(readability-identifier-naming) +bool ccvalue_to_seval(const cc::Value &v, se::Value *ret) { // NOLINT + return ret ? nativevalue_to_se(v, *ret, nullptr) : false; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool ccvaluemap_to_seval(const cc::ValueMap &v, se::Value *ret) { // NOLINT + return ret ? nativevalue_to_se(v, *ret, nullptr) : false; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool ccvaluemapintkey_to_seval(const cc::ValueMapIntKey &v, se::Value *ret) { // NOLINT + CC_ASSERT_NOT_NULL(ret); + + se::HandleObject obj(se::Object::createPlainObject()); + bool ok = true; + for (const auto &e : v) { + std::stringstream keyss; + keyss << e.first; + ccstd::string key = keyss.str(); + const cc::Value &value = e.second; + + if (key.empty()) { + continue; + } + + se::Value tmp; + if (!ccvalue_to_seval(value, &tmp)) { + ok = false; + ret->setUndefined(); + break; + } + + obj->setProperty(key.c_str(), tmp); + } + if (ok) { + ret->setObject(obj); + } + + return ok; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool ccvaluevector_to_seval(const cc::ValueVector &v, se::Value *ret) { // NOLINT + CC_ASSERT_NOT_NULL(ret); + se::HandleObject obj(se::Object::createArrayObject(v.size())); + bool ok = true; + + uint32_t i = 0; + for (const auto &value : v) { + se::Value tmp; + if (!ccvalue_to_seval(value, &tmp)) { + ok = false; + ret->setUndefined(); + break; + } + + obj->setArrayElement(i, tmp); + ++i; + } + if (ok) { + ret->setObject(obj); + } + + return ok; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool ManifestAsset_to_seval(const cc::extension::ManifestAsset &v, se::Value *ret) { + return ret ? nativevalue_to_se(v, *ret, nullptr) : false; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool Data_to_seval(const cc::Data &v, se::Value *ret) { + // NOTICE: should remove this function, kept for backward compatibility + return Data_to_TypedArray(v, ret); +} + +bool Data_to_TypedArray(const cc::Data &v, se::Value *ret) { // NOLINT(readability-identifier-naming) + // NOTICE: should remove this function, kept for backward compatibility + CC_ASSERT_NOT_NULL(ret); + if (v.isNull()) { + ret->setNull(); + } else { + se::HandleObject obj(se::Object::createTypedArray(se::Object::TypedArrayType::UINT8, v.getBytes(), v.getSize())); + ret->setObject(obj, true); + } + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool DownloadTask_to_seval(const cc::network::DownloadTask &v, se::Value *ret) { + return ret ? nativevalue_to_se(v, *ret, nullptr) : false; +} + +bool nativevalue_to_se(const cc::network::DownloadTask &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty("identifier", se::Value(from.identifier)); + obj->setProperty("requestURL", se::Value(from.requestURL)); + obj->setProperty("storagePath", se::Value(from.storagePath)); + to.setObject(obj); + return true; +} + +#if CC_USE_SPINE +// NOLINTNEXTLINE(readability-identifier-naming) +bool nativevalue_to_se(const spine::String &obj, se::Value &val, se::Object * /*unused*/) { + val.setString(obj.buffer()); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool nativevalue_to_se(const spine::Vector &v, se::Value &ret, se::Object * /*unused*/) { + se::HandleObject obj(se::Object::createArrayObject(v.size())); + bool ok = true; + + spine::Vector tmpv = v; + for (uint32_t i = 0, count = static_cast(tmpv.size()); i < count; i++) { + if (!obj->setArrayElement(i, se::Value(tmpv[i].buffer()))) { + ok = false; + ret.setUndefined(); + break; + } + } + + if (ok) { + ret.setObject(obj); + } + + return ok; +} +#endif + +////////////////// custom types + +// NOLINTNEXTLINE(readability-identifier-naming) +bool nativevalue_to_se(const cc::Data &from, se::Value &to, se::Object * /*unused*/) { + se::HandleObject buffer{se::Object::createArrayBufferObject(from.getBytes(), from.getSize())}; + to.setObject(buffer); + return true; +} + +// NOLINTNEXTLINE +bool nativevalue_to_se(const cc::Value &from, se::Value &to, se::Object * /*unused*/) { + bool ok = true; + switch (from.getType()) { + case cc::Value::Type::NONE: + to.setNull(); + break; + case cc::Value::Type::UNSIGNED: + to.setUint32(from.asUnsignedInt()); + break; + case cc::Value::Type::BOOLEAN: + to.setBoolean(from.asBool()); + break; + case cc::Value::Type::FLOAT: + case cc::Value::Type::DOUBLE: + to.setDouble(from.asDouble()); + break; + case cc::Value::Type::INTEGER: + to.setInt32(from.asInt()); + break; + case cc::Value::Type::STRING: + to.setString(from.asString()); + break; + case cc::Value::Type::VECTOR: + ok = nativevalue_to_se(from.asValueVector(), to, nullptr); + break; + case cc::Value::Type::MAP: + ok = nativevalue_to_se(from.asValueMap(), to, nullptr); + break; + case cc::Value::Type::INT_KEY_MAP: + ok = nativevalue_to_se(from.asIntKeyMap(), to, nullptr); + break; + default: + SE_LOGE("Could not the way to convert cc::Value::Type (%d) type!", (int)from.getType()); + ok = false; + break; + } + return ok; +} + +// NOLINTNEXTLINE +bool nativevalue_to_se(const ccstd::unordered_map &from, se::Value &to, se::Object * /*unused*/) { + se::HandleObject obj(se::Object::createPlainObject()); + bool ok = true; + for (const auto &e : from) { + const ccstd::string &key = e.first; + const cc::Value &value = e.second; + + if (key.empty()) { + continue; + } + + se::Value tmp; + if (!nativevalue_to_se(value, tmp, nullptr)) { + ok = false; + to.setUndefined(); + break; + } + + obj->setProperty(key.c_str(), tmp); + } + if (ok) { + to.setObject(obj); + } + + return ok; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool nativevalue_to_se(const cc::Size &from, se::Value &to, se::Object * /*unused*/) { + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty("width", se::Value(from.width)); + obj->setProperty("height", se::Value(from.height)); + obj->setProperty("type", se::Value(static_cast(MathType::SIZE))); + to.setObject(obj); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool nativevalue_to_se(const cc::extension::ManifestAsset &from, se::Value &to, se::Object * /*unused*/) { + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty("md5", se::Value(from.md5)); + obj->setProperty("path", se::Value(from.path)); + obj->setProperty("compressed", se::Value(from.compressed)); + obj->setProperty("size", se::Value(from.size)); + obj->setProperty("downloadState", se::Value(from.downloadState)); + to.setObject(obj); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool nativevalue_to_se(const cc::Rect &from, se::Value &to, se::Object * /*unused*/) { + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty("x", se::Value(from.x)); + obj->setProperty("y", se::Value(from.y)); + obj->setProperty("width", se::Value(from.width)); + obj->setProperty("height", se::Value(from.height)); + obj->setProperty("type", se::Value(static_cast(MathType::RECT))); + to.setObject(obj); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool nativevalue_to_se(const cc::gfx::Rect &from, se::Value &to, se::Object * /*unused*/) { + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty("x", se::Value(from.x)); + obj->setProperty("y", se::Value(from.y)); + obj->setProperty("width", se::Value(from.width)); + obj->setProperty("height", se::Value(from.height)); + to.setObject(obj); + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool nativevalue_to_se(const cc::gfx::FormatInfo *from, se::Value &to, se::Object * /*unused*/) { + se::HandleObject obj(se::Object::createPlainObject()); + if (from) { + obj->setProperty("name", se::Value(from->name)); + obj->setProperty("size", se::Value(from->size)); + obj->setProperty("count", se::Value(from->count)); + obj->setProperty("type", se::Value(static_cast(from->type))); + obj->setProperty("hasAlpha", se::Value(from->hasAlpha)); + obj->setProperty("hasDepth", se::Value(from->hasDepth)); + obj->setProperty("hasStencil", se::Value(from->hasStencil)); + obj->setProperty("isCompressed", se::Value(from->isCompressed)); + to.setObject(obj); + } else { + to.setNull(); + } + return true; +} + +// NOLINTNEXTLINE(readability-identifier-naming) +bool nativevalue_to_se(const cc::ArrayBuffer &arrayBuffer, se::Value &to, se::Object * /*ctx*/) { + to.setObject(arrayBuffer.getJSArrayBuffer()); + return true; +} + +//// NOLINTNEXTLINE(readability-identifier-naming) +// bool nativevalue_to_se(const cc::TypedArray &from, se::Value &to, se::Object * /*ctx*/) { +// std::visit([&](auto &typedArray) { +// to.setObject(typedArray.getJSTypedArray()); +// }, +// from); +// return true; +// } + +// NOLINTNEXTLINE(readability-identifier-naming) +bool nativevalue_to_se(const cc::NativeDep &from, se::Value &to, se::Object * /*ctx*/) { + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty("uuid", se::Value(from.uuid)); + obj->setProperty("ext", se::Value(from.ext)); + obj->setProperty("__isNative__", se::Value(from.__isNative__)); + to.setObject(obj); + return true; +} + +#if CC_USE_PHYSICS_PHYSX + +bool nativevalue_to_se(const ccstd::vector> &from, se::Value &to, se::Object * /*ctx*/) { + se::HandleObject array(se::Object::createArrayObject(from.size() * cc::physics::TriggerEventPair::COUNT)); + for (size_t i = 0; i < from.size(); i++) { + auto t = i * cc::physics::TriggerEventPair::COUNT; + array->setArrayElement(static_cast(t + 0), se::Value(from[i]->shapeA)); + array->setArrayElement(static_cast(t + 1), se::Value(from[i]->shapeB)); + array->setArrayElement(static_cast(t + 2), se::Value(static_cast(from[i]->state))); + } + to.setObject(array); + return true; +} + +bool nativevalue_to_se(const ccstd::vector &from, se::Value &to, se::Object * /*ctx*/) { + const auto contactCount = from.size(); + se::HandleObject array(se::Object::createArrayObject(contactCount)); + for (size_t i = 0; i < contactCount; i++) { + auto t = i * cc::physics::ContactPoint::COUNT; + uint32_t j = 0; + array->setArrayElement(static_cast(t + j++), se::Value(from[i].position.x)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].position.y)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].position.z)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].normal.x)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].normal.y)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].normal.z)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].impulse.x)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].impulse.y)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].impulse.z)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].separation)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].internalFaceIndex0)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].internalFaceIndex1)); + } + to.setObject(array); + return true; +} + +bool nativevalue_to_se(const ccstd::vector> &from, se::Value &to, se::Object *ctx) { + se::HandleObject array(se::Object::createArrayObject(from.size() * cc::physics::ContactEventPair::COUNT)); + for (size_t i = 0; i < from.size(); i++) { + auto t = i * cc::physics::ContactEventPair::COUNT; + array->setArrayElement(static_cast(t + 0), se::Value(from[i]->shapeA)); + array->setArrayElement(static_cast(t + 1), se::Value(from[i]->shapeB)); + array->setArrayElement(static_cast(t + 2), se::Value(static_cast(from[i]->state))); + array->setArrayElement(static_cast(t + 3), [&]() -> se::Value { + auto obj = se::Value(); + nativevalue_to_se(from[i]->contacts, obj, ctx); + return obj; + }()); + } + to.setObject(array); + return true; +} + +bool nativevalue_to_se(const ccstd::vector &from, se::Value &to, se::Object * /*ctx*/) { + const auto contactCount = from.size(); + se::HandleObject array(se::Object::createArrayObject(contactCount)); + for (size_t i = 0; i < contactCount; i++) { + auto t = i * cc::physics::CharacterControllerContact::COUNT; + uint32_t j = 0; + array->setArrayElement(static_cast(t + j++), se::Value(from[i].worldPosition.x)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].worldPosition.y)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].worldPosition.z)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].worldNormal.x)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].worldNormal.y)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].worldNormal.z)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].motionDirection.x)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].motionDirection.y)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].motionDirection.z)); + array->setArrayElement(static_cast(t + j++), se::Value(from[i].motionLength)); + } + to.setObject(array); + return true; +} + +bool nativevalue_to_se(const ccstd::vector> &from, se::Value &to, se::Object *ctx) { + se::HandleObject array(se::Object::createArrayObject(from.size() * cc::physics::CCTShapeEventPair::COUNT)); + for (size_t i = 0; i < from.size(); i++) { + auto t = i * cc::physics::CCTShapeEventPair::COUNT; + array->setArrayElement(static_cast(t + 0), se::Value(from[i]->cct)); + array->setArrayElement(static_cast(t + 1), se::Value(from[i]->shape)); + //array->setArrayElement(static_cast(t + 2), se::Value(static_cast(from[i]->state))); + array->setArrayElement(static_cast(t + 2), [&]() -> se::Value { + auto obj = se::Value(); + nativevalue_to_se(from[i]->contacts, obj, ctx); + return obj; + }()); + } + to.setObject(array); + return true; +} + +bool nativevalue_to_se(const ccstd::vector> &from, se::Value &to, se::Object * /*ctx*/) { + se::HandleObject array(se::Object::createArrayObject(from.size() * cc::physics::CCTTriggerEventPair::COUNT)); + for (size_t i = 0; i < from.size(); i++) { + auto t = i * cc::physics::CCTTriggerEventPair::COUNT; + array->setArrayElement(static_cast(t + 0), se::Value(from[i]->cct)); + array->setArrayElement(static_cast(t + 1), se::Value(from[i]->shape)); + array->setArrayElement(static_cast(t + 2), se::Value(static_cast(from[i]->state))); + } + to.setObject(array); + return true; +} + +bool nativevalue_to_se(const cc::physics::RaycastResult &from, se::Value &to, se::Object *ctx) { + se::HandleObject obj(se::Object::createPlainObject()); + obj->setProperty("shape", se::Value(from.shape)); + obj->setProperty("distance", se::Value(from.distance)); + + se::Value tmp; + if (nativevalue_to_se(from.hitPoint, tmp, ctx)) obj->setProperty("hitPoint", tmp); + if (nativevalue_to_se(from.hitNormal, tmp, ctx)) obj->setProperty("hitNormal", tmp); + to.setObject(obj); + return true; +} + +bool sevalue_to_native(const se::Value &from, cc::physics::ConvexDesc *to, se::Object *ctx) { + CC_ASSERT(from.isObject()); + se::Object *json = from.toObject(); + auto *data = static_cast(json->getPrivateData()); + if (data) { + *to = *data; + return true; + } + + se::Value field; + bool ok = true; + + json->getProperty("positionLength", &field); + if (!field.isNullOrUndefined()) ok &= sevalue_to_native(field, &to->positionLength, ctx); + + CC_UNUSED size_t dataLength = 0; + json->getProperty("positions", &field); + if (!field.isNullOrUndefined()) { + se::Object *obj = field.toObject(); + if (obj->isArrayBuffer()) { + ok &= obj->getArrayBufferData(reinterpret_cast(&to->positions), &dataLength); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok &= obj->getTypedArrayData(reinterpret_cast(&to->positions), &dataLength); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + ok &= false; + } + } + return ok; +} + +bool sevalue_to_native(const se::Value &from, cc::physics::TrimeshDesc *to, se::Object *ctx) { + if (!sevalue_to_native(from, reinterpret_cast(to), ctx)) { + return false; + } + + CC_ASSERT(from.isObject()); + se::Object *json = from.toObject(); + auto *data = static_cast(json->getPrivateData()); + if (data) { + *to = *data; + return true; + } + se::Value field; + bool ok = true; + + json->getProperty("triangleLength", &field); + if (!field.isNullOrUndefined()) ok &= sevalue_to_native(field, &(to->triangleLength), ctx); + + json->getProperty("isU16", &field); + if (!field.isNullOrUndefined()) ok &= sevalue_to_native(field, &(to->isU16), ctx); + + CC_UNUSED size_t dataLength = 0; + json->getProperty("triangles", &field); + if (!field.isNullOrUndefined()) { + se::Object *obj = field.toObject(); + if (obj->isArrayBuffer()) { + ok &= obj->getArrayBufferData(reinterpret_cast(&to->triangles), &dataLength); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok &= obj->getTypedArrayData(reinterpret_cast(&to->triangles), &dataLength); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + ok &= false; + } + } + + return ok; +} + +bool sevalue_to_native(const se::Value &from, cc::physics::HeightFieldDesc *to, se::Object *ctx) { + CC_ASSERT(from.isObject()); + se::Object *json = from.toObject(); + auto *data = static_cast(json->getPrivateData()); + if (data) { + *to = *data; + return true; + } + + se::Value field; + bool ok = true; + + json->getProperty("rows", &field); + if (!field.isNullOrUndefined()) ok &= sevalue_to_native(field, &to->rows, ctx); + + json->getProperty("columns", &field); + if (!field.isNullOrUndefined()) ok &= sevalue_to_native(field, &to->columns, ctx); + + CC_UNUSED size_t dataLength = 0; + json->getProperty("samples", &field); + if (!field.isNullOrUndefined()) { + se::Object *obj = field.toObject(); + if (obj->isArrayBuffer()) { + ok &= obj->getArrayBufferData(reinterpret_cast(&to->samples), &dataLength); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok &= obj->getTypedArrayData(reinterpret_cast(&to->samples), &dataLength); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + ok &= false; + } + } + return ok; +} + +bool sevalue_to_native(const se::Value &from, cc::physics::RaycastOptions *to, se::Object *ctx) { + CC_ASSERT(from.isObject()); + se::Object *json = from.toObject(); + auto *data = static_cast(json->getPrivateData()); + if (data) { + *to = *data; + return true; + } + + se::Value field; + bool ok = true; + + json->getProperty("origin", &field); + if (!field.isNullOrUndefined()) ok &= sevalue_to_native(field, &to->origin, ctx); + + json->getProperty("unitDir", &field); + if (!field.isNullOrUndefined()) ok &= sevalue_to_native(field, &to->unitDir, ctx); + + json->getProperty("mask", &field); + if (!field.isNullOrUndefined()) ok &= sevalue_to_native(field, &to->mask, ctx); + + json->getProperty("distance", &field); + if (!field.isNullOrUndefined()) ok &= sevalue_to_native(field, &to->distance, ctx); + + json->getProperty("queryTrigger", &field); + if (!field.isNullOrUndefined()) ok &= sevalue_to_native(field, &to->queryTrigger, ctx); + + return ok; +} + +#endif // CC_USE_PHYSICS_PHYSX diff --git a/cocos/bindings/manual/jsb_conversions_spec.h b/cocos/bindings/manual/jsb_conversions_spec.h new file mode 100644 index 0000000..86ff906 --- /dev/null +++ b/cocos/bindings/manual/jsb_conversions_spec.h @@ -0,0 +1,638 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + http://www.cocos.com + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Ptr.h" +#include "base/Value.h" +#include "base/std/any.h" +#include "base/std/container/unordered_map.h" +#include "base/std/optional.h" +#include "base/std/variant.h" +#include "bindings/jswrapper/SeApi.h" +#include "core/TypedArray.h" +#include "core/assets/AssetsModuleHeader.h" +#include "core/assets/RenderingSubMesh.h" + +#if CC_USE_PHYSICS_PHYSX + #include "physics/spec/IShape.h" + #include "physics/spec/IWorld.h" +#endif + +namespace cc { +class Data; +class Vec4; +class Vec2; +class Vec3; +class Size; +class Mat3; +class Mat4; +class Quaternion; +class Color; +class Rect; + +template +class RefMap; + +struct NativeDep; + +class ArrayBuffer; + +// class TypedArray; +// class IBArray; + +namespace network { +struct DownloaderHints; +class DownloadTask; +} // namespace network + +namespace scene { +class FogInfo; +class ShadowsInfo; +class SkyboxInfo; +} // namespace scene + +namespace geometry { +class AABB; +class Capsule; +class Line; +class Ray; +class Sphere; +class Triangle; +class Plane; +class Frustum; +class Spline; +} // namespace geometry + +namespace extension { +struct ManifestAsset; +} + +namespace middleware { +class Texture2D; +} + +namespace gfx { +struct Viewport; +struct Offset; +struct Extent; +struct TextureSubres; +struct TextureCopy; +struct BufferTextureCopy; +struct BufferInfo; +struct BufferViewInfo; +struct TextureInfo; +struct DescriptorSetInfo; +struct BindingMappingInfo; +struct ShaderStage; +struct UniformSampler; +struct UniformBlock; +struct Uniform; +struct ShaderInfo; +struct DrawInfo; +struct IndirectBuffer; +struct SamplerInfo; +struct ColorAttachment; +struct DepthStencilAttachment; +struct SubPassInfo; +struct RenderPassInfo; +struct QueueInfo; +struct PipelineLayoutInfo; +struct DescriptorSetLayoutBinding; +struct DescriptorSetLayoutInfo; +struct FramebufferInfo; +struct CommandBufferInfo; +struct InputAssemblerInfo; +} // namespace gfx +} // namespace cc + +#if CC_USE_SPINE + +namespace spine { +class String; +template +class Vector; +template +class Map; +class Vector2; +} // namespace spine + +#endif + +//////////////////////////////////////////////////////////////////////////// +/////////////////sevalue to native///////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +// se value -> native value +CC_DEPRECATED(3.6, "use sevalue_to_native instead") +bool seval_to_ccvalue(const se::Value &v, cc::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use sevalue_to_native instead") +bool seval_to_ccvaluemap(const se::Value &v, cc::ValueMap *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use sevalue_to_native instead") +bool seval_to_ccvaluemapintkey(const se::Value &v, cc::ValueMapIntKey *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use sevalue_to_native instead") +bool seval_to_ccvaluevector(const se::Value &v, cc::ValueVector *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use sevalue_to_native instead") +bool sevals_variadic_to_ccvaluevector(const se::ValueArray &args, cc::ValueVector *ret); // NOLINT(readability-identifier-naming) + +CC_DEPRECATED(3.6, "use sevalue_to_native instead") +bool seval_to_Data(const se::Value &v, cc::Data *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use sevalue_to_native instead") +bool seval_to_DownloaderHints(const se::Value &v, cc::network::DownloaderHints *ret); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::MacroValue *to, se::Object *ctx); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::IPreCompileInfoValueType *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::IPropertyEditorValueType *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) + +// ccstd::any +bool sevalue_to_native(const se::Value &from, ccstd::any *to, se::Object *ctx); // NOLINT(readability-identifier-naming) +////////////////// ArrayBuffer +bool sevalue_to_native(const se::Value &from, cc::ArrayBuffer *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::ArrayBuffer **to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, ccstd::vector *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::MaterialProperty *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) + +inline bool sevalue_to_native(const se::Value &from, ccstd::string *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + if (from.isString()) { + *to = from.toString(); + return true; + } + if (from.isNumber()) { + *to = from.toStringForce(); + return true; + } + if (from.isNullOrUndefined()) { + to->clear(); + return true; + } + CC_ABORTF("parmater '%s' is not a string nor number", from.toStringForce().c_str()); + to->clear(); + return false; +} + +inline bool seval_to_std_string(const se::Value &from, ccstd::string *ret) { // NOLINT(readability-identifier-naming) + assert(ret); + *ret = from.toStringForce(); + return true; +} + +inline bool sevalue_to_native(const se::Value &from, std::string_view *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + if (from.isString()) { + *to = from.toString(); + } + return true; +} + +///// integers +inline bool sevalue_to_native(const se::Value &from, bool *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + *to = from.isNullOrUndefined() ? false : (from.isNumber() ? from.toDouble() != 0 : from.toBoolean()); + return true; +} + +inline bool sevalue_to_native(const se::Value &from, int32_t *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + *to = from.toInt32(); + return true; +} +inline bool sevalue_to_native(const se::Value &from, uint32_t *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + *to = from.toUint32(); + return true; +} + +inline bool sevalue_to_native(const se::Value &from, int16_t *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + *to = from.toInt16(); + return true; +} +inline bool sevalue_to_native(const se::Value &from, uint16_t *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + *to = from.toUint16(); + return true; +} + +inline bool sevalue_to_native(const se::Value &from, int8_t *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + *to = from.toInt8(); + return true; +} +inline bool sevalue_to_native(const se::Value &from, uint8_t *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + *to = from.toUint8(); + return true; +} + +inline bool sevalue_to_native(const se::Value &from, uint64_t *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + *to = from.toUint64(); + return true; +} + +inline bool sevalue_to_native(const se::Value &from, int64_t *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + *to = from.toInt64(); + return true; +} + +#if CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS +inline bool sevalue_to_native(const se::Value &from, unsigned long *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + // on mac: unsiged long === uintptr_t + static_assert(sizeof(*to) == 8, ""); + *to = static_cast(from.toUint64()); + return true; +} +inline bool sevalue_to_native(const se::Value &from, long *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + // on mac: unsiged long === uintptr_t + static_assert(sizeof(*to) == 8, ""); + *to = static_cast(from.toUint64()); + return true; +} +#endif + +inline bool sevalue_to_native(const se::Value &from, float *to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + *to = from.toFloat(); + return true; +} +inline bool sevalue_to_native(const se::Value &from, double *to, se::Object * /*unused*/) { // NOLINT(readability-identifier-naming) + *to = from.toDouble(); + return true; +} + +// inline bool sevalue_to_native(const se::Value & /*from*/, void * /*to*/, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) +// CC_ABORT(); // void not supported +// return false; +// } + +bool sevalue_to_native(const se::Value &from, cc::Data *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::Value *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +inline bool sevalue_to_native(const se::Value &from, se::Value *to, se::Object * /*unused*/) { // NOLINT(readability-identifier-naming) + *to = from; + return true; +} + +inline bool sevalue_to_native(const se::Value &from, se::Object **to, se::Object * /*unused*/) { // NOLINT(readability-identifier-naming) + *to = from.toObject(); + return true; +} + +inline bool sevalue_to_native(const se::Value &from, const se::Object **to, se::Object * /*unused*/) { // NOLINT(readability-identifier-naming) + *to = from.toObject(); + return true; +} + +bool sevalue_to_native(const se::Value &from, cc::Vec4 *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::Mat3 *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::Mat4 *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::Vec3 *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::Vec2 *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::Size *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::Quaternion *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::Color *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::Rect *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::gfx::Rect *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +inline bool sevalue_to_native(const se::Value &from, ccstd::vector *to, se::Object * /*unused*/) { // NOLINT(readability-identifier-naming) + if (from.isNullOrUndefined()) { + to->clear(); + return true; + } + CC_ASSERT(from.isObject() && from.toObject()->isArray()); + auto *array = from.toObject(); + to->clear(); + uint32_t size; + array->getArrayLength(&size); + for (uint32_t i = 0; i < size; i++) { + se::Value ele; + array->getArrayElement(i, &ele); + to->emplace_back(ele); + } + return true; +} + +////////////////// ccstd::any +inline bool sevalue_to_native(const se::Value & /*from*/, ccstd::any * /*to*/, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + CC_ABORT(); + SE_LOGE("Can not convert any to specific types"); + return false; +} + +bool sevalue_to_native(const se::Value &from, cc::TypedArray *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, cc::IBArray *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) + +// bool sevalue_to_native(const se::Value &from, cc::gfx::Context **to, se::Object*) {// NOLINT(readability-identifier-naming) +// CC_ASSERT(from.isObject()); +// *to = (cc::gfx::Context*)from.toObject()->getPrivateData(); +// return true; +// } + +inline bool sevalue_to_native(const se::Value &from, void **to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + CC_ASSERT_NOT_NULL(to); + if (from.isNumber() || from.isBigInt()) { + // NOLINTNEXTLINE(performance-no-int-to-ptr) + *to = reinterpret_cast(from.toUint64()); + return true; + } + if (from.isObject()) { + *to = from.toObject()->getPrivateData(); + return true; + } + SE_LOGE("[warn] failed to convert to void *\n"); + return false; +} + +inline bool sevalue_to_native(const se::Value &from, ccstd::string **to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + if (to != nullptr && *to != nullptr) { + **to = from.toString(); + } + return true; +} + +bool sevalue_to_native(const se::Value &from, cc::ValueMap *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, ccstd::vector *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, ccstd::variant *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, ccstd::vector *to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::IPropertyValue *to, se::Object *ctx); // NOLINT(readability-identifier-naming) +inline bool sevalue_to_native(const se::Value & /*from*/, ccstd::monostate * /*to*/, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + // nothing todo + return false; +} + +//////////////////////// scene info +bool sevalue_to_native(const se::Value &from, cc::scene::FogInfo *, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::scene::ShadowsInfo *, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::scene::SkyboxInfo *, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) + +/////////////////////// geometry + +bool sevalue_to_native(const se::Value &from, cc::geometry::AABB *, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Capsule *, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Line *, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Ray *, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Sphere *, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Triangle *, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Plane *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Plane **to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Frustum *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) +bool sevalue_to_native(const se::Value &from, cc::geometry::Spline *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +//////////////////////////////////////////////////////////////////////////// +////////////////////nativevalue to se ///////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// + +// native value -> se value +CC_DEPRECATED(3.6, "use native_to_se instead") +bool ccvalue_to_seval(const cc::Value &v, se::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use native_to_se instead") +bool ccvaluemap_to_seval(const cc::ValueMap &v, se::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use native_to_se instead") +bool ccvaluemapintkey_to_seval(const cc::ValueMapIntKey &v, se::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use native_to_se instead") +bool ccvaluevector_to_seval(const cc::ValueVector &v, se::Value *ret); // NOLINT(readability-identifier-naming) + +CC_DEPRECATED(3.6, "use native_to_se instead") +bool ManifestAsset_to_seval(const cc::extension::ManifestAsset &v, se::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use native_to_se instead") +bool Data_to_seval(const cc::Data &v, se::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use native_to_se instead") +bool DownloadTask_to_seval(const cc::network::DownloadTask &v, se::Value *ret); // NOLINT(readability-identifier-naming) + +CC_DEPRECATED(3.6, "use native_to_se instead") +bool Vec2_to_seval(const cc::Vec2 &v, se::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use native_to_se instead") +bool Vec3_to_seval(const cc::Vec3 &v, se::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use native_to_se instead") +bool Vec4_to_seval(const cc::Vec4 &v, se::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use native_to_se instead") +bool Mat4_to_seval(const cc::Mat4 &v, se::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use native_to_se instead") +bool Size_to_seval(const cc::Size &v, se::Value *ret); // NOLINT(readability-identifier-naming) +CC_DEPRECATED(3.6, "use native_to_se instead") +bool Rect_to_seval(const cc::Rect &v, se::Value *ret); // NOLINT(readability-identifier-naming) + +// bool nativevalue_to_se(const cc::TypedArray &typedArray, se::Value &to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) // NOLINT + +/** + * WARN: call nativevalue_to_se instead and it converts cc::Data to ArrayBuffer + */ +bool Data_to_TypedArray(const cc::Data &v, se::Value *ret); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const cc::ArrayBuffer &arrayBuffer, se::Value &to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) // NOLINT +inline bool nativevalue_to_se(cc::ArrayBuffer *arrayBuffer, se::Value &to, se::Object *ctx) { // NOLINT(readability-identifier-naming) // NOLINT + if (arrayBuffer == nullptr) { + return false; + } + return nativevalue_to_se(*arrayBuffer, to, ctx); +} + +inline bool nativevalue_to_se(const ccstd::vector &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + se::HandleObject array{se::Object::createTypedArray(se::Object::TypedArrayType::INT8, from.data(), from.size())}; + to.setObject(array); + return true; +} + +inline bool nativevalue_to_se(const ccstd::vector &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + se::HandleObject array{se::Object::createTypedArray(se::Object::TypedArrayType::UINT8, from.data(), from.size())}; + to.setObject(array); + return true; +} + +inline bool nativevalue_to_se(int64_t from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setInt64(from); + return true; +} + +inline bool nativevalue_to_se(uint64_t from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setUint64(from); + return true; +} + +inline bool nativevalue_to_se(int32_t from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setInt32(from); + return true; +} + +inline bool nativevalue_to_se(uint32_t from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setUint32(from); + return true; +} +inline bool nativevalue_to_se(int16_t from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setInt16(from); + return true; +} +inline bool nativevalue_to_se(uint16_t from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setUint16(from); + return true; +} + +inline bool nativevalue_to_se(int8_t from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setInt8(from); + return true; +} + +inline bool nativevalue_to_se(uint8_t from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setUint8(from); + return true; +} + +inline bool nativevalue_to_se(float from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setFloat(from); + return true; +} +inline bool nativevalue_to_se(double from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setDouble(from); + return true; +} +inline bool nativevalue_to_se(bool from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setBoolean(from); + return true; +} + +#if CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS +inline bool nativevalue_to_se(unsigned long from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + static_assert(sizeof(from) == 8, ""); + to.setDouble(static_cast(from)); + return true; +} +inline bool nativevalue_to_se(long from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + static_assert(sizeof(from) == 8, ""); + to.setDouble(static_cast(from)); + return true; +} + +#endif + +inline bool nativevalue_to_se(const ccstd::string &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setString(from); + return true; +} + +inline bool nativevalue_to_se(const std::string_view &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setString(from); + return true; +} + +inline bool nativevalue_to_se(const char *from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setString(from); + return true; +} + +inline bool nativevalue_to_se(char *from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setString(from); + return true; +} + +bool nativevalue_to_se(const cc::NativeDep &from, se::Value &to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +// JSB_REGISTER_OBJECT_TYPE(cc::network::DownloaderHints); + +bool nativevalue_to_se(const cc::Data &from, se::Value &to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const cc::Value &from, se::Value &to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const ccstd::unordered_map &from, se::Value &to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const cc::Size &from, se::Value &to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const cc::extension::ManifestAsset &from, se::Value &to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const cc::Rect &from, se::Value &to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const cc::gfx::Rect &from, se::Value &to, se::Object *ctx); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const cc::gfx::FormatInfo *from, se::Value &to, se::Object *ctx); // NOLINT(readability-identifier-naming + +bool nativevalue_to_se(const cc::network::DownloadTask &from, se::Value &to, se::Object * /*ctx*/); // NOLINT(readability-identifier-naming) + +inline bool nativevalue_to_se(const ccstd::monostate & /*from*/, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setUndefined(); + return true; +} + +inline bool nativevalue_to_se(const ccstd::any &from, se::Value &to, se::Object *ctx) { // NOLINT + CC_ABORT(); + SE_LOGE("should not convert ccstd::any"); + return true; +} + +using void_p = void *; +inline bool nativevalue_to_se(const void_p &from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + if (!from) { + to.setUndefined(); + } else { + auto ptr = reinterpret_cast(from); + sizeof(from) == 8 ? to.setUint64(static_cast(ptr)) : to.setUint32(static_cast(ptr)); + } + return true; +} + +// Spine conversions +#if CC_USE_SPINE + +bool sevalue_to_native(const se::Value &, spine::String *, se::Object *); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const spine::Vector &v, se::Value &ret, se::Object *ctx); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const spine::String &obj, se::Value &val, se::Object *ctx); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &v, spine::Vector *ret, se::Object *ctx); // NOLINT(readability-identifier-naming) + +bool sevalue_to_native(const se::Value &from, spine::Vector2 *to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +bool nativevalue_to_se(const spine::Vector2 &from, se::Value &to, se::Object * /*unused*/); // NOLINT(readability-identifier-naming) + +#endif + +inline bool nativevalue_to_se(const se::Object *from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setObject(const_cast(from)); + return true; +} + +inline bool nativevalue_to_se(se::Object *from, se::Value &to, se::Object * /*ctx*/) { // NOLINT(readability-identifier-naming) + to.setObject(from); + return true; +} + +#if CC_USE_MIDDLEWARE +bool seval_to_Map_string_key(const se::Value &v, cc::RefMap *ret); // NOLINT(readability-identifier-naming) +#endif // CC_USE_MIDDLEWARE + +#if CC_USE_PHYSICS_PHYSX + +bool nativevalue_to_se(const ccstd::vector> &from, se::Value &to, se::Object * /*ctx*/); +bool nativevalue_to_se(const ccstd::vector &from, se::Value &to, se::Object * /*ctx*/); +bool nativevalue_to_se(const ccstd::vector> &from, se::Value &to, se::Object *ctx); +bool nativevalue_to_se(const cc::physics::RaycastResult &from, se::Value &to, se::Object *ctx); +bool nativevalue_to_se(const ccstd::vector> &from, se::Value &to, se::Object *ctx); +bool nativevalue_to_se(const ccstd::vector> &from, se::Value &to, se::Object * /*ctx*/); + +bool sevalue_to_native(const se::Value &from, cc::physics::ConvexDesc *to, se::Object *ctx); +bool sevalue_to_native(const se::Value &from, cc::physics::TrimeshDesc *to, se::Object *ctx); +bool sevalue_to_native(const se::Value &from, cc::physics::HeightFieldDesc *to, se::Object *ctx); +bool sevalue_to_native(const se::Value &from, cc::physics::RaycastOptions *to, se::Object *ctx); + +#endif // USE_PHYSICS_PHYSX diff --git a/cocos/bindings/manual/jsb_dragonbones_manual.cpp b/cocos/bindings/manual/jsb_dragonbones_manual.cpp new file mode 100644 index 0000000..94da7a7 --- /dev/null +++ b/cocos/bindings/manual/jsb_dragonbones_manual.cpp @@ -0,0 +1,486 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_dragonbones_manual.h" +#include "cocos/bindings/auto/jsb_dragonbones_auto.h" +#include "cocos/bindings/auto/jsb_editor_support_auto.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" +#include "cocos/bindings/manual/jsb_helper.h" +#include "cocos/editor-support/dragonbones-creator-support/CCDragonBonesHeaders.h" + +using namespace cc; + +// add by fins +static bool js_cocos2dx_dragonbones_Slot_get_globalTransformMatrix(se::State &s) { + dragonBones::Slot *cobj = (dragonBones::Slot *)s.nativeThisObject(); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 0) { + dragonBones::Matrix *result = cobj->getGlobalTransformMatrix(); + ok &= native_ptr_to_seval((dragonBones::Matrix *)result, &s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 0); + return false; +} +SE_BIND_PROP_GET(js_cocos2dx_dragonbones_Slot_get_globalTransformMatrix) + +// add by fins +static bool js_cocos2dx_dragonbones_Animation_get_animations(se::State &s) { + dragonBones::Animation *cobj = (dragonBones::Animation *)s.nativeThisObject(); + se::HandleObject retObj(se::Object::createPlainObject()); + bool ok = false; + se::Value tmp; + for (const auto &e : cobj->getAnimations()) { + if (!e.first.empty()) { + ok = native_ptr_to_seval(e.second, __jsb_dragonBones_AnimationData_class, &tmp); + SE_PRECONDITION2(ok, false, "Convert dragonBones::AnimationData to se::Value failed!"); + retObj->setProperty(e.first.c_str(), tmp); + } + } + s.rval().setObject(retObj); + return true; +} +SE_BIND_PROP_GET(js_cocos2dx_dragonbones_Animation_get_animations) + +static bool js_cocos2dx_dragonbones_Armature_getDisplay(se::State &s) { + if (s.args().size() == 0) { + dragonBones::Armature *cobj = (dragonBones::Armature *)s.nativeThisObject(); + dragonBones::CCArmatureDisplay *ret = (dragonBones::CCArmatureDisplay *)(cobj->getDisplay()); + if (ret != nullptr) { + bool ok = native_ptr_to_seval(ret, __jsb_dragonBones_CCArmatureDisplay_class, &s.rval()); + SE_PRECONDITION2(ok, false, "Convert dragonBones::Animation to se::Value failed!"); + } else { + s.rval().setNull(); + } + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)s.args().size(), 0); + return false; +} +SE_BIND_FUNC(js_cocos2dx_dragonbones_Armature_getDisplay) + +static bool js_cocos2dx_dragonbones_Armature_getSlots(se::State &s) { + dragonBones::Armature *cobj = (dragonBones::Armature *)s.nativeThisObject(); + const auto &result = cobj->getSlots(); + se::HandleObject arr(se::Object::createArrayObject(result.size())); + + uint32_t i = 0; + se::Value tmp; + bool ok = true; + for (const auto &slot : result) { + if (!native_ptr_to_seval(slot, &tmp)) { + ok = false; + break; + } + arr->setArrayElement(i, tmp); + ++i; + } + if (ok) + s.rval().setObject(arr); + + SE_PRECONDITION2(ok, false, "Convert getSlots to se::Value failed!"); + return true; +} +SE_BIND_FUNC(js_cocos2dx_dragonbones_Armature_getSlots) + +static bool js_cocos2dx_dragonbones_Armature_getBones(se::State &s) { + dragonBones::Armature *cobj = (dragonBones::Armature *)s.nativeThisObject(); + const auto &result = cobj->getBones(); + se::HandleObject arr(se::Object::createArrayObject(result.size())); + + uint32_t i = 0; + se::Value tmp; + bool ok = true; + for (const auto &bone : result) { + if (!native_ptr_to_seval(bone, &tmp)) { + ok = false; + break; + } + arr->setArrayElement(i, tmp); + ++i; + } + if (ok) + s.rval().setObject(arr); + + SE_PRECONDITION2(ok, false, "Convert getBones to se::Value failed!"); + return true; +} +SE_BIND_FUNC(js_cocos2dx_dragonbones_Armature_getBones) + +static bool js_cocos2dx_dragonbones_Armature_getBoneByDisplay(se::State &s) { + dragonBones::Armature *cobj = (dragonBones::Armature *)s.nativeThisObject(); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + dragonBones::CCArmatureDisplay *display = nullptr; + ok = seval_to_native_ptr(args[0], &display); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + dragonBones::Bone *result = cobj->getBoneByDisplay(display); + ok &= native_ptr_to_seval((dragonBones::Bone *)result, &s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_cocos2dx_dragonbones_Armature_getBoneByDisplay) + +static bool js_cocos2dx_dragonbones_Armature_getSlotByDisplay(se::State &s) { + dragonBones::Armature *cobj = (dragonBones::Armature *)s.nativeThisObject(); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + dragonBones::CCArmatureDisplay *display = nullptr; + ok = seval_to_native_ptr(args[0], &display); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + dragonBones::Slot *result = cobj->getSlotByDisplay(display); + ok &= native_ptr_to_seval((dragonBones::Slot *)result, &s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_cocos2dx_dragonbones_Armature_getSlotByDisplay) + +static bool js_cocos2dx_dragonbones_Armature_setReplacedTexture(se::State &s) { + dragonBones::Armature *cobj = (dragonBones::Armature *)s.nativeThisObject(); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + middleware::Texture2D *texture = nullptr; + ok = seval_to_native_ptr(args[0], &texture); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->setReplacedTexture(texture); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_cocos2dx_dragonbones_Armature_setReplacedTexture) + +static bool js_cocos2dx_dragonbones_Armature_getReplacedTexture(se::State &s) { + dragonBones::Armature *cobj = (dragonBones::Armature *)s.nativeThisObject(); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 0) { + middleware::Texture2D *result = (middleware::Texture2D *)cobj->getReplacedTexture(); + ok = native_ptr_to_seval(result, __jsb_cc_middleware_Texture2D_class, &s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 0); + return false; +} +SE_BIND_FUNC(js_cocos2dx_dragonbones_Armature_getReplacedTexture) + +static bool js_cocos2dx_dragonbones_ArmatureData_get_animations(se::State &s) { + dragonBones::ArmatureData *cobj = (dragonBones::ArmatureData *)s.nativeThisObject(); + se::HandleObject retObj(se::Object::createPlainObject()); + bool ok = false; + se::Value tmp; + for (const auto &e : cobj->animations) { + if (!e.first.empty()) { + ok = native_ptr_to_seval(e.second, __jsb_dragonBones_AnimationData_class, &tmp); + SE_PRECONDITION2(ok, false, "Convert dragonBones::AnimationData to se::Value failed!"); + retObj->setProperty(e.first.c_str(), tmp); + } + } + s.rval().setObject(retObj); + return true; +} +SE_BIND_PROP_GET(js_cocos2dx_dragonbones_ArmatureData_get_animations) + +static bool js_cocos2dx_dragonbones_ArmatureData_get_bones(se::State &s) { + dragonBones::ArmatureData *cobj = (dragonBones::ArmatureData *)s.nativeThisObject(); + se::HandleObject retObj(se::Object::createPlainObject()); + bool ok = false; + se::Value tmp; + for (const auto &e : cobj->bones) { + if (!e.first.empty()) { + ok = native_ptr_to_seval(e.second, __jsb_dragonBones_BoneData_class, &tmp); + SE_PRECONDITION2(ok, false, "Convert dragonBones::AnimationData to se::Value failed!"); + retObj->setProperty(e.first.c_str(), tmp); + } + } + s.rval().setObject(retObj); + return true; +} +SE_BIND_PROP_GET(js_cocos2dx_dragonbones_ArmatureData_get_bones) + +static bool js_cocos2dx_dragonbones_ArmatureData_get_skins(se::State &s) { + dragonBones::ArmatureData *cobj = (dragonBones::ArmatureData *)s.nativeThisObject(); + se::HandleObject retObj(se::Object::createPlainObject()); + bool ok = false; + se::Value tmp; + for (const auto &e : cobj->skins) { + if (!e.first.empty()) { + ok = native_ptr_to_seval(e.second, __jsb_dragonBones_SkinData_class, &tmp); + SE_PRECONDITION2(ok, false, "Convert dragonBones::AnimationData to se::Value failed!"); + retObj->setProperty(e.first.c_str(), tmp); + } + } + s.rval().setObject(retObj); + return true; +} +SE_BIND_PROP_GET(js_cocos2dx_dragonbones_ArmatureData_get_skins) + +static bool js_cocos2dx_dragonbones_ArmatureData_get_slots(se::State &s) { + dragonBones::ArmatureData *cobj = (dragonBones::ArmatureData *)s.nativeThisObject(); + se::HandleObject retObj(se::Object::createPlainObject()); + bool ok = false; + se::Value tmp; + for (const auto &e : cobj->slots) { + if (!e.first.empty()) { + ok = native_ptr_to_seval(e.second, __jsb_dragonBones_SlotData_class, &tmp); + SE_PRECONDITION2(ok, false, "Convert dragonBones::AnimationData to se::Value failed!"); + retObj->setProperty(e.first.c_str(), tmp); + } + } + s.rval().setObject(retObj); + return true; +} +SE_BIND_PROP_GET(js_cocos2dx_dragonbones_ArmatureData_get_slots) + +static bool js_cocos2dx_dragonbones_DragonBonesData_get_armatureNames(se::State &s) { + dragonBones::DragonBonesData *cobj = (dragonBones::DragonBonesData *)s.nativeThisObject(); + + const auto &ret = cobj->getArmatureNames(); + bool ok = nativevalue_to_se(ret, s.rval()); + SE_PRECONDITION2(ok, false, "Convert ArmatureNames to se::Value failed!"); + return true; +} +SE_BIND_PROP_GET(js_cocos2dx_dragonbones_DragonBonesData_get_armatureNames) + +static bool js_cocos2dx_dragonbones_Slot_getDisplay(se::State &s) { + dragonBones::Slot *cobj = (dragonBones::Slot *)s.nativeThisObject(); + dragonBones::CCArmatureDisplay *ret = static_cast(cobj->getDisplay()); + bool ok = native_ptr_to_seval(ret, __jsb_dragonBones_CCArmatureDisplay_class, &s.rval()); + SE_PRECONDITION2(ok, false, "Convert dragonBones::DBCCSprite to se::Value failed!"); + return true; +} +SE_BIND_FUNC(js_cocos2dx_dragonbones_Slot_getDisplay) + +static bool js_cocos2dx_dragonbones_Slot_set_displayIndex(se::State &s) { + const auto &args = s.args(); + dragonBones::Slot *cobj = (dragonBones::Slot *)s.nativeThisObject(); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + CC_UNUSED bool ok = true; + int32_t arg0; + ok &= sevalue_to_native(args[0], &arg0); + SE_PRECONDITION2(ok, false, "Error processing new value"); + cobj->setDisplayIndex(arg0); + return true; +} +SE_BIND_PROP_SET(js_cocos2dx_dragonbones_Slot_set_displayIndex) + +static bool js_cocos2dx_dragonbones_Slot_get_displayIndex(se::State &s) { + dragonBones::Slot *cobj = (dragonBones::Slot *)s.nativeThisObject(); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + const int32_t ret = cobj->getDisplayIndex(); + bool ok = nativevalue_to_se(ret, s.rval()); + SE_PRECONDITION2(ok, false, "js_cocos2dx_dragonbones_Slot_get_displayIndex to se::Value failed!"); + return true; +} +SE_BIND_PROP_GET(js_cocos2dx_dragonbones_Slot_get_displayIndex) + +static bool js_cocos2dx_dragonbones_Slot_setDisplay(se::State &s) { + const auto &args = s.args(); + int argc = (int)args.size(); + + if (argc == 2) { + dragonBones::Slot *cobj = (dragonBones::Slot *)s.nativeThisObject(); + dragonBones::CCArmatureDisplay *dbSprite = nullptr; + bool ok = seval_to_native_ptr(args[0], &dbSprite); + SE_PRECONDITION2(ok, false, "Convert se::Value to dragonBones::DBCCSprite failed!"); + dragonBones::DisplayType type; + ok = sevalue_to_native(args[1], (int32_t *)&type); + SE_PRECONDITION2(ok, false, "Convert se::Value to dragonBones::DisplayType failed!"); + cobj->setDisplay(dbSprite, type); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 2); + return false; +} +SE_BIND_FUNC(js_cocos2dx_dragonbones_Slot_setDisplay) + +static bool js_cocos2dx_dragonbones_BaseFactory_parseTextureAtlasData(se::State &s) { + dragonBones::BaseFactory *cobj = (dragonBones::BaseFactory *)s.nativeThisObject(); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 2) { + const char *arg0 = nullptr; + void *arg1 = nullptr; + ccstd::string arg0_tmp; + ok &= sevalue_to_native(args[0], &arg0_tmp); + arg0 = arg0_tmp.c_str(); + ok &= seval_to_native_ptr(args[1], &arg1); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + dragonBones::TextureAtlasData *result = cobj->parseTextureAtlasData(arg0, arg1); + ok &= native_ptr_to_seval((dragonBones::TextureAtlasData *)result, &s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + if (argc == 3) { + const char *arg0 = nullptr; + void *arg1 = nullptr; + ccstd::string arg2; + ccstd::string arg0_tmp; + ok &= sevalue_to_native(args[0], &arg0_tmp); + arg0 = arg0_tmp.c_str(); + ok &= seval_to_native_ptr(args[1], &arg1); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + ok &= sevalue_to_native(args[2], &arg2); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + dragonBones::TextureAtlasData *result = cobj->parseTextureAtlasData(arg0, arg1, arg2); + ok &= native_ptr_to_seval((dragonBones::TextureAtlasData *)result, &s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + if (argc == 4) { + const char *arg0 = nullptr; + void *arg1 = nullptr; + ccstd::string arg2; + float arg3 = 0; + ccstd::string arg0_tmp; + ok &= sevalue_to_native(args[0], &arg0_tmp); + arg0 = arg0_tmp.c_str(); + ok &= seval_to_native_ptr(args[1], &arg1); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + ok &= sevalue_to_native(args[2], &arg2); + ok &= sevalue_to_native(args[3], &arg3); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + dragonBones::TextureAtlasData *result = cobj->parseTextureAtlasData(arg0, arg1, arg2, arg3); + ok &= native_ptr_to_seval((dragonBones::TextureAtlasData *)result, &s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 4); + return false; +} +SE_BIND_FUNC(js_cocos2dx_dragonbones_BaseFactory_parseTextureAtlasData) + +bool register_all_dragonbones_manual(se::Object *obj) { + // add by fins + __jsb_dragonBones_Slot_proto->defineProperty("globalTransformMatrix", _SE(js_cocos2dx_dragonbones_Slot_get_globalTransformMatrix), nullptr); + // add by fins + __jsb_dragonBones_Animation_proto->defineProperty("animations", _SE(js_cocos2dx_dragonbones_Animation_get_animations), nullptr); + + __jsb_dragonBones_Armature_proto->defineFunction("getDisplay", _SE(js_cocos2dx_dragonbones_Armature_getDisplay)); + __jsb_dragonBones_Armature_proto->defineFunction("getSlots", _SE(js_cocos2dx_dragonbones_Armature_getSlots)); + __jsb_dragonBones_Armature_proto->defineFunction("getBones", _SE(js_cocos2dx_dragonbones_Armature_getBones)); + __jsb_dragonBones_Armature_proto->defineFunction("getBoneByDisplay", _SE(js_cocos2dx_dragonbones_Armature_getBoneByDisplay)); + __jsb_dragonBones_Armature_proto->defineFunction("getSlotByDisplay", _SE(js_cocos2dx_dragonbones_Armature_getSlotByDisplay)); + __jsb_dragonBones_Armature_proto->defineFunction("setReplacedTexture", _SE(js_cocos2dx_dragonbones_Armature_setReplacedTexture)); + __jsb_dragonBones_Armature_proto->defineFunction("getReplacedTexture", _SE(js_cocos2dx_dragonbones_Armature_getReplacedTexture)); + + __jsb_dragonBones_ArmatureData_proto->defineProperty("animations", _SE(js_cocos2dx_dragonbones_ArmatureData_get_animations), nullptr); + __jsb_dragonBones_ArmatureData_proto->defineProperty("bones", _SE(js_cocos2dx_dragonbones_ArmatureData_get_bones), nullptr); + __jsb_dragonBones_ArmatureData_proto->defineProperty("skins", _SE(js_cocos2dx_dragonbones_ArmatureData_get_skins), nullptr); + __jsb_dragonBones_ArmatureData_proto->defineProperty("slots", _SE(js_cocos2dx_dragonbones_ArmatureData_get_slots), nullptr); + + __jsb_dragonBones_DragonBonesData_proto->defineProperty("armatureNames", _SE(js_cocos2dx_dragonbones_DragonBonesData_get_armatureNames), nullptr); + + __jsb_dragonBones_Slot_proto->defineProperty("displayIndex", _SE(js_cocos2dx_dragonbones_Slot_get_displayIndex), _SE(js_cocos2dx_dragonbones_Slot_set_displayIndex)); + __jsb_dragonBones_Slot_proto->defineFunction("getDisplay", _SE(js_cocos2dx_dragonbones_Slot_getDisplay)); + __jsb_dragonBones_Slot_proto->defineFunction("setDisplay", _SE(js_cocos2dx_dragonbones_Slot_setDisplay)); + + __jsb_dragonBones_BaseFactory_proto->defineFunction("parseTextureAtlasData", _SE(js_cocos2dx_dragonbones_BaseFactory_parseTextureAtlasData)); + + dragonBones::BaseObject::setObjectRecycleOrDestroyCallback([](dragonBones::BaseObject *obj, int type) { + //ccstd::string typeName = typeid(*obj).name(); + if (!se::NativePtrToObjectMap::isValid()) { + return; + } + se::NativePtrToObjectMap::forEach(obj, [](se::Object *seObj) { + seObj->setClearMappingInFinalizer(false); + // Unmap native and js object since native object was destroyed. + // Otherwise, it may trigger 'assertion' in se::Object::setPrivateData later + // since native obj is already released and the new native object may be assigned with + // the same address. + }); + se::NativePtrToObjectMap::erase(obj); + }); + + se::ScriptEngine::getInstance()->addAfterCleanupHook([]() { + // If dragonBones has not init,then no need to cleanup. + if (!dragonBones::CCFactory::isInit()) { + return; + } + + dragonBones::DragonBones::checkInPool = false; + + auto factory = dragonBones::CCFactory::getFactory(); + factory->stopSchedule(); + + // Copy the dragonbones object vector since vector element will be deleted in BaseObject destructor. + ccstd::vector allDragonBonesObjects = dragonBones::BaseObject::getAllObjects(); + CC_LOG_INFO("Starting to cleanup dragonbones object, count: %d\n", (int)allDragonBonesObjects.size()); + for (auto dbObj : allDragonBonesObjects) { + if (!dbObj->isInPool()) { + dbObj->returnToPool(); + } + } + + dragonBones::BaseObject::clearPool(0); + dragonBones::CCFactory::destroyFactory(); + + dragonBones::DragonBones::checkInPool = true; + + // Don't need to use copy operator since we only print leak object below. + auto &refAllDragonBonesObjects = dragonBones::BaseObject::getAllObjects(); + SE_LOGD("After cleanup, dragonbones object remained count: %d\n", (int)refAllDragonBonesObjects.size()); + + // Print leak objects + for (auto dbObj : refAllDragonBonesObjects) { + SE_LOGD("Leak dragonbones object: %s, %p\n", typeid(*dbObj).name(), dbObj); + } + + refAllDragonBonesObjects.clear(); + }); + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} diff --git a/cocos/bindings/manual/jsb_dragonbones_manual.h b/cocos/bindings/manual/jsb_dragonbones_manual.h new file mode 100644 index 0000000..cfcd994 --- /dev/null +++ b/cocos/bindings/manual/jsb_dragonbones_manual.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { +class Object; +} + +bool register_all_dragonbones_manual(se::Object *obj); diff --git a/cocos/bindings/manual/jsb_geometry_manual.cpp b/cocos/bindings/manual/jsb_geometry_manual.cpp new file mode 100644 index 0000000..51174b0 --- /dev/null +++ b/cocos/bindings/manual/jsb_geometry_manual.cpp @@ -0,0 +1,139 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_geometry_manual.h" + +#include "bindings/manual/jsb_global.h" +#include "cocos/bindings/auto/jsb_geometry_auto.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global_init.h" + +template +static bool bindAsExternalBuffer(se::State &s) { // NOLINT + auto *self = SE_THIS_OBJECT(s); + if (!self) { + return false; + } + // NOLINTNEXTLINE + se::HandleObject buffer(se::Object::createExternalArrayBufferObject(self, sizeof(*self), [](void *, size_t, void *) {})); + s.rval().setObject(buffer); + return true; +} + +#define REG_UNDERLINE_DATA(type) \ + __jsb_cc_geometry_##type##_proto->defineFunction("underlyingData", _SE(js_cc_geometry_##type##_underlyingData)) + +#define DESC_OFFSET_OF(type, field) \ + static_cast(reinterpret_cast(&(static_cast(nullptr)->field))) + +#define DESC_UNDERLINE_DATA_BEGIN(kls) \ + { \ + using current_type = cc::geometry::kls; \ + se::HandleObject info{se::Object::createPlainObject()}; + +#define DESC_UNDERLINE_DATA_FIELD(field) \ + { \ + se::HandleObject fieldInfo{se::Object::createPlainObject()}; \ + int fieldOffset = DESC_OFFSET_OF(current_type, field); \ + constexpr int fieldSize = static_cast(sizeof(std::declval().field)); \ + fieldInfo->setProperty("fieldName", se::Value(#field)); \ + fieldInfo->setProperty("fieldOffset", se::Value(fieldOffset)); \ + fieldInfo->setProperty("fieldSize", se::Value(fieldSize)); \ + info->setProperty(#field, se::Value(fieldInfo)); \ + } + +#define DESC_UNDERLINE_DATA_END(kls) \ + se::Value protoVal; \ + __jsb_cc_geometry_##kls##_proto->getProperty("constructor", &protoVal); \ + protoVal.toObject()->setProperty("__nativeFields__", se::Value(info)); \ + } \ + REG_UNDERLINE_DATA(kls); + +#define IMPL_UNDERLINE_DATA(type) \ + static bool js_cc_geometry_##type##_underlyingData(se::State &s) { \ + return bindAsExternalBuffer(s); \ + } \ + SE_BIND_FUNC(js_cc_geometry_##type##_underlyingData) + +IMPL_UNDERLINE_DATA(Line) +IMPL_UNDERLINE_DATA(Plane) +IMPL_UNDERLINE_DATA(Ray) +IMPL_UNDERLINE_DATA(Triangle) +IMPL_UNDERLINE_DATA(Sphere) +IMPL_UNDERLINE_DATA(AABB) +IMPL_UNDERLINE_DATA(Capsule) +IMPL_UNDERLINE_DATA(Frustum) + +bool register_all_geometry_manual(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) + + DESC_UNDERLINE_DATA_BEGIN(Line) + DESC_UNDERLINE_DATA_FIELD(s) + DESC_UNDERLINE_DATA_FIELD(e) + DESC_UNDERLINE_DATA_END(Line) + + DESC_UNDERLINE_DATA_BEGIN(Plane) + DESC_UNDERLINE_DATA_FIELD(n) + DESC_UNDERLINE_DATA_FIELD(d) + DESC_UNDERLINE_DATA_END(Plane) + + DESC_UNDERLINE_DATA_BEGIN(Ray) + DESC_UNDERLINE_DATA_FIELD(o) + DESC_UNDERLINE_DATA_FIELD(d) + DESC_UNDERLINE_DATA_END(Ray) + + DESC_UNDERLINE_DATA_BEGIN(Triangle) + DESC_UNDERLINE_DATA_FIELD(a) + DESC_UNDERLINE_DATA_FIELD(b) + DESC_UNDERLINE_DATA_FIELD(c) + DESC_UNDERLINE_DATA_END(Triangle) + + DESC_UNDERLINE_DATA_BEGIN(Sphere) + DESC_UNDERLINE_DATA_FIELD(_center) + DESC_UNDERLINE_DATA_FIELD(_radius) + DESC_UNDERLINE_DATA_END(Sphere) + + DESC_UNDERLINE_DATA_BEGIN(AABB) + DESC_UNDERLINE_DATA_FIELD(center) + DESC_UNDERLINE_DATA_FIELD(halfExtents) + DESC_UNDERLINE_DATA_END(AABB) + + DESC_UNDERLINE_DATA_BEGIN(Capsule) + DESC_UNDERLINE_DATA_FIELD(radius) + DESC_UNDERLINE_DATA_FIELD(halfHeight) + DESC_UNDERLINE_DATA_FIELD(axis) + DESC_UNDERLINE_DATA_FIELD(center) + DESC_UNDERLINE_DATA_FIELD(rotation) + DESC_UNDERLINE_DATA_FIELD(ellipseCenter0) + DESC_UNDERLINE_DATA_FIELD(ellipseCenter1) + DESC_UNDERLINE_DATA_END(Capsule) + + // underlying data not required for Frustum + // DESC_UNDERLINE_DATA_BEGIN(Frustum) + // DESC_UNDERLINE_DATA_FIELD(vertices) + // DESC_UNDERLINE_DATA_FIELD(planes) + // DESC_UNDERLINE_DATA_END(Frustum) + + return true; +} diff --git a/cocos/bindings/manual/jsb_geometry_manual.h b/cocos/bindings/manual/jsb_geometry_manual.h new file mode 100644 index 0000000..85a3515 --- /dev/null +++ b/cocos/bindings/manual/jsb_geometry_manual.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { +class Object; +} + +bool register_all_geometry_manual(se::Object *obj); // NOLINT diff --git a/cocos/bindings/manual/jsb_gfx_manual.cpp b/cocos/bindings/manual/jsb_gfx_manual.cpp new file mode 100644 index 0000000..460f771 --- /dev/null +++ b/cocos/bindings/manual/jsb_gfx_manual.cpp @@ -0,0 +1,516 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_gfx_manual.h" +#include "bindings/auto/jsb_gfx_auto.h" +#include "bindings/jswrapper/SeApi.h" +#include "bindings/manual/jsb_conversions.h" +#include "bindings/manual/jsb_global.h" +#include "core/data/JSBNativeDataHolder.h" + +#include +#include + +bool js_gfx_Device_copyBuffersToTexture(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 3) { + cc::gfx::BufferDataList arg0; + cc::gfx::Texture *arg1 = nullptr; + cc::gfx::BufferTextureCopyList arg2; + if (args[0].isObject()) { + se::Object *dataObj = args[0].toObject(); + SE_PRECONDITION2(dataObj->isArray(), false, "Buffers must be an array!"); + uint32_t length = 0; + dataObj->getArrayLength(&length); + arg0.resize(length); + + se::Value value; + for (uint32_t i = 0; i < length; ++i) { + if (dataObj->getArrayElement(i, &value)) { + uint8_t *ptr = nullptr; + CC_UNUSED size_t dataLength = 0; + if (value.isObject()) { + se::Object *obj = value.toObject(); + if (obj->isArrayBuffer()) { + ok = obj->getArrayBufferData(&ptr, &dataLength); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok = obj->getTypedArrayData(&ptr, &dataLength); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + CC_ABORT(); + } + } else { + ptr = reinterpret_cast(value.asPtr()); // NOLINT(performance-no-int-to-ptr) script engine bad API design + } + + arg0[i] = ptr; + } + } + } + ok &= seval_to_native_ptr(args[1], &arg1); + ok &= sevalue_to_native(args[2], &arg2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->copyBuffersToTexture(arg0, arg1, arg2); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 3); + return false; +} +SE_BIND_FUNC(js_gfx_Device_copyBuffersToTexture) + +bool js_gfx_Device_copyTextureToBuffers(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 3) { + cc::gfx::Texture *arg0 = nullptr; + ccstd::vector arg1; + cc::gfx::BufferTextureCopyList arg2; + if (args[1].isObject()) { + se::Object *dataObj = args[1].toObject(); + SE_PRECONDITION2(dataObj->isArray(), false, "Buffers must be an array!"); + uint32_t length = 0; + dataObj->getArrayLength(&length); + arg1.resize(length); + + se::Value value; + for (uint32_t i = 0; i < length; ++i) { + if (dataObj->getArrayElement(i, &value)) { + uint8_t *ptr = nullptr; + CC_UNUSED size_t dataLength = 0; + if (value.isObject()) { + se::Object *obj = value.toObject(); + if (obj->isArrayBuffer()) { + ok = obj->getArrayBufferData(&ptr, &dataLength); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok = obj->getTypedArrayData(&ptr, &dataLength); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + CC_ABORT(); + } + } else { + ptr = reinterpret_cast(value.asPtr()); // NOLINT(performance-no-int-to-ptr) script engine bad API design + } + + arg1[i] = ptr; + } + } + } + ok &= seval_to_native_ptr(args[0], &arg0); + ok &= sevalue_to_native(args[2], &arg2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->copyTextureToBuffers(arg0, arg1.data(), arg2.data(), cc::utils::toUint(arg2.size())); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 3); + return false; +} +SE_BIND_FUNC(js_gfx_Device_copyTextureToBuffers) + +bool js_gfx_Device_copyTexImagesToTexture(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 3) { + cc::gfx::BufferDataList arg0; + cc::gfx::Texture *arg1 = nullptr; + cc::gfx::BufferTextureCopyList arg2; + + if (args[0].isObject()) { + se::Object *dataObj = args[0].toObject(); + SE_PRECONDITION2(dataObj->isArray(), false, "Buffers must be an array!"); + uint32_t length = 0; + dataObj->getArrayLength(&length); + arg0.resize(length); + + se::Value value; + for (uint32_t i = 0; i < length; ++i) { + if (dataObj->getArrayElement(i, &value)) { + if (value.isObject()) { + CC_UNUSED size_t dataLength = 0; + uint8_t *buffer{nullptr}; + if (value.toObject()->isTypedArray()) { + value.toObject()->getTypedArrayData(&buffer, &dataLength); + } else if (value.toObject()->isArrayBuffer()) { + value.toObject()->getArrayBufferData(&buffer, &dataLength); + } else { + auto *dataHolder = static_cast(value.toObject()->getPrivateData()); + CC_ASSERT_NOT_NULL(dataHolder); + buffer = dataHolder->getData(); + } + arg0[i] = buffer; + } else { + CC_ABORT(); + } + } + } + } else { + ok &= false; + } + ok &= seval_to_native_ptr(args[1], &arg1); + ok &= sevalue_to_native(args[2], &arg2, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->copyBuffersToTexture(arg0, arg1, arg2); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 3); + return false; +} +SE_BIND_FUNC(js_gfx_Device_copyTexImagesToTexture) + +static bool js_gfx_Device_createBuffer(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + + if (argc == 2) { + cc::gfx::Buffer *buffer = nullptr; + + bool createBufferView = false; + sevalue_to_native(args[1], &createBufferView); + + if (createBufferView) { + cc::gfx::BufferViewInfo bufferViewInfo; + sevalue_to_native(args[0], &bufferViewInfo, s.thisObject()); + buffer = cobj->createBuffer(bufferViewInfo); + } else { + cc::gfx::BufferInfo bufferInfo; + sevalue_to_native(args[0], &bufferInfo, s.thisObject()); + buffer = cobj->createBuffer(bufferInfo); + } + CC_UNUSED bool ok = native_ptr_to_seval(buffer, &s.rval()); + s.rval().toObject()->getPrivateObject()->tryAllowDestroyInGC(); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d", (int)argc); + return false; +} +SE_BIND_FUNC(js_gfx_Device_createBuffer) + +static bool js_gfx_Device_createTexture(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + + if (argc == 2) { + cc::gfx::Texture *texture = nullptr; + bool createTextureView = false; + sevalue_to_native(args[1], &createTextureView); + if (createTextureView) { + cc::gfx::TextureViewInfo textureViewInfo; + sevalue_to_native(args[0], &textureViewInfo, s.thisObject()); + texture = cobj->createTexture(textureViewInfo); + } else { + cc::gfx::TextureInfo textureInfo; + sevalue_to_native(args[0], &textureInfo, s.thisObject()); + texture = cobj->createTexture(textureInfo); + } + CC_UNUSED bool ok = native_ptr_to_seval(texture, &s.rval()); + s.rval().toObject()->getPrivateObject()->tryAllowDestroyInGC(); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d", (int)argc); + return false; +} +SE_BIND_FUNC(js_gfx_Device_createTexture) + +static bool js_gfx_Buffer_initialize(se::State &s) { // NOLINT(readability-identifier-naming) + CC_UNUSED bool ok = true; + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + + if (argc == 2) { + bool initWithBufferViewInfo = false; + sevalue_to_native(args[1], &initWithBufferViewInfo); + + if (initWithBufferViewInfo) { + cc::gfx::BufferViewInfo bufferViewInfo; + sevalue_to_native(args[0], &bufferViewInfo, s.thisObject()); + cobj->initialize(bufferViewInfo); + } else { + cc::gfx::BufferInfo bufferInfo; + sevalue_to_native(args[0], &bufferInfo, s.thisObject()); + cobj->initialize(bufferInfo); + } + + ok &= nativevalue_to_se(ok, s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d", (int)argc); + return false; +} +SE_BIND_FUNC(js_gfx_Buffer_initialize) + +static bool js_gfx_Texture_initialize(se::State &s) { // NOLINT(readability-identifier-naming) + CC_UNUSED bool ok = true; + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + + if (argc == 2) { + bool initWithTextureViewInfo = false; + sevalue_to_native(args[1], &initWithTextureViewInfo); + + if (initWithTextureViewInfo) { + cc::gfx::TextureViewInfo textureViewInfo; + sevalue_to_native(args[0], &textureViewInfo, s.thisObject()); + cobj->initialize(textureViewInfo); + } else { + cc::gfx::TextureInfo textureInfo; + sevalue_to_native(args[0], &textureInfo, s.thisObject()); + cobj->initialize(textureInfo); + } + + ok &= nativevalue_to_se(ok, s.rval()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d", (int)argc); + return false; +} +SE_BIND_FUNC(js_gfx_Texture_initialize) + +static bool js_gfx_GFXBuffer_update(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + + uint8_t *arg0 = nullptr; + CC_UNUSED size_t dataLength = 0; + se::Object *obj = args[0].toObject(); + if (obj->isArrayBuffer()) { + ok = obj->getArrayBufferData(&arg0, &dataLength); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok = obj->getTypedArrayData(&arg0, &dataLength); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + ok = false; + } + + if (argc == 1) { + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->update(arg0, static_cast(dataLength)); + return true; + } + if (argc == 2) { + unsigned int arg1 = 0; + ok &= sevalue_to_native(args[1], &arg1); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->update(arg0, arg1); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 3); + return false; +} +SE_BIND_FUNC(js_gfx_GFXBuffer_update) + +static bool js_gfx_CommandBuffer_execute(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 2) { + cc::gfx::CommandBufferList cmdBufs; + unsigned int count = 0; + ok &= sevalue_to_native(args[1], &count); + + se::Object *jsarr = args[0].toObject(); + CC_ASSERT(jsarr->isArray()); + uint32_t len = 0; + ok &= jsarr->getArrayLength(&len); + if (len < count) { + ok = false; + } + if (ok) { + cmdBufs.resize(count); + + se::Value tmp; + for (uint32_t i = 0; i < count; ++i) { + ok = jsarr->getArrayElement(i, &tmp); + if (!ok || !tmp.isObject()) { + cmdBufs.clear(); + break; + } + auto *cmdBuf = static_cast(tmp.toObject()->getPrivateData()); + cmdBufs[i] = cmdBuf; + } + } + + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->execute(cmdBufs, count); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2); + return false; +} +SE_BIND_FUNC(js_gfx_CommandBuffer_execute) + +static bool js_gfx_CommandBuffer_updateBuffer(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + + cc::gfx::Buffer *arg0 = nullptr; + SE_PRECONDITION2(args[0].isObject(), false, "Invalid Native Object"); + arg0 = static_cast(args[0].toObject()->getPrivateData()); + + uint8_t *arg1 = nullptr; + CC_UNUSED size_t dataLength = 0; + se::Object *obj = args[1].toObject(); + if (obj->isArrayBuffer()) { + ok = obj->getArrayBufferData(&arg1, &dataLength); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok = obj->getTypedArrayData(&arg1, &dataLength); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + ok = false; + } + + if (argc == 2) { + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->updateBuffer(arg0, arg1, static_cast(dataLength)); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 3); + return false; +} +SE_BIND_FUNC(js_gfx_CommandBuffer_updateBuffer) + +static bool js_gfx_CommandBuffer_copyBuffersToTexture(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 3) { + cc::gfx::BufferDataList arg0; + cc::gfx::Texture *arg1 = nullptr; + cc::gfx::BufferTextureCopyList arg2; + if (args[0].isObject()) { + se::Object *dataObj = args[0].toObject(); + SE_PRECONDITION2(dataObj->isArray(), false, "Buffers must be an array!"); + uint32_t length = 0; + dataObj->getArrayLength(&length); + arg0.resize(length); + + se::Value value; + for (uint32_t i = 0; i < length; ++i) { + if (dataObj->getArrayElement(i, &value)) { + uint8_t *ptr = nullptr; + CC_UNUSED size_t dataLength = 0; + se::Object *obj = value.toObject(); + if (obj->isArrayBuffer()) { + ok = obj->getArrayBufferData(&ptr, &dataLength); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok = obj->getTypedArrayData(&ptr, &dataLength); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + CC_ABORT(); + } + arg0[i] = ptr; + } + } + } + ok &= seval_to_native_ptr(args[1], &arg1); + ok &= seval_to_std_vector(args[2], &arg2); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->copyBuffersToTexture(arg0, arg1, arg2); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 3); + return false; +} +SE_BIND_FUNC(js_gfx_CommandBuffer_copyBuffersToTexture) + +bool js_gfx_get_deviceInstance(se::State &s) { // NOLINT(readability-identifier-naming) + nativevalue_to_se(cc::gfx::Device::getInstance(), s.rval(), nullptr); + return true; +} +SE_BIND_PROP_GET(js_gfx_get_deviceInstance) + +bool register_all_gfx_manual(se::Object *obj) { + __jsb_cc_gfx_Device_proto->defineFunction("copyBuffersToTexture", _SE(js_gfx_Device_copyBuffersToTexture)); + __jsb_cc_gfx_Device_proto->defineFunction("copyTextureToBuffers", _SE(js_gfx_Device_copyTextureToBuffers)); + __jsb_cc_gfx_Device_proto->defineFunction("copyTexImagesToTexture", _SE(js_gfx_Device_copyTexImagesToTexture)); + + __jsb_cc_gfx_Device_proto->defineFunction("createBuffer", _SE(js_gfx_Device_createBuffer)); + __jsb_cc_gfx_Device_proto->defineFunction("createTexture", _SE(js_gfx_Device_createTexture)); + + __jsb_cc_gfx_Buffer_proto->defineFunction("update", _SE(js_gfx_GFXBuffer_update)); + + __jsb_cc_gfx_CommandBuffer_proto->defineFunction("execute", _SE(js_gfx_CommandBuffer_execute)); + __jsb_cc_gfx_CommandBuffer_proto->defineFunction("updateBuffer", _SE(js_gfx_CommandBuffer_updateBuffer)); + __jsb_cc_gfx_CommandBuffer_proto->defineFunction("copyBuffersToTexture", _SE(js_gfx_CommandBuffer_copyBuffersToTexture)); + + __jsb_cc_gfx_Buffer_proto->defineFunction("initialize", _SE(js_gfx_Buffer_initialize)); + __jsb_cc_gfx_Texture_proto->defineFunction("initialize", _SE(js_gfx_Texture_initialize)); + + // Get the ns + se::Value nsVal; + if (!obj->getProperty("gfx", &nsVal)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + obj->setProperty("gfx", nsVal); + } + + return true; +} diff --git a/cocos/bindings/manual/jsb_gfx_manual.h b/cocos/bindings/manual/jsb_gfx_manual.h new file mode 100644 index 0000000..8e08189 --- /dev/null +++ b/cocos/bindings/manual/jsb_gfx_manual.h @@ -0,0 +1,30 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { +class Object; +} +bool register_all_gfx_manual(se::Object *obj); // NOLINT(readability-identifier-naming) diff --git a/cocos/bindings/manual/jsb_global.cpp b/cocos/bindings/manual/jsb_global.cpp new file mode 100644 index 0000000..b282c8d --- /dev/null +++ b/cocos/bindings/manual/jsb_global.cpp @@ -0,0 +1,1624 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_global.h" +#include "application/ApplicationManager.h" +#include "base/Assertf.h" +#include "base/Data.h" +#include "base/DeferredReleasePool.h" +#include "base/Scheduler.h" +#include "base/ThreadPool.h" +#include "base/ZipUtils.h" +#include "base/base64.h" +#include "bindings/auto/jsb_cocos_auto.h" +#include "core/data/JSBNativeDataHolder.h" +#include "gfx-base/GFXDef.h" +#include "jsb_conversions.h" +#include "network/Downloader.h" +#include "network/HttpClient.h" +#include "platform/Image.h" +#include "platform/interfaces/modules/ISystem.h" +#include "platform/interfaces/modules/ISystemWindow.h" +#include "ui/edit-box/EditBox.h" +#include "v8/Object.h" +#include "xxtea/xxtea.h" + +#include +#include +#include + +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include "platform/java/jni/JniImp.h" +#endif + +#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY && SCRIPT_ENGINE_TYPE != SCRIPT_ENGINE_NAPI + #include "platform/openharmony/napi/NapiHelper.h" +#endif + +extern void jsb_register_ADPF(se::Object *); // NOLINT + +using namespace cc; // NOLINT + +static LegacyThreadPool *gThreadPool = nullptr; + +static std::shared_ptr gLocalDownloader = nullptr; +static ccstd::unordered_map> gLocalDownloaderHandlers; +static uint64_t gLocalDownloaderTaskId = 1000000; + +static cc::network::Downloader *localDownloader() { + if (!gLocalDownloader) { + gLocalDownloader = std::make_shared(); + gLocalDownloader->onDataTaskSuccess = [=](const cc::network::DownloadTask &task, + const ccstd::vector &data) { + if (data.empty()) { + SE_REPORT_ERROR("Getting image from (%s) failed!", task.requestURL.c_str()); + return; + } + + auto callback = gLocalDownloaderHandlers.find(task.identifier); + if (callback == gLocalDownloaderHandlers.end()) { + SE_REPORT_ERROR("Getting image from (%s), callback not found!!", task.requestURL.c_str()); + return; + } + size_t imageBytes = data.size(); + auto *imageData = static_cast(malloc(imageBytes)); + memcpy(imageData, data.data(), imageBytes); + + (callback->second)("", imageData, static_cast(imageBytes)); + // initImageFunc("", imageData, imageBytes); + gLocalDownloaderHandlers.erase(callback); + }; + gLocalDownloader->onTaskError = [=](const cc::network::DownloadTask &task, + int errorCode, // NOLINT + int errorCodeInternal, // NOLINT + const ccstd::string &errorStr) { // NOLINT + SE_REPORT_ERROR("Getting image from (%s) failed!", task.requestURL.c_str()); + gLocalDownloaderHandlers.erase(task.identifier); + }; + } + return gLocalDownloader.get(); +} + +static void localDownloaderCreateTask(const ccstd::string &url, const std::function &callback) { +#if CC_PLATFORM != CC_PLATFORM_OPENHARMONY // TODO(qgh):May be removed later + std::stringstream ss; + ss << "jsb_loadimage_" << (gLocalDownloaderTaskId++); + ccstd::string key = ss.str(); + auto task = localDownloader()->createDataTask(url, key); + gLocalDownloaderHandlers.emplace(task->identifier, callback); +#endif +} + +bool jsb_set_extend_property(const char *ns, const char *clsName) { // NOLINT + se::Object *globalObj = se::ScriptEngine::getInstance()->getGlobalObject(); + se::Value nsVal; + if (globalObj->getProperty(ns, &nsVal) && nsVal.isObject()) { + se::Value ccVal; + if (globalObj->getProperty("cc", &ccVal) && ccVal.isObject()) { + se::Value ccClassVal; + if (ccVal.toObject()->getProperty("Class", &ccClassVal) && ccClassVal.isObject()) { + se::Value extendVal; + if (ccClassVal.toObject()->getProperty("extend", &extendVal) && extendVal.isObject() && extendVal.toObject()->isFunction()) { + se::Value targetClsVal; + if (nsVal.toObject()->getProperty(clsName, &targetClsVal) && targetClsVal.isObject()) { + return targetClsVal.toObject()->setProperty("extend", extendVal); + } + } + } + } + } + return false; +} + +namespace { + +ccstd::unordered_map gModuleCache; + +static bool require(se::State &s) { // NOLINT + const auto &args = s.args(); + int argc = static_cast(args.size()); + CC_ASSERT_GE(argc, 1); + CC_ASSERT(args[0].isString()); + + return jsb_run_script(args[0].toString(), &s.rval()); +} +SE_BIND_FUNC(require) + +static bool doModuleRequire(const ccstd::string &path, se::Value *ret, const ccstd::string &prevScriptFileDir) { // NOLINT + se::AutoHandleScope hs; + CC_ASSERT(!path.empty()); + + const auto &fileOperationDelegate = se::ScriptEngine::getInstance()->getFileOperationDelegate(); + CC_ASSERT(fileOperationDelegate.isValid()); + + ccstd::string fullPath; + + ccstd::string pathWithSuffix = path; + if (pathWithSuffix.rfind(".js") != (pathWithSuffix.length() - 3)) { + pathWithSuffix += ".js"; + } + ccstd::string scriptBuffer = fileOperationDelegate.onGetStringFromFile(pathWithSuffix); + + if (scriptBuffer.empty() && !prevScriptFileDir.empty()) { + ccstd::string secondPath = prevScriptFileDir; + if (secondPath[secondPath.length() - 1] != '/') { + secondPath += "/"; + } + + secondPath += path; + + if (FileUtils::getInstance()->isDirectoryExist(secondPath)) { + if (secondPath[secondPath.length() - 1] != '/') { + secondPath += "/"; + } + secondPath += "index.js"; + } else { + if (path.rfind(".js") != (path.length() - 3)) { + secondPath += ".js"; + } + } + + fullPath = fileOperationDelegate.onGetFullPath(secondPath); + scriptBuffer = fileOperationDelegate.onGetStringFromFile(fullPath); + } else { + fullPath = fileOperationDelegate.onGetFullPath(pathWithSuffix); + } + + if (!scriptBuffer.empty()) { + const auto &iter = gModuleCache.find(fullPath); + if (iter != gModuleCache.end()) { + *ret = iter->second; + // printf("Found cache: %s, value: %d\n", fullPath.c_str(), (int)ret->getType()); + return true; + } + ccstd::string currentScriptFileDir = FileUtils::getInstance()->getFileDir(fullPath); + + // Add closure for evalutate the script + char prefix[] = "(function(currentScriptDir){ window.module = window.module || {}; var exports = window.module.exports = {}; "; + char suffix[512] = {0}; + snprintf(suffix, sizeof(suffix), "\nwindow.module.exports = window.module.exports || exports;\n})('%s'); ", currentScriptFileDir.c_str()); + + // Add current script path to require function invocation + scriptBuffer = prefix + std::regex_replace(scriptBuffer, std::regex("([^A-Za-z0-9]|^)requireModule\\((.*?)\\)"), "$1requireModule($2, currentScriptDir)") + suffix; + + // FILE* fp = fopen("/Users/james/Downloads/test.txt", "wb"); + // fwrite(scriptBuffer.c_str(), scriptBuffer.length(), 1, fp); + // fclose(fp); + +#if CC_PLATFORM == CC_PLATFORM_MACOS || CC_PLATFORM == CC_PLATFORM_IOS + ccstd::string reletivePath = fullPath; + #if CC_PLATFORM == CC_PLATFORM_MACOS + const ccstd::string reletivePathKey = "/Contents/Resources"; + #else + const ccstd::string reletivePathKey = ".app"; + #endif + + size_t pos = reletivePath.find(reletivePathKey); + if (pos != ccstd::string::npos) { + reletivePath = reletivePath.substr(pos + reletivePathKey.length() + 1); + } +#else + const ccstd::string &reletivePath = fullPath; +#endif + + auto *se = se::ScriptEngine::getInstance(); + bool succeed = se->evalString(scriptBuffer.c_str(), static_cast(scriptBuffer.length()), nullptr, reletivePath.c_str()); + se::Value moduleVal; + if (succeed && se->getGlobalObject()->getProperty("module", &moduleVal) && moduleVal.isObject()) { + se::Value exportsVal; + if (moduleVal.toObject()->getProperty("exports", &exportsVal)) { + if (ret != nullptr) { + *ret = exportsVal; + } + gModuleCache[fullPath] = std::move(exportsVal); + } else { + gModuleCache[fullPath] = se::Value::Undefined; + } + // clear module.exports + moduleVal.toObject()->setProperty("exports", se::Value::Undefined); + } else { + gModuleCache[fullPath] = se::Value::Undefined; + } + CC_ASSERT(succeed); + return succeed; + } + + SE_LOGE("doModuleRequire %s, buffer is empty!\n", path.c_str()); + CC_ABORT(); + return false; +} + +static bool moduleRequire(se::State &s) { // NOLINT + const auto &args = s.args(); + int argc = static_cast(args.size()); + CC_ASSERT_GE(argc, 2); + CC_ASSERT(args[0].isString()); + CC_ASSERT(args[1].isString()); + + return doModuleRequire(args[0].toString(), &s.rval(), args[1].toString()); +} +SE_BIND_FUNC(moduleRequire) +} // namespace + +bool jsb_run_script(const ccstd::string &filePath, se::Value *rval /* = nullptr */) { // NOLINT + se::AutoHandleScope hs; + return se::ScriptEngine::getInstance()->runScript(filePath, rval); +} + +bool jsb_run_script_module(const ccstd::string &filePath, se::Value *rval /* = nullptr */) { // NOLINT + return doModuleRequire(filePath, rval, ""); +} + +static bool jsc_garbageCollect(se::State &s) { // NOLINT + se::ScriptEngine::getInstance()->garbageCollect(); + return true; +} +SE_BIND_FUNC(jsc_garbageCollect) + +static bool jsc_dumpNativePtrToSeObjectMap(se::State &s) { // NOLINT + CC_LOG_DEBUG(">>> total: %d, Dump (native -> jsobj) map begin", (int)se::NativePtrToObjectMap::size()); + + struct NamePtrStruct { + const char *name; + void *ptr; + }; + + ccstd::vector namePtrArray; + + for (const auto &e : se::NativePtrToObjectMap::instance()) { + se::Object *jsobj = e.second; + CC_ASSERT(jsobj->_getClass() != nullptr); + NamePtrStruct tmp; + tmp.name = jsobj->_getClass()->getName(); + tmp.ptr = e.first; + namePtrArray.push_back(tmp); + } + + std::sort(namePtrArray.begin(), namePtrArray.end(), [](const NamePtrStruct &a, const NamePtrStruct &b) -> bool { + ccstd::string left = a.name; + ccstd::string right = b.name; + for (ccstd::string::const_iterator lit = left.begin(), rit = right.begin(); lit != left.end() && rit != right.end(); ++lit, ++rit) { + if (::tolower(*lit) < ::tolower(*rit)) { + return true; + } + if (::tolower(*lit) > ::tolower(*rit)) { + return false; + } + } + return left.size() < right.size(); + }); + + for (const auto &e : namePtrArray) { + CC_LOG_DEBUG("%s: %p", e.name, e.ptr); + } + CC_LOG_DEBUG(">>> total: %d, Dump (native -> jsobj) map end", (int)se::NativePtrToObjectMap::size()); + return true; +} +SE_BIND_FUNC(jsc_dumpNativePtrToSeObjectMap) + +static bool jsc_dumpRoot(se::State &s) { // NOLINT + CC_ABORT(); + return true; +} +SE_BIND_FUNC(jsc_dumpRoot) + +static bool JSBCore_platform(se::State &s) { // NOLINT + // Application::Platform platform = CC_CURRENT_ENGINE()->getPlatform(); + cc::BasePlatform::OSType type = + cc::BasePlatform::getPlatform()->getOSType(); + s.rval().setInt32(static_cast(type)); + return true; +} +SE_BIND_FUNC(JSBCore_platform) + +static bool JSBCore_os(se::State &s) { // NOLINT + se::Value os; + + // osx, ios, android, windows, linux, etc.. +#if (CC_PLATFORM == CC_PLATFORM_IOS) + os.setString("iOS"); +#elif (CC_PLATFORM == CC_PLATFORM_ANDROID) + os.setString("Android"); +#elif (CC_PLATFORM == CC_PLATFORM_WINDOWS) + os.setString("Windows"); +#elif (CC_PLATFORM == CC_PLATFORM_LINUX) + os.setString("Linux"); +#elif (CC_PLATFORM == CC_PLATFORM_QNX) + os.setString("Qnx"); +#elif (CC_PLATFORM == CC_PLATFORM_MACOS) + os.setString("OS X"); +#elif (CC_PLATFORM == CC_PLATFORM_OHOS) + os.setString("OHOS"); +#elif (CC_PLATFORM == CC_PLATFORM_OPENHARMONY) + os.setString("OpenHarmony"); +#endif + + s.rval() = os; + return true; +} +SE_BIND_FUNC(JSBCore_os) + +static bool JSBCore_getCurrentLanguage(se::State &s) { // NOLINT + auto *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); + CC_ASSERT_NOT_NULL(systemIntf); + ccstd::string languageStr = systemIntf->getCurrentLanguageToString(); + s.rval().setString(languageStr); + return true; +} +SE_BIND_FUNC(JSBCore_getCurrentLanguage) + +static bool JSBCore_getCurrentLanguageCode(se::State &s) { // NOLINT + auto *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); + CC_ASSERT_NOT_NULL(systemIntf); + ccstd::string language = systemIntf->getCurrentLanguageCode(); + s.rval().setString(language); + return true; +} +SE_BIND_FUNC(JSBCore_getCurrentLanguageCode) + +static bool JSB_getOSVersion(se::State &s) { // NOLINT + auto *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); + CC_ASSERT_NOT_NULL(systemIntf); + ccstd::string systemVersion = systemIntf->getSystemVersion(); + s.rval().setString(systemVersion); + return true; +} +SE_BIND_FUNC(JSB_getOSVersion) + +static bool JSB_supportHPE(se::State &s) { // NOLINT +#if CC_PLATFORM == CC_PLATFORM_ANDROID + s.rval().setBoolean(getSupportHPE()); +#else + s.rval().setBoolean(false); +#endif + return true; +} +SE_BIND_FUNC(JSB_supportHPE) + +static bool JSB_core_restartVM(se::State &s) { // NOLINT + // REFINE: release AudioEngine, waiting HttpClient & WebSocket threads to exit. + CC_CURRENT_APPLICATION()->restart(); + return true; +} +SE_BIND_FUNC(JSB_core_restartVM) + +static bool JSB_closeWindow(se::State &s) { // NOLINT + CC_CURRENT_APPLICATION()->close(); + return true; +} +SE_BIND_FUNC(JSB_closeWindow) + +static bool JSB_exit(se::State &s) { // NOLINT + BasePlatform::getPlatform()->exit(); + return true; +} +SE_BIND_FUNC(JSB_exit); + +static bool JSB_isObjectValid(se::State &s) { // NOLINT + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc == 1) { + void *nativePtr = nullptr; + seval_to_native_ptr(args[0], &nativePtr); + s.rval().setBoolean(nativePtr != nullptr); + return true; + } + + SE_REPORT_ERROR("Invalid number of arguments: %d. Expecting: 1", argc); + return false; +} +SE_BIND_FUNC(JSB_isObjectValid) + +static bool JSB_setCursorEnabled(se::State &s) { // NOLINT + const auto &args = s.args(); + int argc = static_cast(args.size()); + SE_PRECONDITION2(argc == 1, false, "Invalid number of arguments"); + bool ok = true; + bool value = true; + ok &= sevalue_to_native(args[0], &value); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + auto *systemWindowIntf = CC_GET_SYSTEM_WINDOW(ISystemWindow::mainWindowId); + CC_ASSERT_NOT_NULL(systemWindowIntf); + systemWindowIntf->setCursorEnabled(value); + return true; +} +SE_BIND_FUNC(JSB_setCursorEnabled) + +static bool JSB_saveByteCode(se::State &s) { // NOLINT + const auto &args = s.args(); + int argc = static_cast(args.size()); + SE_PRECONDITION2(argc == 2, false, "Invalid number of arguments"); + bool ok = true; + ccstd::string srcfile; + ccstd::string dstfile; + ok &= sevalue_to_native(args[0], &srcfile); + ok &= sevalue_to_native(args[1], &dstfile); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + ok = se::ScriptEngine::getInstance()->saveByteCodeToFile(srcfile, dstfile); + s.rval().setBoolean(ok); + return true; +} +SE_BIND_FUNC(JSB_saveByteCode) + +static bool getOrCreatePlainObject_r(const char *name, se::Object *parent, se::Object **outObj) { // NOLINT + CC_ASSERT_NOT_NULL(parent); + CC_ASSERT_NOT_NULL(outObj); + se::Value tmp; + + if (parent->getProperty(name, &tmp) && tmp.isObject()) { + *outObj = tmp.toObject(); + (*outObj)->incRef(); + } else { + *outObj = se::Object::createPlainObject(); + parent->setProperty(name, se::Value(*outObj)); + } + + return true; +} + +static bool js_performance_now(se::State &s) { // NOLINT + auto now = std::chrono::steady_clock::now(); + auto micro = std::chrono::duration_cast(now - se::ScriptEngine::getInstance()->getStartTime()).count(); + s.rval().setDouble(static_cast(micro) * 0.001); + return true; +} +SE_BIND_FUNC(js_performance_now) + +namespace { +struct ImageInfo { + uint32_t length = 0; + uint32_t width = 0; + uint32_t height = 0; + uint8_t *data = nullptr; + cc::gfx::Format format = cc::gfx::Format::UNKNOWN; + bool hasAlpha = false; + bool compressed = false; + ccstd::vector mipmapLevelDataSize; +}; + +uint8_t *convertRGB2RGBA(uint32_t length, uint8_t *src) { + auto *dst = reinterpret_cast(malloc(length)); + for (uint32_t i = 0; i < length; i += 4) { + dst[i] = *src++; + dst[i + 1] = *src++; + dst[i + 2] = *src++; + dst[i + 3] = 255; + } + return dst; +} + +uint8_t *convertIA2RGBA(uint32_t length, uint8_t *src) { + auto *dst = reinterpret_cast(malloc(length)); + for (uint32_t i = 0; i < length; i += 4) { + dst[i] = *src; + dst[i + 1] = *src; + dst[i + 2] = *src++; + dst[i + 3] = *src++; + } + return dst; +} + +uint8_t *convertI2RGBA(uint32_t length, uint8_t *src) { + auto *dst = reinterpret_cast(malloc(length)); + for (uint32_t i = 0; i < length; i += 4) { + dst[i] = *src; + dst[i + 1] = *src; + dst[i + 2] = *src++; + dst[i + 3] = 255; + } + return dst; +} + +struct ImageInfo *createImageInfo(Image *img) { + auto *imgInfo = ccnew struct ImageInfo(); + imgInfo->length = static_cast(img->getDataLen()); + imgInfo->width = img->getWidth(); + imgInfo->height = img->getHeight(); + img->takeData(&imgInfo->data); + imgInfo->format = img->getRenderFormat(); + imgInfo->compressed = img->isCompressed(); + imgInfo->mipmapLevelDataSize = img->getMipmapLevelDataSize(); + + // Convert to RGBA888 because standard web api will return only RGBA888. + // If not, then it may have issue in glTexSubImage. For example, engine + // will create a big texture, and update its content with small pictures. + // The big texture is RGBA888, then the small picture should be the same + // format, or it will cause 0x502 error on OpenGL ES 2. + if (!imgInfo->compressed && imgInfo->format != cc::gfx::Format::RGBA8) { + imgInfo->length = img->getWidth() * img->getHeight() * 4; + uint8_t *dst = nullptr; + uint32_t length = imgInfo->length; + uint8_t *src = imgInfo->data; + switch (imgInfo->format) { + case cc::gfx::Format::A8: + case cc::gfx::Format::LA8: + dst = convertIA2RGBA(length, src); + break; + case cc::gfx::Format::L8: + case cc::gfx::Format::R8: + case cc::gfx::Format::R8I: + dst = convertI2RGBA(length, src); + break; + case cc::gfx::Format::RGB8: + dst = convertRGB2RGBA(length, src); + break; + default: + SE_LOGE("unknown image format"); + break; + } + + if (dst != imgInfo->data) free(imgInfo->data); + imgInfo->data = dst; + imgInfo->hasAlpha = true; + } + + return imgInfo; +} +} // namespace + +bool jsb_global_load_image(const ccstd::string &path, const se::Value &callbackVal) { // NOLINT(readability-identifier-naming) + if (path.empty()) { + se::ValueArray seArgs; + callbackVal.toObject()->call(seArgs, nullptr); + return true; + } + + std::shared_ptr callbackPtr = std::make_shared(callbackVal); + + auto initImageFunc = [path, callbackPtr](const ccstd::string &fullPath, unsigned char *imageData, int imageBytes) { + auto *img = ccnew Image(); + + gThreadPool->pushTask([=](int /*tid*/) { + // NOTE: FileUtils::getInstance()->fullPathForFilename isn't a threadsafe method, + // Image::initWithImageFile will call fullPathForFilename internally which may + // cause thread race issues. Therefore, we get the full path of file before + // going into task callback. + // Be careful of invoking any Cocos2d-x interface in a sub-thread. + bool loadSucceed = false; + if (fullPath.empty()) { + loadSucceed = img->initWithImageData(imageData, imageBytes); + free(imageData); + } else { + loadSucceed = img->initWithImageFile(fullPath); + } + + ImageInfo *imgInfo = nullptr; + if (loadSucceed) { + imgInfo = createImageInfo(img); + } + auto app = CC_CURRENT_APPLICATION(); + if (!app) { + delete imgInfo; + delete img; + return; + } + auto engine = app->getEngine(); + CC_ASSERT_NOT_NULL(engine); + engine->getScheduler()->performFunctionInCocosThread([=]() { + se::AutoHandleScope hs; + se::ValueArray seArgs; + + if (loadSucceed) { + se::HandleObject retObj(se::Object::createPlainObject()); + auto *obj = se::Object::createObjectWithClass(__jsb_cc_JSBNativeDataHolder_class); + auto *nativeObj = JSB_MAKE_PRIVATE_OBJECT(cc::JSBNativeDataHolder, imgInfo->data); + obj->setPrivateObject(nativeObj); + retObj->setProperty("data", se::Value(obj)); + retObj->setProperty("width", se::Value(imgInfo->width)); + retObj->setProperty("height", se::Value(imgInfo->height)); + + se::Value mipmapLevelDataSizeArr; + nativevalue_to_se(imgInfo->mipmapLevelDataSize, mipmapLevelDataSizeArr, nullptr); + retObj->setProperty("mipmapLevelDataSize", mipmapLevelDataSizeArr); + + seArgs.push_back(se::Value(retObj)); + + delete imgInfo; + } else { + SE_REPORT_ERROR("initWithImageFile: %s failed!", path.c_str()); + } + callbackPtr->toObject()->call(seArgs, nullptr); + delete img; + }); + }); + }; + size_t pos = ccstd::string::npos; + if (path.find("http://") == 0 || path.find("https://") == 0) { + localDownloaderCreateTask(path, initImageFunc); + + } else if (path.find("data:") == 0 && (pos = path.find("base64,")) != ccstd::string::npos) { + int imageBytes = 0; + unsigned char *imageData = nullptr; + size_t dataStartPos = pos + strlen("base64,"); + const char *base64Data = path.data() + dataStartPos; + size_t dataLen = path.length() - dataStartPos; + imageBytes = base64Decode(reinterpret_cast(base64Data), static_cast(dataLen), &imageData); + if (imageBytes <= 0 || imageData == nullptr) { + SE_REPORT_ERROR("Decode base64 image data failed!"); + return false; + } + initImageFunc("", imageData, imageBytes); + } else { + ccstd::string fullPath(FileUtils::getInstance()->fullPathForFilename(path)); + if (0 == path.find("file://")) { + fullPath = FileUtils::getInstance()->fullPathForFilename(path.substr(strlen("file://"))); + } + + if (fullPath.empty()) { + SE_REPORT_ERROR("File (%s) doesn't exist!", path.c_str()); + return false; + } + initImageFunc(fullPath, nullptr, 0); + } + return true; +} + +static bool js_loadImage(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 2) { + ccstd::string path; + ok &= sevalue_to_native(args[0], &path); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + const auto &callbackVal = args[1]; + CC_ASSERT(callbackVal.isObject()); + CC_ASSERT(callbackVal.toObject()->isFunction()); + + return jsb_global_load_image(path, callbackVal); + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2); + return false; +} +SE_BIND_FUNC(js_loadImage) +// pixels(RGBA), width, height, fullFilePath(*.png/*.jpg) +static bool js_saveImageData(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + bool ok = true; // NOLINT(readability-identifier-length) + if (argc == 4 || argc == 5) { + auto *uint8ArrayObj = args[0].toObject(); + uint8_t *uint8ArrayData{nullptr}; + size_t length = 0; + uint8ArrayObj->root(); + uint8ArrayObj->incRef(); + uint8ArrayObj->getTypedArrayData(&uint8ArrayData, &length); + int32_t width; + int32_t height; + ok &= sevalue_to_native(args[1], &width); + ok &= sevalue_to_native(args[2], &height); + + std::string filePath; + ok &= sevalue_to_native(args[3], &filePath); + SE_PRECONDITION2(ok, false, "js_saveImageData : Error processing arguments"); + + se::Value callbackVal = argc == 5 ? args[4] : se::Value::Null; + se::Object *callbackObj{nullptr}; + if (!callbackVal.isNull()) { + CC_ASSERT(callbackVal.isObject()); + CC_ASSERT(callbackVal.toObject()->isFunction()); + callbackObj = callbackVal.toObject(); + callbackObj->root(); + callbackObj->incRef(); + } + + gThreadPool->pushTask([=](int /*tid*/) { + // isToRGB = false, to keep alpha channel + auto *img = ccnew Image(); + // A conversion from size_t to uint32_t might lose integer precision + img->initWithRawData(uint8ArrayData, static_cast(length), width, height, 32 /*Unused*/); + bool isSuccess = img->saveToFile(filePath, false /*isToRGB*/); + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() { + se::AutoHandleScope hs; + se::ValueArray seArgs; + + se::Value isSuccessVal; + nativevalue_to_se(isSuccess, isSuccessVal); + + seArgs.push_back(isSuccessVal); + if (callbackObj) { + callbackObj->call(seArgs, nullptr); + callbackObj->unroot(); + callbackObj->decRef(); + } + uint8ArrayObj->unroot(); + uint8ArrayObj->decRef(); + delete img; + }); + }); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d or %d", (int)argc, 4, 5); + return false; +} +SE_BIND_FUNC(js_saveImageData) +static bool js_destroyImage(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + cc::JSBNativeDataHolder *dataHolder = nullptr; + ok &= sevalue_to_native(args[0], &dataHolder); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + dataHolder->destroy(); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_destroyImage) + +static bool JSB_openURL(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc > 0) { + ccstd::string url; + ok = sevalue_to_native(args[0], &url); + SE_PRECONDITION2(ok, false, "url is invalid!"); + auto *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); + CC_ASSERT_NOT_NULL(systemIntf); + systemIntf->openURL(url); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_openURL) + +static bool JSB_copyTextToClipboard(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc > 0) { + ccstd::string text; + ok = sevalue_to_native(args[0], &text); + SE_PRECONDITION2(ok, false, "text is invalid!"); + auto *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); + CC_ASSERT_NOT_NULL(systemIntf); + systemIntf->copyTextToClipboard(text); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_copyTextToClipboard) + +static bool JSB_setPreferredFramesPerSecond(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc > 0) { + int32_t fps; + ok = sevalue_to_native(args[0], &fps); + SE_PRECONDITION2(ok, false, "fps is invalid!"); + // cc::log("EMPTY IMPLEMENTATION OF jsb.setPreferredFramesPerSecond"); + CC_CURRENT_ENGINE()->setPreferredFramesPerSecond(fps); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_setPreferredFramesPerSecond) + +#if CC_USE_EDITBOX +static bool JSB_showInputBox(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + bool ok; + se::Value tmp; + const auto &obj = args[0].toObject(); + + cc::EditBox::ShowInfo showInfo; + + ok = obj->getProperty("defaultValue", &tmp); + SE_PRECONDITION2(ok && tmp.isString(), false, "defaultValue is invalid!"); + showInfo.defaultValue = tmp.toString(); + + ok = obj->getProperty("maxLength", &tmp); + SE_PRECONDITION2(ok && tmp.isNumber(), false, "maxLength is invalid!"); + showInfo.maxLength = tmp.toInt32(); + + ok = obj->getProperty("multiple", &tmp); + SE_PRECONDITION2(ok && tmp.isBoolean(), false, "multiple is invalid!"); + showInfo.isMultiline = tmp.toBoolean(); + + if (obj->getProperty("confirmHold", &tmp)) { + SE_PRECONDITION2(tmp.isBoolean(), false, "confirmHold is invalid!"); + if (!tmp.isUndefined()) { + showInfo.confirmHold = tmp.toBoolean(); + } + } + + if (obj->getProperty("confirmType", &tmp)) { + SE_PRECONDITION2(tmp.isString(), false, "confirmType is invalid!"); + if (!tmp.isUndefined()) { + showInfo.confirmType = tmp.toString(); + } + } + + if (obj->getProperty("inputType", &tmp)) { + SE_PRECONDITION2(tmp.isString(), false, "inputType is invalid!"); + if (!tmp.isUndefined()) { + showInfo.inputType = tmp.toString(); + } + } + + if (obj->getProperty("originX", &tmp)) { + SE_PRECONDITION2(tmp.isNumber(), false, "originX is invalid!"); + if (!tmp.isUndefined()) { + showInfo.x = tmp.toInt32(); + } + } + + if (obj->getProperty("originY", &tmp)) { + SE_PRECONDITION2(tmp.isNumber(), false, "originY is invalid!"); + if (!tmp.isUndefined()) { + showInfo.y = tmp.toInt32(); + } + } + + if (obj->getProperty("width", &tmp)) { + SE_PRECONDITION2(tmp.isNumber(), false, "width is invalid!"); + if (!tmp.isUndefined()) { + showInfo.width = tmp.toInt32(); + } + } + + if (obj->getProperty("height", &tmp)) { + SE_PRECONDITION2(tmp.isNumber(), false, "height is invalid!"); + if (!tmp.isUndefined()) { + showInfo.height = tmp.toInt32(); + } + } + if (obj->getProperty("fontSize", &tmp)) { + SE_PRECONDITION2(tmp.isNumber(), false, "fontSize is invalid!"); + if (!tmp.isUndefined()) { + showInfo.fontSize = tmp.toUint32(); + } + } + if (obj->getProperty("fontColor", &tmp)) { + SE_PRECONDITION2(tmp.isNumber(), false, "fontColor is invalid!"); + if (!tmp.isUndefined()) { + showInfo.fontColor = tmp.toUint32(); + } + } + if (obj->getProperty("isBold", &tmp)) { + SE_PRECONDITION2(tmp.isBoolean(), false, "isBold is invalid!"); + if (!tmp.isUndefined()) { + showInfo.isBold = tmp.toBoolean(); + } + } + if (obj->getProperty("isItalic", &tmp)) { + SE_PRECONDITION2(tmp.isBoolean(), false, "isItalic is invalid!"); + if (!tmp.isUndefined()) { + showInfo.isItalic = tmp.toBoolean(); + } + } + if (obj->getProperty("isUnderline", &tmp)) { + SE_PRECONDITION2(tmp.isBoolean(), false, "isUnderline is invalid!"); + if (!tmp.isUndefined()) { + showInfo.isUnderline = tmp.toBoolean(); + } + } + if (obj->getProperty("underlineColor", &tmp)) { + SE_PRECONDITION2(tmp.isNumber(), false, "underlinrColor is invalid!"); + if (!tmp.isUndefined()) { + showInfo.underlineColor = tmp.toUint32(); + } + } + if (obj->getProperty("backColor", &tmp)) { + SE_PRECONDITION2(tmp.isNumber(), false, "backColor is invalid!"); + if (!tmp.isUndefined()) { + showInfo.backColor = tmp.toUint32(); + } + } + if (obj->getProperty("backgroundColor", &tmp)) { + SE_PRECONDITION2(tmp.isNumber(), false, "backgroundColor is invalid!"); + if (!tmp.isUndefined()) { + showInfo.backgroundColor = tmp.toUint32(); + } + } + if (obj->getProperty("textAlignment", &tmp)) { + SE_PRECONDITION2(tmp.isNumber(), false, "textAlignment is invalid!"); + if (!tmp.isUndefined()) { + showInfo.textAlignment = tmp.toUint32(); + } + } + EditBox::show(showInfo); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_showInputBox); + +static bool JSB_hideInputBox(se::State &s) { // NOLINT + EditBox::hide(); + return true; +} +SE_BIND_FUNC(JSB_hideInputBox) + +#endif + +static bool jsb_createExternalArrayBuffer(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + uint32_t byteLength{0}; + ok &= sevalue_to_native(args[0], &byteLength, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + if (byteLength > 0) { +// NOTE: Currently V8 use shared_ptr which has different abi on win64-debug and win64-release +#if CC_PLATFORM == CC_PLATFORM_WINDOWS && SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + se::HandleObject arrayBuffer{se::Object::createArrayBufferObject(nullptr, byteLength)}; +#else + void *buffer = malloc(byteLength); + memset(buffer, 0x00, byteLength); + se::HandleObject arrayBuffer{se::Object::createExternalArrayBufferObject(buffer, byteLength, [](void *contents, size_t /*byteLength*/, void * /*userData*/) { + if (contents != nullptr) { + free(contents); + } + })}; +#endif // CC_PLATFORM == CC_PLATFORM_WINDOWS + s.rval().setObject(arrayBuffer); + } else { + s.rval().setNull(); + } + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(jsb_createExternalArrayBuffer) + +static bool JSB_zipUtils_inflateMemory(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc > 0) { + unsigned char *arg0 = nullptr; + size_t arg1 = 0; + if (args[0].isString()) { + const ccstd::string &str = args[0].toString(); + arg0 = const_cast(reinterpret_cast(str.c_str())); + arg1 = str.size(); + } else if (args[0].isObject()) { + se::Object *obj = args[0].toObject(); + if (obj->isArrayBuffer()) { + ok &= obj->getArrayBufferData(&arg0, &arg1); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok &= obj->getTypedArrayData(&arg0, &arg1); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + ok = false; + } + } else { + ok = false; + } + SE_PRECONDITION2(ok, false, "args[0] is not in type of string | ArrayBuffer | TypedArray"); + unsigned char *arg2 = nullptr; + uint32_t len = 0; + if (argc == 1) { + len = ZipUtils::inflateMemory(arg0, static_cast(arg1), &arg2); + } else if (argc == 2) { + SE_PRECONDITION2(args[1].isNumber(), false, "outLengthHint is invalid!"); + uint32_t outLengthHint = 0; + if (!args[1].isUndefined()) { + outLengthHint = args[1].toUint32(); + len = ZipUtils::inflateMemoryWithHint(arg0, static_cast(arg1), &arg2, outLengthHint); + } else { + ok = false; + } + } else { + ok = false; + } + SE_PRECONDITION2(ok, false, "args number is not as expected!"); + se::HandleObject seObj(se::Object::createArrayBufferObject(arg2, len)); + if (!seObj.isEmpty() && len > 0) { + s.rval().setObject(seObj); + } else { + s.rval().setNull(); + } + free(arg2); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_zipUtils_inflateMemory) + +static bool JSB_zipUtils_inflateGZipFile(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + SE_PRECONDITION2(args[0].isString(), false, "path is invalid!"); + ccstd::string arg0 = args[0].toString(); + unsigned char *arg1 = nullptr; + int32_t len = ZipUtils::inflateGZipFile(arg0.c_str(), &arg1); + se::HandleObject seObj(se::Object::createArrayBufferObject(arg1, len)); + if (!seObj.isEmpty() && len > 0) { + s.rval().setObject(seObj); + } else { + s.rval().setNull(); + } + free(arg1); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_zipUtils_inflateGZipFile) + +static bool JSB_zipUtils_isGZipFile(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + SE_PRECONDITION2(args[0].isString(), false, "path is invalid!"); + ccstd::string arg0 = args[0].toString(); + bool flag = ZipUtils::isGZipFile(arg0.c_str()); + s.rval().setBoolean(flag); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_zipUtils_isGZipFile) + +static bool JSB_zipUtils_isGZipBuffer(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + unsigned char *arg0 = nullptr; + size_t arg1 = 0; + if (args[0].isString()) { + const ccstd::string &str = args[0].toString(); + arg0 = const_cast(reinterpret_cast(str.c_str())); + arg1 = str.size(); + } else if (args[0].isObject()) { + se::Object *obj = args[0].toObject(); + if (obj->isArrayBuffer()) { + ok &= obj->getArrayBufferData(&arg0, &arg1); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok &= obj->getTypedArrayData(&arg0, &arg1); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + ok = false; + } + } else { + ok = false; + } + SE_PRECONDITION2(ok, false, "args[0] is not in type of string | ArrayBuffer | TypedArray"); + bool flag = ZipUtils::isGZipBuffer(arg0, static_cast(arg1)); + s.rval().setBoolean(flag); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_zipUtils_isGZipBuffer) + +static bool JSB_zipUtils_inflateCCZFile(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + SE_PRECONDITION2(args[0].isString(), false, "path is invalid!"); + ccstd::string arg0 = args[0].toString(); + unsigned char *arg1 = nullptr; + int32_t len = ZipUtils::inflateCCZFile(arg0.c_str(), &arg1); + se::HandleObject seObj(se::Object::createArrayBufferObject(arg1, len)); + if (!seObj.isEmpty() && len > 0) { + s.rval().setObject(seObj); + } else { + s.rval().setNull(); + } + free(arg1); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_zipUtils_inflateCCZFile) + +static bool JSB_zipUtils_inflateCCZBuffer(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + unsigned char *arg0 = nullptr; + size_t arg1 = 0; + if (args[0].isString()) { + const ccstd::string &str = args[0].toString(); + arg0 = const_cast(reinterpret_cast(str.c_str())); + arg1 = str.size(); + } else if (args[0].isObject()) { + se::Object *obj = args[0].toObject(); + if (obj->isArrayBuffer()) { + ok &= obj->getArrayBufferData(&arg0, &arg1); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok &= obj->getTypedArrayData(&arg0, &arg1); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + ok = false; + } + } else { + ok = false; + } + SE_PRECONDITION2(ok, false, "args[0] is not in type of string | ArrayBuffer | TypedArray"); + unsigned char *arg2 = nullptr; + int32_t len = ZipUtils::inflateCCZBuffer(arg0, static_cast(arg1), &arg2); + se::HandleObject seObj(se::Object::createArrayBufferObject(arg2, len)); + if (!seObj.isEmpty() && len > 0) { + s.rval().setObject(seObj); + } else { + s.rval().setNull(); + } + free(arg2); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_zipUtils_inflateCCZBuffer) + +static bool JSB_zipUtils_isCCZFile(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 1) { + ccstd::string arg0; + SE_PRECONDITION2(args[0].isString(), false, "path is invalid!"); + arg0 = args[0].toString(); + bool flag = ZipUtils::isCCZFile(arg0.c_str()); + s.rval().setBoolean(flag); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_zipUtils_isCCZFile) + +static bool JSB_zipUtils_isCCZBuffer(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + unsigned char *arg0 = nullptr; + size_t arg1 = 0; + if (args[0].isString()) { + const ccstd::string &str = args[0].toString(); + arg0 = const_cast(reinterpret_cast(str.c_str())); + arg1 = str.size(); + } else if (args[0].isObject()) { + se::Object *obj = args[0].toObject(); + if (obj->isArrayBuffer()) { + ok &= obj->getArrayBufferData(&arg0, &arg1); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (obj->isTypedArray()) { + ok &= obj->getTypedArrayData(&arg0, &arg1); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + ok = false; + } + } else { + ok = false; + } + SE_PRECONDITION2(ok, false, "args[0] is not in type of string | ArrayBuffer | TypedArray"); + bool flag = ZipUtils::isCCZBuffer(arg0, static_cast(arg1)); + s.rval().setBoolean(flag); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_zipUtils_isCCZBuffer) + +static bool JSB_zipUtils_setPvrEncryptionKeyPart(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 2) { + SE_PRECONDITION2(args[0].isNumber() && args[1].isNumber(), false, "args is not as expected"); + int32_t arg0 = args[0].toInt32(); + uint32_t arg1 = args[1].toUint32(); + ZipUtils::setPvrEncryptionKeyPart(arg0, arg1); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2); + return false; +} +SE_BIND_FUNC(JSB_zipUtils_setPvrEncryptionKeyPart) + +static bool JSB_zipUtils_setPvrEncryptionKey(se::State &s) { // NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 4) { + SE_PRECONDITION2(args[0].isNumber() && args[1].isNumber(), false, "args is not as expected"); + uint32_t arg0 = args[0].toUint32(); + uint32_t arg1 = args[1].toUint32(); + uint32_t arg2 = args[2].toUint32(); + uint32_t arg3 = args[3].toUint32(); + ZipUtils::setPvrEncryptionKey(arg0, arg1, arg2, arg3); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 4); + return false; +} +SE_BIND_FUNC(JSB_zipUtils_setPvrEncryptionKey) + +// TextEncoder +static se::Class *__jsb_TextEncoder_class = nullptr; // NOLINT + +static bool js_TextEncoder_finalize(se::State &) // NOLINT +{ + return true; +} +SE_BIND_FINALIZE_FUNC(js_TextEncoder_finalize) + +static bool js_TextEncoder_constructor(se::State &s) // NOLINT(readability-identifier-naming) +{ + const auto &args = s.args(); + size_t argc = args.size(); + if (argc > 0) { + if (args[0].isString() && args[0].toString() != "utf-8") { + CC_LOG_WARNING("TextEncoder only supports utf-8"); + } + } + s.thisObject()->setProperty("encoding", se::Value{"utf-8"}); + s.thisObject()->setPrivateObject(nullptr); + return true; +} +SE_BIND_CTOR(js_TextEncoder_constructor, __jsb_TextEncoder_class, js_TextEncoder_finalize) + +static bool js_TextEncoder_encode(se::State &s) // NOLINT(readability-identifier-naming) +{ + const auto &args = s.args(); + size_t argc = args.size(); + + if (argc == 1) { + const auto &arg0 = args[0]; + SE_PRECONDITION2(arg0.isString(), false, "js_TextEncoder_encode, arg0 is not a string"); + const auto &str = arg0.toString(); + se::HandleObject encodedUint8Array{ + se::Object::createTypedArray(se::Object::TypedArrayType::UINT8, str.data(), str.length())}; + + s.rval().setObject(encodedUint8Array); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_TextEncoder_encode) + +// TextDecoder +static se::Class *__jsb_TextDecoder_class = nullptr; // NOLINT + +static bool js_TextDecoder_finalize(se::State &) // NOLINT +{ + return true; +} +SE_BIND_FINALIZE_FUNC(js_TextDecoder_finalize) + +static bool js_TextDecoder_constructor(se::State &s) // NOLINT(readability-identifier-naming) +{ + const auto &args = s.args(); + size_t argc = args.size(); + if (argc > 0) { + if (args[0].isString() && args[0].toString() != "utf-8") { + CC_LOG_WARNING("TextDecoder only supports utf-8"); + } + } + s.thisObject()->setProperty("encoding", se::Value{"utf-8"}); + s.thisObject()->setProperty("fatal", se::Value{false}); + s.thisObject()->setProperty("ignoreBOM", se::Value{false}); + s.thisObject()->setPrivateObject(nullptr); // FIXME: Don't need this line if https://github.com/cocos/3d-tasks/issues/11365 is done. + return true; +} +SE_BIND_CTOR(js_TextDecoder_constructor, __jsb_TextDecoder_class, js_TextDecoder_finalize) + +static bool js_TextDecoder_decode(se::State &s) // NOLINT(readability-identifier-naming) +{ + const auto &args = s.args(); + size_t argc = args.size(); + + if (argc == 1) { + const auto &arg0 = args[0]; + SE_PRECONDITION2(arg0.isObject() && arg0.toObject()->isTypedArray(), false, "js_TextDecoder_decode, arg0 is not a Uint8Array"); + auto *uint8ArrayObj = arg0.toObject(); + uint8_t *uint8ArrayData = nullptr; + size_t length = 0; + bool ok = uint8ArrayObj->getTypedArrayData(&uint8ArrayData, &length); + SE_PRECONDITION2(ok, false, "js_TextDecoder_decode, get typedarray data failed!"); + + ccstd::string str{reinterpret_cast(uint8ArrayData), length}; + s.rval().setString(str); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_TextDecoder_decode) + +static bool jsb_register_TextEncoder(se::Object *globalObj) { // NOLINT + auto *cls = se::Class::create("TextEncoder", globalObj, nullptr, _SE(js_TextEncoder_constructor)); + cls->defineFunction("encode", _SE(js_TextEncoder_encode)); + cls->defineFinalizeFunction(_SE(js_TextEncoder_finalize)); + cls->install(); + + __jsb_TextEncoder_class = cls; + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +static bool jsb_register_TextDecoder(se::Object *globalObj) { // NOLINT + auto *cls = se::Class::create("TextDecoder", globalObj, nullptr, _SE(js_TextDecoder_constructor)); + cls->defineFunction("decode", _SE(js_TextDecoder_decode)); + cls->defineFinalizeFunction(_SE(js_TextDecoder_finalize)); + cls->install(); + + __jsb_TextDecoder_class = cls; + + se::ScriptEngine::getInstance()->clearException(); + return true; +} + +static bool JSB_process_get_argv(se::State &s) // NOLINT(readability-identifier-naming) +{ + const auto &args = CC_CURRENT_APPLICATION()->getArguments(); + nativevalue_to_se(args, s.rval()); + return true; +} +SE_BIND_PROP_GET(JSB_process_get_argv) + +#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY && SCRIPT_ENGINE_TYPE != SCRIPT_ENGINE_NAPI + +static bool sevalue_to_napivalue(const se::Value &seVal, Napi::Value *napiVal, Napi::Env env); + +static bool seobject_to_napivalue(se::Object *seObj, Napi::Value *napiVal, Napi::Env env) { + auto napiObj = Napi::Object::New(env); + ccstd::vector allKeys; + bool ok = seObj->getAllKeys(&allKeys); + if (ok && !allKeys.empty()) { + for (const auto &key : allKeys) { + Napi::Value napiProp; + se::Value prop; + ok = seObj->getProperty(key, &prop); + if (ok) { + ok = sevalue_to_napivalue(prop, &napiProp, env); + if (ok) { + napiObj.Set(key.c_str(), napiProp); + } + } + } + } + *napiVal = napiObj; + return true; +} + +static bool sevalue_to_napivalue(const se::Value &seVal, Napi::Value *napiVal, Napi::Env env) { + // Only supports number or {tag: number, url: string} now + if (seVal.isNumber()) { + *napiVal = Napi::Number::New(env, seVal.toDouble()); + } else if (seVal.isString()) { + *napiVal = Napi::String::New(env, seVal.toString().c_str()); + } else if (seVal.isBoolean()) { + *napiVal = Napi::Boolean::New(env, seVal.toBoolean()); + } else if (seVal.isObject()) { + seobject_to_napivalue(seVal.toObject(), napiVal, env); + } else { + CC_LOG_WARNING("sevalue_to_napivalue, Unsupported type: %d", static_cast(seVal.getType())); + return false; + } + + return true; +} + +static bool JSB_openharmony_postMessage(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + + if (argc == 2) { + bool ok = false; + ccstd::string msgType; + ok = sevalue_to_native(args[0], &msgType, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + const auto &arg1 = args[1]; + auto env = NapiHelper::getWorkerEnv(); + + Napi::Value napiArg1 = env.Undefined(); + + if (arg1.isNumber()) { + napiArg1 = Napi::Number::New(env, arg1.toDouble()); + } else if (arg1.isObject()) { + seobject_to_napivalue(arg1.toObject(), &napiArg1, env); + } else { + SE_REPORT_ERROR("postMessage, Unsupported type"); + return false; + } + + NapiHelper::postMessageToUIThread(msgType.c_str(), napiArg1); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2); + return false; +} +SE_BIND_FUNC(JSB_openharmony_postMessage) + +static bool JSB_empty_promise_then(se::State &s) { + return true; +} +SE_BIND_FUNC(JSB_empty_promise_then) + +static bool JSB_openharmony_postSyncMessage(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + + if (argc == 2) { + bool ok = false; + ccstd::string msgType; + ok = sevalue_to_native(args[0], &msgType, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + const auto &arg1 = args[1]; + auto env = NapiHelper::getWorkerEnv(); + + Napi::Value napiArg1 = env.Undefined(); + + if (arg1.isNumber()) { + napiArg1 = Napi::Number::New(env, arg1.toDouble()); + } else if (arg1.isObject()) { + seobject_to_napivalue(arg1.toObject(), &napiArg1, env); + } else { + SE_REPORT_ERROR("postMessage, Unsupported type"); + return false; + } + + Napi::Value napiPromise = NapiHelper::postSyncMessageToUIThread(msgType.c_str(), napiArg1); + + // TODO(cjh): Implement Promise for se + se::HandleObject retObj(se::Object::createPlainObject()); + retObj->defineFunction("then", _SE(JSB_empty_promise_then)); + s.rval().setObject(retObj); + // + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2); + return false; +} +SE_BIND_FUNC(JSB_openharmony_postSyncMessage) +#endif + +bool jsb_register_global_variables(se::Object *global) { // NOLINT + gThreadPool = LegacyThreadPool::newFixedThreadPool(3); + +#if CC_EDITOR + global->defineFunction("__require", _SE(require)); +#else + global->defineFunction("require", _SE(require)); +#endif + global->defineFunction("requireModule", _SE(moduleRequire)); + + getOrCreatePlainObject_r("jsb", global, &__jsbObj); + + __jsbObj->defineFunction("garbageCollect", _SE(jsc_garbageCollect)); + __jsbObj->defineFunction("dumpNativePtrToSeObjectMap", _SE(jsc_dumpNativePtrToSeObjectMap)); + + __jsbObj->defineFunction("loadImage", _SE(js_loadImage)); + __jsbObj->defineFunction("saveImageData", _SE(js_saveImageData)); + __jsbObj->defineFunction("openURL", _SE(JSB_openURL)); + __jsbObj->defineFunction("copyTextToClipboard", _SE(JSB_copyTextToClipboard)); + __jsbObj->defineFunction("setPreferredFramesPerSecond", _SE(JSB_setPreferredFramesPerSecond)); + __jsbObj->defineFunction("destroyImage", _SE(js_destroyImage)); +#if CC_USE_EDITBOX + __jsbObj->defineFunction("showInputBox", _SE(JSB_showInputBox)); + __jsbObj->defineFunction("hideInputBox", _SE(JSB_hideInputBox)); +#endif + __jsbObj->defineFunction("setCursorEnabled", _SE(JSB_setCursorEnabled)); + __jsbObj->defineFunction("saveByteCode", _SE(JSB_saveByteCode)); + __jsbObj->defineFunction("createExternalArrayBuffer", _SE(jsb_createExternalArrayBuffer)); + + // Create process object + se::HandleObject processObj{se::Object::createPlainObject()}; + processObj->defineProperty("argv", _SE(JSB_process_get_argv), nullptr); + __jsbObj->setProperty("process", se::Value(processObj)); + + se::HandleObject zipUtils(se::Object::createPlainObject()); + zipUtils->defineFunction("inflateMemory", _SE(JSB_zipUtils_inflateMemory)); + zipUtils->defineFunction("inflateGZipFile", _SE(JSB_zipUtils_inflateGZipFile)); + zipUtils->defineFunction("isGZipFile", _SE(JSB_zipUtils_isGZipFile)); + zipUtils->defineFunction("isGZipBuffer", _SE(JSB_zipUtils_isGZipBuffer)); + zipUtils->defineFunction("inflateCCZFile", _SE(JSB_zipUtils_inflateCCZFile)); + zipUtils->defineFunction("inflateCCZBuffer", _SE(JSB_zipUtils_inflateCCZBuffer)); + zipUtils->defineFunction("isCCZFile", _SE(JSB_zipUtils_isCCZFile)); + zipUtils->defineFunction("isCCZBuffer", _SE(JSB_zipUtils_isCCZBuffer)); + zipUtils->defineFunction("setPvrEncryptionKeyPart", _SE(JSB_zipUtils_setPvrEncryptionKeyPart)); + zipUtils->defineFunction("setPvrEncryptionKey", _SE(JSB_zipUtils_setPvrEncryptionKey)); + + __jsbObj->setProperty("zipUtils", se::Value(zipUtils)); + + global->defineFunction("__getPlatform", _SE(JSBCore_platform)); + global->defineFunction("__getOS", _SE(JSBCore_os)); + global->defineFunction("__getOSVersion", _SE(JSB_getOSVersion)); + global->defineFunction("__supportHPE", _SE(JSB_supportHPE)); + global->defineFunction("__getCurrentLanguage", _SE(JSBCore_getCurrentLanguage)); + global->defineFunction("__getCurrentLanguageCode", _SE(JSBCore_getCurrentLanguageCode)); + global->defineFunction("__restartVM", _SE(JSB_core_restartVM)); + global->defineFunction("__close", _SE(JSB_closeWindow)); + global->defineFunction("__isObjectValid", _SE(JSB_isObjectValid)); + global->defineFunction("__exit", _SE(JSB_exit)); + + se::HandleObject performanceObj(se::Object::createPlainObject()); + performanceObj->defineFunction("now", _SE(js_performance_now)); + global->setProperty("performance", se::Value(performanceObj)); + +#if CC_PLATFORM == CC_PLATFORM_OPENHARMONY + #if SCRIPT_ENGINE_TYPE != SCRIPT_ENGINE_NAPI + se::HandleObject ohObj(se::Object::createPlainObject()); + global->setProperty("oh", se::Value(ohObj)); + ohObj->defineFunction("postMessage", _SE(JSB_openharmony_postMessage)); + ohObj->defineFunction("postSyncMessage", _SE(JSB_openharmony_postSyncMessage)); + #endif +#endif + + jsb_register_TextEncoder(global); + jsb_register_TextDecoder(global); + + jsb_register_ADPF(__jsbObj); + + se::ScriptEngine::getInstance()->clearException(); + + se::ScriptEngine::getInstance()->addBeforeCleanupHook([]() { + delete gThreadPool; + gThreadPool = nullptr; + + DeferredReleasePool::clear(); + }); + + se::ScriptEngine::getInstance()->addAfterCleanupHook([]() { + DeferredReleasePool::clear(); + + gModuleCache.clear(); + + SAFE_DEC_REF(__jsbObj); + SAFE_DEC_REF(__glObj); + }); + + return true; +} diff --git a/cocos/bindings/manual/jsb_global.h b/cocos/bindings/manual/jsb_global.h new file mode 100644 index 0000000..ca16a2e --- /dev/null +++ b/cocos/bindings/manual/jsb_global.h @@ -0,0 +1,78 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "bindings/jswrapper/PrivateObject.h" +#include "jsb_global_init.h" + +template +T *jsb_override_new(Args &&...args) { // NOLINT(readability-identifier-naming) + // create object in the default way + return ccnew T(std::forward(args)...); +} + +template +void jsb_override_delete(T *arg) { // NOLINT(readability-identifier-naming) + // create object in gfx way + delete (arg); +} + +template +se::PrivateObjectBase *jsb_make_private_object(ARGS &&...args) { // NOLINT(readability-identifier-naming) + if constexpr (std::is_base_of::value) { + return se::ccintrusive_ptr_private_object(ccnew T(std::forward(args)...)); + } else { + return se::shared_ptr_private_object(std::make_shared(std::forward(args)...)); + } +} + +template +typename std::enable_if::value, se::TypedPrivateObject *>::type +jsb_make_private_object_with_instance(T *instance) { // NOLINT(readability-identifier-naming) + return se::ccintrusive_ptr_private_object(instance); +} + +template +typename std::enable_if::value, se::TypedPrivateObject *>::type +jsb_make_private_object_with_instance(T *instance) { // NOLINT(readability-identifier-naming) + return se::shared_ptr_private_object(std::shared_ptr(instance)); +} + +#define JSB_MAKE_PRIVATE_OBJECT(kls, ...) jsb_make_private_object(__VA_ARGS__) +#define JSB_MAKE_PRIVATE_OBJECT_WITH_INSTANCE(instance) jsb_make_private_object_with_instance(instance) +#define JSB_ALLOC(kls, ...) jsb_override_new(__VA_ARGS__) +#define JSB_FREE(kls) jsb_override_delete(kls) +namespace se { +class Class; +class Value; +} // namespace se + +bool jsb_register_global_variables(se::Object *global); // NOLINT(readability-identifier-naming) + +bool jsb_set_extend_property(const char *ns, const char *clsName); // NOLINT(readability-identifier-naming) +bool jsb_run_script(const ccstd::string &filePath, se::Value *rval = nullptr); // NOLINT(readability-identifier-naming) +bool jsb_run_script_module(const ccstd::string &filePath, se::Value *rval = nullptr); // NOLINT(readability-identifier-naming) + +bool jsb_global_load_image(const ccstd::string &path, const se::Value &callbackVal); // NOLINT(readability-identifier-naming) diff --git a/cocos/bindings/manual/jsb_global_init.cpp b/cocos/bindings/manual/jsb_global_init.cpp new file mode 100644 index 0000000..cd0a621 --- /dev/null +++ b/cocos/bindings/manual/jsb_global_init.cpp @@ -0,0 +1,244 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +// clang-format off +#include "base/Macros.h" +// clang-format: off +#include "base/std/container/string.h" +#include "uv.h" +// clang-format on + +#include "jsb_global_init.h" +#include +#include + +#include "base/Scheduler.h" +#include "base/ZipUtils.h" +#include "base/base64.h" +#include "base/memory/Memory.h" +#include "jsb_conversions.h" +#include "xxtea/xxtea.h" + +#include +#include +#include + +using namespace cc; //NOLINT + +se::Object *__jsbObj = nullptr; //NOLINT +se::Object *__glObj = nullptr; //NOLINT + +static std::basic_string xxteaKey; + +void jsb_set_xxtea_key(const ccstd::string &key) { //NOLINT + xxteaKey.assign(key.begin(), key.end()); +} + +static const char *BYTE_CODE_FILE_EXT = ".jsc"; //NOLINT + +static ccstd::string removeFileExt(const ccstd::string &filePath) { + size_t pos = filePath.rfind('.'); + if (0 < pos) { + return filePath.substr(0, pos); + } + return filePath; +} + +static int selectPort(int port) { + struct sockaddr_in addr; + static uv_tcp_t server; + uv_loop_t loop; + uv_loop_init(&loop); + int tryTimes = 200; + int startPort = port; +#if CC_PLATFORM == CC_PLATFORM_ANDROID + constexpr int localPortMin = 37000; // query from /proc/sys/net/ipv4/ip_local_port_range + if (startPort < localPortMin) { + uv_interface_address_t *info = nullptr; + int count = 0; + + uv_interface_addresses(&info, &count); + if (count == 0) { + SE_LOGE("Failed to accquire interfaces, error: %s\n Re-select port after 37000", strerror(errno)); + startPort = localPortMin + port; + } + if (info) { + uv_free_interface_addresses(info, count); + } + } +#endif + while (tryTimes-- > 0) { + uv_tcp_init(&loop, &server); + uv_ip4_addr("0.0.0.0", startPort, &addr); + uv_tcp_bind(&server, reinterpret_cast(&addr), 0); + int r = uv_listen(reinterpret_cast(&server), 5, nullptr); + uv_close(reinterpret_cast(&server), nullptr); + if (r) { + SE_LOGD("Failed to listen port %d, error: %s. Try next port\n", startPort, uv_strerror(r)); + startPort += 1; + } else { + break; + } + } + uv_loop_close(&loop); + return startPort; +} + +void jsb_init_file_operation_delegate() { //NOLINT + + static se::ScriptEngine::FileOperationDelegate delegate; + if (!delegate.isValid()) { + delegate.onGetDataFromFile = [](const ccstd::string &path, const std::function &readCallback) -> void { + CC_ASSERT(!path.empty()); + + Data fileData; + + ccstd::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT; + if (FileUtils::getInstance()->isFileExist(byteCodePath)) { + fileData = FileUtils::getInstance()->getDataFromFile(byteCodePath); + + uint32_t dataLen = 0; + uint8_t *data = xxtea_decrypt(fileData.getBytes(), static_cast(fileData.getSize()), + const_cast(xxteaKey.data()), + static_cast(xxteaKey.size()), reinterpret_cast(&dataLen)); + + if (data == nullptr) { + SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str()); + return; + } + + if (ZipUtils::isGZipBuffer(data, dataLen)) { + uint8_t *unpackedData; + uint32_t unpackedLen = ZipUtils::inflateMemory(data, dataLen, &unpackedData); + + if (unpackedData == nullptr) { + SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str()); + return; + } + + readCallback(unpackedData, unpackedLen); + free(data); + free(unpackedData); + } else { + readCallback(data, dataLen); + free(data); + } + + return; + } + + fileData = FileUtils::getInstance()->getDataFromFile(path); + readCallback(fileData.getBytes(), fileData.getSize()); + }; + + delegate.onGetStringFromFile = [](const ccstd::string &path) -> ccstd::string { + CC_ASSERT(!path.empty()); + + ccstd::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT; + if (FileUtils::getInstance()->isFileExist(byteCodePath)) { + Data fileData = FileUtils::getInstance()->getDataFromFile(byteCodePath); + + uint32_t dataLen; + uint8_t *data = xxtea_decrypt(static_cast(fileData.getBytes()), static_cast(fileData.getSize()), + const_cast(xxteaKey.data()), + static_cast(xxteaKey.size()), &dataLen); + + if (data == nullptr) { + SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str()); + return ""; + } + + if (ZipUtils::isGZipBuffer(data, dataLen)) { + uint8_t *unpackedData; + uint32_t unpackedLen = ZipUtils::inflateMemory(data, dataLen, &unpackedData); + if (unpackedData == nullptr) { + SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str()); + return ""; + } + + ccstd::string ret(reinterpret_cast(unpackedData), unpackedLen); + free(unpackedData); + free(data); + + return ret; + } + ccstd::string ret(reinterpret_cast(data), dataLen); + free(data); + return ret; + } + + if (FileUtils::getInstance()->isFileExist(path)) { + return FileUtils::getInstance()->getStringFromFile(path); + } + SE_LOGE("ScriptEngine::onGetStringFromFile %s not found, possible missing file.\n", path.c_str()); + return ""; + }; + + delegate.onGetFullPath = [](const ccstd::string &path) -> ccstd::string { + CC_ASSERT(!path.empty()); + ccstd::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT; + if (FileUtils::getInstance()->isFileExist(byteCodePath)) { + return FileUtils::getInstance()->fullPathForFilename(byteCodePath); + } + return FileUtils::getInstance()->fullPathForFilename(path); + }; + + delegate.onCheckFileExist = [](const ccstd::string &path) -> bool { + CC_ASSERT(!path.empty()); + return FileUtils::getInstance()->isFileExist(path); + }; + + CC_ASSERT(delegate.isValid()); + + se::ScriptEngine::getInstance()->setFileOperationDelegate(delegate); + } else { + // Games may be restarted in the same process and run in different threads. Android may restart from recent task list. + se::ScriptEngine::getInstance()->setFileOperationDelegate(delegate); + } +} + +bool jsb_enable_debugger(const ccstd::string &debuggerServerAddr, uint32_t port, bool isWaitForConnect) { //NOLINT +#if SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8 + if (debuggerServerAddr.empty() || port == 0) { + return false; + } + + port = static_cast(selectPort(static_cast(port))); + + auto *se = se::ScriptEngine::getInstance(); + if (se != nullptr) { + se->enableDebugger(debuggerServerAddr, port, isWaitForConnect); + } else { + // NOTE: jsb_enable_debugger may be invoked before se::ScriptEngine is initialized, + // So cache the debugger information in global and use it in se::ScriptEngine::start. + // This strategy keeps the compatibility of se::ScriptEngine::enableDebugger. + se::ScriptEngine::DebuggerInfo debuggerInfo; + debuggerInfo.serverAddr = debuggerServerAddr; + debuggerInfo.port = port; + debuggerInfo.isWait = isWaitForConnect; + se::ScriptEngine::_setDebuggerInfo(debuggerInfo); + } +#endif + return true; +} diff --git a/cocos/bindings/manual/jsb_global_init.h b/cocos/bindings/manual/jsb_global_init.h new file mode 100644 index 0000000..a6fcff2 --- /dev/null +++ b/cocos/bindings/manual/jsb_global_init.h @@ -0,0 +1,39 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/std/container/string.h" + +namespace se { +class Object; +} // namespace se + +extern se::Object *__jsbObj; //NOLINT +extern se::Object *__glObj; //NOLINT + +void jsb_init_file_operation_delegate(); //NOLINT +void jsb_set_xxtea_key(const ccstd::string &key); //NOLINT +bool jsb_enable_debugger(const ccstd::string &debuggerServerAddr, uint32_t port, bool isWaitForConnect = false); //NOLINT \ No newline at end of file diff --git a/cocos/bindings/manual/jsb_helper.cpp b/cocos/bindings/manual/jsb_helper.cpp new file mode 100644 index 0000000..7221dd1 --- /dev/null +++ b/cocos/bindings/manual/jsb_helper.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_helper.h" +#include "base/DeferredReleasePool.h" +#include "base/memory/Memory.h" + +/* static */ +void CleanupTask::pushTaskToAutoReleasePool(const std::function &cb) { + auto *ret = ccnew CleanupTask; + ret->_cb = cb; + cc::DeferredReleasePool::add(ret); +} + +CleanupTask::CleanupTask() = default; + +CleanupTask::~CleanupTask() { + if (_cb != nullptr) { + _cb(); + } +} diff --git a/cocos/bindings/manual/jsb_helper.h b/cocos/bindings/manual/jsb_helper.h new file mode 100644 index 0000000..2a24f1f --- /dev/null +++ b/cocos/bindings/manual/jsb_helper.h @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/RefCounted.h" + +#include + +class CleanupTask : public cc::RefCounted { +public: + static void pushTaskToAutoReleasePool(const std::function &cb); + + CleanupTask(); + ~CleanupTask() override; + +private: + std::function _cb{nullptr}; +}; diff --git a/cocos/bindings/manual/jsb_module_register.cpp b/cocos/bindings/manual/jsb_module_register.cpp new file mode 100644 index 0000000..6ca0b06 --- /dev/null +++ b/cocos/bindings/manual/jsb_module_register.cpp @@ -0,0 +1,233 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "cocos/bindings/manual/jsb_module_register.h" +#include "cocos/base/DeferredReleasePool.h" +#include "cocos/bindings/auto/jsb_2d_auto.h" +#include "cocos/bindings/auto/jsb_assets_auto.h" +#include "cocos/bindings/auto/jsb_cocos_auto.h" +#include "cocos/bindings/auto/jsb_extension_auto.h" +#include "cocos/bindings/auto/jsb_geometry_auto.h" +#include "cocos/bindings/auto/jsb_gfx_auto.h" +#include "cocos/bindings/auto/jsb_gi_auto.h" +#include "cocos/bindings/auto/jsb_network_auto.h" +#include "cocos/bindings/auto/jsb_pipeline_auto.h" +#include "cocos/bindings/auto/jsb_render_auto.h" +#include "cocos/bindings/auto/jsb_scene_auto.h" +#include "cocos/bindings/dop/jsb_dop.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_assets_manual.h" +#include "cocos/bindings/manual/jsb_cocos_manual.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_geometry_manual.h" +#include "cocos/bindings/manual/jsb_gfx_manual.h" +#include "cocos/bindings/manual/jsb_global.h" +#include "cocos/bindings/manual/jsb_network_manual.h" +#include "cocos/bindings/manual/jsb_pipeline_manual.h" +#include "cocos/bindings/manual/jsb_platform.h" +#include "cocos/bindings/manual/jsb_scene_manual.h" +#include "cocos/bindings/manual/jsb_xmlhttprequest.h" + +#if USE_GFX_RENDERER +#endif + +#if CC_USE_SOCKET + #include "cocos/bindings/manual/jsb_socketio.h" + #include "cocos/bindings/manual/jsb_websocket.h" +#endif // CC_USE_SOCKET + +#if CC_USE_AUDIO + #include "cocos/bindings/auto/jsb_audio_auto.h" + #include "cocos/bindings/manual/jsb_audio_manual.h" +#endif + +#if CC_USE_XR + #include "cocos/bindings/auto/jsb_xr_auto.h" + #include "cocos/bindings/auto/jsb_xr_extension_auto.h" +#endif + +#if CC_USE_AR_MODULE + #include "cocos/bindings/auto/jsb_ar_auto.h" + #include "cocos/bindings/manual/jsb_ar_manual.h" +#endif + +#if (CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS) + #include "cocos/bindings/manual/JavaScriptObjCBridge.h" +#endif + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS) + #include "cocos/bindings/manual/JavaScriptJavaBridge.h" +#endif + +#if(CC_PLATFORM == CC_PLATFORM_OPENHARMONY) + #if CC_USE_WEBVIEW + #include "cocos/bindings/auto/jsb_webview_auto.h" + #endif +#endif + +#if (CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS) + + #if CC_USE_VIDEO + #include "cocos/bindings/auto/jsb_video_auto.h" + #endif + + #if CC_USE_WEBVIEW + #include "cocos/bindings/auto/jsb_webview_auto.h" + #endif + +#endif // (CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_ANDROID) + +#if CC_USE_SOCKET && CC_USE_WEBSOCKET_SERVER + #include "cocos/bindings/manual/jsb_websocket_server.h" +#endif + +#if CC_USE_MIDDLEWARE + #include "cocos/bindings/auto/jsb_editor_support_auto.h" + + #if CC_USE_SPINE + #include "cocos/bindings/auto/jsb_spine_auto.h" + #include "cocos/bindings/manual/jsb_spine_manual.h" + #endif + + #if CC_USE_DRAGONBONES + #include "cocos/bindings/auto/jsb_dragonbones_auto.h" + #include "cocos/bindings/manual/jsb_dragonbones_manual.h" + #endif + +#endif // CC_USE_MIDDLEWARE + +#if CC_USE_PHYSICS_PHYSX + #include "cocos/bindings/auto/jsb_physics_auto.h" +#endif + +bool jsb_register_all_modules() { + se::ScriptEngine *se = se::ScriptEngine::getInstance(); + + se->addBeforeCleanupHook([se]() { + se->garbageCollect(); + cc::DeferredReleasePool::clear(); + se->garbageCollect(); + cc::DeferredReleasePool::clear(); + }); + + se->addRegisterCallback(jsb_register_global_variables); + se->addRegisterCallback(register_all_engine); + se->addRegisterCallback(register_all_cocos_manual); + se->addRegisterCallback(register_platform_bindings); + se->addRegisterCallback(register_all_gfx); + se->addRegisterCallback(register_all_gfx_manual); + + se->addRegisterCallback(register_all_network); + se->addRegisterCallback(register_all_network_manual); + se->addRegisterCallback(register_all_xmlhttprequest); + // extension depend on network + se->addRegisterCallback(register_all_extension); + se->addRegisterCallback(register_all_dop_bindings); + se->addRegisterCallback(register_all_assets); + se->addRegisterCallback(register_all_assets_manual); + // pipeline depend on asset + se->addRegisterCallback(register_all_pipeline); + se->addRegisterCallback(register_all_pipeline_manual); + se->addRegisterCallback(register_all_geometry); + se->addRegisterCallback(register_all_geometry_manual); + se->addRegisterCallback(register_all_scene); + se->addRegisterCallback(register_all_gi); + se->addRegisterCallback(register_all_scene_manual); + se->addRegisterCallback(register_all_render); + se->addRegisterCallback(register_all_native2d); + +#if (CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_MACOS) + se->addRegisterCallback(register_javascript_objc_bridge); + se->addRegisterCallback(register_script_native_bridge); +#endif + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS) + se->addRegisterCallback(register_javascript_java_bridge); + se->addRegisterCallback(register_script_native_bridge); +#endif + +#if CC_USE_AUDIO + se->addRegisterCallback(register_all_audio); + se->addRegisterCallback(register_all_audio_manual); +#endif + +#if CC_USE_XR + se->addRegisterCallback(register_all_xr); + se->addRegisterCallback(register_all_xr_extension); +#endif + +#if CC_USE_SOCKET + se->addRegisterCallback(register_all_websocket); + se->addRegisterCallback(register_all_socketio); +#endif + +#if CC_USE_MIDDLEWARE + se->addRegisterCallback(register_all_editor_support); + + #if CC_USE_SPINE + se->addRegisterCallback(register_all_spine); + se->addRegisterCallback(register_all_spine_manual); + #endif + + #if CC_USE_DRAGONBONES + se->addRegisterCallback(register_all_dragonbones); + se->addRegisterCallback(register_all_dragonbones_manual); + #endif + +#endif // CC_USE_MIDDLEWARE + +#if CC_USE_PHYSICS_PHYSX + se->addRegisterCallback(register_all_physics); +#endif + +#if CC_USE_AR_MODULE + se->addRegisterCallback(register_all_ar); + se->addRegisterCallback(register_all_ar_manual); +#endif // CC_USE_AR_MODULE +#if (CC_PLATFORM == CC_PLATFORM_OPENHARMONY) + #if CC_USE_WEBVIEW + se->addRegisterCallback(register_all_webview); + #endif +#endif +#if (CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS) + + #if CC_USE_VIDEO + se->addRegisterCallback(register_all_video); + #endif + + #if CC_USE_WEBVIEW + se->addRegisterCallback(register_all_webview); + #endif + +#endif // (CC_PLATFORM == CC_PLATFORM_IOS || CC_PLATFORM == CC_PLATFORM_ANDROID) + +#if CC_USE_SOCKET && CC_USE_WEBSOCKET_SERVER + se->addRegisterCallback(register_all_websocket_server); +#endif + se->addAfterCleanupHook([]() { + cc::DeferredReleasePool::clear(); + JSBClassType::cleanup(); + }); + return true; +} diff --git a/cocos/bindings/manual/jsb_module_register.h b/cocos/bindings/manual/jsb_module_register.h new file mode 100644 index 0000000..9766c3f --- /dev/null +++ b/cocos/bindings/manual/jsb_module_register.h @@ -0,0 +1,29 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/string.h" + +bool jsb_register_all_modules(); diff --git a/cocos/bindings/manual/jsb_network_manual.cpp b/cocos/bindings/manual/jsb_network_manual.cpp new file mode 100644 index 0000000..3360799 --- /dev/null +++ b/cocos/bindings/manual/jsb_network_manual.cpp @@ -0,0 +1,335 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_network_manual.h" +#include "base/Config.h" +#include "bindings/auto/jsb_network_auto.h" +#include "bindings/manual/jsb_conversions.h" +#include "bindings/manual/jsb_global.h" +#include "network/Downloader.h" + +// deprecated since v3.6 +static bool js_network_Downloader_createDownloadFileTask(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, + "js_network_Downloader_createDownloadFileTask : Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 2) { + ccstd::string arg0; + ccstd::string arg1; + ok &= sevalue_to_native(args[0], &arg0); + ok &= sevalue_to_native(args[1], &arg1); + SE_PRECONDITION2(ok, false, + "js_network_Downloader_createDownloadFileTask : Error processing arguments"); + std::shared_ptr result = cobj->createDownloadTask( + arg0, arg1); + ok &= nativevalue_to_se(result, s.rval()); + //ROOT downloader object + s.thisObject()->root(); + + SE_PRECONDITION2(ok, false, + "js_network_Downloader_createDownloadFileTask : Error processing arguments"); + return true; + } + if (argc == 3) { + ccstd::string arg0; + ccstd::string arg1; + ccstd::string arg2; + ok &= sevalue_to_native(args[0], &arg0); + ok &= sevalue_to_native(args[1], &arg1); + ok &= sevalue_to_native(args[2], &arg2); + SE_PRECONDITION2(ok, false, + "js_network_Downloader_createDownloadFileTask : Error processing arguments"); + std::shared_ptr result = cobj->createDownloadTask( + arg0, arg1, arg2); + ok &= nativevalue_to_se(result, s.rval()); + //ROOT downloader object + s.thisObject()->root(); + + SE_PRECONDITION2(ok, false, + "js_network_Downloader_createDownloadFileTask : Error processing arguments"); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 3); + return false; +} + +SE_BIND_FUNC(js_network_Downloader_createDownloadFileTask) + +static bool js_network_Downloader_createDownloadTask(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, + "js_network_Downloader_createDownloadFileTask : Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 2) { + ccstd::string arg0; + ccstd::string arg1; + ok &= sevalue_to_native(args[0], &arg0); + ok &= sevalue_to_native(args[1], &arg1); + SE_PRECONDITION2(ok, false, + "js_network_Downloader_createDownloadTask : Error processing arguments"); + std::shared_ptr result = cobj->createDownloadTask( + arg0, arg1); + ok &= nativevalue_to_se(result, s.rval()); + //ROOT downloader object + s.thisObject()->root(); + + SE_PRECONDITION2(ok, false, + "js_network_Downloader_createDownloadTask : Error processing arguments"); + return true; + } + if (argc == 3) { + ccstd::string arg0; + ccstd::string arg1; + ccstd::string arg2; + ok &= sevalue_to_native(args[0], &arg0); + ok &= sevalue_to_native(args[1], &arg1); + ok &= sevalue_to_native(args[2], &arg2); + SE_PRECONDITION2(ok, false, + "js_network_Downloader_createDownloadTask : Error processing arguments"); + std::shared_ptr result = cobj->createDownloadTask( + arg0, arg1, arg2); + ok &= nativevalue_to_se(result, s.rval()); + //ROOT downloader object + s.thisObject()->root(); + + SE_PRECONDITION2(ok, false, + "js_network_Downloader_createDownloadTask : Error processing arguments"); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 3); + return false; +} + +SE_BIND_FUNC(js_network_Downloader_createDownloadTask) + +// deprecated since v3.6 +static bool js_network_Downloader_setOnFileTaskSuccess(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + std::function arg0; + do { + if (args[0].isObject() && args[0].toObject()->isFunction()) { + se::Value jsThis(s.thisObject()); + se::Value jsFunc(args[0]); + jsThis.toObject()->attachObject(jsFunc.toObject()); + auto *thisObj = s.thisObject(); + auto lambda = [=](const cc::network::DownloadTask &larg0) -> void { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + CC_UNUSED bool ok = true; + se::ValueArray args; + args.resize(1); + ok &= nativevalue_to_se(larg0, args[0]); + se::Value rval; + se::Object *funcObj = jsFunc.toObject(); + bool succeed = funcObj->call(args, thisObj, &rval); + if (!succeed) { + se::ScriptEngine::getInstance()->clearException(); + } + if (thisObj) { + thisObj->unroot(); + } + }; + arg0 = lambda; + } else { + arg0 = nullptr; + } + } while (false); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->setOnSuccess(arg0); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_network_Downloader_setOnFileTaskSuccess) // NOLINT(readability-identifier-naming) + +static bool js_network_Downloader_setOnSuccess(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + std::function arg0; + do { + if (args[0].isObject() && args[0].toObject()->isFunction()) { + se::Value jsThis(s.thisObject()); + se::Value jsFunc(args[0]); + jsThis.toObject()->attachObject(jsFunc.toObject()); + auto *thisObj = s.thisObject(); + auto lambda = [=](const cc::network::DownloadTask &larg0) -> void { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + CC_UNUSED bool ok = true; + se::ValueArray args; + args.resize(1); + ok &= nativevalue_to_se(larg0, args[0]); + se::Value rval; + se::Object *funcObj = jsFunc.toObject(); + bool succeed = funcObj->call(args, thisObj, &rval); + if (!succeed) { + se::ScriptEngine::getInstance()->clearException(); + } + if (thisObj) { + thisObj->unroot(); + } + }; + arg0 = lambda; + } else { + arg0 = nullptr; + } + } while (false); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->setOnSuccess(arg0); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC_AS_PROP_SET(js_network_Downloader_setOnSuccess) + +// deprecated in v3.6 +static bool js_network_Downloader_setOnTaskError(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + std::function arg0; + do { + if (args[0].isObject() && args[0].toObject()->isFunction()) { + se::Value jsThis(s.thisObject()); + se::Value jsFunc(args[0]); + jsThis.toObject()->attachObject(jsFunc.toObject()); + auto *thisObj = s.thisObject(); + auto lambda = [=](const cc::network::DownloadTask &larg0, int larg1, int larg2, const ccstd::string &larg3) -> void { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + CC_UNUSED bool ok = true; + se::ValueArray args; + args.resize(4); + ok &= nativevalue_to_se(larg0, args[0]); + ok &= nativevalue_to_se(larg1, args[1]); + ok &= nativevalue_to_se(larg2, args[2]); + ok &= nativevalue_to_se(larg3, args[3]); + se::Value rval; + se::Object *funcObj = jsFunc.toObject(); + bool succeed = funcObj->call(args, thisObj, &rval); + if (!succeed) { + se::ScriptEngine::getInstance()->clearException(); + } + if (thisObj) { + thisObj->unroot(); + } + }; + arg0 = lambda; + } else { + arg0 = nullptr; + } + } while (false); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->setOnError(arg0); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_network_Downloader_setOnTaskError) // NOLINT(readability-identifier-naming) + +static bool js_network_Downloader_setOnError(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + std::function arg0; + do { + if (args[0].isObject() && args[0].toObject()->isFunction()) { + se::Value jsThis(s.thisObject()); + se::Value jsFunc(args[0]); + jsThis.toObject()->attachObject(jsFunc.toObject()); + auto *thisObj = s.thisObject(); + auto lambda = [=](const cc::network::DownloadTask &larg0, int larg1, int larg2, const ccstd::string &larg3) -> void { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + CC_UNUSED bool ok = true; + se::ValueArray args; + args.resize(4); + ok &= nativevalue_to_se(larg0, args[0]); + ok &= nativevalue_to_se(larg1, args[1]); + ok &= nativevalue_to_se(larg2, args[2]); + ok &= nativevalue_to_se(larg3, args[3]); + se::Value rval; + se::Object *funcObj = jsFunc.toObject(); + bool succeed = funcObj->call(args, thisObj, &rval); + if (!succeed) { + se::ScriptEngine::getInstance()->clearException(); + } + if (thisObj) { + thisObj->unroot(); + } + }; + arg0 = lambda; + } else { + arg0 = nullptr; + } + } while (false); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + cobj->setOnError(arg0); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC_AS_PROP_SET(js_network_Downloader_setOnError) + +bool register_all_network_manual(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) + __jsb_cc_network_Downloader_proto->defineProperty("onSuccess", nullptr, _SE(js_network_Downloader_setOnSuccess_asSetter)); + __jsb_cc_network_Downloader_proto->defineProperty("onError", nullptr, _SE(js_network_Downloader_setOnError_asSetter)); + __jsb_cc_network_Downloader_proto->defineFunction("createDownloadTask", + _SE(js_network_Downloader_createDownloadTask)); + __jsb_cc_network_Downloader_proto->defineFunction("createDownloadFileTask", + _SE(js_network_Downloader_createDownloadFileTask)); // deprecated since v3.6 + __jsb_cc_network_Downloader_proto->defineFunction("setOnTaskError", + _SE(js_network_Downloader_setOnTaskError)); // deprecated since v3.6 + __jsb_cc_network_Downloader_proto->defineFunction("setOnFileTaskSuccess", + _SE(js_network_Downloader_setOnFileTaskSuccess)); // deprecated since v3.6 + return true; +} diff --git a/cocos/bindings/manual/jsb_network_manual.h b/cocos/bindings/manual/jsb_network_manual.h new file mode 100644 index 0000000..1afcdda --- /dev/null +++ b/cocos/bindings/manual/jsb_network_manual.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { +class Object; +} + +bool register_all_network_manual(se::Object *obj); diff --git a/cocos/bindings/manual/jsb_pipeline_manual.cpp b/cocos/bindings/manual/jsb_pipeline_manual.cpp new file mode 100644 index 0000000..21aa770 --- /dev/null +++ b/cocos/bindings/manual/jsb_pipeline_manual.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "cocos/bindings/auto/jsb_gfx_auto.h" +#include "cocos/bindings/auto/jsb_pipeline_auto.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" +#include "gfx-base/GFXPipelineState.h" +#include "renderer/pipeline/Define.h" +#include "renderer/pipeline/PipelineStateManager.h" +#include "renderer/pipeline/RenderPipeline.h" + +static bool JSB_getOrCreatePipelineState(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + size_t argc = args.size(); + if (argc == 4) { + auto *pass = static_cast(args[0].toObject()->getPrivateData()); + auto *shader = static_cast(args[1].toObject()->getPrivateData()); + auto *renderPass = static_cast(args[2].toObject()->getPrivateData()); + auto *inputAssembler = static_cast(args[3].toObject()->getPrivateData()); + auto *pipelineState = cc::pipeline::PipelineStateManager::getOrCreatePipelineState(pass, shader, inputAssembler, renderPass); + native_ptr_to_seval(pipelineState, &s.rval()); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 4); + return false; +} +SE_BIND_FUNC(JSB_getOrCreatePipelineState); + +bool register_all_pipeline_manual(se::Object *obj) { // NOLINT(readability-identifier-naming) + // Get the ns + se::Value nrVal; + if (!obj->getProperty("nr", &nrVal)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nrVal.setObject(jsobj); + obj->setProperty("nr", nrVal); + } + se::Object *nr = nrVal.toObject(); + + se::Value psmVal; + se::HandleObject jsobj(se::Object::createPlainObject()); + psmVal.setObject(jsobj); + nr->setProperty("PipelineStateManager", psmVal); + psmVal.toObject()->defineFunction("getOrCreatePipelineState", _SE(JSB_getOrCreatePipelineState)); + + return true; +} diff --git a/cocos/bindings/manual/jsb_pipeline_manual.h b/cocos/bindings/manual/jsb_pipeline_manual.h new file mode 100644 index 0000000..cd6061c --- /dev/null +++ b/cocos/bindings/manual/jsb_pipeline_manual.h @@ -0,0 +1,30 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { +class Object; +} +bool register_all_pipeline_manual(se::Object *obj); \ No newline at end of file diff --git a/cocos/bindings/manual/jsb_platform.h b/cocos/bindings/manual/jsb_platform.h new file mode 100644 index 0000000..189b121 --- /dev/null +++ b/cocos/bindings/manual/jsb_platform.h @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" + +namespace se { +class Object; +} + +bool register_platform_bindings(se::Object *obj); // NOLINT[readability-identifier-naming] +const ccstd::unordered_map &getFontFamilyNameMap(); diff --git a/cocos/bindings/manual/jsb_platform_android.cpp b/cocos/bindings/manual/jsb_platform_android.cpp new file mode 100644 index 0000000..ff92ab8 --- /dev/null +++ b/cocos/bindings/manual/jsb_platform_android.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_platform.h" + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" +#include "cocos/platform/FileUtils.h" +#include "cocos/platform/java/jni/JniHelper.h" + +#include + +#ifndef JCLS_CANVASIMPL + #define JCLS_CANVASIMPL "com/cocos/lib/CanvasRenderingContext2DImpl" +#endif + +static ccstd::unordered_map gFontFamilyNameMap; + +const ccstd::unordered_map &getFontFamilyNameMap() { + return gFontFamilyNameMap; +} + +static bool jsbLoadFont(se::State &s) { + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc >= 1) { + s.rval().setNull(); + + ccstd::string originalFamilyName; + ok &= sevalue_to_native(args[0], &originalFamilyName); + SE_PRECONDITION2(ok, false, "Error processing argument: originalFamilyName"); + + ccstd::string source; + ok &= sevalue_to_native(args[1], &source); + SE_PRECONDITION2(ok, false, "Error processing argument: source"); + + ccstd::string fontFilePath; + std::regex re(R"(url\(\s*'\s*(.*?)\s*'\s*\))"); + std::match_results results; + if (std::regex_search(source.cbegin(), source.cend(), results, re)) { + fontFilePath = results[1].str(); + } + + fontFilePath = cc::FileUtils::getInstance()->fullPathForFilename(fontFilePath); + if (fontFilePath.empty()) { + SE_LOGE("Font (%s) doesn't exist!", fontFilePath.c_str()); + return true; + } + + cc::JniHelper::callStaticVoidMethod(JCLS_CANVASIMPL, "loadTypeface", originalFamilyName, fontFilePath); + + s.rval().setString(originalFamilyName); + + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(jsbLoadFont) + +bool register_platform_bindings(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) + __jsbObj->defineFunction("loadFont", _SE(jsbLoadFont)); + return true; +} \ No newline at end of file diff --git a/cocos/bindings/manual/jsb_platform_apple.mm b/cocos/bindings/manual/jsb_platform_apple.mm new file mode 100644 index 0000000..93fcc85 --- /dev/null +++ b/cocos/bindings/manual/jsb_platform_apple.mm @@ -0,0 +1,121 @@ +/**************************************************************************** + Copyright (c) 2017-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#import "jsb_platform.h" + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" +#include "cocos/platform/FileUtils.h" + +#import +#import +#include + +using namespace cc; + +static ccstd::unordered_map _fontFamilyNameMap; + +const ccstd::unordered_map &getFontFamilyNameMap() { + return _fontFamilyNameMap; +} + +static bool JSB_loadFont(se::State &s) { + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc >= 1) { + s.rval().setNull(); + + ccstd::string originalFamilyName; + ok &= sevalue_to_native(args[0], &originalFamilyName); + SE_PRECONDITION2(ok, false, "Error processing argument: originalFamilyName"); + + // Don't reload font again to avoid memory leak. + if (_fontFamilyNameMap.find(originalFamilyName) != _fontFamilyNameMap.end()) { + s.rval().setString(_fontFamilyNameMap[originalFamilyName]); + return true; + } + + ccstd::string source; + ok &= sevalue_to_native(args[1], &source); + SE_PRECONDITION2(ok, false, "Error processing argument: source"); + + ccstd::string fontFilePath; + std::regex re("url\\(\\s*'\\s*(.*?)\\s*'\\s*\\)"); + std::match_results results; + if (std::regex_search(source.cbegin(), source.cend(), results, re)) { + fontFilePath = results[1].str(); + } + + fontFilePath = FileUtils::getInstance()->fullPathForFilename(fontFilePath); + if (fontFilePath.empty()) { + SE_LOGE("Font (%s) doesn't exist!", fontFilePath.c_str()); + return true; + } + + NSData *dynamicFontData = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:fontFilePath.c_str()]]; + if (!dynamicFontData) { + SE_LOGE("load font (%s) failed!", source.c_str()); + return true; + } + + bool succeed = true; + CFErrorRef error; + CGDataProviderRef providerRef = CGDataProviderCreateWithCFData((CFDataRef)dynamicFontData); + CGFontRef font = CGFontCreateWithDataProvider(providerRef); + if (!CTFontManagerRegisterGraphicsFont(font, &error)) { + CFStringRef errorDescription = CFErrorCopyDescription(error); + const char *cErrorStr = CFStringGetCStringPtr(errorDescription, kCFStringEncodingUTF8); + SE_LOGE("Failed to load font: %s", cErrorStr); + CFRelease(errorDescription); + succeed = false; + } + + if (succeed) { + CFStringRef fontName = CGFontCopyFullName(font); + ccstd::string familyName([(NSString *)fontName UTF8String]); + + if (!familyName.empty()) { + _fontFamilyNameMap.emplace(originalFamilyName, familyName); + s.rval().setString(familyName); + } + CFRelease(fontName); + } + + CFRelease(font); + CFRelease(providerRef); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_loadFont) + +bool register_platform_bindings(se::Object *obj) { + __jsbObj->defineFunction("loadFont", _SE(JSB_loadFont)); + return true; +} diff --git a/cocos/bindings/manual/jsb_platform_linux.cpp b/cocos/bindings/manual/jsb_platform_linux.cpp new file mode 100644 index 0000000..9558af9 --- /dev/null +++ b/cocos/bindings/manual/jsb_platform_linux.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_platform.h" + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global_init.h" +#include "cocos/platform/FileUtils.h" + +#include + +using namespace cc; + +static ccstd::unordered_map _fontFamilyNameMap; + +const ccstd::unordered_map &getFontFamilyNameMap() { + return _fontFamilyNameMap; +} + +static bool JSB_loadFont(se::State &s) { + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc >= 1) { + s.rval().setNull(); + + ccstd::string originalFamilyName; + ok &= sevalue_to_native(args[0], &originalFamilyName); + SE_PRECONDITION2(ok, false, "Error processing argument: originalFamilyName"); + + ccstd::string source; + ok &= sevalue_to_native(args[1], &source); + SE_PRECONDITION2(ok, false, "Error processing argument: source"); + + ccstd::string fontFilePath; + std::regex re(R"(url\(\s*'\s*(.*?)\s*'\s*\))"); + std::match_results results; + if (std::regex_search(source.cbegin(), source.cend(), results, re)) { + fontFilePath = results[1].str(); + } + + fontFilePath = FileUtils::getInstance()->fullPathForFilename(fontFilePath); + if (fontFilePath.empty()) { + SE_LOGE("Font (%s) doesn't exist!", fontFilePath.c_str()); + return true; + } + + // just put the path info, used at CanvasRenderingContext2DImpl::updateFont() + _fontFamilyNameMap.emplace(originalFamilyName, fontFilePath); + + s.rval().setString(originalFamilyName); + + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_loadFont) + +bool register_platform_bindings(se::Object *obj) { + __jsbObj->defineFunction("loadFont", _SE(JSB_loadFont)); + return true; +} diff --git a/cocos/bindings/manual/jsb_platform_ohos.cpp b/cocos/bindings/manual/jsb_platform_ohos.cpp new file mode 100644 index 0000000..6f7c6fe --- /dev/null +++ b/cocos/bindings/manual/jsb_platform_ohos.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_platform.h" + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" +#include "cocos/platform/FileUtils.h" +#include "cocos/platform/java/jni/JniHelper.h" + +#include + +#ifndef JCLS_CANVASIMPL + #define JCLS_CANVASIMPL "com/cocos/lib/CanvasRenderingContext2DImpl" +#endif + +using namespace cc; //NOLINT + +static ccstd::unordered_map gFontFamilyNameMap; + +const ccstd::unordered_map &getFontFamilyNameMap() { + return gFontFamilyNameMap; +} + +static bool JSB_loadFont(se::State &s) { //NOLINT + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc >= 1) { + s.rval().setNull(); + + ccstd::string originalFamilyName; + ok &= sevalue_to_native(args[0], &originalFamilyName); + SE_PRECONDITION2(ok, false, "Error processing argument: originalFamilyName"); + + ccstd::string source; + ok &= sevalue_to_native(args[1], &source); + SE_PRECONDITION2(ok, false, "Error processing argument: source"); + + ccstd::string fontFilePath; + std::regex re(R"(url\(\s*'\s*(.*?)\s*'\s*\))"); + std::match_results results; + if (std::regex_search(source.cbegin(), source.cend(), results, re)) { + fontFilePath = results[1].str(); + } + + fontFilePath = FileUtils::getInstance()->fullPathForFilename(fontFilePath); + if (fontFilePath.empty()) { + SE_LOGE("Font (%s) doesn't exist!", fontFilePath.c_str()); + return true; + } + + JniHelper::callStaticVoidMethod(JCLS_CANVASIMPL, "loadTypeface", originalFamilyName, fontFilePath); + + s.rval().setString(originalFamilyName); + + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(JSB_loadFont) + +bool register_platform_bindings(se::Object *obj) { //NOLINT + __jsbObj->defineFunction("loadFont", _SE(JSB_loadFont)); + return true; +} \ No newline at end of file diff --git a/cocos/bindings/manual/jsb_platform_openharmony.cpp b/cocos/bindings/manual/jsb_platform_openharmony.cpp new file mode 100644 index 0000000..9dc9431 --- /dev/null +++ b/cocos/bindings/manual/jsb_platform_openharmony.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_platform.h" + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global_init.h" +#include "cocos/platform/FileUtils.h" + +#include + +using namespace cc; + +static std::unordered_map _fontFamilyNameMap; + +const std::unordered_map &getFontFamilyNameMap() { + return _fontFamilyNameMap; +} + +static bool JSB_loadFont(se::State &s) { + // TODO(qgh):Currently it does not support loading OpenHarmony fonts, it may be supported in the future + return true; +} +SE_BIND_FUNC(JSB_loadFont) + +bool register_platform_bindings(se::Object *obj) { + __jsbObj->defineFunction("loadFont", _SE(JSB_loadFont)); + return true; +} diff --git a/cocos/bindings/manual/jsb_platform_win32.cpp b/cocos/bindings/manual/jsb_platform_win32.cpp new file mode 100644 index 0000000..851ffe6 --- /dev/null +++ b/cocos/bindings/manual/jsb_platform_win32.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_platform.h" + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global_init.h" +#include "cocos/platform/FileUtils.h" + +#include + +using namespace cc; + +static ccstd::unordered_map fontFamilyNameMap; + +const ccstd::unordered_map &getFontFamilyNameMap() { + return fontFamilyNameMap; +} + +static bool jsbLoadFont(se::State &s) { + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc >= 1) { + s.rval().setNull(); + + ccstd::string originalFamilyName; + ok &= sevalue_to_native(args[0], &originalFamilyName); + SE_PRECONDITION2(ok, false, "Error processing argument: originalFamilyName"); + + ccstd::string source; + ok &= sevalue_to_native(args[1], &source); + SE_PRECONDITION2(ok, false, "Error processing argument: source"); + + ccstd::string fontFilePath; + std::regex re(R"(url\(\s*'\s*(.*?)\s*'\s*\))"); + std::match_results results; + if (std::regex_search(source.cbegin(), source.cend(), results, re)) { + fontFilePath = results[1].str(); + } + + fontFilePath = FileUtils::getInstance()->fullPathForFilename(fontFilePath); + if (fontFilePath.empty()) { + SE_LOGE("Font (%s) doesn't exist!", fontFilePath.c_str()); + return true; + } + + // just put the path info, used at CanvasRenderingContext2DImpl::updateFont() + fontFamilyNameMap.emplace(originalFamilyName, fontFilePath); + + s.rval().setString(originalFamilyName); + + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(jsbLoadFont) + +bool register_platform_bindings(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming) + __jsbObj->defineFunction("loadFont", _SE(jsbLoadFont)); + return true; +} diff --git a/cocos/bindings/manual/jsb_scene_manual.cpp b/cocos/bindings/manual/jsb_scene_manual.cpp new file mode 100644 index 0000000..bd5370b --- /dev/null +++ b/cocos/bindings/manual/jsb_scene_manual.cpp @@ -0,0 +1,902 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_scene_manual.h" +#include "bindings/auto/jsb_gfx_auto.h" +#include "bindings/auto/jsb_scene_auto.h" +#include "core/Root.h" +#include "core/scene-graph/Node.h" +#include "scene/Model.h" + +#ifndef JSB_ALLOC + #define JSB_ALLOC(kls, ...) ccnew kls(__VA_ARGS__) +#endif + +#ifndef JSB_FREE + #define JSB_FREE(ptr) delete ptr +#endif + +#define DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emmiter) \ + se::Object *jsObject = emitter->getScriptObject(); \ + if (jsObject == nullptr) { \ + jsObject = se::NativePtrToObjectMap::findFirst(emitter); \ + emmiter->setScriptObject(jsObject); \ + } + +namespace { + +class TempFloatArray final { +public: + TempFloatArray() = default; + ~TempFloatArray() = default; + + inline void setData(float *data) { _data = data; } + + inline void writeVec3(const cc::Vec3 &p) { + _data[0] = p.x; + _data[1] = p.y; + _data[2] = p.z; + } + + inline cc::Vec3 readVec3() const { + return cc::Vec3{_data[0], _data[1], _data[2]}; + } + + inline void writeQuaternion(const cc::Quaternion &p) { + _data[0] = p.x; + _data[1] = p.y; + _data[2] = p.z; + _data[3] = p.w; + } + + inline cc::Quaternion readQuaternion() const { + return cc::Quaternion{_data[0], _data[1], _data[2], _data[3]}; + } + + inline void writeMat4(const cc::Mat4 &m) { + memcpy(_data, m.m, sizeof(float) * 16); + } + + inline cc::Mat4 readMat4() const { + cc::Mat4 ret; + memcpy(ret.m, _data, sizeof(float) * 16); + return ret; + } + + inline void writeRay(const cc::geometry::Ray &ray) { + _data[0] = ray.o.x; + _data[1] = ray.o.y; + _data[2] = ray.o.z; + _data[3] = ray.d.x; + _data[4] = ray.d.y; + _data[5] = ray.d.z; + } + + inline cc::geometry::Ray readRay() const { + return cc::geometry::Ray{_data[0], _data[1], _data[2], _data[3], _data[4], _data[5]}; + } + + inline const float &operator[](size_t index) const { return _data[index]; } + inline float &operator[](size_t index) { return _data[index]; } + +private: + float *_data{nullptr}; + + CC_DISALLOW_ASSIGN(TempFloatArray) +}; + +TempFloatArray tempFloatArray; + +} // namespace + +static bool js_root_registerListeners(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + +#define DISPATCH_EVENT_TO_JS_ARGS_0(eventType, jsFuncName) \ + cobj->on([](cc::Root *rootObj) { \ + se::AutoHandleScope hs; \ + se::Value rootVal; \ + bool ok = nativevalue_to_se(rootObj, rootVal); \ + SE_PRECONDITION2_FUNCNAME_VOID(ok, #jsFuncName, "js_root_registerListeners : Error processing arguments"); \ + if (rootVal.isObject()) { \ + se::ScriptEngine::getInstance()->callFunction(rootVal.toObject(), #jsFuncName, 0, nullptr); \ + } \ + }); + + DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::BeforeCommit, _onDirectorBeforeCommit); + DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::BeforeRender, _onDirectorBeforeRender); + DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::AfterRender, _onDirectorAfterRender); + DISPATCH_EVENT_TO_JS_ARGS_0(cc::Root::PipelineChanged, _onDirectorPipelineChanged); + + return true; +} +SE_BIND_FUNC(js_root_registerListeners) // NOLINT(readability-identifier-naming) + +static void registerOnTransformChanged(cc::Node *node) { + node->on( + +[](cc::Node *emitter, cc::TransformBit transformBit) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(transformBit, arg0); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onTransformChanged", 1, &arg0); + }); +} + +static void registerOnParentChanged(cc::Node *node) { + node->on( + +[](cc::Node *emitter, cc::Node *oldParent) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(oldParent, arg0); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onParentChanged", 1, &arg0); + }); +} + +static void registerOnMobilityChanged(cc::Node *node) { + node->on( + +[](cc::Node *emitter) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onMobilityChanged", 0, nullptr); + }); +} + +static void registerOnLayerChanged(cc::Node *node) { + node->on( + +[](cc::Node *emitter, uint32_t layer) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(layer, arg0); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onLayerChanged", 1, &arg0); + }); +} + +static void registerOnChildRemoved(cc::Node *node) { + node->on( + +[](cc::Node *emitter, cc::Node *child) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(child, arg0); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onChildRemoved", 1, &arg0); + }); +} + +static void registerOnChildAdded(cc::Node *node) { + node->on( + +[](cc::Node *emitter, cc::Node *child) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(child, arg0); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onChildAdded", 1, &arg0); + }); +} + +static void registerOnSiblingOrderChanged(cc::Node *node) { + node->on( + +[](cc::Node *emitter) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope scope; + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onSiblingOrderChanged", 0, nullptr); + }); +} + +static void registerOnActiveNode(cc::Node *node) { + node->on( + +[](cc::Node *emitter, bool shouldActiveNow) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(shouldActiveNow, arg0); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onActiveNode", 1, &arg0); + }); +} + +static void registerOnBatchCreated(cc::Node *node) { + node->on( + +[](cc::Node *emitter, bool dontChildPrefab) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(dontChildPrefab, arg0); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onBatchCreated", 1, &arg0); + }); +} + +static void registerLocalPositionRotationScaleUpdated(cc::Node *node) { + node->on( + +[](cc::Node *emitter, float x, float y, float z) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + ccstd::array args; + nativevalue_to_se(x, args[0]); + nativevalue_to_se(y, args[1]); + nativevalue_to_se(z, args[2]); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onLocalPositionUpdated", static_cast(args.size()), args.data()); + }); + + node->on( + +[](cc::Node *emitter, float x, float y, float z, float w) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + ccstd::array args; + nativevalue_to_se(x, args[0]); + nativevalue_to_se(y, args[1]); + nativevalue_to_se(z, args[2]); + nativevalue_to_se(w, args[3]); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onLocalRotationUpdated", static_cast(args.size()), args.data()); + }); + + node->on( + +[](cc::Node *emitter, float x, float y, float z) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + ccstd::array args; + nativevalue_to_se(x, args[0]); + nativevalue_to_se(y, args[1]); + nativevalue_to_se(z, args[2]); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onLocalScaleUpdated", static_cast(args.size()), args.data()); + }); + + node->on( + +[](cc::Node *emitter, float px, float py, float pz, float rx, float ry, float rz, float rw, float sx, float sy, float sz) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + ccstd::array args; + nativevalue_to_se(px, args[0]); + nativevalue_to_se(py, args[1]); + nativevalue_to_se(pz, args[2]); + + nativevalue_to_se(rx, args[3]); + nativevalue_to_se(ry, args[4]); + nativevalue_to_se(rz, args[5]); + nativevalue_to_se(rw, args[6]); + + nativevalue_to_se(sx, args[7]); + nativevalue_to_se(sy, args[8]); + nativevalue_to_se(sz, args[9]); + + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onLocalPositionRotationScaleUpdated", static_cast(args.size()), args.data()); + }); +} + +static void registerOnLightProbeBakingChanged(cc::Node *node, se::Object *jsObject) { + node->on( + [jsObject](cc::Node * /*emitter*/) { + se::AutoHandleScope hs; + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onLightProbeBakingChanged", 0, nullptr); + }); +} + +static bool js_scene_Node_registerListeners(cc::Node *cobj) // NOLINT(readability-identifier-naming) +{ +#define NODE_DISPATCH_EVENT_TO_JS(eventType, jsFuncName) \ + cobj->on( \ + +[](cc::Node *emitter) { \ + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) \ + se::AutoHandleScope scope; \ + se::ScriptEngine::getInstance()->callFunction(jsObject, #jsFuncName, 0, nullptr); \ + }); + + registerOnActiveNode(cobj); + // NOTE: Node.prototype._onBatchCreated was implemented in TS and invoked in scene.jsb.ts (Scene.prototype._load), + // so don't need to register the listener here. + // registerOnBatchCreated(cobj); + registerOnChildAdded(cobj); + registerOnChildRemoved(cobj); + + NODE_DISPATCH_EVENT_TO_JS(cc::Node::Reattach, _onReAttach); + NODE_DISPATCH_EVENT_TO_JS(cc::Node::RemovePersistRootNode, _onRemovePersistRootNode); + + cobj->on(+[](cc::Node *emitter, index_t newIndex) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(newIndex, arg0); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onSiblingIndexChanged", 1, &arg0); + }); + + cobj->on( + +[](cc::Node *emitter, cc::Scene *scene) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(scene, arg0); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onSceneUpdated", 1, &arg0); + }); + +#if CC_EDITOR + cobj->on( + +[](cc::Node *emitter, bool attached) { + DEFINE_JS_OBJECT_IN_EVENT_CALLBACK(emitter) + + se::AutoHandleScope hs; + se::Value arg0; + nativevalue_to_se(attached, arg0); + se::ScriptEngine::getInstance()->callFunction(jsObject, "_onEditorAttached", 1, &arg0); + }); +#endif + + registerLocalPositionRotationScaleUpdated(cobj); + + return true; +} + +static bool js_cc_Node_initAndReturnSharedBuffer(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + auto *result = cobj->_getSharedArrayBufferObject(); + js_scene_Node_registerListeners(cobj); + s.rval().setObject(result); + return true; +} +SE_BIND_FUNC(js_cc_Node_initAndReturnSharedBuffer) // NOLINT(readability-identifier-naming) + +static bool js_scene_Node_registerOnTransformChanged(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + registerOnTransformChanged(cobj); + return true; +} +SE_BIND_FUNC(js_scene_Node_registerOnTransformChanged) // NOLINT(readability-identifier-naming) + +static bool js_scene_Node_registerOnParentChanged(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + registerOnParentChanged(cobj); + return true; +} +SE_BIND_FUNC(js_scene_Node_registerOnParentChanged) // NOLINT(readability-identifier-naming) + +static bool js_scene_Node_registerOnMobilityChanged(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + registerOnMobilityChanged(cobj); + return true; +} +SE_BIND_FUNC(js_scene_Node_registerOnMobilityChanged) // NOLINT(readability-identifier-naming) + +static bool js_scene_Node_registerOnLayerChanged(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + registerOnLayerChanged(cobj); + return true; +} +SE_BIND_FUNC(js_scene_Node_registerOnLayerChanged) // NOLINT(readability-identifier-naming) + +static bool js_scene_Node_registerOnSiblingOrderChanged(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + registerOnSiblingOrderChanged(cobj); + return true; +} +SE_BIND_FUNC(js_scene_Node_registerOnSiblingOrderChanged) // NOLINT(readability-identifier-naming) + +static bool js_scene_Node_registerOnLightProbeBakingChanged(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + + auto *jsObject = s.thisObject(); + + registerOnLightProbeBakingChanged(cobj, jsObject); + return true; +} +SE_BIND_FUNC(js_scene_Node_registerOnLightProbeBakingChanged) // NOLINT(readability-identifier-naming) + +static bool js_scene_Camera_screenPointToRay(void *nativeObject) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(nativeObject); + cc::geometry::Ray ray = cobj->screenPointToRay(tempFloatArray[0], tempFloatArray[1]); + tempFloatArray.writeRay(ray); + return true; +} +SE_BIND_FUNC_FAST(js_scene_Camera_screenPointToRay) + +static bool js_scene_Camera_screenToWorld(void *nativeObject) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(nativeObject); + cc::Vec3 ret = cobj->screenToWorld(tempFloatArray.readVec3()); + tempFloatArray.writeVec3(ret); + return true; +} +SE_BIND_FUNC_FAST(js_scene_Camera_screenToWorld) + +static bool js_scene_Camera_worldToScreen(void *nativeObject) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(nativeObject); + cc::Vec3 ret = cobj->worldToScreen(tempFloatArray.readVec3()); + tempFloatArray.writeVec3(ret); + return true; +} +SE_BIND_FUNC_FAST(js_scene_Camera_worldToScreen) + +static bool js_scene_Camera_worldMatrixToScreen(void *nativeObject) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(nativeObject); + cc::Mat4 worldMatrix = tempFloatArray.readMat4(); + cc::Mat4 ret = cobj->worldMatrixToScreen(worldMatrix, static_cast(tempFloatArray[16]), static_cast(tempFloatArray[17])); + tempFloatArray.writeMat4(ret); + return true; +} +SE_BIND_FUNC_FAST(js_scene_Camera_worldMatrixToScreen) + +static bool js_scene_Node_setTempFloatArray(se::State &s) // NOLINT(readability-identifier-naming) +{ + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 1) { + uint8_t *buffer = nullptr; + args[0].toObject()->getArrayBufferData(&buffer, nullptr); + tempFloatArray.setData(reinterpret_cast(buffer)); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1); + return false; +} +SE_BIND_FUNC(js_scene_Node_setTempFloatArray) + +#define FAST_GET_VALUE(ns, className, method, type) \ + static bool js_scene_##className##_##method(void *nativeObject) { \ + auto *cobj = reinterpret_cast(nativeObject); \ + auto result = cobj->method(); \ + tempFloatArray.write##type(result); \ + return true; \ + } \ + SE_BIND_FUNC_FAST(js_scene_##className##_##method) + +#define FAST_GET_CONST_REF(ns, className, method, type) \ + static bool js_scene_##className##_##method(void *nativeObject) { \ + auto *cobj = reinterpret_cast(nativeObject); \ + const auto &result = cobj->method(); \ + tempFloatArray.write##type(result); \ + return true; \ + } \ + SE_BIND_FUNC_FAST(js_scene_##className##_##method) + +FAST_GET_VALUE(cc, Node, getRight, Vec3) +FAST_GET_VALUE(cc, Node, getForward, Vec3) +FAST_GET_VALUE(cc, Node, getUp, Vec3) +FAST_GET_VALUE(cc, Node, getWorldRS, Mat4) +FAST_GET_VALUE(cc, Node, getWorldRT, Mat4) + +FAST_GET_CONST_REF(cc, Node, getWorldPosition, Vec3) +FAST_GET_CONST_REF(cc, Node, getWorldRotation, Quaternion) +FAST_GET_CONST_REF(cc, Node, getWorldScale, Vec3) +FAST_GET_CONST_REF(cc, Node, getWorldMatrix, Mat4) +FAST_GET_CONST_REF(cc, Node, getEulerAngles, Vec3) + +FAST_GET_CONST_REF(cc::scene, Camera, getMatView, Mat4) +FAST_GET_CONST_REF(cc::scene, Camera, getMatProj, Mat4) +FAST_GET_CONST_REF(cc::scene, Camera, getMatProjInv, Mat4) +FAST_GET_CONST_REF(cc::scene, Camera, getMatViewProj, Mat4) +FAST_GET_CONST_REF(cc::scene, Camera, getMatViewProjInv, Mat4) + +static bool js_scene_Node_setPosition(void *s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(s); + auto argc = static_cast(tempFloatArray[0]); + if (argc == 2) { + cobj->setPositionInternal(tempFloatArray[1], tempFloatArray[2], true); + } else { + cobj->setPositionInternal(tempFloatArray[1], tempFloatArray[2], tempFloatArray[3], true); + } + return true; +} +SE_BIND_FUNC_FAST(js_scene_Node_setPosition) + +static bool js_scene_Node_setRotation(void *s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(s); + cobj->setRotationInternal(tempFloatArray[0], tempFloatArray[1], tempFloatArray[2], tempFloatArray[3], true); + return true; +} +SE_BIND_FUNC_FAST(js_scene_Node_setRotation) + +static bool js_scene_Node_setRotationFromEuler(void *s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(s); + cobj->setRotationFromEuler(tempFloatArray[0], tempFloatArray[1], tempFloatArray[2]); + return true; +} +SE_BIND_FUNC_FAST(js_scene_Node_setRotationFromEuler) + +static bool js_scene_Node_setScale(void *s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(s); + auto argc = static_cast(tempFloatArray[0]); + if (argc == 2) { + cobj->setScaleInternal(tempFloatArray[1], tempFloatArray[2], true); + + } else { + cobj->setScaleInternal(tempFloatArray[1], tempFloatArray[2], tempFloatArray[3], true); + } + return true; +} +SE_BIND_FUNC_FAST(js_scene_Node_setScale) + +static bool js_scene_Node_setRTS(void *s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(s); + cc::Quaternion qt; + auto rotSize = static_cast(tempFloatArray[0]); + if (rotSize > 0) { + qt.set(tempFloatArray[1], tempFloatArray[2], tempFloatArray[3], tempFloatArray[4]); + } + + auto posSize = static_cast(tempFloatArray[5]); + cc::Vec3 pos; + if (posSize > 0) { + pos.set(tempFloatArray[6], tempFloatArray[7], tempFloatArray[8]); + } + + auto scaleSize = static_cast(tempFloatArray[9]); + cc::Vec3 scale; + if (scaleSize > 0) { + scale.set(tempFloatArray[10], tempFloatArray[11], tempFloatArray[12]); + } + cobj->setRTSInternal(rotSize > 0 ? &qt : nullptr, posSize > 0 ? &pos : nullptr, scaleSize > 0 ? &scale : nullptr, true); + return true; +} +SE_BIND_FUNC_FAST(js_scene_Node_setRTS) + +static bool js_scene_Node_rotateForJS(void *s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(s); + auto argc = static_cast(tempFloatArray[0]); + if (argc == 4) { + cobj->rotateForJS(tempFloatArray[1], tempFloatArray[2], tempFloatArray[3], tempFloatArray[4]); + } else { + auto size = static_cast(tempFloatArray[5]); + cobj->rotateForJS(tempFloatArray[1], tempFloatArray[2], tempFloatArray[3], tempFloatArray[4], size == 0 ? cc::NodeSpace::LOCAL : static_cast(static_cast(std::roundf(tempFloatArray[5])))); + } + + const auto &lrot = cobj->getRotation(); + tempFloatArray.writeQuaternion(lrot); + return true; +} +SE_BIND_FUNC_FAST(js_scene_Node_rotateForJS) + +static bool js_scene_Node_inverseTransformPoint(void *nativeObject) // NOLINT(readability-identifier-naming) +{ + auto *cobj = reinterpret_cast(nativeObject); + auto p = cobj->inverseTransformPoint(tempFloatArray.readVec3()); + tempFloatArray.writeVec3(p); + return true; +} +SE_BIND_FUNC_FAST(js_scene_Node_inverseTransformPoint) + +static bool js_scene_Pass_blocks_getter(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + auto *thiz = s.thisObject(); + + se::Value blocksVal; + if (thiz->getProperty("_blocks", &blocksVal, true) && blocksVal.isObject() && blocksVal.toObject()->isArray()) { + s.rval() = blocksVal; + return true; + } + + const auto &blocks = cobj->getBlocks(); + const uint8_t *blockDataBase = cobj->getRootBlock()->getData(); + + se::HandleObject jsBlocks{se::Object::createArrayObject(blocks.size())}; + int32_t i = 0; + for (const auto &block : blocks) { + se::HandleObject jsBlock{ + se::Object::createTypedArrayWithBuffer( + se::Object::TypedArrayType::FLOAT32, + cobj->getRootBlock()->getJSArrayBuffer(), + reinterpret_cast(block.data) - blockDataBase, + block.count * 4)}; + jsBlocks->setArrayElement(i, se::Value(jsBlock)); + ++i; + } + thiz->setProperty("_blocks", se::Value(jsBlocks)); + s.rval().setObject(jsBlocks); + return true; +} +SE_BIND_PROP_GET(js_scene_Pass_blocks_getter) + +static bool js_scene_RenderScene_root_getter(se::State &s) { // NOLINT(readability-identifier-naming) + nativevalue_to_se(cc::Root::getInstance(), s.rval()); + return true; +} +SE_BIND_PROP_GET(js_scene_RenderScene_root_getter) + +static bool js_Model_setInstancedAttribute(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + const auto &args = s.args(); + size_t argc = args.size(); + CC_UNUSED bool ok = true; + if (argc == 2) { + ccstd::string name; + ok &= sevalue_to_native(args[0], &name, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + const auto &val = args[1]; + if (val.isObject()) { + if (val.toObject()->isArray()) { + uint32_t len = 0; + val.toObject()->getArrayLength(&len); + + se::Value dataVal; + ccstd::array stackData; + float *pData = nullptr; + bool needFree = false; + + if (len <= static_cast(stackData.size())) { + pData = stackData.data(); + } else { + pData = static_cast(CC_MALLOC(len)); + needFree = true; + } + + for (uint32_t i = 0; i < len; ++i) { + ok = val.toObject()->getArrayElement(i, &dataVal); + CC_ASSERT(ok && dataVal.isNumber()); + pData[i] = dataVal.toFloat(); + } + + cobj->setInstancedAttribute(name, pData, len * sizeof(float)); + + if (needFree) { + CC_FREE(pData); + } + return true; + } + + if (val.toObject()->isTypedArray()) { + se::Object::TypedArrayType type = val.toObject()->getTypedArrayType(); + switch (type) { + case se::Object::TypedArrayType::FLOAT32: { + uint8_t *data = nullptr; + size_t byteLength = 0; + if (val.toObject()->getTypedArrayData(&data, &byteLength) && data != nullptr && byteLength > 0) { + cobj->setInstancedAttribute(name, reinterpret_cast(data), static_cast(byteLength)); + } + } break; + + default: + // FIXME: + CC_ABORT(); + break; + } + return true; + } + } + + SE_REPORT_ERROR("js_Model_setInstancedAttribute : Error processing arguments"); + return false; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 0); + return false; +} +SE_BIND_FUNC(js_Model_setInstancedAttribute) + +static bool js_Model_registerListeners(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + auto *thiz = s.thisObject(); + +#define MODEL_DISPATCH_EVENT_TO_JS(eventType, jsFuncName) \ + cobj->on([=](cc::scene::Model * /*emitter*/, uint32_t stamp) { \ + cobj->setCalledFromJS(true); \ + se::AutoHandleScope hs; \ + se::Value stampVal{stamp}; \ + se::ScriptEngine::getInstance()->callFunction(thiz, #jsFuncName, 1, &stampVal); \ + }) + + MODEL_DISPATCH_EVENT_TO_JS(cc::scene::Model::UpdateTransform, updateTransform); + MODEL_DISPATCH_EVENT_TO_JS(cc::scene::Model::UpdateUBO, updateUBOs); + +#undef MODEL_DISPATCH_EVENT_TO_JS + + cobj->on( + [=](cc::scene::Model * /*emitter*/, index_t subModelIndex, cc::gfx::DescriptorSet *descriptorSet) { + cobj->setCalledFromJS(true); + se::AutoHandleScope hs; + + ccstd::array args; + nativevalue_to_se(subModelIndex, args[0]); + nativevalue_to_se(descriptorSet, args[1]); + se::ScriptEngine::getInstance()->callFunction(thiz, "_updateLocalDescriptors", static_cast(args.size()), args.data()); + }); + + cobj->on([=](cc::scene::Model * /*emitter*/, index_t subModelIndex, cc::gfx::DescriptorSet *descriptorSet) { + cobj->setCalledFromJS(true); + se::AutoHandleScope hs; + + ccstd::array args; + nativevalue_to_se(subModelIndex, args[0]); + nativevalue_to_se(descriptorSet, args[1]); + se::ScriptEngine::getInstance()->callFunction(thiz, "_updateLocalSHDescriptors", static_cast(args.size()), args.data()); + }); + + cobj->on([=](cc::scene::Model * /*emitter*/, index_t subModelIndex, cc::gfx::DescriptorSet *descriptorSet) { + cobj->setCalledFromJS(true); + se::AutoHandleScope hs; + + ccstd::array args; + nativevalue_to_se(subModelIndex, args[0]); + nativevalue_to_se(descriptorSet, args[1]); + se::ScriptEngine::getInstance()->callFunction(thiz, "_updateWorldBoundDescriptors", static_cast(args.size()), args.data()); + }); + + cobj->on([=](cc::scene::Model * /*emitter*/, const ccstd::vector &attributes, cc::scene::SubModel *subModel) { + cobj->setCalledFromJS(true); + se::AutoHandleScope hs; + + ccstd::array args; + nativevalue_to_se(attributes, args[0]); + nativevalue_to_se(subModel, args[1]); + se::ScriptEngine::getInstance()->callFunction(thiz, "_updateInstancedAttributes", static_cast(args.size()), args.data()); + }); + + cobj->on( + [=](cc::scene::Model * /*emitter*/, index_t subModelIndex, ccstd::vector *pPatches) { + cobj->setCalledFromJS(true); + se::AutoHandleScope hs; + + se::Value rval; + ccstd::array args; + nativevalue_to_se(subModelIndex, args[0]); + bool ok = se::ScriptEngine::getInstance()->callFunction(thiz, "getMacroPatches", static_cast(args.size()), args.data(), &rval); + + if (ok) { + sevalue_to_native(rval, pPatches); + } + }); + + return true; +} +SE_BIND_FUNC(js_Model_registerListeners) // NOLINT(readability-identifier-naming) + +static bool js_assets_MaterialInstance_registerListeners(se::State &s) // NOLINT(readability-identifier-naming) +{ + auto *cobj = SE_THIS_OBJECT(s); + SE_PRECONDITION2(cobj, false, "Invalid Native Object"); + cobj->setRebuildPSOCallback([](index_t /*index*/, cc::Material *material) { + se::AutoHandleScope hs; + se::Value matVal; + bool ok = nativevalue_to_se(material, matVal); + if (!ok) { + return; + } + se::ScriptEngine::getInstance()->callFunction(matVal.toObject(), "_onRebuildPSO", 0, nullptr); + }); + + return true; +} +SE_BIND_FUNC(js_assets_MaterialInstance_registerListeners) // NOLINT(readability-identifier-naming) + +bool register_all_scene_manual(se::Object *obj) // NOLINT(readability-identifier-naming) +{ + // Get the ns + se::Value nsVal; + if (!obj->getProperty("ns", &nsVal)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + obj->setProperty("ns", nsVal); + } + + __jsb_cc_Root_proto->defineFunction("_registerListeners", _SE(js_root_registerListeners)); + + __jsb_cc_scene_Camera_proto->defineFunction("screenPointToRay", _SE(js_scene_Camera_screenPointToRay)); + __jsb_cc_scene_Camera_proto->defineFunction("screenToWorld", _SE(js_scene_Camera_screenToWorld)); + __jsb_cc_scene_Camera_proto->defineFunction("worldToScreen", _SE(js_scene_Camera_worldToScreen)); + __jsb_cc_scene_Camera_proto->defineFunction("worldMatrixToScreen", _SE(js_scene_Camera_worldMatrixToScreen)); + + __jsb_cc_scene_Camera_proto->defineFunction("getMatView", _SE(js_scene_Camera_getMatView)); + __jsb_cc_scene_Camera_proto->defineFunction("getMatProj", _SE(js_scene_Camera_getMatProj)); + __jsb_cc_scene_Camera_proto->defineFunction("getMatProjInv", _SE(js_scene_Camera_getMatProjInv)); + __jsb_cc_scene_Camera_proto->defineFunction("getMatViewProj", _SE(js_scene_Camera_getMatViewProj)); + __jsb_cc_scene_Camera_proto->defineFunction("getMatViewProjInv", _SE(js_scene_Camera_getMatViewProjInv)); + + // Node TS wrapper will invoke this function to let native object listen some events. + __jsb_cc_Node_proto->defineFunction("_initAndReturnSharedBuffer", _SE(js_cc_Node_initAndReturnSharedBuffer)); + + __jsb_cc_Node_proto->defineFunction("_registerOnTransformChanged", _SE(js_scene_Node_registerOnTransformChanged)); + __jsb_cc_Node_proto->defineFunction("_registerOnParentChanged", _SE(js_scene_Node_registerOnParentChanged)); + __jsb_cc_Node_proto->defineFunction("_registerOnMobilityChanged", _SE(js_scene_Node_registerOnMobilityChanged)); + __jsb_cc_Node_proto->defineFunction("_registerOnLayerChanged", _SE(js_scene_Node_registerOnLayerChanged)); + __jsb_cc_Node_proto->defineFunction("_registerOnSiblingOrderChanged", _SE(js_scene_Node_registerOnSiblingOrderChanged)); + __jsb_cc_Node_proto->defineFunction("_registerOnLightProbeBakingChanged", _SE(js_scene_Node_registerOnLightProbeBakingChanged)); + + se::Value jsbVal; + obj->getProperty("jsb", &jsbVal); + se::Value nodeVal; + jsbVal.toObject()->getProperty("Node", &nodeVal); + + nodeVal.toObject()->defineFunction("_setTempFloatArray", _SE(js_scene_Node_setTempFloatArray)); + + __jsb_cc_Node_proto->defineFunction("_setPosition", _SE(js_scene_Node_setPosition)); + __jsb_cc_Node_proto->defineFunction("_setScale", _SE(js_scene_Node_setScale)); + __jsb_cc_Node_proto->defineFunction("_setRotation", _SE(js_scene_Node_setRotation)); + __jsb_cc_Node_proto->defineFunction("_setRotationFromEuler", _SE(js_scene_Node_setRotationFromEuler)); + __jsb_cc_Node_proto->defineFunction("_rotateForJS", _SE(js_scene_Node_rotateForJS)); + + __jsb_cc_Node_proto->defineFunction("_getEulerAngles", _SE(js_scene_Node_getEulerAngles)); + __jsb_cc_Node_proto->defineFunction("_getForward", _SE(js_scene_Node_getForward)); + __jsb_cc_Node_proto->defineFunction("_getUp", _SE(js_scene_Node_getUp)); + __jsb_cc_Node_proto->defineFunction("_getRight", _SE(js_scene_Node_getRight)); + + __jsb_cc_Node_proto->defineFunction("_getWorldPosition", _SE(js_scene_Node_getWorldPosition)); + __jsb_cc_Node_proto->defineFunction("_getWorldRotation", _SE(js_scene_Node_getWorldRotation)); + __jsb_cc_Node_proto->defineFunction("_getWorldScale", _SE(js_scene_Node_getWorldScale)); + + __jsb_cc_Node_proto->defineFunction("_getWorldMatrix", _SE(js_scene_Node_getWorldMatrix)); + __jsb_cc_Node_proto->defineFunction("_getWorldRS", _SE(js_scene_Node_getWorldRS)); + __jsb_cc_Node_proto->defineFunction("_getWorldRT", _SE(js_scene_Node_getWorldRT)); + + __jsb_cc_Node_proto->defineFunction("_setRTS", _SE(js_scene_Node_setRTS)); + __jsb_cc_Node_proto->defineFunction("_inverseTransformPoint", _SE(js_scene_Node_inverseTransformPoint)); + + __jsb_cc_scene_Pass_proto->defineProperty("blocks", _SE(js_scene_Pass_blocks_getter), nullptr); + + __jsb_cc_scene_RenderScene_proto->defineProperty("root", _SE(js_scene_RenderScene_root_getter), nullptr); + + __jsb_cc_scene_Model_proto->defineFunction("_setInstancedAttribute", _SE(js_Model_setInstancedAttribute)); + + __jsb_cc_scene_Model_proto->defineFunction("_registerListeners", _SE(js_Model_registerListeners)); + __jsb_cc_MaterialInstance_proto->defineFunction("_registerListeners", _SE(js_assets_MaterialInstance_registerListeners)); + + return true; +} diff --git a/cocos/bindings/manual/jsb_scene_manual.h b/cocos/bindings/manual/jsb_scene_manual.h new file mode 100644 index 0000000..cabc7f1 --- /dev/null +++ b/cocos/bindings/manual/jsb_scene_manual.h @@ -0,0 +1,33 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "cocos/bindings/jswrapper/SeApi.h" + +namespace se { +class Object; +} + +bool register_all_scene_manual(se::Object *obj); diff --git a/cocos/bindings/manual/jsb_socketio.cpp b/cocos/bindings/manual/jsb_socketio.cpp new file mode 100644 index 0000000..2b18a66 --- /dev/null +++ b/cocos/bindings/manual/jsb_socketio.cpp @@ -0,0 +1,353 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_socketio.h" +#include "application/ApplicationManager.h" +#include "base/UTF8.h" +#include "cocos/base/DeferredReleasePool.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" +#include "cocos/network/SocketIO.h" + +// using namespace cc; +// using namespace cc::network; + +se::Class *__jsb_SocketIO_class = nullptr; // NOLINT + +class JSB_SocketIODelegate : public cc::RefCounted, public cc::network::SocketIO::SIODelegate { +public: + // c++11 map to callbacks + using JSB_SIOCallbackRegistry = ccstd::unordered_map; + + JSB_SocketIODelegate() = default; + + ~JSB_SocketIODelegate() override { + CC_LOG_INFO("In the destructor of JSB_SocketIODelegate(%p)", this); + } + + void onConnect(cc::network::SIOClient * /*client*/) override { + } + + void onMessage(cc::network::SIOClient * /*client*/, const ccstd::string & /*data*/) override { + } + + void onClose(cc::network::SIOClient *client) override { // NOLINT + CC_LOG_DEBUG("JSB SocketIO::SIODelegate->onClose method called from native"); + this->fireEventToScript(client, "disconnect", ""); + + se::NativePtrToObjectMap::forEach(client, [](se::Object *obj) { + obj->unroot(); + }); + + if (getRefCount() == 1) { + cc::DeferredReleasePool::add(this); + } else { + release(); + } + } + + void onError(cc::network::SIOClient *client, const ccstd::string &data) override { // NOLINT + CC_LOG_DEBUG("JSB SocketIO::SIODelegate->onError method called from native with data: %s", data.c_str()); + this->fireEventToScript(client, "error", data); + + se::NativePtrToObjectMap::forEach(client, [](se::Object *obj) { + obj->unroot(); + }); + } + + void fireEventToScript(cc::network::SIOClient *client, const ccstd::string &eventName, const ccstd::string &data) override { // NOLINT + CC_LOG_DEBUG("JSB SocketIO::SIODelegate->fireEventToScript method called from native with name '%s' data: %s", eventName.c_str(), data.c_str()); + + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + if (!CC_CURRENT_APPLICATION()) { + return; + } + + if (!se::NativePtrToObjectMap::contains(client)) { // IDEA: client probably be a new value with the same address as the old one, it may cause undefined result. + return; + } + + se::Value dataVal; + if (data.empty()) { + dataVal.setNull(); + } else { + dataVal.setString(data); + } + + auto it = _eventRegistry.find(eventName); + + if (it != _eventRegistry.end()) { + const se::ValueArray &cbStruct = it->second; + CC_ASSERT(cbStruct.size() == 2); + const se::Value &callback = cbStruct[0]; + const se::Value &target = cbStruct[1]; + if (callback.isObject() && callback.toObject()->isFunction() && target.isObject()) { + se::ValueArray args; + args.push_back(dataVal); + callback.toObject()->call(args, target.toObject()); + } + } + + if (eventName == "disconnect") { + CC_LOG_DEBUG("disconnect ... "); // IDEA: + } + } + + void addEvent(const ccstd::string &eventName, const se::Value &callback, const se::Value &target) { + CC_ASSERT(callback.isObject() && callback.toObject()->isFunction()); + CC_ASSERT(target.isObject()); + _eventRegistry[eventName].clear(); + _eventRegistry[eventName].push_back(callback); + _eventRegistry[eventName].push_back(target); + target.toObject()->attachObject(callback.toObject()); + } + +private: + JSB_SIOCallbackRegistry _eventRegistry; +}; + +static bool SocketIO_finalize(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + CC_LOG_INFO("jsbindings: finalizing JS object %p (SocketIO)", cobj); + cobj->disconnect(); + auto *delegate = static_cast(cobj->getDelegate()); + if (delegate->getRefCount() == 1) { + cc::DeferredReleasePool::add(delegate); + } else { + delegate->release(); + } + return true; +} +SE_BIND_FINALIZE_FUNC(SocketIO_finalize) // NOLINT(readability-identifier-naming) + +static bool SocketIO_prop_getTag(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + s.rval().setString(cobj->getTag()); + return true; +} +SE_BIND_PROP_GET(SocketIO_prop_getTag) // NOLINT(readability-identifier-naming) + +static bool SocketIO_prop_setTag(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + cobj->setTag(s.args()[0].toString().c_str()); + return true; +} +SE_BIND_PROP_SET(SocketIO_prop_setTag) // NOLINT(readability-identifier-naming) + +static bool SocketIO_send(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + auto *cobj = static_cast(s.nativeThisObject()); + + if (argc == 1) { + ccstd::string payload; + bool ok = sevalue_to_native(args[0], &payload); + SE_PRECONDITION2(ok, false, "Converting payload failed!"); + + cobj->send(payload); + return true; + } + + SE_REPORT_ERROR("Wrong number of arguments: %d, expected: %d", argc, 1); + return false; +} +SE_BIND_FUNC(SocketIO_send) // NOLINT(readability-identifier-naming) + +static bool SocketIO_emit(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + auto *cobj = static_cast(s.nativeThisObject()); + + if (argc >= 1) { + bool ok = false; + ccstd::string eventName; + ok = sevalue_to_native(args[0], &eventName); + SE_PRECONDITION2(ok, false, "Converting eventName failed!"); + + ccstd::string payload; + if (argc >= 2) { + const auto &arg1 = args[1]; + // Add this check to make it compatible with old version. + // jsval_to_std_string in v1.6 returns empty string if arg1 is null or undefined + // while seval_to_std_string since 1.7.2 follows JS standard to return "null" or "undefined". + // Therefore, we need a workaround to make it be compatible with versions lower than v1.7. + if (!arg1.isNullOrUndefined()) { + ok = sevalue_to_native(arg1, &payload); + SE_PRECONDITION2(ok, false, "Converting payload failed!"); + } + } + + cobj->emit(eventName, payload); + return true; + } + + SE_REPORT_ERROR("Wrong number of arguments: %d, expected: %d", argc, 2); + return false; +} +SE_BIND_FUNC(SocketIO_emit) // NOLINT(readability-identifier-naming) + +static bool SocketIO_disconnect(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + auto *cobj = static_cast(s.nativeThisObject()); + + if (argc == 0) { + cobj->disconnect(); + return true; + } + + SE_REPORT_ERROR("Wrong number of arguments: %d, expected: %d", argc, 0); + return false; +} +SE_BIND_FUNC(SocketIO_disconnect) // NOLINT(readability-identifier-naming) + +static bool SocketIO_on(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + auto *cobj = static_cast(s.nativeThisObject()); + + if (argc == 2) { + bool ok = false; + ccstd::string eventName; + ok = sevalue_to_native(args[0], &eventName); + SE_PRECONDITION2(ok, false, "Converting eventName failed!"); + + CC_LOG_DEBUG("JSB SocketIO eventName to: '%s'", eventName.c_str()); + + (static_cast(cobj->getDelegate()))->addEvent(eventName, args[1], se::Value(s.thisObject())); + return true; + } + + SE_REPORT_ERROR("Wrong number of arguments: %d, expected: %d", argc, 2); + return false; +} +SE_BIND_FUNC(SocketIO_on) // NOLINT(readability-identifier-naming) + +// static +static bool SocketIO_connect(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + CC_LOG_DEBUG("JSB SocketIO.connect method called"); + + if (argc >= 1 && argc <= 3) { + ccstd::string url; + ccstd::string caFilePath; + bool ok = false; + + ok = sevalue_to_native(args[0], &url); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + if (argc == 2) { + if (args[1].isObject()) { + // Just ignore the option argument + } else if (args[1].isString()) { + // Assume it's CA root file path + ok = sevalue_to_native(args[1], &caFilePath); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + } + } + + if (argc == 3) { + // Just ignore the option argument + + if (args[2].isString()) { + // Assume it's CA root file path + ok = sevalue_to_native(args[2], &caFilePath); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + } + } + + auto *siodelegate = ccnew JSB_SocketIODelegate(); + + CC_LOG_DEBUG("Calling native SocketIO.connect method"); + cc::network::SIOClient *ret = cc::network::SocketIO::connect(url, *siodelegate, caFilePath); + if (ret != nullptr) { + ret->addRef(); + siodelegate->addRef(); + + se::Object *obj = se::Object::createObjectWithClass(__jsb_SocketIO_class); + obj->setPrivateData(ret); + + s.rval().setObject(obj); + obj->root(); + + return true; + } + siodelegate->release(); + SE_REPORT_ERROR("SocketIO.connect return nullptr!"); + return false; + } + SE_REPORT_ERROR("JSB SocketIO.connect: Wrong number of arguments"); + return false; +} +SE_BIND_FUNC(SocketIO_connect) // NOLINT(readability-identifier-naming) + +// static +static bool SocketIO_close(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc == 0) { + return true; + } + + SE_REPORT_ERROR("Wrong number of arguments: %d, expected: %d", argc, 0); + return false; +} +SE_BIND_FUNC(SocketIO_close) // NOLINT(readability-identifier-naming) + +bool register_all_socketio(se::Object *global) { + se::Value nsVal; + if (!global->getProperty("jsb", &nsVal, true)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + global->setProperty("jsb", nsVal); + } + se::Object *ns = nsVal.toObject(); + se::Class *cls = se::Class::create("SocketIO", ns, nullptr, nullptr); + cls->defineFinalizeFunction(_SE(SocketIO_finalize)); + + cls->defineProperty("tag", _SE(SocketIO_prop_getTag), _SE(SocketIO_prop_setTag)); + + cls->defineFunction("send", _SE(SocketIO_send)); + cls->defineFunction("emit", _SE(SocketIO_emit)); + cls->defineFunction("disconnect", _SE(SocketIO_disconnect)); + cls->defineFunction("on", _SE(SocketIO_on)); + + cls->install(); + + JSBClassType::registerClass(cls); + + se::Value ctorVal; + ns->getProperty("SocketIO", &ctorVal); + ctorVal.toObject()->defineFunction("connect", _SE(SocketIO_connect)); + ctorVal.toObject()->defineFunction("close", _SE(SocketIO_close)); + + __jsb_SocketIO_class = cls; + + se::ScriptEngine::getInstance()->clearException(); + return true; +} diff --git a/cocos/bindings/manual/jsb_socketio.h b/cocos/bindings/manual/jsb_socketio.h new file mode 100644 index 0000000..bcad0a9 --- /dev/null +++ b/cocos/bindings/manual/jsb_socketio.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { +class Object; +} + +bool register_all_socketio(se::Object *obj); diff --git a/cocos/bindings/manual/jsb_spine_manual.cpp b/cocos/bindings/manual/jsb_spine_manual.cpp new file mode 100644 index 0000000..4bc5f5f --- /dev/null +++ b/cocos/bindings/manual/jsb_spine_manual.cpp @@ -0,0 +1,609 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_spine_manual.h" +#include "base/Data.h" +#include "base/memory/Memory.h" +#include "bindings/auto/jsb_spine_auto.h" +#include "bindings/jswrapper/SeApi.h" +#include "bindings/manual/jsb_conversions.h" +#include "bindings/manual/jsb_global.h" +#include "bindings/manual/jsb_helper.h" +#include "editor-support/spine-creator-support/spine-cocos2dx.h" +#include "editor-support/spine/spine.h" +#include "middleware-adapter.h" +#include "platform/FileUtils.h" +#include "spine-creator-support/SkeletonDataMgr.h" +#include "spine-creator-support/SkeletonRenderer.h" +#include "spine-creator-support/spine-cocos2dx.h" +#include "spine-creator-support/Vector2.h" + +using namespace cc; + +static spine::Cocos2dTextureLoader textureLoader; +static cc::RefMap *_preloadedAtlasTextures = nullptr; +static middleware::Texture2D *_getPreloadedAtlasTexture(const char *path) { + CC_ASSERT(_preloadedAtlasTextures); + auto it = _preloadedAtlasTextures->find(path); + return it != _preloadedAtlasTextures->end() ? it->second : nullptr; +} + +static bool js_register_spine_initSkeletonData(se::State &s) { + const auto &args = s.args(); + int argc = (int)args.size(); + if (argc != 5) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 5); + return false; + } + bool ok = false; + + ccstd::string uuid; + ok = sevalue_to_native(args[0], &uuid); + SE_PRECONDITION2(ok, false, "Invalid uuid content!"); + + auto mgr = spine::SkeletonDataMgr::getInstance(); + bool hasSkeletonData = mgr->hasSkeletonData(uuid); + if (hasSkeletonData) { + spine::SkeletonData *skeletonData = mgr->retainByUUID(uuid); + native_ptr_to_seval(skeletonData, &s.rval()); + return true; + } + + ccstd::string skeletonDataFile; + ok = sevalue_to_native(args[1], &skeletonDataFile); + SE_PRECONDITION2(ok, false, "Invalid json path!"); + + ccstd::string atlasText; + ok = sevalue_to_native(args[2], &atlasText); + SE_PRECONDITION2(ok, false, "Invalid atlas content!"); + + cc::RefMap textures; + ok = seval_to_Map_string_key(args[3], &textures); + SE_PRECONDITION2(ok, false, "Invalid textures!"); + + float scale = 1.0f; + ok = sevalue_to_native(args[4], &scale); + SE_PRECONDITION2(ok, false, "Invalid scale!"); + + // create atlas from preloaded texture + + _preloadedAtlasTextures = &textures; + spine::spAtlasPage_setCustomTextureLoader(_getPreloadedAtlasTexture); + + spine::Atlas *atlas = ccnew_placement(__FILE__, __LINE__) spine::Atlas(atlasText.c_str(), (int)atlasText.size(), "", &textureLoader); + + _preloadedAtlasTextures = nullptr; + spine::spAtlasPage_setCustomTextureLoader(nullptr); + + spine::AttachmentLoader *attachmentLoader = ccnew_placement(__FILE__, __LINE__) spine::Cocos2dAtlasAttachmentLoader(atlas); + spine::SkeletonData *skeletonData = nullptr; + + std::size_t length = skeletonDataFile.length(); + auto binPos = skeletonDataFile.find(".skel", length - 5); + if (binPos == ccstd::string::npos) binPos = skeletonDataFile.find(".bin", length - 4); + + if (binPos != ccstd::string::npos) { + auto fileUtils = cc::FileUtils::getInstance(); + if (fileUtils->isFileExist(skeletonDataFile)) { + cc::Data cocos2dData; + const auto fullpath = fileUtils->fullPathForFilename(skeletonDataFile); + fileUtils->getContents(fullpath, &cocos2dData); + + spine::SkeletonBinary binary(attachmentLoader); + binary.setScale(scale); + skeletonData = binary.readSkeletonData(cocos2dData.getBytes(), (int)cocos2dData.getSize()); + CC_ASSERT(skeletonData); // Can use binary.getError() to get error message. + } + } else { + spine::SkeletonJson json(attachmentLoader); + json.setScale(scale); + skeletonData = json.readSkeletonData(skeletonDataFile.c_str()); + CC_ASSERT(skeletonData); // Can use json.getError() to get error message. + } + + if (skeletonData) { + ccstd::vector texturesIndex; + for (auto it = textures.begin(); it != textures.end(); it++) { + texturesIndex.push_back(it->second->getRealTextureIndex()); + } + mgr->setSkeletonData(uuid, skeletonData, atlas, attachmentLoader, texturesIndex); + native_ptr_to_seval(skeletonData, &s.rval()); + } else { + if (atlas) { + delete atlas; + atlas = nullptr; + } + if (attachmentLoader) { + delete attachmentLoader; + attachmentLoader = nullptr; + } + } + return true; +} +SE_BIND_FUNC(js_register_spine_initSkeletonData) + +static bool js_register_spine_disposeSkeletonData(se::State &s) { + const auto &args = s.args(); + int argc = (int)args.size(); + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 5); + return false; + } + bool ok = false; + + ccstd::string uuid; + ok = sevalue_to_native(args[0], &uuid); + SE_PRECONDITION2(ok, false, "Invalid uuid content!"); + + auto mgr = spine::SkeletonDataMgr::getInstance(); + bool hasSkeletonData = mgr->hasSkeletonData(uuid); + if (!hasSkeletonData) return true; + mgr->releaseByUUID(uuid); + return true; +} +SE_BIND_FUNC(js_register_spine_disposeSkeletonData) + +static bool js_register_spine_initSkeletonRenderer(se::State &s) { + // renderer, jsonPath, atlasText, textures, scale + const auto &args = s.args(); + int argc = (int)args.size(); + if (argc != 2) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 5); + return false; + } + bool ok = false; + + spine::SkeletonRenderer *node = nullptr; + ok = seval_to_native_ptr(args[0], &node); + SE_PRECONDITION2(ok, false, "Converting SpineRenderer failed!"); + + ccstd::string uuid; + ok = sevalue_to_native(args[1], &uuid); + SE_PRECONDITION2(ok, false, "Invalid uuid content!"); + + auto mgr = spine::SkeletonDataMgr::getInstance(); + bool hasSkeletonData = mgr->hasSkeletonData(uuid); + if (hasSkeletonData) { + node->initWithUUID(uuid); + } + return true; +} +SE_BIND_FUNC(js_register_spine_initSkeletonRenderer) + +static bool js_register_spine_retainSkeletonData(se::State &s) { + const auto &args = s.args(); + int argc = (int)args.size(); + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", argc, 1); + return false; + } + bool ok = false; + + ccstd::string uuid; + ok = sevalue_to_native(args[0], &uuid); + SE_PRECONDITION2(ok, false, "Invalid uuid content!"); + + auto mgr = spine::SkeletonDataMgr::getInstance(); + bool hasSkeletonData = mgr->hasSkeletonData(uuid); + if (hasSkeletonData) { + spine::SkeletonData *skeletonData = mgr->retainByUUID(uuid); + native_ptr_to_seval(skeletonData, &s.rval()); + } + return true; +} +SE_BIND_FUNC(js_register_spine_retainSkeletonData) + +static bool js_VertexAttachment_computeWorldVertices(se::State &s) { + const auto &args = s.args(); + + spine::VertexAttachment *vertexAttachment = SE_THIS_OBJECT(s); + if (nullptr == vertexAttachment) return true; + + spine::Slot *slot = nullptr; + size_t start = 0, count = 0, offset = 0, stride = 0; + se::Value worldVerticesVal; + + bool ok = false; + ok = sevalue_to_native(args[0], &slot, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing slot"); + + ok = sevalue_to_native(args[1], &start, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing start"); + + ok = sevalue_to_native(args[2], &worldVerticesVal, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing vertices"); + + ok = sevalue_to_native(args[3], &count, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing count"); + + ok = sevalue_to_native(args[4], &offset, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing offset"); + + ok = sevalue_to_native(args[5], &stride, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing stride"); + + if (worldVerticesVal.toObject()->isTypedArray()) { + uint8_t* ptr = nullptr; + size_t len = 0; + worldVerticesVal.toObject()->getTypedArrayData(&ptr, &len); + vertexAttachment->computeWorldVertices(*slot, start, count, reinterpret_cast(ptr), offset, stride); + } else if (worldVerticesVal.toObject()->isArray()) { + spine::Vector worldVertices; + worldVertices.ensureCapacity(count); + vertexAttachment->computeWorldVertices(*slot, start, count, worldVertices, 0); + + int tCount = offset + (count >> 1) * stride; + + for (size_t i = offset, t = 0; i < tCount; i += stride, t += 2) { + worldVerticesVal.toObject()->setArrayElement(i, se::Value(worldVertices[t])); + worldVerticesVal.toObject()->setArrayElement(i + 1, se::Value(worldVertices[t + 1])); + } + } + return true; +} +SE_BIND_FUNC(js_VertexAttachment_computeWorldVertices) + +static bool js_RegionAttachment_computeWorldVertices(se::State &s) { + const auto &args = s.args(); + + spine::RegionAttachment *regionAttachment = SE_THIS_OBJECT(s); + if (nullptr == regionAttachment) return true; + + spine::Bone *bone = nullptr; + size_t offset = 0, stride = 0; + se::Value worldVerticesVal; + + bool ok = false; + ok = sevalue_to_native(args[0], &bone, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + ok = sevalue_to_native(args[1], &worldVerticesVal, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + ok = sevalue_to_native(args[2], &offset, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + ok = sevalue_to_native(args[3], &stride, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + if (worldVerticesVal.toObject()->isTypedArray()) { + uint8_t* ptr = nullptr; + size_t len = 0; + worldVerticesVal.toObject()->getTypedArrayData(&ptr, &len); + regionAttachment->computeWorldVertices(*bone, reinterpret_cast(ptr), offset, stride); + } else if (worldVerticesVal.toObject()->isArray()) { + spine::Vector worldVertices; + int count = 8; + worldVertices.ensureCapacity(count); + regionAttachment->computeWorldVertices(*bone, worldVertices, 0); + + int curr = offset; + worldVerticesVal.toObject()->setArrayElement(curr, se::Value(worldVertices[0])); + worldVerticesVal.toObject()->setArrayElement(curr + 1, se::Value(worldVertices[1])); + + curr += stride; + worldVerticesVal.toObject()->setArrayElement(curr, se::Value(worldVertices[2])); + worldVerticesVal.toObject()->setArrayElement(curr + 1, se::Value(worldVertices[3])); + + curr += stride; + worldVerticesVal.toObject()->setArrayElement(curr, se::Value(worldVertices[4])); + worldVerticesVal.toObject()->setArrayElement(curr + 1, se::Value(worldVertices[5])); + + curr += stride; + worldVerticesVal.toObject()->setArrayElement(curr, se::Value(worldVertices[6])); + worldVerticesVal.toObject()->setArrayElement(curr + 1, se::Value(worldVertices[7])); + } + return true; +} +SE_BIND_FUNC(js_RegionAttachment_computeWorldVertices) + +static bool js_Skeleton_getBounds(se::State &s) { + const auto &args = s.args(); + spine::Skeleton* skeleton = SE_THIS_OBJECT(s); + if (nullptr == skeleton) return true; + + se::Value temp; + + bool ok = false; + ok = sevalue_to_native(args[2], &temp, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + { + float offx = 0.F, offy = 0.F, sizex = 0.F, sizey = 0.F; + spine::Vector outVertexBuffer; + skeleton->getBounds(offx, offy, sizex, sizey, outVertexBuffer); + args[0].toObject()->setProperty("x", se::Value(offx)); + args[0].toObject()->setProperty("y", se::Value(offy)); + args[1].toObject()->setProperty("x", se::Value(sizex)); + args[1].toObject()->setProperty("y", se::Value(sizey)); + if (temp.isObject()) { + for (int i = 0; i < outVertexBuffer.size(); ++i) { + temp.toObject()->setArrayElement(i, se::Value(outVertexBuffer[i])); + } + } + } + return true; +} +SE_BIND_FUNC(js_Skeleton_getBounds) + +static bool js_Bone_worldToLocal(se::State &s) { + const auto &args = s.args(); + spine::Bone* bone = SE_THIS_OBJECT(s); + if (nullptr == bone) return true; + + spine::Vector2 world(0, 0); + + bool ok = false; + ok = sevalue_to_native(args[0], &world, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + float outX = 0.F, outY = 0.F; + bone->worldToLocal(world.x, world.y, outX, outY); + + spine::Vector2 outNative(outX, outY); + se::Value ret; + nativevalue_to_se(outNative, ret, s.thisObject()); + s.rval().setObject(ret.toObject()); + return true; +} +SE_BIND_FUNC(js_Bone_worldToLocal) + +static bool js_Bone_localToWorld(se::State &s) { + const auto &args = s.args(); + spine::Bone* bone = SE_THIS_OBJECT(s); + if (nullptr == bone) return true; + + spine::Vector2 local(0, 0); + + bool ok = false; + ok = sevalue_to_native(args[0], &local, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + float outX = 0.F, outY = 0.F; + bone->localToWorld(local.x, local.y, outX, outY); + + spine::Vector2 outNative(outX, outY); + se::Value ret; + nativevalue_to_se(outNative, ret, s.thisObject()); + s.rval().setObject(ret.toObject()); + return true; +} +SE_BIND_FUNC(js_Bone_localToWorld) + +static bool js_PointAttachment_computeWorldPosition(se::State &s) { + const auto &args = s.args(); + spine::PointAttachment* pointAttachment = SE_THIS_OBJECT(s); + if (nullptr == pointAttachment) return true; + + spine::Bone* bone = nullptr; + + bool ok = false; + ok = sevalue_to_native(args[0], &bone, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + float outX = 0.F, outY = 0.F; + pointAttachment->computeWorldPosition(*bone, outX, outY); + + spine::Vector2 outNative(outX, outY); + se::Value ret; + nativevalue_to_se(outNative, ret, s.thisObject()); + s.rval().setObject(ret.toObject()); + return true; +} +SE_BIND_FUNC(js_PointAttachment_computeWorldPosition) + +static bool js_Skin_findAttachmentsForSlot(se::State &s) { + const auto &args = s.args(); + spine::Skin* skin = SE_THIS_OBJECT(s); + if (nullptr == skin) return true; + + size_t slotIndex = 0; + se::Value attachmentsVal; + + bool ok = false; + ok = sevalue_to_native(args[0], &slotIndex, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + attachmentsVal = args[1]; + ok = attachmentsVal.isObject(); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + spine::Skin::AttachmentMap::Entries entries = skin->getAttachments(); + uint32_t index = 0; + while (entries.hasNext()) { + spine::Skin::AttachmentMap::Entry &entry = entries.next(); + if (entry._slotIndex == slotIndex) { + se::Value entryVal; + ok = nativevalue_to_se(&entry, entryVal); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + attachmentsVal.toObject()->setArrayElement(index++, entryVal); + } + } + return true; +} +SE_BIND_FUNC(js_Skin_findAttachmentsForSlot) + +static bool js_VertexEffect_transform(se::State &s) { + const auto &args = s.args(); + spine::VertexEffect* effect = SE_THIS_OBJECT(s); + if (nullptr == effect) return true; + + float outX = 0.F, outY = 0.F; + effect->transform(outX, outY); + + args[0].toObject()->setProperty("x", se::Value(outX)); + args[0].toObject()->setProperty("y", se::Value(outY)); + return true; +} +SE_BIND_FUNC(js_VertexEffect_transform) + +static bool js_SwirlVertexEffect_transform(se::State &s) { + const auto &args = s.args(); + spine::SwirlVertexEffect* effect = SE_THIS_OBJECT(s); + if (nullptr == effect) return true; + + float outX = 0.F, outY = 0.F; + effect->transform(outX, outY); + + args[0].toObject()->setProperty("x", se::Value(outX)); + args[0].toObject()->setProperty("y", se::Value(outY)); + return true; +} +SE_BIND_FUNC(js_SwirlVertexEffect_transform) + +static bool js_JitterVertexEffect_transform(se::State &s) { + const auto &args = s.args(); + spine::JitterVertexEffect* effect = SE_THIS_OBJECT(s); + if (nullptr == effect) return true; + + float outX = 0.F, outY = 0.F; + effect->transform(outX, outY); + + args[0].toObject()->setProperty("x", se::Value(outX)); + args[0].toObject()->setProperty("y", se::Value(outY)); + return true; +} +SE_BIND_FUNC(js_JitterVertexEffect_transform) + +static bool js_spine_Skin_getAttachments(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + size_t argc = args.size(); + spine::Skin *skin = (spine::Skin *) NULL ; + + if(argc != 0) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 0); + return false; + } + skin = SE_THIS_OBJECT(s); + if (nullptr == skin) return true; + spine::Skin::AttachmentMap::Entries attachments = skin->getAttachments(); + + std::vector entries; + while (attachments.hasNext()) { + spine::Skin::AttachmentMap::Entry &entry = attachments.next(); + se::Value entryVal; + ok = nativevalue_to_se(&entry, entryVal); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + entries.push_back(entryVal); + } + + se::HandleObject array(se::Object::createArrayObject(entries.size())); + for (int i = 0; i < entries.size(); ++i) { + array->setArrayElement(i, entries[i]); + } + s.rval().setObject(array); + + return true; +} +SE_BIND_FUNC(js_spine_Skin_getAttachments) + +static bool js_spine_Slot_setAttachment(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + spine::Slot *slot = (spine::Slot *) NULL ; + + slot = SE_THIS_OBJECT(s); + if (nullptr == slot) return true; + + spine::Attachment* attachment = nullptr; + + ok = sevalue_to_native(args[0], &attachment, s.thisObject()); + SE_PRECONDITION2(ok, false, "Error processing arguments"); + + slot->setAttachment(attachment); + + return true; +} +SE_BIND_FUNC(js_spine_Slot_setAttachment) + +static bool js_spine_Slot_getAttachment(se::State& s) { + CC_UNUSED bool ok = true; + const auto& args = s.args(); + spine::Slot *slot = (spine::Slot *) NULL ; + + slot = SE_THIS_OBJECT(s); + if (nullptr == slot) return true; + + spine::Attachment *attachment = slot->getAttachment(); + if (attachment) { + nativevalue_to_se(attachment, s.rval(), s.thisObject()); + return true; + } + return false; +} +SE_BIND_FUNC(js_spine_Slot_getAttachment) + +bool register_all_spine_manual(se::Object *obj) { + // Get the ns + se::Value nsVal; + if (!obj->getProperty("spine", &nsVal)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + obj->setProperty("spine", nsVal); + } + se::Object *ns = nsVal.toObject(); + + ns->defineFunction("initSkeletonRenderer", _SE(js_register_spine_initSkeletonRenderer)); + ns->defineFunction("initSkeletonData", _SE(js_register_spine_initSkeletonData)); + ns->defineFunction("retainSkeletonData", _SE(js_register_spine_retainSkeletonData)); + ns->defineFunction("disposeSkeletonData", _SE(js_register_spine_disposeSkeletonData)); + + __jsb_spine_VertexAttachment_proto->defineFunction("computeWorldVertices", _SE(js_VertexAttachment_computeWorldVertices)); + __jsb_spine_RegionAttachment_proto->defineFunction("computeWorldVertices", _SE(js_RegionAttachment_computeWorldVertices)); + __jsb_spine_Skeleton_proto->defineFunction("getBounds", _SE(js_Skeleton_getBounds)); + __jsb_spine_Skin_proto->defineFunction("getAttachmentsForSlot", _SE(js_Skin_findAttachmentsForSlot)); + __jsb_spine_Bone_proto->defineFunction("worldToLocal", _SE(js_Bone_worldToLocal)); + __jsb_spine_Bone_proto->defineFunction("localToWorld", _SE(js_Bone_localToWorld)); + __jsb_spine_PointAttachment_proto->defineFunction("computeWorldPosition", _SE(js_PointAttachment_computeWorldPosition)); + __jsb_spine_VertexEffect_proto->defineFunction("transform", _SE(js_VertexEffect_transform)); + __jsb_spine_SwirlVertexEffect_proto->defineFunction("transform", _SE(js_SwirlVertexEffect_transform)); + __jsb_spine_JitterVertexEffect_proto->defineFunction("transform", _SE(js_JitterVertexEffect_transform)); + __jsb_spine_Skin_proto->defineFunction("getAttachments", _SE(js_spine_Skin_getAttachments)); + __jsb_spine_Slot_proto->defineFunction("setAttachment", _SE(js_spine_Slot_setAttachment)); + __jsb_spine_Slot_proto->defineFunction("getAttachment", _SE(js_spine_Slot_getAttachment)); + + spine::setSpineObjectDisposeCallback([](void *spineObj) { + if (!se::NativePtrToObjectMap::isValid()) { + return; + } + // Support Native Spine fo Creator V3.0 + se::NativePtrToObjectMap::forEach(spineObj, [](se::Object *seObj) { + // Unmap native and js object since native object was destroyed. + // Otherwise, it may trigger 'assertion' in se::Object::setPrivateData later + // since native obj is already released and the new native object may be assigned with + // the same address. + seObj->setClearMappingInFinalizer(false); + }); + se::NativePtrToObjectMap::erase(spineObj); + }); + + se::ScriptEngine::getInstance()->addBeforeCleanupHook([]() { + spine::SkeletonDataMgr::destroyInstance(); + }); + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} diff --git a/cocos/bindings/manual/jsb_spine_manual.h b/cocos/bindings/manual/jsb_spine_manual.h new file mode 100644 index 0000000..a13bf53 --- /dev/null +++ b/cocos/bindings/manual/jsb_spine_manual.h @@ -0,0 +1,32 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "cocos/bindings/jswrapper/SeApi.h" + +namespace se { +class Object; +} + +bool register_all_spine_manual(se::Object *obj); diff --git a/cocos/bindings/manual/jsb_websocket.cpp b/cocos/bindings/manual/jsb_websocket.cpp new file mode 100644 index 0000000..a96625b --- /dev/null +++ b/cocos/bindings/manual/jsb_websocket.cpp @@ -0,0 +1,566 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_websocket.h" +#include "MappingUtils.h" +#include "base/std/container/unordered_set.h" +#include "cocos/base/DeferredReleasePool.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" + +#include "application/ApplicationManager.h" +#include "base/UTF8.h" + +/* + [Constructor(in DOMString url, in optional DOMString protocols)] + [Constructor(in DOMString url, in optional DOMString[] protocols)] + interface WebSocket { + readonly attribute DOMString url; + + // ready state + const unsigned short CONNECTING = 0; + const unsigned short OPEN = 1; + const unsigned short CLOSING = 2; + const unsigned short CLOSED = 3; + readonly attribute unsigned short readyState; + readonly attribute unsigned long bufferedAmount; + + // networking + attribute Function onopen; + attribute Function onmessage; + attribute Function onerror; + attribute Function onclose; + readonly attribute DOMString protocol; + void send(in DOMString data); + void close(); + }; + WebSocket implements EventTarget; + */ + +#define GET_DELEGATE_FN(name) \ + _JSDelegate.isObject() && _JSDelegate.toObject()->getProperty(name, &func) + +namespace { +se::Class *jsbWebSocketClass = nullptr; +ccstd::unordered_set jsbWebSocketDelegates; +} // namespace + +JsbWebSocketDelegate::JsbWebSocketDelegate() { + jsbWebSocketDelegates.insert(this); +} +JsbWebSocketDelegate::~JsbWebSocketDelegate() { + CC_LOG_INFO("In the destructor of JSbWebSocketDelegate(%p)", this); + jsbWebSocketDelegates.erase(this); +} + +void JsbWebSocketDelegate::onOpen(cc::network::WebSocket *ws) { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + if (CC_CURRENT_APPLICATION() == nullptr) { + return; + } + + se::Object *wsObj = se::NativePtrToObjectMap::findFirst(ws); + if (!wsObj) { + return; + } + + wsObj->setProperty("protocol", se::Value(ws->getProtocol())); + + se::HandleObject jsObj(se::Object::createPlainObject()); + jsObj->setProperty("type", se::Value("open")); + se::Value target; + native_ptr_to_seval(ws, &target); + jsObj->setProperty("target", target); + + se::Value func; + bool ok = GET_DELEGATE_FN("onopen"); + if (ok && func.isObject() && func.toObject()->isFunction()) { + se::ValueArray args; + args.push_back(se::Value(jsObj)); + func.toObject()->call(args, wsObj); + } else { + SE_REPORT_ERROR("Can't get onopen function!"); + } +} + +void JsbWebSocketDelegate::onMessage(cc::network::WebSocket *ws, const cc::network::WebSocket::Data &data) { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + if (CC_CURRENT_APPLICATION() == nullptr) { + return; + } + + se::Object *wsObj = se::NativePtrToObjectMap::findFirst(ws); + if (!wsObj) { + return; + } + se::HandleObject jsObj(se::Object::createPlainObject()); + jsObj->setProperty("type", se::Value("message")); + se::Value target; + native_ptr_to_seval(ws, &target); + jsObj->setProperty("target", target); + + se::Value func; + bool ok = GET_DELEGATE_FN("onmessage"); + if (ok && func.isObject() && func.toObject()->isFunction()) { + se::ValueArray args; + args.push_back(se::Value(jsObj)); + + if (data.isBinary) { + se::HandleObject dataObj(se::Object::createArrayBufferObject(data.bytes, data.len)); + jsObj->setProperty("data", se::Value(dataObj)); + } else { + se::Value dataVal; + if (strlen(data.bytes) == 0 && data.len > 0) { // String with 0x00 prefix + ccstd::string str(data.bytes, data.len); + dataVal.setString(str); + } else { // Normal string + dataVal.setString(ccstd::string(data.bytes, data.len)); + } + + if (dataVal.isNullOrUndefined()) { + ws->closeAsync(); + } else { + jsObj->setProperty("data", se::Value(dataVal)); + } + } + + func.toObject()->call(args, wsObj); + } else { + SE_REPORT_ERROR("Can't get onmessage function!"); + } +} + +void JsbWebSocketDelegate::onClose(cc::network::WebSocket *ws, uint16_t code, const ccstd::string &reason, bool wasClean) { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + if (CC_CURRENT_APPLICATION() == nullptr) { + return; + } + + se::Object *wsObj = se::NativePtrToObjectMap::findFirst(ws); + do { + if (!wsObj) { + CC_LOG_INFO("WebSocket js instance was destroyted, don't need to invoke onclose callback!"); + break; + } + + se::HandleObject jsObj(se::Object::createPlainObject()); + jsObj->setProperty("type", se::Value("close")); // deprecated since v3.6 + se::Value target; + native_ptr_to_seval(ws, &target); + jsObj->setProperty("target", target); // deprecated since v3.6 + + // CloseEvent attributes + jsObj->setProperty("code", se::Value(code)); + jsObj->setProperty("reason", se::Value(reason)); + jsObj->setProperty("wasClean", se::Value(wasClean)); + + se::Value func; + bool ok = GET_DELEGATE_FN("onclose"); + if (ok && func.isObject() && func.toObject()->isFunction()) { + se::ValueArray args; + args.push_back(se::Value(jsObj)); + func.toObject()->call(args, wsObj); + } else { + SE_REPORT_ERROR("Can't get onclose function!"); + } + + // JS Websocket object now can be GC, since the connection is closed. + wsObj->unroot(); + if (_JSDelegate.isObject()) { + _JSDelegate.toObject()->unroot(); + } + + // Websocket instance is attached to global object in 'WebSocket_close' + // It's safe to detach it here since JS 'onclose' method has been already invoked. + se::ScriptEngine::getInstance()->getGlobalObject()->detachObject(wsObj); + + } while (false); + + ws->release(); + release(); // Release delegate self at last +} + +void JsbWebSocketDelegate::onError(cc::network::WebSocket *ws, const cc::network::WebSocket::ErrorCode & /*error*/) { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + if (CC_CURRENT_APPLICATION() == nullptr) { + return; + } + + se::Object *wsObj = se::NativePtrToObjectMap::findFirst(ws); + if (!wsObj) { + return; + } + se::HandleObject jsObj(se::Object::createPlainObject()); + jsObj->setProperty("type", se::Value("error")); + se::Value target; + native_ptr_to_seval(ws, &target); + jsObj->setProperty("target", target); + + se::Value func; + bool ok = GET_DELEGATE_FN("onerror"); + if (ok && func.isObject() && func.toObject()->isFunction()) { + se::ValueArray args; + args.push_back(se::Value(jsObj)); + func.toObject()->call(args, wsObj); + } else { + SE_REPORT_ERROR("Can't get onerror function!"); + } +} + +void JsbWebSocketDelegate::setJSDelegate(const se::Value &jsDelegate) { + CC_ASSERT(jsDelegate.isObject()); + _JSDelegate = jsDelegate; + se::ScriptEngine::getInstance()->addBeforeCleanupHook([this]() { + if (jsbWebSocketDelegates.find(this) != jsbWebSocketDelegates.end()) { + _JSDelegate.setUndefined(); + } + }); +} + +static bool webSocketFinalize(se::State &s) { + auto *cobj = static_cast(s.nativeThisObject()); + CC_LOG_INFO("jsbindings: finalizing JS object %p (WebSocket)", cobj); + + // Manually close if web socket is not closed + if (cobj->getReadyState() != cc::network::WebSocket::State::CLOSED) { + CC_LOG_INFO("WebSocket (%p) isn't closed, try to close it!", cobj); + cobj->closeAsync(); + } + + static_cast(cobj->getDelegate())->release(); + return true; +} +SE_BIND_FINALIZE_FUNC(webSocketFinalize) + +static bool webSocketConstructor(se::State &s) { + const auto &args = s.args(); + int argc = static_cast(args.size()); + + if (argc == 1 || argc == 2 || argc == 3) { + ccstd::string url; + + bool ok = sevalue_to_native(args[0], &url); + SE_PRECONDITION2(ok, false, "Error processing url argument"); + + se::Object *obj = s.thisObject(); + cc::network::WebSocket *cobj = nullptr; + if (argc >= 2) { + ccstd::string caFilePath; + ccstd::vector protocols; + + if (args[1].isString()) { + ccstd::string protocol; + ok = sevalue_to_native(args[1], &protocol); + SE_PRECONDITION2(ok, false, "Error processing protocol string"); + protocols.push_back(protocol); + } else if (args[1].isObject() && args[1].toObject()->isArray()) { + se::Object *protocolArr = args[1].toObject(); + uint32_t len = 0; + ok = protocolArr->getArrayLength(&len); + SE_PRECONDITION2(ok, false, "getArrayLength failed!"); + + se::Value tmp; + for (uint32_t i = 0; i < len; ++i) { + if (!protocolArr->getArrayElement(i, &tmp)) { + continue; + } + + ccstd::string protocol; + ok = sevalue_to_native(tmp, &protocol); + SE_PRECONDITION2(ok, false, "Error processing protocol object"); + protocols.push_back(protocol); + } + } + + if (argc > 2) { + ok = sevalue_to_native(args[2], &caFilePath); + SE_PRECONDITION2(ok, false, "Error processing caFilePath"); + } + + cobj = ccnew cc::network::WebSocket(); + auto *delegate = ccnew JsbWebSocketDelegate(); + delegate->addRef(); + if (cobj->init(*delegate, url, &protocols, caFilePath)) { + delegate->setJSDelegate(se::Value(obj, true)); + cobj->addRef(); // release in finalize function and onClose delegate method + delegate->addRef(); // release in finalize function and onClose delegate method + } else { + cobj->release(); + delegate->release(); + SE_REPORT_ERROR("WebSocket init failed!"); + return false; + } + } else { + cobj = ccnew cc::network::WebSocket(); + auto *delegate = ccnew JsbWebSocketDelegate(); + delegate->addRef(); + if (cobj->init(*delegate, url)) { + delegate->setJSDelegate(se::Value(obj, true)); + cobj->addRef(); // release in finalize function and onClose delegate method + delegate->addRef(); // release in finalize function and onClose delegate method + } else { + cobj->release(); + delegate->release(); + SE_REPORT_ERROR("WebSocket init failed!"); + return false; + } + } + + obj->setProperty("url", args[0]); + + // The websocket draft uses lowercase 'url', so 'URL' need to be deprecated. + obj->setProperty("URL", args[0]); + + // Initialize protocol property with an empty string, it will be assigned in onOpen delegate. + obj->setProperty("protocol", se::Value("")); + + obj->setPrivateData(cobj); + + obj->root(); + + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1<= and <=3", argc); + return false; +} +SE_BIND_CTOR(webSocketConstructor, jsbWebSocketClass, webSocketFinalize) + +static bool webSocketSend(se::State &s) { + const auto &args = s.args(); + int argc = static_cast(args.size()); + + if (argc == 1) { + auto *cobj = static_cast(s.nativeThisObject()); + bool ok = false; + if (args[0].isString()) { + ccstd::string data; + ok = sevalue_to_native(args[0], &data); + SE_PRECONDITION2(ok, false, "Convert string failed"); + // IDEA: We didn't find a way to get the JS string length in JSB2.0. + // if (data.empty() && len > 0) + // { + // CC_LOG_DEBUGWARN("Text message to send is empty, but its length is greater than 0!"); + // //IDEA: Note that this text message contains '0x00' prefix, so its length calcuted by strlen is 0. + // // we need to fix that if there is '0x00' in text message, + // // since javascript language could support '0x00' inserted at the beginning or the middle of text message + // } + + cobj->send(data); + } else if (args[0].isObject()) { + se::Object *dataObj = args[0].toObject(); + uint8_t *ptr = nullptr; + size_t length = 0; + if (dataObj->isArrayBuffer()) { + ok = dataObj->getArrayBufferData(&ptr, &length); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (dataObj->isTypedArray()) { + ok = dataObj->getTypedArrayData(&ptr, &length); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + CC_ABORT(); + } + + cobj->send(ptr, static_cast(length)); + } else { + CC_ABORT(); + } + + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1", argc); + return false; +} +SE_BIND_FUNC(webSocketSend) + +static bool webSocketClose(se::State &s) { + const auto &args = s.args(); + int argc = static_cast(args.size()); + + auto *cobj = static_cast(s.nativeThisObject()); + if (argc == 0) { + cobj->closeAsync(); + } else if (argc == 1) { + if (args[0].isNumber()) { + int reasonCode{0}; + sevalue_to_native(args[0], &reasonCode); + cobj->closeAsync(reasonCode, "no_reason"); + } else if (args[0].isString()) { + ccstd::string reasonString; + sevalue_to_native(args[0], &reasonString); + cobj->closeAsync(1005, reasonString); + } else { + CC_ABORT(); + } + } else if (argc == 2) { + if (args[0].isNumber()) { + int reasonCode{0}; + if (args[1].isString()) { + ccstd::string reasonString; + sevalue_to_native(args[0], &reasonCode); + sevalue_to_native(args[1], &reasonString); + cobj->closeAsync(reasonCode, reasonString); + } else if (args[1].isNullOrUndefined()) { + sevalue_to_native(args[0], &reasonCode); + cobj->closeAsync(reasonCode, "no_reason"); + } else { + CC_ABORT(); + } + } else if (args[0].isNullOrUndefined()) { + if (args[1].isString()) { + ccstd::string reasonString; + sevalue_to_native(args[1], &reasonString); + cobj->closeAsync(1005, reasonString); + } else if (args[1].isNullOrUndefined()) { + cobj->closeAsync(); + } else { + CC_ABORT(); + } + } else { + CC_ABORT(); + } + } else { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting <=2", argc); + CC_ABORT(); + } + // Attach current WebSocket instance to global object to prevent WebSocket instance + // being garbage collected after "ws.close(); ws = null;" + // There is a state that current WebSocket JS instance is being garbaged but its finalize + // callback has not be invoked. Then in "JSB_WebSocketDelegate::onClose", se::Object is + // still be able to be found and while invoking JS 'onclose' method, crash will happen since + // JS instance is invalid and is going to be collected. This bug is easiler reproduced on iOS + // because JavaScriptCore is more GC sensitive. + // Please note that we need to detach it from global object in "JSB_WebSocketDelegate::onClose". + se::ScriptEngine::getInstance()->getGlobalObject()->attachObject(s.thisObject()); + return true; +} +SE_BIND_FUNC(webSocketClose) + +static bool webSocketGetReadyState(se::State &s) { + const auto &args = s.args(); + int argc = static_cast(args.size()); + + if (argc == 0) { + auto *cobj = static_cast(s.nativeThisObject()); + s.rval().setInt32(static_cast(cobj->getReadyState())); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); + return false; +} +SE_BIND_PROP_GET(webSocketGetReadyState) + +static bool webSocketGetBufferedAmount(se::State &s) { + const auto &args = s.args(); + int argc = static_cast(args.size()); + + if (argc == 0) { + auto *cobj = static_cast(s.nativeThisObject()); + s.rval().setUint32(static_cast(cobj->getBufferedAmount())); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); + return false; +} +SE_BIND_PROP_GET(webSocketGetBufferedAmount) + +static bool webSocketGetExtensions(se::State &s) { + const auto &args = s.args(); + int argc = static_cast(args.size()); + + if (argc == 0) { + auto *cobj = static_cast(s.nativeThisObject()); + s.rval().setString(cobj->getExtensions()); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); + return false; +} +SE_BIND_PROP_GET(webSocketGetExtensions) + +#define WEBSOCKET_DEFINE_READONLY_INT_FIELD(full_name, value) \ + static bool full_name(se::State &s) { \ + const auto &args = s.args(); \ + int argc = (int)args.size(); \ + if (argc == 0) { \ + s.rval().setInt32(value); \ + return true; \ + } \ + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); \ + return false; \ + } \ + SE_BIND_PROP_GET(full_name) + +WEBSOCKET_DEFINE_READONLY_INT_FIELD(Websocket_CONNECTING, static_cast(cc::network::WebSocket::State::CONNECTING)) +WEBSOCKET_DEFINE_READONLY_INT_FIELD(Websocket_OPEN, static_cast(cc::network::WebSocket::State::OPEN)) +WEBSOCKET_DEFINE_READONLY_INT_FIELD(Websocket_CLOSING, static_cast(cc::network::WebSocket::State::CLOSING)) +WEBSOCKET_DEFINE_READONLY_INT_FIELD(Websocket_CLOSED, static_cast(cc::network::WebSocket::State::CLOSED)) + +bool register_all_websocket(se::Object *global) { // NOLINT (readability-identifier-naming) + se::Value nsVal; + if (!global->getProperty("jsb", &nsVal, true)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + global->setProperty("jsb", nsVal); + } + se::Object *ns = nsVal.toObject(); + se::Class *cls = se::Class::create("WebSocket", ns, nullptr, _SE(webSocketConstructor)); + cls->defineFinalizeFunction(_SE(webSocketFinalize)); + + cls->defineFunction("send", _SE(webSocketSend)); + cls->defineFunction("close", _SE(webSocketClose)); + cls->defineProperty("readyState", _SE(webSocketGetReadyState), nullptr); + cls->defineProperty("bufferedAmount", _SE(webSocketGetBufferedAmount), nullptr); + cls->defineProperty("extensions", _SE(webSocketGetExtensions), nullptr); + cls->defineProperty("CONNECTING", _SE(Websocket_CONNECTING), nullptr); + cls->defineProperty("CLOSING", _SE(Websocket_CLOSING), nullptr); + cls->defineProperty("OPEN", _SE(Websocket_OPEN), nullptr); + cls->defineProperty("CLOSED", _SE(Websocket_CLOSED), nullptr); + + cls->install(); + + se::Value tmp; + ns->getProperty("WebSocket", &tmp); + tmp.toObject()->defineProperty("CONNECTING", _SE(Websocket_CONNECTING), nullptr); + tmp.toObject()->defineProperty("CLOSING", _SE(Websocket_CLOSING), nullptr); + tmp.toObject()->defineProperty("OPEN", _SE(Websocket_OPEN), nullptr); + tmp.toObject()->defineProperty("CLOSED", _SE(Websocket_CLOSED), nullptr); + + JSBClassType::registerClass(cls); + + jsbWebSocketClass = cls; + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} diff --git a/cocos/bindings/manual/jsb_websocket.h b/cocos/bindings/manual/jsb_websocket.h new file mode 100644 index 0000000..7c075bc --- /dev/null +++ b/cocos/bindings/manual/jsb_websocket.h @@ -0,0 +1,59 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/network/WebSocket.h" + +namespace se { +class Object; +class Value; +} // namespace se + +class JsbWebSocketDelegate : public cc::RefCounted, public cc::network::WebSocket::Delegate { +public: + JsbWebSocketDelegate(); + + void onOpen(cc::network::WebSocket *ws) override; + + void onMessage(cc::network::WebSocket *ws, + const cc::network::WebSocket::Data &data) override; + + void onClose(cc::network::WebSocket *ws, uint16_t code, const ccstd::string &reason, bool wasClean) override; + + void onError(cc::network::WebSocket *ws, + const cc::network::WebSocket::ErrorCode &error) override; + + void setJSDelegate(const se::Value &jsDelegate); + +private: + ~JsbWebSocketDelegate() override; + + se::Value _JSDelegate; // NOLINT (bugprone-reserved-identifier) +}; + +SE_DECLARE_FINALIZE_FUNC(WebSocket_finalize); + +bool register_all_websocket(se::Object *obj); // NOLINT (readability-identifier-naming) diff --git a/cocos/bindings/manual/jsb_websocket_server.cpp b/cocos/bindings/manual/jsb_websocket_server.cpp new file mode 100644 index 0000000..90692bd --- /dev/null +++ b/cocos/bindings/manual/jsb_websocket_server.cpp @@ -0,0 +1,932 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +// clang-format off +#include "base/Macros.h" +#include "uv.h" +// clang-format on + +#include "cocos/bindings/manual/jsb_websocket_server.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/bindings/manual/jsb_global.h" +#include "cocos/network/WebSocketServer.h" + +namespace { +template +std::shared_ptr sharedPtrObj(se::State &s) { + auto *privateObj = s.thisObject()->getPrivateObject(); + assert(privateObj->isSharedPtr()); + return static_cast *>(privateObj)->getData(); +} +} // namespace + +se::Class *__jsb_WebSocketServer_class = nullptr; // NOLINT +se::Class *__jsb_WebSocketServer_Connection_class = nullptr; // NOLINT + +static int sendIndex = 1; + +static ccstd::string genSendIndex() { + char buf[128] = {0}; + snprintf(buf, 127, "__send_[%d]", sendIndex++); + return buf; +} + +static bool WebSocketServer_finalize(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + CC_LOG_INFO("jsbindings: finalizing JS object %p (WebSocketServer)", cobj); + return true; +} +SE_BIND_FINALIZE_FUNC(WebSocketServer_finalize) + +static bool WebSocketServer_constructor(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc == 0) { + se::Object *obj = s.thisObject(); + auto *cobj = ccnew cc::network::WebSocketServer(); + obj->setPrivateData(cobj); + cobj->setData(obj); + + cobj->setOnBegin([obj]() { + obj->root(); + }); + + cobj->setOnEnd([obj]() { + obj->unroot(); + }); + + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); + return false; +} +SE_BIND_CTOR(WebSocketServer_constructor, __jsb_WebSocketServer_class, WebSocketServer_finalize) + +static bool WebSocketServer_listen(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc == 0) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1,2,3", argc); + return false; + } + + auto cobj = sharedPtrObj(s); + int argPort = 0; + ccstd::string argHost; + std::function argCallback; + + bool ok; + + if (argc >= 1) { // port + ok = sevalue_to_native(args[0], &argPort); + SE_PRECONDITION2(ok, false, "Convert args[0] to port failed"); + } + if (argc >= 2) { // host or callback + if (args[1].isString()) { //to host + ok = sevalue_to_native(args[1], &argHost); + SE_PRECONDITION2(ok, false, "Convert args[1] to host failed"); + } + se::Object *funObj = nullptr; + if (args[1].isObject() && args[1].toObject()->isFunction()) { + funObj = args[1].toObject(); + } + + if (argc > 2 && args[2].isObject() && args[2].toObject()->isFunction()) { + funObj = args[2].toObject(); + } + if (funObj) { + s.thisObject()->setProperty("__onlisten", se::Value(funObj)); + std::weak_ptr serverWeak = cobj; + argCallback = [serverWeak](const ccstd::string &err) { + se::AutoHandleScope hs; + + auto serverPtr = serverWeak.lock(); + if (!serverPtr) { + return; + } + auto *sobj = static_cast(serverPtr->getData()); + if (!sobj) { + return; + } + se::Value callback; + if (!sobj->getProperty("__onlisten", &callback)) { + SE_REPORT_ERROR("onlisten attribute not set!"); + return; + } + + se::ValueArray args; + if (!err.empty()) { + args.push_back(se::Value(err)); + } + bool success = callback.toObject()->call(args, sobj, nullptr); + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }; + } + } + + cc::network::WebSocketServer::listenAsync(cobj, argPort, argHost, argCallback); + return true; +} +SE_BIND_FUNC(WebSocketServer_listen) + +static bool WebSocketServer_onconnection(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (!(argc == 1 && args[0].isObject() && args[0].toObject()->isFunction())) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1 & function", argc); + return false; + } + + s.thisObject()->setProperty("__onconnection", args[0]); + + auto cobj = sharedPtrObj(s); + std::weak_ptr serverWeak = cobj; + + cobj->setOnConnection([serverWeak](const std::shared_ptr &conn) { + se::AutoHandleScope hs; + + auto server = serverWeak.lock(); + if (!server) { + return; + } + auto *sobj = static_cast(server->getData()); + if (!sobj) { + return; + } + se::Value callback; + if (!sobj->getProperty("__onconnection", &callback)) { + SE_REPORT_ERROR("onconnection callback not found!"); + return; + } + + se::Object *obj = se::Object::createObjectWithClass(__jsb_WebSocketServer_Connection_class); + // a connection is dead only if no reference & closed! + obj->root(); + obj->setPrivateObject(se::shared_ptr_private_object(conn)); + + conn->setData(obj); + std::weak_ptr connWeak = conn; + conn->setOnEnd([connWeak, obj]() { + // release we connection is gone! + auto ptr = connWeak.lock(); + if (ptr) { + auto *sobj = static_cast(ptr->getData()); + sobj->unroot(); + CC_ASSERT_EQ(obj, sobj); + } + }); + se::ValueArray args; + args.push_back(se::Value(obj)); + bool success = callback.toObject()->call(args, sobj, nullptr); + ; + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }); + return true; +} +SE_BIND_PROP_SET(WebSocketServer_onconnection) + +static bool WebSocketServer_onclose(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc != 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1", argc); + return false; + } + if (!(args[0].isObject() && args[0].toObject()->isFunction())) { + SE_REPORT_ERROR("argument type error, function expected!"); + } + + std::function callback; + auto cobj = sharedPtrObj(s); + std::weak_ptr serverWeak = cobj; + s.thisObject()->setProperty("__onclose", args[0]); + + callback = [serverWeak](const ccstd::string &err) { + se::AutoHandleScope hs; + + auto server = serverWeak.lock(); + if (!server) { + return; + } + auto *sobj = static_cast(server->getData()); + if (!sobj) { + return; + } + se::Value callback; + if (!sobj->getProperty("__onclose", &callback)) { + SE_REPORT_ERROR("onclose callback not found!"); + return; + } + + se::ValueArray args; + if (!err.empty()) { + args.push_back(se::Value(err)); + } + bool success = callback.toObject()->call(args, sobj, nullptr); + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }; + cobj->setOnClose(callback); + return true; +} +SE_BIND_PROP_SET(WebSocketServer_onclose) + +static bool WebSocketServer_close(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc > 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0, 1", argc); + return false; + } + + std::function callback; + auto cobj = sharedPtrObj(s); + + if (argc == 1) { + if (args[0].isObject() && args[0].toObject()->isFunction()) { + s.thisObject()->setProperty("__close", args[0]); + std::weak_ptr serverWeak = cobj; + + callback = [serverWeak](const ccstd::string &err) { + se::AutoHandleScope hs; + + auto server = serverWeak.lock(); + if (!server) { + return; + } + auto *sobj = static_cast(server->getData()); + if (!sobj) { + return; + } + se::Value callback; + if (!sobj->getProperty("__close", &callback)) { + SE_REPORT_ERROR("onclose callback not found!"); + return; + } + + se::ValueArray args; + if (!err.empty()) { + args.push_back(se::Value(err)); + } + bool success = callback.toObject()->call(args, sobj, nullptr); + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }; + cobj->closeAsync(callback); + } else { + SE_REPORT_ERROR("wrong argument type, function expected"); + return false; + } + } else { + cobj->closeAsync(); + } + + return true; +} +SE_BIND_FUNC(WebSocketServer_close) + +static bool WebSocketServer_connections(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + + if (argc == 0) { + auto cobj = sharedPtrObj(s); + auto conns = cobj->getConnections(); + se::Object *ret = se::Object::createArrayObject(conns.size()); + for (uint32_t i = 0; i < conns.size(); i++) { + std::shared_ptr &con = conns[i]; + auto *obj = static_cast(con->getData()); + ret->setArrayElement(i, se::Value(obj)); + } + s.rval().setObject(ret); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); + return false; +} +SE_BIND_PROP_GET(WebSocketServer_connections) + +static bool WebSocketServer_Connection_finalize(se::State &s) { // NOLINT(readability-identifier-naming) + auto *cobj = static_cast(s.nativeThisObject()); + CC_LOG_INFO("jsbindings: finalizing JS object %p (WebSocketServer_Connection)", cobj); + return true; +} +SE_BIND_FINALIZE_FUNC(WebSocketServer_Connection_finalize) + +static bool WebSocketServer_Connection_constructor(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc == 0) { + se::Object *obj = s.thisObject(); + //private data should be set when connected + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); + return false; +} +SE_BIND_CTOR(WebSocketServer_Connection_constructor, __jsb_WebSocketServer_Connection_class, WebSocketServer_Connection_finalize) + +static bool WebSocketServer_Connection_send(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + + auto cobj = sharedPtrObj(s); + + if (!cobj) { + SE_REPORT_ERROR("Connection is not constructed by WebSocketServer, invalidate format!!"); + return false; + } + + if (argc >= 1) { + std::function callback; + if (args[argc - 1].isObject() && args[argc - 1].toObject()->isFunction()) { + ccstd::string callbackId = genSendIndex(); + s.thisObject()->setProperty(callbackId.c_str(), args[argc - 1]); + std::weak_ptr connWeak = cobj; + + callback = [callbackId, connWeak](const ccstd::string &err) { + se::AutoHandleScope hs; + auto conn = connWeak.lock(); + if (!conn) { + return; + } + auto *sobj = static_cast(conn->getData()); + if (!sobj) { + return; + } + se::Value callback; + if (!sobj->getProperty(callbackId.c_str(), &callback)) { + SE_REPORT_ERROR("send[%s] callback not found!", callbackId.c_str()); + return; + } + se::ValueArray args; + if (!err.empty()) { + args.push_back(se::Value(err)); + } + bool success = callback.toObject()->call(args, sobj, nullptr); + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + sobj->deleteProperty(callbackId.c_str()); + }; + } + + bool ok = false; + if (args[0].isString()) { + ccstd::string data; + ok = sevalue_to_native(args[0], &data); + SE_PRECONDITION2(ok, false, "Convert string failed"); + cobj->sendTextAsync(data, callback); + } else if (args[0].isObject()) { + se::Object *dataObj = args[0].toObject(); + uint8_t *ptr = nullptr; + size_t length = 0; + if (dataObj->isArrayBuffer()) { + ok = dataObj->getArrayBufferData(&ptr, &length); + SE_PRECONDITION2(ok, false, "getArrayBufferData failed!"); + } else if (dataObj->isTypedArray()) { + ok = dataObj->getTypedArrayData(&ptr, &length); + SE_PRECONDITION2(ok, false, "getTypedArrayData failed!"); + } else { + CC_ABORT(); + } + + cobj->sendBinaryAsync(ptr, static_cast(length), callback); + } else { + CC_ABORT(); + } + + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1", argc); + return false; +} +SE_BIND_FUNC(WebSocketServer_Connection_send) + +static bool WebSocketServer_Connection_close(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc > 1) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0, 1", argc); + return false; + } + + auto *cobj = static_cast(s.nativeThisObject()); + + if (!cobj) { + SE_REPORT_ERROR("Connection is not constructed by WebSocketServer, invalidate format!!"); + return false; + } + + std::function callback; + int argCode = -1; + ccstd::string argReason; + bool ok; + + if (argc >= 1) { + ok = sevalue_to_native(args[0], &argCode); + SE_PRECONDITION2(ok, false, "Convert args[0] should be a number"); + + if (argc >= 2) { + ok = sevalue_to_native(args[1], &argReason); + SE_PRECONDITION2(ok, false, "Convert args[1] should be a string"); + } + } + + if (argCode > 0 && !argReason.empty()) { + cobj->closeAsync(argCode, argReason); + } else if (argCode > 0) { + cobj->closeAsync(argCode, "unknown reason"); + } else { + cobj->closeAsync(1000, "default close reason"); + } + return true; +} +SE_BIND_FUNC(WebSocketServer_Connection_close) + +static bool WebSocketServer_Connection_onconnect(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (!(argc == 1 && args[0].isObject() && args[0].toObject()->isFunction())) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1 & function", argc); + return false; + } + + auto cobj = sharedPtrObj(s); + + if (!cobj) { + SE_REPORT_ERROR("Connection is not constructed by WebSocketServer, invalidate format!!"); + return false; + } + + s.thisObject()->setProperty("__onconnect", args[0]); + + std::weak_ptr connWeak = cobj; + + cobj->setOnConnect([connWeak]() { + se::AutoHandleScope hs; + auto conn = connWeak.lock(); + if (!conn) { + return; + } + auto *sobj = static_cast(conn->getData()); + if (!sobj) { + return; + } + se::Value callback; + if (!sobj->getProperty("__onconnect", &callback)) { + SE_REPORT_ERROR("__onconnect callback not found!"); + return; + } + se::ValueArray args; + bool success = callback.toObject()->call(args, sobj, nullptr); + ; + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }); + return true; +} +SE_BIND_PROP_SET(WebSocketServer_Connection_onconnect) + +static bool WebSocketServer_Connection_onerror(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (!(argc == 1 && args[0].isObject() && args[0].toObject()->isFunction())) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1 & function", argc); + return false; + } + auto cobj = sharedPtrObj(s); + + if (!cobj) { + SE_REPORT_ERROR("Connection is not constructed by WebSocketServer, invalidate format!!"); + return false; + } + + s.thisObject()->setProperty("__onerror", args[0]); + std::weak_ptr connWeak = cobj; + + cobj->setOnError([connWeak](const ccstd::string &err) { + se::AutoHandleScope hs; + auto conn = connWeak.lock(); + if (!conn) { + return; + } + auto *sobj = static_cast(conn->getData()); + if (!sobj) { + return; + } + se::Value callback; + if (!sobj->getProperty("__onerror", &callback)) { + SE_REPORT_ERROR("__onerror callback not found!"); + return; + } + se::ValueArray args; + if (!err.empty()) { + args.push_back(se::Value(err)); + } + bool success = callback.toObject()->call(args, sobj, nullptr); + ; + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }); + return true; +} +SE_BIND_PROP_SET(WebSocketServer_Connection_onerror) + +static bool WebSocketServer_Connection_onclose(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (!(argc == 1 && args[0].isObject() && args[0].toObject()->isFunction())) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1 & function", argc); + return false; + } + auto cobj = sharedPtrObj(s); + + if (!cobj) { + SE_REPORT_ERROR("Connection is not constructed by WebSocketServer, invalidate format!!"); + return false; + } + + s.thisObject()->setProperty("__onclose", args[0]); + std::weak_ptr connWeak = cobj; + + cobj->setOnClose([connWeak](int code, const ccstd::string &err) { + se::AutoHandleScope hs; + + auto conn = connWeak.lock(); + if (!conn) { + return; + } + auto *sobj = static_cast(conn->getData()); + if (!sobj) { + return; + } + se::Value callback; + if (!sobj->getProperty("__onclose", &callback)) { + SE_REPORT_ERROR("__onclose callback not found!"); + return; + } + + se::ValueArray args; + args.push_back(se::Value(code)); + if (!err.empty()) { + args.push_back(se::Value(err)); + } + bool success = callback.toObject()->call(args, sobj, nullptr); + ; + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }); + return true; +} +SE_BIND_PROP_SET(WebSocketServer_Connection_onclose) + +static bool WebSocketServer_Connection_ontext(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (!(argc == 1 && args[0].isObject() && args[0].toObject()->isFunction())) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1 & function", argc); + return false; + } + auto cobj = sharedPtrObj(s); + + if (!cobj) { + SE_REPORT_ERROR("Connection is not constructed by WebSocketServer, invalidate format!!"); + return false; + } + + s.thisObject()->setProperty("__ontext", args[0]); + std::weak_ptr connWeak = cobj; + + cobj->setOnText([connWeak](const std::shared_ptr &text) { + se::AutoHandleScope hs; + + auto conn = connWeak.lock(); + if (!conn) { + return; + } + auto *sobj = static_cast(conn->getData()); + if (!sobj) { + return; + } + se::Value callback; + if (!sobj->getProperty("__ontext", &callback)) { + SE_REPORT_ERROR("__ontext callback not found!"); + return; + } + + se::ValueArray args; + args.push_back(se::Value(text->toString())); + bool success = callback.toObject()->call(args, sobj, nullptr); + ; + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }); + return true; +} +SE_BIND_PROP_SET(WebSocketServer_Connection_ontext) + +static bool WebSocketServer_Connection_onbinary(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (!(argc == 1 && args[0].isObject() && args[0].toObject()->isFunction())) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1 & function", argc); + return false; + } + auto cobj = sharedPtrObj(s); + + if (!cobj) { + SE_REPORT_ERROR("Connection is not constructed by WebSocketServer, invalidate format!!"); + return false; + } + + s.thisObject()->setProperty("__onbinary", args[0]); + std::weak_ptr connWeak = cobj; + + cobj->setOnBinary([connWeak](const std::shared_ptr &text) { + se::AutoHandleScope hs; + auto conn = connWeak.lock(); + if (!conn) { + return; + } + auto *sobj = static_cast(conn->getData()); + if (!sobj) { + return; + } + se::Value callback; + if (!sobj->getProperty("__onbinary", &callback)) { + SE_REPORT_ERROR("__onbinary callback not found!"); + return; + } + + se::ValueArray args; + se::Object *buffer = se::Object::createArrayBufferObject(text->getData(), text->size()); + args.push_back(se::Value(buffer)); + bool success = callback.toObject()->call(args, sobj, nullptr); + ; + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }); + return true; +} +SE_BIND_PROP_SET(WebSocketServer_Connection_onbinary) + +//deprecated since v3.7, please use WebSocketServer_Connection_onmessage to instead. +static bool WebSocketServer_Connection_ondata(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (!(argc == 1 && args[0].isObject() && args[0].toObject()->isFunction())) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1 & function", argc); + return false; + } + auto cobj = sharedPtrObj(s); + + if (!cobj) { + SE_REPORT_ERROR("Connection is not constructed by WebSocketServer, invalidate format!!"); + return false; + } + + s.thisObject()->setProperty("__ondata", args[0]); + std::weak_ptr connWeak = cobj; + + cobj->setOnMessage([connWeak](const std::shared_ptr &text) { + se::AutoHandleScope hs; + + auto connPtr = connWeak.lock(); + if (!connPtr) { + return; + } + auto *sobj = static_cast(connPtr->getData()); + if (!sobj) { + return; + } + + se::Value callback; + if (!sobj->getProperty("__ondata", &callback)) { + return; + } + + se::ValueArray args; + if (text->isBinary()) { + se::Object *buffer = se::Object::createArrayBufferObject(text->getData(), text->size()); + args.push_back(se::Value(buffer)); + } else if (text->isString()) { + args.push_back(se::Value(text->toString())); + } + bool success = callback.toObject()->call(args, sobj, nullptr); + ; + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }); + SE_REPORT_ERROR("Deprecated callback function ondata since v3.7, please use onmessage to instead."); + return true; +} +SE_BIND_PROP_SET(WebSocketServer_Connection_ondata) + +static bool WebSocketServer_Connection_onmessage(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (!(argc == 1 && args[0].isObject() && args[0].toObject()->isFunction())) { + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1 & function", argc); + return false; + } + auto cobj = sharedPtrObj(s); + + if (!cobj) { + SE_REPORT_ERROR("Connection is not constructed by WebSocketServer, invalidate format!!"); + return false; + } + + s.thisObject()->setProperty("__onmessage", args[0]); + std::weak_ptr connWeak = cobj; + + cobj->setOnMessage([connWeak](const std::shared_ptr &text) { + se::AutoHandleScope hs; + + auto connPtr = connWeak.lock(); + if (!connPtr) { + return; + } + auto *sobj = static_cast(connPtr->getData()); + if (!sobj) { + return; + } + + se::Value callback; + if (!sobj->getProperty("__onmessage", &callback)) { + return; + } + + se::ValueArray args; + if (text->isBinary()) { + se::Object *buffer = se::Object::createArrayBufferObject(text->getData(), text->size()); + args.push_back(se::Value(buffer)); + } else if (text->isString()) { + args.push_back(se::Value(text->toString())); + } + bool success = callback.toObject()->call(args, sobj, nullptr); + ; + if (!success) { + se::ScriptEngine::getInstance()->clearException(); + } + }); + return true; +} +SE_BIND_PROP_SET(WebSocketServer_Connection_onmessage) + +static bool WebSocketServer_Connection_headers(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + + if (argc == 0) { + auto *cobj = static_cast(s.nativeThisObject()); + auto headers = cobj->getHeaders(); + se::Object *ret = se::Object::createPlainObject(); + for (auto &itr : headers) { + ret->setProperty(itr.first.c_str(), se::Value(itr.second)); + } + s.rval().setObject(ret); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); + return false; +} +SE_BIND_PROP_GET(WebSocketServer_Connection_headers) + +static bool WebSocketServer_Connection_protocols(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + + if (argc == 0) { + auto *cobj = static_cast(s.nativeThisObject()); + auto protocols = cobj->getProtocols(); + se::Object *ret = se::Object::createArrayObject(protocols.size()); + for (uint32_t i = 0; i < protocols.size(); i++) { + ret->setArrayElement(i, se::Value(protocols[i])); + } + s.rval().setObject(ret); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); + return false; +} +SE_BIND_PROP_GET(WebSocketServer_Connection_protocols) + +static bool WebSocketServer_Connection_protocol(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + + if (argc == 0) { + auto *cobj = static_cast(s.nativeThisObject()); + auto protocols = cobj->getProtocols(); + if (!protocols.empty()) { + s.rval().setString(protocols[0]); + } else { + s.rval().setUndefined(); + } + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); + return false; +} +SE_BIND_PROP_GET(WebSocketServer_Connection_protocol) + +static bool WebSocketServer_Connection_readyState(se::State &s) { // NOLINT(readability-identifier-naming) + const auto &args = s.args(); + int argc = static_cast(args.size()); + + if (argc == 0) { + auto *cobj = static_cast(s.nativeThisObject()); + auto state = cobj->getReadyState(); + s.rval().setInt32(state); + return true; + } + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); + return false; +} +SE_BIND_PROP_GET(WebSocketServer_Connection_readyState) + +bool register_all_websocket_server(se::Object *obj) { + se::Class *cls = se::Class::create("WebSocketServer", obj, nullptr, _SE(WebSocketServer_constructor)); + cls->defineFinalizeFunction(_SE(WebSocketServer_finalize)); + + cls->defineFunction("close", _SE(WebSocketServer_close)); + cls->defineFunction("listen", _SE(WebSocketServer_listen)); + cls->defineProperty("onconnection", nullptr, _SE(WebSocketServer_onconnection)); + cls->defineProperty("onclose", nullptr, _SE(WebSocketServer_onclose)); + cls->defineProperty("connections", _SE(WebSocketServer_connections), nullptr); + cls->install(); + + JSBClassType::registerClass(cls); + __jsb_WebSocketServer_class = cls; + se::ScriptEngine::getInstance()->clearException(); + + cls = se::Class::create("WebSocketServerConnection", obj, nullptr, _SE(WebSocketServer_Connection_constructor)); + cls->defineFinalizeFunction(_SE(WebSocketServer_Connection_finalize)); + cls->defineFunction("close", _SE(WebSocketServer_Connection_close)); + cls->defineFunction("send", _SE(WebSocketServer_Connection_send)); + + cls->defineProperty("ontext", nullptr, _SE(WebSocketServer_Connection_ontext)); + cls->defineProperty("onbinary", nullptr, _SE(WebSocketServer_Connection_onbinary)); + cls->defineProperty("onconnect", nullptr, _SE(WebSocketServer_Connection_onconnect)); + cls->defineProperty("onerror", nullptr, _SE(WebSocketServer_Connection_onerror)); + cls->defineProperty("onclose", nullptr, _SE(WebSocketServer_Connection_onclose)); + cls->defineProperty("ondata", nullptr, _SE(WebSocketServer_Connection_ondata)); //deprecated since v3.7, please use onmessage to instead. + cls->defineProperty("onmessage", nullptr, _SE(WebSocketServer_Connection_onmessage)); + + cls->defineProperty("headers", _SE(WebSocketServer_Connection_headers), nullptr); + cls->defineProperty("protocols", _SE(WebSocketServer_Connection_protocols), nullptr); + cls->defineProperty("protocol", _SE(WebSocketServer_Connection_protocol), nullptr); + cls->defineProperty("readyState", _SE(WebSocketServer_Connection_readyState), nullptr); + + cls->install(); + + se::Value tmp; + obj->getProperty("WebSocketServerConnection", &tmp); + tmp.toObject()->setProperty("CONNECTING", se::Value(cc::network::WebSocketServerConnection::CONNECTING)); + tmp.toObject()->setProperty("OPEN", se::Value(cc::network::WebSocketServerConnection::OPEN)); + tmp.toObject()->setProperty("CLOSING", se::Value(cc::network::WebSocketServerConnection::CLOSING)); + tmp.toObject()->setProperty("CLOSED", se::Value(cc::network::WebSocketServerConnection::CLOSED)); + + JSBClassType::registerClass(cls); + __jsb_WebSocketServer_Connection_class = cls; + se::ScriptEngine::getInstance()->clearException(); + + return true; +} diff --git a/cocos/bindings/manual/jsb_websocket_server.h b/cocos/bindings/manual/jsb_websocket_server.h new file mode 100644 index 0000000..a286011 --- /dev/null +++ b/cocos/bindings/manual/jsb_websocket_server.h @@ -0,0 +1,35 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "cocos/bindings/jswrapper/SeApi.h" +namespace se { +class Object; +class Value; +} // namespace se + +SE_DECLARE_FINALIZE_FUNC(WebSocketServer_finalize); + +bool register_all_websocket_server(se::Object *obj); diff --git a/cocos/bindings/manual/jsb_xmlhttprequest.cpp b/cocos/bindings/manual/jsb_xmlhttprequest.cpp new file mode 100644 index 0000000..fdf44d4 --- /dev/null +++ b/cocos/bindings/manual/jsb_xmlhttprequest.cpp @@ -0,0 +1,1002 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "jsb_xmlhttprequest.h" +#include +#include +#include +#include "application/ApplicationManager.h" +#include "base/Config.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "cocos/base/Data.h" +#include "cocos/base/DeferredReleasePool.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_conversions.h" +#include "cocos/engine/BaseEngine.h" +#include "cocos/network/HttpClient.h" + +using namespace cc; //NOLINT +using namespace cc::network; //NOLINT + +namespace { +// NOLINTNEXTLINE(readability-identifier-naming) +ccstd::unordered_map _httpStatusCodeMap = { + {100, "Continue"}, + {101, "Switching Protocols"}, + {102, "Processing"}, + {200, "OK"}, + {201, "Created"}, + {202, "Accepted"}, + {203, "Non-authoritative Information"}, + {204, "No Content"}, + {205, "Reset Content"}, + {206, "Partial Content"}, + {207, "Multi-Status"}, + {208, "Already Reported"}, + {226, "IM Used"}, + {300, "Multiple Choices"}, + {301, "Moved Permanently"}, + {302, "Found"}, + {303, "See Other"}, + {304, "Not Modified"}, + {305, "Use Proxy"}, + {307, "Temporary Redirect"}, + {308, "Permanent Redirect"}, + {400, "Bad Request"}, + {401, "Unauthorized"}, + {402, "Payment Required"}, + {403, "Forbidden"}, + {404, "Not Found"}, + {405, "Method Not Allowed"}, + {406, "Not Acceptable"}, + {407, "Proxy Authentication Required"}, + {408, "Request Timeout"}, + {409, "Conflict"}, + {410, "Gone"}, + {411, "Length Required"}, + {412, "Precondition Failed"}, + {413, "Payload Too Large"}, + {414, "Request-URI Too Long"}, + {415, "Unsupported Media Type"}, + {416, "Requested Range Not Satisfiable"}, + {417, "Expectation Failed"}, + {418, "I'm a teapot"}, + {421, "Misdirected Request"}, + {422, "Unprocessable Entity"}, + {423, "Locked"}, + {424, "Failed Dependency"}, + {426, "Upgrade Required"}, + {428, "Precondition Required"}, + {429, "Too Many Requests"}, + {431, "Request Header Fields Too Large"}, + {444, "Connection Closed Without Response"}, + {451, "Unavailable For Legal Reasons"}, + {499, "Client Closed Request"}, + {500, "Internal Server Error"}, + {501, "Not Implemented"}, + {502, "Bad Gateway"}, + {503, "Service Unavailable"}, + {504, "Gateway Timeout"}, + {505, "HTTP Version Not Supported"}, + {506, "Variant Also Negotiates"}, + {507, "Insufficient Storage"}, + {508, "Loop Detected"}, + {510, "Not Extended"}, + {511, "Network Authentication Required"}, + {599, "Network Connect Timeout Error"}}; +} // namespace + +class XMLHttpRequest : public RefCounted { +public: + // Ready States: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState + enum class ReadyState : char { + UNSENT = 0, // Client has been created. open() not called yet. + OPENED = 1, // open() has been called. + HEADERS_RECEIVED = 2, // send() has been called, and headers and status are available. + LOADING = 3, // Downloading; responseText holds partial data. + DONE = 4 // The operation is complete. + }; + + enum class ResponseType : char { + STRING, + ARRAY_BUFFER, + BLOB, + DOCUMENT, + JSON + }; + + std::function onloadstart; + std::function onload; + std::function onloadend; + std::function onreadystatechange; + std::function onabort; + std::function onerror; + std::function ontimeout; + + XMLHttpRequest(); + + bool open(const ccstd::string &method, const ccstd::string &url); + void send(); + void sendString(const ccstd::string &str); + void sendBinary(const cc::Data &data); + + void setRequestHeader(const ccstd::string &key, const ccstd::string &value); + ccstd::string getAllResponseHeaders() const; + ccstd::string getResponseHeader(const ccstd::string &key) const; + + ReadyState getReadyState() const { return _readyState; } + uint16_t getStatus() const { return _status; } + const ccstd::string &getStatusText() const { return _statusText; } + const ccstd::string &getResponseText() const { return _responseText; } + const cc::Data &getResponseData() const { return _responseData; } + ResponseType getResponseType() const { return _responseType; } + void setResponseType(ResponseType type) { _responseType = type; } + + void overrideMimeType(const ccstd::string &mimeType); + ccstd::string getMimeType() const; + + void setTimeout(uint32_t timeoutInMilliseconds); + uint32_t getTimeout() const; + + void abort(); + + bool isDiscardedByReset() const { return _isDiscardedByReset; } + + inline void clearCallbacks() { + onloadstart = nullptr; + onload = nullptr; + onloadend = nullptr; + onreadystatechange = nullptr; + onabort = nullptr; + onerror = nullptr; + ontimeout = nullptr; + } + +private: + ~XMLHttpRequest() override; + + void setReadyState(ReadyState readyState); + void getHeader(const ccstd::string &header); + void onResponse(cc::network::HttpClient *client, cc::network::HttpResponse *response); + + void setHttpRequestData(const char *data, size_t len); + void sendRequest(); + void setHttpRequestHeader(); + + BaseEngine::SchedulerPtr _scheduler; + ccstd::unordered_map _httpHeader; + ccstd::unordered_map _requestHeader; + + ccstd::string _url; + ccstd::string _method; + ccstd::string _responseText; + ccstd::string _responseXML; + ccstd::string _statusText; + ccstd::string _overrideMimeType; + + cc::Data _responseData; + + cc::network::HttpRequest *_httpRequest; + // cc::EventListenerCustom* _resetDirectorListener; + + uint32_t _timeoutInMilliseconds{}; + uint16_t _status{}; + + ResponseType _responseType{}; + ReadyState _readyState{}; + + bool _withCredentialsValue{false}; + bool _errorFlag{false}; + bool _isAborted{false}; + bool _isLoadStart{false}; + bool _isLoadEnd{false}; + bool _isDiscardedByReset{false}; + bool _isTimeout{false}; + bool _isSending{false}; +}; + +XMLHttpRequest::XMLHttpRequest() +: onloadstart(nullptr), + onload(nullptr), + onloadend(nullptr), + onreadystatechange(nullptr), + onabort(nullptr), + onerror(nullptr), + ontimeout(nullptr), + _httpRequest(ccnew HttpRequest()), + _responseType(ResponseType::STRING), + _readyState(ReadyState::UNSENT) { + _httpRequest->addRef(); + _scheduler = CC_CURRENT_ENGINE()->getScheduler(); +} + +XMLHttpRequest::~XMLHttpRequest() { + if (_scheduler) { + _scheduler->unscheduleAllForTarget(this); + } + // Avoid HttpClient response call a released object! + _httpRequest->setResponseCallback(nullptr); + CC_SAFE_RELEASE(_httpRequest); +} + +bool XMLHttpRequest::open(const ccstd::string &method, const ccstd::string &url) { + if (_readyState != ReadyState::UNSENT) { + return false; + } + + _method = method; + _url = url; + + HttpRequest::Type requestType = HttpRequest::Type::UNKNOWN; + + if (_method == "get" || _method == "GET") { + requestType = HttpRequest::Type::GET; + } else if (_method == "post" || _method == "POST") { + requestType = HttpRequest::Type::POST; + } else if (_method == "put" || _method == "PUT") { + requestType = HttpRequest::Type::PUT; + } else if (_method == "head" || _method == "HEAD") { + requestType = HttpRequest::Type::HEAD; + } else if (_method == "delete" || _method == "DELETE") { + requestType = HttpRequest::Type::DELETE; + } else if (_method == "patch" || _method == "PATCH") { + requestType = HttpRequest::Type::PATCH; + + } + + CC_ASSERT(requestType != HttpRequest::Type::UNKNOWN); + + _httpRequest->setRequestType(requestType); + _httpRequest->setUrl(_url); + + _status = 0; + _isAborted = false; + _isTimeout = false; + + setReadyState(ReadyState::OPENED); + + return true; +} + +void XMLHttpRequest::send() { + sendRequest(); +} + +void XMLHttpRequest::sendString(const ccstd::string &str) { + setHttpRequestData(str.c_str(), str.length()); + sendRequest(); +} + +void XMLHttpRequest::sendBinary(const Data &data) { + setHttpRequestData(reinterpret_cast(data.getBytes()), data.getSize()); + sendRequest(); +} + +void XMLHttpRequest::setTimeout(uint32_t timeoutInMilliseconds) { + _timeoutInMilliseconds = timeoutInMilliseconds; + _httpRequest->setTimeout(static_cast(timeoutInMilliseconds) / 1000.0F + 2.0F); // Add 2 seconds more to ensure the timeout scheduler is invoked before http response. +} + +uint32_t XMLHttpRequest::getTimeout() const { + return static_cast(_httpRequest->getTimeout() * 1000); +} + +void XMLHttpRequest::abort() { + if (!_isLoadStart) { + return; + } + + _isAborted = true; + _isSending = false; + + setReadyState(ReadyState::DONE); + + // Unregister timeout timer while abort is invoked. + _scheduler->unscheduleAllForTarget(this); + + if (onabort != nullptr) { + onabort(); + } + + _isLoadEnd = true; + if (onloadend != nullptr) { + onloadend(); + } + + _readyState = ReadyState::UNSENT; + + //request is aborted, no more callback needed. + _httpRequest->setResponseCallback(nullptr); +} + +void XMLHttpRequest::setReadyState(ReadyState readyState) { + if (_readyState != readyState) { + _readyState = readyState; + if (onreadystatechange != nullptr) { + onreadystatechange(); + } + } +} + +void XMLHttpRequest::getHeader(const ccstd::string &header) { + // check for colon. + size_t found_header_field = header.find_first_of(':'); // NOLINT(readability-identifier-naming) + + if (found_header_field != ccstd::string::npos) { + // Found a header field. + ccstd::string http_field; // NOLINT(readability-identifier-naming) + ccstd::string http_value; // NOLINT(readability-identifier-naming) + + http_field = header.substr(0, found_header_field); + http_value = header.substr(found_header_field + 1, header.length()); + + // trim \n at the end of the string + if (!http_value.empty() && http_value[http_value.size() - 1] == '\n') { + http_value.erase(http_value.size() - 1); + } + + // trim leading space (header is field: value format) + if (!http_value.empty() && http_value[0] == ' ') { + http_value.erase(0, 1); + } + + // Transform field name to lower case as they are case-insensitive + std::transform(http_field.begin(), http_field.end(), http_field.begin(), ::tolower); + + _httpHeader[http_field] = http_value; + + } else { + // Get Header and Set StatusText + // Split String into Tokens + if (header.find("HTTP") == 0) { + int _v1; // NOLINT(readability-identifier-naming) + int _v2; // NOLINT(readability-identifier-naming) + int code = 0; + char statusText[64] = {0}; + sscanf(header.c_str(), "HTTP/%d.%d %d %63[^\n]", &_v1, &_v2, &code, statusText); + _statusText = statusText; + if (_statusText.empty()) { + auto itCode = _httpStatusCodeMap.find(code); + if (itCode != _httpStatusCodeMap.end()) { + _statusText = itCode->second; + } else { + CC_LOG_DEBUG("XMLHTTPRequest invalid response code %d", code); + } + } + } + } +} + +void XMLHttpRequest::onResponse(HttpClient * /*client*/, HttpResponse *response) { + CC_ASSERT(_scheduler); + _scheduler->unscheduleAllForTarget(this); + _isSending = false; + + if (_isTimeout) { + _isLoadEnd = true; + if (onloadend) { + onloadend(); + } + return; + } + + if (_isAborted || _readyState == ReadyState::UNSENT) { + return; + } + + ccstd::string tag = response->getHttpRequest()->getTag(); + if (!tag.empty()) { + SE_LOGD("XMLHttpRequest::onResponse, %s completed\n", tag.c_str()); + } + + auto statusCode = response->getResponseCode(); + char statusString[64] = {0}; + sprintf(statusString, "HTTP Status Code: %ld, tag = %s", statusCode, tag.c_str()); + + _responseText.clear(); + _responseData.clear(); + + if (!response->isSucceed()) { + ccstd::string errorBuffer = response->getErrorBuffer(); + SE_LOGD("Response failed, error buffer: %s\n", errorBuffer.c_str()); + if (statusCode == 0 || statusCode == -1) { + _errorFlag = true; + _status = 0; + _statusText.clear(); + if (onerror != nullptr) { + onerror(); + } + + _isLoadEnd = true; + if (onloadend != nullptr) { + onloadend(); + } + return; + } + } + + // set header + ccstd::vector *headers = response->getResponseHeader(); + + ccstd::string header(headers->begin(), headers->end()); + + std::istringstream stream(header); + ccstd::string line; + while (std::getline(stream, line)) { + getHeader(line); + } + + /** get the response data **/ + ccstd::vector *buffer = response->getResponseData(); + + if (_responseType == ResponseType::STRING || _responseType == ResponseType::JSON) { + _responseText.append(buffer->data(), buffer->size()); + } else { + _responseData.copy(reinterpret_cast(buffer->data()), static_cast(buffer->size())); + } + + _status = statusCode; + + setReadyState(ReadyState::DONE); + + if (onload != nullptr) { + onload(); + } + + _isLoadEnd = true; + if (onloadend != nullptr) { + onloadend(); + } +} + +void XMLHttpRequest::overrideMimeType(const ccstd::string &mimeType) { + _overrideMimeType = mimeType; +} + +ccstd::string XMLHttpRequest::getMimeType() const { + if (!_overrideMimeType.empty()) { + return _overrideMimeType; + } + auto contentType = getResponseHeader("Content-Type"); + return contentType.empty() ? "text" : contentType; +} + +void XMLHttpRequest::sendRequest() { + if (_isSending) { + //ref https://xhr.spec.whatwg.org/#the-send()-method + // TODO(unknown): if send() flag is set, an exception should be thrown out. + return; + } + _isSending = true; + _isTimeout = false; + if (_timeoutInMilliseconds > 0) { + CC_ASSERT(_scheduler); + _scheduler->schedule([this](float /* dt */) { + if (ontimeout != nullptr) { + ontimeout(); + } + _isTimeout = true; + _readyState = ReadyState::UNSENT; + }, + this, static_cast(_timeoutInMilliseconds) / 1000.0F, 0, 0.0F, false, "XMLHttpRequest"); + } + setHttpRequestHeader(); + + _httpRequest->setResponseCallback(CC_CALLBACK_2(XMLHttpRequest::onResponse, this)); //NOLINT + cc::network::HttpClient::getInstance()->sendImmediate(_httpRequest); + + if (onloadstart != nullptr) { + onloadstart(); + } + + _isLoadStart = true; +} + +void XMLHttpRequest::setHttpRequestData(const char *data, size_t len) { + if (len > 0 && + (_method == "post" || _method == "POST" || _method == "put" || _method == "PUT" || _method == "patch" || _method == "PATCH")) { + _httpRequest->setRequestData(data, len); + } +} + +void XMLHttpRequest::setRequestHeader(const ccstd::string &key, const ccstd::string &value) { + std::stringstream header_s; // NOLINT(readability-identifier-naming) + std::stringstream value_s; // NOLINT(readability-identifier-naming) + ccstd::string header; + + auto iter = _requestHeader.find(key); + + // Concatenate values when header exists. + if (iter != _requestHeader.end()) { + value_s << iter->second << "," << value; + } else { + value_s << value; + } + + _requestHeader[key] = value_s.str(); +} + +ccstd::string XMLHttpRequest::getAllResponseHeaders() const { + std::stringstream responseHeaders; + ccstd::string responseHeader; + + for (const auto &it : _httpHeader) { + responseHeaders << it.first << ": " << it.second << "\n"; + } + + responseHeader = responseHeaders.str(); + return responseHeader; +} + +ccstd::string XMLHttpRequest::getResponseHeader(const ccstd::string &key) const { + ccstd::string ret; + ccstd::string value = key; + std::transform(value.begin(), value.end(), value.begin(), ::tolower); + + auto iter = _httpHeader.find(value); + if (iter != _httpHeader.end()) { + ret = iter->second; + } + return ret; +} + +void XMLHttpRequest::setHttpRequestHeader() { + ccstd::vector headers; + + for (auto &it : _requestHeader) { + headers.emplace_back(it.first + ": " + it.second); + } + + if (!headers.empty()) { + _httpRequest->setHeaders(headers); + } +} + +se::Class *__jsb_XMLHttpRequest_class = nullptr; //NOLINT(readability-identifier-naming, bugprone-reserved-identifier) + +static bool XMLHttpRequest_finalize(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *request = static_cast(s.nativeThisObject()); + SE_LOGD("XMLHttpRequest_finalize, %p ... \n", request); + request->clearCallbacks(); + return true; +} +SE_BIND_FINALIZE_FUNC(XMLHttpRequest_finalize) + +static bool XMLHttpRequest_constructor(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *request = ccnew XMLHttpRequest(); + s.thisObject()->setPrivateData(request); + + se::Value thiz(s.thisObject()); + + auto cb = [thiz](const char *eventName) { + se::ScriptEngine::getInstance()->clearException(); + se::AutoHandleScope hs; + + se::Object *thizObj = thiz.toObject(); + + se::Value func; + if (thizObj->getProperty(eventName, &func) && func.isObject() && func.toObject()->isFunction()) { + func.toObject()->call(se::EmptyValueArray, thizObj); + } + }; + + request->onloadstart = [=]() { + if (!request->isDiscardedByReset()) { + thiz.toObject()->root(); + cb("onloadstart"); + } + }; + request->onload = [=]() { + if (!request->isDiscardedByReset()) { + cb("onload"); + } + }; + request->onloadend = [=]() { + if (!request->isDiscardedByReset()) { + // SE_LOGD("XMLHttpRequest (%p) onloadend ...\n", request); + cb("onloadend"); + thiz.toObject()->unroot(); + } else { + SE_LOGD("XMLHttpRequest (%p) onloadend after restart ScriptEngine.\n", request); + request->release(); + } + }; + request->onreadystatechange = [=]() { + if (!request->isDiscardedByReset()) { + cb("onreadystatechange"); + } + }; + request->onabort = [=]() { + if (!request->isDiscardedByReset()) { + cb("onabort"); + } + }; + request->onerror = [=]() { + if (!request->isDiscardedByReset()) { + cb("onerror"); + } + }; + request->ontimeout = [=]() { + if (!request->isDiscardedByReset()) { + cb("ontimeout"); + } + }; + return true; +} +SE_BIND_CTOR(XMLHttpRequest_constructor, __jsb_XMLHttpRequest_class, XMLHttpRequest_finalize) + +static bool XMLHttpRequest_open(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc >= 2) { + auto *request = static_cast(s.nativeThisObject()); + bool ok = false; + ccstd::string method; + ok = sevalue_to_native(args[0], &method); + SE_PRECONDITION2(ok, false, "args[0] isn't a string."); + ccstd::string url; + ok = sevalue_to_native(args[1], &url); + SE_PRECONDITION2(ok, false, "args[1] isn't a string."); + bool ret = request->open(method, url); + s.rval().setBoolean(ret); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting >=2", (int)argc); + return false; +} +SE_BIND_FUNC(XMLHttpRequest_open) + +static bool XMLHttpRequest_abort(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *request = static_cast(s.nativeThisObject()); + request->abort(); + return true; +} +SE_BIND_FUNC(XMLHttpRequest_abort) + +static bool XMLHttpRequest_send(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + const auto &args = s.args(); + size_t argc = args.size(); + auto *request = static_cast(s.nativeThisObject()); + + if (argc == 0) { + request->send(); + } else if (argc > 0) { + const auto &arg0 = args[0]; + if (arg0.isNullOrUndefined()) { + request->send(); + } else if (arg0.isString()) { + request->sendString(arg0.toString()); + } else if (arg0.isObject()) { + se::Object *obj = arg0.toObject(); + + if (obj->isTypedArray()) { + uint8_t *ptr = nullptr; + size_t len = 0; + if (obj->getTypedArrayData(&ptr, &len)) { + Data data; + data.copy(ptr, static_cast(len)); + request->sendBinary(data); + } else { + SE_REPORT_ERROR("Failed to get data of TypedArray!"); + return false; + } + } else if (obj->isArrayBuffer()) { + uint8_t *ptr = nullptr; + size_t len = 0; + if (obj->getArrayBufferData(&ptr, &len)) { + Data data; + data.copy(ptr, static_cast(len)); + request->sendBinary(data); + } else { + SE_REPORT_ERROR("Failed to get data of ArrayBufferObject!"); + return false; + } + } else { + SE_REPORT_ERROR("args[0] isn't a typed array or an array buffer"); + return false; + } + } else { + const char *typeName = "UNKNOWN"; + if (arg0.isBoolean()) { + typeName = "boolean"; + } else if (arg0.isNumber()) { + typeName = "number"; + } + + SE_REPORT_ERROR("args[0] type: %s isn't supported!", typeName); + return false; + } + } + + return true; +} +SE_BIND_FUNC(XMLHttpRequest_send) + +static bool XMLHttpRequest_setRequestHeader(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + const auto &args = s.args(); + size_t argc = args.size(); + + if (argc >= 2) { + auto *xhr = static_cast(s.nativeThisObject()); + ccstd::string key; + bool ok = sevalue_to_native(args[0], &key); + SE_PRECONDITION2(ok, false, "args[0] couldn't be converted to string."); + ccstd::string value; + ok = sevalue_to_native(args[1], &value); + SE_PRECONDITION2(ok, false, "args[1] couldn't be converted to string."); + xhr->setRequestHeader(key, value); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting >=2", (int)argc); + return false; +} +SE_BIND_FUNC(XMLHttpRequest_setRequestHeader) + +static bool XMLHttpRequest_getAllResponseHeaders(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *xhr = static_cast(s.nativeThisObject()); + ccstd::string headers = xhr->getAllResponseHeaders(); + s.rval().setString(headers); + return true; +} +SE_BIND_FUNC(XMLHttpRequest_getAllResponseHeaders) + +static bool XMLHttpRequest_getResonpseHeader(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + const auto &args = s.args(); + size_t argc = args.size(); + if (argc > 0) { + auto *xhr = static_cast(s.nativeThisObject()); + ccstd::string key; + bool ok = sevalue_to_native(args[0], &key); + SE_PRECONDITION2(ok, false, "args[0] couldn't be converted to string."); + ccstd::string header = xhr->getResponseHeader(key); + s.rval().setString(header); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting > 0", (int)argc); + return false; +} +SE_BIND_FUNC(XMLHttpRequest_getResonpseHeader) + +static bool XMLHttpRequest_overrideMimeType(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc > 0 && args[0].isString()) { + ccstd::string mimeType; + sevalue_to_native(args[0], &mimeType); + auto *xhr = static_cast(s.nativeThisObject()); + xhr->overrideMimeType(mimeType); + } + return true; +} +SE_BIND_FUNC(XMLHttpRequest_overrideMimeType) + +static bool XMLHttpRequest_getMIMEType(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *xhr = static_cast(s.nativeThisObject()); + auto type = xhr->getMimeType(); + s.rval().setString(type); + return true; +} +SE_BIND_PROP_GET(XMLHttpRequest_getMIMEType) + +// getter + +static bool XMLHttpRequest_getReadyState(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *xhr = static_cast(s.nativeThisObject()); + s.rval().setInt32(static_cast(xhr->getReadyState())); + return true; +} +SE_BIND_PROP_GET(XMLHttpRequest_getReadyState) + +static bool XMLHttpRequest_getStatus(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *xhr = static_cast(s.nativeThisObject()); + s.rval().setUint16(xhr->getStatus()); + return true; +} +SE_BIND_PROP_GET(XMLHttpRequest_getStatus) + +static bool XMLHttpRequest_getStatusText(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *xhr = static_cast(s.nativeThisObject()); + s.rval().setString(xhr->getStatusText()); + return true; +} +SE_BIND_PROP_GET(XMLHttpRequest_getStatusText) + +static bool XMLHttpRequest_getResponseText(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *xhr = static_cast(s.nativeThisObject()); + s.rval().setString(xhr->getResponseText()); + return true; +} +SE_BIND_PROP_GET(XMLHttpRequest_getResponseText) + +static bool XMLHttpRequest_getResponseXML(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + // DOM API is not fully supported in cocos2d-x-lite. + // `.responseXML` requires a document object that is not possible to fulfill. + s.rval().setNull(); + return true; +} +SE_BIND_PROP_GET(XMLHttpRequest_getResponseXML) + +static bool XMLHttpRequest_getResponse(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *xhr = static_cast(s.nativeThisObject()); + + if (xhr->getResponseType() == XMLHttpRequest::ResponseType::STRING) { + s.rval().setString(xhr->getResponseText()); + } else { + if (xhr->getReadyState() != XMLHttpRequest::ReadyState::DONE) { + s.rval().setNull(); + } else { + if (xhr->getResponseType() == XMLHttpRequest::ResponseType::JSON) { + const ccstd::string &jsonText = xhr->getResponseText(); + se::HandleObject seObj(se::Object::createJSONObject(jsonText)); + if (!seObj.isEmpty()) { + s.rval().setObject(seObj); + } else { + s.rval().setNull(); + } + } else if (xhr->getResponseType() == XMLHttpRequest::ResponseType::ARRAY_BUFFER) { + const Data &data = xhr->getResponseData(); + se::HandleObject seObj(se::Object::createArrayBufferObject(data.getBytes(), data.getSize())); + if (!seObj.isEmpty()) { + s.rval().setObject(seObj); + } else { + s.rval().setNull(); + } + } else if (xhr->getResponseType() == XMLHttpRequest::ResponseType::BLOB) { + SE_PRECONDITION2(false, false, "Don't support blob response type"); + } else { + SE_PRECONDITION2(false, false, "Invalid response type"); + } + } + } + return true; +} +SE_BIND_PROP_GET(XMLHttpRequest_getResponse) + +static bool XMLHttpRequest_getTimeout(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *cobj = static_cast(s.nativeThisObject()); + s.rval().setUint32(cobj->getTimeout()); + return true; +} +SE_BIND_PROP_GET(XMLHttpRequest_getTimeout) + +static bool XMLHttpRequest_setTimeout(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + const auto &args = s.args(); + int argc = static_cast(args.size()); + if (argc > 0) { + auto *cobj = static_cast(s.nativeThisObject()); + uint32_t timeoutInMilliseconds = 0; + bool ok = sevalue_to_native(args[0], &timeoutInMilliseconds); + SE_PRECONDITION2(ok, false, "args[0] isn't a number"); + if (timeoutInMilliseconds < 50) { + SE_LOGE("The timeout value (%u ms) is too small, please note that timeout unit is milliseconds!", timeoutInMilliseconds); + } + cobj->setTimeout(timeoutInMilliseconds); + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting > 0", argc); + return false; +} +SE_BIND_PROP_SET(XMLHttpRequest_setTimeout) + +static bool XMLHttpRequest_getResponseType(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + auto *xhr = static_cast(s.nativeThisObject()); + switch (xhr->getResponseType()) { + case XMLHttpRequest::ResponseType::STRING: + s.rval().setString("text"); + break; + case XMLHttpRequest::ResponseType::ARRAY_BUFFER: + s.rval().setString("arraybuffer"); + break; + case XMLHttpRequest::ResponseType::JSON: + s.rval().setString("json"); + break; + default: + break; + } + return true; +} +SE_BIND_PROP_GET(XMLHttpRequest_getResponseType) + +static bool XMLHttpRequest_setResponseType(se::State &s) { //NOLINT(readability-identifier-naming, google-runtime-references) + const auto &args = s.args(); + size_t argc = args.size(); + + if (argc > 0) { + ccstd::string type; + bool ok = sevalue_to_native(args[0], &type); + SE_PRECONDITION2(ok, false, "args[0] couldn't be converted to string!"); + + auto *xhr = static_cast(s.nativeThisObject()); + if (type == "text") { + xhr->setResponseType(XMLHttpRequest::ResponseType::STRING); + } else if (type == "arraybuffer") { + xhr->setResponseType(XMLHttpRequest::ResponseType::ARRAY_BUFFER); + } else if (type == "json") { + xhr->setResponseType(XMLHttpRequest::ResponseType::JSON); + } else if (type == "document") { + xhr->setResponseType(XMLHttpRequest::ResponseType::DOCUMENT); + } else if (type == "blob") { + xhr->setResponseType(XMLHttpRequest::ResponseType::BLOB); + } else { + SE_PRECONDITION2(false, false, "The response type isn't supported!"); + } + return true; + } + + SE_REPORT_ERROR("wrong number of arguments: %d, was expecting > 0", (int)argc); + return false; +} +SE_BIND_PROP_SET(XMLHttpRequest_setResponseType) + +static bool XMLHttpRequest_getWithCredentials(se::State & /*s*/) { //NOLINT(readability-identifier-naming, google-runtime-references) + SE_LOGD("XMLHttpRequest.withCredentials isn't implemented on JSB!"); + return true; +} +SE_BIND_PROP_GET(XMLHttpRequest_getWithCredentials) + +bool register_all_xmlhttprequest(se::Object *global) { //NOLINT(readability-identifier-naming) + se::Value nsVal; + if (!global->getProperty("jsb", &nsVal, true)) { + se::HandleObject jsobj(se::Object::createPlainObject()); + nsVal.setObject(jsobj); + global->setProperty("jsb", nsVal); + } + se::Object *ns = nsVal.toObject(); + se::Class *cls = se::Class::create("XMLHttpRequest", ns, nullptr, _SE(XMLHttpRequest_constructor)); + cls->defineFinalizeFunction(_SE(XMLHttpRequest_finalize)); + + cls->defineFunction("open", _SE(XMLHttpRequest_open)); + cls->defineFunction("abort", _SE(XMLHttpRequest_abort)); + cls->defineFunction("send", _SE(XMLHttpRequest_send)); + cls->defineFunction("setRequestHeader", _SE(XMLHttpRequest_setRequestHeader)); + cls->defineFunction("getAllResponseHeaders", _SE(XMLHttpRequest_getAllResponseHeaders)); + cls->defineFunction("getResponseHeader", _SE(XMLHttpRequest_getResonpseHeader)); + cls->defineFunction("overrideMimeType", _SE(XMLHttpRequest_overrideMimeType)); + cls->defineProperty("__mimeType", _SE(XMLHttpRequest_getMIMEType), nullptr); + + cls->defineProperty("readyState", _SE(XMLHttpRequest_getReadyState), nullptr); + cls->defineProperty("status", _SE(XMLHttpRequest_getStatus), nullptr); + cls->defineProperty("statusText", _SE(XMLHttpRequest_getStatusText), nullptr); + cls->defineProperty("responseText", _SE(XMLHttpRequest_getResponseText), nullptr); + cls->defineProperty("responseXML", _SE(XMLHttpRequest_getResponseXML), nullptr); + cls->defineProperty("response", _SE(XMLHttpRequest_getResponse), nullptr); + cls->defineProperty("timeout", _SE(XMLHttpRequest_getTimeout), _SE(XMLHttpRequest_setTimeout)); + cls->defineProperty("responseType", _SE(XMLHttpRequest_getResponseType), _SE(XMLHttpRequest_setResponseType)); + cls->defineProperty("withCredentials", _SE(XMLHttpRequest_getWithCredentials), nullptr); + + cls->install(); + + JSBClassType::registerClass(cls); + + __jsb_XMLHttpRequest_class = cls; + + se::ScriptEngine::getInstance()->clearException(); + + return true; +} diff --git a/cocos/bindings/manual/jsb_xmlhttprequest.h b/cocos/bindings/manual/jsb_xmlhttprequest.h new file mode 100644 index 0000000..d9d7518 --- /dev/null +++ b/cocos/bindings/manual/jsb_xmlhttprequest.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace se { +class Object; +} + +bool register_all_xmlhttprequest(se::Object *global); // NOLINT (readability-identifier-naming) diff --git a/cocos/bindings/sebind/class.inl b/cocos/bindings/sebind/class.inl new file mode 100644 index 0000000..ba078e5 --- /dev/null +++ b/cocos/bindings/sebind/class.inl @@ -0,0 +1,414 @@ +/**************************************************************************** + Copyright (c) 2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include "base/memory/Memory.h" +#include "bindings/jswrapper/SeApi.h" +#include "bindings/jswrapper/Value.h" + + +#include "bindings/manual/jsb_conversions.h" +#include "bindings/manual/jsb_global.h" +#include "intl/common.h" + +namespace sebind { + +/** + * @brief Remove all cached data. Should be invoked before restart. + * + */ +inline void reset() { + intl::ContextDB::reset(); +} + +/** + * @brief Export C++ Class/Funtions to JS + * + * @tparam T C++ type + */ +template +class class_ final { //NOLINT +public: + using class_type = T; + using Context = intl::ContextDB::Context; + + explicit class_(Context *ctx) : _ctx(ctx) {} + + /** + * @brief Construct a new class_ object + * + * @param name specify the class name in JS + */ + explicit class_(const char *name); + + /** + * @brief Construct a new class_ object + * + * @param name Specify the class name in JS + * @param parentProto The prototype object of the parent class + */ + explicit class_(const char *name, se::Object *parentProto); + + ~class_() { + assert(_installed); // procedure `class_::install` has not been invoked? + } + + /** + * @brief Attach the function object to the namespace object, + * then the exported class can be accessed through the namespace object in JS. + * And the namespace object is a global object in most cases. + * + * @param nsObject the namespace object + * @return true + */ + bool install(se::Object *nsObject); + + /** + * @brief Define a constructor by argument type list + * + * The parameter list must match a specific constructor. + * + * @tparam ARGS parameter types for a constructor of class T + * @return class_& + */ + template + class_ &constructor(); + + /** + * @brief Define a constructor by a function + * + * The signature of the function pointer can be + * - `bool(*)(se::State&)` + * or + * - `T*(*)(ARGS...)` + * + * @tparam F + * @param callback The function pointer + * @return class_& + */ + template + class_ &constructor(F callback); + + /** + * @brief Register a callback when GC occurs + * @param callback GC callback + * @return class_& + */ + class_ &finalizer(void (*callback)(T *)); + + /** + * @brief Define a member function for js class + * + * @tparam Method + * @param name The method name + * @param method Member function pointer of class `T`, or normal function which the first argument is `T*`. + * @return class_& + */ + template + class_ &function(const char *name, Method method); + + /** + * @brief Define a property for js class + * + * @tparam Field + * @param name Field name + * @param field Member data pointer of class `T` + * @return class_& + */ + template + class_ &property(const char *name, Field field); + + /** + * @brief Define a property for JS class + * + * The getter and setter can not be both null. + * @tparam Getter Member function pointer or normal function pointer which first parameter is `T*`. + * @tparam Setter Member function pointer or normal function pointer which first parameter is `T*`. + * @param name Property name + * @param getter Getter function pointer + * @param setter Setter function pointer + * @return class_& + */ + template + class_ &property(const char *name, Getter getter, Setter setter); + + /** + * @brief Define a static property for JS class + * + * @tparam Method static function pointer or normal function pointer. + * @param name property name + * @param method function pointer + * @return class_& + */ + template + class_ &staticFunction(const char *name, Method method); + + /** + * @brief Define a static property for JS class + * + * The getter and setter can not be both null. + * + * @tparam Getter Static function pointer or normal function pointer. + * @tparam Setter Static function pointer or normal function pointer. + * @param name property name + * @param getter function pointer + * @param setter function pointer + * @return class_& + */ + template + class_ &staticProperty(const char *name, Getter getter, Setter setter); + + /** + * @brief Define a constructor with verbose callback. + * + * @param callback function pointer + * @return class_& + */ + class_ &constructor(SeCallbackFnPtr callback); + + /** + * @brief Define a function with verbose callback. + * + * @param name function name + * @param callback function pointer + * @return class_& + */ + class_ &function(const char *name, SeCallbackFnPtr callback); + + /** + * @brief Define a property with verbose callback. + * getter and setter can not be both null + * @param name property name + * @param getter function pointer + * @param setter function pointer + * @return class_& + */ + class_ &property(const char *name, SeCallbackFnPtr getter, SeCallbackFnPtr setter); + + /** + * @brief Define static function with verbose callback + * + * @param name static function name + * @param callback + * @return class_& + */ + class_ &staticFunction(const char *name, SeCallbackFnPtr callback); + + /** + * @brief Define a property with verbose callback. + * getter and setter can not be both null + * @param name property name + * @param getter function pointer + * @param setter function pointer + * @return class_& + */ + class_ &staticProperty(const char *name, SeCallbackFnPtr getter, SeCallbackFnPtr setter); + + /** + * @brief Prototype of JS class, only valid after invoke install. + * + * @return se::Object* + */ + se::Object *prototype() { + return _ctx->kls->getProto(); + } + +private: + bool _installed{false}; + Context *_ctx{nullptr}; + template + friend void genericConstructor(const v8::FunctionCallbackInfo &); +}; + +// implements + +template +class_::class_(const char *name) { + _ctx = intl::ContextDB::instance()[name]; + _ctx->className = name; +} + +template +class_::class_(const char *name, se::Object *parentProto) { + _ctx = intl::ContextDB::instance()[name]; + _ctx->className = name; + _ctx->parentProto = parentProto; +} + +template +template +class_ &class_::constructor() { + using CTYPE = intl::Constructor>; + using MTYPE = intl::TypeMapping>; + static_assert(intl::IsConstructibleWithTypeList::value, "No matched constructor found"); + auto *constructp = ccnew CTYPE(); + constructp->argCount = MTYPE::NEW_ARGN; + _ctx->constructors.emplace_back(constructp); + return *this; +} + +template +template +class_ &class_::constructor(F callback) { + using FTYPE = intl::StaticFunctionWrapper; + static_assert(std::is_same::value, "Function should return a instance pointer"); + using CTYPE = intl::Constructor; + auto *constructp = ccnew CTYPE(); + constructp->argCount = FTYPE::ARG_N; + constructp->func = callback; + _ctx->constructors.emplace_back(constructp); + return *this; +} + +template +class_ &class_::constructor(SeCallbackFnPtr callback) { + auto *constructp = ccnew intl::ConstructorBase(); + constructp->argCount = -1; + constructp->bfnPtr = callback; + _ctx->constructors.emplace_back(constructp); + return *this; +} + +template +class_ &class_::finalizer(void (*callback)(T *)) { + auto *fin = ccnew intl::Finalizer(); + fin->func = callback; + _ctx->finalizeCallbacks.emplace_back(fin); + return *this; +} + +template +template +class_ &class_::function(const char *name, Method method) { + using MTYPE = intl::InstanceMethod; + static_assert(std::is_base_of::value, "incorrect class type"); + auto *methodp = ccnew MTYPE(); + methodp->methodName = name; + methodp->className = _ctx->className; + methodp->argCount = MTYPE::ARG_N; + methodp->func = method; + _ctx->functions.emplace_back(name, methodp); + return *this; +} + +template +class_ &class_::function(const char *name, SeCallbackFnPtr callback) { + auto *methodp = ccnew intl::InstanceMethodBase(); + methodp->methodName = name; + methodp->className = _ctx->className; + methodp->argCount = -1; + methodp->bfnPtr = callback; + _ctx->functions.emplace_back(name, methodp); + return *this; +} + +template +template +class_ &class_::property(const char *name, Field field) { + static_assert(std::is_member_pointer::value, "2nd parameter should be a member pointer"); + using FTYPE = intl::InstanceField; + static_assert(std::is_base_of::value, "class_type incorrect"); + auto *fieldp = ccnew FTYPE(); + fieldp->func = field; + fieldp->attrName = name; + fieldp->className = _ctx->className; + _ctx->fields.emplace_back(name, fieldp); + return *this; +} + +template +template +class_ &class_::property(const char *name, Getter getter, Setter setter) { + using ATYPE = intl::InstanceAttribute>; + auto *attrp = ccnew ATYPE(); + attrp->getterPtr = ATYPE::HAS_GETTER ? getter : nullptr; + attrp->setterPtr = ATYPE::HAS_SETTER ? setter : nullptr; + attrp->className = _ctx->className; + attrp->attrName = name; + _ctx->properties.emplace_back(name, attrp); + return *this; +} + +template +class_ &class_::property(const char *name, SeCallbackFnPtr getter, SeCallbackFnPtr setter) { + auto *attrp = ccnew intl::InstanceAttributeBase(); + attrp->bfnGetPtr = getter; + attrp->bfnSetPtr = setter; + attrp->className = _ctx->className; + attrp->attrName = name; + _ctx->properties.emplace_back(name, attrp); + return *this; +} + +template +template +class_ &class_::staticFunction(const char *name, Method method) { + using MTYPE = intl::StaticMethod; + auto *methodp = ccnew MTYPE(); + methodp->methodName = name; + methodp->className = _ctx->className; + methodp->argCount = MTYPE::ARG_N; + methodp->func = method; + _ctx->staticFunctions.emplace_back(name, methodp); + return *this; +} + +template +class_ &class_::staticFunction(const char *name, SeCallbackFnPtr callback) { + auto *methodp = ccnew intl::StaticMethodBase(); + methodp->methodName = name; + methodp->className = _ctx->className; + methodp->argCount = -1; + methodp->bfnPtr = callback; + _ctx->staticFunctions.emplace_back(name, methodp); + return *this; +} + +template +template +class_ &class_::staticProperty(const char *name, Getter getter, Setter setter) { + using ATYPE = intl::StaticAttribute>; + auto *attrp = ccnew ATYPE(); + attrp->getterPtr = ATYPE::HAS_GETTER ? getter : nullptr; + attrp->setterPtr = ATYPE::HAS_SETTER ? setter : nullptr; + attrp->className = _ctx->className; + attrp->attrName = name; + _ctx->staticProperties.emplace_back(name, attrp); + return *this; +} + +template +class_ &class_::staticProperty(const char *name, SeCallbackFnPtr getter, SeCallbackFnPtr setter) { + auto *attrp = ccnew intl::StaticAttributeBase(); + attrp->bfnGetPtr = getter; + attrp->bfnSetPtr = setter; + attrp->className = _ctx->className; + attrp->attrName = name; + _ctx->staticProperties.emplace_back(name, attrp); + return *this; +} + +} // namespace sebind diff --git a/cocos/bindings/sebind/class_v8.cpp b/cocos/bindings/sebind/class_v8.cpp new file mode 100644 index 0000000..34f04c4 --- /dev/null +++ b/cocos/bindings/sebind/class_v8.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "class_v8.h" + +namespace sebind { +// adoption layer from v8 function to se function +void genericFunction(const v8::FunctionCallbackInfo &v8args) { + void *ctx = v8args.Data().IsEmpty() ? nullptr : v8args.Data().As()->Value(); + auto *method = reinterpret_cast(ctx); + assert(ctx); + bool ret = false; + v8::Isolate *isolate = v8args.GetIsolate(); + v8::HandleScope handleScope(isolate); + bool needDeleteValueArray{false}; + se::ValueArray &args = se::gValueArrayPool.get(v8args.Length(), needDeleteValueArray); + se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; + se::internal::jsToSeArgs(v8args, args); + auto *thisObject = reinterpret_cast(se::internal::getPrivate(isolate, v8args.This())); + se::State state(thisObject, args); + ret = method->invoke(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", method->methodName.c_str(), __FILE__, __LINE__); + } + se::internal::setReturnValue(state.rval(), v8args); +} + +} // namespace sebind diff --git a/cocos/bindings/sebind/class_v8.h b/cocos/bindings/sebind/class_v8.h new file mode 100644 index 0000000..50ade1b --- /dev/null +++ b/cocos/bindings/sebind/class_v8.h @@ -0,0 +1,206 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include "class.inl" +#include "cocos/bindings/jswrapper/ValueArrayPool.h" + +namespace sebind { +// finalizer callback +template +void genericFinalizer(se::Object *obj) { + se::PrivateObjectBase *privateObject = obj->getPrivateObject(); + using context_type = typename class_::Context; + if (privateObject == nullptr) { + return; + } + auto *scriptEngine = se::ScriptEngine::getInstance(); + scriptEngine->_setGarbageCollecting(true); + auto *self = reinterpret_cast(privateObject->finalizerData); + auto *thisPtr = privateObject->get(); + for (auto &fin : self->finalizeCallbacks) { + fin->finalize(thisPtr); + } + scriptEngine->_setGarbageCollecting(false); +} + +// function wrappers for v8 +template +void genericConstructor(const v8::FunctionCallbackInfo &v8args) { + using context_type = typename class_::Context; + v8::Isolate *isolate = v8args.GetIsolate(); + v8::HandleScope handleScope(isolate); + int ctorInvokeTimes{0}; + bool constructed{false}; + bool needDeleteValueArray{false}; + se::ValueArray &args = se::gValueArrayPool.get(v8args.Length(), needDeleteValueArray); + se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; + se::internal::jsToSeArgs(v8args, args); + auto *self = reinterpret_cast(v8args.Data().IsEmpty() ? nullptr : v8args.Data().As()->Value()); + se::Object *thisObject = se::Object::_createJSObject(self->kls, v8args.This()); + if (!self->finalizeCallbacks.empty()) { + auto *finalizer = &genericFinalizer; + thisObject->_setFinalizeCallback(finalizer); + } + se::State state(thisObject, args); + + assert(!self->constructors.empty()); + for (auto &ctor : self->constructors) { + if (ctor->argCount == -1 || ctor->argCount == args.size()) { + ctorInvokeTimes++; + constructed = ctor->construct(state); + if (constructed) break; + } + } + + if (ctorInvokeTimes == 0) { + SE_LOGE("[ERROR] Failed match constructor for class %s, %d args, location: %s:%d\n", self->className.c_str(), + static_cast(args.size()), __FILE__, __LINE__); + } + + if (!constructed) { + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", "constructor", __FILE__, __LINE__); + } + + assert(constructed); // construction failure is not allowed. + + if (!self->finalizeCallbacks.empty()) { + state.thisObject()->getPrivateObject()->finalizerData = self; + } + + se::Value propertyVal; + if (thisObject->getProperty("_ctor", &propertyVal, true)) { + propertyVal.toObject()->call(args, thisObject); + } +} +// v8 property callback +template +void genericAccessorSet(const v8::FunctionCallbackInfo &v8args) { + auto *attr = reinterpret_cast(v8args.Data().IsEmpty() ? nullptr : v8args.Data().As()->Value()); + assert(attr); + v8::Isolate *isolate = v8args.GetIsolate(); + v8::HandleScope handleScope(isolate); + bool ret = true; + auto *thisObject = reinterpret_cast(se::internal::getPrivate(isolate, v8args.This())); + bool needDeleteValueArray{false}; + se::ValueArray &args = se::gValueArrayPool.get(1, needDeleteValueArray); + se::CallbackDepthGuard depthGuard{args, se::gValueArrayPool._depth, needDeleteValueArray}; + se::Value &data{args[0]}; + se::internal::jsToSeValue(isolate, v8args[0], &data); + se::State state(thisObject, args); + ret = attr->set(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke set %s, location: %s:%d\n", attr->attrName.c_str(), __FILE__, __LINE__); + } +} +template +void genericAccessorGet(const v8::FunctionCallbackInfo &v8args) { + auto *attr = reinterpret_cast(v8args.Data().IsEmpty() ? nullptr : v8args.Data().As()->Value()); + assert(attr); + v8::Isolate *isolate = v8args.GetIsolate(); + v8::HandleScope handleScope(isolate); + bool ret = true; + auto *thisObject = reinterpret_cast(se::internal::getPrivate(isolate, v8args.This())); + se::State state(thisObject); + ret = attr->get(state); + if (!ret) { + SE_LOGE("[ERROR] Failed to invoke %s, location: %s:%d\n", attr->attrName.c_str(), __FILE__, __LINE__); + } + se::internal::setReturnValue(state.rval(), v8args); +} + +void genericFunction(const v8::FunctionCallbackInfo &v8args); + +template +bool class_::install(se::Object *nsObject) { + constexpr auto *cstp = &genericConstructor; + assert(nsObject); + _installed = true; + + if (_ctx->constructors.empty()) { + if constexpr (std::is_default_constructible::value) { + constructor(); // add default constructor + } + } + _ctx->kls = se::Class::create(_ctx->className, nsObject, _ctx->parentProto, cstp, _ctx); + + auto *getter = &genericAccessorGet; + auto *setter = &genericAccessorSet; + for (auto &attr : _ctx->properties) { + _ctx->kls->defineProperty(std::get<0>(attr).c_str(), getter, setter, std::get<1>(attr).get()); + } + auto *fieldGetter = &genericAccessorGet; + auto *fieldSetter = &genericAccessorSet; + for (auto &field : _ctx->fields) { + _ctx->kls->defineProperty(std::get<0>(field).c_str(), fieldGetter, fieldSetter, std::get<1>(field).get()); + } + // defineFunctions + { + ccstd::unordered_map> multimap; + for (auto &method : _ctx->functions) { + multimap[std::get<0>(method)].emplace_back(std::get<1>(method).get()); + } + for (auto &method : multimap) { + if (method.second.size() > 1) { + auto *overloaded = ccnew intl::InstanceMethodOverloaded; + overloaded->className = _ctx->className; + overloaded->methodName = method.first; + for (auto *method : method.second) { + overloaded->functions.push_back(method); + } + _ctx->kls->defineFunction(method.first.c_str(), &genericFunction, overloaded); + } else { + _ctx->kls->defineFunction(method.first.c_str(), &genericFunction, method.second[0]); + } + } + } + // define static functions + { + ccstd::unordered_map> multimap; + for (auto &method : _ctx->staticFunctions) { + multimap[std::get<0>(method)].emplace_back(std::get<1>(method).get()); + } + for (auto &method : multimap) { + if (method.second.size() > 1) { + auto *overloaded = ccnew intl::StaticMethodOverloaded; + overloaded->className = _ctx->className; + overloaded->methodName = method.first; + for (auto *method : method.second) { + overloaded->functions.push_back(method); + } + _ctx->kls->defineStaticFunction(method.first.c_str(), &genericFunction, overloaded); + } else { + _ctx->kls->defineStaticFunction(method.first.c_str(), &genericFunction, method.second[0]); + } + } + } + for (auto &prop : _ctx->staticProperties) { + _ctx->kls->defineStaticProperty(std::get<0>(prop).c_str(), fieldGetter, fieldSetter, std::get<1>(prop).get()); + } + _ctx->kls->install(); + JSBClassType::registerClass(_ctx->kls); + + return true; +} +} // namespace sebind diff --git a/cocos/bindings/sebind/intl/common.cpp b/cocos/bindings/sebind/intl/common.cpp new file mode 100644 index 0000000..0d700ea --- /dev/null +++ b/cocos/bindings/sebind/intl/common.cpp @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "common.h" +namespace sebind { + +namespace intl { +ContextDB::Context *ContextDB::operator[](const char *key) { + auto itr = _contexts.find(key); + if (itr == _contexts.end()) { + auto *ctx = ccnew Context; + _contexts.emplace(key, ctx); + return ctx; + } + return itr->second.get(); +} + +ContextDB &ContextDB::instance() { + static ContextDB database; + return database; +} + +void ContextDB::reset() { + auto &inst = instance(); + inst._contexts.clear(); +} +} // namespace intl +} // namespace sebind diff --git a/cocos/bindings/sebind/intl/common.h b/cocos/bindings/sebind/intl/common.h new file mode 100644 index 0000000..7c800b7 --- /dev/null +++ b/cocos/bindings/sebind/intl/common.h @@ -0,0 +1,966 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include +#include +#include + +#include "bindings/manual/jsb_conversions.h" +#include "bindings/manual/jsb_global.h" + +#include "base/std/container/array.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" + +namespace sebind { + +struct ThisObject {}; +using SeCallbackType = bool(se::State &); +using SeCallbackFnPtr = bool (*)(se::State &); + +namespace intl { + +template +struct FunctionExactor; + +template +struct FunctionExactor { + using type = R(ARGS...); + using return_type = R; + static std::function bind(const se::Value &fnVal) { + std::function func = [=](ARGS... args) { + se::AutoHandleScope scope; + se::ValueArray jsArgs; + jsArgs.resize(sizeof...(ARGS)); + nativevalue_to_se_args_v(jsArgs, std::forward(args)...); + se::Value rval; + bool succ = fnVal.toObject()->call(jsArgs, nullptr, &rval); + if constexpr (!std::is_void_v) { + R result; + sevalue_to_native(rval, &result, nullptr); + return result; + } + }; + return func; + } + static R call(se::Object *jsThisObject, const se::Value &fnVal, ARGS &&...args) { + se::AutoHandleScope scope; + se::ValueArray jsArgs; + jsArgs.resize(sizeof...(ARGS)); + nativevalue_to_se_args_v(jsArgs, std::forward(args)...); + se::Value rval; + bool succ = fnVal.toObject()->call(jsArgs, jsThisObject, &rval); + if constexpr (!std::is_void_v) { + R result; + sevalue_to_native(rval, &result, jsThisObject); + return result; + } + } +}; + +template +struct TypeList; + +template +struct TypeList { + constexpr static size_t COUNT = 1 + sizeof...(OTHERS); + using head = T; + using tail = TypeList; + using tuple_type = std::tuple; + using tuple_type_mutable = std::tuple::type>::type, + typename std::remove_reference::type>::type...>; +}; + +template <> +struct TypeList<> { + constexpr static size_t COUNT = 0; + using head = void; + using tail = TypeList<>; + using tuple_type = std::tuple<>; + using tuple_type_mutable = std::tuple<>; +}; + +template +struct Cons; + +template +struct Cons> { + using type = TypeList; +}; + +template +struct FunctionWrapper; + +template +struct FunctionWrapper { + using type = R (*)(C *, ARGS...); + using return_type = R; + using arg_list = TypeList; + static constexpr size_t ARG_N = sizeof...(ARGS); + template + inline static R invoke(type func, C *self, ARGS2 &&...args) { + return (*func)(self, std::forward(args)...); + } +}; + +template +struct FunctionWrapper { + using type = R (C::*)(ARGS...); + using return_type = R; + using arg_list = TypeList; + static constexpr size_t ARG_N = sizeof...(ARGS); + template + inline static R invoke(type func, C *self, ARGS2 &&...args) { + return (self->*func)(std::forward(args)...); + } +}; + +template +struct FunctionWrapper { + using type = R (C::*)(ARGS...) const; + using return_type = R; + using arg_list = TypeList; + static constexpr size_t ARG_N = sizeof...(ARGS); + template + inline static R invoke(type func, C *self, ARGS2 &&...args) { + return (self->*func)(std::forward(args)...); + } +}; + +template <> +struct FunctionWrapper { + using type = std::nullptr_t; + using return_type = std::nullptr_t; + using arg_list = TypeList<>; + static constexpr size_t ARG_N = 0; + template + static void invoke(type /*func*/, C * /*self*/, ARGS &&.../*args*/) { + } +}; + +template +struct StaticFunctionWrapper; + +template +struct StaticFunctionWrapper { + using type = R (*)(ARGS...); + using return_type = R; + using arg_list = TypeList; + static constexpr size_t ARG_N = sizeof...(ARGS); + template + inline static R invoke(type func, ARGS2 &&...args) { + return (*func)(std::forward(args)...); + } +}; + +template <> +struct StaticFunctionWrapper { + using type = std::nullptr_t; + using return_type = void; + using arg_list = TypeList<>; + static constexpr size_t ARG_N = 0; + template + static void invoke(type /*func*/, ARGS &&.../*args*/) { + } +}; + +template +struct IsConstructibleWithTypeList; + +template +struct IsConstructibleWithTypeList> { + // NOLINTNEXTLINE + constexpr static bool value = std::is_constructible::value; +}; + +template +struct MapTypeListToTuple; + +template +struct MapTypeListToTuple> { + using tuple = std::tuple::value>...>; +}; + +template +struct IsThisObject { + // NOLINTNEXTLINE + constexpr static bool value = std::is_same::type>::type>::type>::value; +}; + +template +struct FilterThisObject; + +template +struct FilterThisObject { + using filtered_types = std::conditional_t::value, + typename FilterThisObject::filtered_types, + typename Cons::filtered_types>::type>; + using mapped_types = typename Cons::value, se::Object *, T>, + typename FilterThisObject::mapped_types>::type; +}; + +template <> +struct FilterThisObject<> { + using filtered_types = TypeList<>; + using mapped_types = TypeList<>; +}; + +template +struct MapArg { + constexpr static size_t FROM = From; + constexpr static size_t TO = To; // NOLINT + constexpr static bool SKIP = Skip; +}; + +template +struct GenMapArgImpl { + using list = typename Cons, typename GenMapArgImpl::list>::type; +}; + +template +struct GenMapArgImpl { + using list = TypeList<>; +}; + +template +struct GenMapArg { + using list = typename GenMapArgImpl<0, N>::list; +}; + +template +struct TypeListMapImpl { + constexpr static bool SHOULD_SKIP = IsThisObject::value; + constexpr static int NEXT_INCOMPLETE = SHOULD_SKIP ? IncompleteIdx : IncompleteIdx + 1; + constexpr static int NEXT_INCOMPLETE_REMAIN = SHOULD_SKIP ? IncompleteRemain : IncompleteRemain - 1; + using full_tail = typename FullTypeList::tail; + using incomplete_tail = std::conditional_t; + using map_value = MapArg; + using map_list = typename Cons::map_list>::type; +}; + +template +struct TypeListMapImpl { + using map_list = TypeList<>; +}; + +template +struct TypeListMapImpl { + static_assert(FullRemain == FullTypeList::COUNT, "Parameter count incorrect!"); + using map_list = typename GenMapArg::list; +}; + +template +struct TypeListMap { + using map_list = typename TypeListMapImpl<0, 0, FullTypeList::COUNT, IncompleteTypeList::COUNT, FullTypeList, IncompleteTypeList>::map_list; +}; + +template +struct TypeMapping; + +template +struct TypeMapping> { + using declare_types = TypeList; + using input_types = typename FilterThisObject::filtered_types; + using result_types = typename FilterThisObject::mapped_types; + using result_types_tuple = typename result_types::tuple_type; + using result_types_tuple_mutable = typename result_types::tuple_type_mutable; + using input_types_tuple = typename input_types::tuple_type; + using mapping_list = typename TypeListMap::map_list; + static constexpr size_t FULL_ARGN = sizeof...(ARGS); + static constexpr size_t NEW_ARGN = input_types::COUNT; + static constexpr bool NEED_REMAP = FULL_ARGN != NEW_ARGN; +}; +template +struct TypeMapReturnSwitch; + +template +struct TypeMapReturnSwitch { + template + static se::Object *select(se::Object *self, V & /*unused*/) { + return self; + } +}; + +template +struct TypeMapReturnSwitch { + template + static auto select(se::Object * /*unused*/, V &tuple) { + return std::get(tuple).value(); + } +}; + +struct ArgumentFilter { + template + static auto forward(se::Object *self, Tuple &tuple) { + constexpr static MapTuple TUPLE_VAL; + using map_arg = std::remove_reference_t(TUPLE_VAL))>; + return TypeMapReturnSwitch::select(self, tuple); + } +}; + +template +ResultType mapTupleArguments(se::Object *self, TupleIn &input, std::index_sequence /*args*/) { + using map_tuple = typename Mapping::mapping_list::tuple_type; + using result_type = typename Mapping::result_types_tuple_mutable; + static_assert(std::is_same::value, "result_type mismatch"); + // if constexpr (std::tuple_size::value > 0) { + return result_type(ArgumentFilter::forward(self, input)...); + //} + // return result_type(); +} + +template +struct BoolArrayAndAll { + static constexpr bool cal(const ccstd::array &bools) { + return bools[M - 1] && BoolArrayAndAll::cal(bools); + } +}; + +template +struct BoolArrayAndAll<0, N> { + static constexpr bool cal(const ccstd::array & /*unused*/) { + return true; + } +}; + +template +// NOLINTNEXTLINE +bool convert_js_args_to_tuple(const se::ValueArray &jsArgs, std::tuple &args, se::Object *thisObj, std::index_sequence) { + constexpr static size_t ARG_N = sizeof...(ARGS); + ccstd::array all = {sevalue_to_native(jsArgs[indexes], &std::get(args).data, thisObj)...}; + return BoolArrayAndAll::cal(all); +} +template +// NOLINTNEXTLINE +bool convert_js_args_to_tuple(const se::ValueArray &jsArgs, std::tuple<> &args, se::Object *ctx, std::index_sequence) { + return true; +} + +struct ConstructorBase { + size_t argCount = 0; + SeCallbackFnPtr bfnPtr{nullptr}; + virtual bool construct(se::State &state) { + if (bfnPtr) { + return (*bfnPtr)(state); + } + return false; + } +}; +struct InstanceMethodBase { + ccstd::string className; + ccstd::string methodName; + size_t argCount = 0; + SeCallbackFnPtr bfnPtr{nullptr}; + virtual bool invoke(se::State &state) const { + if (bfnPtr) { + return (*bfnPtr)(state); + } + return false; + } +}; +struct FinalizerBase { + virtual void finalize(void * /*unused*/) {} +}; + +struct InstanceFieldBase { + ccstd::string className; + ccstd::string attrName; + SeCallbackFnPtr bfnSetPtr{nullptr}; + SeCallbackFnPtr bfnGetPtr{nullptr}; + virtual bool get(se::State &state) const { + if (bfnGetPtr) return (*bfnGetPtr)(state); + return false; + } + virtual bool set(se::State &state) const { + if (bfnSetPtr) return (*bfnSetPtr)(state); + return false; + } +}; + +struct InstanceAttributeBase { + ccstd::string className; + ccstd::string attrName; + SeCallbackFnPtr bfnSetPtr{nullptr}; + SeCallbackFnPtr bfnGetPtr{nullptr}; + virtual bool get(se::State &state) const { + if (bfnGetPtr) return (*bfnGetPtr)(state); + return false; + } + virtual bool set(se::State &state) const { + if (bfnSetPtr) return (*bfnSetPtr)(state); + return false; + } +}; + +struct StaticMethodBase { + ccstd::string className; + ccstd::string methodName; + size_t argCount = 0; + SeCallbackFnPtr bfnPtr{nullptr}; + virtual bool invoke(se::State &state) const { + if (bfnPtr) return (*bfnPtr)(state); + return false; + } +}; +struct StaticAttributeBase { + ccstd::string className; + ccstd::string attrName; + SeCallbackFnPtr bfnSetPtr{nullptr}; + SeCallbackFnPtr bfnGetPtr{nullptr}; + virtual bool get(se::State &state) const { + if (bfnGetPtr) return (*bfnGetPtr)(state); + return false; + } + virtual bool set(se::State &state) const { + if (bfnSetPtr) return (*bfnSetPtr)(state); + return false; + } +}; + +template +struct Constructor; + +template +struct InstanceField; + +template +struct InstanceMethod; + +template +struct InstanceAttribute; + +template +struct StaticMethod; + +template +struct StaticAttribute; + +template +struct Constructor> : ConstructorBase { + // no `if constexpr`, more code is needed... + bool construct(se::State &state) override { + using type_mapping = TypeMapping>; + using args_holder_type = typename MapTypeListToTuple::tuple; + constexpr auto indexes = std::make_index_sequence{}; + se::PrivateObjectBase *self{nullptr}; + se::Object *thisObj = state.thisObject(); + args_holder_type args{}; + const auto &jsArgs = state.args(); + convert_js_args_to_tuple(jsArgs, args, thisObj, std::make_index_sequence()); + if constexpr (type_mapping::NEED_REMAP) { + using type_mapping = TypeMapping>; + using map_list_type = typename type_mapping::mapping_list; + using map_tuple_type = typename type_mapping::result_types_tuple_mutable; + static_assert(map_list_type::COUNT == sizeof...(ARGS), "type mapping incorrect"); + + map_tuple_type remapArgs = mapTupleArguments(thisObj, args, std::make_index_sequence{}); + self = constructWithTuple(remapArgs, indexes); + } else { + self = constructWithTupleValue(args, indexes); + } + state.thisObject()->setPrivateObject(self); + return true; + } + + template + se::PrivateObjectBase *constructWithTuple(std::tuple &args, std::index_sequence /*unused*/) { + return JSB_MAKE_PRIVATE_OBJECT(T, std::get(args)...); + } + + template + se::PrivateObjectBase *constructWithTupleValue(std::tuple &args, std::index_sequence /*unused*/) { + return JSB_MAKE_PRIVATE_OBJECT(T, std::get(args).value()...); + } +}; + +template +struct Constructor : ConstructorBase { + using type = T *(*)(ARGS...); + type func{nullptr}; + bool construct(se::State &state) override { + if ((sizeof...(ARGS)) != state.args().size()) { + return false; + } + std::tuple::value>...> args{}; + const auto &jsArgs = state.args(); + se::Object *thisObj = state.thisObject(); + convert_js_args_to_tuple(jsArgs, args, thisObj, std::make_index_sequence()); + T *ptr = constructWithTuple(args, std::make_index_sequence()); + state.thisObject()->setPrivateData(ptr); + return true; + } + + template + T *constructWithTuple(std::tuple &args, std::index_sequence /*unused*/) { + return (*func)(std::get(args).value()...); + } +}; + +template +struct Finalizer : FinalizerBase { + using type = void (*)(T *); + using arg_type = T; + type func{nullptr}; + void finalize(void *ptr) override { + (*func)(reinterpret_cast(ptr)); + } +}; + +template +struct InstanceMethod : InstanceMethodBase { + using type = R (T::*)(ARGS...); + using return_type = R; + using class_type = std::remove_cv_t; + constexpr static size_t ARG_N = sizeof...(ARGS); + constexpr static bool RETURN_VOID = std::is_same::value; + + type func{nullptr}; + + template + R callWithTuple(T *self, std::tuple &args, std::index_sequence /*unused*/) const { + return ((reinterpret_cast(self))->*func)(std::get(args).value()...); + } + + bool invoke(se::State &state) const override { + constexpr auto indexes{std::make_index_sequence()}; + T *self = reinterpret_cast(state.nativeThisObject()); + se::Object *thisObject = state.thisObject(); + const auto &jsArgs = state.args(); + if (ARG_N != jsArgs.size()) { + SE_LOGE("incorret argument size %d, expect %d\n", static_cast(jsArgs.size()), static_cast(ARG_N)); + return false; + } + std::tuple::value>...> args{}; + convert_js_args_to_tuple(jsArgs, args, thisObject, indexes); + if constexpr (RETURN_VOID) { + callWithTuple(self, args, indexes); + } else { + nativevalue_to_se(callWithTuple(self, args, indexes), state.rval(), state.thisObject()); + } + return true; + } +}; + +template +struct InstanceMethod : InstanceMethodBase { + using type = R (T::*)(ARGS...) const; + using return_type = R; + using class_type = std::remove_cv_t; + constexpr static size_t ARG_N = sizeof...(ARGS); + constexpr static bool RETURN_VOID = std::is_same::value; + + type func{nullptr}; + + template + R callWithTuple(T *self, std::tuple &args, std::index_sequence /*unused*/) const { + return ((reinterpret_cast(self))->*func)(std::get(args).value()...); + } + + bool invoke(se::State &state) const override { + constexpr auto indexes{std::make_index_sequence()}; + T *self = reinterpret_cast(state.nativeThisObject()); + se::Object *thisObject = state.thisObject(); + const auto &jsArgs = state.args(); + if (ARG_N != jsArgs.size()) { + SE_LOGE("incorret argument size %d, expect %d\n", static_cast(jsArgs.size()), static_cast(ARG_N)); + return false; + } + std::tuple::value>...> args{}; + convert_js_args_to_tuple(jsArgs, args, thisObject, indexes); + if constexpr (RETURN_VOID) { + callWithTuple(self, args, indexes); + } else { + nativevalue_to_se(callWithTuple(self, args, indexes), state.rval(), state.thisObject()); + } + return true; + } +}; + +template +struct InstanceMethod : InstanceMethodBase { + using type = R (*)(T *, ARGS...); + using return_type = R; + using class_type = std::remove_cv_t; + constexpr static size_t ARG_N = sizeof...(ARGS); + constexpr static bool RETURN_VOID = std::is_same::value; + + type func{nullptr}; + + template + R callWithTuple(T *self, std::tuple &args, std::index_sequence /*unused*/) const { + return (*func)(reinterpret_cast(self), std::get(args).value()...); + } + + bool invoke(se::State &state) const override { + constexpr auto indexes{std::make_index_sequence()}; + T *self = reinterpret_cast(state.nativeThisObject()); + se::Object *thisObject = state.thisObject(); + const auto &jsArgs = state.args(); + if (ARG_N != jsArgs.size()) { + SE_LOGE("incorret argument size %d, expect %d\n", static_cast(jsArgs.size()), static_cast(ARG_N)); + return false; + } + std::tuple::value>...> args{}; + convert_js_args_to_tuple(jsArgs, args, thisObject, indexes); + if constexpr (RETURN_VOID) { + callWithTuple(self, args, indexes); + } else { + nativevalue_to_se(callWithTuple(self, args, indexes), state.rval(), state.thisObject()); + } + return true; + } +}; + +struct InstanceMethodOverloaded : InstanceMethodBase { + ccstd::vector functions; + bool invoke(se::State &state) const override { + bool ret = false; + auto argCount = state.args().size(); + for (auto *method : functions) { + if (method->argCount == -1 || method->argCount == argCount) { + ret = method->invoke(state); + if (ret) return true; + } + } + return false; + } +}; + +template +struct InstanceField : InstanceFieldBase { + using type = F(T::*); + using class_type = T; + using return_type = std::remove_cv_t; + + type func{nullptr}; + + bool get(se::State &state) const override { + T *self = reinterpret_cast(state.nativeThisObject()); + se::Object *thisObject = state.thisObject(); + return nativevalue_to_se((self->*func), state.rval(), thisObject); + } + + bool set(se::State &state) const override { + T *self = reinterpret_cast(state.nativeThisObject()); + se::Object *thisObject = state.thisObject(); + const auto &args = state.args(); + return sevalue_to_native(args[0], &(self->*func), thisObject); + } +}; + +template +struct AttributeAccessor; + +template +struct AccessorGet { +}; + +template +struct AccessorSet { +}; + +template <> +struct AccessorGet { + using class_type = std::nullptr_t; + using type = std::nullptr_t; + using return_type = std::nullptr_t; +}; + +template <> +struct AccessorSet { + using class_type = std::nullptr_t; + using type = std::nullptr_t; + using value_type = std::nullptr_t; +}; + +template +struct AccessorGet { + using class_type = T; + using type = R (T::*)(); + using return_type = R; + static_assert(!std::is_void::value, "Getter should return a value!"); +}; + +template +struct AccessorSet { + using class_type = T; + using type = R (T::*)(F); + using value_type = F; + using ignored_return_type = R; +}; + +template +struct AccessorGet { + using class_type = T; + using type = R (T::*)() const; + using return_type = R; + static_assert(!std::is_void::value, "Getter should return a value"); +}; + +template +struct AccessorSet { + using class_type = T; + using type = R (*)(T *, F); + using value_type = F; + using ignored_return_type = R; +}; +template +struct AccessorGet { + using class_type = T; + using type = R (*)(T *); + using return_type = R; + static_assert(!std::is_void::value, "Getter should return a value"); +}; + +template +struct InstanceAttribute> : InstanceAttributeBase { + using type = T; + using get_accessor = AccessorGet; + using set_accessor = AccessorSet; + using getter_type = typename get_accessor::type; + using setter_type = typename set_accessor::type; + using set_value_type = std::remove_reference_t>; + using get_value_type = std::remove_reference_t>; + using getter_class_type = typename get_accessor::class_type; + using setter_class_type = typename set_accessor::class_type; + + constexpr static bool HAS_GETTER = !std::is_null_pointer::value; + constexpr static bool HAS_SETTER = !std::is_null_pointer::value; + constexpr static bool GETTER_IS_MEMBER_FN = HAS_GETTER && std::is_member_function_pointer::value; + constexpr static bool SETTER_IS_MEMBER_FN = HAS_SETTER && std::is_member_function_pointer::value; + + static_assert(!HAS_GETTER || std::is_base_of::value, "Getter class type is not valid!"); + static_assert(!HAS_SETTER || std::is_base_of::value, "Setter class type is not valid!"); + + setter_type setterPtr; + getter_type getterPtr; + + bool get(se::State &state) const override { + if constexpr (HAS_GETTER) { + T *self = reinterpret_cast(state.nativeThisObject()); + se::Object *thisObject = state.thisObject(); + using func_type = FunctionWrapper; + static_assert(!std::is_void::value, "should return a value"); + return nativevalue_to_se(func_type::invoke(getterPtr, self), state.rval(), thisObject); + } + return false; + } + + bool set(se::State &state) const override { + if constexpr (HAS_SETTER) { + T *self = reinterpret_cast(state.nativeThisObject()); + se::Object *thisObject = state.thisObject(); + const auto &args = state.args(); + HolderType::value> temp; + sevalue_to_native(args[0], &(temp.data), thisObject); + + using func_type = FunctionWrapper; + func_type::invoke(setterPtr, self, temp.value()); + return true; + } + return false; + } +}; + +template +struct StaticMethod : StaticMethodBase { + using type = R (*)(ARGS...); + using return_type = R; + constexpr static size_t ARG_N = sizeof...(ARGS); + constexpr static bool RETURN_VOID = std::is_same::value; + + type func{nullptr}; + + template + R callWithTuple(std::tuple &args, std::index_sequence /*unused*/) const { + return (*func)(std::get(args).value()...); + } + + bool invoke(se::State &state) const override { + constexpr auto indexes{std::make_index_sequence()}; + const auto &jsArgs = state.args(); + if (ARG_N != jsArgs.size()) { + SE_LOGE("incorret argument size %d, expect %d\n", static_cast(jsArgs.size()), static_cast(ARG_N)); + return false; + } + std::tuple::value>...> args{}; + convert_js_args_to_tuple(jsArgs, args, nullptr, indexes); + if constexpr (RETURN_VOID) { + callWithTuple(args, indexes); + } else { + nativevalue_to_se(callWithTuple(args, indexes), state.rval(), nullptr); + } + return true; + } +}; + +struct StaticMethodOverloaded : StaticMethodBase { + ccstd::vector functions; + bool invoke(se::State &state) const override { + bool ret = false; + auto argCount = state.args().size(); + for (auto *method : functions) { + if (method->argCount == -1 || method->argCount == argCount) { + ret = method->invoke(state); + if (ret) return true; + } + } + return false; + } +}; + +template +struct SAttributeAccessor; + +template +struct SAccessorGet { + using type = void; + using return_type = void; +}; + +template +struct SAccessorSet { + using type = void; + using value_type = void; +}; + +template <> +struct SAccessorGet { + using type = std::nullptr_t; + using return_type = std::nullptr_t; +}; + +template <> +struct SAccessorSet { + using type = std::nullptr_t; + using value_type = std::nullptr_t; +}; + +template +struct SAccessorGet { + using type = R(*); + using return_type = R; + static_assert(!std::is_void::value, "Getter should return value"); +}; + +template +struct SAccessorSet { + using type = R (*)(F); + using value_type = F; + using ignored_return_type = R; +}; + +template +struct StaticAttribute> : StaticAttributeBase { + using type = T; + using get_accessor = SAccessorGet; + using set_accessor = SAccessorSet; + using getter_type = typename get_accessor::type; + using setter_type = typename set_accessor::type; + using set_value_type = std::remove_reference_t>; + using get_value_type = std::remove_reference_t>; + + constexpr static bool HAS_GETTER = !std::is_null_pointer::value; + constexpr static bool HAS_SETTER = !std::is_null_pointer::value; + + static_assert(HAS_GETTER || HAS_SETTER, "Either getter or setter should be set"); + + setter_type setterPtr; + getter_type getterPtr; + + bool get(se::State &state) const override { + if constexpr (HAS_GETTER) { + using func_type = StaticFunctionWrapper; + static_assert(!std::is_void::value, "should return a value"); + return nativevalue_to_se(func_type::invoke(getterPtr), state.rval(), nullptr); + } + return false; + } + + bool set(se::State &state) const override { + if constexpr (HAS_SETTER) { + const auto &args = state.args(); + HolderType::value> temp; + sevalue_to_native(args[0], &(temp.data), nullptr); + using func_type = StaticFunctionWrapper; + func_type::invoke(setterPtr, temp.value()); + return true; + } + return false; + } +}; + +// NOLINTNEXTLINE +class ContextDB { +public: + // NOLINTNEXTLINE + struct Context { + ccstd::vector>> properties; + ccstd::vector>> fields; + ccstd::vector>> functions; + ccstd::vector>> staticFunctions; + ccstd::vector>> staticProperties; + ccstd::vector> constructors; + ccstd::vector> finalizeCallbacks; + ccstd::string className; + se::Class *kls = nullptr; + // se::Object * nsObject = nullptr; + se::Object *parentProto = nullptr; + }; + Context *operator[](const char *key); + static ContextDB &instance(); + static void reset(); + +private: + ccstd::unordered_map> _contexts; +}; + +} // namespace intl +template +auto bindFunction(const se::Value &fnVal) { + assert(fnVal.isObject() && fnVal.toObject()->isFunction()); + return intl::FunctionExactor::bind(fnVal); +} + +template +R callFunction(se::Object *jsThisObject, const se::Value &fnVal, ARGS... args) { + using T = R(ARGS...); + assert(fnVal.isObject() && fnVal.toObject()->isFunction()); + if constexpr (!std::is_void_v) { + return intl::FunctionExactor::call(jsThisObject, fnVal, std::forward(args)...); + } else { + intl::FunctionExactor::call(jsThisObject, fnVal, std::forward(args)...); + } +} +template +R callFunction(const se::Value &fnVal, ARGS... args) { + return callFunction(nullptr, fnVal, args...); +} +} // namespace sebind diff --git a/cocos/bindings/sebind/sebind.h b/cocos/bindings/sebind/sebind.h new file mode 100644 index 0000000..c6aaf00 --- /dev/null +++ b/cocos/bindings/sebind/sebind.h @@ -0,0 +1,27 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "class_v8.h" \ No newline at end of file diff --git a/cocos/bindings/utils/BindingUtils.cpp b/cocos/bindings/utils/BindingUtils.cpp new file mode 100644 index 0000000..28fa19e --- /dev/null +++ b/cocos/bindings/utils/BindingUtils.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "bindings/utils/BindingUtils.h" +#include "bindings/jswrapper/SeApi.h" + +namespace cc::bindings { + +NativeMemorySharedToScriptActor::~NativeMemorySharedToScriptActor() { + destroy(); +} + +void NativeMemorySharedToScriptActor::initialize(void* ptr, uint32_t byteLength) { + CC_ASSERT_NULL(_sharedArrayBufferObject); + // The callback of freeing buffer is empty since the memory is managed in native, + // the external array buffer just holds a reference to the memory. + _sharedArrayBufferObject = se::Object::createExternalArrayBufferObject(ptr, byteLength, [](void* /*contents*/, size_t /*byteLength*/, void* /*userData*/) {}); + _sharedArrayBufferObject->root(); +} + +void NativeMemorySharedToScriptActor::destroy() { + if (_sharedArrayBufferObject != nullptr) { + _sharedArrayBufferObject->unroot(); + _sharedArrayBufferObject->decRef(); + _sharedArrayBufferObject = nullptr; + } +} + +} // namespace cc::bindings diff --git a/cocos/bindings/utils/BindingUtils.h b/cocos/bindings/utils/BindingUtils.h new file mode 100644 index 0000000..5b8e937 --- /dev/null +++ b/cocos/bindings/utils/BindingUtils.h @@ -0,0 +1,53 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +#include "base/Macros.h" + +namespace se { +class Object; +} + +namespace cc::bindings { + +class NativeMemorySharedToScriptActor final { +public: + NativeMemorySharedToScriptActor() = default; + ~NativeMemorySharedToScriptActor(); + + void initialize(void *ptr, uint32_t byteLength); + void destroy(); + + inline se::Object *getSharedArrayBufferObject() const { return _sharedArrayBufferObject; } + +private: + se::Object *_sharedArrayBufferObject{nullptr}; + + CC_DISALLOW_COPY_MOVE_ASSIGN(NativeMemorySharedToScriptActor) +}; + +} // namespace cc::bindings diff --git a/cocos/cocos-version.h b/cocos/cocos-version.h new file mode 100644 index 0000000..a81c244 --- /dev/null +++ b/cocos/cocos-version.h @@ -0,0 +1,33 @@ +/**************************************************************************** +Copyright (c) 2022-2024 Xiamen Yaji Software Co., Ltd. + +http://www.cocos.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +#pragma once + +#define COCOS_MAJOR_VERSION 3 +#define COCOS_MINJOR_VERSION 8 +#define COCOS_PATCH_VERSION 2 +#define COCOS_VERSION_STRING "3.8.2" +#define COCOS_VERSION_DEFINED 1 +#define COCOS_VERSION 30802 + +// #define COCOS_PRE_RELEASE "release" diff --git a/cocos/cocos.h b/cocos/cocos.h new file mode 100644 index 0000000..395f990 --- /dev/null +++ b/cocos/cocos.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +// public headers for user application + +#include "application/ApplicationManager.h" +#include "application/BaseGame.h" +#include "bindings/event/EventDispatcher.h" +#include "bindings/jswrapper/SeApi.h" +#include "bindings/manual/jsb_classtype.h" +#include "bindings/manual/jsb_global.h" +#include "bindings/manual/jsb_module_register.h" +#include "cocos-version.h" +#include "core/event/Event.h" \ No newline at end of file diff --git a/cocos/core/Any.h b/cocos/core/Any.h new file mode 100644 index 0000000..93635a9 --- /dev/null +++ b/cocos/core/Any.h @@ -0,0 +1,33 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace cc { + +class Any { +public: +}; + +} // namespace cc diff --git a/cocos/core/ArrayBuffer.h b/cocos/core/ArrayBuffer.h new file mode 100644 index 0000000..ffe8a23 --- /dev/null +++ b/cocos/core/ArrayBuffer.h @@ -0,0 +1,116 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/Ptr.h" +#include "base/RefCounted.h" +#include "base/memory/Memory.h" +#include "bindings/jswrapper/Object.h" + +namespace cc { + +class ArrayBuffer : public RefCounted { +public: + using Ptr = IntrusivePtr; + + explicit ArrayBuffer(uint32_t length) : _byteLength{length} { + _jsArrayBuffer = se::Object::createArrayBufferObject(nullptr, length); + _jsArrayBuffer->root(); + _jsArrayBuffer->getArrayBufferData(static_cast(&_data), nullptr); + memset(_data, 0x00, _byteLength); + } + + ArrayBuffer(const uint8_t *data, uint32_t length) { + reset(data, length); + } + + ArrayBuffer() = default; + + ~ArrayBuffer() override { + if (_jsArrayBuffer) { + _jsArrayBuffer->unroot(); + _jsArrayBuffer->decRef(); + } + } + + inline void setJSArrayBuffer(se::Object *arrayBuffer) { + if (_jsArrayBuffer) { + _jsArrayBuffer->unroot(); + _jsArrayBuffer->decRef(); + } + + _jsArrayBuffer = arrayBuffer; + _jsArrayBuffer->incRef(); + _jsArrayBuffer->root(); + size_t length{0}; + _jsArrayBuffer->getArrayBufferData(static_cast(&_data), &length); + _byteLength = static_cast(length); + } + inline se::Object *getJSArrayBuffer() const { return _jsArrayBuffer; } + + inline uint32_t byteLength() const { return _byteLength; } + + Ptr slice(uint32_t begin) { + return slice(begin, _byteLength); + } + + Ptr slice(uint32_t begin, uint32_t end) { + CC_ASSERT_GT(end, begin); + CC_ASSERT_LT(begin, _byteLength); + CC_ASSERT_LE(end, _byteLength); + uint32_t newBufByteLength = (end - begin); + Ptr buffer = ccnew ArrayBuffer(newBufByteLength); + memcpy(buffer->getData(), _data + begin, newBufByteLength); + return buffer; + } + + // Just use it to copy data. Use TypedArray to get/set data. + inline const uint8_t *getData() const { return _data; } + inline uint8_t *getData() { return _data; } + + inline void reset(const uint8_t *data, uint32_t length) { + if (_jsArrayBuffer != nullptr) { + _jsArrayBuffer->unroot(); + _jsArrayBuffer->decRef(); + } + _jsArrayBuffer = se::Object::createArrayBufferObject(data, length); + _jsArrayBuffer->getArrayBufferData(static_cast(&_data), nullptr); + _byteLength = length; + } + +private: + se::Object *_jsArrayBuffer{nullptr}; + uint8_t *_data{nullptr}; + uint32_t _byteLength{0}; + + template + friend class TypedArrayTemp; + friend class DataView; + + CC_DISALLOW_COPY_MOVE_ASSIGN(ArrayBuffer); +}; + +} // namespace cc diff --git a/cocos/core/DataView.cpp b/cocos/core/DataView.cpp new file mode 100644 index 0000000..6a6057f --- /dev/null +++ b/cocos/core/DataView.cpp @@ -0,0 +1,186 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/DataView.h" +#include "base/TemplateUtils.h" + +namespace cc { + +#include "base/Macros.h" +#include "core/ArrayBuffer.h" + +ccstd::unordered_map DataView::intReaderMap{ + {"getUint8", &DataView::getUint8}, + {"getUint16", &DataView::getUint16}, + {"getUint32", &DataView::getUint32}, + {"getInt8", &DataView::getInt8}, + {"getInt16", &DataView::getInt16}, + {"getInt32", &DataView::getInt32}, +}; + +ccstd::unordered_map DataView::intWritterMap{ + {"setUint8", reinterpret_cast(&DataView::setUint8)}, + {"setUint16", reinterpret_cast(&DataView::setUint16)}, + {"setUint32", reinterpret_cast(&DataView::setUint32)}, + {"setInt8", reinterpret_cast(&DataView::setInt8)}, + {"setInt16", reinterpret_cast(&DataView::setInt16)}, + {"setInt32", reinterpret_cast(&DataView::setInt32)}, +}; + +int32_t DataView::readInt(ReaderVariant &readerVariant, uint32_t offset) { + return ccstd::visit(overloaded{ + [offset, this](auto &reader) { + return static_cast((this->*reader)(offset)); + }, + [](ccstd::monostate & /*unused*/) { return 0; }}, + readerVariant); +} + +DataView::DataView(ArrayBuffer *buffer) : DataView(buffer, 0) {} + +DataView::DataView(ArrayBuffer *buffer, uint32_t byteOffset) +: DataView(buffer, byteOffset, buffer ? (buffer->byteLength() - byteOffset) : 0) {} + +DataView::DataView(ArrayBuffer *buffer, uint32_t byteOffset, uint32_t byteLength) { + assign(buffer, byteOffset, byteLength); +} + +void DataView::assign(ArrayBuffer *buffer) { + assign(buffer, 0); +} + +void DataView::assign(ArrayBuffer *buffer, uint32_t byteOffset) { + assign(buffer, byteOffset, buffer ? buffer->byteLength() : 0); +} + +void DataView::assign(ArrayBuffer *buffer, uint32_t byteOffset, uint32_t byteLength) { + CC_ASSERT_NOT_NULL(buffer); + CC_ASSERT_GT(byteLength, 0); + _buffer = buffer; + _byteOffset = byteOffset; + _byteEndPos = byteLength + byteOffset; + CC_ASSERT(_byteEndPos <= buffer->_byteLength); + + _data = buffer->_data; +} + +uint8_t DataView::getUint8(uint32_t offset) const { + offset += _byteOffset; + CC_ASSERT_LT(offset, _byteEndPos); + + return _data[offset]; +} + +uint16_t DataView::getUint16(uint32_t offset) const { + offset += _byteOffset; + CC_ASSERT(offset < (_byteEndPos - 1)); + + return *reinterpret_cast(_data + offset); +} + +uint32_t DataView::getUint32(uint32_t offset) const { + offset += _byteOffset; + CC_ASSERT(offset < (_byteEndPos - 3)); + + return *reinterpret_cast(_data + offset); +} + +int8_t DataView::getInt8(uint32_t offset) const { + offset += _byteOffset; + CC_ASSERT_LT(offset, _byteEndPos); + + return static_cast(_data[offset]); +} + +int16_t DataView::getInt16(uint32_t offset) const { + offset += _byteOffset; + CC_ASSERT(offset < (_byteEndPos - 1)); + + return *reinterpret_cast(_data + offset); +} + +int32_t DataView::getInt32(uint32_t offset) const { + offset += _byteOffset; + CC_ASSERT(offset < (_byteEndPos - 3)); + + return *reinterpret_cast(_data + offset); +} + +float DataView::getFloat32(uint32_t offset) const { + offset += _byteOffset; + CC_ASSERT(offset < (_byteEndPos - 3)); + + return *reinterpret_cast(_data + offset); +} + +void DataView::setUint8(uint32_t offset, uint8_t value) { + offset += _byteOffset; + CC_ASSERT_LT(offset, _byteEndPos); + + _data[offset] = value; +} + +void DataView::setUint16(uint32_t offset, uint16_t value) { + offset += _byteOffset; + CC_ASSERT(offset < _byteEndPos - 1); + + *reinterpret_cast(_data + offset) = value; +} + +void DataView::setUint32(uint32_t offset, uint32_t value) { + offset += _byteOffset; + CC_ASSERT(offset < _byteEndPos - 3); + + *reinterpret_cast(_data + offset) = value; +} + +void DataView::setInt8(uint32_t offset, int8_t value) { + offset += _byteOffset; + CC_ASSERT_LT(offset, _byteEndPos); + + *reinterpret_cast(_data + offset) = value; +} + +void DataView::setInt16(uint32_t offset, int16_t value) { + offset += _byteOffset; + CC_ASSERT(offset < _byteEndPos - 1); + + *reinterpret_cast(_data + offset) = value; +} + +void DataView::setInt32(uint32_t offset, int32_t value) { + offset += _byteOffset; + CC_ASSERT(offset < _byteEndPos - 3); + + *reinterpret_cast(_data + offset) = value; +} + +void DataView::setFloat32(uint32_t offset, float value) { + offset += _byteOffset; + CC_ASSERT(offset < _byteEndPos - 3); + + *(reinterpret_cast(_data + offset)) = value; +} + +} // namespace cc diff --git a/cocos/core/DataView.h b/cocos/core/DataView.h new file mode 100644 index 0000000..1b3414b --- /dev/null +++ b/cocos/core/DataView.h @@ -0,0 +1,88 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/std/variant.h" +#include "core/ArrayBuffer.h" + +namespace cc { + +class DataView final { +public: + DataView() = default; + explicit DataView(ArrayBuffer *buffer); + DataView(ArrayBuffer *buffer, uint32_t byteOffset); + DataView(ArrayBuffer *buffer, uint32_t byteOffset, uint32_t byteLength); + ~DataView() = default; + + void assign(ArrayBuffer *buffer); + void assign(ArrayBuffer *buffer, uint32_t byteOffset); + void assign(ArrayBuffer *buffer, uint32_t byteOffset, uint32_t byteLength); + + uint8_t getUint8(uint32_t offset) const; + uint16_t getUint16(uint32_t offset) const; + uint32_t getUint32(uint32_t offset) const; + int8_t getInt8(uint32_t offset) const; + int16_t getInt16(uint32_t offset) const; + int32_t getInt32(uint32_t offset) const; + float getFloat32(uint32_t offset) const; + + void setUint8(uint32_t offset, uint8_t value); + void setUint16(uint32_t offset, uint16_t value); + void setUint32(uint32_t offset, uint32_t value); + void setInt8(uint32_t offset, int8_t value); + void setInt16(uint32_t offset, int16_t value); + void setInt32(uint32_t offset, int32_t value); + void setFloat32(uint32_t offset, float value); + + inline const ArrayBuffer *buffer() const { return _buffer; } + inline ArrayBuffer *buffer() { return _buffer; } + inline uint32_t byteOffset() const { return _byteOffset; } + inline uint32_t byteLength() const { + return _byteEndPos - _byteOffset; + } + + using Int32Reader = int32_t (DataView::*)(uint32_t) const; + using UInt32Reader = uint32_t (DataView::*)(uint32_t) const; + using Int16Reader = int16_t (DataView::*)(uint32_t) const; + using UInt16Reader = uint16_t (DataView::*)(uint32_t) const; + using Int8Reader = int8_t (DataView::*)(uint32_t) const; + using UInt8Reader = uint8_t (DataView::*)(uint32_t) const; + using ReaderVariant = ccstd::variant; + static ccstd::unordered_map intReaderMap; + int32_t readInt(ReaderVariant &readerVariant, uint32_t offset); + + using IntWritter = void (DataView::*)(uint32_t, uint32_t); + static ccstd::unordered_map intWritterMap; + +private: + ArrayBuffer::Ptr _buffer; + uint8_t *_data{nullptr}; + uint32_t _byteOffset{0}; + uint32_t _byteEndPos{0}; +}; + +} // namespace cc diff --git a/cocos/core/Root.cpp b/cocos/core/Root.cpp new file mode 100644 index 0000000..be2030e --- /dev/null +++ b/cocos/core/Root.cpp @@ -0,0 +1,676 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/Root.h" +#include "2d/renderer/Batcher2d.h" +#include "application/ApplicationManager.h" +#include "bindings/event/EventDispatcher.h" +#include "pipeline/custom/RenderingModule.h" +#include "platform/interfaces/modules/IScreen.h" +#include "platform/interfaces/modules/ISystemWindow.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" +#include "platform/interfaces/modules/IXRInterface.h" +#if CC_USE_DEBUG_RENDERER + #include "profiler/DebugRenderer.h" +#endif +#include "engine/EngineEvents.h" +#include "profiler/Profiler.h" +#include "renderer/gfx-base/GFXDevice.h" +#include "renderer/gfx-base/GFXSwapchain.h" +#include "renderer/pipeline/Define.h" +#include "renderer/pipeline/GeometryRenderer.h" +#include "renderer/pipeline/PipelineSceneData.h" +#include "renderer/pipeline/custom/NativePipelineTypes.h" +#include "renderer/pipeline/custom/RenderInterfaceTypes.h" +#include "renderer/pipeline/deferred/DeferredPipeline.h" +#include "renderer/pipeline/forward/ForwardPipeline.h" +#include "scene/Camera.h" +#include "scene/DirectionalLight.h" +#include "scene/SpotLight.h" +#include "scene/Skybox.h" + +namespace cc { + +namespace { +Root *instance = nullptr; +} + +Root *Root::getInstance() { + return instance; +} + +Root::Root(gfx::Device *device) +: _device(device) { + instance = this; + // TODO(minggo): + // this._dataPoolMgr = legacyCC.internal.DataPoolManager && new legacyCC.internal.DataPoolManager(device) as DataPoolManager; + + _cameraList.reserve(6); + _swapchains.reserve(2); +} + +Root::~Root() { + destroy(); + instance = nullptr; +} + +void Root::initialize(gfx::Swapchain * /*swapchain*/) { + auto *windowMgr = CC_GET_PLATFORM_INTERFACE(ISystemWindowManager); + const auto &windows = windowMgr->getWindows(); + for (const auto &pair : windows) { + auto *window = pair.second.get(); + scene::RenderWindow *renderWindow = createRenderWindowFromSystemWindow(window); + if (!_mainRenderWindow && (window->getWindowId() == ISystemWindow::mainWindowId)) { + _mainRenderWindow = renderWindow; + } + } + _curRenderWindow = _mainRenderWindow; + _xr = CC_GET_XR_INTERFACE(); + addWindowEventListener(); + // TODO(minggo): + // return Promise.resolve(builtinResMgr.initBuiltinRes(this._device)); + const uint32_t usedUBOVectorCount = (pipeline::UBOGlobal::COUNT + pipeline::UBOCamera::COUNT + pipeline::UBOShadow::COUNT + pipeline::UBOLocal::COUNT + pipeline::UBOWorldBound::COUNT) / 4; + uint32_t maxJoints = (_device->getCapabilities().maxVertexUniformVectors - usedUBOVectorCount) / 3; + maxJoints = maxJoints < 256 ? maxJoints : 256; + pipeline::localDescriptorSetLayoutResizeMaxJoints(maxJoints); + + _debugView = std::make_unique(); +} + +render::Pipeline *Root::getCustomPipeline() const { + return dynamic_cast(_pipelineRuntime.get()); +} + +scene::RenderWindow *Root::createRenderWindowFromSystemWindow(ISystemWindow *window) { + if (!window) { + return nullptr; + } + + uint32_t windowId = window->getWindowId(); + auto handle = window->getWindowHandle(); + const auto &size = window->getViewSize(); + + gfx::SwapchainInfo info; + info.width = static_cast(size.width); + info.height = static_cast(size.height); + info.windowHandle = reinterpret_cast(handle); // NOLINT + info.windowId = window->getWindowId(); + + gfx::Swapchain *swapchain = gfx::Device::getInstance()->createSwapchain(info); + _swapchains.emplace_back(swapchain); + + gfx::RenderPassInfo renderPassInfo; + + gfx::ColorAttachment colorAttachment; + colorAttachment.format = swapchain->getColorTexture()->getFormat(); + renderPassInfo.colorAttachments.emplace_back(colorAttachment); + + auto &depthStencilAttachment = renderPassInfo.depthStencilAttachment; + depthStencilAttachment.format = swapchain->getDepthStencilTexture()->getFormat(); + depthStencilAttachment.depthStoreOp = gfx::StoreOp::DISCARD; + depthStencilAttachment.stencilStoreOp = gfx::StoreOp::DISCARD; + + scene::IRenderWindowInfo windowInfo; + windowInfo.title = StringUtil::format("renderWindow_%d", windowId); + windowInfo.width = swapchain->getWidth(); + windowInfo.height = swapchain->getHeight(); + windowInfo.renderPassInfo = renderPassInfo; + windowInfo.swapchain = swapchain; + + return createWindow(windowInfo); +} + +cc::scene::RenderWindow *Root::createRenderWindowFromSystemWindow(uint32_t windowId) { + if (windowId == 0) { + return nullptr; + } + return createRenderWindowFromSystemWindow(CC_GET_SYSTEM_WINDOW(windowId)); +} + +void Root::destroy() { + destroyScenes(); + removeWindowEventListener(); + if (_pipelineRuntime) { + _pipelineRuntime->destroy(); + } + _pipelineRuntime.reset(); + + CC_SAFE_DESTROY_NULL(_pipeline); + + CC_SAFE_DELETE(_batcher); + + for (auto *swapchain : _swapchains) { + CC_SAFE_DELETE(swapchain); + } + _swapchains.clear(); + + _debugView.reset(); + + // TODO(minggo): + // this.dataPoolManager.clear(); +} + +void Root::resize(uint32_t width, uint32_t height, uint32_t windowId) { // NOLINT + for (const auto &window : _renderWindows) { + auto *swapchain = window->getSwapchain(); + if (swapchain && (swapchain->getWindowId() == windowId)) { + if (_xr) { + // xr, window's width and height should not change by device + width = window->getWidth(); + height = window->getHeight(); + } + window->resize(width, height); + } + } +} + +namespace { + +class RenderPipelineBridge final : public render::PipelineRuntime { +public: + explicit RenderPipelineBridge(pipeline::RenderPipeline *pipelineIn) + : pipeline(pipelineIn) {} + + bool activate(gfx::Swapchain *swapchain) override { + return pipeline->activate(swapchain); + } + bool destroy() noexcept override { + return pipeline->destroy(); + } + void render(const ccstd::vector &cameras) override { + pipeline->render(cameras); + } + gfx::Device *getDevice() const override { + return pipeline->getDevice(); + } + const MacroRecord &getMacros() const override { + return pipeline->getMacros(); + } + pipeline::GlobalDSManager *getGlobalDSManager() const override { + return pipeline->getGlobalDSManager(); + } + gfx::DescriptorSetLayout *getDescriptorSetLayout() const override { + return pipeline->getDescriptorSetLayout(); + } + gfx::DescriptorSet *getDescriptorSet() const override { + return pipeline->getDescriptorSet(); + } + const ccstd::vector &getCommandBuffers() const override { + return pipeline->getCommandBuffers(); + } + pipeline::PipelineSceneData *getPipelineSceneData() const override { + return pipeline->getPipelineSceneData(); + } + const ccstd::string &getConstantMacros() const override { + return pipeline->getConstantMacros(); + } + scene::Model *getProfiler() const override { + return pipeline->getProfiler(); + } + void setProfiler(scene::Model *profiler) override { + pipeline->setProfiler(profiler); + } + pipeline::GeometryRenderer *getGeometryRenderer() const override { + return pipeline->getGeometryRenderer(); + } + float getShadingScale() const override { + return pipeline->getShadingScale(); + } + void setShadingScale(float scale) override { + pipeline->setShadingScale(scale); + } + const ccstd::string &getMacroString(const ccstd::string &name) const override { + static const ccstd::string EMPTY_STRING; + const auto ¯os = pipeline->getMacros(); + auto iter = macros.find(name); + if (iter == macros.end()) { + return EMPTY_STRING; + } + return ccstd::get(iter->second); + } + int32_t getMacroInt(const ccstd::string &name) const override { + const auto ¯os = pipeline->getMacros(); + auto iter = macros.find(name); + if (iter == macros.end()) { + return 0; + } + return ccstd::get(iter->second); + } + bool getMacroBool(const ccstd::string &name) const override { + const auto ¯os = pipeline->getMacros(); + auto iter = macros.find(name); + if (iter == macros.end()) { + return false; + } + return ccstd::get(iter->second); + } + void setMacroString(const ccstd::string &name, const ccstd::string &value) override { + pipeline->setValue(name, value); + } + void setMacroInt(const ccstd::string &name, int32_t value) override { + pipeline->setValue(name, value); + } + void setMacroBool(const ccstd::string &name, bool value) override { + pipeline->setValue(name, value); + } + void onGlobalPipelineStateChanged() override { + pipeline->onGlobalPipelineStateChanged(); + } + void setValue(const ccstd::string &name, int32_t value) override { + pipeline->setValue(name, value); + } + void setValue(const ccstd::string &name, bool value) override { + pipeline->setValue(name, value); + } + bool isOcclusionQueryEnabled() const override { + return pipeline->isOcclusionQueryEnabled(); + } + + void resetRenderQueue(bool reset) override { + pipeline->resetRenderQueue(reset); + } + + bool isRenderQueueReset() const override { + return pipeline->isRenderQueueReset(); + } + + pipeline::RenderPipeline *pipeline = nullptr; +}; + +} // namespace + +bool Root::setRenderPipeline(pipeline::RenderPipeline *rppl /* = nullptr*/) { + if (rppl) { + if (dynamic_cast(rppl) != nullptr) { + _useDeferredPipeline = true; + } + + _pipeline = rppl; + _pipelineRuntime = std::make_unique(rppl); + rppl->setPipelineRuntime(_pipelineRuntime.get()); + + // now cluster just enabled in deferred pipeline + if (!_useDeferredPipeline || !_device->hasFeature(gfx::Feature::COMPUTE_SHADER)) { + // disable cluster + _pipeline->setClusterEnabled(false); + } + _pipeline->setBloomEnabled(false); + + if (!_pipeline->activate(_mainRenderWindow->getSwapchain())) { + _pipeline = nullptr; + return false; + } + } else { + CC_ASSERT(!_pipelineRuntime); + _pipelineRuntime.reset(render::Factory::createPipeline()); + if (!_pipelineRuntime->activate(_mainRenderWindow->getSwapchain())) { + _pipelineRuntime->destroy(); + _pipelineRuntime.reset(); + return false; + } + } + + // TODO(minggo): + // auto *scene = Director::getInstance()->getScene(); + // if (scene) { + // scene->getSceneGlobals()->activate(); + // } + +#if CC_EDITOR + emit(); +#endif + + onGlobalPipelineStateChanged(); + + if (_batcher == nullptr) { + _batcher = ccnew Batcher2d(this); + if (!_batcher->initialize()) { + destroy(); + return false; + } + } + + return true; +} + +void Root::onGlobalPipelineStateChanged() { + for (const auto &scene : _scenes) { + scene->onGlobalPipelineStateChanged(); + } + + if (_pipelineRuntime->getPipelineSceneData()->getSkybox()->isEnabled()) + { + _pipelineRuntime->getPipelineSceneData()->getSkybox()->getModel()->onGlobalPipelineStateChanged(); + } + + _pipelineRuntime->onGlobalPipelineStateChanged(); +} + +void Root::activeWindow(scene::RenderWindow *window) { + _curRenderWindow = window; +} + +void Root::resetCumulativeTime() { + _cumulativeTime = 0; +} + +void Root::frameSync() { + if (_device) { + _device->frameSync(); + } +} + +void Root::frameMoveBegin() { + for (const auto &scene : _scenes) { + scene->removeBatches(); + } + + if (_batcher != nullptr) { + _batcher->update(); + } + + // + _cameraList.clear(); +} + +void Root::frameMoveProcess(bool isNeedUpdateScene, int32_t totalFrames) { + for (const auto &window : _renderWindows) { + window->extractRenderCameras(_cameraList); + } + + if (_pipelineRuntime != nullptr && !_cameraList.empty()) { + _device->acquire(_swapchains); + + // NOTE: c++ doesn't have a Director, so totalFrames need to be set from JS + uint32_t stamp = totalFrames; + + if (_batcher != nullptr) { + _batcher->uploadBuffers(); + } + + if (isNeedUpdateScene) { + for (const auto &scene : _scenes) { + scene->update(stamp); + } + } + + CC_PROFILER_UPDATE; + } +} + +void Root::frameMoveEnd() { + if (_pipelineRuntime != nullptr && !_cameraList.empty()) { + emit(); + std::stable_sort(_cameraList.begin(), _cameraList.end(), [](const auto *a, const auto *b) { + return a->getPriority() < b->getPriority(); + }); +#if !defined(CC_SERVER_MODE) + + #if CC_USE_GEOMETRY_RENDERER + for (auto *camera : _cameraList) { + if (camera->getGeometryRenderer()) { + camera->getGeometryRenderer()->update(); + } + } + #endif + #if CC_USE_DEBUG_RENDERER + CC_DEBUG_RENDERER->update(); + #endif + + emit(); + _pipelineRuntime->render(_cameraList); + emit(); +#endif + _device->present(); + } + + if (_batcher != nullptr) { + _batcher->reset(); + } +} + +void Root::frameMove(float deltaTime, int32_t totalFrames) { // NOLINT + CCObject::deferredDestroy(); + + _frameTime = deltaTime; + + ++_frameCount; + _cumulativeTime += deltaTime; + _fpsTime += deltaTime; + if (_fpsTime > 1.0F) { + _fps = _frameCount; + _frameCount = 0; + _fpsTime = 0.0; + } + + if (_xr) { + doXRFrameMove(totalFrames); + } else { + frameMoveBegin(); + frameMoveProcess(true, totalFrames); + frameMoveEnd(); + } +} + +scene::RenderWindow *Root::createWindow(scene::IRenderWindowInfo &info) { + IntrusivePtr window = ccnew scene::RenderWindow(); + + window->initialize(_device, info); + _renderWindows.emplace_back(window); + return window; +} + +void Root::destroyWindow(scene::RenderWindow *window) { + auto it = std::find(_renderWindows.begin(), _renderWindows.end(), window); + if (it != _renderWindows.end()) { + CC_SAFE_DESTROY(*it); + _renderWindows.erase(it); + } +} + +void Root::destroyWindows() { + for (const auto &window : _renderWindows) { + CC_SAFE_DESTROY(window); + } + _renderWindows.clear(); +} + +uint32_t Root::createSystemWindow(const ISystemWindowInfo &info) { + auto *windowMgr = CC_GET_PLATFORM_INTERFACE(ISystemWindowManager); + ISystemWindow *window = windowMgr->createWindow(info); + if (!window) { + return 0; + } + return window->getWindowId(); +} + +scene::RenderScene *Root::createScene(const scene::IRenderSceneInfo &info) { + IntrusivePtr scene = ccnew scene::RenderScene(); + scene->initialize(info); + _scenes.emplace_back(scene); + return scene.get(); +} + +void Root::destroyScene(scene::RenderScene *scene) { + auto it = std::find(_scenes.begin(), _scenes.end(), scene); + if (it != _scenes.end()) { + CC_SAFE_DESTROY(*it); + _scenes.erase(it); + } +} + +void Root::destroyModel(scene::Model *model) { // NOLINT(readability-convert-member-functions-to-static) + if (model == nullptr) { + return; + } + + if (model->getScene() != nullptr) { + model->getScene()->removeModel(model); + } + model->destroy(); +} + +void Root::destroyLight(scene::Light *light) { // NOLINT(readability-convert-member-functions-to-static) + if (light == nullptr) { + return; + } + + if (light->getScene() != nullptr) { + if (light->getType() == scene::LightType::DIRECTIONAL) { + light->getScene()->removeDirectionalLight(static_cast(light)); + } else if (light->getType() == scene::LightType::SPHERE) { + light->getScene()->removeSphereLight(static_cast(light)); + } else if (light->getType() == scene::LightType::SPOT) { + light->getScene()->removeSpotLight(static_cast(light)); + } else if (light->getType() == scene::LightType::POINT) { + light->getScene()->removePointLight(static_cast(light)); + } else if (light->getType() == scene::LightType::RANGED_DIRECTIONAL) { + light->getScene()->removeRangedDirLight(static_cast(light)); + } + } + light->destroy(); +} + +scene::Camera *Root::createCamera() const { + return ccnew scene::Camera(_device); +} + +void Root::destroyScenes() { + for (const auto &scene : _scenes) { + CC_SAFE_DESTROY(scene); + } + _scenes.clear(); +} + +void Root::doXRFrameMove(int32_t totalFrames) { + if (_xr->isRenderAllowable()) { + bool isSceneUpdated = false; + int viewCount = _xr->getXRConfig(xr::XRConfigKey::VIEW_COUNT).getInt(); + // compatible native pipeline + static bool isNativePipeline = dynamic_cast(_pipelineRuntime.get()) != nullptr; + bool forceUpdateSceneTwice = isNativePipeline ? true : _xr->getXRConfig(xr::XRConfigKey::EYE_RENDER_JS_CALLBACK).getBool(); + for (int xrEye = 0; xrEye < viewCount; xrEye++) { + _xr->beginRenderEyeFrame(xrEye); + + ccstd::vector> allCameras; + for (const auto &window : _renderWindows) { + const ccstd::vector> &wndCams = window->getCameras(); + allCameras.insert(allCameras.end(), wndCams.begin(), wndCams.end()); + } + + // when choose PreEyeCamera, only hmd has PoseTracker + // draw left eye change hmd node's position to -ipd/2 | draw right eye change hmd node's position to ipd/2 + for (const auto &camera : allCameras) { + if (camera->getTrackingType() != cc::scene::TrackingType::NO_TRACKING) { + Node *camNode = camera->getNode(); + if (camNode) { + const auto &viewPosition = _xr->getHMDViewPosition(xrEye, static_cast(camera->getTrackingType())); + camNode->setPosition({viewPosition[0], viewPosition[1], viewPosition[2]}); + } + } + } + + frameMoveBegin(); + // condition1: mainwindow has left camera && right camera, + // but we only need left/right camera when in left/right eye loop + // condition2: main camera draw twice + for (const auto &window : _renderWindows) { + if (window->getSwapchain()) { + // not rt + _xr->bindXREyeWithRenderWindow(window, static_cast(xrEye)); + } + } + + bool isNeedUpdateScene = xrEye == static_cast(xr::XREye::LEFT) || (xrEye == static_cast(xr::XREye::RIGHT) && !isSceneUpdated); + if (forceUpdateSceneTwice) { + isNeedUpdateScene = true; + } + frameMoveProcess(isNeedUpdateScene, totalFrames); + auto camIter = _cameraList.begin(); + while (camIter != _cameraList.end()) { + scene::Camera *cam = *camIter; + bool isMismatchedCam = + (static_cast(xrEye) == xr::XREye::LEFT && cam->getCameraType() == scene::CameraType::RIGHT_EYE) || + (static_cast(xrEye) == xr::XREye::RIGHT && cam->getCameraType() == scene::CameraType::LEFT_EYE); + if (isMismatchedCam) { + // currently is left eye loop, so right camera do not need active + camIter = _cameraList.erase(camIter); + } else { + camIter++; + } + } + + if (_pipelineRuntime != nullptr && !_cameraList.empty()) { + if (isNeedUpdateScene) { + isSceneUpdated = true; + // only one eye enable culling (without other cameras) + if (_cameraList.size() == 1 && _cameraList[0]->getTrackingType() != cc::scene::TrackingType::NO_TRACKING) { + _cameraList[0]->setCullingEnable(true); + _pipelineRuntime->resetRenderQueue(true); + } + } else { + // another eye disable culling (without other cameras) + if (_cameraList.size() == 1 && _cameraList[0]->getTrackingType() != cc::scene::TrackingType::NO_TRACKING) { + _cameraList[0]->setCullingEnable(false); + _pipelineRuntime->resetRenderQueue(false); + } + } + } + + frameMoveEnd(); + _xr->endRenderEyeFrame(xrEye); + } + // recovery to normal status (condition: xr scene jump to normal scene) + if (_pipelineRuntime) { + _pipelineRuntime->resetRenderQueue(true); + } + + for (scene::Camera *cam : _cameraList) { + cam->setCullingEnable(true); + } + } else { + CC_LOG_WARNING("[XR] isRenderAllowable is false !!!"); + } +} + +void Root::addWindowEventListener() { + _windowDestroyListener.bind([this](uint32_t windowId) -> void { + for (const auto &window : _renderWindows) { + window->onNativeWindowDestroy(windowId); + } + }); + + _windowRecreatedListener.bind([this](uint32_t windowId) -> void { + for (const auto &window : _renderWindows) { + window->onNativeWindowResume(windowId); + } + }); +} + +void Root::removeWindowEventListener() { + _windowDestroyListener.reset(); + _windowRecreatedListener.reset(); +} + +} // namespace cc diff --git a/cocos/core/Root.h b/cocos/core/Root.h new file mode 100644 index 0000000..5020f11 --- /dev/null +++ b/cocos/core/Root.h @@ -0,0 +1,337 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +//#include "3d/skeletal-animation/DataPoolManager.h" +#include "bindings/event/EventDispatcher.h" +#include "core/event/Event.h" +#include "core/memop/Pool.h" +#include "renderer/pipeline/RenderPipeline.h" +#include "renderer/pipeline/DebugView.h" +#include "scene/DrawBatch2D.h" +#include "scene/Light.h" +#include "scene/Model.h" +#include "scene/RenderScene.h" +#include "scene/RenderWindow.h" +#include "scene/SphereLight.h" +#include "scene/PointLight.h" +#include "scene/RangedDirectionalLight.h" + +namespace cc { +class IXRInterface; +namespace scene { +class Camera; +class DrawBatch2D; +} // namespace scene +namespace gfx { +class SwapChain; +class Device; +} // namespace gfx +namespace render { +class PipelineRuntime; +class Pipeline; +} // namespace render +class Batcher2d; + +struct ISystemWindowInfo; +class ISystemWindow; + +class Root final { + IMPL_EVENT_TARGET(Root) + DECLARE_TARGET_EVENT_BEGIN(Root) + TARGET_EVENT_ARG0(BeforeCommit) + TARGET_EVENT_ARG0(BeforeRender) + TARGET_EVENT_ARG0(AfterRender) + TARGET_EVENT_ARG0(PipelineChanged) + DECLARE_TARGET_EVENT_END() +public: + static Root *getInstance(); // cjh todo: put Root Managerment to Director class. + explicit Root(gfx::Device *device); + ~Root(); + + // @minggo IRootInfo seems is not use, and how to return Promise? + void initialize(gfx::Swapchain *swapchain); + void destroy(); + + /** + * @zh + * 重置大小 + * @param width 窗口宽度 + * @param height 窗口高度 + * @param windowId 窗口 ID + */ + void resize(uint32_t width, uint32_t height, uint32_t windowId); + + bool setRenderPipeline(pipeline::RenderPipeline *rppl = nullptr); + void onGlobalPipelineStateChanged(); + + /** + * @zh + * 激活指定窗口为当前窗口 + * @param window GFX 窗口 + */ + void activeWindow(scene::RenderWindow *); + + /** + * @zh + * 重置累计时间 + */ + void resetCumulativeTime(); + + /** + * @zh + * 每帧执行函数 + * @param deltaTime 间隔时间 + */ + void frameMove(float deltaTime, int32_t totalFrames); // NOTE: c++ doesn't have a Director, so totalFrames need to be set from JS + + /** + * @zh + * 创建窗口 + * @param info GFX 窗口描述信息 + */ + scene::RenderWindow *createWindow(scene::IRenderWindowInfo &); + + /** + * @zh + * 销毁指定的窗口 + * @param window GFX 窗口 + */ + void destroyWindow(scene::RenderWindow *); + + /** + * @zh + * 销毁全部窗口 + */ + void destroyWindows(); + + /** + * @zh + * 创建一个系统窗口 + * @param info 系统窗口描述信息 + * @return 新创建的系统窗口 ID + */ + static uint32_t createSystemWindow(const cc::ISystemWindowInfo &info); + + /** + * @zh + * 创建渲染场景 + * @param info 渲染场景描述信息 + */ + scene::RenderScene *createScene(const scene::IRenderSceneInfo &); + + /** + * @zh + * 销毁指定的渲染场景 + * @param scene 渲染场景 + */ + void destroyScene(scene::RenderScene *); + + /** + * @zh + * 销毁全部场景 + */ + void destroyScenes(); + +#ifndef SWIGCOCOS + template ::value>> + T *createModel() { + // cjh TODO: need use model pool? + T *model = ccnew T(); + model->initialize(); + return model; + } +#endif + + void destroyModel(scene::Model *model); + +#ifndef SWIGCOCOS + template ::value>> + T *createLight() { + // TODO(xwx): need use model pool? + T *light = ccnew T(); + light->initialize(); + return light; + } +#endif + + void destroyLight(scene::Light *light); + + scene::Camera *createCamera() const; + /** + * @zh + * GFX 设备 + */ + inline gfx::Device *getDevice() const { return _device; } + inline void setDevice(gfx::Device *device) { _device = device; } + + /** + * @zh + * 主窗口 + */ + inline scene::RenderWindow *getMainWindow() const { return _mainRenderWindow.get(); } + + /** + * @zh + * 当前窗口 + */ + inline void setCurWindow(scene::RenderWindow *window) { _curRenderWindow = window; } + + inline scene::RenderWindow *getCurWindow() const { return _curRenderWindow.get(); } + + /** + * @zh + * 临时窗口(用于数据传输) + */ + void setTempWindow(scene::RenderWindow *window) { _tempWindow = window; } + + inline scene::RenderWindow *getTempWindow() const { return _tempWindow.get(); } + + /** + * @zh + * 窗口列表 + */ + inline const ccstd::vector> &getWindows() const { return _renderWindows; } + + /** + * @zh + * 是否启用自定义渲染管线 + */ + inline bool usesCustomPipeline() const { return _usesCustomPipeline; } + + /** + * @zh + * 渲染管线 + */ + inline render::PipelineRuntime *getPipeline() const { return _pipelineRuntime.get(); } + + /** + * @zh + * 自定义渲染管线 + */ + render::Pipeline *getCustomPipeline() const; + + /** + * @zh + * UI实例 + * 引擎内部使用,用户无需调用此接口 + */ + inline Batcher2d *getBatcher2D() const { return _batcher; } + + /** + * @zh + * 场景列表 + */ + inline const ccstd::vector> &getScenes() const { return _scenes; } + + /** + * @zh + * 渲染调试数据 + */ + inline pipeline::DebugView *getDebugView() const { return _debugView.get(); } + + /** + * @zh + * 累计时间(秒) + */ + inline float getCumulativeTime() const { return _cumulativeTime; } + + /** + * @zh + * 帧时间(秒) + */ + inline float getFrameTime() const { return _frameTime; } + + /** + * @zh + * 一秒内的累计帧数 + */ + inline uint32_t getFrameCount() const { return _frameCount; } + + /** + * @zh + * 每秒帧率 + */ + inline uint32_t getFps() const { return _fps; } + + /** + * @zh + * 每秒固定帧率 + */ + void setFixedFPS(uint32_t fps) { _fixedFPS = fps; } + + inline uint32_t getFixedFPS() const { return _fixedFPS; } + + // inline DataPoolManager *getDataPoolManager() { return _dataPoolMgr.get(); } + + inline bool isUsingDeferredPipeline() const { return _useDeferredPipeline; } + + scene::RenderWindow *createRenderWindowFromSystemWindow(uint32_t windowId); + scene::RenderWindow *createRenderWindowFromSystemWindow(cc::ISystemWindow *window); + + const ccstd::vector &getCameraList() const { + return _cameraList; + } + + void frameSync(); + +private: + void frameMoveBegin(); + void frameMoveProcess(bool isNeedUpdateScene, int32_t totalFrames); + void frameMoveEnd(); + void doXRFrameMove(int32_t totalFrames); + void addWindowEventListener(); + void removeWindowEventListener(); + + gfx::Device *_device{nullptr}; + gfx::Swapchain *_swapchain{nullptr}; + Batcher2d *_batcher{nullptr}; + IntrusivePtr _mainRenderWindow; + IntrusivePtr _curRenderWindow; + IntrusivePtr _tempWindow; + ccstd::vector> _renderWindows; + IntrusivePtr _pipeline{nullptr}; + std::unique_ptr _pipelineRuntime; + // IntrusivePtr _dataPoolMgr; + ccstd::vector> _scenes; + std::unique_ptr _debugView; + float _cumulativeTime{0.F}; + float _frameTime{0.F}; + float _fpsTime{0.F}; + uint32_t _frameCount{0}; + uint32_t _fps{0}; + uint32_t _fixedFPS{0}; + bool _useDeferredPipeline{false}; + bool _usesCustomPipeline{true}; + IXRInterface *_xr{nullptr}; + events::WindowDestroy::Listener _windowDestroyListener; + events::WindowRecreated::Listener _windowRecreatedListener; + + // Cache ccstd::vector to avoid allocate every frame in frameMove + ccstd::vector _cameraList; + ccstd::vector _swapchains; + // +}; +} // namespace cc diff --git a/cocos/core/System.h b/cocos/core/System.h new file mode 100644 index 0000000..ce56fd7 --- /dev/null +++ b/cocos/core/System.h @@ -0,0 +1,100 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include "base/Macros.h" +#include "base/std/container/string.h" + +namespace cc { + +namespace core { + +struct ISchedulable { + ccstd::string id; + ccstd::string uuid; +}; + +enum struct Priority : uint32_t { + LOW = 0, + MEDIUM = 100, + HIGH = 200, + SCHEDULER = UINT32_MAX, +}; + +class System : public ISchedulable { +private: + /* data */ +protected: + Priority _priority{Priority::LOW}; + bool _executeInEditMode{false}; + +public: + /** + * @en Sorting between different systems. + * @zh 不同系统间排序。 + * @param a System a + * @param b System b + */ + static int32_t sortByPriority(System *a, System *b) { + if (a->_priority < b->_priority) return 1; + if (a->_priority > b->_priority) return -1; + return 0; + } + + System() = default; + virtual ~System() = default; + + inline const ccstd::string &getId() { return id; } + inline void setId(ccstd::string &s) { id = s; } + + inline Priority getPriority() const { return _priority; } + inline void setPriority(Priority i) { _priority = i; } + + inline bool getExecuteInEditMode() const { return _executeInEditMode; } + inline void setExecuteInEditMode(bool b) { _executeInEditMode = b; } + + /** + * @en Init the system, will be invoked by [[Director]] when registered, should be implemented if needed. + * @zh 系统初始化函数,会在注册时被 [[Director]] 调用,如果需要的话应该由子类实现 + */ + virtual void init() = 0; + + /** + * @en Update function of the system, it will be invoked between all components update phase and late update phase. + * @zh 系统的帧更新函数,它会在所有组件的 update 和 lateUpdate 之间被调用 + * @param dt Delta time after the last frame + */ + virtual void update(float dt) = 0; + + /** + * @en Post update function of the system, it will be invoked after all components late update phase and before the rendering process. + * @zh 系统的帧后处理函数,它会在所有组件的 lateUpdate 之后以及渲染之前被调用 + * @param dt Delta time after the last frame + */ + virtual void postUpdate(float dt) = 0; +}; + +} // namespace core + +} // namespace cc diff --git a/cocos/core/TypedArray.cpp b/cocos/core/TypedArray.cpp new file mode 100644 index 0000000..4cf7649 --- /dev/null +++ b/cocos/core/TypedArray.cpp @@ -0,0 +1,124 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/TypedArray.h" + +namespace cc { + +uint32_t getTypedArrayLength(const TypedArray &arr) { +#define TYPEDARRAY_GET_SIZE(type) \ + do { \ + auto *p = ccstd::get_if(&arr); \ + if (p != nullptr) { \ + return p->length(); \ + } \ + } while (false) + + TYPEDARRAY_GET_SIZE(Float32Array); + TYPEDARRAY_GET_SIZE(Uint32Array); + TYPEDARRAY_GET_SIZE(Uint16Array); + TYPEDARRAY_GET_SIZE(Uint8Array); + TYPEDARRAY_GET_SIZE(Int32Array); + TYPEDARRAY_GET_SIZE(Int16Array); + TYPEDARRAY_GET_SIZE(Int8Array); + TYPEDARRAY_GET_SIZE(Float64Array); + +#undef TYPEDARRAY_GET_SIZE + return 0; +} + +uint32_t getTypedArrayBytesPerElement(const TypedArray &arr) { +#define TYPEDARRAY_GET_BYTES_PER_ELEMENT(type) \ + do { \ + auto *p = ccstd::get_if(&arr); \ + if (p != nullptr) { \ + return type::BYTES_PER_ELEMENT; \ + } \ + } while (false) + + TYPEDARRAY_GET_BYTES_PER_ELEMENT(Float32Array); + TYPEDARRAY_GET_BYTES_PER_ELEMENT(Uint32Array); + TYPEDARRAY_GET_BYTES_PER_ELEMENT(Uint16Array); + TYPEDARRAY_GET_BYTES_PER_ELEMENT(Uint8Array); + TYPEDARRAY_GET_BYTES_PER_ELEMENT(Int32Array); + TYPEDARRAY_GET_BYTES_PER_ELEMENT(Int16Array); + TYPEDARRAY_GET_BYTES_PER_ELEMENT(Int8Array); + TYPEDARRAY_GET_BYTES_PER_ELEMENT(Float64Array); + +#undef TYPEDARRAY_GET_BYPES_PER_ELEMENT + return 0; +} + +void setTypedArrayValue(TypedArray &arr, uint32_t idx, const TypedArrayElementType &value) { +#define TYPEDARRAY_SET_VALUE(type, elemType) \ + do { \ + auto *p = ccstd::get_if(&value); \ + if (p != nullptr) { \ + if (ccstd::holds_alternative(arr)) { \ + ccstd::get(arr)[idx] = static_cast(*p); \ + return; \ + } \ + if (ccstd::holds_alternative(arr)) { \ + ccstd::get(arr)[idx] = static_cast(*p); \ + return; \ + } \ + if (ccstd::holds_alternative(arr)) { \ + ccstd::get(arr)[idx] = static_cast(*p); \ + return; \ + } \ + if (ccstd::holds_alternative(arr)) { \ + ccstd::get(arr)[idx] = static_cast(*p); \ + return; \ + } \ + if (ccstd::holds_alternative(arr)) { \ + ccstd::get(arr)[idx] = static_cast(*p); \ + return; \ + } \ + if (ccstd::holds_alternative(arr)) { \ + ccstd::get(arr)[idx] = static_cast(*p); \ + return; \ + } \ + if (ccstd::holds_alternative(arr)) { \ + ccstd::get(arr)[idx] = static_cast(*p); \ + return; \ + } \ + if (ccstd::holds_alternative(arr)) { \ + ccstd::get(arr)[idx] = static_cast(*p); \ + return; \ + } \ + } \ + } while (false) + + TYPEDARRAY_SET_VALUE(Float32Array, float); + TYPEDARRAY_SET_VALUE(Uint32Array, uint32_t); + TYPEDARRAY_SET_VALUE(Uint16Array, uint16_t); + TYPEDARRAY_SET_VALUE(Uint8Array, uint8_t); + TYPEDARRAY_SET_VALUE(Int32Array, int32_t); + TYPEDARRAY_SET_VALUE(Int16Array, int16_t); + TYPEDARRAY_SET_VALUE(Int8Array, int8_t); // NOLINT + TYPEDARRAY_SET_VALUE(Float64Array, double); +#undef TYPEDARRAY_SET_VALUE +} + +} // namespace cc diff --git a/cocos/core/TypedArray.h b/cocos/core/TypedArray.h new file mode 100644 index 0000000..64b5302 --- /dev/null +++ b/cocos/core/TypedArray.h @@ -0,0 +1,398 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include "base/TypeDef.h" +#include "base/std/variant.h" +#include "bindings/jswrapper/Object.h" +#include "core/ArrayBuffer.h" + +namespace cc { + +template +se::Object::TypedArrayType toTypedArrayType() { + return se::Object::TypedArrayType::NONE; +} + +template <> +inline se::Object::TypedArrayType toTypedArrayType() { + return se::Object::TypedArrayType::INT8; +} + +template <> +inline se::Object::TypedArrayType toTypedArrayType() { + return se::Object::TypedArrayType::INT16; +} + +template <> +inline se::Object::TypedArrayType toTypedArrayType() { + return se::Object::TypedArrayType::INT32; +} + +template <> +inline se::Object::TypedArrayType toTypedArrayType() { + return se::Object::TypedArrayType::UINT8; +} + +template <> +inline se::Object::TypedArrayType toTypedArrayType() { + return se::Object::TypedArrayType::UINT16; +} + +template <> +inline se::Object::TypedArrayType toTypedArrayType() { + return se::Object::TypedArrayType::UINT32; +} + +template <> +inline se::Object::TypedArrayType toTypedArrayType() { + return se::Object::TypedArrayType::FLOAT32; +} + +template <> +inline se::Object::TypedArrayType toTypedArrayType() { + return se::Object::TypedArrayType::FLOAT64; +} + +template +class TypedArrayTemp { +public: + static constexpr uint32_t BYTES_PER_ELEMENT{sizeof(T)}; + using value_type = T; + + TypedArrayTemp() = default; + + explicit TypedArrayTemp(uint32_t length) { + reset(length); + } + + explicit TypedArrayTemp(ArrayBuffer *buffer) + : TypedArrayTemp(buffer, 0) {} + + TypedArrayTemp(ArrayBuffer *buffer, uint32_t byteOffset) + : TypedArrayTemp(buffer, byteOffset, (buffer->byteLength() - byteOffset) / BYTES_PER_ELEMENT) {} + + TypedArrayTemp(ArrayBuffer *buffer, uint32_t byteOffset, uint32_t length) + : _buffer(buffer), + _byteOffset(byteOffset), + _byteLength(length * BYTES_PER_ELEMENT), + _byteEndPos(byteOffset + length * BYTES_PER_ELEMENT) { + CC_ASSERT(_byteEndPos <= _buffer->byteLength()); + _jsTypedArray = se::Object::createTypedArrayWithBuffer(toTypedArrayType(), buffer->getJSArrayBuffer(), byteOffset, _byteLength); + _jsTypedArray->root(); + } + + TypedArrayTemp(const TypedArrayTemp &o) { + *this = o; + } + + TypedArrayTemp(TypedArrayTemp &&o) noexcept { + *this = std::move(o); + } + + ~TypedArrayTemp() { + if (_jsTypedArray != nullptr) { + _jsTypedArray->unroot(); + _jsTypedArray->decRef(); + } + } + + TypedArrayTemp &operator=(const TypedArrayTemp &o) { + if (this != &o) { + setJSTypedArray(o._jsTypedArray); + } + return *this; + } + + TypedArrayTemp &operator=(TypedArrayTemp &&o) noexcept { + if (this != &o) { + _buffer = o._buffer; + _byteOffset = o._byteOffset; + _byteLength = o._byteLength; + _byteEndPos = o._byteEndPos; + + if (_jsTypedArray != nullptr) { + _jsTypedArray->unroot(); + _jsTypedArray->decRef(); + } + _jsTypedArray = o._jsTypedArray; + + o._buffer = nullptr; + o._byteOffset = 0; + o._byteLength = 0; + o._byteEndPos = 0; + o._jsTypedArray = nullptr; + } + return *this; + } + + T &operator[](uint32_t idx) { + CC_ASSERT(idx < length()); + return *((reinterpret_cast(_buffer->_data + _byteOffset)) + idx); + } + + const T &operator[](uint32_t idx) const { + CC_ASSERT(idx < length()); + return *((reinterpret_cast(_buffer->_data + _byteOffset)) + idx); + } + + TypedArrayTemp subarray(uint32_t begin, uint32_t end) { + return TypedArrayTemp(_buffer, begin * BYTES_PER_ELEMENT + _byteOffset, end - begin); + } + + TypedArrayTemp subarray(uint32_t begin) { + return TypedArrayTemp(_buffer, begin * BYTES_PER_ELEMENT + _byteOffset); + } + + TypedArrayTemp slice() { + return slice(0); + } + + TypedArrayTemp slice(uint32_t start) { + return slice(start, _byteLength / BYTES_PER_ELEMENT); + } + + TypedArrayTemp slice(uint32_t start, uint32_t end) { + CC_ASSERT_GT(end, start); + CC_ASSERT(start < (_byteLength / BYTES_PER_ELEMENT)); + CC_ASSERT(end <= (_byteLength / BYTES_PER_ELEMENT)); + uint32_t newBufByteLength = (end - start) * BYTES_PER_ELEMENT; + auto *buffer = ccnew ArrayBuffer(newBufByteLength); + memcpy(buffer->getData(), _buffer->getData() + start * BYTES_PER_ELEMENT + _byteOffset, newBufByteLength); + return TypedArrayTemp(buffer); + } + + void set(ArrayBuffer *buffer) { + set(buffer, 0); + } + + void set(ArrayBuffer *buffer, uint32_t offset) { + CC_ASSERT(buffer->byteLength() + offset <= _byteEndPos); + CC_ASSERT(_buffer); + memcpy(_buffer->_data + offset, buffer->_data, buffer->byteLength()); + } + + template + void set(const TypedArrayTemp &array) { + set(array, 0); + } + + template + typename std::enable_if_t::value, void> + set(const TypedArrayTemp &array, uint32_t offset); + + template + typename std::enable_if_t::value, void> + set(const TypedArrayTemp &array, uint32_t offset); + + void reset(uint32_t length) { + if (_jsTypedArray != nullptr) { + _jsTypedArray->unroot(); + _jsTypedArray->decRef(); + _jsTypedArray = nullptr; + } + const uint32_t byteLength = length * BYTES_PER_ELEMENT; + _buffer = ccnew ArrayBuffer(byteLength); + _byteLength = _buffer->byteLength(); + _byteOffset = 0; + _byteEndPos = byteLength; + _jsTypedArray = se::Object::createTypedArrayWithBuffer(toTypedArrayType(), _buffer->getJSArrayBuffer(), 0, byteLength); + _jsTypedArray->root(); + } + + void clear() { + if (_jsTypedArray != nullptr) { + _jsTypedArray->unroot(); + _jsTypedArray->decRef(); + _jsTypedArray = nullptr; + } + _buffer = nullptr; + _byteLength = 0; + _byteOffset = 0; + _byteEndPos = 0; + } + + inline ArrayBuffer *buffer() const { return _buffer; } + inline uint32_t byteLength() const { return _byteLength; } + inline uint32_t length() const { return _byteLength / BYTES_PER_ELEMENT; } + inline uint32_t byteOffset() const { return _byteOffset; } + inline bool empty() const { return _byteLength == 0; } + inline se::Object *getJSTypedArray() const { return _jsTypedArray; } + inline void setJSTypedArray(se::Object *typedArray) { + if (_jsTypedArray != nullptr) { + _jsTypedArray->unroot(); + _jsTypedArray->decRef(); + } + _jsTypedArray = typedArray; + + if (_jsTypedArray != nullptr) { + _jsTypedArray->root(); + _jsTypedArray->incRef(); + + se::Value tmpVal; + _jsTypedArray->getProperty("buffer", &tmpVal, true); + CC_ASSERT(tmpVal.isObject()); + CC_ASSERT(tmpVal.toObject()->isArrayBuffer()); + + _buffer = ccnew ArrayBuffer(); + _buffer->setJSArrayBuffer(tmpVal.toObject()); + + _jsTypedArray->getProperty("byteOffset", &tmpVal, true); + CC_ASSERT(tmpVal.isNumber()); + _byteOffset = tmpVal.toUint32(); + + _jsTypedArray->getProperty("byteLength", &tmpVal, true); + CC_ASSERT(tmpVal.isNumber()); + _byteLength = tmpVal.toUint32(); + + _byteEndPos = _buffer->byteLength(); + } else { + _buffer = nullptr; + _byteOffset = 0; + _byteLength = 0; + _byteEndPos = 0; + } + } + +private: + ArrayBuffer::Ptr _buffer; + uint32_t _byteOffset{0}; + uint32_t _byteLength{0}; + uint32_t _byteEndPos{0}; + se::Object *_jsTypedArray{nullptr}; +}; + +template +template +typename std::enable_if_t::value, void> TypedArrayTemp::set(const TypedArrayTemp &array, uint32_t offset) { + CC_ASSERT(_buffer); + uint32_t dstByteOffset = offset * BYTES_PER_ELEMENT; + uint32_t srcByteOffset = array.byteOffset(); + uint32_t srcCount = array.length(); + CC_ASSERT(dstByteOffset + srcCount * TypedArrayTemp::BYTES_PER_ELEMENT <= _byteEndPos); + memcpy(_buffer->_data + dstByteOffset, array._buffer->_data + srcByteOffset, array.byteLength()); +} + +template +template +typename std::enable_if_t::value, void> TypedArrayTemp::set(const TypedArrayTemp &array, uint32_t offset) { + CC_ASSERT(_buffer); + uint32_t dstByteOffset = offset * BYTES_PER_ELEMENT; + uint32_t srcByteOffset = array.byteOffset(); + uint32_t srcCount = array.length(); + uint32_t remainCount = (_byteEndPos - dstByteOffset) / BYTES_PER_ELEMENT; + CC_ASSERT_LE(srcCount, remainCount); + for (uint32_t i = 0; i < srcCount; ++i) { + (*this)[offset + i] = reinterpret_cast(array[i]); + } +} + +using Int8Array = TypedArrayTemp; +using Int16Array = TypedArrayTemp; +using Int32Array = TypedArrayTemp; +using Uint8Array = TypedArrayTemp; +using Uint16Array = TypedArrayTemp; +using Uint32Array = TypedArrayTemp; +using Float32Array = TypedArrayTemp; +using Float64Array = TypedArrayTemp; +using TypedArray = ccstd::variant; +using TypedArrayElementType = ccstd::variant; + +uint32_t getTypedArrayLength(const TypedArray &arr); +uint32_t getTypedArrayBytesPerElement(const TypedArray &arr); + +template +T getTypedArrayValue(const TypedArray &arr, uint32_t idx) { +#define TYPEDARRAY_GET_VALUE(type) \ + do { \ + auto *p = ccstd::get_if(&arr); \ + if (p != nullptr) { \ + return static_cast((*p)[idx]); \ + } \ + } while (false) + + TYPEDARRAY_GET_VALUE(Float32Array); + TYPEDARRAY_GET_VALUE(Uint32Array); + TYPEDARRAY_GET_VALUE(Uint16Array); + TYPEDARRAY_GET_VALUE(Uint8Array); + TYPEDARRAY_GET_VALUE(Int32Array); + TYPEDARRAY_GET_VALUE(Int16Array); + TYPEDARRAY_GET_VALUE(Int8Array); + TYPEDARRAY_GET_VALUE(Float64Array); +#undef TYPEDARRAY_GET_VALUE + + return 0; +} + +void setTypedArrayValue(TypedArray &arr, uint32_t idx, const TypedArrayElementType &value); + +template +T &getTypedArrayValueRef(const TypedArray &arr, uint32_t idx) { +#define TYPEDARRAY_GET_VALUE_REF(type) \ + do { \ + auto *p = ccstd::get_if(&arr); \ + if (p != nullptr) { \ + return (*p)[idx]; \ + } \ + } while (false) + + TYPEDARRAY_GET_VALUE_REF(Float32Array); + TYPEDARRAY_GET_VALUE_REF(Uint32Array); + TYPEDARRAY_GET_VALUE_REF(Uint16Array); + TYPEDARRAY_GET_VALUE_REF(Uint8Array); + TYPEDARRAY_GET_VALUE_REF(Int32Array); + TYPEDARRAY_GET_VALUE_REF(Int16Array); + TYPEDARRAY_GET_VALUE_REF(Int8Array); + TYPEDARRAY_GET_VALUE_REF(Float64Array); +#undef TYPEDARRAY_GET_VALUE_REF +} + +template +T getTypedArrayElementValue(const TypedArrayElementType &element) { +#define CAST_TO_T(type) \ + do { \ + auto *p = ccstd::get_if(&element); \ + if (p != nullptr) { \ + return static_cast(*p); \ + } \ + } while (false) + + CAST_TO_T(float); + CAST_TO_T(uint32_t); + CAST_TO_T(uint16_t); + CAST_TO_T(uint8_t); + CAST_TO_T(int32_t); + CAST_TO_T(int16_t); + CAST_TO_T(int8_t); + CAST_TO_T(double); +#undef CAST_TO_T + + return 0; +} + +} // namespace cc diff --git a/cocos/core/Types.h b/cocos/core/Types.h new file mode 100644 index 0000000..8d2b05f --- /dev/null +++ b/cocos/core/Types.h @@ -0,0 +1,69 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/optional.h" + +#include + +#include "base/Value.h" +#include "math/Vec3.h" + +namespace cc { + +struct Error { + ccstd::optional msg; +}; + +struct BoundingBox { + Vec3 min; + Vec3 max; +}; + +struct VertexIdChannel { + uint32_t stream{0}; + uint32_t index{0}; +}; + +struct NativeDep { + ccstd::string uuid; + ccstd::string ext; + bool __isNative__{false}; // NOLINT(bugprone-reserved-identifier) + + explicit NativeDep() = default; + + explicit NativeDep(bool isNative, ccstd::string uuid, ccstd::string ext) + : uuid(std::move(uuid)), ext(std::move(ext)), __isNative__(isNative), _isValid(true) {} + + inline bool isValid() const { return _isValid; } + +private: + bool _isValid{false}; +}; + +} // namespace cc diff --git a/cocos/core/animation/SkeletalAnimationUtils.cpp b/cocos/core/animation/SkeletalAnimationUtils.cpp new file mode 100644 index 0000000..e0206e2 --- /dev/null +++ b/cocos/core/animation/SkeletalAnimationUtils.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/animation/SkeletalAnimationUtils.h" +#include "core/scene-graph/Node.h" + +namespace cc { + +namespace { +ccstd::vector stack; +ccstd::unordered_map pool; +} // namespace + +Mat4 getWorldMatrix(IJointTransform *transform, int32_t stamp) { + uint32_t i = 0; + Mat4 *res = nullptr; + while (transform != nullptr) { + if ((transform->stamp == stamp || transform->stamp + 1 == stamp) && !transform->node->getChangedFlags()) { + res = &transform->world; + transform->stamp = stamp; + break; + } + transform->stamp = stamp; + stack.resize(i + 1); + stack[i++] = transform; + transform = transform->parent; + } + while (i > 0) { + transform = stack[--i]; + stack[i] = nullptr; + const auto *node = transform->node; + CC_ASSERT_NOT_NULL(node); + Mat4::fromRTS(node->getRotation(), node->getPosition(), node->getScale(), &transform->local); + if (res != nullptr) { + Mat4::multiply(*res, transform->local, &transform->world); + } else { + transform->world = transform->local; + } + res = &transform->world; + } + return res != nullptr ? *res : Mat4::IDENTITY; +} + +IJointTransform *getTransform(Node *node, Node *root) { + IJointTransform *joint = nullptr; + uint32_t i = 0; + while (node != root) { + const ccstd::string &id = node->getUuid(); + auto iter = pool.find(id); + if (iter != pool.end()) { + joint = iter->second; + break; + } + // TODO(): object reuse + joint = ccnew IJointTransform; + joint->node = node; + pool[id] = joint; + stack.resize(i + 1); + stack[i++] = joint; + node = node->getParent(); + joint = nullptr; + } + IJointTransform *child; + while (i > 0) { + child = stack[--i]; + stack[i] = nullptr; + child->parent = joint; + joint = child; + } + return joint; +} + +void deleteTransform(Node *node) { + IJointTransform *transform = nullptr; + auto iter = pool.find(node->getUuid()); + if (iter != pool.end()) { + transform = iter->second; + } + + while (transform != nullptr) { + iter = pool.find(transform->node->getUuid()); + if (iter != pool.end()) { + pool.erase(iter); + } + transform = transform->parent; + } +} + +} // namespace cc diff --git a/cocos/core/animation/SkeletalAnimationUtils.h b/cocos/core/animation/SkeletalAnimationUtils.h new file mode 100644 index 0000000..439b760 --- /dev/null +++ b/cocos/core/animation/SkeletalAnimationUtils.h @@ -0,0 +1,61 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Ptr.h" +#include "base/RefCounted.h" +#include "math/Mat4.h" +#include "renderer/gfx-base/GFXTexture.h" + +namespace cc { + +class Node; + +struct IJointTransform : RefCounted { + Node *node{nullptr}; + Mat4 local; + Mat4 world; + int stamp{-1}; + IntrusivePtr parent; +}; + +struct RealTimeJointTexture { + ~RealTimeJointTexture() { + CC_SAFE_DELETE_ARRAY(buffer); + for (auto &texture : textures) { + texture->destroy(); + } + } + std::vector> textures; + float *buffer = nullptr; +}; + +Mat4 getWorldMatrix(IJointTransform *transform, int32_t stamp); + +IJointTransform *getTransform(Node *node, Node *root); + +void deleteTransform(Node *node); + +} // namespace cc diff --git a/cocos/core/assets/Asset.cpp b/cocos/core/assets/Asset.cpp new file mode 100644 index 0000000..07bdfc7 --- /dev/null +++ b/cocos/core/assets/Asset.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/Asset.h" +#include "base/DeferredReleasePool.h" +#include "base/Macros.h" +#include "core/utils/Path.h" + +namespace cc { + +//cjh TODO: +ccstd::string getAssetUrlWithUuid(const ccstd::string &uuid, bool isNative, const ccstd::string &nativeExt, const ccstd::string &nativeName = "") { //NOLINT + return ""; +} + +ccstd::string Asset::getNativeUrl() const { + if (_nativeUrl.empty()) { + if (_native.empty()) { + return ""; + } + const auto &name = _native; + if (name[0] == 47) { // '/' + // remove library tag + // not imported in library, just created on-the-fly + return name.substr(1); + } + + if (name[0] == 46) { // '.' + // imported in dir where json exist + const_cast(this)->_nativeUrl = getAssetUrlWithUuid(_uuid, true, name); + } else { + // imported in an independent dir + const_cast(this)->_nativeUrl = getAssetUrlWithUuid(_uuid, true, extname(name), name); + } + } + return _nativeUrl; +} +Asset::Asset() = default; +Asset::~Asset() = default; + +NativeDep Asset::getNativeDep() const { + if (!_native.empty()) { + return NativeDep(true, _uuid, _native); + } + return NativeDep(); +} + +void Asset::setRawAsset(const ccstd::string &filename, bool inLibrary /* = true*/) { + if (inLibrary) { + _native = filename; + } else { + _native = "/" + filename; // simply use '/' to tag location where is not in the library + } +} + +void Asset::addAssetRef() { + ++_assetRefCount; +} + +void Asset::decAssetRef(bool autoRelease /* = true*/) { + if (_assetRefCount > 0) { + --_assetRefCount; + } + + if (autoRelease) { + //cjh TODO: + } +} + +void Asset::initDefault(const ccstd::optional &uuid) { + if (uuid.has_value()) { + _uuid = uuid.value(); + } + _isDefault = true; +} + +bool Asset::destroy() { + //cjh TODO: debug(getError(12101, this._uuid)); + return Super::destroy(); +} + +void Asset::destruct() { + CCObject::destruct(); + _native.clear(); + _nativeUrl.clear(); +} + +} // namespace cc diff --git a/cocos/core/assets/Asset.h b/cocos/core/assets/Asset.h new file mode 100644 index 0000000..df6c9aa --- /dev/null +++ b/cocos/core/assets/Asset.h @@ -0,0 +1,159 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/std/any.h" +#include "base/std/optional.h" + +#include "base/Macros.h" +#include "core/Types.h" +#include "core/data/Object.h" +#include "core/event/EventTarget.h" + +namespace cc { + +class Node; + +class Asset : public CCObject { +public: + using Super = CCObject; + + Asset(); + ~Asset() override; + + /** + * @en + * Returns the url of this asset's native object, if none it will returns an empty string. + * @zh + * 返回该资源对应的目标平台资源的 URL,如果没有将返回一个空字符串。 + * @readOnly + */ + ccstd::string getNativeUrl() const; + + NativeDep getNativeDep() const; + + inline const ccstd::string &getUuid() const { return _uuid; } + inline void setUuid(const ccstd::string &uuid) { _uuid = uuid; } + + /** + * @en + * The underlying native asset of this asset if one is available.
+ * This property can be used to access additional details or functionality related to the asset.
+ * This property will be initialized by the loader if `_native` is available. + * @zh + * 此资源的基础资源(如果有)。 此属性可用于访问与资源相关的其他详细信息或功能。
+ * 如果`_native`可用,则此属性将由加载器初始化。 + * @default null + * @private + */ + virtual ccstd::any getNativeAsset() const { + return _file; + } + + virtual void setNativeAsset(const ccstd::any &obj) { + _file = obj; + } + + /** + * @param error - null or the error info + * @param node - the created node or null + */ + using CreateNodeCallback = std::function; + /** + * @en + * Create a new node using this asset in the scene.
+ * If this type of asset don't have its corresponding node type, this method should be null. + * @zh + * 使用该资源在场景中创建一个新节点。
+ * 如果这类资源没有相应的节点类型,该方法应该是空的。 + */ + virtual void createNode(const CreateNodeCallback &cb) {} + + void addAssetRef(); + void decAssetRef(bool autoRelease = true); + inline uint32_t getAssetRefCount() const { return _assetRefCount; } + + virtual void onLoaded() {} + + virtual void initDefault() { initDefault(ccstd::nullopt); } + + virtual void initDefault(const ccstd::optional &uuid); + + virtual bool validate() const { return true; } + + bool isDefault() const { return _isDefault; } + + bool destroy() override; + + void destruct() override; + + // SERIALIZATION + + /** + * @return + */ + virtual ccstd::any serialize(const ccstd::any & /*ctxForExporting*/) { return ccstd::any{}; }; + + /** + * + * @param data + */ + virtual void deserialize(const ccstd::any &serializedData, const ccstd::any &handle) {} + + ccstd::string toString() const override { return _nativeUrl; } + +protected: + /** + * @en + * Set native file name for this asset. + * @zh + * 为此资源设置原始文件名。 + * @seealso nativeUrl + * + * @param filename + * @param inLibrary + * @private + */ + void setRawAsset(const ccstd::string &filename, bool inLibrary = true); + + // Make _native, _nativeUrl public for deserialization +public: + ccstd::string _native; + ccstd::string _nativeUrl; + +protected: + ccstd::string _uuid; + + ccstd::any _file; + uint32_t _assetRefCount{0}; + + bool _loaded{true}; + bool _isDefault{false}; + + CC_DISALLOW_COPY_MOVE_ASSIGN(Asset); +}; + +} // namespace cc diff --git a/cocos/core/assets/AssetEnum.h b/cocos/core/assets/AssetEnum.h new file mode 100644 index 0000000..300daa0 --- /dev/null +++ b/cocos/core/assets/AssetEnum.h @@ -0,0 +1,305 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "renderer/gfx-base/GFXDef.h" + +namespace cc { + +// define a specified number for the pixel format which gfx do not have a standard definition. +#define CUSTOM_PIXEL_FORMAT (1024) + +/** + * @en + * The texture pixel format, default value is RGBA8888,
+ * you should note that textures loaded by normal image files (png, jpg) can only support RGBA8888 format,
+ * other formats are supported by compressed file types or raw data. + * @zh + * 纹理像素格式,默认值为RGBA8888,
+ * 你应该注意到普通图像文件(png,jpg)加载的纹理只能支持RGBA8888格式,
+ * 压缩文件类型或原始数据支持其他格式。 + */ +enum class PixelFormat : uint32_t { + /** + * @en + * 16-bit pixel format containing red, green and blue channels + * @zh + * 包含 RGB 通道的 16 位纹理。 + */ + RGB565 = static_cast(gfx::Format::R5G6B5), + /** + * @en + * 16-bit pixel format containing red, green, blue channels with 5 bits per channel and one bit alpha channel: RGB5A1 + * @zh + * 包含 RGB(分别占 5 bits)和 1 bit 的 alpha 通道的 16 位纹理:RGB5A1。 + */ + RGB5A1 = static_cast(gfx::Format::RGB5A1), + /** + * @en + * 16-bit pixel format containing red, green, blue and alpha channels: RGBA4444 + * @zh + * 包含 RGBA 通道的 16 位纹理:RGBA4444。 + */ + RGBA4444 = static_cast(gfx::Format::RGBA4), + /** + * @en + * 24-bit pixel format containing red, green and blue channels: RGB888 + * @zh + * 包含 RGB 通道的 24 位纹理:RGB888。 + */ + RGB888 = static_cast(gfx::Format::RGB8), + /** + * @en + * 32-bit float pixel format containing red, green and blue channels: RGBA32F + * @zh + * 包含 RGB 通道的 32 位浮点数像素格式:RGBA32F。 + */ + RGB32F = static_cast(gfx::Format::RGB32F), + /** + * @en + * 32-bit pixel format containing red, green, blue and alpha channels: RGBA8888 + * @zh + * 包含 RGBA 四通道的 32 位整形像素格式:RGBA8888。 + */ + RGBA8888 = static_cast(gfx::Format::RGBA8), + /** + * @en + * 32-bit float pixel format containing red, green, blue and alpha channels: RGBA32F + * @zh + * 32位浮点数像素格式:RGBA32F。 + */ + RGBA32F = static_cast(gfx::Format::RGBA32F), + /** + * @en + * 8-bit pixel format used as masks + * @zh + * 用作蒙版的8位纹理。 + */ + A8 = static_cast(gfx::Format::A8), + /** + * @en + * 8-bit intensity pixel format + * @zh + * 8位强度纹理。 + */ + I8 = static_cast(gfx::Format::L8), + /** + * @en + * 16-bit pixel format used as masks + * @zh + * 用作蒙版的16位纹理。 + */ + AI8 = static_cast(gfx::Format::LA8), + /** + * @en A pixel format containing red, green, and blue channels that is PVR 2bpp compressed. + * @zh 包含 RGB 通道的 PVR 2BPP 压缩纹理格式 + */ + RGB_PVRTC_2BPPV1 = static_cast(gfx::Format::PVRTC_RGB2), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is PVR 2bpp compressed. + * @zh 包含 RGBA 通道的 PVR 2BPP 压缩纹理格式 + */ + RGBA_PVRTC_2BPPV1 = static_cast(gfx::Format::PVRTC_RGBA2), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is PVR 2bpp compressed. + * RGB_A_PVRTC_2BPPV1 texture is a 2x height RGB_PVRTC_2BPPV1 format texture. + * It separate the origin alpha channel to the bottom half atlas, the origin rgb channel to the top half atlas. + * @zh 包含 RGBA 通道的 PVR 2BPP 压缩纹理格式 + * 这种压缩纹理格式贴图的高度是普通 RGB_PVRTC_2BPPV1 贴图高度的两倍,使用上半部分作为原始 RGB 通道数据,下半部分用来存储透明通道数据。 + */ + RGB_A_PVRTC_2BPPV1 = CUSTOM_PIXEL_FORMAT, + /** + * @en A pixel format containing red, green, and blue channels that is PVR 4bpp compressed. + * @zh 包含 RGB 通道的 PVR 4BPP 压缩纹理格式 + */ + RGB_PVRTC_4BPPV1 = static_cast(gfx::Format::PVRTC_RGB4), + /** + * @en A pixel format containing red, green, blue and alpha channels that is PVR 4bpp compressed. + * @zh 包含 RGBA 通道的 PVR 4BPP 压缩纹理格式 + */ + RGBA_PVRTC_4BPPV1 = static_cast(gfx::Format::PVRTC_RGBA4), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is PVR 4bpp compressed. + * RGB_A_PVRTC_4BPPV1 texture is a 2x height RGB_PVRTC_4BPPV1 format texture. + * It separate the origin alpha channel to the bottom half atlas, the origin rgb channel to the top half atlas. + * @zh 包含 RGBA 通道的 PVR 4BPP 压缩纹理格式 + * 这种压缩纹理格式贴图的高度是普通 RGB_PVRTC_4BPPV1 贴图高度的两倍,使用上半部分作为原始 RGB 通道数据,下半部分用来存储透明通道数据。 + */ + RGB_A_PVRTC_4BPPV1 = CUSTOM_PIXEL_FORMAT + 1, + /** + * @en A pixel format containing red, green, and blue channels that is ETC1 compressed. + * @zh 包含 RGB 通道的 ETC1 压缩纹理格式 + */ + RGB_ETC1 = static_cast(gfx::Format::ETC_RGB8), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ETC1 compressed. + * @zh 包含 RGBA 通道的 ETC1 压缩纹理格式 + */ + RGBA_ETC1 = CUSTOM_PIXEL_FORMAT + 2, + /** + * @en A pixel format containing red, green, and blue channels that is ETC2 compressed. + * @zh 包含 RGB 通道的 ETC2 压缩纹理格式 + */ + RGB_ETC2 = static_cast(gfx::Format::ETC2_RGB8), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ETC2 compressed. + * @zh 包含 RGBA 通道的 ETC2 压缩纹理格式 + */ + RGBA_ETC2 = static_cast(gfx::Format::ETC2_RGBA8), + + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 4x4 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 4x4 + */ + RGBA_ASTC_4X4 = static_cast(gfx::Format::ASTC_RGBA_4X4), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 5x4 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 5x4 + */ + RGBA_ASTC_5X4 = static_cast(gfx::Format::ASTC_RGBA_5X4), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 5x5 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 5x5 + */ + RGBA_ASTC_5X5 = static_cast(gfx::Format::ASTC_RGBA_5X5), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 6x5 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 6x5 + */ + RGBA_ASTC_6X5 = static_cast(gfx::Format::ASTC_RGBA_6X5), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 6x6 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 6x6 + */ + RGBA_ASTC_6X6 = static_cast(gfx::Format::ASTC_RGBA_6X6), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 8x5 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 8x5 + */ + RGBA_ASTC_8X5 = static_cast(gfx::Format::ASTC_RGBA_8X5), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 8x6 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 8x6 + */ + RGBA_ASTC_8X6 = static_cast(gfx::Format::ASTC_RGBA_8X6), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 8x8 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 8x8 + */ + RGBA_ASTC_8X8 = static_cast(gfx::Format::ASTC_RGBA_8X8), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 10x5 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 10x5 + */ + RGBA_ASTC_10X5 = static_cast(gfx::Format::ASTC_RGBA_10X5), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 10x6 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 10x6 + */ + RGBA_ASTC_10X6 = static_cast(gfx::Format::ASTC_RGBA_10X6), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 10x8 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 10x8 + */ + RGBA_ASTC_10X8 = static_cast(gfx::Format::ASTC_RGBA_10X8), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 10x10 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 10x10 + */ + RGBA_ASTC_10X10 = static_cast(gfx::Format::ASTC_RGBA_10X10), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 12x10 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 12x10 + */ + RGBA_ASTC_12X10 = static_cast(gfx::Format::ASTC_RGBA_12X10), + /** + * @en A pixel format containing red, green, blue, and alpha channels that is ASTC compressed with 12x12 block size. + * @zh 包含 RGBA 通道的 ASTC 压缩纹理格式,压缩分块大小为 12x12 + */ + RGBA_ASTC_12X12 = static_cast(gfx::Format::ASTC_RGBA_12X12), +}; + +/** + * @en + * The texture wrap mode. + * @zh + * 纹理环绕方式。 + */ +enum class WrapMode : uint32_t { + /** + * @en + * Specifies that the repeat warp mode will be used. + * @zh + * 指定环绕模式:重复纹理图像。 + */ + REPEAT = static_cast(gfx::Address::WRAP), + /** + * @en + * Specifies that the clamp to edge warp mode will be used. + * @zh + * 指定环绕模式:纹理边缘拉伸效果。 + */ + CLAMP_TO_EDGE = static_cast(gfx::Address::CLAMP), + /** + * @en + * Specifies that the mirrored repeat warp mode will be used. + * @zh + * 指定环绕模式:以镜像模式重复纹理图像。 + */ + MIRRORED_REPEAT = static_cast(gfx::Address::MIRROR), + /** + * @en + * Specifies that the clamp to border wrap mode will be used. + * @zh + * 指定环绕模式:超出纹理坐标部分以用户指定颜色填充。 + */ + CLAMP_TO_BORDER = static_cast(gfx::Address::BORDER), +}; + +/** + * @en + * The texture filter mode + * @zh + * 纹理过滤模式。 + */ +enum class Filter : uint32_t { + NONE = static_cast(gfx::Filter::NONE), + /** + * @en + * Specifies linear filtering. + * @zh + * 线性过滤模式。 + */ + LINEAR = static_cast(gfx::Filter::LINEAR), + /** + * @en + * Specifies nearest filtering. + * @zh + * 临近过滤模式。 + */ + NEAREST = static_cast(gfx::Filter::POINT), +}; + +} // namespace cc diff --git a/cocos/core/assets/AssetsModuleHeader.h b/cocos/core/assets/AssetsModuleHeader.h new file mode 100644 index 0000000..9ec4c3e --- /dev/null +++ b/cocos/core/assets/AssetsModuleHeader.h @@ -0,0 +1,39 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/assets/Asset.h" +#include "core/assets/BufferAsset.h" +#include "core/assets/EffectAsset.h" +#include "core/assets/ImageAsset.h" +//#include "core/assets/JsonAsset.h" +#include "core/assets/Material.h" +//#include "core/assets/Prefab.h" +#include "core/assets/RenderTexture.h" +#include "core/assets/RenderingSubMesh.h" +#include "core/assets/SceneAsset.h" +#include "core/assets/TextAsset.h" +#include "core/assets/Texture2D.h" +#include "core/assets/TextureCube.h" diff --git a/cocos/core/assets/BitmapFont.cpp b/cocos/core/assets/BitmapFont.cpp new file mode 100644 index 0000000..b161968 --- /dev/null +++ b/cocos/core/assets/BitmapFont.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "BitmapFont.h" +#include "base/Log.h" +#include "gfx-base/GFXDevice.h" +#include "platform/Image.h" +#include "tinyxml2/tinyxml2.h" + +namespace cc { + +/** + * BitmapFontFace + */ + +BitmapFontFace::BitmapFontFace(Font *font) +: FontFace(font) { +} +void BitmapFontFace::doInit(const FontFaceInfo & /*info*/) { + const auto &fontPath = _font->getPath(); + const auto &fontData = _font->getData(); + if (fontData.empty()) { + CC_LOG_ERROR("BitmapFontFace doInit failed: empty font data."); + return; + } + + tinyxml2::XMLDocument doc; + + auto error = doc.Parse(reinterpret_cast(fontData.data()), fontData.size()); + if (error) { + CC_LOG_ERROR("BitmapFontFace parse failed."); + return; + } + + auto *fontNode = doc.RootElement(); + auto *infoNode = fontNode->FirstChildElement("info"); + _fontSize = infoNode->UnsignedAttribute("size"); + + auto *commonNode = fontNode->FirstChildElement("common"); + _lineHeight = commonNode->UnsignedAttribute("lineHeight"); + _textureWidth = commonNode->UnsignedAttribute("scaleW"); + _textureHeight = commonNode->UnsignedAttribute("scaleH"); + uint32_t pages = commonNode->UnsignedAttribute("pages"); + int base = commonNode->IntAttribute("base"); + + // load glyphs + auto *charsNode = fontNode->FirstChildElement("chars"); + auto *charNode = charsNode->FirstChildElement("char"); + while (charNode) { + FontGlyph glyph; + uint32_t value{0U}; + + uint32_t code = charNode->UnsignedAttribute("id"); + glyph.x = static_cast(charNode->IntAttribute("x")); + glyph.y = static_cast(charNode->IntAttribute("y")); + glyph.width = static_cast(charNode->UnsignedAttribute("width")); + glyph.height = static_cast(charNode->UnsignedAttribute("height")); + glyph.bearingX = static_cast(charNode->IntAttribute("xoffset")); + glyph.bearingY = static_cast(base - charNode->IntAttribute("yoffset")); + glyph.advance = static_cast(charNode->IntAttribute("xadvance")); + glyph.page = charNode->UnsignedAttribute("page"); + + _glyphs[code] = glyph; + charNode = charNode->NextSiblingElement(); + } + + // load textures + auto *pagesNode = fontNode->FirstChildElement("pages"); + auto *pageNode = pagesNode->FirstChildElement("page"); + _textures.resize(pages, nullptr); + + ccstd::string path = fontPath; + auto pos = fontPath.rfind('/'); + + if (pos == ccstd::string::npos) { + pos = fontPath.rfind('\\'); + } + + if (pos != ccstd::string::npos) { + path = fontPath.substr(0, pos + 1); + } + + while (pageNode) { + uint32_t id = pageNode->UnsignedAttribute("id"); + ccstd::string file = pageNode->Attribute("file"); + _textures[id] = loadTexture(path + file); + + pageNode = pageNode->NextSiblingElement(); + } + + // load kernings + auto *kerningsNode = fontNode->FirstChildElement("kernings"); + auto *kerningNode = kerningsNode->FirstChildElement("kerning"); + while (kerningNode) { + KerningPair pair; + pair.prevCode = kerningNode->UnsignedAttribute("first"); + pair.nextCode = kerningNode->UnsignedAttribute("second"); + _kernings[pair] = static_cast(kerningNode->IntAttribute("amount")); + + kerningNode = kerningNode->NextSiblingElement(); + } +} + +const FontGlyph *BitmapFontFace::getGlyph(uint32_t code) { + auto iter = _glyphs.find(code); + if (iter != _glyphs.end()) { + return &iter->second; + } + + CC_LOG_WARNING("BitmapFontFace getGlyph failed, character: %u.", code); + + return nullptr; +} + +float BitmapFontFace::getKerning(uint32_t prevCode, uint32_t nextCode) { + if (_kernings.empty()) { + return 0.0F; + } + + const auto &iter = _kernings.find({prevCode, nextCode}); + if (iter != _kernings.end()) { + return iter->second; + } + + return 0.0F; +} + +gfx::Texture *BitmapFontFace::loadTexture(const ccstd::string &path) { + auto *image = ccnew Image(); + bool success = image->initWithImageFile(path); + if (!success) { + CC_LOG_WARNING("BitmapFontFace initWithImageFile failed, path: %s.", path.c_str()); + delete image; + return nullptr; + } + + if (image->getRenderFormat() != gfx::Format::R8 && image->getRenderFormat() != gfx::Format::L8) { + CC_LOG_WARNING("BitmapFontFace loadTexture with invalid format, path: %s.", path.c_str()); + delete image; + return nullptr; + } + + auto width = static_cast(image->getWidth()); + auto height = static_cast(image->getHeight()); + + auto *device = gfx::Device::getInstance(); + auto *texture = device->createTexture({gfx::TextureType::TEX2D, + gfx::TextureUsageBit::SAMPLED | gfx::TextureUsageBit::TRANSFER_DST, + gfx::Format::R8, + width, + height}); + + gfx::BufferDataList buffers{image->getData()}; + gfx::BufferTextureCopyList regions = {{0U, + 0U, + 0U, + {0U, 0U, 0U}, + {width, height, 1U}, + {0U, 0U, 1U}}}; + + device->copyBuffersToTexture(buffers, texture, regions); + delete image; + + return texture; +} + +/** + * BitmapFont + */ +BitmapFont::BitmapFont(const ccstd::string &path) +: Font(FontType::BITMAP, path) { +} + +FontFace *BitmapFont::createFace(const FontFaceInfo &info) { + auto *face = ccnew BitmapFontFace(this); + face->doInit(info); + + uint32_t fontSize = face->getFontSize(); + _faces[fontSize] = face; + + return face; +} + +} // namespace cc diff --git a/cocos/core/assets/BitmapFont.h b/cocos/core/assets/BitmapFont.h new file mode 100644 index 0000000..bbe32c2 --- /dev/null +++ b/cocos/core/assets/BitmapFont.h @@ -0,0 +1,67 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "Font.h" + +namespace cc { + +/** + * BitmapFontFace + */ +class BitmapFontFace : public FontFace { +public: + explicit BitmapFontFace(Font *font); + ~BitmapFontFace() override = default; + BitmapFontFace(const BitmapFontFace &) = delete; + BitmapFontFace(BitmapFontFace &&) = delete; + BitmapFontFace &operator=(const BitmapFontFace &) = delete; + BitmapFontFace &operator=(BitmapFontFace &&) = delete; + + const FontGlyph *getGlyph(uint32_t code) override; + float getKerning(uint32_t prevCode, uint32_t nextCode) override; + +private: + void doInit(const FontFaceInfo &info) override; + static gfx::Texture *loadTexture(const ccstd::string &path); + + friend class BitmapFont; +}; + +/** + * BitmapFont + */ +class BitmapFont : public Font { +public: + explicit BitmapFont(const ccstd::string &path); + ~BitmapFont() override = default; + BitmapFont(const BitmapFont &) = delete; + BitmapFont(BitmapFont &&) = delete; + BitmapFont &operator=(const BitmapFont &) = delete; + BitmapFont &operator=(BitmapFont &&) = delete; + + FontFace *createFace(const FontFaceInfo &info) override; +}; + +} // namespace cc diff --git a/cocos/core/assets/BufferAsset.cpp b/cocos/core/assets/BufferAsset.cpp new file mode 100644 index 0000000..86eefe1 --- /dev/null +++ b/cocos/core/assets/BufferAsset.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/BufferAsset.h" + +namespace cc { + +ccstd::any BufferAsset::getNativeAsset() const { + return _buffer; +} + +void BufferAsset::setNativeAsset(const ccstd::any &obj) { + _buffer = ccstd::any_cast(obj); +} + +} // namespace cc diff --git a/cocos/core/assets/BufferAsset.h b/cocos/core/assets/BufferAsset.h new file mode 100644 index 0000000..4bd4fde --- /dev/null +++ b/cocos/core/assets/BufferAsset.h @@ -0,0 +1,52 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/ArrayBuffer.h" +#include "core/assets/Asset.h" + +namespace cc { + +class BufferAsset final : public Asset { +public: + BufferAsset() = default; + ~BufferAsset() override = default; + + inline ArrayBuffer *getBuffer() const { return _buffer; } + + inline void setNativeAssetForJS(ArrayBuffer *buffer) { _buffer = buffer; } + inline ArrayBuffer *getNativeAssetForJS() const { return _buffer; } + + ccstd::any getNativeAsset() const override; + void setNativeAsset(const ccstd::any &obj) override; + bool validate() const override { return _buffer != nullptr; } + +private: + ArrayBuffer::Ptr _buffer; + + CC_DISALLOW_COPY_MOVE_ASSIGN(BufferAsset); +}; + +} // namespace cc diff --git a/cocos/core/assets/EffectAsset.cpp b/cocos/core/assets/EffectAsset.cpp new file mode 100644 index 0000000..3de9bb7 --- /dev/null +++ b/cocos/core/assets/EffectAsset.cpp @@ -0,0 +1,354 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/EffectAsset.h" +#include "ProgramUtils.h" +#include "cocos.h" +#include "cocos/renderer/pipeline/custom/RenderingModule.h" +#include "core/Root.h" +#include "core/platform/Debug.h" +#include "engine/BaseEngine.h" +#include "renderer/core/ProgramLib.h" + +namespace cc { + +IPassStates::IPassStates(const IPassInfoFull &o) { + *this = o; +} + +IPassStates &IPassStates::operator=(const IPassInfoFull &o) { + priority = o.priority; + primitive = o.primitive; + stage = o.stage; + rasterizerState = o.rasterizerState; + depthStencilState = o.depthStencilState; + blendState = o.blendState; + dynamicStates = o.dynamicStates; + phase = o.phase; + subpass = o.subpass; + return *this; +} + +void IPassStates::overrides(const IPassInfoFull &o) { + if (o.priority.has_value()) { + this->priority = o.priority.value(); + } + if (o.primitive.has_value()) { + this->primitive = o.primitive.value(); + } + if (o.stage.has_value()) { + this->stage = o.stage.value(); + } + if (o.rasterizerState.has_value()) { + this->rasterizerState = o.rasterizerState.value(); + } + if (o.depthStencilState.has_value()) { + this->depthStencilState = o.depthStencilState.value(); + } + if (o.blendState.has_value()) { + this->blendState = o.blendState.value(); + } + if (o.dynamicStates.has_value()) { + this->dynamicStates = o.dynamicStates.value(); + } + if (o.phase.has_value()) { + this->phase = o.phase.value(); + } + if (o.subpass.has_value()) { + this->subpass = o.subpass.value(); + } +} + +EffectAsset::RegisteredEffectAssetMap EffectAsset::effects; +bool EffectAsset::layoutValid = true; + +/* static */ +void EffectAsset::registerAsset(EffectAsset *asset) { + if (asset == nullptr) { + return; + } + + EffectAsset::effects.emplace(asset->getName(), asset); + layoutValid = false; +} + +/* static */ +void EffectAsset::remove(const ccstd::string &name) { + auto iter = EffectAsset::effects.find(name); + if (iter != EffectAsset::effects.end() && iter->second->getName() == name) { + EffectAsset::effects.erase(iter); + return; + } + + iter = EffectAsset::effects.begin(); + for (; iter != EffectAsset::effects.end(); ++iter) { + if (iter->second->getUuid() == name) { + break; + } + } + + if (iter != EffectAsset::effects.end()) { + EffectAsset::effects.erase(iter); + } +} + +/* static */ +void EffectAsset::remove(EffectAsset *asset) { + if (asset == nullptr) { + return; + } + + auto iter = EffectAsset::effects.find(asset->getName()); + if (iter != EffectAsset::effects.end() && iter->second == asset) { + EffectAsset::effects.erase(iter); + } +} + +/* static */ +EffectAsset *EffectAsset::get(const ccstd::string &name) { + auto iter = EffectAsset::effects.find(name); + if (iter != EffectAsset::effects.end()) { + return iter->second; + } + + iter = EffectAsset::effects.begin(); + for (; iter != EffectAsset::effects.end(); ++iter) { + if (iter->second->getUuid() == name) { + return iter->second; + } + } + static ccstd::vector legacyBuiltinEffectNames{ + "planar-shadow", + "skybox", + "deferred-lighting", + "bloom", + "copy-pass", + "post-process", + "profiler", + "splash-screen", + "standard", + "unlit", + "sprite", + "particle", + "particle-gpu", + "particle-trail", + "billboard", + "terrain", + "graphics", + "clear-stencil", + "spine", + "occlusion-query", + "geometry-renderer", + "debug-renderer"}; + for (auto &legacyName : legacyBuiltinEffectNames) { + if (name == legacyName) { + debug::warnID(16101, name); + } + } + + return nullptr; +} + +void EffectAsset::onLoaded() { + auto *programLib = render::getProgramLibrary(); + if (programLib) { + render::addEffectDefaultProperties(*this); + programLib->addEffect(this); + } else { + ProgramLib::getInstance()->registerEffect(this); + } + EffectAsset::registerAsset(this); +#if !CC_EDITOR + if (CC_CURRENT_ENGINE()->isInited()) { + precompile(); + } else { + _engineEventId = CC_CURRENT_ENGINE()->on([this](BaseEngine * /*emitter*/, BaseEngine::EngineStatus status) { + if (status == BaseEngine::EngineStatus::ON_START) { + this->precompile(); + } + }); + } +#endif +} + +bool EffectAsset::destroy() { + EffectAsset::remove(this); + if (CC_CURRENT_ENGINE()->isInited()) { + CC_CURRENT_ENGINE()->off(_engineEventId); + } + return Super::destroy(); +} + +void EffectAsset::initDefault(const ccstd::optional &uuid) { + Super::initDefault(uuid); + const auto *effect = EffectAsset::get("builtin-unlit"); + _name = "builtin-unlit"; + _shaders = effect->_shaders; + _combinations = effect->_combinations; + _techniques = effect->_techniques; // NOTE: it will copy effect->_techniques to _techniques and _techniques will kept by SE_HOLD_RETURN_VALUE +} + +bool EffectAsset::validate() const { + return !_techniques.empty() && !_shaders.empty(); +} + +void EffectAsset::precompile() { + Root *root = Root::getInstance(); + for (index_t i = 0; i < _shaders.size(); ++i) { + auto shader = _shaders[i]; + if (i >= _combinations.size()) { + continue; + } + + auto combination = _combinations[i]; + if (combination.empty()) { + continue; + } + + // Native Program Lib can not precompile shader variant without phaseID. + // Shaders are compiled only during the compilation of PSO. A new mechanism may be needed for pre-compilation. + auto *programLib = render::getProgramLibrary(); + if (programLib == nullptr) { + ccstd::vector defines = EffectAsset::doCombine( + ccstd::vector(), combination, combination.begin()); + for (auto &define: defines) { + ProgramLib::getInstance()->getGFXShader(root->getDevice(), shader.name, define, + root->getPipeline()); + } + } + } +} + +/* +// input + +const combination = { +USE_TEXTURE: [true, false], +COLOR_MODE: [0, 1, 2, 3], +ROUGHNESS_CHANNEL: ['r', 'g', 'b'], +}; + +// output + +const defines = [ + { + USE_TEXTURE: true, + COLOR_MODE: 0, + ROUGHNESS_CHANNEL: 'r' + }, + { + USE_TEXTURE: true, + COLOR_MODE: 0, + ROUGHNESS_CHANNEL: 'g' + }, + { + USE_TEXTURE: true, + COLOR_MODE: 0, + ROUGHNESS_CHANNEL: 'b' + }, + { + USE_TEXTURE: true, + COLOR_MODE: 1, + ROUGHNESS_CHANNEL: 'r' + }, + // ... all the combinations (2x4x3 in this case) + ]; + */ +ccstd::vector EffectAsset::doCombine(const ccstd::vector &cur, const IPreCompileInfo &info, IPreCompileInfo::iterator iter) { // NOLINT(misc-no-recursion) + if (iter == info.end()) { + return cur; + } + + const IPreCompileInfoValueType &values = iter->second; + const ccstd::string &key = iter->first; + + ccstd::vector records; + if (cur.empty()) { + records = EffectAsset::generateRecords(key, values); + } else { + records = EffectAsset::insertInfoValue(cur, key, values); + } + + return EffectAsset::doCombine(records, info, ++iter); +} + +ccstd::vector EffectAsset::generateRecords(const ccstd::string &key, const IPreCompileInfoValueType &infoValue) { + ccstd::vector ret; + if (const auto *boolValues = ccstd::get_if>(&infoValue)) { + for (const bool value : *boolValues) { + MacroRecord record; + record[key] = value; + ret.emplace_back(record); + } + } else if (const auto *intValues = ccstd::get_if>(&infoValue)) { + for (const int32_t value : *intValues) { + MacroRecord record; + record[key] = value; + ret.emplace_back(record); + } + } else if (const auto *stringValues = ccstd::get_if>(&infoValue)) { + for (const ccstd::string &value : *stringValues) { + MacroRecord record; + record[key] = value; + ret.emplace_back(record); + } + } else { + CC_ABORT(); + } + + return ret; +} + +ccstd::vector EffectAsset::insertInfoValue(const ccstd::vector &records, + const ccstd::string &key, + const IPreCompileInfoValueType &infoValue) { + ccstd::vector ret; + for (const auto &record : records) { + if (const auto *boolValues = ccstd::get_if>(&infoValue)) { + for (const bool value : *boolValues) { + MacroRecord tmpRecord = record; + tmpRecord[key] = value; + ret.emplace_back(tmpRecord); + } + } else if (const auto *intValues = ccstd::get_if>(&infoValue)) { + for (const int32_t value : *intValues) { + MacroRecord tmpRecord = record; + tmpRecord[key] = value; + ret.emplace_back(tmpRecord); + } + } else if (const auto *stringValues = ccstd::get_if>(&infoValue)) { + for (const ccstd::string &value : *stringValues) { + MacroRecord tmpRecord = record; + tmpRecord[key] = value; + ret.emplace_back(tmpRecord); + } + } else { + CC_ABORT(); + } + } + + return ret; +} + +} // namespace cc diff --git a/cocos/core/assets/EffectAsset.h b/cocos/core/assets/EffectAsset.h new file mode 100644 index 0000000..cc8b420 --- /dev/null +++ b/cocos/core/assets/EffectAsset.h @@ -0,0 +1,706 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/optional.h" + +#include "base/Value.h" +#include "core/Types.h" +#include "core/assets/Asset.h" +#include "engine/BaseEngine.h" +#include "renderer/core/PassUtils.h" +#include "renderer/gfx-base/GFXDef.h" +#include "renderer/pipeline/Define.h" +namespace cc { + +// To avoid errors when generating code using SWIG. +#if !SWIGCOCOS + +// The properties in Pass are obtained from an asset file. If they are directly stored in an unordered_map, +// the order of these properties may become scrambled. To maintain their order, a vector is used +// instead. Since there is no scenario where these data objects are randomly inserted, +// only a find interface is provided. +template +class StablePropertyMap : public ccstd::vector> { // NOLINT + using Super = ccstd::vector>; + +public: + auto find(const K &key) const { + auto *self = static_cast(this); + return std::find_if(self->begin(), self->end(), [&](auto &ele) { + return ele.first == key; + }); + } +}; +#endif + +template +using UnstablePropertyContainer = ccstd::unordered_map; + +template +using StablePropertyContainer = StablePropertyMap; + +#if CC_EDITOR +template +using PropertyContainer = StablePropertyContainer; +#else +template +using PropertyContainer = UnstablePropertyContainer; +#endif + +using IPropertyHandleInfo = std::tuple; + +using IPropertyValue = ccstd::variant, ccstd::string>; + +using IPropertyEditorValueType = ccstd::variant>; +using IPropertyEditorInfo = PropertyContainer; + +struct IPropertyInfo { + int32_t type{0}; // auto-extracted from shader + ccstd::optional handleInfo; // auto-generated from 'target' + ccstd::optional samplerHash; // auto-generated from 'sampler' + ccstd::optional value; // default value + ccstd::optional linear; // whether to convert the input to linear space first before applying + IPropertyEditorInfo editor; // NOTE: used only by editor. +}; + +struct IPassInfoFull; + +struct RasterizerStateInfo { + ccstd::optional isDiscard; + ccstd::optional isFrontFaceCCW; + ccstd::optional depthBiasEnabled; + ccstd::optional isDepthClip; + ccstd::optional isMultisample; + + ccstd::optional polygonMode; + ccstd::optional shadeModel; + ccstd::optional cullMode; + + ccstd::optional depthBias; + ccstd::optional depthBiasClamp; + ccstd::optional depthBiasSlop; + ccstd::optional lineWidth; + + void fromGFXRasterizerState(const gfx::RasterizerState &rs) { + isDiscard = rs.isDiscard; + isFrontFaceCCW = rs.isFrontFaceCCW; + depthBiasEnabled = rs.depthBiasEnabled; + isDepthClip = rs.isDepthClip; + isMultisample = rs.isMultisample; + + polygonMode = rs.polygonMode; + shadeModel = rs.shadeModel; + cullMode = rs.cullMode; + + depthBias = rs.depthBias; + depthBiasClamp = rs.depthBiasClamp; + depthBiasSlop = rs.depthBiasSlop; + lineWidth = rs.lineWidth; + } + + void assignToGFXRasterizerState(gfx::RasterizerState &rs) const { + if (isDiscard.has_value()) { + rs.isDiscard = isDiscard.value(); + } + if (isFrontFaceCCW.has_value()) { + rs.isFrontFaceCCW = isFrontFaceCCW.value(); + } + if (depthBiasEnabled.has_value()) { + rs.depthBiasEnabled = depthBiasEnabled.value(); + } + if (isDepthClip.has_value()) { + rs.isDepthClip = isDepthClip.value(); + } + if (isMultisample.has_value()) { + rs.isMultisample = isMultisample.value(); + } + if (polygonMode.has_value()) { + rs.polygonMode = polygonMode.value(); + } + if (shadeModel.has_value()) { + rs.shadeModel = shadeModel.value(); + } + if (cullMode.has_value()) { + rs.cullMode = cullMode.value(); + } + if (depthBias.has_value()) { + rs.depthBias = depthBias.value(); + } + if (depthBiasClamp.has_value()) { + rs.depthBiasClamp = depthBiasClamp.value(); + } + if (depthBiasSlop.has_value()) { + rs.depthBiasSlop = depthBiasSlop.value(); + } + if (lineWidth.has_value()) { + rs.lineWidth = lineWidth.value(); + } + } +}; + +struct DepthStencilStateInfo { + ccstd::optional depthTest; + ccstd::optional depthWrite; + ccstd::optional stencilTestFront; + ccstd::optional stencilTestBack; + + ccstd::optional depthFunc; + ccstd::optional stencilFuncFront; + ccstd::optional stencilReadMaskFront; + ccstd::optional stencilWriteMaskFront; + ccstd::optional stencilFailOpFront; + ccstd::optional stencilZFailOpFront; + ccstd::optional stencilPassOpFront; + ccstd::optional stencilRefFront; + + ccstd::optional stencilFuncBack; + ccstd::optional stencilReadMaskBack; + ccstd::optional stencilWriteMaskBack; + ccstd::optional stencilFailOpBack; + ccstd::optional stencilZFailOpBack; + ccstd::optional stencilPassOpBack; + ccstd::optional stencilRefBack; + + void fromGFXDepthStencilState(const gfx::DepthStencilState &ds) { + depthTest = ds.depthTest; + depthWrite = ds.depthWrite; + stencilTestFront = ds.stencilTestFront; + stencilTestBack = ds.stencilTestBack; + + depthFunc = ds.depthFunc; + stencilFuncFront = ds.stencilFuncFront; + stencilReadMaskFront = ds.stencilReadMaskFront; + stencilWriteMaskFront = ds.stencilWriteMaskFront; + stencilFailOpFront = ds.stencilFailOpFront; + stencilZFailOpFront = ds.stencilZFailOpFront; + stencilPassOpFront = ds.stencilPassOpFront; + stencilRefFront = ds.stencilRefFront; + + stencilFuncBack = ds.stencilFuncBack; + stencilReadMaskBack = ds.stencilReadMaskBack; + stencilWriteMaskBack = ds.stencilWriteMaskBack; + stencilFailOpBack = ds.stencilFailOpBack; + stencilZFailOpBack = ds.stencilZFailOpBack; + stencilPassOpBack = ds.stencilPassOpBack; + stencilRefBack = ds.stencilRefBack; + } + + void assignToGFXDepthStencilState(gfx::DepthStencilState &ds) const { + if (depthTest.has_value()) { + ds.depthTest = depthTest.value(); + } + if (depthWrite.has_value()) { + ds.depthWrite = depthWrite.value(); + } + if (stencilTestFront.has_value()) { + ds.stencilTestFront = stencilTestFront.value(); + } + if (stencilTestBack.has_value()) { + ds.stencilTestBack = stencilTestBack.value(); + } + if (depthFunc.has_value()) { + ds.depthFunc = depthFunc.value(); + } + if (stencilFuncFront.has_value()) { + ds.stencilFuncFront = stencilFuncFront.value(); + } + if (stencilReadMaskFront.has_value()) { + ds.stencilReadMaskFront = stencilReadMaskFront.value(); + } + if (stencilWriteMaskFront.has_value()) { + ds.stencilWriteMaskFront = stencilWriteMaskFront.value(); + } + if (stencilFailOpFront.has_value()) { + ds.stencilFailOpFront = stencilFailOpFront.value(); + } + if (stencilZFailOpFront.has_value()) { + ds.stencilZFailOpFront = stencilZFailOpFront.value(); + } + if (stencilPassOpFront.has_value()) { + ds.stencilPassOpFront = stencilPassOpFront.value(); + } + if (stencilRefFront.has_value()) { + ds.stencilRefFront = stencilRefFront.value(); + } + if (stencilFuncBack.has_value()) { + ds.stencilFuncBack = stencilFuncBack.value(); + } + if (stencilReadMaskBack.has_value()) { + ds.stencilReadMaskBack = stencilReadMaskBack.value(); + } + if (stencilWriteMaskBack.has_value()) { + ds.stencilWriteMaskBack = stencilWriteMaskBack.value(); + } + if (stencilFailOpBack.has_value()) { + ds.stencilFailOpBack = stencilFailOpBack.value(); + } + if (stencilZFailOpBack.has_value()) { + ds.stencilZFailOpBack = stencilZFailOpBack.value(); + } + if (stencilPassOpBack.has_value()) { + ds.stencilPassOpBack = stencilPassOpBack.value(); + } + if (stencilRefBack.has_value()) { + ds.stencilRefBack = stencilRefBack.value(); + } + } +}; + +struct BlendTargetInfo { + ccstd::optional blend; + ccstd::optional blendSrc; + ccstd::optional blendDst; + ccstd::optional blendEq; + ccstd::optional blendSrcAlpha; + ccstd::optional blendDstAlpha; + ccstd::optional blendAlphaEq; + ccstd::optional blendColorMask; + + void fromGFXBlendTarget(const gfx::BlendTarget &target) { + blend = target.blend; + blendSrc = target.blendSrc; + blendDst = target.blendDst; + blendEq = target.blendEq; + blendSrcAlpha = target.blendSrcAlpha; + blendDstAlpha = target.blendDstAlpha; + blendAlphaEq = target.blendAlphaEq; + blendColorMask = target.blendColorMask; + } + + void assignToGFXBlendTarget(gfx::BlendTarget &target) const { + if (blend.has_value()) { + target.blend = blend.value(); + } + if (blendSrc.has_value()) { + target.blendSrc = blendSrc.value(); + } + if (blendDst.has_value()) { + target.blendDst = blendDst.value(); + } + if (blendEq.has_value()) { + target.blendEq = blendEq.value(); + } + if (blendSrcAlpha.has_value()) { + target.blendSrcAlpha = blendSrcAlpha.value(); + } + if (blendDstAlpha.has_value()) { + target.blendDstAlpha = blendDstAlpha.value(); + } + if (blendAlphaEq.has_value()) { + target.blendAlphaEq = blendAlphaEq.value(); + } + if (blendColorMask.has_value()) { + target.blendColorMask = blendColorMask.value(); + } + } +}; + +using BlendTargetInfoList = ccstd::vector; + +struct BlendStateInfo { + ccstd::optional isA2C; + ccstd::optional isIndepend; + ccstd::optional blendColor; + ccstd::optional targets; + + void fromGFXBlendState(const gfx::BlendState &bs) { + isA2C = bs.isA2C; + isIndepend = bs.isIndepend; + blendColor = bs.blendColor; + size_t len = bs.targets.size(); + if (len > 0) { + BlendTargetInfoList targetsList(len); + for (size_t i = 0; i < len; ++i) { + targetsList[i].fromGFXBlendTarget(bs.targets[i]); + } + targets = targetsList; + } + } + + void assignToGFXBlendState(gfx::BlendState &bs) const { + if (targets.has_value()) { + const auto &targetsVal = targets.value(); + bs.targets.resize(targetsVal.size()); + for (size_t i = 0, len = targetsVal.size(); i < len; ++i) { + targetsVal[i].assignToGFXBlendTarget(bs.targets[i]); + } + } + + if (isA2C.has_value()) { + bs.isA2C = isA2C.value(); + } + + if (isIndepend.has_value()) { + bs.isIndepend = isIndepend.value(); + } + + if (blendColor.has_value()) { + bs.blendColor = blendColor.value(); + } + } +}; + +// Pass instance itself are compliant to IPassStates too +struct IPassStates { + ccstd::optional priority; + ccstd::optional primitive; + ccstd::optional stage; + ccstd::optional rasterizerState; + ccstd::optional depthStencilState; + ccstd::optional blendState; + ccstd::optional dynamicStates; + ccstd::optional phase; + ccstd::optional pass; + ccstd::optional subpass; + + IPassStates() = default; + explicit IPassStates(const IPassInfoFull &o); + IPassStates &operator=(const IPassInfoFull &o); + void overrides(const IPassInfoFull &o); +}; +using PassOverrides = IPassStates; + +using PassPropertyInfoMap = PropertyContainer; + +struct IPassInfoFull final { // cjh } : public IPassInfo { + // IPassStates + ccstd::optional priority; + ccstd::optional primitive; + ccstd::optional stage; + ccstd::optional rasterizerState; + ccstd::optional depthStencilState; + ccstd::optional blendState; + ccstd::optional dynamicStates; + ccstd::optional phase; + ccstd::optional pass; + ccstd::optional subpass; + // IPassInfo + ccstd::string program; // auto-generated from 'vert' and 'frag' + ccstd::optional embeddedMacros; + ccstd::optional propertyIndex; // NOTE: needs to use ccstd::optional<> since jsb should return 'undefined' instead of '-1' to avoid wrong value checking logic. + ccstd::optional switch_; + ccstd::optional properties; + + // IPassInfoFull + // generated part + index_t passIndex{0}; + uint32_t passID = 0xFFFFFFFF; + uint32_t subpassID = 0xFFFFFFFF; + uint32_t phaseID = 0xFFFFFFFF; + MacroRecord defines; + ccstd::optional stateOverrides; + + IPassInfoFull() = default; + explicit IPassInfoFull(const IPassStates &o) { + *this = o; + } + IPassInfoFull &operator=(const IPassStates &o) { + priority = o.priority; + primitive = o.primitive; + stage = o.stage; + rasterizerState = o.rasterizerState; + depthStencilState = o.depthStencilState; + blendState = o.blendState; + dynamicStates = o.dynamicStates; + phase = o.phase; + subpass = o.subpass; + return *this; + } +}; + +using IPassInfo = IPassInfoFull; + +struct ITechniqueInfo { + ccstd::vector passes; + ccstd::optional name; +}; + +struct IBlockInfo { + uint32_t binding{UINT32_MAX}; + ccstd::string name; + ccstd::vector members; + + gfx::ShaderStageFlags stageFlags{gfx::ShaderStageFlags::NONE}; + + ccstd::vector defines; +}; + +struct ISamplerTextureInfo { + uint32_t binding{UINT32_MAX}; + ccstd::string name; + gfx::Type type{gfx::Type::UNKNOWN}; + uint32_t count{0}; + gfx::ShaderStageFlags stageFlags{gfx::ShaderStageFlags::NONE}; + ccstd::vector defines; // NOTE: used in Editor only +}; + +struct ITextureInfo { + uint32_t set{0}; + uint32_t binding{UINT32_MAX}; + ccstd::string name; + gfx::Type type{gfx::Type::UNKNOWN}; + uint32_t count{0}; + gfx::ShaderStageFlags stageFlags{gfx::ShaderStageFlags::NONE}; +}; + +struct ISamplerInfo { + uint32_t set{0}; + uint32_t binding{UINT32_MAX}; + ccstd::string name; + uint32_t count{0}; + gfx::ShaderStageFlags stageFlags{gfx::ShaderStageFlags::NONE}; +}; + +struct IBufferInfo { + uint32_t binding{UINT32_MAX}; + ccstd::string name; + gfx::MemoryAccess memoryAccess{gfx::MemoryAccess::NONE}; + gfx::ShaderStageFlags stageFlags{gfx::ShaderStageFlags::NONE}; +}; + +struct IImageInfo { + uint32_t binding{UINT32_MAX}; + ccstd::string name; + gfx::Type type{gfx::Type::UNKNOWN}; + uint32_t count{0}; + gfx::MemoryAccess memoryAccess{gfx::MemoryAccess::NONE}; + gfx::ShaderStageFlags stageFlags{gfx::ShaderStageFlags::NONE}; +}; + +struct IInputAttachmentInfo { + uint32_t set{0}; + uint32_t binding{UINT32_MAX}; + ccstd::string name; + uint32_t count{0}; + gfx::ShaderStageFlags stageFlags{gfx::ShaderStageFlags::NONE}; +}; + +struct IAttributeInfo { + ccstd::string name; + gfx::Format format{gfx::Format::UNKNOWN}; + bool isNormalized{false}; + uint32_t stream{0U}; + bool isInstanced{false}; + uint32_t location{0U}; + + ccstd::vector defines; +}; + +struct IDefineInfo { + ccstd::string name; + ccstd::string type; + ccstd::optional> range; // cjh number is float? ?: number[]; + ccstd::optional> options; + ccstd::optional defaultVal; + ccstd::optional> defines; // NOTE: it's only used in Editor + ccstd::optional>> editor; // NOTE: it's only used in Editor +}; + +struct IBuiltin { + ccstd::string name; + ccstd::vector defines; +}; + +struct IBuiltinInfo { + ccstd::vector buffers; + ccstd::vector blocks; + ccstd::vector samplerTextures; + ccstd::vector images; +}; + +using BuiltinsStatisticsType = ccstd::unordered_map; + +struct IBuiltins { + IBuiltinInfo globals; + IBuiltinInfo locals; + BuiltinsStatisticsType statistics; +}; + +struct IDescriptorInfo { + uint32_t rate{0}; + ccstd::vector blocks; + ccstd::vector samplerTextures; + ccstd::vector samplers; + ccstd::vector textures; + ccstd::vector buffers; + ccstd::vector images; + ccstd::vector subpassInputs; +}; + +struct IShaderSource { + ccstd::string vert; + ccstd::string frag; + ccstd::optional compute; +}; + +struct IShaderInfo { + ccstd::string name; + ccstd::hash_t hash{gfx::INVALID_SHADER_HASH}; + IShaderSource glsl4; + IShaderSource glsl3; + IShaderSource glsl1; + IBuiltins builtins; + ccstd::vector defines; + ccstd::vector attributes; + ccstd::vector blocks; + ccstd::vector samplerTextures; + ccstd::vector samplers; + ccstd::vector textures; + ccstd::vector buffers; + ccstd::vector images; + ccstd::vector subpassInputs; + ccstd::vector descriptors; + + const IShaderSource *getSource(const ccstd::string &version) const { + if (version == "glsl1") return &glsl1; + if (version == "glsl3") return &glsl3; + if (version == "glsl4") return &glsl4; + return nullptr; + } + IShaderSource *getSource(const ccstd::string &version) { + if (version == "glsl1") return &glsl1; + if (version == "glsl3") return &glsl3; + if (version == "glsl4") return &glsl4; + return nullptr; + } +}; + +using IPreCompileInfoValueType = ccstd::variant, ccstd::vector, ccstd::vector>; +using IPreCompileInfo = ccstd::unordered_map; + +class EffectAsset final : public Asset { +public: + using Super = Asset; + + EffectAsset() = default; + ~EffectAsset() override = default; + /** + * @en Register the effect asset to the static map + * @zh 将指定 effect 注册到全局管理器。 + */ + static void registerAsset(EffectAsset *asset); + + /** + * @en Unregister the effect asset from the static map + * @zh 将指定 effect 从全局管理器移除。 + */ + static void remove(const ccstd::string &name); + static void remove(EffectAsset *asset); + + /** + * @en Get the effect asset by the given name. + * @zh 获取指定名字的 effect 资源。 + */ + static EffectAsset *get(const ccstd::string &name); + + using RegisteredEffectAssetMap = ccstd::unordered_map>; + /** + * @en Get all registered effect assets. + * @zh 获取所有已注册的 effect 资源。 + */ + static RegisteredEffectAssetMap &getAll() { return EffectAsset::effects; } + + static bool isLayoutValid() { return layoutValid; } + static void setLayoutValid() { layoutValid = true; } + + inline void setTechniques(const ccstd::vector &val) { _techniques = val; } + inline void setShaders(const ccstd::vector &val) { _shaders = val; } + inline void setCombinations(const ccstd::vector &val) { _combinations = val; } + + inline const ccstd::vector &getTechniques() const { return _techniques; } + inline const ccstd::vector &getShaders() const { return _shaders; } + inline const ccstd::vector &getCombinations() const { return _combinations; } + + /* + @serializable + @editorOnly + */ + bool hideInEditor = false; + + /** + * @en The loaded callback which should be invoked by the [[Loader]], will automatically register the effect. + * @zh 通过 [[Loader]] 加载完成时的回调,将自动注册 effect 资源。 + */ + void onLoaded() override; + bool destroy() override; + void initDefault(const ccstd::optional &uuid) override; + bool validate() const override; + +protected: + BaseEngine::EngineStatusChange::EventID _engineEventId; + + static ccstd::vector doCombine(const ccstd::vector &cur, const IPreCompileInfo &info, IPreCompileInfo::iterator iter); + static ccstd::vector generateRecords(const ccstd::string &key, const IPreCompileInfoValueType &value); + static ccstd::vector insertInfoValue(const ccstd::vector &records, + const ccstd::string &key, + const IPreCompileInfoValueType &value); + + void precompile(); + + // We need make it to public for deserialization +public: + /** + * @en The techniques used by the current effect. + * @zh 当前 effect 的所有可用 technique。 + + @serializable + @editable*/ + ccstd::vector _techniques; + + /** + * @en The shaders used by the current effect. + * @zh 当前 effect 使用的所有 shader。 + + @serializable + @editable*/ + ccstd::vector _shaders; + + /** + * @en The preprocess macro combinations for the shader + * @zh 每个 shader 需要预编译的宏定义组合。 + + @serializable + @editable*/ + ccstd::vector _combinations; + // +protected: + static RegisteredEffectAssetMap effects; // cjh TODO: how to clear when game exits. + static bool layoutValid; + + CC_DISALLOW_COPY_MOVE_ASSIGN(EffectAsset); + + friend class EffectAssetDeserializer; + friend class Material; + friend class ProgramLib; + friend class MaterialInstance; + friend class BuiltinResMgr; +}; + +} // namespace cc diff --git a/cocos/core/assets/Font.cpp b/cocos/core/assets/Font.cpp new file mode 100644 index 0000000..4e7c46d --- /dev/null +++ b/cocos/core/assets/Font.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Font.h" +#include +#include +#include "base/Data.h" +#include "base/Log.h" +#include "base/Macros.h" +#include "base/memory/Memory.h" +#include "base/std/hash/hash.h" +#include "gfx-base/GFXTexture.h" +#include "math/Math.h" +#include "platform/FileUtils.h" +#include "profiler/Profiler.h" + +namespace cc { + +ccstd::hash_t KerningHash::operator()(const KerningPair &k) const { + ccstd::hash_t seed = 2; + ccstd::hash_combine(seed, k.prevCode); + ccstd::hash_combine(seed, k.nextCode); + return seed; +} + +/** + * FontFaceInfo + */ +FontFaceInfo::FontFaceInfo(uint32_t size) +: fontSize(size) {} + +FontFaceInfo::FontFaceInfo(uint32_t size, uint32_t width, uint32_t height) +: fontSize(size), textureWidth(width), textureHeight(height) { + CC_ASSERT(math::isPowerOfTwo(width) && math::isPowerOfTwo(height)); // Font texture size must be power of 2. + + // preload digit & alphabet characters by default + for (auto i = 0U; i < 128; i++) { + if (std::isdigit(i) || std::isalpha(i)) { + preLoadedCharacters.emplace_back(i); + } + } +} + +FontFaceInfo::FontFaceInfo(uint32_t size, uint32_t width, uint32_t height, ccstd::vector chars) +: fontSize(size), textureWidth(width), textureHeight(height), preLoadedCharacters(std::move(chars)) { + CC_ASSERT(math::isPowerOfTwo(width) && math::isPowerOfTwo(height)); // Font texture size must be power of 2. +} + +/** + * FontFace + */ +FontFace::FontFace(Font *font) +: _font(font) { +} + +FontFace::~FontFace() { + for (auto *texture : _textures) { + CC_SAFE_DESTROY_AND_DELETE(texture); + } +} + +/** + * Font + */ +Font::Font(FontType type, const ccstd::string &path) +: _type(type), _path(path) { + load(path); +} + +Font::~Font() { + releaseFaces(); + CC_PROFILE_MEMORY_DEC(Font, _data.size()); +} + +void Font::load(const ccstd::string &path) { + Data data = FileUtils::getInstance()->getDataFromFile(path); + if (data.isNull()) { + CC_LOG_WARNING("Font load failed, path: %s.", path.c_str()); + return; + } + + _data.resize(data.getSize()); + memcpy(&_data[0], data.getBytes(), data.getSize()); + CC_PROFILE_MEMORY_INC(Font, _data.size()); +} + +void Font::releaseFaces() { + for (auto &iter : _faces) { + delete iter.second; + } + + _faces.clear(); +} + +} // namespace cc diff --git a/cocos/core/assets/Font.h b/cocos/core/assets/Font.h new file mode 100644 index 0000000..c9c6762 --- /dev/null +++ b/cocos/core/assets/Font.h @@ -0,0 +1,157 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" +#include "core/assets/Asset.h" + +namespace cc { + +namespace gfx { +class Texture; +} + +class Font; + +constexpr uint32_t DEFAULT_FREETYPE_TEXTURE_SIZE = 512U; +constexpr uint32_t MIN_FONT_SIZE = 1U; +constexpr uint32_t MAX_FONT_SIZE = 128U; + +enum class FontType { + INVALID, + FREETYPE, + BITMAP, +}; + +struct FontGlyph { + int16_t x{0}; + int16_t y{0}; + uint16_t width{0U}; + uint16_t height{0U}; + int16_t bearingX{0}; + int16_t bearingY{0}; + int32_t advance{0}; + uint32_t page{0U}; // index of textures +}; + +struct KerningPair { + uint32_t prevCode{0U}; + uint32_t nextCode{0U}; + + inline bool operator==(const KerningPair &k) const noexcept { + return prevCode == k.prevCode && nextCode == k.nextCode; + } + + inline bool operator!=(const KerningPair &k) const noexcept { + return !(*this == k); + } +}; + +struct KerningHash { + ccstd::hash_t operator()(const KerningPair &k) const; +}; + +/** + * FontFaceInfo + */ +struct FontFaceInfo { + explicit FontFaceInfo(uint32_t size); + FontFaceInfo(uint32_t size, uint32_t width, uint32_t height); + FontFaceInfo(uint32_t size, uint32_t width, uint32_t height, ccstd::vector chars); + + // only used in freetype, for bitmap font, fontSize is determined by file. + uint32_t fontSize{1U}; + uint32_t textureWidth{DEFAULT_FREETYPE_TEXTURE_SIZE}; + uint32_t textureHeight{DEFAULT_FREETYPE_TEXTURE_SIZE}; + ccstd::vector preLoadedCharacters; + //~ +}; + +/** + * FontFace + */ +class FontFace { +public: + explicit FontFace(Font *font); + virtual ~FontFace(); + FontFace(const FontFace &) = delete; + FontFace(FontFace &&) = delete; + FontFace &operator=(const FontFace &) = delete; + FontFace &operator=(FontFace &&) = delete; + + virtual const FontGlyph *getGlyph(uint32_t code) = 0; + virtual float getKerning(uint32_t prevCode, uint32_t nextCode) = 0; + + inline Font *getFont() const { return _font; } + inline uint32_t getFontSize() const { return _fontSize; } + inline uint32_t getLineHeight() const { return _lineHeight; } + inline const ccstd::vector &getTextures() const { return _textures; } + inline gfx::Texture *getTexture(uint32_t page) const { return _textures[page]; } + inline uint32_t getTextureWidth() const { return _textureWidth; } + inline uint32_t getTextureHeight() const { return _textureHeight; } + +protected: + virtual void doInit(const FontFaceInfo &info) = 0; + + Font *_font{nullptr}; + uint32_t _fontSize{1U}; + uint32_t _lineHeight{0U}; + ccstd::unordered_map _glyphs; + ccstd::unordered_map _kernings; + ccstd::vector _textures; + uint32_t _textureWidth{0U}; + uint32_t _textureHeight{0U}; +}; + +/** + * Font + */ +class Font : public Asset { +public: + Font(FontType type, const ccstd::string &path); + ~Font() override; + Font(const Font &) = delete; + Font(Font &&) = delete; + Font &operator=(const Font &) = delete; + Font &operator=(Font &&) = delete; + + virtual FontFace *createFace(const FontFaceInfo &info) = 0; + + inline FontType getType() const { return _type; } + inline const ccstd::string &getPath() const { return _path; } + inline const ccstd::vector &getData() const { return _data; } + inline FontFace *getFace(uint32_t fontSize) { return _faces[fontSize]; } + void releaseFaces(); + +protected: + void load(const ccstd::string &path); + + FontType _type{FontType::INVALID}; + ccstd::string _path; + ccstd::vector _data; + ccstd::unordered_map _faces; +}; + +} // namespace cc diff --git a/cocos/core/assets/FreeTypeFont.cpp b/cocos/core/assets/FreeTypeFont.cpp new file mode 100644 index 0000000..dafeaa5 --- /dev/null +++ b/cocos/core/assets/FreeTypeFont.cpp @@ -0,0 +1,316 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + Portions of this software are copyright ? <2022> The FreeType + Project (www.freetype.org). All rights reserved. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "FreeTypeFont.h" +#include +#include FT_FREETYPE_H +#include +#include "base/Log.h" +#include "gfx-base/GFXDevice.h" + +namespace cc { + +/** + * FTLibrary + */ +struct FTLibrary { + FTLibrary() { + FT_Error error = FT_Init_FreeType(&lib); + if (error) { + CC_LOG_ERROR("FreeType init failed, error code: %d.", error); + } + } + + ~FTLibrary() { + if (lib) { + FT_Error error = FT_Done_FreeType(lib); + if (error) { + CC_LOG_ERROR("FreeType exit failed, error code: %d.", error); + } + + lib = nullptr; + } + } + + FT_Library lib{nullptr}; +}; + +/** + * FTFace + */ +struct FTFace { + explicit FTFace(FT_Face f) + : face(f) { + } + + ~FTFace() { + if (face) { + FT_Done_Face(face); + face = nullptr; + } + } + + FT_Face face{nullptr}; +}; + +/** + * GlyphAllocator: allocate space for glyph from top to down, from left to right. + */ +class GlyphAllocator { +public: + GlyphAllocator(uint32_t maxWidth, uint32_t maxHeight) + : _maxWidth(maxWidth), _maxHeight(maxHeight) {} + + inline void reset() { + _nextX = 0U; + _nextY = 0U; + _maxLineHeight = 0U; + } + + bool allocate(uint32_t width, uint32_t height, uint32_t &x, uint32_t &y) { + // try current line + if (_nextX + width <= _maxWidth && _nextY + height <= _maxHeight) { + x = _nextX; + y = _nextY; + + _nextX += width; + _maxLineHeight = std::max(_maxLineHeight, height); + + return true; + } + + // try next line + uint32_t nextY = _nextY + _maxLineHeight; + if (width <= _maxWidth && nextY + height <= _maxHeight) { + x = 0U; + y = nextY; + + _nextX = width; + _nextY = nextY; + _maxLineHeight = height; + + return true; + } + + return false; + } + +private: + // texture resolution + const uint32_t _maxWidth{0U}; + const uint32_t _maxHeight{0U}; + + // next space + uint32_t _nextX{0U}; + uint32_t _nextY{0U}; + + // max height of current line + uint32_t _maxLineHeight{0U}; +}; + +/** + * FreeTypeFontFace + */ +FTLibrary *FreeTypeFontFace::library = nullptr; +FreeTypeFontFace::FreeTypeFontFace(Font *font) +: FontFace(font) { + if (!library) { + library = ccnew FTLibrary(); + } +} + +void FreeTypeFontFace::doInit(const FontFaceInfo &info) { + const auto &fontData = _font->getData(); + if (fontData.empty()) { + CC_LOG_ERROR("FreeTypeFontFace doInit failed: empty font data."); + return; + } + + _fontSize = info.fontSize < MIN_FONT_SIZE ? MIN_FONT_SIZE : (info.fontSize > MAX_FONT_SIZE ? MAX_FONT_SIZE : info.fontSize); + _textureWidth = info.textureWidth; + _textureHeight = info.textureHeight; + _allocator = std::make_unique(_textureWidth, _textureHeight); + + FT_Face face{nullptr}; + FT_Error error = FT_New_Memory_Face(library->lib, fontData.data(), static_cast(fontData.size()), 0, &face); + if (error) { + CC_LOG_ERROR("FT_New_Memory_Face failed, error code: %d.", error); + return; + } + + error = FT_Set_Pixel_Sizes(face, 0, _fontSize); + if (error) { + CC_LOG_ERROR("FT_Set_Pixel_Sizes failed, error code: %d.", error); + return; + } + + _face = std::make_unique(face); + _lineHeight = static_cast(face->size->metrics.height >> 6); + + for (const auto &code : info.preLoadedCharacters) { + loadGlyph(code); + } +} + +const FontGlyph *FreeTypeFontFace::getGlyph(uint32_t code) { + auto iter = _glyphs.find(code); + if (iter != _glyphs.end()) { + return &iter->second; + } + + return loadGlyph(code); +} + +float FreeTypeFontFace::getKerning(uint32_t prevCode, uint32_t nextCode) { + FT_Face face = _face->face; + if (!FT_HAS_KERNING(face)) { + return 0.0F; + } + + const auto &iter = _kernings.find({prevCode, nextCode}); + if (iter != _kernings.end()) { + return iter->second; + } + + FT_Vector kerning; + FT_UInt prevIndex = FT_Get_Char_Index(face, prevCode); + FT_UInt nextIndex = FT_Get_Char_Index(face, nextCode); + FT_Error error = FT_Get_Kerning(face, prevIndex, nextIndex, FT_KERNING_DEFAULT, &kerning); + + if (error) { + CC_LOG_WARNING("FT_Get_Kerning failed, error code: %d, prevCode: %d, nextCode: %d", error, prevCode, nextCode); + return 0.0F; + } + + auto result = static_cast(kerning.x >> 6); + _kernings[{prevCode, nextCode}] = result; + + return result; +} + +const FontGlyph *FreeTypeFontFace::loadGlyph(uint32_t code) { + FT_Face face = _face->face; + FT_Error error = FT_Load_Char(face, code, FT_LOAD_RENDER); + if (error) { + CC_LOG_WARNING("FT_Load_Char failed, error code: %d, character: %u.", error, code); + return nullptr; + } + + FontGlyph glyph; + glyph.width = face->glyph->bitmap.width; + glyph.height = face->glyph->bitmap.rows; + glyph.bearingX = face->glyph->bitmap_left; + glyph.bearingY = face->glyph->bitmap_top; + glyph.advance = static_cast(face->glyph->advance.x >> 6); // advance.x's unit is 1/64 pixels + + uint32_t x = 0U; + uint32_t y = 0U; + + if (_textures.empty()) { + createTexture(_textureWidth, _textureHeight); + } + + if (glyph.width > 0U && glyph.height > 0U) { + // try current texture + if (!_allocator->allocate(glyph.width + 1, glyph.height + 1, x, y)) { + createTexture(_textureWidth, _textureHeight); + + // try new empty texture + _allocator->reset(); + if (!_allocator->allocate(glyph.width + 1, glyph.height + 1, x, y)) { + CC_LOG_WARNING("Glyph allocate failed, character: %u.", code); + return nullptr; + } + } + + auto page = static_cast(_textures.size() - 1); + updateTexture(page, x, y, glyph.width, glyph.height, face->glyph->bitmap.buffer); + + glyph.x = x; + glyph.y = y; + glyph.page = page; + } + + _glyphs[code] = glyph; + + return &_glyphs[code]; +} + +void FreeTypeFontFace::createTexture(uint32_t width, uint32_t height) { + auto *device = gfx::Device::getInstance(); + auto *texture = device->createTexture({gfx::TextureType::TEX2D, + gfx::TextureUsageBit::SAMPLED | gfx::TextureUsageBit::TRANSFER_DST, + gfx::Format::R8, + width, + height}); + + _textures.push_back(texture); + + std::vector empty(width * height, 0); + auto page = static_cast(_textures.size() - 1); + updateTexture(page, 0U, 0U, width, height, empty.data()); +} + +void FreeTypeFontFace::updateTexture(uint32_t page, uint32_t x, uint32_t y, uint32_t width, uint32_t height, const uint8_t *buffer) { + auto *texture = getTexture(page); + gfx::BufferDataList buffers{buffer}; + gfx::BufferTextureCopyList regions = {{0U, + 0U, + 0U, + {static_cast(x), static_cast(y), 0U}, + {width, height, 1U}, + {0U, 0U, 1U}}}; + + auto *device = gfx::Device::getInstance(); + device->copyBuffersToTexture(buffers, texture, regions); +} + +void FreeTypeFontFace::destroyFreeType() { + if (library) { + delete library; + library = nullptr; + } +} + +/** + * FreeTypeFont + */ +FreeTypeFont::FreeTypeFont(const ccstd::string &path) +: Font(FontType::FREETYPE, path) { +} + +FontFace *FreeTypeFont::createFace(const FontFaceInfo &info) { + auto *face = ccnew FreeTypeFontFace(this); + face->doInit(info); + + uint32_t fontSize = face->getFontSize(); + _faces[fontSize] = face; + + return face; +} + +} // namespace cc diff --git a/cocos/core/assets/FreeTypeFont.h b/cocos/core/assets/FreeTypeFont.h new file mode 100644 index 0000000..fb6c4ba --- /dev/null +++ b/cocos/core/assets/FreeTypeFont.h @@ -0,0 +1,85 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + Portions of this software are copyright ? <2022> The FreeType + Project (www.freetype.org). All rights reserved. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include +#include "Font.h" +#include "base/std/container/string.h" + +namespace cc { + +struct FTLibrary; +struct FTFace; +class GlyphAllocator; + +/** + * FreeTypeFontFace + */ +class FreeTypeFontFace : public FontFace { +public: + explicit FreeTypeFontFace(Font *font); + ~FreeTypeFontFace() override = default; + FreeTypeFontFace(const FreeTypeFontFace &) = delete; + FreeTypeFontFace(FreeTypeFontFace &&) = delete; + FreeTypeFontFace &operator=(const FreeTypeFontFace &) = delete; + FreeTypeFontFace &operator=(FreeTypeFontFace &&) = delete; + + const FontGlyph *getGlyph(uint32_t code) override; + float getKerning(uint32_t prevCode, uint32_t nextCode) override; + static void destroyFreeType(); + +private: + void doInit(const FontFaceInfo &info) override; + const FontGlyph *loadGlyph(uint32_t code); + void createTexture(uint32_t width, uint32_t height); + void updateTexture(uint32_t page, uint32_t x, uint32_t y, uint32_t width, uint32_t height, const uint8_t *buffer); + + std::unique_ptr _allocator{nullptr}; + std::unique_ptr _face; + static FTLibrary *library; + + friend class FreeTypeFont; +}; + +/** + * FreeTypeFont + */ +class FreeTypeFont : public Font { +public: + explicit FreeTypeFont(const ccstd::string &path); + ~FreeTypeFont() override = default; + FreeTypeFont(const FreeTypeFont &) = delete; + FreeTypeFont(FreeTypeFont &&) = delete; + FreeTypeFont &operator=(const FreeTypeFont &) = delete; + FreeTypeFont &operator=(FreeTypeFont &&) = delete; + + FontFace *createFace(const FontFaceInfo &info) override; + +private: +}; + +} // namespace cc diff --git a/cocos/core/assets/ImageAsset.cpp b/cocos/core/assets/ImageAsset.cpp new file mode 100644 index 0000000..db77619 --- /dev/null +++ b/cocos/core/assets/ImageAsset.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/ImageAsset.h" + +#include "platform/Image.h" + +#include "base/Log.h" + +namespace cc { + +ImageAsset::~ImageAsset() { + if (_needFreeData && _data) { + free(_data); + } +} + +void ImageAsset::setNativeAsset(const ccstd::any &obj) { + if (obj.has_value()) { + auto **pImage = const_cast(ccstd::any_cast(&obj)); + if (pImage != nullptr) { + Image *image = *pImage; + image->takeData(&_data); + _needFreeData = true; + + _width = image->getWidth(); + _height = image->getHeight(); + _format = static_cast(image->getRenderFormat()); + _url = image->getFilePath(); + _mipmapLevelDataSize = image->getMipmapLevelDataSize(); + } else { + const auto *imageSource = ccstd::any_cast(&obj); + if (imageSource != nullptr) { + _arrayBuffer = imageSource->data; + _data = const_cast(_arrayBuffer->getData()); + _width = imageSource->width; + _height = imageSource->height; + _format = imageSource->format; + _mipmapLevelDataSize = imageSource->mipmapLevelDataSize; + } else { + CC_LOG_WARNING("ImageAsset::setNativeAsset, unknown type!"); + } + } + } +} + +IntrusivePtr ImageAsset::extractMipmap0() { + auto *res = new ImageAsset; + + res->_data = nullptr; + res->_needFreeData = false; + res->_width = _width; + res->_height = _height; + res->_format = _format; + res->_uuid = _uuid; + res->_data = _data; + + return res; +} + +std::vector> ImageAsset::extractMipmaps() { + std::vector> res{}; + + if (!_mipmapLevelDataSize.empty()) { + size_t offset = 0UL; + auto height = _height; + auto width = _width; + for (auto mipmapSize : _mipmapLevelDataSize) { + auto *mipmap = new ImageAsset; + mipmap->_data = _data + offset; + mipmap->_needFreeData = false; + mipmap->_width = width; + mipmap->_height = height; + mipmap->_format = _format; + mipmap->_uuid = _uuid; + + offset += mipmapSize; + width = std::max(width >> 1, 1U); + height = std::max(height >> 1, 1U); + res.emplace_back(mipmap); + } + } else { + res.emplace_back(this); + } + return res; +} + +const uint8_t *ImageAsset::getData() const { + return _data; +} + +uint32_t ImageAsset::getWidth() const { + return _width; +} + +uint32_t ImageAsset::getHeight() const { + return _height; +} + +PixelFormat ImageAsset::getFormat() const { + return _format; +} + +const std::vector &ImageAsset::getMipmapLevelDataSize() const { + return _mipmapLevelDataSize; +} + +bool ImageAsset::isCompressed() const { + return (_format >= PixelFormat::RGB_ETC1 && _format <= PixelFormat::RGBA_ASTC_12X12) || (_format >= PixelFormat::RGB_A_PVRTC_2BPPV1 && _format <= PixelFormat::RGBA_ETC1); +} + +const ccstd::string &ImageAsset::getUrl() const { + return _url; +} + +} // namespace cc diff --git a/cocos/core/assets/ImageAsset.h b/cocos/core/assets/ImageAsset.h new file mode 100644 index 0000000..b1f0527 --- /dev/null +++ b/cocos/core/assets/ImageAsset.h @@ -0,0 +1,138 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/assets/Asset.h" +#include "core/assets/AssetEnum.h" + +#include "core/ArrayBuffer.h" + +namespace cc { + +class Image; + +/** + * @en Image source in memory + * @zh 内存图像源。 + */ +struct IMemoryImageSource { + ArrayBuffer::Ptr data; + bool compressed{false}; + uint32_t width{0}; + uint32_t height{0}; + PixelFormat format{PixelFormat::RGBA8888}; + ccstd::vector mipmapLevelDataSize; +}; + +/** + * @en Image Asset. + * @zh 图像资源。 + */ +class ImageAsset final : public Asset { +public: + using Super = Asset; + + ImageAsset() = default; + ~ImageAsset() override; + + //minggo: do not need it in c++. + // ccstd::any getNativeAsset() const override { return ccstd::any(_nativeData); } + void setNativeAsset(const ccstd::any &obj) override; + + /** + * @en Image data. + * @zh 此图像资源的图像数据。 + */ + const uint8_t *getData() const; + + /** + * @en The pixel width of the image. + * @zh 此图像资源的像素宽度。 + */ + uint32_t getWidth() const; + + /** + * @en The pixel height of the image. + * @zh 此图像资源的像素高度。 + */ + uint32_t getHeight() const; + + /** + * @en The pixel format of the image. + * @zh 此图像资源的像素格式。 + */ + PixelFormat getFormat() const; + + /** + * @en The pixel mipmap level data size of the image. + * @zh 此图像资源的mipmap层级大小。 + */ + const ccstd::vector &getMipmapLevelDataSize() const; + + /** + * @en Whether the image is in compressed texture format. + * @zh 此图像资源是否为压缩像素格式。 + */ + bool isCompressed() const; + + /** + * @en The original source image URL, it could be empty. + * @zh 此图像资源的原始图像源的 URL。当原始图像元不是 HTML 文件时可能为空。 + * @deprecated Please use [[nativeUrl]] + */ + const ccstd::string &getUrl() const; + + // Functions for TS. + inline void setWidth(uint32_t width) { _width = width; } + inline void setHeight(uint32_t height) { _height = height; } + inline void setFormat(PixelFormat format) { _format = format; } + inline void setData(uint8_t *data) { _data = data; } + inline void setNeedFreeData(bool v) { _needFreeData = v; } + inline void setUrl(const ccstd::string &url) { _url = url; } + inline void setMipmapLevelDataSize(const ccstd::vector &mipmapLevelDataSize) { _mipmapLevelDataSize = mipmapLevelDataSize; } + + // Functions for Utils. + IntrusivePtr extractMipmap0(); + std::vector> extractMipmaps(); + +private: + uint8_t *_data{nullptr}; + + PixelFormat _format{PixelFormat::RGBA8888}; + uint32_t _width{0}; + uint32_t _height{0}; + + bool _needFreeData{false}; // Should free data if the data is assigned in C++. + + ArrayBuffer::Ptr _arrayBuffer; //minggo: hold the data from ImageSource. + + ccstd::string _url; + + ccstd::vector _mipmapLevelDataSize; + + CC_DISALLOW_COPY_MOVE_ASSIGN(ImageAsset); +}; + +} // namespace cc diff --git a/cocos/core/assets/Material.cpp b/cocos/core/assets/Material.cpp new file mode 100644 index 0000000..d757081 --- /dev/null +++ b/cocos/core/assets/Material.cpp @@ -0,0 +1,447 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/Material.h" + +#include "base/Utils.h" +#include "core/Root.h" +#include "core/assets/EffectAsset.h" +#include "core/builtin/BuiltinResMgr.h" +#include "core/platform/Debug.h" +#include "math/Color.h" +#include "renderer/pipeline/helper/Utils.h" +#include "scene/Pass.h" +namespace cc { + +/* static */ +ccstd::hash_t Material::getHashForMaterial(Material *material) { + if (material == nullptr) { + return 0; + } + + ccstd::hash_t hash = 0U; + const auto &passes = *material->_passes; + for (const auto &pass : passes) { + hash ^= pass->getHash(); + } + return hash; +} + +Material::Material() { + _passes = std::make_shared>>(); +} + +Material::~Material() = default; + +void Material::initialize(const IMaterialInfo &info) { + auto &passes = *_passes; + if (!passes.empty()) { + debug::warnID(12005); + return; + } + + if (!_defines.empty()) { + _defines.clear(); + } + if (!_states.empty()) { + _states.clear(); + } + if (!_props.empty()) { + _props.clear(); + } + + fillInfo(info); + update(); +} + +void Material::reset(const IMaterialInfo &info) { // to be consistent with other assets + initialize(info); +} + +bool Material::destroy() { + doDestroy(); + return Super::destroy(); +} + +void Material::doDestroy() { + auto &passes = *_passes; + if (!passes.empty()) { + for (const auto &pass : passes) { + pass->destroy(); + } + } + passes.clear(); + emit(); +} + +void Material::recompileShaders(const MacroRecord & /*overrides*/, index_t /*passIdx*/) { + CC_ABORT(); + CC_LOG_WARNING("Shaders in material asset '%s' cannot be modified at runtime, please instantiate the material first.", _name.c_str()); +} + +void Material::overridePipelineStates(const PassOverrides & /*overrides*/, index_t /*passIdx*/) { + CC_ABORT(); + CC_LOG_WARNING("Pipeline states in material asset '%s' cannot be modified at runtime, please instantiate the material first.", _name.c_str()); +} + +void Material::onLoaded() { + update(); +} + +void Material::resetUniforms(bool clearPasses /* = true */) { + const auto &passes = *_passes; + _props.resize(passes.size()); + + if (!clearPasses) { + return; + } + + for (const auto &pass : passes) { + pass->resetUBOs(); + pass->resetTextures(); + } +} + +void Material::setProperty(const ccstd::string &name, const MaterialPropertyVariant &val, index_t passIdx /* = CC_INVALID_INDEX */) { + const auto &passes = *_passes; + bool success = false; + if (passIdx == CC_INVALID_INDEX) { // try set property for all applicable passes + size_t len = passes.size(); + for (size_t i = 0; i < len; i++) { + const auto &pass = passes[i]; + if (uploadProperty(pass, name, val)) { + _props[pass->getPropertyIndex()][name] = val; + success = true; + } + } + } else { + if (passIdx >= passes.size()) { + CC_LOG_WARNING("illegal pass index: %d.", passIdx); + return; + } + + const auto &pass = passes[passIdx]; + if (uploadProperty(pass, name, val)) { + _props[pass->getPropertyIndex()][name] = val; + success = true; + } + } + + if (!success) { + CC_LOG_WARNING("illegal property name: %s.", name.c_str()); + } +} + +void Material::setPropertyNull(const ccstd::string &name, index_t passIdx) { + MaterialPropertyVariant val; + setProperty(name, val, passIdx); +} + +#define CC_MATERIAL_SETPROPERTY_IMPL(funcNameSuffix, type) \ + void Material::setProperty##funcNameSuffix(const ccstd::string &name, type val, index_t passIdx /* = CC_INVALID_INDEX*/) { \ + setProperty(name, val, passIdx); \ + } + +CC_MATERIAL_SETPROPERTY_IMPL(Float32, float) +CC_MATERIAL_SETPROPERTY_IMPL(Int32, int32_t) +CC_MATERIAL_SETPROPERTY_IMPL(Vec2, const Vec2 &) +CC_MATERIAL_SETPROPERTY_IMPL(Vec3, const Vec3 &) +CC_MATERIAL_SETPROPERTY_IMPL(Vec4, const Vec4 &) +CC_MATERIAL_SETPROPERTY_IMPL(Color, const cc::Color &) +CC_MATERIAL_SETPROPERTY_IMPL(Mat3, const Mat3 &) +CC_MATERIAL_SETPROPERTY_IMPL(Mat4, const Mat4 &) +CC_MATERIAL_SETPROPERTY_IMPL(Quaternion, const Quaternion &) +CC_MATERIAL_SETPROPERTY_IMPL(TextureBase, TextureBase *) +CC_MATERIAL_SETPROPERTY_IMPL(GFXTexture, gfx::Texture *) + +#undef CC_MATERIAL_SETPROPERTY_IMPL + +#define CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(funcNameSuffix, type) \ + void Material::setProperty##funcNameSuffix##Array(const ccstd::string &name, const ccstd::vector &val, index_t /*passIdx = CC_INVALID_INDEX*/) { \ + MaterialPropertyList propertyArr; \ + propertyArr.reserve(val.size()); \ + for (const auto &e : val) { \ + propertyArr.emplace_back(e); \ + } \ + setProperty(name, propertyArr); \ + } + +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(Float32, float) +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(Int32, int32_t) +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(Vec2, Vec2) +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(Vec3, Vec3) +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(Vec4, Vec4) +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(Color, Color) +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(Mat3, Mat3) +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(Mat4, Mat4) +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(Quaternion, Quaternion) +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(TextureBase, TextureBase *) +CC_MATERIAL_SETPROPERTY_ARRAY_IMPL(GFXTexture, gfx::Texture *) + +#undef CC_MATERIAL_SETPROPERTY_ARRAY_IMPL + +const MaterialPropertyVariant *Material::getProperty(const ccstd::string &name, index_t passIdx) const { + if (passIdx == CC_INVALID_INDEX) { // try get property in all possible passes + const auto &propsArray = _props; + size_t len = propsArray.size(); + for (size_t i = 0; i < len; i++) { + const auto &props = propsArray[i]; + auto iter = props.find(name); + if (iter != props.end()) { + return &iter->second; + } + } + } else { + if (passIdx >= _props.size()) { + CC_LOG_WARNING("illegal pass index: %d.", passIdx); + return nullptr; + } + + const auto &passes = *_passes; + const auto &props = _props[passes[passIdx]->getPropertyIndex()]; + auto iter = props.find(name); + if (iter != props.end()) { + return &iter->second; + } + } + return nullptr; +} + +void Material::fillInfo(const IMaterialInfo &info) { + if (info.technique != ccstd::nullopt) { + _techIdx = info.technique.value(); + } + + if (info.effectAsset != nullptr) { + _effectAsset = info.effectAsset; + } else if (info.effectName != ccstd::nullopt) { + _effectAsset = EffectAsset::get(info.effectName.value()); + } + + if (info.defines != ccstd::nullopt) { + prepareInfo(info.defines.value(), _defines); + } + if (info.states != ccstd::nullopt) { + prepareInfo(info.states.value(), _states); + } +} + +void Material::copy(const Material *mat, IMaterialInfo *overrides) { + if (mat == nullptr) { + return; + } + + _techIdx = mat->_techIdx; + _props.resize(mat->_props.size()); + for (size_t i = 0, len = mat->_props.size(); i < len; ++i) { + _props[i] = mat->_props[i]; + } + _defines.resize(mat->_defines.size()); + for (size_t i = 0, len = mat->_defines.size(); i < len; ++i) { + _defines[i] = mat->_defines[i]; + } + _states.resize(mat->_states.size()); + for (size_t i = 0, len = mat->_states.size(); i < len; ++i) { + _states[i] = mat->_states[i]; + } + _effectAsset = mat->_effectAsset; + if (overrides) { + fillInfo(*overrides); + } + update(); +} + +void Material::update(bool keepProps /* = true*/) { + if (_effectAsset) { + *_passes = createPasses(); + CC_ASSERT(!_effectAsset->_techniques.empty()); + // handle property values + size_t totalPasses = _effectAsset->_techniques[_techIdx].passes.size(); + _props.resize(totalPasses); + if (keepProps) { + auto cb = [this](auto *pass, size_t i) { + if (i >= _props.size()) { + _props.resize(i + 1); + } + + auto &props = _props[i]; + + if (pass->getPropertyIndex() != CC_INVALID_INDEX) { + props = _props[pass->getPropertyIndex()]; + } + + for (const auto &prop : props) { + uploadProperty(pass, prop.first, prop.second); + } + }; + + const auto &passes = *_passes; + for (size_t i = 0, len = passes.size(); i < len; ++i) { + cb(passes[i].get(), i); + } + } + + emit(); + } + _hash = Material::getHashForMaterial(this); +} + +ccstd::vector> Material::createPasses() { + ccstd::vector> passes; + ITechniqueInfo *tech = nullptr; + if (_techIdx < _effectAsset->_techniques.size()) { + tech = &_effectAsset->_techniques[_techIdx]; + } + + if (tech == nullptr) { + return passes; + } + + size_t passNum = tech->passes.size(); + for (size_t k = 0; k < passNum; ++k) { + auto &passInfo = tech->passes[k]; + index_t propIdx = passInfo.passIndex = static_cast(k); + + if (propIdx >= _defines.size()) { + _defines.resize(propIdx + 1); + } + passInfo.defines = _defines[propIdx]; + auto &defines = passInfo.defines; + + if (propIdx >= _states.size()) { + _states.resize(propIdx + 1); + } + passInfo.stateOverrides = _states[propIdx]; + + if (passInfo.propertyIndex.has_value()) { + utils::mergeToMap(defines, _defines[passInfo.propertyIndex.value()]); + } + + if (passInfo.embeddedMacros.has_value()) { + utils::mergeToMap(defines, passInfo.embeddedMacros.value()); + } + + if (passInfo.switch_.has_value() && defines.find(passInfo.switch_.value()) == defines.end()) { + continue; + } + + auto *pass = ccnew scene::Pass(Root::getInstance()); + pass->initialize(passInfo); + passes.emplace_back(pass); + } + return passes; +} + +bool Material::uploadProperty(scene::Pass *pass, const ccstd::string &name, const MaterialPropertyVariant &val) { + uint32_t handle = pass->getHandle(name); + if (!handle) { + return false; + } + + const auto type = scene::Pass::getTypeFromHandle(handle); + if (type < gfx::Type::SAMPLER1D) { + if (val.index() == MATERIAL_PROPERTY_INDEX_LIST) { + pass->setUniformArray(handle, ccstd::get(val)); + } else if (val.index() == MATERIAL_PROPERTY_INDEX_SINGLE) { + const auto &passProps = pass->getProperties(); + auto iter = passProps.find(name); + if (iter != passProps.end() && iter->second.linear.has_value()) { + CC_ASSERT(ccstd::holds_alternative(val)); + const auto &prop = ccstd::get(val); + Vec4 srgb; + if (ccstd::holds_alternative(prop)) { + srgb = ccstd::get(prop).toVec4(); + } else if (ccstd::holds_alternative(prop)) { + srgb = ccstd::get(prop); + } else { + CC_ABORT(); + } + + Vec4 linear; + pipeline::srgbToLinear(&linear, srgb); + linear.w = srgb.w; + pass->setUniform(handle, linear); + } else { + pass->setUniform(handle, ccstd::get(val)); + } + } else { + pass->resetUniform(name); + } + } else if (val.index() == MATERIAL_PROPERTY_INDEX_LIST) { + const auto &textureArray = ccstd::get(val); + for (size_t i = 0; i < textureArray.size(); i++) { + bindTexture(pass, handle, textureArray[i], static_cast(i)); + } + } else if (val.index() == MATERIAL_PROPERTY_INDEX_SINGLE) { + bindTexture(pass, handle, ccstd::get(val)); + } else { + pass->resetTexture(name); + } + return true; +} + +void Material::bindTexture(scene::Pass *pass, uint32_t handle, const MaterialProperty &val, uint32_t index) { + if (pass == nullptr) { + return; + } + + const uint32_t binding = scene::Pass::getBindingFromHandle(handle); + if (const auto *pTexture = ccstd::get_if>(&val)) { + pass->bindTexture(binding, const_cast(pTexture->get()), index); + } else if (const auto *pTextureBase = ccstd::get_if>(&val)) { + auto *textureBase = pTextureBase->get(); + gfx::Texture *texture = nullptr; + if (textureBase != nullptr) { + texture = textureBase->getGFXTexture(); + } + + if (texture == nullptr) { + CC_LOG_WARNING("Material(%p, %s)::bindTexture failed, texture is nullptr", this, _uuid.c_str()); + return; + } + + if (texture->getWidth() == 0 || texture->getHeight() == 0) { + CC_LOG_WARNING("Material(%p, %s)::bindTexture failed, texture size is 0", this, _uuid.c_str()); + return; + } + pass->bindTexture(binding, texture, index); + pass->bindSampler(binding, textureBase->getGFXSampler(), index); + } +} + +void Material::initDefault(const ccstd::optional &uuid) { + Super::initDefault(uuid); + MacroRecord defines{{"USE_COLOR", true}}; + IMaterialInfo info; + info.effectName = ccstd::string{"builtin-unlit"}; + info.defines = IMaterialInfo::DefinesType{defines}; + initialize(info); + setProperty("mainColor", Color{0xFF, 0x00, 0xFF, 0xFF}); +} + +bool Material::validate() const { + return _effectAsset != nullptr && !_effectAsset->isDefault() && !_passes->empty(); +} + +} // namespace cc diff --git a/cocos/core/assets/Material.h b/cocos/core/assets/Material.h new file mode 100644 index 0000000..bdcc294 --- /dev/null +++ b/cocos/core/assets/Material.h @@ -0,0 +1,361 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Ptr.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/optional.h" +#include "base/std/variant.h" +#include "core/assets/EffectAsset.h" +#include "core/event/Event.h" + +namespace cc { + +// class RenderableComponent; + +namespace scene { +class Pass; +} + +/** + * @en The basic infos for material initialization. + * @zh 用来初始化材质的基本信息。 + */ +struct IMaterialInfo { + /** + * @en The EffectAsset to use. Must provide if `effectName` is not specified. + * @zh + * 这个材质将使用的 EffectAsset,直接提供资源引用,和 `effectName` 至少要指定一个。 + */ + EffectAsset *effectAsset{nullptr}; + /** + * @en + * The name of the EffectAsset to use. Must provide if `effectAsset` is not specified. + * @zh + * 这个材质将使用的 EffectAsset,通过 effect 名指定,和 `effectAsset` 至少要指定一个。 + */ + ccstd::optional effectName; + /** + * @en + * The index of the technique to use. + * @zh + * 这个材质将使用第几个 technique,默认为 0。 + */ + ccstd::optional technique; + + using DefinesType = ccstd::variant>; + /** + * @en + * The shader macro definitions. Default to 0 or the specified value in [[EffectAsset]]. + * @zh + * 这个材质定义的预处理宏,默认全为 0,或 [[EffectAsset]] 中的指定值。 + */ + ccstd::optional defines; + + using PassOverridesType = ccstd::variant>; + /** + * @en + * The override values on top of the pipeline states specified in [[EffectAsset]]. + * @zh + * 这个材质的自定义管线状态,将覆盖 effect 中的属性。
+ * 注意在可能的情况下请尽量少的自定义管线状态,以减小对渲染效率的影响。 + */ + ccstd::optional states; +}; + +class Material : public Asset { + IMPL_EVENT_TARGET(Material) + DECLARE_TARGET_EVENT_BEGIN(Material) + TARGET_EVENT_ARG0(PassesUpdated) + DECLARE_TARGET_EVENT_END() +public: + using Super = Asset; + /** + * @en Get hash for a material + * @zh 获取一个材质的哈希值 + * @param material + */ + static ccstd::hash_t getHashForMaterial(Material *material); + + Material(); + ~Material() override; + + /** + * @en Initialize this material with the given information. + * @zh 根据所给信息初始化这个材质,初始化正常结束后材质即可立即用于渲染。 + * @param info Material description info. + */ + void initialize(const IMaterialInfo &info); + void reset(const IMaterialInfo &info); + + void initDefault(const ccstd::optional &uuid) override; + bool validate() const override; + + /** + * @en + * Destroy the material definitively.
+ * Cannot re-initialize after destroy.
+ * Modifications on active materials can be acheived by
+ * creating a new Material, invoke the `copy` function
+ * with the desired overrides, and assigning it to the target components. + * @zh + * 彻底销毁材质,注意销毁后无法重新初始化。
+ * 如需修改现有材质,请创建一个新材质,
+ * 调用 copy 函数传入需要的 overrides 并赋给目标组件。 + */ + bool destroy() override; + + /** + * @en Recompile the shader with the specified macro overrides. Allowed only on material instances. + * @zh 使用指定预处理宏重新编译当前 pass(数组)中的 shader。只允许对材质实例执行。 + * @param overrides The shader macro override values. + * @param passIdx The pass to apply to. Will apply to all passes if not specified. + */ + virtual void recompileShaders(const MacroRecord &overrides) { + Material::recompileShaders(overrides, CC_INVALID_INDEX); + } + virtual void recompileShaders(const MacroRecord &overrides, index_t passIdx); + + /** + * @en Override the passes with the specified pipeline states. Allowed only on material instances. + * @zh 使用指定管线状态重载当前的 pass(数组)。只允许对材质实例执行。 + * @param overrides The pipeline state override values. + * @param passIdx The pass to apply to. Will apply to all passes if not specified. + */ + virtual void overridePipelineStates(const PassOverrides &overrides) { + Material::overridePipelineStates(overrides, CC_INVALID_INDEX); + } + virtual void overridePipelineStates(const PassOverrides &overrides, index_t passIdx); + + /** + * @en Callback function after material is loaded in [[Loader]]. Initialize the resources automatically. + * @zh 通过 [[Loader]] 加载完成时的回调,将自动初始化材质资源。 + */ + void onLoaded() override; + + /** + * @en Reset all the uniforms to the default value specified in [[EffectAsset]]. + * @zh 重置材质的所有 uniform 参数数据为 [[EffectAsset]] 中的默认初始值。 + * @param clearPasses Will the rendering data be cleared too? + */ + void resetUniforms(bool clearPasses = true); + + /** + * @en + * Convenient property setter provided for quick material setup.
+ * [[Pass.setUniform]] should be used instead if you need to do per-frame uniform update. + * @zh + * 设置材质 uniform 参数的统一入口。
+ * 注意如果需要每帧更新 uniform,建议使用 [[Pass.setUniform]] 以获得更好的性能。 + * @param name The target uniform name. + * @param val The target value. + * @param passIdx The pass to apply to. Will apply to all passes if not specified. + */ + void setProperty(const ccstd::string &name, const MaterialPropertyVariant &val, index_t passIdx = CC_INVALID_INDEX); + + void setPropertyNull(const ccstd::string &name, index_t passIdx = CC_INVALID_INDEX); + void setPropertyFloat32(const ccstd::string &name, float val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyInt32(const ccstd::string &name, int32_t val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyVec2(const ccstd::string &name, const Vec2 &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyVec3(const ccstd::string &name, const Vec3 &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyVec4(const ccstd::string &name, const Vec4 &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyColor(const ccstd::string &name, const Color &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyMat3(const ccstd::string &name, const Mat3 &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyMat4(const ccstd::string &name, const Mat4 &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyQuaternion(const ccstd::string &name, const Quaternion &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyTextureBase(const ccstd::string &name, TextureBase *val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyGFXTexture(const ccstd::string &name, gfx::Texture *val, index_t passIdx = CC_INVALID_INDEX); + + void setPropertyFloat32Array(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyInt32Array(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyVec2Array(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyVec3Array(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyVec4Array(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyColorArray(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyMat3Array(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyMat4Array(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyQuaternionArray(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyTextureBaseArray(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + void setPropertyGFXTextureArray(const ccstd::string &name, const ccstd::vector &val, index_t passIdx = CC_INVALID_INDEX); + + /** + * @en + * Get the specified uniform value for this material.
+ * Note that only uniforms set through [[Material.setProperty]] can be acquired here.
+ * For the complete rendering data, use [[Pass.getUniform]] instead. + * @zh + * 获取当前材质的指定 uniform 参数的值。
+ * 注意只有通过 [[Material.setProperty]] 函数设置的参数才能从此函数取出,
+ * 如需取出完整的渲染数据,请使用 [[Pass.getUniform]]。 + * @param name The property or uniform name. + * @param passIdx The target pass index. If not specified, return the first found value in all passes. + */ + const MaterialPropertyVariant *getProperty(const ccstd::string &name, index_t passIdx = CC_INVALID_INDEX) const; + + /** + * @en Copy the target material, with optional overrides. + * @zh 复制目标材质到当前实例,允许提供重载信息。。 + * @param mat The material to be copied. + * @param overrides The overriding states on top of the original material. + */ + void copy(const Material *mat, IMaterialInfo *overrides = nullptr); + + void fillInfo(const IMaterialInfo &info); + + // For deserialization, we need to make the following properties public + /* @type(EffectAsset) */ + IntrusivePtr _effectAsset; + + /* @serializable */ + uint32_t _techIdx{0}; + + /* @serializable */ + ccstd::vector _defines; + + /* @serializable */ + ccstd::vector _states; + + /* @serializable */ + ccstd::vector> _props; + // + +protected: + std::shared_ptr>> _passes; + + ccstd::hash_t _hash{0U}; + +public: + /** + * @en Set current [[EffectAsset]]. + * @zh 设置使用的 [[EffectAsset]] 资源。 + */ + inline void setEffectAsset(EffectAsset *val) { + _effectAsset = val; + } + + /** + * @en The current [[EffectAsset]]. + * @zh 当前使用的 [[EffectAsset]] 资源。 + */ + inline EffectAsset *getEffectAsset() const { + return _effectAsset.get(); + } + + /** + * @en Name of the current [[EffectAsset]]. + * @zh 当前使用的 [[EffectAsset]] 资源名。 + */ + inline ccstd::string getEffectName() const { + return _effectAsset ? _effectAsset->getName() : ""; + } + + /** + * @en The current technique index. + * @zh 当前的 technique 索引。 + */ + inline uint32_t getTechniqueIndex() const { + return _techIdx; + } + + /** + * @en The passes defined in this material. + * @zh 当前正在使用的 pass 数组。 + */ + std::shared_ptr>> &getPasses() { + return _passes; + } + + /** + * @en The hash value of this material. + * @zh 材质的 hash。 + */ + inline ccstd::hash_t getHash() const { + return _hash; + } + + /** + * @en The parent material + * @zh 父材质 + */ + virtual Material *getParent() const { + return nullptr; + } + + /** + * @en The owner render component + * @zh 该材质所归属的渲染组件 + */ + // virtual RenderableComponent *getOwner() const { + // return nullptr; + // } + +protected: + void update(bool keepProps = true); + bool uploadProperty(scene::Pass *pass, const ccstd::string &name, const MaterialPropertyVariant &val); + void bindTexture(scene::Pass *pass, uint32_t handle, const MaterialProperty &val, uint32_t index = 0); + + template + void prepareInfo(const T1 &patch, ccstd::vector &cur) { + auto *pOneElement = ccstd::get_if(&patch); + if (pOneElement != nullptr) { + size_t len = _effectAsset != nullptr ? _effectAsset->_techniques[_techIdx].passes.size() : 1; + + ccstd::vector patchArray; + patchArray.reserve(len); + for (size_t i = 0; i < len; ++i) { + patchArray.emplace_back(*pOneElement); + } + + cur.resize(patchArray.size()); + + for (size_t i = 0; i < len; ++i) { + cur[i] = patchArray[i]; + } + } else { + auto *pPatchArray = ccstd::get_if>(&patch); + if (pPatchArray != nullptr) { + const auto &patchArray = *pPatchArray; + size_t len = patchArray.size(); + cur.resize(len); + + for (size_t i = 0; i < len; ++i) { + cur[i] = patchArray[i]; + } + } + } + } + + virtual void doDestroy(); + + virtual ccstd::vector> createPasses(); + +private: + friend class MaterialDeserializer; + + CC_DISALLOW_COPY_MOVE_ASSIGN(Material); +}; + +} // namespace cc diff --git a/cocos/core/assets/RenderTexture.cpp b/cocos/core/assets/RenderTexture.cpp new file mode 100644 index 0000000..564e94c --- /dev/null +++ b/cocos/core/assets/RenderTexture.cpp @@ -0,0 +1,175 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/RenderTexture.h" +#include "core/Root.h" +#include "core/platform/Debug.h" +#include "core/utils/IDGenerator.h" +#include "renderer/gfx-base/GFXDef-common.h" +#include "scene/RenderWindow.h" + +namespace cc { + +namespace { +gfx::RenderPassInfo getDefaultRenderPassInfo(gfx::Device *device) { + gfx::RenderPassInfo info; + info.colorAttachments.push_back({ + gfx::Format::RGBA8, + gfx::SampleCount::X1, + gfx::LoadOp::CLEAR, + gfx::StoreOp::STORE, + device->getGeneralBarrier({ + gfx::AccessFlagBit::FRAGMENT_SHADER_READ_TEXTURE, + gfx::AccessFlagBit::FRAGMENT_SHADER_READ_TEXTURE, + }), + }); + info.depthStencilAttachment.format = gfx::Format::DEPTH_STENCIL; + return info; +} +} // namespace + +RenderTexture::RenderTexture() = default; +RenderTexture::~RenderTexture() = default; + +void RenderTexture::initialize(const IRenderTextureCreateInfo &info) { + _name = info.name.has_value() ? info.name.value() : ""; + _width = info.width; + _height = info.height; + initWindow(info); +} + +void RenderTexture::reset(const IRenderTextureCreateInfo &info) { + initialize(info); +} + +bool RenderTexture::destroy() { + if (_window != nullptr) { + Root::getInstance()->destroyWindow(_window); + _window = nullptr; + } + return Super::destroy(); +} + +void RenderTexture::resize(uint32_t width, uint32_t height) { + _width = std::floor(clampf(static_cast(width), 1.F, 2048.F)); + _height = std::floor(clampf(static_cast(height), 1.F, 2048.F)); + if (_window != nullptr) { + _window->resize(_width, _height); + } + // emit(ccstd::string("resize"), _window); //TODO(xwx): not inherit form Eventify in Asset base class +} + +gfx::Texture *RenderTexture::getGFXTexture() const { + return _window ? _window->getFramebuffer()->getColorTextures()[0] : nullptr; +} + +void RenderTexture::onLoaded() { + initWindow(); +} + +void RenderTexture::initWindow() { + auto *device{Root::getInstance()->getDevice()}; + + cc::scene::IRenderWindowInfo windowInfo; + windowInfo.title = _name; + windowInfo.width = _width; + windowInfo.height = _height; + windowInfo.renderPassInfo = getDefaultRenderPassInfo(device); + + if (_window != nullptr) { + _window->destroy(); + _window->initialize(device, windowInfo); + } else { + _window = Root::getInstance()->createWindow(windowInfo); + } +} + +void RenderTexture::initWindow(const IRenderTextureCreateInfo &info) { + auto *device{Root::getInstance()->getDevice()}; + + cc::scene::IRenderWindowInfo windowInfo; + windowInfo.title = _name; + windowInfo.width = _width; + windowInfo.height = _height; + if (info.passInfo.has_value()) { + windowInfo.renderPassInfo = info.passInfo.value(); + } else { + windowInfo.renderPassInfo = getDefaultRenderPassInfo(device); + } + + if (info.externalResLow.has_value()) { + windowInfo.externalResLow = info.externalResLow.value(); + } + + if (info.externalResHigh.has_value()) { + windowInfo.externalResHigh = info.externalResHigh.value(); + } + + if (info.externalFlag.has_value()) { + windowInfo.externalFlag = info.externalFlag.value(); + } + + if (_window != nullptr) { + _window->destroy(); + _window->initialize(device, windowInfo); + } else { + _window = Root::getInstance()->createWindow(windowInfo); + } +} + +void RenderTexture::initDefault(const ccstd::optional &uuid) { + Super::initDefault(uuid); + _width = 1; + _height = 1; + initWindow(); +} + +bool RenderTexture::validate() const { + return _width >= 1 && _width <= 2048 && _height >= 1 && _height <= 2048; +} + +ccstd::vector RenderTexture::readPixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height) const { + auto *gfxTexture = getGFXTexture(); + if (!gfxTexture) { + debug::errorID(7606); + return {}; + } + + auto *gfxDevice = getGFXDevice(); + + gfx::BufferTextureCopy region0{}; + region0.texOffset.x = static_cast(x); + region0.texOffset.y = static_cast(y); + region0.texExtent.width = width; + region0.texExtent.height = height; + + ccstd::vector buffer; + buffer.resize(width * height * 4); + uint8_t *pBuffer = buffer.data(); + gfxDevice->copyTextureToBuffers(gfxTexture, &pBuffer, ®ion0, 1); + + return buffer; +} + +} // namespace cc diff --git a/cocos/core/assets/RenderTexture.h b/cocos/core/assets/RenderTexture.h new file mode 100644 index 0000000..8aa19e7 --- /dev/null +++ b/cocos/core/assets/RenderTexture.h @@ -0,0 +1,123 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/assets/TextureBase.h" +#include "renderer/gfx-base/GFXDef.h" + +namespace cc { + +struct IRenderTextureCreateInfo { + ccstd::optional name; + uint32_t width; + uint32_t height; + ccstd::optional passInfo; + ccstd::optional externalResLow; // for vulkan vkImage/opengl es texture created from external + ccstd::optional externalResHigh; // for vulkan vkImage created from external + ccstd::optional externalFlag; // external texture type normal or oes +}; +namespace scene { +class RenderWindow; +} + +namespace gfx { +class Texture; +class Sampler; +} // namespace gfx + +/** + * @en Render texture is a render target for [[Camera]] or [[Canvas]] component, + * the render pipeline will use its [[RenderWindow]] as the target of the rendering process. + * @zh 渲染贴图是 [[Camera]] 或 [[Canvas]] 组件的渲染目标对象,渲染管线会使用它的 [[RenderWindow]] 作为渲染的目标窗口。 + */ +class RenderTexture final : public TextureBase { +public: + using Super = TextureBase; + + RenderTexture(); + ~RenderTexture() override; + + /** + * @en The render window for the render pipeline, it's created internally and cannot be modified. + * @zh 渲染管线所使用的渲染窗口,内部逻辑创建,无法被修改。 + */ + inline scene::RenderWindow *getWindow() const { return _window; } + + void initialize(const IRenderTextureCreateInfo &info); + void reset(const IRenderTextureCreateInfo &info); // to be consistent with other assets + + bool destroy() override; + + /** + * @en Resize the render texture + * @zh 修改渲染贴图的尺寸 + * @param width The pixel width, the range is from 1 to 2048 + * @param height The pixel height, the range is from 1 to 2048 + */ + void resize(uint32_t width, uint32_t height); + + // TODO(minggo): migration with TextureBase data + // @ts-expect-error Hack + // get _serialize () { return null; } + // @ts-expect-error Hack + // get _deserialize () { return null; } + + // To be compatible with material property interface + /** + * @en Gets the related [[Texture]] resource, it's also the color attachment for the render window + * @zh 获取渲染贴图的 GFX 资源,同时也是渲染窗口所指向的颜色缓冲贴图资源 + */ + gfx::Texture *getGFXTexture() const override; + + void onLoaded() override; + + void initWindow(); + void initWindow(const IRenderTextureCreateInfo &info); + + void initDefault(const ccstd::optional &uuid) override; + + bool validate() const override; + + /** + * @en Read pixel buffer from render texture + * @param x The location on x axis + * @param y The location on y axis + * @param width The pixel width + * @param height The pixel height + * @zh 从 render texture 读取像素数据 + * @param x 起始位置X轴坐标 + * @param y 起始位置Y轴坐标 + * @param width 像素宽度 + * @param height 像素高度 + */ + ccstd::vector readPixels(uint32_t x, uint32_t y, uint32_t width, uint32_t height) const; + +private: + scene::RenderWindow *_window{nullptr}; + + CC_DISALLOW_COPY_MOVE_ASSIGN(RenderTexture); +}; + +} // namespace cc diff --git a/cocos/core/assets/RenderingSubMesh.cpp b/cocos/core/assets/RenderingSubMesh.cpp new file mode 100644 index 0000000..f8e09cb --- /dev/null +++ b/cocos/core/assets/RenderingSubMesh.cpp @@ -0,0 +1,380 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/RenderingSubMesh.h" +#include +#include "3d/assets/Mesh.h" +#include "3d/misc/Buffer.h" +#include "core/DataView.h" +#include "core/TypedArray.h" +#include "math/Utils.h" +#include "math/Vec3.h" +#include "renderer/gfx-base/GFXBuffer.h" +#include "renderer/gfx-base/GFXDevice.h" + +namespace cc { + +RenderingSubMesh::RenderingSubMesh(const gfx::BufferList &vertexBuffers, + const gfx::AttributeList &attributes, + gfx::PrimitiveMode primitiveMode) +: RenderingSubMesh(vertexBuffers, attributes, primitiveMode, nullptr, nullptr, true) { +} + +RenderingSubMesh::RenderingSubMesh(const gfx::BufferList &vertexBuffers, + const gfx::AttributeList &attributes, + gfx::PrimitiveMode primitiveMode, + gfx::Buffer *indexBuffer) +: RenderingSubMesh(vertexBuffers, attributes, primitiveMode, indexBuffer, nullptr, true) { +} + +RenderingSubMesh::RenderingSubMesh(const gfx::BufferList &vertexBuffers, + const gfx::AttributeList &attributes, + gfx::PrimitiveMode primitiveMode, + gfx::Buffer *indexBuffer, + gfx::Buffer *indirectBuffer) +: RenderingSubMesh(vertexBuffers, attributes, primitiveMode, indexBuffer, indirectBuffer, true) { +} + +RenderingSubMesh::RenderingSubMesh(const gfx::BufferList &vertexBuffers, + const gfx::AttributeList &attributes, + gfx::PrimitiveMode primitiveMode, + gfx::Buffer *indexBuffer, + gfx::Buffer *indirectBuffer, + bool isOwnerOfIndexBuffer) +: _vertexBuffers(vertexBuffers), + _attributes(attributes), + _primitiveMode(primitiveMode), + _indexBuffer(indexBuffer), + _indirectBuffer(indirectBuffer), + _isOwnerOfIndexBuffer(isOwnerOfIndexBuffer) { + _iaInfo.attributes = attributes; + _iaInfo.vertexBuffers = vertexBuffers; + _iaInfo.indexBuffer = indexBuffer; + _iaInfo.indirectBuffer = indirectBuffer; +} + +RenderingSubMesh::~RenderingSubMesh() { + destroy(); +} + +const IGeometricInfo &RenderingSubMesh::getGeometricInfo() { + if (_geometricInfo.has_value()) { + return _geometricInfo.value(); + } + // NOLINTNEXTLINE + static const IGeometricInfo EMPTY_GEOMETRIC_INFO; + if (_mesh == nullptr) { + return EMPTY_GEOMETRIC_INFO; + } + + if (!_subMeshIdx.has_value()) { + return EMPTY_GEOMETRIC_INFO; + } + + auto iter = std::find_if(_attributes.cbegin(), _attributes.cend(), [](const gfx::Attribute &element) -> bool { + return element.name == gfx::ATTR_NAME_POSITION; + }); + if (iter == _attributes.end()) { + return EMPTY_GEOMETRIC_INFO; + } + + const auto &attri = *iter; + const uint32_t count = gfx::GFX_FORMAT_INFOS[static_cast(attri.format)].count; + + auto index = static_cast(_subMeshIdx.value()); + const auto &positionsVar = _mesh->readAttribute(index, gfx::ATTR_NAME_POSITION); + + Float32Array const *pPositions = nullptr; + switch (attri.format) { + case gfx::Format::RG32F: + case gfx::Format::RGB32F: { + pPositions = ccstd::get_if(&positionsVar); + if (pPositions == nullptr) { + return EMPTY_GEOMETRIC_INFO; + } + break; + } + case gfx::Format::RGBA32F: { + const auto *data = ccstd::get_if(&positionsVar); + if (data == nullptr) { + return EMPTY_GEOMETRIC_INFO; + } + const auto count = data->length() / 4; + auto *pos = ccnew Float32Array(count * 3); + for (uint32_t i = 0; i < count; i++) { + const auto dstPtr = i * 3; + const auto srcPtr = i * 4; + (*pos)[dstPtr] = (*data)[srcPtr]; + (*pos)[dstPtr + 1] = (*data)[srcPtr + 1]; + (*pos)[dstPtr + 2] = (*data)[srcPtr + 2]; + } + pPositions = pos; + break; + } + case gfx::Format::RG16F: + case gfx::Format::RGB16F: { + const auto *data = ccstd::get_if(&positionsVar); + if (data == nullptr) { + return EMPTY_GEOMETRIC_INFO; + } + auto *pos = ccnew Float32Array(data->length()); + for (uint32_t i = 0; i < data->length(); ++i) { + (*pos)[i] = mathutils::halfToFloat((*data)[i]); + } + pPositions = pos; + break; + } + case gfx::Format::RGBA16F: { + const auto *data = ccstd::get_if(&positionsVar); + if (data == nullptr) { + return EMPTY_GEOMETRIC_INFO; + } + const auto count = data->length() / 4; + auto *pos = ccnew Float32Array(count * 3); + for (uint32_t i = 0; i < count; i++) { + const auto dstPtr = i * 3; + const auto srcPtr = i * 4; + (*pos)[dstPtr] = mathutils::halfToFloat((*data)[srcPtr]); + (*pos)[dstPtr + 1] = mathutils::halfToFloat((*data)[srcPtr + 1]); + (*pos)[dstPtr + 2] = mathutils::halfToFloat((*data)[srcPtr + 2]); + } + pPositions = pos; + break; + } + default: + return EMPTY_GEOMETRIC_INFO; + }; + + const auto &positions = *pPositions; + const auto &indicesVar = _mesh->readIndices(index); + + Vec3 max; + Vec3 min; + + if (count == 2) { + max.set(positions[0], positions[1], 0); + min.set(positions[0], positions[1], 0); + } else { + max.set(positions[0], positions[1], positions[2]); + min.set(positions[0], positions[1], positions[2]); + } + + for (int i = 0; i < positions.length(); i += static_cast(count)) { + if (count == 2) { + max.x = positions[i] > max.x ? positions[i] : max.x; + max.y = positions[i + 1] > max.y ? positions[i + 1] : max.y; + min.x = positions[i] < min.x ? positions[i] : min.x; + min.y = positions[i + 1] < min.y ? positions[i + 1] : min.y; + } else { + max.x = positions[i] > max.x ? positions[i] : max.x; + max.y = positions[i + 1] > max.y ? positions[i + 1] : max.y; + max.z = positions[i + 2] > max.z ? positions[i + 2] : max.z; + min.x = positions[i] < min.x ? positions[i] : min.x; + min.y = positions[i + 1] < min.y ? positions[i + 1] : min.y; + min.z = positions[i + 2] < min.z ? positions[i + 2] : min.z; + } + } + + IGeometricInfo info; + info.positions = positions; + info.indices = indicesVar; + info.boundingBox.max = max; + info.boundingBox.min = min; + + _geometricInfo = info; + return _geometricInfo.value(); +} + +void RenderingSubMesh::genFlatBuffers() { + if (!_flatBuffers.empty() || _mesh == nullptr || !_subMeshIdx.has_value()) { + return; + } + + uint32_t idxCount = 0; + const auto &prim = _mesh->getStruct().primitives[_subMeshIdx.value()]; + if (prim.indexView.has_value()) { + idxCount = prim.indexView.value().count; + } + for (size_t i = 0; i < prim.vertexBundelIndices.size(); i++) { + const uint32_t bundleIdx = prim.vertexBundelIndices[i]; + const Mesh::IVertexBundle &vertexBundle = _mesh->getStruct().vertexBundles[bundleIdx]; + const uint32_t vbCount = prim.indexView.has_value() ? prim.indexView.value().count : vertexBundle.view.count; + const uint32_t vbStride = vertexBundle.view.stride; + const uint32_t vbSize = vbStride * vbCount; + Uint8Array view(_mesh->getData().buffer(), vertexBundle.view.offset, vertexBundle.view.length); + Uint8Array sharedView(prim.indexView.has_value() ? vbSize : vertexBundle.view.length); + + if (!prim.indexView.has_value()) { + sharedView.set(_mesh->getData().subarray(vertexBundle.view.offset, vertexBundle.view.offset + vertexBundle.view.length)); + _flatBuffers.emplace_back(IFlatBuffer{vbStride, vbCount, sharedView}); + continue; + } + + IBArray ibView = _mesh->readIndices(static_cast(_subMeshIdx.value())); + // transform to flat buffer + for (uint32_t n = 0; n < idxCount; ++n) { + auto idx = getIBArrayValue(ibView, static_cast(n)); + uint32_t offset = n * vbStride; + uint32_t srcOffset = idx * vbStride; + for (uint32_t m = 0; m < vbStride; ++m) { + sharedView[static_cast(offset + m)] = view[static_cast(srcOffset + m)]; + } + } + _flatBuffers.emplace_back(IFlatBuffer{vbStride, vbCount, std::move(sharedView)}); + } +} + +void RenderingSubMesh::enableVertexIdChannel(gfx::Device *device) { + if (_vertexIdChannel.has_value()) { + return; + } + + const auto streamIndex = static_cast(_vertexBuffers.size()); + const auto attributeIndex = static_cast(_attributes.size()); + + gfx::Buffer *vertexIdBuffer = allocVertexIdBuffer(device); + _vertexBuffers.pushBack(vertexIdBuffer); + _attributes.push_back({"a_vertexId", gfx::Format::R32F, false, streamIndex, false, 0}); + + _iaInfo.attributes = _attributes; + _iaInfo.vertexBuffers = _vertexBuffers.get(); + + _vertexIdChannel = VertexIdChannel{ + streamIndex, + attributeIndex, + }; +} + +bool RenderingSubMesh::destroy() { + _vertexBuffers.clear(); + _indexBuffer = nullptr; + _indirectBuffer = nullptr; + + if (!_jointMappedBuffers.empty() && !_jointMappedBufferIndices.empty()) { + for (uint32_t index : _jointMappedBufferIndices) { + _jointMappedBuffers.at(index)->destroy(); + } + _jointMappedBuffers.clear(); + _jointMappedBufferIndices.clear(); + } + return true; +} + +const gfx::BufferList &RenderingSubMesh::getJointMappedBuffers() { + if (!_jointMappedBuffers.empty()) { + return _jointMappedBuffers.get(); + } + + auto &buffers = _jointMappedBuffers; + auto &indices = _jointMappedBufferIndices; + + if (!_mesh || !_subMeshIdx.has_value()) { + _jointMappedBuffers = _vertexBuffers; + return _jointMappedBuffers.get(); + } + + const auto &structInfo = _mesh->getStruct(); + const auto &prim = structInfo.primitives[_subMeshIdx.value()]; + if (!structInfo.jointMaps.has_value() || !prim.jointMapIndex.has_value() || structInfo.jointMaps.value()[prim.jointMapIndex.value()].empty()) { + _jointMappedBuffers = _vertexBuffers; + return _jointMappedBuffers.get(); + } + gfx::Format jointFormat = gfx::Format::UNKNOWN; + int32_t jointOffset = 0; + gfx::Device *device = gfx::Device::getInstance(); + for (size_t i = 0; i < prim.vertexBundelIndices.size(); i++) { + const auto &bundle = structInfo.vertexBundles[prim.vertexBundelIndices[i]]; + jointOffset = 0; + jointFormat = gfx::Format::UNKNOWN; + for (const auto &attr : bundle.attributes) { + if (attr.name == gfx::ATTR_NAME_JOINTS) { + jointFormat = attr.format; + break; + } + jointOffset += static_cast(gfx::GFX_FORMAT_INFOS[static_cast(attr.format)].size); + } + if (jointFormat != gfx::Format::UNKNOWN) { + Uint8Array data{_mesh->getData().buffer(), bundle.view.offset, bundle.view.length}; + DataView dataView(data.slice().buffer()); + const auto &idxMap = structInfo.jointMaps.value()[prim.jointMapIndex.value()]; + + mapBuffer( + dataView, [&](const DataVariant &cur, uint32_t /*idx*/, const DataView & /*view*/) -> DataVariant { + if (ccstd::holds_alternative(cur)) { + auto iter = std::find(idxMap.begin(), idxMap.end(), ccstd::get(cur)); + if (iter != idxMap.end()) { + return static_cast(iter - idxMap.begin()); + } + } + CC_ABORT(); + return -1; + }, + jointFormat, jointOffset, bundle.view.length, bundle.view.stride, &dataView); + + auto *buffer = device->createBuffer(gfx::BufferInfo{ + gfx::BufferUsageBit::VERTEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE, + bundle.view.length, + bundle.view.stride}); + + buffer->update(dataView.buffer()->getData()); + buffers.pushBack(buffer); + indices.emplace_back(i); + } else { + buffers.pushBack(_vertexBuffers.at(prim.vertexBundelIndices[i])); + } + } + + if (_vertexIdChannel) { + buffers.pushBack(allocVertexIdBuffer(device)); + } + return buffers.get(); +} + +gfx::Buffer *RenderingSubMesh::allocVertexIdBuffer(gfx::Device *device) { + const uint32_t vertexCount = (_vertexBuffers.empty() || _vertexBuffers.at(0)->getStride() == 0) + ? 0 + // TODO(minggo): This depends on how stride of a vertex buffer is defined; Consider padding problem. + : _vertexBuffers.at(0)->getSize() / _vertexBuffers.at(0)->getStride(); + + ccstd::vector vertexIds(vertexCount); + for (int iVertex = 0; iVertex < vertexCount; ++iVertex) { + // `+0.5` because on some platforms, the "fetched integer" may have small error. + // For example `26` may yield `25.99999`, which is convert to `25` instead of `26` using `int()`. + vertexIds[iVertex] = static_cast(iVertex) + 0.5F; + } + + uint32_t vertexIdxByteLength = sizeof(float) * vertexCount; + gfx::Buffer *vertexIdBuffer = device->createBuffer({ + gfx::BufferUsageBit::VERTEX | gfx::BufferUsageBit::TRANSFER_DST, + gfx::MemoryUsageBit::DEVICE, + vertexIdxByteLength, + sizeof(float), + }); + vertexIdBuffer->update(vertexIds.data(), vertexIdxByteLength); + + return vertexIdBuffer; +} + +} // namespace cc diff --git a/cocos/core/assets/RenderingSubMesh.h b/cocos/core/assets/RenderingSubMesh.h new file mode 100644 index 0000000..ea8bfe0 --- /dev/null +++ b/cocos/core/assets/RenderingSubMesh.h @@ -0,0 +1,230 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "3d/assets/Types.h" +#include "base/RefCounted.h" +#include "base/RefVector.h" +#include "base/std/variant.h" +#include "core/TypedArray.h" +#include "core/Types.h" +#include "renderer/gfx-base/GFXDef.h" + +namespace cc { + +class Mesh; + +/** + * @en The interface of geometric information + * @zh 几何信息。 + */ +struct IGeometricInfo { + /** + * @en Vertex positions + * @zh 顶点位置。 + */ + Float32Array positions; + + /** + * @en Indices data + * @zh 索引数据。 + */ + ccstd::optional indices; + + /** + * @en Whether the geometry is treated as double sided + * @zh 是否将图元按双面对待。 + */ + ccstd::optional doubleSided; + + /** + * @en The bounding box + * @zh 此几何体的轴对齐包围盒。 + */ + BoundingBox boundingBox; +}; + +/** + * @en Flat vertex buffer + * @zh 扁平化顶点缓冲区 + */ +struct IFlatBuffer { + uint32_t stride{0}; + uint32_t count{0}; + Uint8Array buffer; +}; + +namespace gfx { +class Buffer; +} +/** + * @en Sub mesh for rendering which contains all geometry data, it can be used to create [[InputAssembler]]. + * @zh 包含所有顶点数据的渲染子网格,可以用来创建 [[InputAssembler]]。 + */ +class RenderingSubMesh : public RefCounted { +public: + RenderingSubMesh(const gfx::BufferList &vertexBuffers, + const gfx::AttributeList &attributes, + gfx::PrimitiveMode primitiveMode); + + RenderingSubMesh(const gfx::BufferList &vertexBuffers, + const gfx::AttributeList &attributes, + gfx::PrimitiveMode primitiveMode, + gfx::Buffer *indexBuffer); + + RenderingSubMesh(const gfx::BufferList &vertexBuffers, + const gfx::AttributeList &attributes, + gfx::PrimitiveMode primitiveMode, + gfx::Buffer *indexBuffer, + gfx::Buffer *indirectBuffer); + + RenderingSubMesh(const gfx::BufferList &vertexBuffers, + const gfx::AttributeList &attributes, + gfx::PrimitiveMode primitiveMode, + gfx::Buffer *indexBuffer, + gfx::Buffer *indirectBuffer, + bool isOwnerOfIndexBuffer); + + ~RenderingSubMesh() override; + + /** + * @en All vertex attributes used by the sub mesh + * @zh 所有顶点属性。 + */ + inline const gfx::AttributeList &getAttributes() const { return _attributes; } + + /** + * @en All vertex buffers used by the sub mesh + * @zh 使用的所有顶点缓冲区。 + */ + inline const gfx::BufferList &getVertexBuffers() const { return _vertexBuffers.get(); } + + /** + * @en Index buffer used by the sub mesh + * @zh 使用的索引缓冲区,若未使用则无需指定。 + */ + inline gfx::Buffer *getIndexBuffer() const { return _indexBuffer; } + + /** + * @en Indirect buffer used by the sub mesh + * @zh 间接绘制缓冲区。 + */ + inline gfx::Buffer *indirectBuffer() const { return _indirectBuffer; } + + /** + * @en The geometric info of the sub mesh, used for raycast. + * @zh (用于射线检测的)几何信息。 + */ + const IGeometricInfo &getGeometricInfo(); + + /** + * @en Invalidate the geometric info of the sub mesh after geometry changed. + * @zh 网格更新后,设置(用于射线检测的)几何信息为无效,需要重新计算。 + */ + inline void invalidateGeometricInfo() { _geometricInfo.reset(); } + + /** + * @en Primitive mode used by the sub mesh + * @zh 图元类型。 + */ + inline gfx::PrimitiveMode getPrimitiveMode() const { return _primitiveMode; } + + /** + * @en Flatted vertex buffers + * @zh 扁平化的顶点缓冲区。 + */ + inline const ccstd::vector &getFlatBuffers() const { return _flatBuffers; } + inline void setFlatBuffers(const ccstd::vector &flatBuffers) { _flatBuffers = flatBuffers; } + + void genFlatBuffers(); + + inline const gfx::InputAssemblerInfo &getIaInfo() const { return _iaInfo; } + inline gfx::InputAssemblerInfo &getIaInfo() { return _iaInfo; } + + inline void setDrawInfo(const gfx::DrawInfo &info) { _drawInfo = info; } + inline ccstd::optional &getDrawInfo() { return _drawInfo; } + + /** + * @en The vertex buffer for joint after mapping + * @zh 骨骼索引按映射表处理后的顶点缓冲。 + */ + const gfx::BufferList &getJointMappedBuffers(); + + bool destroy(); + + /** + * @en Adds a vertex attribute input called 'a_vertexId' into this sub-mesh. + * This is useful if you want to simulate `gl_VertexId` in WebGL context prior to 2.0. + * Once you call this function, the vertex attribute is permanently added. + * Subsequent calls to this function take no effect. + * @param device Device used to create related rendering resources. + */ + void enableVertexIdChannel(gfx::Device *device); + + inline void setMesh(Mesh *mesh) { _mesh = mesh; } + inline Mesh *getMesh() const { return _mesh; } + + inline void setSubMeshIdx(const ccstd::optional &idx) { _subMeshIdx = idx; } + inline const ccstd::optional &getSubMeshIdx() const { return _subMeshIdx; } + +private: + gfx::Buffer *allocVertexIdBuffer(gfx::Device *device); + + bool _isOwnerOfIndexBuffer{true}; + + // Mesh will includes RenderingSubMesh, so use Mesh* here. + Mesh *_mesh{nullptr}; + ccstd::optional _subMeshIdx; + + ccstd::vector _flatBuffers; + + // As gfx::InputAssemblerInfo needs the data structure, so not use IntrusivePtr. + RefVector _jointMappedBuffers; + + ccstd::vector _jointMappedBufferIndices; + + ccstd::optional _vertexIdChannel; + + ccstd::optional _geometricInfo; + + // As gfx::InputAssemblerInfo needs the data structure, so not use IntrusivePtr. + RefVector _vertexBuffers; + + gfx::AttributeList _attributes; + + IntrusivePtr _indexBuffer; + + IntrusivePtr _indirectBuffer; + + gfx::PrimitiveMode _primitiveMode{gfx::PrimitiveMode::TRIANGLE_LIST}; + + gfx::InputAssemblerInfo _iaInfo; + + ccstd::optional _drawInfo; + + CC_DISALLOW_COPY_MOVE_ASSIGN(RenderingSubMesh); +}; + +} // namespace cc diff --git a/cocos/core/assets/SceneAsset.cpp b/cocos/core/assets/SceneAsset.cpp new file mode 100644 index 0000000..dd3e54e --- /dev/null +++ b/cocos/core/assets/SceneAsset.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/SceneAsset.h" +#include "base/memory/Memory.h" +#include "core/scene-graph/Scene.h" + +namespace cc { + +SceneAsset::SceneAsset() = default; +SceneAsset::~SceneAsset() = default; + +bool SceneAsset::validate() const { + return _scene.get() != nullptr; +} + +void SceneAsset::setScene(Scene *scene) { _scene = scene; }; + +void SceneAsset::initDefault(const ccstd::optional &uuid) { + Super::initDefault(uuid); + _scene = ccnew Scene("New Scene"); +} + +} // namespace cc diff --git a/cocos/core/assets/SceneAsset.h b/cocos/core/assets/SceneAsset.h new file mode 100644 index 0000000..5814e68 --- /dev/null +++ b/cocos/core/assets/SceneAsset.h @@ -0,0 +1,59 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Ptr.h" +#include "core/assets/Asset.h" + +namespace cc { + +class Scene; + +class SceneAsset final : public Asset { +public: + using Super = Asset; + SceneAsset(); + ~SceneAsset() override; + + void initDefault(const ccstd::optional &uuid) override; + + bool validate() const override; + + inline Scene *getScene() const { return _scene.get(); } + void setScene(Scene *scene); + +private: + /** + * @en The scene node + * @zh 场景节点。 + + @editable + @serializable*/ + IntrusivePtr _scene; + + CC_DISALLOW_COPY_MOVE_ASSIGN(SceneAsset); +}; + +} // namespace cc diff --git a/cocos/core/assets/SimpleTexture.cpp b/cocos/core/assets/SimpleTexture.cpp new file mode 100644 index 0000000..b24457e --- /dev/null +++ b/cocos/core/assets/SimpleTexture.cpp @@ -0,0 +1,233 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/SimpleTexture.h" +#include "core/assets/ImageAsset.h" +#include "core/platform/Debug.h" +#include "core/platform/Macro.h" +#include "renderer/gfx-base/GFXDevice.h" + +namespace cc { + +namespace { + +uint32_t getMipLevel(uint32_t width, uint32_t height) { + uint32_t size = std::max(width, height); + uint32_t level = 0; + while (size) { + size >>= 1; + level++; + } + return level; +} + +bool isPOT(uint32_t n) { return n && (n & (n - 1)) == 0; } + +bool canGenerateMipmap(uint32_t w, uint32_t h) { + return isPOT(w) && isPOT(h); +} + +} // namespace + +SimpleTexture::SimpleTexture() = default; +SimpleTexture::~SimpleTexture() = default; + +bool SimpleTexture::destroy() { + tryDestroyTextureView(); + tryDestroyTexture(); + return Super::destroy(); +} + +void SimpleTexture::updateImage() { + updateMipmaps(0, 0); +} + +void SimpleTexture::uploadDataWithArrayBuffer(const ArrayBuffer &source, uint32_t level /* = 0 */, uint32_t arrayIndex /* = 0 */) { + uploadData(source.getData(), level, arrayIndex); +} + +void SimpleTexture::uploadData(const uint8_t *source, uint32_t level /* = 0 */, uint32_t arrayIndex /* = 0 */) { + if (!_gfxTexture || _mipmapLevel <= level) { + return; + } + + auto *gfxDevice = getGFXDevice(); + if (!gfxDevice) { + return; + } + + gfx::BufferTextureCopy region; + region.texExtent.width = _textureWidth >> level; + region.texExtent.height = _textureHeight >> level; + region.texSubres.mipLevel = level; + region.texSubres.baseArrayLayer = arrayIndex; + + const uint8_t *buffers[1]{source}; + gfxDevice->copyBuffersToTexture(buffers, _gfxTexture, ®ion, 1); +} + +void SimpleTexture::assignImage(ImageAsset *image, uint32_t level, uint32_t arrayIndex /* = 0 */) { + const uint8_t *data = image->getData(); + if (!data) { + return; + } + + uploadData(data, level, arrayIndex); + checkTextureLoaded(); + + emit(image); +} + +void SimpleTexture::checkTextureLoaded() { + textureReady(); +} + +void SimpleTexture::textureReady() { + _loaded = true; + //cjh this.emit('load'); +} + +void SimpleTexture::setMipmapLevel(uint32_t value) { + _mipmapLevel = value < 1 ? 1 : value; +} + +void SimpleTexture::tryReset() { + tryDestroyTextureView(); + tryDestroyTexture(); + if (_mipmapLevel == 0) { + return; + } + auto *device = getGFXDevice(); + if (!device) { + return; + } + createTexture(device); + _gfxTextureView = createTextureView(device); +} + +void SimpleTexture::createTexture(gfx::Device *device) { + if (_width == 0 || _height == 0) { + return; + } + + auto flags = gfx::TextureFlagBit::NONE; + auto usage = gfx::TextureUsageBit::SAMPLED | gfx::TextureUsageBit::TRANSFER_DST; + if (_mipFilter != Filter::NONE && canGenerateMipmap(_width, _height)) { + _mipmapLevel = getMipLevel(_width, _height); + if (!isUsingOfflineMipmaps() && !isCompressed()) { + flags = gfx::TextureFlagBit::GEN_MIPMAP; + } + } + + const auto gfxFormat = getGFXFormat(); + if (hasFlag(gfx::Device::getInstance()->getFormatFeatures(gfxFormat), gfx::FormatFeatureBit::RENDER_TARGET)) { + usage |= gfx::TextureUsageBit::COLOR_ATTACHMENT; + } + + auto textureCreateInfo = getGfxTextureCreateInfo( + usage, + gfxFormat, + _mipmapLevel, + flags); + + //cjh if (!textureCreateInfo) { + // return; + // } + + auto *texture = device->createTexture(textureCreateInfo); + _textureWidth = textureCreateInfo.width; + _textureHeight = textureCreateInfo.height; + + _gfxTexture = texture; + + notifyTextureUpdated(); +} + +gfx::Texture *SimpleTexture::createTextureView(gfx::Device *device) { + if (!_gfxTexture) { + return nullptr; + } + const uint32_t maxLevel = _maxLevel < _mipmapLevel ? _maxLevel : _mipmapLevel - 1; + auto textureViewCreateInfo = getGfxTextureViewCreateInfo( + _gfxTexture, + getGFXFormat(), + _baseLevel, + maxLevel - _baseLevel + 1); + + //TODO(minggo) + // if (!textureViewCreateInfo) { + // return; + // } + + return device->createTexture(textureViewCreateInfo); +} + +void SimpleTexture::tryDestroyTexture() { + if (_gfxTexture != nullptr) { + _gfxTexture->destroy(); + _gfxTexture = nullptr; + + notifyTextureUpdated(); + } +} + +void SimpleTexture::tryDestroyTextureView() { + if (_gfxTextureView != nullptr) { + _gfxTextureView->destroy(); + _gfxTextureView = nullptr; + + //TODO(minggo): should notify JS if the performance is low. + } +} + +void SimpleTexture::setMipRange(uint32_t baseLevel, uint32_t maxLevel) { + debug::assertID(baseLevel <= maxLevel, 3124); + + setMipRangeInternal(baseLevel, maxLevel); + + auto *device = getGFXDevice(); + if (!device) { + return; + } + // create a new texture view before the destruction of the previous one to bypass the bug that + // vulkan destroys textureview in use. This is a temporary solution, should be fixed later. + gfx::Texture *textureView = createTextureView(device); + tryDestroyTextureView(); + _gfxTextureView = textureView; +} + +bool SimpleTexture::isUsingOfflineMipmaps() { + return false; +} + +void SimpleTexture::setMipRangeInternal(uint32_t baseLevel, uint32_t maxLevel) { + _baseLevel = baseLevel < 1 ? 0 : baseLevel; + _maxLevel = _maxLevel < 1 ? 0 : maxLevel; +} + +void SimpleTexture::notifyTextureUpdated() { + emit(_gfxTexture.get()); +} + +} // namespace cc diff --git a/cocos/core/assets/SimpleTexture.h b/cocos/core/assets/SimpleTexture.h new file mode 100644 index 0000000..facfaa5 --- /dev/null +++ b/cocos/core/assets/SimpleTexture.h @@ -0,0 +1,171 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/ArrayBuffer.h" +#include "core/assets/TextureBase.h" + +namespace cc { + +class ImageAsset; + +/** + * @en The simple texture base class. + * It create the GFX Texture and can set mipmap levels. + * @zh 简单贴图基类。 + * 简单贴图内部创建了 GFX 贴图和该贴图上的 GFX 贴图视图。 + * 简单贴图允许指定不同的 Mipmap 层级。 + */ +class SimpleTexture : public TextureBase { + IMPL_EVENT_TARGET(SimpleTexture) + DECLARE_TARGET_EVENT_BEGIN_WITH_PARENTS(SimpleTexture, TextureBase) + TARGET_EVENT_ARG1(TextureUpdated, cc::gfx::Texture *) + TARGET_EVENT_ARG1(AfterAssignImage, cc::ImageAsset *) + DECLARE_TARGET_EVENT_END() +public: + ~SimpleTexture() override; + + using Super = TextureBase; + /** + * @en The mipmap level of the texture + * @zh 贴图中的 Mipmap 层级数量 + */ + inline uint32_t mipmapLevel() const { + return _mipmapLevel; + } + + /** + * @en The GFX Texture resource + * @zh 获取此贴图底层的 GFX 贴图对象。 + */ + gfx::Texture *getGFXTexture() const override { + return _gfxTextureView.get(); + } + + bool destroy() override; + + /** + * @en Update the level 0 mipmap image. + * @zh 更新 0 级 Mipmap。 + */ + void updateImage(); + + /** + * @en Update the given level mipmap image. + * @zh 更新指定层级范围内的 Mipmap。当 Mipmap 数据发生了改变时应调用此方法提交更改。 + * 若指定的层级范围超出了实际已有的层级范围,只有覆盖的那些层级范围会被更新。 + * @param firstLevel First level to be updated + * @param count Mipmap level count to be updated + */ + virtual void updateMipmaps(uint32_t firstLevel, uint32_t count) {} + + /** + * @en Upload data to the given mipmap level. + * The size of the image will affect how the mipmap is updated. + * - When the image is an ArrayBuffer, the size of the image must match the mipmap size. + * - If the image size matches the mipmap size, the mipmap data will be updated entirely. + * - If the image size is smaller than the mipmap size, the mipmap will be updated from top left corner. + * - If the image size is larger, an error will be raised + * @zh 上传图像数据到指定层级的 Mipmap 中。 + * 图像的尺寸影响 Mipmap 的更新范围: + * - 当图像是 `ArrayBuffer` 时,图像的尺寸必须和 Mipmap 的尺寸一致;否则, + * - 若图像的尺寸与 Mipmap 的尺寸相同,上传后整个 Mipmap 的数据将与图像数据一致; + * - 若图像的尺寸小于指定层级 Mipmap 的尺寸(不管是长或宽),则从贴图左上角开始,图像尺寸范围内的 Mipmap 会被更新; + * - 若图像的尺寸超出了指定层级 Mipmap 的尺寸(不管是长或宽),都将引起错误。 + * @param source The source image or image data + * @param level Mipmap level to upload the image to + * @param arrayIndex The array index + */ + void uploadDataWithArrayBuffer(const ArrayBuffer &source, uint32_t level = 0, uint32_t arrayIndex = 0); + void uploadData(const uint8_t *source, uint32_t level = 0, uint32_t arrayIndex = 0); + + void assignImage(ImageAsset *image, uint32_t level, uint32_t arrayIndex = 0); + + void checkTextureLoaded(); + + /** + * Set mipmap level of this texture. + * The value is passes as presumed info to `this._getGfxTextureCreateInfo()`. + * @param value The mipmap level. + * @warn As it is invoked by subclass(TextureCube) in TS, so should make it as public. + */ + void setMipmapLevel(uint32_t value); + + /** + * Set mipmap level range for this texture. + * @param baseLevel The base mipmap level. + * @param maxLevel The maximum mipmap level. + */ + void setMipRange(uint32_t baseLevel, uint32_t maxLevel); + + /** + * @en Whether mipmaps are baked convolutional maps. + * @zh mipmaps是否为烘焙出来的卷积图。 + */ + virtual bool isUsingOfflineMipmaps(); + +protected: + SimpleTexture(); + void textureReady(); + + /** + * @en This method is override by derived classes to provide GFX texture info. + * @zh 这个方法被派生类重写以提供 GFX 纹理信息。 + * @param presumed The presumed GFX texture info. + */ + virtual gfx::TextureInfo getGfxTextureCreateInfo(gfx::TextureUsageBit usage, gfx::Format format, uint32_t levelCount, gfx::TextureFlagBit flags) = 0; + + /** + * @en This method is overrided by derived classes to provide GFX TextureViewInfo. + * @zh 这个方法被派生类重写以提供 GFX 纹理视图信息。 + * @param presumed The presumed GFX TextureViewInfo. + */ + virtual gfx::TextureViewInfo getGfxTextureViewCreateInfo(gfx::Texture *texture, gfx::Format format, uint32_t baseLevel, uint32_t levelCount) = 0; + + void tryReset(); + + void createTexture(gfx::Device *device); + gfx::Texture *createTextureView(gfx::Device *device); + + void tryDestroyTexture(); + void tryDestroyTextureView(); + void notifyTextureUpdated(); + void setMipRangeInternal(uint32_t baseLevel, uint32_t maxLevel); + + IntrusivePtr _gfxTexture; + IntrusivePtr _gfxTextureView; + + uint32_t _mipmapLevel{1}; + // Cache these data to reduce JSB invoking. + uint32_t _textureWidth{0}; + uint32_t _textureHeight{0}; + + uint32_t _baseLevel{0}; + uint32_t _maxLevel{1000}; + + CC_DISALLOW_COPY_MOVE_ASSIGN(SimpleTexture); +}; + +} // namespace cc diff --git a/cocos/core/assets/TextAsset.h b/cocos/core/assets/TextAsset.h new file mode 100644 index 0000000..fbb596a --- /dev/null +++ b/cocos/core/assets/TextAsset.h @@ -0,0 +1,55 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/assets/Asset.h" + +namespace cc { + +/** + * @en Class for text file. + * @zh 文本资源。 + */ +class TextAsset final : public Asset { +public: + explicit TextAsset() = default; + ~TextAsset() override = default; + /** + * @en The text content. + * @zh 此资源包含的文本。 + + @serializable + @editable*/ + ccstd::string text; + + ccstd::string toString() const override { + return text; + } + +private: + CC_DISALLOW_COPY_MOVE_ASSIGN(TextAsset); +}; + +} // namespace cc diff --git a/cocos/core/assets/Texture2D.cpp b/cocos/core/assets/Texture2D.cpp new file mode 100644 index 0000000..1d22d16 --- /dev/null +++ b/cocos/core/assets/Texture2D.cpp @@ -0,0 +1,242 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/Texture2D.h" + +#include + +#include "base/Log.h" +#include "core/assets/ImageAsset.h" + +namespace cc { + +Texture2D::Texture2D() = default; +Texture2D::~Texture2D() = default; + +void Texture2D::syncMipmapsForJS(const ccstd::vector> &value) { + _mipmaps = value; +} + +void Texture2D::setMipmaps(const ccstd::vector> &value) { + _mipmaps = value; + + auto mipmaps = ccstd::vector>{}; + + if (value.size() == 1) { + const auto images = value[0]->extractMipmaps(); + std::copy(std::cbegin(images), std::cend(images), std::back_inserter(mipmaps)); + } else if (value.size() > 1) { + for (const auto &image : value) { + mipmaps.emplace_back(image->extractMipmap0()); + } + } + + setMipmapParams(mipmaps); +} + +void Texture2D::setMipmapParams(const ccstd::vector> &value) { + _generatedMipmaps = value; + setMipmapLevel(static_cast(_generatedMipmaps.size())); + if (!_generatedMipmaps.empty()) { + ImageAsset *imageAsset = _generatedMipmaps[0]; + ITexture2DCreateInfo info; + info.width = imageAsset->getWidth(); + info.height = imageAsset->getHeight(); + info.format = imageAsset->getFormat(); + info.mipmapLevel = static_cast(_generatedMipmaps.size()); + info.baseLevel = _baseLevel; + info.maxLevel = _maxLevel; + reset(info); + + for (size_t i = 0, len = _generatedMipmaps.size(); i < len; ++i) { + assignImage(_generatedMipmaps[i], static_cast(i)); + } + + } else { + ITexture2DCreateInfo info; + info.width = 0; + info.height = 0; + info.mipmapLevel = static_cast(_generatedMipmaps.size()); + info.baseLevel = _baseLevel; + info.maxLevel = _maxLevel; + reset(info); + } +} + +void Texture2D::initialize() { + setMipmaps(_mipmaps); +} + +void Texture2D::onLoaded() { + initialize(); +} + +void Texture2D::reset(const ITexture2DCreateInfo &info) { + _width = info.width; + _height = info.height; + setGFXFormat(info.format); + + const uint32_t mipLevels = info.mipmapLevel.has_value() ? info.mipmapLevel.value() : 1; + setMipmapLevel(mipLevels); + + const uint32_t minLod = info.baseLevel.has_value() ? info.baseLevel.value() : 0; + const uint32_t maxLod = info.maxLevel.has_value() ? info.maxLevel.value() : 1000; + setMipRange(minLod, maxLod); + + tryReset(); +} + +void Texture2D::create(uint32_t width, uint32_t height, PixelFormat format /* = PixelFormat::RGBA8888*/, uint32_t mipmapLevel /* = 1*/, uint32_t baseLevel, uint32_t maxLevel) { + reset({width, + height, + format, + mipmapLevel, + baseLevel, + maxLevel}); +} + +ccstd::string Texture2D::toString() const { + ccstd::string ret; + if (!_mipmaps.empty()) { + ret = _mipmaps[0]->getUrl(); + } + return ret; +} + +void Texture2D::updateMipmaps(uint32_t firstLevel, uint32_t count) { + if (firstLevel >= _generatedMipmaps.size()) { + return; + } + + const auto nUpdate = static_cast(std::min( + (count == 0 ? _generatedMipmaps.size() : count), + (_generatedMipmaps.size() - firstLevel))); + + for (uint32_t i = 0; i < nUpdate; ++i) { + const uint32_t level = firstLevel + i; + assignImage(_generatedMipmaps[level], level); + } +} + +bool Texture2D::destroy() { + _mipmaps.clear(); + _generatedMipmaps.clear(); + return Super::destroy(); +} + +ccstd::string Texture2D::description() const { + std::stringstream ret; + ccstd::string url; + if (!_mipmaps.empty()) { + url = _mipmaps[0]->getUrl(); + } + ret << ""; + return ret.str(); +} + +void Texture2D::releaseTexture() { + destroy(); +} + +ccstd::any Texture2D::serialize(const ccstd::any & /*ctxForExporting*/) { + // if (EDITOR || TEST) { + // return { + // base: super._serialize(ctxForExporting), + // mipmaps: this._mipmaps.map((mipmap) => { + // if (!mipmap || !mipmap._uuid) { + // return null; + // } + // if (ctxForExporting && ctxForExporting._compressUuid) { + // // ctxForExporting.dependsOn('_textureSource', texture); TODO + // return EditorExtends.UuidUtils.compressUuid(mipmap._uuid, true); + // } + // return mipmap._uuid; + // }), + // }; + // } + return nullptr; +} + +void Texture2D::deserialize(const ccstd::any &serializedData, const ccstd::any &handle) { + const auto *data = ccstd::any_cast(&serializedData); + if (data == nullptr) { + CC_LOG_WARNING("serializedData is not ITexture2DSerializeData"); + return; + } + Super::deserialize(data->base, handle); + + _mipmaps.resize(data->mipmaps.size()); + for (size_t i = 0; i < data->mipmaps.size(); ++i) { + // Prevent resource load failed + _mipmaps[i] = ccnew ImageAsset(); + if (data->mipmaps[i].empty()) { + continue; + } + ccstd::string mipmapUUID = data->mipmaps[i]; + //cjh TODO: handle.result.push(this._mipmaps, `${i}`, mipmapUUID, js.getClassId(ImageAsset)); + } +} + +gfx::TextureInfo Texture2D::getGfxTextureCreateInfo(gfx::TextureUsageBit usage, gfx::Format format, uint32_t levelCount, gfx::TextureFlagBit flags) { + gfx::TextureInfo texInfo; + texInfo.type = gfx::TextureType::TEX2D; + texInfo.width = _width; + texInfo.height = _height; + texInfo.usage = usage; + texInfo.format = format; + texInfo.levelCount = levelCount; + texInfo.flags = flags; + return texInfo; +} + +gfx::TextureViewInfo Texture2D::getGfxTextureViewCreateInfo(gfx::Texture *texture, gfx::Format format, uint32_t baseLevel, uint32_t levelCount) { + gfx::TextureViewInfo texViewInfo; + texViewInfo.type = gfx::TextureType::TEX2D; + texViewInfo.texture = texture; + texViewInfo.format = format; + texViewInfo.baseLevel = baseLevel; + texViewInfo.levelCount = levelCount; + return texViewInfo; +} + +void Texture2D::initDefault(const ccstd::optional &uuid) { + Super::initDefault(uuid); + auto *imageAsset = ccnew ImageAsset(); + imageAsset->initDefault(ccstd::nullopt); + setImage(imageAsset); +} + +void Texture2D::setImage(ImageAsset *value) { + ccstd::vector> mipmaps; + if (value != nullptr) { + mipmaps.emplace_back(value); + } + setMipmaps(mipmaps); +} + +bool Texture2D::validate() const { + return !_mipmaps.empty(); +} + +} // namespace cc diff --git a/cocos/core/assets/Texture2D.h b/cocos/core/assets/Texture2D.h new file mode 100644 index 0000000..5dc6037 --- /dev/null +++ b/cocos/core/assets/Texture2D.h @@ -0,0 +1,216 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Ptr.h" +#include "base/std/optional.h" +#include "core/assets/Asset.h" +#include "core/assets/AssetEnum.h" +#include "core/assets/SimpleTexture.h" + +namespace cc { + +struct ITexture2DSerializeData { + ccstd::string base; + ccstd::vector mipmaps; +}; + +/** + * @en The create information for [[Texture2D]] + * @zh 用来创建贴图的信息。 + */ +struct ITexture2DCreateInfo { + /** + * @en The pixel width + * @zh 像素宽度。 + */ + uint32_t width{0}; + + /** + * @en The pixel height + * @zh 像素高度。 + */ + uint32_t height{0}; + + /** + * @en The pixel format + * @zh 像素格式。 + * @default PixelFormat.RGBA8888 + */ + ccstd::optional format; + + /** + * @en The mipmap level count + * @zh mipmap 层级。 + * @default 1 + */ + ccstd::optional mipmapLevel; + + /** + * @en The selected base mipmap level + * @zh 选择使用的最小 mipmap 层级。 + * @default 1 + */ + ccstd::optional baseLevel; + + /** + * @en The selected maximum mipmap level + * @zh 选择使用的最大 mipmap 层级。 + * @default 1 + */ + ccstd::optional maxLevel; +}; + +/** + * @en The 2D texture asset. It supports mipmap, each level of mipmap use an [[ImageAsset]]. + * @zh 二维贴图资源。二维贴图资源的每个 Mipmap 层级都为一张 [[ImageAsset]]。 + */ +class Texture2D final : public SimpleTexture { +public: + using Super = SimpleTexture; + + Texture2D(); + ~Texture2D() override; + + /** + * @en All levels of mipmap images, be noted, automatically generated mipmaps are not included. + * When setup mipmap, the size of the texture and pixel format could be modified. + * @zh 所有层级 Mipmap,注意,这里不包含自动生成的 Mipmap。 + * 当设置 Mipmap 时,贴图的尺寸以及像素格式可能会改变。 + */ + const ccstd::vector> &getMipmaps() const { + return _mipmaps; + } + + const ccstd::vector &getMipmapsUuids() const { // TODO(xwx): temporary use _mipmaps as string array + return _mipmapsUuids; + } + + //cjh TODO: TextureCube also needs this method. + void syncMipmapsForJS(const ccstd::vector> &value); + + void setMipmaps(const ccstd::vector> &value); + + /** + * @en Level 0 mipmap image. + * Be noted, `this.image = img` equals `this.mipmaps = [img]`, + * sets image will clear all previous mipmaps. + * @zh 0 级 Mipmap。 + * 注意,`this.image = img` 等价于 `this.mipmaps = [img]`, + * 也就是说,通过 `this.image` 设置 0 级 Mipmap 时将隐式地清除之前的所有 Mipmap。 + */ + inline ImageAsset *getImage() const { + return _mipmaps.empty() ? nullptr : _mipmaps[0].get(); + } + + void setImage(ImageAsset *value); + + void initialize(); + + void onLoaded() override; + + /** + * @en Reset the current texture with given size, pixel format and mipmap images. + * After reset, the gfx resource will become invalid, you must use [[uploadData]] explicitly to upload the new mipmaps to GPU resources. + * @zh 将当前贴图重置为指定尺寸、像素格式以及指定 mipmap 层级。重置后,贴图的像素数据将变为未定义。 + * mipmap 图像的数据不会自动更新到贴图中,你必须显式调用 [[uploadData]] 来上传贴图数据。 + * @param info The create information + */ + void reset(const ITexture2DCreateInfo &info); + + /** + * @en Reset the current texture with given size, pixel format and mipmap images. + * After reset, the gfx resource will become invalid, you must use [[uploadData]] explicitly to upload the new mipmaps to GPU resources. + * @zh 将当前贴图重置为指定尺寸、像素格式以及指定 mipmap 层级。重置后,贴图的像素数据将变为未定义。 + * mipmap 图像的数据不会自动更新到贴图中,你必须显式调用 [[uploadData]] 来上传贴图数据。 + * @param width Pixel width + * @param height Pixel height + * @param format Pixel format + * @param mipmapLevel Mipmap level count + * @param baseLevel Mipmap base level + * @param maxLevel Mipmap maximum level + + * @deprecated since v1.0 please use [[reset]] instead + */ + void create(uint32_t width, uint32_t height, PixelFormat format = PixelFormat::RGBA8888, uint32_t mipmapLevel = 1, uint32_t baseLevel = 0, uint32_t maxLevel = 1000); + + ccstd::string toString() const override; + + void updateMipmaps(uint32_t firstLevel, uint32_t count) override; + + /** + * @en Destroy the current 2d texture, clear up all mipmap levels and the related GPU resources. + * @zh 销毁此贴图,清空所有 Mipmap 并释放占用的 GPU 资源。 + */ + bool destroy() override; + + /** + * @en Gets the description of the 2d texture + * @zh 返回此贴图的描述。 + * @returns The description + */ + ccstd::string description() const; + + /** + * @en Release used GPU resources. + * @zh 释放占用的 GPU 资源。 + * @deprecated please use [[destroy]] instead + */ + void releaseTexture(); + + // SERIALIZATION + + /** + * @return + */ + ccstd::any serialize(const ccstd::any &ctxForExporting) override; + + /** + * + * @param data + */ + void deserialize(const ccstd::any &serializedData, const ccstd::any &handle) override; + + gfx::TextureInfo getGfxTextureCreateInfo(gfx::TextureUsageBit usage, gfx::Format format, uint32_t levelCount, gfx::TextureFlagBit flags) override; + gfx::TextureViewInfo getGfxTextureViewCreateInfo(gfx::Texture *texture, gfx::Format format, uint32_t baseLevel, uint32_t levelCount) override; + + void initDefault(const ccstd::optional &uuid) override; + + bool validate() const override; + +private: + void setMipmapParams(const ccstd::vector> &value); + + ccstd::vector> _mipmaps; + ccstd::vector> _generatedMipmaps; + + ccstd::vector _mipmapsUuids; // TODO(xwx): temporary use _mipmaps as UUIDs string array + + friend class Texture2DDeserializer; + + CC_DISALLOW_COPY_MOVE_ASSIGN(Texture2D); +}; + +} // namespace cc diff --git a/cocos/core/assets/TextureBase.cpp b/cocos/core/assets/TextureBase.cpp new file mode 100644 index 0000000..696e0bc --- /dev/null +++ b/cocos/core/assets/TextureBase.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/TextureBase.h" +#include "base/StringUtil.h" +#include "cocos/core/platform/Debug.h" +#include "core/utils/IDGenerator.h" + +#include "renderer/gfx-base/GFXDevice.h" +#include "renderer/pipeline/Define.h" + +#include "base/std/hash/hash.h" + +namespace cc { + +namespace { +IDGenerator idGenerator("Tex"); +} + +TextureBase::TextureBase() { + // Id for generate hash in material + _id = idGenerator.getNewId(); + _gfxDevice = getGFXDevice(); + ccstd::hash_t seed = 666; + ccstd::hash_range(seed, _id.begin(), _id.end()); + _textureHash = seed; +} + +TextureBase::~TextureBase() = default; + +void TextureBase::setWrapMode(WrapMode wrapS, WrapMode wrapT, WrapMode wrapR) { + _wrapS = wrapS; + _samplerInfo.addressU = static_cast(wrapS), + + _wrapT = wrapT; + _samplerInfo.addressV = static_cast(wrapT), + + _wrapR = wrapR; + _samplerInfo.addressW = static_cast(wrapR); + + if (_gfxDevice != nullptr) { + _gfxSampler = _gfxDevice->getSampler(_samplerInfo); + } + + notifySamplerUpdated(); +} + +void TextureBase::setWrapMode(WrapMode wrapS, WrapMode wrapT) { + setWrapMode(wrapS, wrapT, wrapS); // wrap modes should be as consistent as possible for performance +} + +void TextureBase::setFilters(Filter minFilter, Filter magFilter) { + _minFilter = minFilter; + _samplerInfo.minFilter = static_cast(minFilter); + _magFilter = magFilter; + _samplerInfo.magFilter = static_cast(magFilter); + + if (_gfxDevice != nullptr) { + _gfxSampler = _gfxDevice->getSampler(_samplerInfo); + } + + notifySamplerUpdated(); +} + +void TextureBase::setMipFilter(Filter mipFilter) { + _mipFilter = mipFilter; + _samplerInfo.mipFilter = static_cast(mipFilter); + + if (_gfxDevice != nullptr) { + _gfxSampler = _gfxDevice->getSampler(_samplerInfo); + } + + notifySamplerUpdated(); +} + +void TextureBase::setAnisotropy(uint32_t anisotropy) { + _anisotropy = anisotropy; + _samplerInfo.maxAnisotropy = anisotropy; + + if (_gfxDevice != nullptr) { + _gfxSampler = _gfxDevice->getSampler(_samplerInfo); + } + + notifySamplerUpdated(); +} + +bool TextureBase::destroy() { + const bool destroyed = Super::destroy(); + //cjh TODO: if (destroyed && legacyCC.director.root?.batcher2D) { + // legacyCC.director.root.batcher2D._releaseDescriptorSetCache(this._textureHash); + // } + return destroyed; +} + +gfx::Sampler *TextureBase::getGFXSampler() const { + if (_gfxSampler == nullptr) { + if (_gfxDevice != nullptr) { + const_cast(this)->_gfxSampler = _gfxDevice->getSampler(_samplerInfo); + } else { + debug::errorID(9302); + } + } + return _gfxSampler; +} + +ccstd::any TextureBase::serialize(const ccstd::any & /*ctxForExporting*/) { + //cjh TODO: if (EDITOR || TEST) { + // return `${this._minFilter},${this._magFilter},${ + // this._wrapS},${this._wrapT},${ + // this._mipFilter},${this._anisotropy}`; + // } + return ccstd::string(""); +} + +void TextureBase::deserialize(const ccstd::any &serializedData, const ccstd::any & /*handle*/) { + const auto *pData = ccstd::any_cast(&serializedData); + if (pData == nullptr) { + return; + } + const ccstd::string &data = *pData; + auto fields = StringUtil::split(data, ","); + fields.insert(fields.begin(), ""); + + if (fields.size() >= 5) { + // decode filters + setFilters(static_cast(atoi(fields[1].c_str())), static_cast(atoi(fields[2].c_str()))); + // decode wraps + setWrapMode(static_cast(atoi(fields[3].c_str())), static_cast(atoi(fields[4].c_str()))); + } + + if (fields.size() >= 7) { + setMipFilter(static_cast(atoi(fields[5].c_str()))); + setAnisotropy(atoi(fields[6].c_str())); + } +} + +gfx::Device *TextureBase::getGFXDevice() { + return gfx::Device::getInstance(); +} + +gfx::Format TextureBase::getGFXFormat() const { + return getGFXPixelFormat(_format); +} + +void TextureBase::setGFXFormat(const ccstd::optional &format) { + _format = format.has_value() ? format.value() : PixelFormat::RGBA8888; +} + +gfx::Format TextureBase::getGFXPixelFormat(PixelFormat format) { + if (format == PixelFormat::RGBA_ETC1) { + format = PixelFormat::RGB_ETC1; + } else if (format == PixelFormat::RGB_A_PVRTC_4BPPV1) { + format = PixelFormat::RGB_PVRTC_4BPPV1; + } else if (format == PixelFormat::RGB_A_PVRTC_2BPPV1) { + format = PixelFormat::RGB_PVRTC_2BPPV1; + } + return static_cast(format); +} + +bool TextureBase::isCompressed() const { + return (_format >= PixelFormat::RGB_ETC1 && _format <= PixelFormat::RGBA_ASTC_12X12) || (_format >= PixelFormat::RGB_A_PVRTC_2BPPV1 && _format <= PixelFormat::RGBA_ETC1); +} + +void TextureBase::notifySamplerUpdated() { + // emit(EventTypesToJS::TEXTURE_BASE_GFX_SAMPLER_UPDATED, _gfxSampler); + emit(_gfxSampler); +} + +} // namespace cc diff --git a/cocos/core/assets/TextureBase.h b/cocos/core/assets/TextureBase.h new file mode 100644 index 0000000..ffde4bb --- /dev/null +++ b/cocos/core/assets/TextureBase.h @@ -0,0 +1,261 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Ptr.h" +#include "core/assets/Asset.h" +#include "core/assets/AssetEnum.h" +#include "renderer/gfx-base/GFXDef.h" +#include "base/std/container/unordered_map.h" + +#include "base/std/any.h" + +namespace cc { + +namespace gfx { +class Sampler; +class Device; +class Texture; +} // namespace gfx +/** + * @en The base texture class, it defines features shared by all textures. + * @zh 贴图资源基类。它定义了所有贴图共用的概念。 + */ +class TextureBase : public Asset { + IMPL_EVENT_TARGET(TextureBase) + DECLARE_TARGET_EVENT_BEGIN(TextureBase) + TARGET_EVENT_ARG1(SamplerUpdated, cc::gfx::Sampler *) + DECLARE_TARGET_EVENT_END() +public: + using Super = Asset; + + /** + * @en The pixel format enum. + * @zh 像素格式枚举类型 + */ + using PixelFormat = cc::PixelFormat; + + /** + * @en The wrap mode enum. + * @zh 环绕模式枚举类型 + */ + using WrapMode = cc::WrapMode; + + /** + * @en The texture filter mode enum + * @zh 纹理过滤模式枚举类型 + */ + using Filter = cc::Filter; + + TextureBase(); // NOTE: Editor needs to invoke 'new TextureBase' in JS, so we need to make the constructor public. + ~TextureBase() override; + /** + * @en Whether the pixel data is compressed. + * @zh 此贴图是否为压缩的像素格式。 + */ + bool isCompressed() const; + + /** + * @en Pixel width of the texture + * @zh 此贴图的像素宽度。 + */ + uint32_t getWidth() const { + return _width; + } + + /** + * @en Pixel height of the texture + * @zh 此贴图的像素高度。 + */ + uint32_t getHeight() const { + return _height; + } + + // Functions for TS deserialization. + inline void setWidth(uint32_t width) { _width = width; } + inline void setHeight(uint32_t height) { _height = height; } + + /** + * @en Gets the id of the texture + * @zh 获取标识符。 + * @returns The id + */ + inline const ccstd::string &getId() const { + return _id; + } + + /** + * @en Gets the pixel format + * @zh 获取像素格式。 + * @returns The pixel format + */ + inline PixelFormat getPixelFormat() const { + return _format; + } + + /** + * @en Gets the anisotropy + * @zh 获取各向异性。 + * @returns The anisotropy + */ + inline uint32_t getAnisotropy() const { + return _anisotropy; + } + + /** + * @en Sets the wrap mode of the texture. + * Be noted, if the size of the texture is not power of two, only [[WrapMode.CLAMP_TO_EDGE]] is allowed. + * @zh 设置此贴图的缠绕模式。 + * 注意,若贴图尺寸不是 2 的整数幂,缠绕模式仅允许 [[WrapMode.CLAMP_TO_EDGE]]。 + * @param wrapS S(U) coordinate wrap mode + * @param wrapT T(V) coordinate wrap mode + * @param wrapR R(W) coordinate wrap mode + */ + void setWrapMode(WrapMode wrapS, WrapMode wrapT, WrapMode wrapR); + void setWrapMode(WrapMode wrapS, WrapMode wrapT); + + /** + * @en Sets the texture's filter mode + * @zh 设置此贴图的过滤算法。 + * @param minFilter Filter mode for scale down + * @param magFilter Filter mode for scale up + */ + void setFilters(Filter minFilter, Filter magFilter); + + /** + * @en Sets the texture's mip filter + * @zh 设置此贴图的缩小过滤算法。 + * @param mipFilter Filter mode for scale down + */ + void setMipFilter(Filter mipFilter); + + /** + * @en Sets the texture's anisotropy + * @zh 设置此贴图的各向异性。 + * @param anisotropy + */ + void setAnisotropy(uint32_t anisotropy); + + /** + * @en Destroy the current texture, clear up the related GPU resources. + * @zh 销毁此贴图,并释放占用的 GPU 资源。 + */ + bool destroy() override; + + /** + * @en Gets the texture hash. + * @zh 获取此贴图的哈希值。 + */ + inline ccstd::hash_t getHash() const { + return _textureHash; + } + + /** + * @en Gets the GFX Texture resource + * @zh 获取此贴图底层的 GFX 贴图对象。 + */ + virtual gfx::Texture *getGFXTexture() const { + return nullptr; + } + + /** + * @en Gets the internal GFX sampler information. + * @zh 获取此贴图内部使用的 GFX 采样器信息。 + * @private + */ + virtual const gfx::SamplerInfo &getSamplerInfo() const { + return _samplerInfo; + } + + /** + * @en Gets the sampler resource for the texture + * @zh 获取此贴图底层的 GFX 采样信息。 + */ + virtual gfx::Sampler *getGFXSampler() const; + + // SERIALIZATION + /** + * @return + */ + ccstd::any serialize(const ccstd::any &ctxForExporting) override; + + /** + * + * @param data + */ + void deserialize(const ccstd::any &serializedData, const ccstd::any &handle) override; + +protected: + static gfx::Device *getGFXDevice(); + static gfx::Format getGFXPixelFormat(PixelFormat format); + + gfx::Format getGFXFormat() const; + + void setGFXFormat(const ccstd::optional &format); + +private: + void notifySamplerUpdated(); + +public: + /*@serializable*/ + PixelFormat _format{PixelFormat::RGBA8888}; + + /*@serializable*/ + Filter _minFilter{Filter::LINEAR}; + + /*@serializable*/ + Filter _magFilter{Filter::LINEAR}; + + /*@serializable*/ + Filter _mipFilter{Filter::NONE}; + + /*@serializable*/ + WrapMode _wrapS{WrapMode::REPEAT}; + + /*@serializable*/ + WrapMode _wrapT{WrapMode::REPEAT}; + + /*@serializable*/ + WrapMode _wrapR{WrapMode::REPEAT}; + + /*@serializable*/ + uint32_t _anisotropy{0}; + +protected: + uint32_t _width{1}; + uint32_t _height{1}; + ccstd::string _id; + gfx::SamplerInfo _samplerInfo; + gfx::Sampler *_gfxSampler{nullptr}; + gfx::Device *_gfxDevice{nullptr}; + + ccstd::hash_t _textureHash{0U}; + +private: + CC_DISALLOW_COPY_MOVE_ASSIGN(TextureBase); +}; + +} // namespace cc diff --git a/cocos/core/assets/TextureCube.cpp b/cocos/core/assets/TextureCube.cpp new file mode 100644 index 0000000..0473530 --- /dev/null +++ b/cocos/core/assets/TextureCube.cpp @@ -0,0 +1,407 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/assets/TextureCube.h" +#include "core/assets/ImageAsset.h" +#include "core/assets/Texture2D.h" +#include "renderer/gfx-base/GFXTexture.h" + +namespace cc { + +namespace { + +using ForEachFaceCallback = std::function; +/** + * @param {Mipmap} mipmap + * @param {(face: ImageAsset) => void} callback + */ +void forEachFace(const ITextureCubeMipmap &mipmap, const ForEachFaceCallback &callback) { + callback(mipmap.front, TextureCube::FaceIndex::FRONT); + callback(mipmap.back, TextureCube::FaceIndex::BACK); + callback(mipmap.left, TextureCube::FaceIndex::LEFT); + callback(mipmap.right, TextureCube::FaceIndex::RIGHT); + callback(mipmap.top, TextureCube::FaceIndex::TOP); + callback(mipmap.bottom, TextureCube::FaceIndex::BOTTOM); +} + +} // namespace + +/* static */ +TextureCube *TextureCube::fromTexture2DArray(const ccstd::vector &textures) { + size_t nMipmaps = textures.size() / 6; + ccstd::vector mipmaps; + mipmaps.reserve(nMipmaps); + for (size_t i = 0; i < nMipmaps; i++) { + size_t x = i * 6; + + ITextureCubeMipmap mipmap; + mipmap.front = textures[x + static_cast(FaceIndex::FRONT)]->getImage(), + mipmap.back = textures[x + static_cast(FaceIndex::BACK)]->getImage(), + mipmap.left = textures[x + static_cast(FaceIndex::LEFT)]->getImage(), + mipmap.right = textures[x + static_cast(FaceIndex::RIGHT)]->getImage(), + mipmap.top = textures[x + static_cast(FaceIndex::TOP)]->getImage(), + mipmap.bottom = textures[x + static_cast(FaceIndex::BOTTOM)]->getImage(), + + mipmaps.emplace_back(mipmap); + } + auto *out = ccnew TextureCube(); + out->setMipmaps(mipmaps); + return out; +} + +TextureCube::TextureCube() = default; + +TextureCube::~TextureCube() = default; + +void TextureCube::setMipmaps(const ccstd::vector &value) { + _mipmaps = value; + + auto cubeMaps = ccstd::vector{}; + if (value.size() == 1) { + const auto &cubeMipmap = value.at(0); + const auto &front = cubeMipmap.front->extractMipmaps(); + const auto &back = cubeMipmap.back->extractMipmaps(); + const auto &left = cubeMipmap.left->extractMipmaps(); + const auto &right = cubeMipmap.right->extractMipmaps(); + const auto &top = cubeMipmap.top->extractMipmaps(); + const auto &bottom = cubeMipmap.bottom->extractMipmaps(); + + if (front.size() != back.size() || + front.size() != left.size() || + front.size() != right.size() || + front.size() != top.size() || + front.size() != bottom.size()) { + assert("different faces should have the same mipmap level"); + this->setMipmapParams({}); + return; + } + + const auto level = front.size(); + + for (auto i = 0U; i < level; i++) { + const auto cubeMap = ITextureCubeMipmap{ + front[i], + back[i], + left[i], + right[i], + top[i], + bottom[i], + }; + cubeMaps.emplace_back(cubeMap); + } + } else if (value.size() > 1) { + for (const auto &mipmap : value) { + const auto cubeMap = ITextureCubeMipmap{ + mipmap.front->extractMipmap0(), + mipmap.back->extractMipmap0(), + mipmap.left->extractMipmap0(), + mipmap.right->extractMipmap0(), + mipmap.top->extractMipmap0(), + mipmap.bottom->extractMipmap0(), + }; + cubeMaps.emplace_back(cubeMap); + } + } + + setMipmapParams(cubeMaps); +} + +void TextureCube::setMipmapParams(const ccstd::vector &value) { + _generatedMipmaps = value; + setMipmapLevel(static_cast(_generatedMipmaps.size())); + if (!_generatedMipmaps.empty()) { + ImageAsset *imageAsset = _generatedMipmaps[0].front; + reset({imageAsset->getWidth(), + imageAsset->getHeight(), + imageAsset->getFormat(), + static_cast(_generatedMipmaps.size()), + _baseLevel, + _maxLevel}); + + for (size_t level = 0, len = _generatedMipmaps.size(); level < len; ++level) { + forEachFace(_generatedMipmaps[level], [this, level](ImageAsset *face, TextureCube::FaceIndex faceIndex) { + assignImage(face, static_cast(level), static_cast(faceIndex)); + }); + } + + } else { + reset({0, + 0, + ccstd::nullopt, + static_cast(_generatedMipmaps.size()), + _baseLevel, + _maxLevel}); + } +} + +void TextureCube::setMipmapAtlas(const TextureCubeMipmapAtlasInfo &value) { + if (value.layout.empty()) { + return; + } + _mipmapAtlas = value; + const ITextureCubeMipmap &atlas = _mipmapAtlas.atlas; + const ccstd::vector &layouts = _mipmapAtlas.layout; + setMipmapLevel(static_cast(layouts.size())); + + const MipmapAtlasLayoutInfo &lv0Layout = layouts[0]; + const ImageAsset *imageAsset = atlas.front; + + reset({lv0Layout.width, + lv0Layout.height, + imageAsset->getFormat(), + static_cast(layouts.size()), + _baseLevel, + _maxLevel}); + + const uint32_t pixelSize = gfx::GFX_FORMAT_INFOS[static_cast(imageAsset->getFormat())].size; + + for (size_t level = 0; level < layouts.size(); level++) { + const MipmapAtlasLayoutInfo &layoutInfo = layouts[level]; + uint32_t currentSize = layoutInfo.width * layoutInfo.height * pixelSize; + + //Upload 6 sides by level + forEachFace(atlas, [this, currentSize, lv0Layout, layoutInfo, level, pixelSize](ImageAsset *face, TextureCube::FaceIndex faceIndex) { + auto *buffer = ccnew uint8_t[currentSize]; + memset(buffer, 0, currentSize); + const uint8_t *data = face->getData(); + //Splitting Atlas + if (level == 0) { + memcpy(buffer, data, currentSize); + } else { + uint32_t bufferOffset = 0; + uint32_t dateOffset = lv0Layout.width * lv0Layout.height * pixelSize; + uint32_t leftOffset = layoutInfo.left * pixelSize; + for (size_t j = 0; j < layoutInfo.height; j++) { + memcpy(buffer + bufferOffset, data + dateOffset + leftOffset, layoutInfo.width * pixelSize); + bufferOffset += layoutInfo.width * pixelSize; + dateOffset += lv0Layout.width * pixelSize; + } + } + auto *tempAsset = ccnew ImageAsset(); + tempAsset->addRef(); + auto *arrayBuffer = ccnew ArrayBuffer(buffer, static_cast(currentSize)); + IMemoryImageSource source{arrayBuffer, face->isCompressed(), layoutInfo.width, layoutInfo.height, face->getFormat()}; + tempAsset->setNativeAsset(source); + + assignImage(tempAsset, static_cast(level), static_cast(faceIndex)); + CC_SAFE_DELETE_ARRAY(buffer); + tempAsset->release(); + tempAsset = nullptr; + }); + } +} + +void TextureCube::setMipmapsForJS(const ccstd::vector &value) { + _mipmaps = value; +} + +void TextureCube::setMipmapAtlasForJS(const TextureCubeMipmapAtlasInfo &value) { + _mipmapAtlas = value; +} + +void TextureCube::setImage(const ITextureCubeMipmap *value) { + if (value != nullptr) { + setMipmaps({*value}); + } else { + setMipmaps({}); + } +} + +void TextureCube::reset(const ITextureCubeCreateInfo &info) { + _width = info.width; + _height = info.height; + setGFXFormat(info.format); + + uint32_t mipLevels = info.mipmapLevel.has_value() ? info.mipmapLevel.value() : 1; + setMipmapLevel(mipLevels); + + uint32_t minLod = info.baseLevel.has_value() ? info.baseLevel.value() : 0; + uint32_t maxLod = info.maxLevel.has_value() ? info.maxLevel.value() : 1000; + setMipRange(minLod, maxLod); + + tryReset(); +} + +void TextureCube::releaseTexture() { + destroy(); +} + +void TextureCube::updateMipmaps(uint32_t firstLevel, uint32_t count) { + if (firstLevel >= _generatedMipmaps.size()) { + return; + } + + auto nUpdate = static_cast(std::min( + count == 0 ? _generatedMipmaps.size() : count, + _generatedMipmaps.size() - firstLevel)); + + for (uint32_t i = 0; i < nUpdate; ++i) { + uint32_t level = firstLevel + i; + forEachFace(_generatedMipmaps[level], [this, level](auto face, auto faceIndex) { + assignImage(face, level, static_cast(faceIndex)); + }); + } +} + +bool TextureCube::isUsingOfflineMipmaps() { + return _mipmapMode == MipmapMode::BAKED_CONVOLUTION_MAP; +} + +void TextureCube::initialize() { + if (_mipmapMode == MipmapMode::BAKED_CONVOLUTION_MAP) { + setMipmapAtlas(_mipmapAtlas); + } else { + setMipmaps(_mipmaps); + } +} + +void TextureCube::onLoaded() { + initialize(); +} + +bool TextureCube::destroy() { + _mipmaps.clear(); + _generatedMipmaps.clear(); + _mipmapAtlas.layout.clear(); + return Super::destroy(); +} + +ccstd::any TextureCube::serialize(const ccstd::any & /*ctxForExporting*/) { + //cjh TODO: if (EDITOR || TEST) { + // return { + // base: super._serialize(ctxForExporting), + // rgbe: this.isRGBE, + // mipmaps: this._mipmaps.map((mipmap) => ((ctxForExporting && ctxForExporting._compressUuid) ? { + // front: EditorExtends.UuidUtils.compressUuid(mipmap.front._uuid, true), + // back: EditorExtends.UuidUtils.compressUuid(mipmap.back._uuid, true), + // left: EditorExtends.UuidUtils.compressUuid(mipmap.left._uuid, true), + // right: EditorExtends.UuidUtils.compressUuid(mipmap.right._uuid, true), + // top: EditorExtends.UuidUtils.compressUuid(mipmap.top._uuid, true), + // bottom: EditorExtends.UuidUtils.compressUuid(mipmap.bottom._uuid, true), + // } : { + // front: mipmap.front._uuid, + // back: mipmap.back._uuid, + // left: mipmap.left._uuid, + // right: mipmap.right._uuid, + // top: mipmap.top._uuid, + // bottom: mipmap.bottom._uuid, + // })), + // }; + // } + return nullptr; +} + +void TextureCube::deserialize(const ccstd::any &serializedData, const ccstd::any &handle) { + const auto *data = ccstd::any_cast(&serializedData); + if (data == nullptr) { + return; + } + Super::deserialize(data->base, handle); + isRGBE = data->rgbe; + _mipmapMode = data->mipmapMode; + + _mipmaps.resize(data->mipmaps.size()); + for (size_t i = 0; i < data->mipmaps.size(); ++i) { + // Prevent resource load failed + ITextureCubeMipmap mipmap; + mipmap.front = ccnew ImageAsset(), + mipmap.back = ccnew ImageAsset(), + mipmap.left = ccnew ImageAsset(), + mipmap.right = ccnew ImageAsset(), + mipmap.top = ccnew ImageAsset(), + mipmap.bottom = ccnew ImageAsset(); + _mipmaps[i] = mipmap; + // auto* mipmap = data->mipmaps[i]; + + //cjh TODO: what's handle.result?? const imageAssetClassId = js.getClassId(ImageAsset); + // + // handle.result.push(this._mipmaps[i], `front`, mipmap.front, imageAssetClassId); + // handle.result.push(this._mipmaps[i], `back`, mipmap.back, imageAssetClassId); + // handle.result.push(this._mipmaps[i], `left`, mipmap.left, imageAssetClassId); + // handle.result.push(this._mipmaps[i], `right`, mipmap.right, imageAssetClassId); + // handle.result.push(this._mipmaps[i], `top`, mipmap.top, imageAssetClassId); + // handle.result.push(this._mipmaps[i], `bottom`, mipmap.bottom, imageAssetClassId); + } +} + +gfx::TextureInfo TextureCube::getGfxTextureCreateInfo(gfx::TextureUsageBit usage, gfx::Format format, uint32_t levelCount, gfx::TextureFlagBit flags) { + gfx::TextureInfo texInfo; + texInfo.type = gfx::TextureType::CUBE; + texInfo.width = _width; + texInfo.height = _height; + texInfo.layerCount = 6; + texInfo.usage = usage; + texInfo.format = format; + texInfo.levelCount = levelCount; + texInfo.flags = flags; + return texInfo; +} + +gfx::TextureViewInfo TextureCube::getGfxTextureViewCreateInfo(gfx::Texture *texture, gfx::Format format, uint32_t baseLevel, uint32_t levelCount) { + gfx::TextureViewInfo texViewInfo; + texViewInfo.type = gfx::TextureType::CUBE; + texViewInfo.baseLayer = 0; + texViewInfo.layerCount = 6; + texViewInfo.texture = texture; + texViewInfo.format = format; + texViewInfo.baseLevel = baseLevel; + texViewInfo.levelCount = levelCount; + return texViewInfo; +} + +void TextureCube::initDefault(const ccstd::optional &uuid) { + Super::initDefault(uuid); + + auto *imageAsset = ccnew ImageAsset(); + imageAsset->initDefault(ccstd::nullopt); + + ITextureCubeMipmap mipmap; + + mipmap.front = imageAsset; + mipmap.back = imageAsset; + mipmap.top = imageAsset; + mipmap.bottom = imageAsset; + mipmap.left = imageAsset; + mipmap.right = imageAsset; + + setMipmaps({mipmap}); +} + +bool TextureCube::validate() const { + if (_mipmapMode == MipmapMode::BAKED_CONVOLUTION_MAP) { + if (_mipmapAtlas.layout.empty()) { + return false; + } + return (_mipmapAtlas.atlas.top && _mipmapAtlas.atlas.bottom && _mipmapAtlas.atlas.front && _mipmapAtlas.atlas.back && _mipmapAtlas.atlas.left && _mipmapAtlas.atlas.right); + } + if (_mipmaps.empty()) { + return false; + } + return std::all_of(_mipmaps.begin(), + _mipmaps.end(), + [&](const ITextureCubeMipmap &mipmap) { + return (mipmap.top && mipmap.bottom && mipmap.front && mipmap.back && mipmap.left && mipmap.right); + }); +} +} // namespace cc diff --git a/cocos/core/assets/TextureCube.h b/cocos/core/assets/TextureCube.h new file mode 100644 index 0000000..a8f705a --- /dev/null +++ b/cocos/core/assets/TextureCube.h @@ -0,0 +1,270 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/assets/Asset.h" +#include "core/assets/SimpleTexture.h" + +namespace cc { + +class ImageAsset; +class Texture2D; +struct ITexture2DCreateInfo; + +using ITextureCubeCreateInfo = ITexture2DCreateInfo; + +/** + * @en The texture cube mipmap interface + * @zh 立方体贴图的 Mipmap 接口。 + */ +struct ITextureCubeMipmap { + IntrusivePtr front; + IntrusivePtr back; + IntrusivePtr left; + IntrusivePtr right; + IntrusivePtr top; + IntrusivePtr bottom; +}; + +struct ITextureCubeSerializeMipmapData { + ccstd::string front; + ccstd::string back; + ccstd::string left; + ccstd::string right; + ccstd::string top; + ccstd::string bottom; +}; + +/** + * @en The MipmapAtlas region interface + * @zh MipmapAtlas的region接口。 + */ +struct MipmapAtlasLayoutInfo { + uint32_t left{0}; + uint32_t top{0}; + uint32_t width{0}; + uint32_t height{0}; + uint32_t level{0}; +}; +/** + * @en The texture cube MipmapAtlas interface + * @zh 立方体贴图的 MipmapAtlas 接口。 + */ +struct TextureCubeMipmapAtlasInfo { + ITextureCubeMipmap atlas; + ccstd::vector layout; +}; + +/** + * @en The way to fill mipmaps. + * @zh 填充mipmaps的方式。 + */ +enum class MipmapMode { + /** + * @zh + * 不使用mipmaps + * @en + * Not using mipmaps + * @readonly + */ + NONE = 0, + /** + * @zh + * 使用自动生成的mipmaps + * @en + * Using the automatically generated mipmaps + * @readonly + */ + AUTO = 1, + /** + * @zh + * 使用卷积图填充mipmaps + * @en + * Filling mipmaps with convolutional maps. + * @readonly + */ + BAKED_CONVOLUTION_MAP = 2 +}; +struct TextureCubeSerializeData { + ccstd::string base; + bool rgbe{false}; + MipmapMode mipmapMode{MipmapMode::NONE}; + ccstd::vector mipmaps; + TextureCubeMipmapAtlasInfo mipmapAtlas; +}; + +/** + * @en The texture cube asset. + * Each mipmap level of a texture cube have 6 [[ImageAsset]], represents 6 faces of the cube. + * @zh 立方体贴图资源。 + * 立方体贴图资源的每个 Mipmap 层级都为 6 张 [[ImageAsset]],分别代表了立方体贴图的 6 个面。 + */ +class TextureCube final : public SimpleTexture { +public: + using Super = SimpleTexture; + + TextureCube(); + ~TextureCube() override; + + /** + * @en The index for all faces of the cube + * @zh 立方体每个面的约定索引。 + */ + enum class FaceIndex { + RIGHT = 0, + LEFT = 1, + TOP = 2, + BOTTOM = 3, + FRONT = 4, + BACK = 5, + }; + + /** + * @en Create a texture cube with an array of [[Texture2D]] which represents 6 faces of the texture cube. + * @zh 通过二维贴图数组指定每个 Mipmap 的每个面创建立方体贴图。 + * @param textures Texture array, the texture count must be multiple of 6. Every 6 textures are 6 faces of a mipmap level. + * The order should obey [[FaceIndex]] order. + * @param out Output texture cube, if not given, will create a new texture cube. + * @returns The created texture cube. + * @example + * ```ts + * const textures = new Array(6); + * textures[TextureCube.FaceIndex.front] = frontImage; + * textures[TextureCube.FaceIndex.back] = backImage; + * textures[TextureCube.FaceIndex.left] = leftImage; + * textures[TextureCube.FaceIndex.right] = rightImage; + * textures[TextureCube.FaceIndex.top] = topImage; + * textures[TextureCube.FaceIndex.bottom] = bottomImage; + * const textureCube = TextureCube.fromTexture2DArray(textures); + * ``` + */ + static TextureCube *fromTexture2DArray(const ccstd::vector &textures); + + /** + * @en All levels of mipmap images, be noted, automatically generated mipmaps are not included. + * When setup mipmap, the size of the texture and pixel format could be modified. + * @zh 所有层级 Mipmap,注意,这里不包含自动生成的 Mipmap。 + * 当设置 Mipmap 时,贴图的尺寸以及像素格式可能会改变。 + */ + const ccstd::vector &getMipmaps() const { + return _mipmaps; + } + + inline const TextureCubeMipmapAtlasInfo &getMipmapAtlas() const { + return _mipmapAtlas; + } + + void setMipmaps(const ccstd::vector &value); + + void setMipmapsForJS(const ccstd::vector &value); + + void setMipmapAtlasForJS(const TextureCubeMipmapAtlasInfo &value); + + /** + * @en Fill mipmaps with convolutional maps. + * @zh 使用卷积图填充mipmaps。 + * @param value All mipmaps of each face of the cube map are stored in the form of atlas. + * and the value contains the atlas of the 6 faces and the layout information of each mipmap layer. + */ + void setMipmapAtlas(const TextureCubeMipmapAtlasInfo &value); + + /** + * @en Level 0 mipmap image. + * Be noted, `this.image = img` equals `this.mipmaps = [img]`, + * sets image will clear all previous mipmaps. + * @zh 0 级 Mipmap。 + * 注意,`this.image = img` 等价于 `this.mipmaps = [img]`, + * 也就是说,通过 `this.image` 设置 0 级 Mipmap 时将隐式地清除之前的所有 Mipmap。 + */ + const ITextureCubeMipmap *getImage() const { + return _mipmaps.empty() ? nullptr : &_mipmaps[0]; + } + + void setImage(const ITextureCubeMipmap *value); + + /** + * @en Reset the current texture with given size, pixel format and mipmap images. + * After reset, the gfx resource will become invalid, you must use [[uploadData]] explicitly to upload the new mipmaps to GPU resources. + * @zh 将当前贴图重置为指定尺寸、像素格式以及指定 mipmap 层级。重置后,贴图的像素数据将变为未定义。 + * mipmap 图像的数据不会自动更新到贴图中,你必须显式调用 [[uploadData]] 来上传贴图数据。 + * @param info The create information + */ + void reset(const ITextureCubeCreateInfo &info); + + /** + * @en Release used GPU resources. + * @zh 释放占用的 GPU 资源。 + * @deprecated please use [[destroy]] instead + */ + void releaseTexture(); + + // Override functions + void updateMipmaps(uint32_t firstLevel, uint32_t count) override; + + /** + * @en Whether mipmaps are baked convolutional maps. + * @zh mipmaps是否为烘焙出来的卷积图。 + */ + bool isUsingOfflineMipmaps() override; + + void initialize(); + void onLoaded() override; + /** + * 销毁此贴图,清空所有 Mipmap 并释放占用的 GPU 资源。 + */ + bool destroy() override; + + ccstd::any serialize(const ccstd::any &ctxForExporting) override; + void deserialize(const ccstd::any &serializedData, const ccstd::any &handle) override; + + gfx::TextureInfo getGfxTextureCreateInfo(gfx::TextureUsageBit usage, gfx::Format format, uint32_t levelCount, gfx::TextureFlagBit flags) override; + gfx::TextureViewInfo getGfxTextureViewCreateInfo(gfx::Texture *texture, gfx::Format format, uint32_t baseLevel, uint32_t levelCount) override; + + void initDefault(const ccstd::optional &uuid) override; + + bool validate() const override; + // + + /*@serializable*/ + MipmapMode _mipmapMode{MipmapMode::NONE}; + + /*@serializable*/ + bool isRGBE{false}; + +private: + void setMipmapParams(const ccstd::vector &value); + + /*@serializable*/ + ccstd::vector _mipmaps; + + ccstd::vector _generatedMipmaps; + + /*@serializable*/ + TextureCubeMipmapAtlasInfo _mipmapAtlas; + + CC_DISALLOW_COPY_MOVE_ASSIGN(TextureCube); +}; + +} // namespace cc diff --git a/cocos/core/builtin/BuiltinResMgr.cpp b/cocos/core/builtin/BuiltinResMgr.cpp new file mode 100644 index 0000000..12b188f --- /dev/null +++ b/cocos/core/builtin/BuiltinResMgr.cpp @@ -0,0 +1,279 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/builtin/BuiltinResMgr.h" +#include "core/assets/EffectAsset.h" +#include "core/assets/ImageAsset.h" +#include "core/assets/Material.h" +#include "core/assets/Texture2D.h" +#include "core/assets/TextureCube.h" +#include "math/Color.h" +#include "platform/Image.h" +#include "rapidjson/document.h" +#include "renderer/core/ProgramLib.h" +#include "scene/Pass.h" + +namespace cc { + +namespace { +constexpr uint8_t BLACK_IMAGE_RGBA_DATA_2X2[2 * 2 * 4] = { + // r, g, b, a + 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF}; + +constexpr uint8_t GREY_IMAGE_RGBA_DATA_2X2[2 * 2 * 4] = { + 0x77, 0x77, 0x77, 0xFF, + 0x77, 0x77, 0x77, 0xFF, + 0x77, 0x77, 0x77, 0xFF, + 0x77, 0x77, 0x77, 0xFF}; + +constexpr uint8_t WHITE_IMAGE_RGBA_DATA_2X2[2 * 2 * 4] = { + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; + +constexpr uint8_t NORMAL_IMAGE_RGBA_DATA_2X2[2 * 2 * 4] = { + 0x7F, 0x7F, 0xFF, 0xFF, + 0x7F, 0x7F, 0xFF, 0xFF, + 0x7F, 0x7F, 0xFF, 0xFF, + 0x7F, 0x7F, 0xFF, 0xFF}; + +constexpr uint8_t EMPTY_IMAGE_RGBA_DATA_2X2[2 * 2 * 4] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + +const uint8_t DEFAULT_IMAGE_RGBA_DATA_16X16[16 * 16 * 4] = { + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x55, 0x55, 0x55, 0xFF}; + +} // namespace + +BuiltinResMgr *BuiltinResMgr::instance = nullptr; + +/* static */ +BuiltinResMgr *BuiltinResMgr::getInstance() { + return BuiltinResMgr::instance; +} + +BuiltinResMgr::BuiltinResMgr() { + BuiltinResMgr::instance = this; +} + +BuiltinResMgr::~BuiltinResMgr() { + BuiltinResMgr::instance = nullptr; +} + +void BuiltinResMgr::addAsset(const ccstd::string &uuid, Asset *asset) { + _resources.emplace(uuid, asset); +} + +Asset *BuiltinResMgr::getAsset(const ccstd::string &uuid) { + auto iter = _resources.find(uuid); + if (iter != _resources.end()) { + return iter->second.get(); + } + + return nullptr; +} + +bool BuiltinResMgr::initBuiltinRes() { + if (_isInitialized) { + return true; + } + + _isInitialized = true; + + // NOTE: C++ use const array to store color value, no need to synchronize ts's valueView logic + + // black texture + initTexture2DWithUuid("black-texture", BLACK_IMAGE_RGBA_DATA_2X2, sizeof(BLACK_IMAGE_RGBA_DATA_2X2), 2, 2); + + // empty texture + initTexture2DWithUuid("empty-texture", EMPTY_IMAGE_RGBA_DATA_2X2, sizeof(EMPTY_IMAGE_RGBA_DATA_2X2), 2, 2); + + // grey texture + initTexture2DWithUuid("grey-texture", GREY_IMAGE_RGBA_DATA_2X2, sizeof(GREY_IMAGE_RGBA_DATA_2X2), 2, 2); + + // white texture + initTexture2DWithUuid("white-texture", WHITE_IMAGE_RGBA_DATA_2X2, sizeof(WHITE_IMAGE_RGBA_DATA_2X2), 2, 2); + + // normal texture + initTexture2DWithUuid("normal-texture", NORMAL_IMAGE_RGBA_DATA_2X2, sizeof(NORMAL_IMAGE_RGBA_DATA_2X2), 2, 2); + + // default texture + initTexture2DWithUuid("default-texture", DEFAULT_IMAGE_RGBA_DATA_16X16, sizeof(DEFAULT_IMAGE_RGBA_DATA_16X16), 16, 16); + + // black cube texture + initTextureCubeWithUuid("black-cube-texture", BLACK_IMAGE_RGBA_DATA_2X2, sizeof(BLACK_IMAGE_RGBA_DATA_2X2), 2, 2); + + // grey cube texture + initTextureCubeWithUuid("grey-cube-texture", GREY_IMAGE_RGBA_DATA_2X2, sizeof(GREY_IMAGE_RGBA_DATA_2X2), 2, 2); + + // white cube texture + initTextureCubeWithUuid("white-cube-texture", WHITE_IMAGE_RGBA_DATA_2X2, sizeof(WHITE_IMAGE_RGBA_DATA_2X2), 2, 2); + + // default cube texture + initTextureCubeWithUuid("default-cube-texture", DEFAULT_IMAGE_RGBA_DATA_16X16, sizeof(DEFAULT_IMAGE_RGBA_DATA_16X16), 16, 16); + + //cjh TODO: if (SpriteFrame) { + // const spriteFrame = ccnew SpriteFrame() as SpriteFrame; + // const image = imgAsset; + // const texture = ccnew Texture2D(); + // texture.image = image; + // spriteFrame.texture = texture; + // spriteFrame._uuid = 'default-spriteframe"; + // resources[spriteFrame->getUuid()] = spriteFrame; + // } + // + // + // return Promise.resolve().then(() => { + + // initMaterials(); + + return true; +} + +void BuiltinResMgr::initTexture2DWithUuid(const ccstd::string &uuid, const uint8_t *data, size_t dataBytes, uint32_t width, uint32_t height) { + IMemoryImageSource imageSource; + imageSource.width = width; + imageSource.height = height; + imageSource.data = ccnew ArrayBuffer(data, static_cast(dataBytes)); + imageSource.compressed = false; + imageSource.format = PixelFormat::RGBA8888; + + auto *texture = ccnew Texture2D(); + if (texture) { + texture->setUuid(uuid); + + auto *imgAsset = ccnew ImageAsset(); + imgAsset->setNativeAsset(imageSource); + texture->setImage(imgAsset); + + texture->initialize(); + _resources.emplace(texture->getUuid(), texture); + } +} + +void BuiltinResMgr::initTextureCubeWithUuid(const ccstd::string &uuid, const uint8_t *data, size_t dataBytes, uint32_t width, uint32_t height) { + IMemoryImageSource imageSource; + imageSource.width = width; + imageSource.height = height; + imageSource.data = ccnew ArrayBuffer(data, static_cast(dataBytes)); + imageSource.compressed = false; + imageSource.format = PixelFormat::RGBA8888; + + auto *texture = ccnew TextureCube(); + if (texture) { + texture->setUuid(uuid); + texture->setMipFilter(TextureCube::Filter::NEAREST); + + ITextureCubeMipmap mipmap; + mipmap.front = ccnew ImageAsset(); + mipmap.front->setNativeAsset(imageSource); + mipmap.back = ccnew ImageAsset(); + mipmap.back->setNativeAsset(imageSource); + mipmap.left = ccnew ImageAsset(); + mipmap.left->setNativeAsset(imageSource); + mipmap.right = ccnew ImageAsset(); + mipmap.right->setNativeAsset(imageSource); + mipmap.top = ccnew ImageAsset(); + mipmap.top->setNativeAsset(imageSource); + mipmap.bottom = ccnew ImageAsset(); + mipmap.bottom->setNativeAsset(imageSource); + + texture->setImage(&mipmap); + + texture->initialize(); + _resources.emplace(texture->getUuid(), texture); + } +} + +} // namespace cc diff --git a/cocos/core/builtin/BuiltinResMgr.h b/cocos/core/builtin/BuiltinResMgr.h new file mode 100644 index 0000000..c9f0abe --- /dev/null +++ b/cocos/core/builtin/BuiltinResMgr.h @@ -0,0 +1,71 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/Ptr.h" +#include "base/RefCounted.h" +#include "core/assets/Asset.h" + +namespace cc { + +namespace gfx { +class Device; +} + +class Material; +class Asset; + +class BuiltinResMgr final { +public: + static BuiltinResMgr *getInstance(); + + BuiltinResMgr(); + ~BuiltinResMgr(); + + bool initBuiltinRes(); + inline bool isInitialized() const { return _isInitialized; } + + void addAsset(const ccstd::string &uuid, Asset *asset); + Asset *getAsset(const ccstd::string &uuid); + + template ::value>> + T *get(const ccstd::string &uuid) { + return static_cast(getAsset(uuid)); + } + +private: + void initTexture2DWithUuid(const ccstd::string &uuid, const uint8_t *data, size_t dataBytes, uint32_t width, uint32_t height); + void initTextureCubeWithUuid(const ccstd::string &uuid, const uint8_t *data, size_t dataBytes, uint32_t width, uint32_t height); + + static BuiltinResMgr *instance; + + ccstd::unordered_map> _resources; + bool _isInitialized{false}; + + CC_DISALLOW_COPY_MOVE_ASSIGN(BuiltinResMgr); +}; + +} // namespace cc diff --git a/cocos/core/builtin/DebugInfos.cpp b/cocos/core/builtin/DebugInfos.cpp new file mode 100644 index 0000000..d83f1db --- /dev/null +++ b/cocos/core/builtin/DebugInfos.cpp @@ -0,0 +1,493 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +// clang-format off + +#include "DebugInfos.h" + +namespace cc { + +ccstd::unordered_map debugInfos = { +{ 0100, "%s not yet implemented." }, +{ 0200, "You should specify a valid DOM canvas element." }, +{ 1006, "[Action step]. override me" }, +{ 1007, "[Action update]. override me" }, +{ 1008, "[Action reverse]. override me" }, +{ 1100, "Expected 'data' dict, but not found. Config file: %s" }, +{ 1101, "Please load the resource first : %s" }, +{ 1102, "Effect settings not found, effects will not be imported." }, +{ 1103, "Success to load scene: %s" }, +{ 1200, "cocos2d: Director: Error in gettimeofday" }, +{ 1204, "Running scene should not be null" }, +{ 1205, "The scene should not be null" }, +{ 1206, "loadScene: The scene index to load (%s) is out of range." }, +{ 1207, "loadScene: Unknown name type to load: '%s'" }, +{ 1208, "loadScene: Failed to load scene '%s' because '%s' is already being loaded." }, +{ 1209, "loadScene: Can not load the scene '%s' because it was not in the build settings before playing." }, +{ 1210, "Failed to preload '%s', %s" }, +{ 1211, "loadScene: The scene index to load (%s) is out of range." }, +{ 1212, "loadScene: Unknown name type to load: '%s'" }, +{ 1213, "loadScene: Failed to load scene '%s' because '%s' is already loading" }, +{ 1214, "loadScene: Can not load the scene '%s' because it was not in the build settings before playing." }, +{ 1215, "Failed to preload '%s', %s" }, +{ 1216, "Director.runSceneImmediate: scene is not valid" }, +{ 1217, "Director._initOnEngineInitialized: renderer root initialization failed" }, +{ 1218, "Forward render pipeline initialized." }, +{ 1219, "Deferred render pipeline initialized. Note that non-transparent materials with no lighting will not be rendered, such as builtin-unlit." }, +{ 1220, "Failed to set shading scale, pipelineSceneData is invalid." }, +{ 1221, "Setting orientation is not supported yet." }, +{ 1300, "%s is not in the model pool and cannot be destroyed by destroyModel." }, +{ 1400, "'%s' is deprecated, please use '%s' instead." }, +{ 1404, "cc.spriteFrameCache is removed, please use cc.loader to load and cache sprite frames of atlas format." }, +{ 1406, "'%s.%s' is removed" }, +{ 1408, "'%s' is removed" }, +{ 1409, "element type is wrong!" }, +{ 1502, "cc.scheduler.scheduleCallbackForTarget(): target should be non-null." }, +{ 1503, "cc.Scheduler.pauseTarget():target should be non-null" }, +{ 1504, "cc.Scheduler.resumeTarget():target should be non-null" }, +{ 1505, "cc.Scheduler.isTargetPaused():target should be non-null" }, +{ 1506, "warning: you CANNOT change update priority in scheduled function" }, +{ 1507, "scheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f" }, +{ 1508, "Argument callback must not be empty" }, +{ 1509, "Argument target must be non-nullptr" }, +{ 1510, "cc.Scheduler: Illegal target which doesn't have id, you should do Scheduler.enableForTarget(target) before all scheduler API usage on target" }, +{ 1511, "cc.Scheduler: pause state of the scheduled task doesn't match the element pause state in Scheduler, the given paused state will be ignored." }, +{ 1513, "cc.Scheduler: scheduler stopped using `__instanceId` as id since v2.0, you should do Scheduler.enableForTarget(target) before all scheduler API usage on target" }, +{ 1514, "since v3.8.0, `Scheduler.schedule(target, callback, interval)` is deprecated, please use `Scheduler.schedule(callback, target, interval)` instead." }, +{ 1607, "removeFromParentAndCleanup is deprecated. Use removeFromParent instead" }, +{ 1619, "callback function must be non-null" }, +{ 1620, "interval must be positive" }, +{ 1623, "Set '%s' to normal node (not persist root node)." }, +{ 1624, "Replacing with the same sgNode" }, +{ 1625, "The replacement sgNode should not contain any child." }, +{ 1626, "Should not set alpha via 'color', set 'opacity' please." }, +{ 1627, "Not support for asynchronous creating node in SG" }, +{ 1632, "Node name can not include '/'." }, +{ 1633, "Internal error, should not remove unknown node from parent." }, +{ 1635, "reorderChild: this child is not in children list." }, +{ 1636, "Node's zIndex value can't be greater than cc.macro.MAX_ZINDEX, setting to the maximum value" }, +{ 1637, "Node's zIndex value can't be smaller than cc.macro.MIN_ZINDEX, setting to the minimum value" }, +{ 1638, "Private node's zIndex can't be set, it will keep cc.macro.MIN_ZINDEX as its value" }, +{ 1800, "cc._EventListenerKeyboard.checkAvailable(): Invalid EventListenerKeyboard!" }, +{ 1801, "cc._EventListenerTouchOneByOne.checkAvailable(): Invalid EventListenerTouchOneByOne!" }, +{ 1802, "cc._EventListenerTouchAllAtOnce.checkAvailable(): Invalid EventListenerTouchAllAtOnce!" }, +{ 1803, "cc._EventListenerAcceleration.checkAvailable():_onAccelerationEvent must be non-nil" }, +{ 1900, "Invalid parameter." }, +{ 2104, "Layer collision. The name of layer (%s) is collided with the name or value of some layer" }, +{ 2200, "Design resolution not valid" }, +{ 2201, "should set resolutionPolicy" }, +{ 2300, "The touches is more than MAX_TOUCHES, nUnusedIndex = %s" }, +{ 2402, "Forward pipeline startup failed!" }, +{ 3103, "cc.Texture.addImage(): path should be non-null" }, +{ 3119, "Lazy init texture with image element failed due to image loading failure: %s" }, +{ 3120, "Loading texture with unsupported type: '%s'. Add '%s' into 'cc.macro.SUPPORT_TEXTURE_FORMATS' please." }, +{ 3121, "Can't find a texture format supported by the current platform! Please add a fallback format in the editor." }, +{ 3122, "Error Texture in %s." }, +{ 3123, "Set same texture %s." }, +{ 3124, "Texture: setMipRange failed because base level is larger than max level" }, +{ 3300, "Rect width exceeds maximum margin: %s" }, +{ 3301, "Rect height exceeds maximum margin: %s" }, +{ 3500, "0 priority is forbidden for fixed priority since it's used for scene graph based priority." }, +{ 3501, "Invalid listener type!" }, +{ 3502, "Can't set fixed priority with scene graph based listener." }, +{ 3503, "Invalid parameters." }, +{ 3504, "listener must be a cc.EventListener object when adding a fixed priority listener" }, +{ 3505, "The listener has been registered, please don't register it again." }, +{ 3506, "Unsupported listener target." }, +{ 3507, "Invalid scene graph priority!" }, +{ 3508, "If program goes here, there should be event in dispatch." }, +{ 3509, "_inDispatch should be 1 here." }, +{ 3510, "%s's scene graph node not contains in the parent's children" }, +{ 3511, "event is undefined" }, +{ 3512, "Event manager only support scene graph priority for ui nodes which contain UIComponent" }, +{ 3520, "Device Motion Event request permission: %s" }, +{ 3521, "Device Motion Event request permission failed: %s" }, +{ 3601, "The editor property 'playOnFocus' should be used with 'executeInEditMode' in class '%s'" }, +{ 3602, "Unknown editor property '%s' in class '%s'." }, +{ 3603, "Use 'cc.Float' or 'cc.Integer' instead of 'cc.Number' please." }, +{ 3604, "Can only indicate one type attribute for %s." }, +{ 3605, "The default value of %s is not instance of %s." }, +{ 3606, "No needs to indicate the '%s' attribute for %s, which its default value is type of %s." }, +{ 3607, "The default value of %s must be an empty string." }, +{ 3608, "The type of %s must be CCString, not String." }, +{ 3609, "The type of %s must be CCBoolean, not Boolean." }, +{ 3610, "The type of %s must be CCFloat or CCInteger, not Number." }, +{ 3611, "Can not indicate the '%s' attribute for %s, which its default value is type of %s." }, +{ 3612, "%s Just set the default value to 'new %s()' and it will be handled properly." }, +{ 3613, "'No need to use 'serializable: false' or 'editorOnly: true' for the getter of '%s.%s', every getter is actually non-serialized." }, +{ 3614, "Should not define constructor for cc.Component %s." }, +{ 3615, "Each script can have at most one Component." }, +{ 3616, "Should not specify class name %s for Component which defines in project." }, +{ 3618, "ctor of '%s' can not be another CCClass" }, +{ 3623, "Can not use 'editor' attribute, '%s' not inherits from Components." }, +{ 3625, "[isChildClassOf] superclass should be function type, not" }, +{ 3626, "Can't remove '%s' because '%s' depends on it." }, +{ 3627, "Should not add renderer component (%s) to a Canvas node." }, +{ 3628, "Should not add %s to a node which size is already used by its other component." }, +{ 3633, "Properties function of '%s' should return an object!" }, +{ 3634, "Disallow to use '.' in property name" }, +{ 3637, "Can not declare %s.%s, it is already defined in the prototype of %s" }, +{ 3639, "Can not apply the specified attribute to the getter of '%s.%s', attribute index: %s" }, +{ 3640, "'%s': the setter of '%s' is already defined!" }, +{ 3641, "Can not construct %s because it contains object property." }, +{ 3644, "Please define 'type' parameter of %s.%s as the actual constructor." }, +{ 3645, "Please define 'type' parameter of %s.%s as the constructor of %s." }, +{ 3646, "Unknown 'type' parameter of %s.%s:%s" }, +{ 3647, "The length of range array must be equal or greater than 2" }, +{ 3648, "Can not declare %s.%s method, it is already defined in the properties of %s." }, +{ 3652, "Failed to `new %s()` under the hood, %s\nIt is used for getting default values declared in TypeScript in the first place.\nPlease ensure the constructor can be called during the script's initialization." }, +{ 3653, "Please do not specifiy 'default' attribute in decorator of '%s' property in '%s' class.\nDefault value must be initialized at their declaration:\n\n \n// Before:\n@property({\n type: cc.SpriteFrame\n default: null // <--\n})\nmyProp;\n// After:\n@property({\n type: cc.SpriteFrame\n})\nmyProp = null; // <--" }, +{ 3654, "Please specifiy a default value for '%s.%s' property at its declaration:\n\n \n// Before:\n@property(...)\nmyProp;\n// After:\n@property(...)\nmyProp = 0;" }, +{ 3655, "Can not specifiy 'get' or 'set' attribute in decorator for '%s' property in '%s' class.\nPlease use:\n\n \n@property(...)\nget %s () {\n ...\n}\n@property\nset %s (value) {\n ...\n}" }, +{ 3659, "Violation error: extending enumerations shall have non-overlaped member names or member values" }, +{ 3660, "You are explicitly specifying `undefined` type to cc property '%s' of cc class '%s'.\nIs this intended? If not, this may indicate a circular reference.\nFor example:\n\n \n// foo.ts\nimport { _decorator } from 'cc';\nimport { Bar } from './bar'; // Given that './bar' also reference 'foo.ts'.\n // When importing './bar', execution of './bar' is hung on to wait execution of 'foo.ts',\n // the `Bar` imported here is `undefined` until './bar' finish its execution.\n // It leads to that\n@_decorator.ccclass // ↓\nexport class Foo { // ↓\n @_decorator.type(Bar) // → is equivalent to `@_decorator.type(undefined)`\n public bar: Bar; // To eliminate this error, either:\n // - Refactor your module structure(recommended), or\n // - specify the type as cc class name: `@_decorator.type('Bar'/* or any name you specified for `Bar` */)`\n}" }, +{ 3700, "internal error: _prefab is undefined" }, +{ 3701, "Failed to load prefab asset for node '%s'" }, +{ 3800, "The target can not be made persist because it's not a cc.Node or it doesn't have _id property." }, +{ 3801, "The node can not be made persist because it's not under root node." }, +{ 3802, "The node can not be made persist because it's not in current scene." }, +{ 3803, "The target can not be made persist because it's not a cc.Node or it doesn't have _id property." }, +{ 3804, "getComponent: Type must be non-nil" }, +{ 3805, "Can't add component '%s' because %s already contains the same component." }, +{ 3806, "Can't add component '%s' to %s because it conflicts with the existing '%s' derived component." }, +{ 3807, "addComponent: Failed to get class '%s'" }, +{ 3808, "addComponent: Should not add component ('%s') when the scripts are still loading." }, +{ 3809, "addComponent: The component to add must be a constructor" }, +{ 3810, "addComponent: The component to add must be child class of cc.Component" }, +{ 3811, "_addComponentAt: The component to add must be a constructor" }, +{ 3812, "_addComponentAt: Index out of range" }, +{ 3813, "removeComponent: Component must be non-nil" }, +{ 3814, "Argument must be non-nil" }, +{ 3815, "Component not owned by this entity" }, +{ 3816, "Node '%s' is already activating" }, +{ 3817, "Sorry, the component of '%s' which with an index of %s is corrupted! It has been removed." }, +{ 3818, "Failed to read or parse project.json" }, +{ 3819, "Warning: target element is not a DIV or CANVAS" }, +{ 3820, "The renderer doesn't support the renderMode %s" }, +{ 3821, "Cannot change hierarchy while activating or deactivating the parent." }, +{ 3822, "addComponent: Cannot add any component to the scene." }, +{ 3823, "The enabled component (id: %s, name: %s) doesn't have a valid node" }, +{ 3900, "Invalid clip to add" }, +{ 3901, "Invalid clip to remove" }, +{ 3902, "clip is defaultClip, set force to true to force remove clip and animation state" }, +{ 3903, "animation state is playing, set force to true to force stop and remove clip and animation state" }, +{ 3904, "motion path of target [%s] in prop [%s] frame [%s] is not valid" }, +{ 3905, "sprite frames must be an Array." }, +{ 3906, "Can't find easing type [%s]" }, +{ 3907, "Animation state is not playing or already removed" }, +{ 3912, "already-playing" }, +{ 3920, "Current context does not allow root motion." }, +{ 3921, "You provided a ill-formed track path. The last component of track path should be property key, or the setter should not be empty." }, +{ 3923, "Root motion is ignored since root bone could not be located in animation." }, +{ 3924, "Root motion is ignored since the root bone could not be located in scene." }, +{ 3925, "Target of hierarchy path should be of type Node." }, +{ 3926, "Node '%s' has no path '%s'." }, +{ 3927, "Target of component path should be of type Node." }, +{ 3928, "Node '%s' has no component '%s'." }, +{ 3929, "Target object has no property '%s'." }, +{ 3930, "Can not decide type for untyped track: runtime binding does not provide a getter." }, +{ 3931, "Can not decide type for untyped track: got a unsupported value from runtime binding." }, +{ 3932, "Common targets should only target Vectors/`Size`/`Color`." }, +{ 3933, "Each curve that has common target should be numeric curve and targets string property." }, +{ 3934, "Misconfigured legacy curve: the first keyframe value is number but others aren't." }, +{ 3935, "We don't currently support conversion of `CubicSplineQuatValue`." }, +{ 3936, "Instancing/Batching enabled for non-baked skinning model '%s', this may result in unexpected rendering artifacts. Consider turning it off in the material if you do not intend to do this." }, +{ 3937, "Previous error occurred when instantiating animation clip %s on node %s." }, +{ 3938, "'%s' is not found from '%s'. It's specified as the root node to play animation clip '%s'." }, +{ 3940, "Error when animation attempted to bind material uniform target: target %s is not a material." }, +{ 3941, "Error when animation attempted to bind material uniform target: material %s has no recorded pass %s." }, +{ 3942, "Error when animation attempted to bind material uniform target: material %s at pass %s has no recorded uniform %s." }, +{ 3943, "Error when animation attempted to bind material uniform target: material %s at pass %s's uniform %s has no recorded channel %s." }, +{ 4003, "Label font size can't be shirnked less than 0!" }, +{ 4004, "force notify all fonts loaded!" }, +{ 4011, "Property spriteFrame of Font '%s' is invalid. Using system font instead." }, +{ 4012, "The texture of Font '%s' must be already loaded on JSB. Using system font instead." }, +{ 4013, "Sorry, lineHeight of system font not supported on JSB." }, +{ 4200, "MaskType: IMAGE_STENCIL only support WebGL mode." }, +{ 4201, "The alphaThreshold invalid in Canvas Mode." }, +{ 4202, "The inverted invalid in Canvas Mode." }, +{ 4300, "Can not found the %s page." }, +{ 4301, "Can not add a page without UITransform." }, +{ 4302, "Can not set the scroll view content when it hasn't UITransform or its parent hasn't UITransform." }, +{ 4303, "The %s scrollBar on the '%s' node is not available, please check it." }, +{ 4400, "Invalid RichText img tag! The sprite frame name can't be found in the ImageAtlas!" }, +{ 4500, "Graphics: There is no model in %s." }, +{ 4600, "Script attached to '%s' is missing or invalid." }, +{ 4601, "Failed to load wasm module, WebAssembly is not supported on this platform, but as a fallback Asm.js module is culled by mistake." }, +{ 4700, "The dom control is not created!" }, +{ 4800, "unknown asset type" }, +{ 4901, "loadRes: should not specify the extname in %s %s" }, +{ 4902, "No need to release non-cached asset." }, +{ 4914, "Resources url '%s' does not exist." }, +{ 4915, "Pack indices and data do not match in size" }, +{ 4916, "Failed to download package for %s" }, +{ 4921, "Invalid pipe or invalid index provided!" }, +{ 4922, "The pipe to be inserted is already in the pipeline!" }, +{ 4923, "Uuid Loader: Parse asset [ %s ] failed : %s" }, +{ 4924, "JSON Loader: Input item doesn't contain string content" }, +{ 4925, "Uuid Loader: Deserialize asset [ %s ] failed : %s" }, +{ 4926, "Audio Downloader: no web audio context." }, +{ 4927, "Audio Downloader: audio not supported on this browser!" }, +{ 4928, "Load %s failed!" }, +{ 4929, "Load Webp ( %s ) failed" }, +{ 4930, "Load image ( %s ) failed" }, +{ 4932, "Since v1.10, for any atlas ('%s') in the 'resources' directory, it is not possible to find the contained SpriteFrames via `loadRes`, `getRes` or `releaseRes`. Load the SpriteAtlas first and then use `spriteAtlas.getSpriteFrame(name)` instead please." }, +{ 4933, "Download Font [ %s ] failed, using Arial or system default font instead" }, +{ 4934, "Please assure that the full path of sub asset is correct!" }, +{ 4935, "Failed to skip prefab asset while deserializing PrefabInfo" }, +{ 5000, "You are trying to destroy a object twice or more." }, +{ 5001, "object not yet destroyed" }, +{ 5100, "Not a plist file!" }, +{ 5200, "Warning: localStorage isn't enabled. Please confirm browser cookie or privacy option" }, +{ 5201, "browser don't support web audio" }, +{ 5202, "This feature supports WebGL render mode only." }, +{ 5300, "Type of target to deserialize not matched with data: target is %s, data is %s" }, +{ 5301, "Can not find script '%s'" }, +{ 5302, "Can not find class '%s'" }, +{ 5303, "Failed to deserialize %s, missing _deserialize function." }, +{ 5304, "Unable to deserialize version %s data." }, +{ 5402, "cc.js.addon called on non-object:" }, +{ 5403, "cc.js.mixin: arguments must be type object:" }, +{ 5404, "The base class to extend from must be non-nil" }, +{ 5405, "The class to extend must be non-nil" }, +{ 5406, "Class should be extended before assigning any prototype members." }, +{ 5500, "'notify' can not be used in 'get/set' !" }, +{ 5501, "'notify' must be used with 'default' !" }, +{ 5507, "The 'default' attribute of '%s.%s' must be an array" }, +{ 5508, "Invalid type of %s.%s" }, +{ 5510, "The 'type' attribute of '%s.%s' can not be 'Number', use cc.Float or cc.Integer instead please." }, +{ 5511, "The 'type' attribute of '%s.%s' is undefined when loading script" }, +{ 5512, "Can not serialize '%s.%s' because the specified type is anonymous, please provide a class name or set the 'serializable' attribute of '%s.%s' to 'false'." }, +{ 5513, "The 'default' value of '%s.%s' should not be used with a 'get' function." }, +{ 5514, "The 'default' value of '%s.%s' should not be used with a 'set' function." }, +{ 5515, "The 'default' value of '%s.%s' can not be an constructor. Set default to null please." }, +{ 5517, "'%s.%s' hides inherited property '%s.%s'. To make the current property override that implementation, add the `override: true` attribute please." }, +{ 5601, "Can not get current scene." }, +{ 5602, "Scene is destroyed" }, +{ 5603, "reference node is destroyed" }, +{ 5700, "no %s or %s on %s" }, +{ 5800, "%s.lerp not yet implemented." }, +{ 5801, "%s.clone not yet implemented." }, +{ 5802, "%s.equals not yet implemented." }, +{ 5900, "MotionStreak only support WebGL mode." }, +{ 5901, "cc.MotionStreak.getOpacity has not been supported." }, +{ 5902, "cc.MotionStreak.setOpacity has not been supported." }, +{ 6000, "Custom should not be false if file is not specified." }, +{ 6001, "The new %s must not be NaN" }, +{ 6017, "Incomplete or corrupt PNG file" }, +{ 6018, "Invalid filter algorithm: %s" }, +{ 6019, "Invalid byte order value." }, +{ 6020, "You forgot your towel!" }, +{ 6021, "Unknown Field Tag: %s" }, +{ 6022, "Too many bits requested" }, +{ 6023, "No bits requested" }, +{ 6024, "Cannot recover from missing StripByteCounts" }, +{ 6025, "Cannot handle sub-byte bits per sample" }, +{ 6026, "Cannot handle sub-byte bits per pixel" }, +{ 6027, "Palette image missing color map" }, +{ 6028, "Unknown Photometric Interpretation: %s" }, +{ 6029, "Unkown error" }, +{ 6030, "cc.ParticleSystem: error decoding or ungzipping textureImageData" }, +{ 6031, "cc.ParticleSystem: unknown image format with Data" }, +{ 6032, "cc.ParticleSystem.initWithDictionary() : error loading the texture" }, +{ 6033, "cc.ParticleSystem: not allowing create to be invoked twice with different particle system" }, +{ 6034, "cc.ParticleSystem: shouldn't be initialized repetitively, otherwise there will be potential leak" }, +{ 6035, "cc.ParticleSystem: change material failed, please use proper particle material" }, +{ 6036, "cc.ParticleSystem: life time should bigger than 1 or buffer will be insufficient" }, +{ 6400, "asset.url is not usable in core process" }, +{ 6402, "AssetLibrary has already been initialized!" }, +{ 6500, "Widget target must be one of the parent nodes of it" }, +{ 6600, "collider not added or already removed" }, +{ 6601, "Can't find testFunc for (%s, $s)." }, +{ 6700, "Can't init canvas '%s' because it conflicts with the existing '%s', the scene should only have one active canvas at the same time." }, +{ 6705, "Argument must be non-nil" }, +{ 6706, "Priority can't be set in RenderRoot2D node" }, +{ 6800, "Callback of event must be non-nil" }, +{ 6801, "The message must be provided" }, +{ 6900, "The thing you want to instantiate must be an object" }, +{ 6901, "The thing you want to instantiate is nil" }, +{ 6902, "The thing you want to instantiate is destroyed" }, +{ 6903, "The instantiate method for given asset do not implemented" }, +{ 6904, "Can not instantiate array" }, +{ 6905, "Can not instantiate DOM element" }, +{ 7100, "%s already defined in Enum." }, +{ 7101, "Sorry, 'cc.Enum' not available on this platform, please report this error here: " }, +{ 7200, "Method 'initWithTMXFile' is no effect now, please set property 'tmxAsset' instead." }, +{ 7201, "Method 'initWithXML' is no effect now, please set property 'tmxAsset' instead." }, +{ 7202, "Add component TiledLayer into node failed." }, +{ 7203, "Property 'mapLoaded' is unused now. Please write the logic to the callback 'start'." }, +{ 7210, "TMX Hexa zOrder not supported" }, +{ 7211, "TMX invalid value" }, +{ 7215, "cocos2d: Warning: TMX Layer %s has no tiles" }, +{ 7216, "cocos2d: TMXFormat: Unsupported TMX version: %s" }, +{ 7217, "cocos2d: TMXFomat: Unsupported orientation: %s" }, +{ 7218, "cc.TMXMapInfo.parseXMLFile(): unsupported compression method" }, +{ 7219, "cc.TMXMapInfo.parseXMLFile(): Only base64 and/or gzip/zlib maps are supported" }, +{ 7221, "cc.TMXMapInfo.parseXMLFile(): Texture '%s' not found." }, +{ 7222, "Parse %s failed." }, +{ 7236, "cc.TMXLayer.getTileAt(): TMXLayer: the tiles map has been released" }, +{ 7237, "cc.TMXLayer.getTileGIDAt(): TMXLayer: the tiles map has been released" }, +{ 7238, "cc.TMXLayer.setTileGID(): TMXLayer: the tiles map has been released" }, +{ 7239, "cc.TMXLayer.setTileGID(): invalid gid: %s" }, +{ 7240, "cc.TMXLayer.getTileFlagsAt(): TMXLayer: the tiles map has been released" }, +{ 7241, "cc.TiledMap.initWithXML(): Map not found. Please check the filename." }, +{ 7401, "Failed to set _defaultArmatureIndex for '%s' because the index is out of range." }, +{ 7402, "Failed to set _animationIndex for '%s' because the index is out of range." }, +{ 7501, "Failed to set _defaultSkinIndex for '%s' because the index is out of range." }, +{ 7502, "Failed to set _animationIndex for '%s' because its skeletonData is invalid." }, +{ 7503, "Failed to set _animationIndex for '%s' because the index is out of range." }, +{ 7504, "Can not render dynamic created SkeletonData" }, +{ 7506, "Failed to load spine atlas '$s'" }, +{ 7507, "Please re-import '%s' because its textures is not initialized! (This workflow will be improved in the future.)" }, +{ 7508, "The atlas asset of '%s' is not exists!" }, +{ 7509, "Spine: Animation not found: %s" }, +{ 7510, "Spine: Animation not found: %s" }, +{ 7511, "Spine: Invalid input!" }, +{ 7600, "The context of RenderTexture is invalid." }, +{ 7601, "cc.RenderTexture._initWithWidthAndHeightForWebGL() : only RGB and RGBA formats are valid for a render texture;" }, +{ 7602, "Could not attach texture to the framebuffer" }, +{ 7603, "clearDepth isn't supported on Cocos2d-Html5" }, +{ 7604, "saveToFile isn't supported on Cocos2d-Html5" }, +{ 7605, "newCCImage isn't supported on Cocos2d-Html5" }, +{ 7606, "GFXTexture is null" }, +{ 7607, "readPixels buffer size smaller than %d" }, +{ 7700, "On the web is always keep the aspect ratio" }, +{ 7701, "Can't know status" }, +{ 7702, "Video player's duration is not ready to get now!" }, +{ 7703, "Video Downloader: video not supported on this browser!" }, +{ 7800, "Web does not support loading" }, +{ 7801, "Web does not support query history" }, +{ 7802, "Web does not support query history" }, +{ 7803, "The current browser does not support the GoBack" }, +{ 7804, "The current browser does not support the GoForward" }, +{ 7805, "Web does not support zoom" }, +{ 7900, "cc.math.Matrix3.assign(): current matrix equals matIn" }, +{ 7901, "cc.math.mat4Assign(): pOut equals pIn" }, +{ 7902, "cc.mat.Matrix4.assignFrom(): mat4 equals current matrix" }, +{ 7903, "cc.math.Matrix4 equal: pMat1 and pMat2 are same object." }, +{ 7904, "cc.math.Matrix4.extractPlane: Invalid plane index" }, +{ 7905, "cc.math.mat4Assign(): pOut equals pIn" }, +{ 7906, "cc.mat.Matrix4.assignFrom(): mat4 equals current matrix" }, +{ 7907, "cc.math.Matrix4 equals: pMat1 and pMat2 are same object." }, +{ 7908, "Invalid matrix mode specified" }, +{ 7909, "current quaternion is an invalid value" }, +{ 8000, "Can't handle this field type or size" }, +{ 8001, "No bytes requested" }, +{ 8002, "Too many bytes requested" }, +{ 8003, "Missing StripByteCounts!" }, +{ 8100, "cocos2d: ERROR: Failed to compile shader:\n %s" }, +{ 8101, "cocos2d: ERROR: Failed to compile vertex shader" }, +{ 8102, "cocos2d: ERROR: Failed to compile fragment shader" }, +{ 8103, "cc.GLProgram.link(): Cannot link invalid program" }, +{ 8104, "cocos2d: ERROR: Failed to link program: %s" }, +{ 8105, "cocos2d: cc.shaderCache._loadDefaultShader, error shader type" }, +{ 8106, "Please load the resource firset : %s" }, +{ 8107, "cc.GLProgram.getUniformLocationForName(): uniform name should be non-null" }, +{ 8108, "cc.GLProgram.getUniformLocationForName(): Invalid operation. Cannot get uniform location when program is not initialized" }, +{ 8109, "modelView matrix is undefined." }, +{ 8200, "Please set node's active instead of rigidbody's enabled." }, +{ 8300, "Should only one camera exists, please check your project." }, +{ 8301, "Camera does not support Canvas Mode." }, +{ 8302, "Camera.viewport is deprecated, please use setViewportInOrientedSpace instead." }, +{ 8400, "Wrong type arguments, 'filePath' must be a String." }, +{ 9000, "Stencil manager does not support level bigger than %d in this device." }, +{ 9001, "Stencil manager is already empty, cannot pop any mask" }, +{ 9002, "Failed to request any buffer from a mesh buffer without accessor" }, +{ 9003, "The internal state of LinearBufferAccessor have severe issue and irreversible, please check the reason" }, +{ 9004, "Failed to allocate chunk in StaticVBAccessor, the requested buffer might be too large: %d bytes" }, +{ 9005, "BATCHER2D_MEM_INCREMENT is too large, the Max value for BATCHER2D_MEM_INCREMENT is 2303KB (smaller than 65536 *9* 4 / 1024 = 2304KB)" }, +{ 9006, "QuadRenderData is removed, please use MeshRenderData instead." }, +{ 9007, "Since v3.6, Because mask changes the inheritance relationship, you can directly manipulate the rendering components under the same node to complete the operation." }, +{ 9100, "texture size exceeds current device limits %d/%d" }, +{ 9101, "The length of the TypedArrayBuffer must be an integer." }, +{ 9201, "Cannot access game frame or container." }, +{ 9202, "Setting window size is not supported." }, +{ 9300, "The current buffer beyond the limit in ui static component, please reduce the amount" }, +{ 9301, "The UI has not been initialized" }, +{ 9302, "Can't getGFXSampler with out device" }, +{ 9600, "[Physics]: please check to see if physics modules are included" }, +{ 9610, "[Physics]: cannon.js physics system doesn't support capsule collider" }, +{ 9611, "[Physics]: builtin physics system doesn't support mesh collider" }, +{ 9612, "[Physics]: builtin physics system doesn't support cylinder collider" }, +{ 9613, "[Physics]: cannon.js physics system doesn't support hinge drive and angular limit" }, +{ 9620, "[Physics][Ammo]: changing the mesh is not supported after the initialization is completed" }, +{ 9630, "[Physics]: A dynamic rigid body can not have the following collider shapes: Terrain, Plane and Non-convex Mesh. Node name: %s" }, +{ 9640, "[Physics][builtin]: sweep functions are not supported in builtin" }, +{ 9641, "[Physics][cannon.js]: sweep functions are not supported in cannon.js" }, +{ 10001, "The sub-mesh contains %d vertices, which beyonds the capability (%d vertices most) of renderer of your platform." }, +{ 10002, "Sub-mesh may include at most %d morph targets, but you specified %d." }, +{ 11000, "WebGL context lost." }, +{ 12001, "BlendFactors are disabled when using custom material, please modify the blend state in the material instead." }, +{ 12002, "Can't add renderable component to this node because it already have one." }, +{ 12004, "SubModel can only support %d passes." }, +{ 12005, "Material already initialized, request aborted." }, +{ 12006, "Pass already destroyed." }, +{ 12007, "This is old usage, please swap the parameters." }, +{ 12008, "GeometryRenderer: too many lines." }, +{ 12009, "GeometryRenderer: too many triangles." }, +{ 12010, "PassUtils: illegal uniform handle, accessing uniform at offset %d" }, +{ 12011, "Pass: setUniform is invoked with incompatible uniform data type for binding %d, expected type is %s" }, +{ 12012, "Can't set a material instance to a sharedMaterial slot" }, +{ 12100, "The font size is too big to be fitted into texture atlas. Please switch to other label cache modes or choose a smaller font size." }, +{ 12101, "The asset %s has been destroyed!" }, +{ 12102, "Base pass cannot override states, please use pass instance instead." }, +{ 12103, "Custom pipeline create shader %s failed. Please restart editor." }, +{ 12104, "Create shader %s failed." }, +{ 12105, "Pass resources incomplete." }, +{ 12106, "Cannot patch non-builtin macros." }, +{ 12107, "Custom pipeline invalid render pass, program: %s. Please restart editor." }, +{ 12108, "Custom pipeline invalid render phase, program: %s. Please restart editor." }, +{ 13100, "Incorrect CCON magic." }, +{ 13101, "Unknown CCON version number: %d." }, +{ 13102, "CCON Format error." }, +{ 13103, "Can not encode CCON binary: lack of text encoder." }, +{ 13104, "Can not decode CCON binary: lack of text decoder." }, +{ 14000, "State machine matched too many transitions(greater than %s) during this frame: %s." }, +{ 14100, "Pool.destroy no longer take a function as parameter, Please specify destruct function in the construction of Pool instead" }, +{ 14200, "Can not update a static mesh." }, +{ 14201, "The primitiveIndex is out of range." }, +{ 14202, "meshopt asm decoder initialized" }, +{ 14203, "meshopt wasm decoder initialized" }, +{ 14204, "meshopt decoder error: %d" }, +{ 14300, "Can not keep world transform due to the zero scaling of parent node" }, +{ 14400, "Spline error: less than 2 knots." }, +{ 14401, "Spline error: less than 4 knots or not a multiple of 4.\n\n" }, +{ 15000, "Can not find corresponding diffuse map for environment lighting, use hemisphere diffuse instead, change environment lighting type to regenerate diffuse map" }, +{ 15001, "Can not find environment map, disable IBL lighting" }, +{ 15002, "Diffuse map resource is missing, please change environment lighting type to regenerate resource" }, +{ 15003, "The shadow visible distance is so small that CSM stratification is not effective, Please change the value of shadowDistance so that it is 10 times greater than 0.1" }, +{ 15004, "The native folder may be generated from older versions, please refer https://docs.cocos.com/creator/manual/en/release-notes/ to upgrade." }, +{ 15100, "Camera '%s' clear flag is skybox, but skybox is disabled, may cause strange background effect, please set camera clear flag to solid color." }, +{ 16000, "'%s' is deprecated since v%s." }, +{ 16001, "'%s' is deprecated since v%s, please use '%s' instead." }, +{ 16002, "'%s' is removed since v%s." }, +{ 16003, "'%s' is removed since v%s, please use '%s' instead." }, +{ 16101, "The effect('%s') you are looking for does not exist, please confirm the effect name in the editor. NOTE: Since 3.6, the name of the built-in effect has been changed to its name in the editor, please check it out. More information please refer to https://docs.cocos.com/creator/manual/en/shader/effect-inspector.html" }, +{ 16201, "The asset replacing failed, can not found override asset('%s') for '%s'" }, +{ 16301, "node '%s' doesn't have any ModelRenderer component, this component will not work. please add ModelRenderer component first" }, +{ 16302, "There is no reflection probe in the scene or no probe is near the current object. No reflection probe will take effect on this object. Please create a new reflection probe or move existing ones closer." }, +{ 16303, "Skin material needs floating-point render target, please check ENABLE_FLOAT_OUTPUT define in Project Settings--Macro" }, +{ 16304, "Skin material may need more accurate calculations, please select a head model of standard size, check the isGlobalStandardSkinObject option in the MeshRender component." }, + +}; +}//namespace cc diff --git a/cocos/core/builtin/DebugInfos.cpp.in b/cocos/core/builtin/DebugInfos.cpp.in new file mode 100644 index 0000000..a7a99ac --- /dev/null +++ b/cocos/core/builtin/DebugInfos.cpp.in @@ -0,0 +1,33 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +// clang-format off + +#include "DebugInfos.h" + +namespace cc { + +ccstd::unordered_map debugInfos = { +${PLACE_HOLDER} +}; +}//namespace cc diff --git a/cocos/core/builtin/DebugInfos.h b/cocos/core/builtin/DebugInfos.h new file mode 100644 index 0000000..3b59ab0 --- /dev/null +++ b/cocos/core/builtin/DebugInfos.h @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" + +namespace cc { + +extern ccstd::unordered_map debugInfos; + +} diff --git a/cocos/core/data/JSBNativeDataHolder.h b/cocos/core/data/JSBNativeDataHolder.h new file mode 100644 index 0000000..60af291 --- /dev/null +++ b/cocos/core/data/JSBNativeDataHolder.h @@ -0,0 +1,53 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Utils.h" + +namespace cc { +class JSBNativeDataHolder final { +public: + JSBNativeDataHolder() = default; + explicit JSBNativeDataHolder(uint8_t* data) : _data(data){}; + + ~JSBNativeDataHolder() { + if (_data != nullptr) { + free(_data); // Remove data in destructor + } + } + + inline void setData(uint8_t* data) { _data = data; } + inline uint8_t* getData() const { return _data; } + + inline void destroy() { // Also support to invoke destroy method to free memory before garbage collection + free(_data); + _data = nullptr; + } + +private: + uint8_t* _data{nullptr}; +}; +} // namespace cc \ No newline at end of file diff --git a/cocos/core/data/Object.cpp b/cocos/core/data/Object.cpp new file mode 100644 index 0000000..50f5734 --- /dev/null +++ b/cocos/core/data/Object.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/data/Object.h" +#include "base/std/container/vector.h" +#include "core/platform/Debug.h" + +namespace cc { + +namespace { +ccstd::vector objectsToDestroy; +} + +/* static */ +void CCObject::deferredDestroy() { + if (objectsToDestroy.empty()) return; + auto deleteCount = static_cast(objectsToDestroy.size()); + for (size_t i = 0; i < deleteCount; ++i) { + CCObject *obj = objectsToDestroy[i]; + if (!(obj->_objFlags & Flags::DESTROYED)) { + obj->destroyImmediate(); + obj->release(); + } + } + // if we called b.destory() in a.onDestroy(), objectsToDestroy will be resized, + // but we only destroy the objects which called destory in this frame. + if (deleteCount == objectsToDestroy.size()) { + objectsToDestroy.clear(); + } else { + objectsToDestroy.erase(objectsToDestroy.begin(), objectsToDestroy.begin() + deleteCount); + } +} + +CCObject::CCObject(ccstd::string name /* = ""*/) +: _name(std::move(name)) { +} + +CCObject::~CCObject() = default; + +void CCObject::destruct() { + _name.clear(); +} + +bool CCObject::destroy() { + //NOTE: _objFlags will be set to TO_DESTROY when destroy method in TS is triggered. + // CCObject::destroy method will be invoked at the end. Refer to cocos/core/data/object.ts + /* + public destroy (): boolean { + if (this._objFlags & Destroyed) { + warnID(5000); + return false; + } + ... ... + + if (JSB) { + // @ts-ignore + // _destroy is a JSB method + this._destroy(); + } + + return true; + } + */ + + if (static_cast(_objFlags & Flags::TO_DESTROY)) { + //NOTE: Should not return false because _objFlags is already set to TO_DESTROY in TS. + // And Scene::destroy depends on the return value. Refer to: + /* + bool Scene::destroy() { + bool success = Super::destroy(); + if (success) { + for (auto &child : _children) { + child->setActive(false); + } + } + ...... + } + */ + return true; + } + + if (static_cast(_objFlags & Flags::DESTROYED)) { + debug::warnID(5000); + return false; + } + + _objFlags |= Flags::TO_DESTROY; + addRef(); + objectsToDestroy.emplace_back(this); + + //NOTE: EDITOR's deferredDestroyTimer trigger from ts + return true; +} + +void CCObject::destroyImmediate() { + if (static_cast(_objFlags & Flags::DESTROYED)) { + debug::errorID(5000); + return; + } + + onPreDestroy(); + + // NOTE: native has been use smart pointer, not needed to implement 'destruct' interface, remove 'destruct' reference code + + _objFlags |= Flags::DESTROYED; +} + +bool isObjectValid(CCObject *value, bool strictMode /* = false*/) { + if (value == nullptr) { + return false; + } + + return !(value->_objFlags & (strictMode ? (CCObject::Flags::DESTROYED | CCObject::Flags::TO_DESTROY) : CCObject::Flags::DESTROYED)); +} + +} // namespace cc diff --git a/cocos/core/data/Object.h b/cocos/core/data/Object.h new file mode 100644 index 0000000..fae477c --- /dev/null +++ b/cocos/core/data/Object.h @@ -0,0 +1,284 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/string.h" + +#include "base/Macros.h" +#include "base/RefCounted.h" +#include "base/TypeDef.h" + +namespace se { +class Object; +} + +namespace cc { + +/** + * @en + * The base class of most of all the objects in Cocos Creator. + * @zh + * 大部分对象的基类。 + * @private + */ +class CCObject : public RefCounted /*cjh implements EditorExtendableObject*/ { +public: + // definitions for CCObject.Flags + enum class Flags : FlagBits { + ZERO = 0, + DESTROYED = 1 << 0, + REAL_DESTROYED = 1 << 1, + TO_DESTROY = 1 << 2, + /** + * @en The object will not be saved. + * @zh 该对象将不会被保存。 + */ + DONT_SAVE = 1 << 3, + /** + * @en The object will not be saved when building a player. + * @zh 构建项目时,该对象将不会被保存。 + */ + EDITOR_ONLY = 1 << 4, + DIRTY = 1 << 5, + /** + * @en Dont destroy automatically when loading a new scene. + * @zh 加载一个新场景时,不自动删除该对象。 + * @private + */ + DONT_DESTROY = 1 << 6, + DESTROYING = 1 << 7, + /** + * @en The node is deactivating. + * @zh 节点正在反激活的过程中。 + * @private + */ + DEACTIVATING = 1 << 8, + /** + * @en The lock node, when the node is locked, cannot be clicked in the scene. + * @zh 锁定节点,锁定后场景内不能点击。 + * @private + */ + LOCKED_IN_EDITOR = 1 << 9, + /** + * @en Hide the object in editor. + * @zh 在编辑器中隐藏该对象。 + */ + HIDE_IN_HIERARCHY = 1 << 10, + + IS_ON_ENABLE_CALLED = 1 << 11, + IS_EDITOR_ON_ENABLE_CALLED = 1 << 12, + IS_PRELOAD_STARTED = 1 << 13, + IS_ON_LOAD_CALLED = 1 << 14, + IS_ON_LOAD_STARTED = 1 << 15, + IS_START_CALLED = 1 << 16, + + IS_ROTATION_LOCKED = 1 << 17, + IS_SCALE_LOCKED = 1 << 18, + IS_ANCHOR_LOCKED = 1 << 19, + IS_SIZE_LOCKED = 1 << 20, + IS_POSITION_LOCKED = 1 << 21, + + // Distributed + IS_REPLICATED = 1 << 22, + IS_CLIENT_LOAD = 1 << 23, + + // var Hide = HideInGame | HideInEditor; + // should not clone or serialize these flags + PERSISTENT_MASK = ~(TO_DESTROY | DIRTY | DESTROYING | DONT_DESTROY | DEACTIVATING | IS_PRELOAD_STARTED | IS_ON_LOAD_STARTED | IS_ON_LOAD_CALLED | IS_START_CALLED | IS_ON_ENABLE_CALLED | IS_EDITOR_ON_ENABLE_CALLED | IS_ROTATION_LOCKED | IS_SCALE_LOCKED | IS_ANCHOR_LOCKED | IS_SIZE_LOCKED | IS_POSITION_LOCKED + /* RegisteredInEditor */), + + // all the hideFlags + /** + * @en The object will not be saved and hide the object in editor,and lock node, when the node is locked, + * cannot be clicked in the scene,and The object will not be saved when building a player. + * @zh 该对象将不会被保存,构建项目时,该对象将不会被保存, 锁定节点,锁定后场景内不能点击, 在编辑器中隐藏该对象。 + */ + ALL_HIDE_MASKS = DONT_SAVE | EDITOR_ONLY | LOCKED_IN_EDITOR | HIDE_IN_HIERARCHY, + }; + + static void deferredDestroy(); + + // cjh public declare [editorExtrasTag]: unknown; + + Flags _objFlags{Flags::ZERO}; + ccstd::string _name; + + explicit CCObject(ccstd::string name = ""); + ~CCObject() override; + + // MEMBER + + /** + * @en The name of the object. + * @zh 该对象的名称。 + * @default "" + * @example + * ``` + * obj.name = "New Obj"; + * ``` + */ + inline const ccstd::string& getName() const { return _name; } + inline void setName(const ccstd::string& value) { _name = value; } + + /** + * @en After inheriting CCObject objects, control whether you need to hide, lock, serialize, and other functions. + * @zh 在继承 CCObject 对象后,控制是否需要隐藏,锁定,序列化等功能。 + */ + inline void setHideFlags(Flags hideFlags); + inline Flags getHideFlags() const; + + /** + * @en + * Indicates whether the object is not yet destroyed. (It will not be available after being destroyed)
+ * When an object's `destroy` is called, it is actually destroyed after the end of this frame. + * So `isValid` will return false from the next frame, while `isValid` in the current frame will still be true. + * If you want to determine whether the current frame has called `destroy`, use `isValid(obj, true)`, + * but this is often caused by a particular logical requirements, which is not normally required. + * + * @zh + * 表示该对象是否可用(被 destroy 后将不可用)。
+ * 当一个对象的 `destroy` 调用以后,会在这一帧结束后才真正销毁。
+ * 因此从下一帧开始 `isValid` 就会返回 false,而当前帧内 `isValid` 仍然会是 true。
+ * 如果希望判断当前帧是否调用过 `destroy`,请使用 `isValid(obj, true)`,不过这往往是特殊的业务需求引起的,通常情况下不需要这样。 + * @default true + * @readOnly + * @example + * ```ts + * import { Node, log } from 'cc'; + * const node = new Node(); + * log(node.isValid); // true + * node.destroy(); + * log(node.isValid); // true, still valid in this frame + * // after a frame... + * log(node.isValid); // false, destroyed in the end of last frame + * ``` + */ + inline bool isValid() const; + + virtual void destruct(); + + /** + * @en + * Destroy this Object, and release all its own references to other objects.
+ * Actual object destruction will delayed until before rendering. + * From the next frame, this object is not usable any more. + * You can use `isValid(obj)` to check whether the object is destroyed before accessing it. + * @zh + * 销毁该对象,并释放所有它对其它对象的引用。
+ * 实际销毁操作会延迟到当前帧渲染前执行。从下一帧开始,该对象将不再可用。 + * 您可以在访问对象之前使用 `isValid(obj)` 来检查对象是否已被销毁。 + * @return whether it is the first time the destroy being called + * @example + * ``` + * obj.destroy(); + * ``` + */ + virtual bool destroy(); + + /** + * Clear all references in the instance. + * + * NOTE: this method will not clear the getter or setter functions which defined in the instance of CCObject. + * You can override the _destruct method if you need, for example: + * _destruct: function () { + * for (var key in this) { + * if (hasOwnProperty(key)) { + * switch (typeof this[key]) { + * case 'string': + * this[key] = ''; + * break; + * case 'object': + * case 'function': + * this[key] = null; + * break; + * } + * } + * } + * + */ + + void destroyImmediate(); + + virtual ccstd::string toString() const { return ""; }; + + inline void setScriptObject(se::Object* seObj) { _scriptObject = seObj; } + inline se::Object* getScriptObject() const { return _scriptObject; } + +protected: + virtual bool onPreDestroy() { + // FIXME: need reture value + return true; + } + + se::Object* _scriptObject{nullptr}; // weak reference +}; + +CC_ENUM_BITWISE_OPERATORS(CCObject::Flags); + +inline bool CCObject::isValid() const { + return !(_objFlags & Flags::DESTROYED); +} + +inline void CCObject::setHideFlags(Flags hideFlags) { + Flags flags = hideFlags & Flags::ALL_HIDE_MASKS; + _objFlags = (_objFlags & ~Flags::ALL_HIDE_MASKS) | flags; +} + +inline CCObject::Flags CCObject::getHideFlags() const { + return _objFlags & Flags::ALL_HIDE_MASKS; +} + +/* + * @en + * Checks whether the object is non-nil and not yet destroyed.
+ * When an object's `destroy` is called, it is actually destroyed after the end of this frame. + * So `isValid` will return false from the next frame, while `isValid` in the current frame will still be true. + * If you want to determine whether the current frame has called `destroy`, use `isValid(obj, true)`, + * but this is often caused by a particular logical requirements, which is not normally required. + * + * @zh + * 检查该对象是否不为 null 并且尚未销毁。
+ * 当一个对象的 `destroy` 调用以后,会在这一帧结束后才真正销毁。
+ * 因此从下一帧开始 `isValid` 就会返回 false,而当前帧内 `isValid` 仍然会是 true。
+ * 如果希望判断当前帧是否调用过 `destroy`,请使用 `isValid(obj, true)`,不过这往往是特殊的业务需求引起的,通常情况下不需要这样。 + * + * @method isValid + * @param value + * @param [strictMode=false] - If true, Object called destroy() in this frame will also treated as invalid. + * @return whether is valid + * @example + * ``` + * import { Node, log } from 'cc'; + * var node = new Node(); + * log(isValid(node)); // true + * node.destroy(); + * log(isValid(node)); // true, still valid in this frame + * // after a frame... + * log(isValid(node)); // false, destroyed in the end of last frame + * ``` + */ +bool isObjectValid(CCObject* value, bool strictMode = false); + +} // namespace cc diff --git a/cocos/core/event/Event.h b/cocos/core/event/Event.h new file mode 100644 index 0000000..3a7ed56 --- /dev/null +++ b/cocos/core/event/Event.h @@ -0,0 +1,55 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once +#ifdef SWIGCOCOS + #define IMPL_EVENT_TARGET_WITH_PARENT(...) + #define IMPL_EVENT_TARGET(...) + #define DECLARE_TARGET_EVENT_BEGIN(...) + #define DECLARE_TARGET_EVENT_BEGIN_WITH_PARENTS(...) + #define DECLARE_TARGET_EVENT_END(...) + #define TARGET_EVENT_ARG0(...) + #define TARGET_EVENT_ARG1(...) + #define TARGET_EVENT_ARG2(...) + #define TARGET_EVENT_ARG3(...) + #define TARGET_EVENT_ARG4(...) + #define TARGET_EVENT_ARG5(...) + #define TARGET_EVENT_ARG6(...) + #define TARGET_EVENT_ARG7(...) + #define TARGET_EVENT_ARG8(...) + #define TARGET_EVENT_ARG9(...) + #define TARGET_EVENT_ARG10(...) + #define TARGET_EVENT_ARG11(...) + #define TARGET_EVENT_ARG12(...) + #define TARGET_EVENT_ARG13(...) + #define TARGET_EVENT_ARG14(...) + #define TARGET_EVENT_ARG15(...) + #define TARGET_EVENT_ARG16(...) + #define TARGET_EVENT_ARG17(...) + #define TARGET_EVENT_ARG18(...) + #define TARGET_EVENT_ARG19(...) + #define TARGET_EVENT_ARG20(...) +#else + #include "EventBus.h" + #include "EventTarget.h" +#endif \ No newline at end of file diff --git a/cocos/core/event/EventBus.cpp b/cocos/core/event/EventBus.cpp new file mode 100644 index 0000000..979dc17 --- /dev/null +++ b/cocos/core/event/EventBus.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "EventBus.h" +#include +#include "intl/List.h" + +namespace cc { +namespace event { + +void BusEventListenerContainer::addListener(BusEventListenerBase *listener) { + if (_isBroadcasting) { + intl::listAppend(&_listenersToAdd, listener->entry); + return; + } + intl::listAppend(&_listenerList, listener->entry); +} + +void BusEventListenerContainer::removeListener(BusEventListenerBase *listener) { + if (_isBroadcasting) { + _listenersToRemove.emplace_back(listener->entry); + listener->entry->listener = nullptr; + return; + } + intl::detachFromList(&_listenerList, listener->entry); + delete listener->entry; +} + +void BusEventListenerContainer::addOrRemovePendingListeners() { + for (auto &entry : _listenersToRemove) { + intl::detachFromList(&_listenerList, entry); + delete entry; + } + EVENT_LIST_LOOP_REV_BEGIN(curr, _listenersToAdd) + intl::detachFromList(&_listenersToAdd, curr); + intl::listAppend(&_listenerList, curr); + EVENT_LIST_LOOP_REV_END(curr, _listenersToAdd) + _listenersToAdd = nullptr; + _listenersToRemove.clear(); +} +} // namespace event +} // namespace cc diff --git a/cocos/core/event/EventBus.h b/cocos/core/event/EventBus.h new file mode 100644 index 0000000..359bac2 --- /dev/null +++ b/cocos/core/event/EventBus.h @@ -0,0 +1,238 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include +#include +#include + +#include "base/Log.h" +#include "base/std/container/vector.h" +#include "core/memop/Pool.h" +#include "intl/EventIntl.h" +#include "intl/List.h" + +namespace cc { +namespace event { +class BusEventListenerBase; + +template +class BusEventListenerDB; + +template +class BusEventBroadcaster; + +class BusEventListenerContainer; +struct BusEventListenerEntry { + BusEventListenerEntry *next{nullptr}; + BusEventListenerEntry *prev{nullptr}; + BusEventListenerBase *listener{nullptr}; +}; +class BusEventListenerBase { +protected: + BusEventListenerEntry *entry{nullptr}; // NOLINT + friend class BusEventListenerContainer; +}; + +class BusEventListenerContainer { +public: + template + bool broadcast(ARGS &&...args); + +protected: + BusEventListenerContainer() = default; + virtual ~BusEventListenerContainer() = default; + void addListener(BusEventListenerBase *); + void removeListener(BusEventListenerBase *); + + bool hasPendingListeners() const { + return !_listenersToRemove.empty() || _listenersToAdd; + } + void addOrRemovePendingListeners(); + // fields + BusEventListenerEntry *_listenerList{nullptr}; + BusEventListenerEntry *_listenersToAdd{nullptr}; + ccstd::vector _listenersToRemove; + int _isBroadcasting = 0; + + template + friend class BusEventListenerDB; + + template + friend class BusEventBroadcaster; + + friend class BusEventListenerBase; + template + friend class Listener; +}; + +template +class BusEventBroadcaster final : public BusEventListenerContainer { +public: + bool doBroadcast(ARGS &&...args); +}; + +template +bool BusEventListenerContainer::broadcast(ARGS &&...args) { + using BusType = BusEventBroadcaster; + _isBroadcasting++; + auto ret = static_cast(this)->doBroadcast(std::forward(args)...); + _isBroadcasting--; + if (!_isBroadcasting && hasPendingListeners()) { + addOrRemovePendingListeners(); + } + return false; +} + +template +class BusEventListenerDB final { +public: + static BusEventListenerContainer *container() { + if (ctn == nullptr) { + ctn = new BusEventListenerContainer; + } + return ctn; + } + +private: + static BusEventListenerContainer *ctn; +}; + +template +BusEventListenerContainer *BusEventListenerDB::ctn = nullptr; + +template +struct BusEventTrait { + using _bus_type = BusType; + using _return_type = R; + using _argument_tuple_types = std::tuple; + constexpr static int ARG_COUNT = sizeof...(ARGS); +}; +/** + * Bus Event Listener + */ +template +class Listener : public BusEventListenerBase { +public: + using _agurment_tuple_types = typename EHandler::_argument_tuple_types; + using _argument_wrapper = typename intl::TupleExtractor<_agurment_tuple_types>; + // using func_type = typename _argument_wrapper::func_type; + using _std_func_type = typename _argument_wrapper::std_func_type; + + constexpr static const char *BUS_NAME = EHandler::BUS_NAME; + constexpr static const char *HANDLE_CLASS = EHandler::HANDLE_CLASS; + + Listener(); + ~Listener(); + + inline bool isEnabled() const { return _enabled; } + inline void enable() { _enabled = true; } + inline void disable() { _enabled = false; } + inline void reset() { _callback = nullptr; } + + template + bool bind(Fn &&func) { + _callback = intl::convertLambda(std::forward(func)); + return true; + } + + template + void invoke(ARGS &&...args) { + if (_callback && _enabled) { + _callback(std::forward(args)...); + } else { + CC_LOG_DEBUG("EventBus[%s] has no listener found!", BUS_NAME); + } + } + + const char *getBusName() const { return BUS_NAME; } + const char *getHandlerName() const { return HANDLE_CLASS; } + +private: + bool _enabled{true}; + _std_func_type _callback; + + friend class BusEventListenerContainer; +}; + +template +Listener::Listener() { + entry = new BusEventListenerEntry; + entry->listener = this; + BusEventListenerDB::container()->addListener(this); +} + +template +Listener::~Listener() { + BusEventListenerDB::container()->removeListener(this); +} + +template +bool BusEventBroadcaster::doBroadcast(ARGS &&...args) { + // broadcast events to all listeners + EVENT_LIST_LOOP_BEGIN(curr, _listenerList) + if (curr->listener) { + static_cast *>(curr->listener)->invoke(std::forward(args)...); + } + EVENT_LIST_LOOP_END(curr, _listenerList) + return true; +} + +template +void broadcast(ARGS &&...args) { + static_assert(sizeof...(ARGS) == EHandler::ARG_COUNT, "parameter count incorrect"); + event::intl::validateParameters<0, EHandler, ARGS...>(std::forward(args)...); + auto *listenerSet = BusEventListenerDB::container(); + listenerSet->template broadcast(std::forward(args)...); +} + +} // namespace event +} // namespace cc + +// NOLINTNEXTLINE +#define EventBusName_(n) n##_ebus + +#define DECLARE_EVENT_BUS(EventBusClass) \ + struct EventBusClass##_ebus { \ + constexpr static const char *BUS_NAME = #EventBusClass; \ + }; + +// NOLINTNEXTLINE +#define _DECLARE_BUS_EVENT_VA(BusEventClass, EventBusClass, ...) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = BusType::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + template \ + static inline void emit(ARGS &&...args) { \ + cc::event::emit(std::forward(args)...); \ + } \ + }; + +#include "intl/EventBusMacros.h" diff --git a/cocos/core/event/EventTarget.h b/cocos/core/event/EventTarget.h new file mode 100644 index 0000000..12dda07 --- /dev/null +++ b/cocos/core/event/EventTarget.h @@ -0,0 +1,715 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include +#include +#include "base/Log.h" +#include "base/Macros.h" +#include "base/memory/Memory.h" +#include "intl/EventIntl.h" +#include "intl/List.h" + +namespace cc { +namespace event { + +class TgtEventTraitClass {}; + +enum class EventPhaseType { + CAPTUREING_PHASE = 1, + AT_TARGET = 2, + BUBBLING_PHASE = 3, + UNKNOWN = 4, +}; + +struct TgtEventInfo { + EventPhaseType eventPhase{EventPhaseType::UNKNOWN}; + bool bubbles{true}; + bool cancelable{true}; + + void stopPropagation() { + propagationStopped = true; + } + + void preventDefault() { /* TODO: */ + } + + bool propagationStopped{false}; +}; + +template +struct Event final : TgtEventInfo { + using EmitterType = typename TgtEvent::_emitter_type; + using _argument_tuple_types = typename TgtEvent::_argument_tuple_types; + using _argument_local_types = typename TgtEvent::_argument_local_types; + EmitterType *target{nullptr}; + EmitterType *currentTarget{nullptr}; + _argument_local_types args; + + Event() = default; + + explicit Event(const _argument_local_types &argsIn) : args(argsIn) { + } + + template + auto get() const { + return std::get(args); + } + + template + auto get() { + return std::get(args); + } + + template + void set(A &&value) { + std::get(args) = value; + } + + void initEvent(bool canBubbleArg, bool cancelableArg) { + // TODO() + } + + template + void update(ARGS &&...values) { + args = std::make_tuple<>(std::forward(values)...); + } +}; + +template +class TgtEventTrait : public TgtEventTraitClass { +public: + using _emitter_type = EmitterType; + using _argument_tuple_types = std::tuple; + using _argument_local_types = std::tuple>...>; + constexpr static int ARG_COUNT = sizeof...(ARGS); +}; + +class TgtMemberFnCmp { +public: + virtual ~TgtMemberFnCmp() = default; + + virtual void *getContext() = 0; + + virtual bool equals(TgtMemberFnCmp *) const = 0; +}; + +template +class TgtMemberHandleFn final : public TgtMemberFnCmp { +public: + ~TgtMemberHandleFn() override = default; + void *getContext() override { return context; } + + bool equals(TgtMemberFnCmp *other) const override { + auto *fake = reinterpret_cast(other); + return context == fake->context && func == fake->func; + } + + Fn func; + void *context; +}; + +class TargetEventListenerBase { +public: + enum class RunState { + NORMAL, + PENDING_ONCE, + ONCE_DONE, + }; + + virtual ~TargetEventListenerBase() = default; + + virtual void *getContext() const = 0; + + virtual const char *getEventName() const { return nullptr; } + + bool isEnabled() const { return _enabled; } + + void setOnce() { _state = RunState::PENDING_ONCE; } + + inline size_t getEventTypeID() const { return _eventTypeID; } + + int32_t id{-1}; + + TargetEventListenerBase *next{nullptr}; + TargetEventListenerBase *prev{nullptr}; + +protected: + bool _enabled = true; + RunState _state{RunState::NORMAL}; + size_t _eventTypeID; +}; + +template +class TargetEventListener final : public TargetEventListenerBase { +public: + using _emitter_type = typename TgtEvent::_emitter_type; + using EventType = typename TgtEvent::EventType; + using _persist_function_type = typename TgtEvent::_persist_function_type; + + explicit TargetEventListener(_persist_function_type func) : _func(func) { + _eventTypeID = TgtEvent::TYPE_ID; + } + + ~TargetEventListener() override { + delete _fnCmptor; + } + + template + void setMemberFuncAddr(Fn func, void *context) { + auto fctx = new TgtMemberHandleFn; + fctx->func = func; + fctx->context = context; + _fnCmptor = fctx; + } + + void *getContext() const override { + if (_fnCmptor) return _fnCmptor->getContext(); + return nullptr; + } + + const char *getEventName() const override { + return TgtEvent::EVENT_NAME; + } + + void apply(_emitter_type *self, EventType *evobj) { + switch (_state) { + case RunState::ONCE_DONE: + return; + case RunState::PENDING_ONCE: + _state = RunState::ONCE_DONE; + break; + default: + break; + } + _func(self, evobj); + } + +protected: + _persist_function_type _func; + TgtMemberFnCmp *_fnCmptor{nullptr}; +}; + +using TargetEventIdType = int32_t; + +template +class TargetEventID final { +public: + using HandleType = TgtEvent; + using IdType = TargetEventIdType; + + TargetEventID() = default; + + TargetEventID(IdType eventId) : _eventId(eventId) {} // NOLINT + + TargetEventID(const TargetEventID &) = default; + + TargetEventID(TargetEventID &&) noexcept = default; + + TargetEventID &operator=(const TargetEventID &) = default; + + TargetEventID &operator=(TargetEventID &&) noexcept = default; + + IdType value() { return _eventId; } + +private: + IdType _eventId{}; +}; + +template +struct TargetListenerContainer final { + std::array data{nullptr}; + + inline TargetEventListenerBase *&operator[](size_t index) { return data[index]; } + + void clear() { + for (size_t i = 0; i < N; i++) { + auto &handlers = data[i]; + EVENT_LIST_LOOP_REV_BEGIN(handle, handlers) + delete handle; + EVENT_LIST_LOOP_REV_END(handle, handlers) + data[i] = nullptr; + } + } + + ~TargetListenerContainer() { + clear(); + } +}; + +template +class EventTargetImpl final { +public: + constexpr static bool HAS_PARENT = hasParent; + +protected: + template + TargetEventID addEventListener(Fn &&func, bool useCapture, bool once) { + /* CC_ASSERT(!_emittingEvent); */ + using func_type = std::conditional_t::IS_LAMBDA, + typename intl::lambda_without_class_t, Fn>; + using wrap_type = intl::TgtEvtFnTrait; + auto stdfn = wrap_type::template wrap(intl::convertLambda(std::forward(func))); + auto *newHandler = new TargetEventListener(stdfn); + auto newId = ++_handlerId; + newHandler->id = newId; + if (once) { + newHandler->setOnce(); + } + if constexpr (wrap_type::IS_MEMBER_FUNC) { + newHandler->setMemberFuncAddr(std::forward(func), nullptr); + } + if (useCapture) { + intl::listAppend(&_capturingHandlers[TgtEvent::TYPE_ID], newHandler); + } else { + intl::listAppend(&_bubblingHandlers[TgtEvent::TYPE_ID], newHandler); + } + return TargetEventID(newId); + } + + template + TargetEventID addEventListener(Fn &&func, O *ctx, bool useCapture, bool once) { + /* CC_ASSERT(!_emittingEvent);*/ + using wrap_type = intl::TgtEvtFnTrait; + auto stdfn = wrap_type::template wrapWithContext(std::forward(func), ctx); + auto *newHandler = new TargetEventListener(stdfn); + auto newId = ++_handlerId; + newHandler->id = newId; + if (once) { + newHandler->setOnce(); + } + if constexpr (wrap_type::IS_MEMBER_FUNC) { + newHandler->setMemberFuncAddr(std::forward(func), ctx); + } + if (useCapture) { + intl::listAppend(&_capturingHandlers[TgtEvent::TYPE_ID], newHandler); + } else { + intl::listAppend(&_bubblingHandlers[TgtEvent::TYPE_ID], newHandler); + } + return TargetEventID(newId); + } + +public: + template + TargetEventID once(Fn &&func, bool useCapture) { + return this->template addEventListener(std::forward(func), useCapture, true); + } + + template + TargetEventID once(Fn &&func, O *ctx) { + return this->template addEventListener(std::forward(func), ctx, true); + } + + template + TargetEventID on(Fn &&func, bool useCapture = false) { + static_assert(std::is_base_of::value, "mismatch target type"); + return this->template addEventListener(std::forward(func), useCapture, false); + } + + template + TargetEventID on(Fn &&func, O *ctx, bool useCapture = false) { + return this->template addEventListener(std::forward(func), ctx, useCapture, false); + } + + template + bool off(TargetEventID eventId) { + CC_ASSERT(!_emittingEvent[TgtEvent::TYPE_ID]); + + TargetEventListenerBase *&bubblingHandlers = _bubblingHandlers[TgtEvent::TYPE_ID]; + + EVENT_LIST_LOOP_REV_BEGIN(handle, bubblingHandlers) + if (handle && handle->id == eventId.value()) { + CC_ASSERT(handle->getEventTypeID() == TgtEvent::TYPE_ID); + intl::detachFromList(&bubblingHandlers, handle); + delete handle; + return true; + } + EVENT_LIST_LOOP_REV_END(handle, bubblingHandlers) + TargetEventListenerBase *&capturingHandlers = _capturingHandlers[TgtEvent::TYPE_ID]; + EVENT_LIST_LOOP_REV_BEGIN(handle, capturingHandlers) + if (handle && handle->id == eventId.value()) { + CC_ASSERT(handle->getEventTypeID() == TgtEvent::TYPE_ID); + intl::detachFromList(&capturingHandlers, handle); + delete handle; + return true; + } + EVENT_LIST_LOOP_REV_END(handle, capturingHandlers) + return false; + } + + void offAll() { +#if CC_DEBUG + for (auto &itr : _emittingEvent) { + CC_ASSERT(!itr); + } +#endif + _bubblingHandlers.clear(); + _capturingHandlers.clear(); + } + + template + void off() { + static_assert(std::is_base_of_v, "incorrect template argument"); + CC_ASSERT(!_emittingEvent[TgtEvent::TYPE_ID]); + TargetEventListenerBase *&bubblingHandlers = _bubblingHandlers[TgtEvent::TYPE_ID]; + + EVENT_LIST_LOOP_REV_BEGIN(handle, bubblingHandlers) + if (handle) { + intl::detachFromList(&bubblingHandlers, handle); + delete handle; + } + EVENT_LIST_LOOP_REV_END(handle, bubblingHandlers) + + TargetEventListenerBase *&capturingHandlers = _capturingHandlers[TgtEvent::TYPE_ID]; + EVENT_LIST_LOOP_REV_BEGIN(handle, capturingHandlers) + if (handle) { + intl::detachFromList(&capturingHandlers, handle); + delete handle; + } + EVENT_LIST_LOOP_REV_END(handle, capturingHandlers) + } + + template + void emit(Self *self, ARGS &&...args) { /* TODO() : statistics */ + using _handler_function_type = TargetEventListener; + using EventType = typename TgtEvent::EventType; + static_assert(sizeof...(ARGS) == TgtEvent::ARG_COUNT, "Parameter count incorrect for function EventTarget::emit"); + if (_bubblingHandlers[TgtEvent::TYPE_ID] == nullptr) return; + intl::validateParameters<0, TgtEvent, ARGS...>(std::forward(args)...); + EventType eventObj(std::make_tuple(std::forward(args)...)); + eventObj.target = self; + eventObj.currentTarget = self; + + emitEvtObj(self, &eventObj); + } + + template + void emitEvtObj(Self *self, EvtObj *eventObj) { + using EventType = typename TgtEvent::EventType; + using _handler_function_type = TargetEventListener; + static_assert(std::is_same_v, "Event type mismatch"); + _emittingEvent[TgtEvent::TYPE_ID]++; + if constexpr (useCapture) { + TargetEventListenerBase *&handlers = _capturingHandlers[TgtEvent::TYPE_ID]; + EVENT_LIST_LOOP_BEGIN(handle, handlers) + if (handle->isEnabled()) { +#if CC_DEBUG + bool sameAddr = handle->getEventName() == TgtEvent::EVENT_NAME; + if (!sameAddr && strcmp(handle->getEventName(), TgtEvent::EVENT_NAME)) { + // different event should not shared a same typeid. + CC_LOG_ERROR("Event '%s' and '%s' shared the same TypeID(), for event declaration of subclasses, please use DECLARE_TARGET_EVENT_BEGIN_OFFSET()", TgtEvent::EVENT_NAME, handle->getEventName()); + CC_ABORT(); + } +#endif + static_cast<_handler_function_type *>(handle)->apply(self, eventObj); + } + EVENT_LIST_LOOP_END(handle, handlers); + } else { + TargetEventListenerBase *&handlers = _bubblingHandlers[TgtEvent::TYPE_ID]; + EVENT_LIST_LOOP_BEGIN(handle, handlers) + if (handle->isEnabled()) { +#if CC_DEBUG + bool sameAddr = handle->getEventName() == TgtEvent::EVENT_NAME; + if (!sameAddr && strcmp(handle->getEventName(), TgtEvent::EVENT_NAME)) { + // different event should not shared a same typeid. + CC_LOG_ERROR("Event '%s' and '%s' shared the same TypeID(), for event declaration of subclasses, please use DECLARE_TARGET_EVENT_BEGIN_OFFSET()", TgtEvent::EVENT_NAME, handle->getEventName()); + CC_ABORT(); + } +#endif + static_cast<_handler_function_type *>(handle)->apply(self, eventObj); + } + EVENT_LIST_LOOP_END(handle, handlers); + } + _emittingEvent[TgtEvent::TYPE_ID]--; + } + + template + std::enable_if_t>, void> + dispatchEvent(Self *self, EvtType &eventObj) { + if constexpr (HAS_PARENT) { + std::vector parents; + Self *curr = self->evGetParent(); + while (curr) { + if (curr->getEventTarget().template hasEventHandler()) { + parents.emplace_back(curr); + } + curr = curr->evGetParent(); + } + for (auto itr = parents.rbegin(); itr != parents.rend(); itr++) { + eventObj.currentTarget = *itr; + (*itr)->getEventTarget().template emitEvtObj(*itr, &eventObj); + if (eventObj.propagationStopped) { + return; + } + } + } + + eventObj.eventPhase = EventPhaseType::AT_TARGET; + eventObj.currentTarget = self; + + emitEvtObj(self, &eventObj); + if (!eventObj.propagationStopped) { + emitEvtObj(self, &eventObj); + } + + if constexpr (HAS_PARENT) { + if (!eventObj.propagationStopped && eventObj.bubbles) { + auto *curr = self->evGetParent(); + std::vector parents; + + while (curr) { + if (curr->getEventTarget().template hasEventHandler()) { + parents.emplace_back(curr); + } + curr = curr->evGetParent(); + } + for (auto itr = parents.begin(); itr != parents.end(); itr++) { + eventObj.currentTarget = *itr; + (*itr)->getEventTarget().template emitEvtObj(*itr, &eventObj); + if (eventObj.propagationStopped) { + return; + } + } + } + } + } + + template + std::enable_if_t::head>>), + void> + dispatchEvent(Self *self, ARGS &&...args) { + using _handler_function_type = TargetEventListener; + using EventType = typename TgtEvent::EventType; + static_assert(sizeof...(ARGS) == TgtEvent::ARG_COUNT, "Parameter count incorrect for function EventTarget::emit"); + intl::validateParameters<0, TgtEvent, ARGS...>(std::forward(args)...); + EventType eventObj(std::make_tuple(std::forward(args)...)); + eventObj.target = self; + eventObj.currentTarget = self; + eventObj.eventPhase = EventPhaseType::CAPTUREING_PHASE; + dispatchEvent(self, eventObj); + } + + template + void dispatchEvent(Self *self) { + using _handler_function_type = TargetEventListener; + using EventType = typename TgtEvent::EventType; + static_assert(0 == TgtEvent::ARG_COUNT, "Parameter count incorrect for function EventTarget::emit"); + EventType eventObj; + eventObj.target = self; + eventObj.currentTarget = self; + eventObj.eventPhase = EventPhaseType::CAPTUREING_PHASE; + dispatchEvent(self, eventObj); + } + + template + bool hasEventHandler() { + TargetEventListenerBase *&bubblingHandlers = _bubblingHandlers[TgtEvent::TYPE_ID]; + EVENT_LIST_LOOP_BEGIN(handle, bubblingHandlers) + if (handle->isEnabled()) { + return true; + } + EVENT_LIST_LOOP_END(handle, bubblingHandlers); + TargetEventListenerBase *&capturingHandlers = _capturingHandlers[TgtEvent::TYPE_ID]; + EVENT_LIST_LOOP_BEGIN(handle, capturingHandlers) + if (handle->isEnabled()) { + return true; + } + EVENT_LIST_LOOP_END(handle, capturingHandlers); + return false; + } + + template + bool hasEventHandler(Fn func, C *target) { + using wrap_type = intl::TgtEvtFnTrait; + using _handler_function_type = event::TargetEventListener; + static_assert(std::is_same::value, "member function type mismatch"); + + TargetEventListenerBase *&bubblingHandlers = _bubblingHandlers[TgtEvent::TYPE_ID]; + EVENT_LIST_LOOP_BEGIN(handle, bubblingHandlers) + if (handle->isEnabled() && handle->getContext() == target) { + auto *ptr = static_cast<_handler_function_type *>(handle); + return ptr->getMemberFuncAddr() == func; + } + EVENT_LIST_LOOP_END(handle, bubblingHandlers); + TargetEventListenerBase *&capturingHandlers = _capturingHandlers[TgtEvent::TYPE_ID]; + EVENT_LIST_LOOP_BEGIN(handle, capturingHandlers) + if (handle->isEnabled() && handle->getContext() == target) { + auto *ptr = static_cast<_handler_function_type *>(handle); + return ptr->getMemberFuncAddr() == func; + } + EVENT_LIST_LOOP_END(handle, capturingHandlers); + + return false; + } + +protected: + TargetListenerContainer _bubblingHandlers; + TargetListenerContainer _capturingHandlers; + TargetEventIdType _handlerId{1}; + std::array _emittingEvent{0}; +}; + +template +class HeapObject final { +public: + HeapObject() { + _data = ccnew T; + } + + ~HeapObject() { + delete _data; + } + + inline T &get() { + return *_data; + } + +private: + T *_data{nullptr}; +}; + +template +using EventTargetGuard = HeapObject>; + +} // namespace event +} // namespace cc + +#define TARGET_EVENT_ARG0(EventTypeClass) \ + class EventTypeClass final : public cc::event::TgtEventTrait { \ + public: \ + using BaseType = cc::event::TgtEventTrait; \ + using EventType = cc::event::Event; \ + using EventID = cc::event::TargetEventID; \ + using _persist_function_type = std::function; \ + using _handler_function_type = std::function; \ + constexpr static const char *EVENT_NAME = #EventTypeClass; \ + constexpr static size_t TYPE_ID = __COUNTER__ - __counter_start__ - 1 + __counter_offset__; \ + constexpr static size_t TypeHash() { \ + return cc::event::intl::hash(#EventTypeClass); \ + } \ + }; + +// NOLINTNEXTLINE +#define _DECLARE_TARGET_EVENT_INTER(EventTypeClass, ...) \ + class EventTypeClass final : public cc::event::TgtEventTrait { \ + public: \ + using BaseType = cc::event::TgtEventTrait; \ + using EventType = cc::event::Event; \ + using EventID = cc::event::TargetEventID; \ + using _persist_function_type = std::function; \ + using _handler_function_type = std::function; \ + constexpr static const char *EVENT_NAME = #EventTypeClass; \ + constexpr static size_t TYPE_ID = __COUNTER__ - __counter_start__ - 1 + __counter_offset__; \ + constexpr static size_t TypeHash() { \ + return cc::event::intl::hash(#EventTypeClass); \ + } \ + }; + +// NOLINTNEXTLINE +#define _IMPL_EVENT_TARGET_(Self) \ +public: \ + template \ + cc::event::TargetEventID once(Fn &&func, bool useCapture) { \ + return _eventTargetImpl.get().once(std::forward(func), useCapture); \ + } \ + \ + template \ + cc::event::TargetEventID once(Fn &&func, O *ctx) { \ + return _eventTargetImpl.get().once(std::forward(func), ctx); \ + } \ + template \ + cc::event::TargetEventID on(Fn &&func, bool useCapture = false) { \ + static_assert(std::is_base_of::value, "mismatch target type"); \ + return _eventTargetImpl.get().on(std::forward(func), useCapture); \ + } \ + template \ + cc::event::TargetEventID on(Fn &&func, O *ctx, bool useCapture = false) { \ + return _eventTargetImpl.get().on(std::forward(func), ctx, useCapture); \ + } \ + template \ + bool off(cc::event::TargetEventID eventId) { \ + return _eventTargetImpl.get().off(eventId); \ + } \ + void offAll() { \ + _eventTargetImpl.get().offAll(); \ + } \ + template \ + void off() { \ + _eventTargetImpl.get().off(); \ + } \ + template \ + void emit(ARGS &&...args) { \ + _eventTargetImpl.get().emit(this, std::forward(args)...); \ + } \ + template \ + void dispatchEvent(ARGS &&...args) { \ + _eventTargetImpl.get().dispatchEvent(this, std::forward(args)...); \ + } + +#define IMPL_EVENT_TARGET(TargetClass) \ +public: \ + static constexpr bool HAS_PARENT = false; \ + inline TargetClass *evGetParent() { return nullptr; } \ + _IMPL_EVENT_TARGET_(TargetClass) + +#define IMPL_EVENT_TARGET_WITH_PARENT(TargetClass, getParentMethod) \ +public: \ + static constexpr bool HAS_PARENT = true; \ + inline auto evGetParent() { \ + return getParentMethod(); \ + } \ + _IMPL_EVENT_TARGET_(TargetClass) + +// NOLINTNEXTLINE +#define _DECLARE_TARGET_EVENT_BEGIN(TargetClass) \ + using Self = TargetClass; \ + \ +private: \ + constexpr static int __counter_start__ = __COUNTER__; \ + \ +public: + +#define DECLARE_TARGET_EVENT_BEGIN(TargetClass) \ + constexpr static int __counter_offset__ = 0; \ + _DECLARE_TARGET_EVENT_BEGIN(TargetClass) + +#define DECLARE_TARGET_EVENT_BEGIN_WITH_PARENTS(TargetClass, ...) \ + constexpr static int __counter_offset__ = cc::event::intl::TotalEvents<__VA_ARGS__>; \ + _DECLARE_TARGET_EVENT_BEGIN(TargetClass) + +#define DECLARE_TARGET_EVENT_END() \ +private: \ + constexpr static int __counter_stop__ = __COUNTER__; \ + constexpr static int __event_count__ = __counter_stop__ - __counter_start__ + __counter_offset__; \ + cc::event::EventTargetGuard _eventTargetImpl; \ + \ +public: \ + constexpr static int getEventCount() { return __event_count__; } \ + auto &getEventTarget() { return _eventTargetImpl.get(); } + +#include "intl/EventTargetMacros.h" diff --git a/cocos/core/event/intl/EventBusMacros.h b/cocos/core/event/intl/EventBusMacros.h new file mode 100644 index 0000000..736dbac --- /dev/null +++ b/cocos/core/event/intl/EventBusMacros.h @@ -0,0 +1,295 @@ +// generated code + +#define DECLARE_BUS_EVENT_ARG0(BusEventClass, EventBusClass) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast() { \ + cc::event::broadcast(); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG1(BusEventClass, EventBusClass, ArgType0) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0) { \ + cc::event::broadcast(arg0); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG2(BusEventClass, EventBusClass, ArgType0, ArgType1) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1) { \ + cc::event::broadcast(arg0, arg1); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG3(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2) { \ + cc::event::broadcast(arg0, arg1, arg2); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG4(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG5(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG6(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG7(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG8(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG9(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG10(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG11(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9, ArgType10 arg10) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG12(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9, ArgType10 arg10, ArgType11 arg11) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG13(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9, ArgType10 arg10, ArgType11 arg11, ArgType12 arg12) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG14(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9, ArgType10 arg10, ArgType11 arg11, ArgType12 arg12, ArgType13 arg13) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG15(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9, ArgType10 arg10, ArgType11 arg11, ArgType12 arg12, ArgType13 arg13, ArgType14 arg14) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG16(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9, ArgType10 arg10, ArgType11 arg11, ArgType12 arg12, ArgType13 arg13, ArgType14 arg14, ArgType15 arg15) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG17(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9, ArgType10 arg10, ArgType11 arg11, ArgType12 arg12, ArgType13 arg13, ArgType14 arg14, ArgType15 arg15, ArgType16 arg16) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG18(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9, ArgType10 arg10, ArgType11 arg11, ArgType12 arg12, ArgType13 arg13, ArgType14 arg14, ArgType15 arg15, ArgType16 arg16, ArgType17 arg17) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG19(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17, ArgType18) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9, ArgType10 arg10, ArgType11 arg11, ArgType12 arg12, ArgType13 arg13, ArgType14 arg14, ArgType15 arg15, ArgType16 arg16, ArgType17 arg17, ArgType18 arg18) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + } \ + }; + +#define DECLARE_BUS_EVENT_ARG20(BusEventClass, EventBusClass, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17, ArgType18, ArgType19) \ + struct BusEventClass final : cc::event::BusEventTrait { \ + using BusType = EventBusName_(EventBusClass); \ + using Listener = cc::event::Listener; \ + constexpr static const char *BUS_NAME = EventBusName_(EventBusClass)::BUS_NAME; \ + constexpr static const char *HANDLE_CLASS = #BusEventClass; \ + constexpr static size_t TypeID() { \ + return cc::event::intl::hash(#BusEventClass); \ + } \ + static inline void broadcast(ArgType0 arg0, ArgType1 arg1, ArgType2 arg2, ArgType3 arg3, ArgType4 arg4, ArgType5 arg5, ArgType6 arg6, ArgType7 arg7, ArgType8 arg8, ArgType9 arg9, ArgType10 arg10, ArgType11 arg11, ArgType12 arg12, ArgType13 arg13, ArgType14 arg14, ArgType15 arg15, ArgType16 arg16, ArgType17 arg17, ArgType18 arg18, ArgType19 arg19) { \ + cc::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19); \ + } \ + }; diff --git a/cocos/core/event/intl/EventIntl.h b/cocos/core/event/intl/EventIntl.h new file mode 100644 index 0000000..ee3fe45 --- /dev/null +++ b/cocos/core/event/intl/EventIntl.h @@ -0,0 +1,481 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include +#include +#include +namespace cc { +namespace event { +namespace intl { +template +struct hash_calc { // NOLINT + static constexpr size_t apply(const char (&str)[N]) { + return (hash_calc::apply(str) ^ str[I]) * 16777619U; + }; +}; + +template +struct hash_calc { // NOLINT + static constexpr size_t apply(const char (&/*used*/)[N]) { + return 2166136261U; + }; +}; + +template +constexpr size_t hash(const char (&str)[N]) { + return hash_calc::apply(str); +} + +template +struct HeadType; + +template +struct HeadType { + using head = Head; + using remain = HeadType; +}; + +template +struct HeadType { + using head = Head; + using remain = HeadType<>; +}; + +template <> +struct HeadType<> { + using head = void; + using remain = HeadType<>; +}; + +template +constexpr bool validateParameters() { + return true; +} + +template +constexpr bool validateParameters(Head && /*unused*/) { + using element_t = std::remove_reference_t>; + using head = std::remove_reference_t; + constexpr bool assignable = std::is_assignable_v || std::is_convertible_v; + constexpr bool ret = assignable; + static_assert(ret, "Parameter type incorrect"); + return ret; +} + +template +constexpr bool validateParameters(Head &&head, ARGS &&...remain) { + return validateParameters(std::forward(head)) && validateParameters(std::forward(remain)...); +} + +template +struct TupleExtractor { + using func_type = void(); +}; + +template +struct TupleExtractor> { + using func_type = void(ARGS...); + using std_func_type = std::function; +}; + +template +struct FunctionTrait : public FunctionTrait { + constexpr static bool IS_LAMBDA = true; +}; + +template +struct FunctionTrait { + constexpr static bool IS_LAMBDA = false; + using function_type = R (C::*)(ARGS...); +}; + +template +struct FunctionTrait { + constexpr static bool IS_LAMBDA = false; + using function_type = R (C::*)(ARGS...) const; +}; + +template +struct FunctionTrait { + using function_type = R (*)(ARGS...); + constexpr static bool IS_LAMBDA = false; +}; + +template +struct FunctionTrait { + using function_type = R(ARGS...); + constexpr static bool IS_LAMBDA = false; +}; +template +struct FunctionTrait> { + using function_type = std::function; + constexpr static bool IS_LAMBDA = false; +}; + +template +struct RemoveClass { + using type = T; +}; + +template +struct RemoveClass { + using type = std::function; +}; +template +struct RemoveClass { + using type = std::function; +}; + +template +using function_type_t = typename FunctionTrait::function_type; + +template +using lambda_without_class_t = typename RemoveClass>::type; + +template +inline auto convertLambda(F &&lambda) { + if constexpr (FunctionTrait::IS_LAMBDA) { + return static_cast>(std::forward(lambda)); + } else { + return static_cast>(std::forward(lambda)); + } +} +template +struct TgtEvtFnTrait; + +template +struct TgtEvtFnTrait { + using param0_type = C; + using src_func_type = R(C *, ARGS...); + constexpr static bool IS_MEMBER_FUNC = false; + template + static constexpr bool accept() { + if constexpr (sizeof...(ARGS) == 0) { + using event_type = typename TgtEvent::EventType; + return std::is_same_v; + } else { + return false; + } + } + template + static auto wrap(src_func_type func) { + using event_type = typename TgtEvent::EventType; + using persist_function_type = typename TgtEvent::_persist_function_type; + using emitter_type = typename TgtEvent::_emitter_type; + persist_function_type ret; + if constexpr (accept()) { + ret = [&func](emitter_type * /*self*/, event_type *event) { + func(event); + }; + } else { + static_assert(std::is_same_v, "mismatch emitter type"); + ret = [func](emitter_type *self, event_type *event) { + return std::apply([self, func](auto &&...args) { func(self, args...); }, event->args); + }; + } + return ret; + } +}; +template +struct TgtEvtFnTrait { + using param0_type = C; + using src_func_type = R(const C *, ARGS...); + constexpr static bool IS_MEMBER_FUNC = false; + template + static constexpr bool accept() { + if constexpr (sizeof...(ARGS) == 0) { + using event_type = typename TgtEvent::EventType; + return std::is_same_v; + } else { + return false; + } + } + template + static auto wrap(src_func_type func) { + using event_type = typename TgtEvent::EventType; + using emitter_type = typename TgtEvent::_emitter_type; + using persist_function_type = typename TgtEvent::_persist_function_type; + persist_function_type ret; + if constexpr (accept()) { + ret = [func](emitter_type * /*self*/, event_type *event) { + func(event); + }; + } else { + static_assert(std::is_same_v, "mismatch emitter type"); + ret = [func](emitter_type *self, event_type *event) { return std::apply([self, func](auto &&...args) { func(self, args...); }, event->args); }; + } + return ret; + } +}; +template +struct TgtEvtFnTrait { + using param0_type = C; + using src_func_type = R (*)(C *, ARGS...); + constexpr static bool IS_MEMBER_FUNC = false; + template + static constexpr bool accept() { + if constexpr (sizeof...(ARGS) == 0) { + using event_type = typename TgtEvent::EventType; + return std::is_same_v; + } else { + return false; + } + } + template + static auto wrap(src_func_type func) { + using EventType = typename TgtEvent::EventType; + using persist_function_type = typename TgtEvent::_persist_function_type; + using emitter_type = typename TgtEvent::_emitter_type; + persist_function_type ret; + if constexpr (accept()) { + auto ret2 = [func](emitter_type * /*self*/, EventType *event) { + func(event); + }; + ret = ret2; + } else { + static_assert(std::is_same_v, "mismatch emitter type"); + ret = [func](emitter_type *self, EventType *event) { + return std::apply([self, func](auto &&...args) { func(self, args...); }, event->args); + }; + } + return ret; + } +}; +template +struct TgtEvtFnTrait { + using src_func_type = R (*)(const C *, ARGS...); + using param0_type = C; + constexpr static bool IS_MEMBER_FUNC = false; + template + static constexpr bool accept() { + if constexpr (sizeof...(ARGS) == 0) { + using event_type = typename TgtEvent::EventType; + return std::is_same_v; + } else { + return false; + } + } + template + static auto wrap(src_func_type func) { + using event_type = typename TgtEvent::EventType; + using emitter_type = typename TgtEvent::_emitter_type; + using persist_function_type = typename TgtEvent::_persist_function_type; + persist_function_type ret; + if constexpr (accept()) { + ret = [&func](emitter_type * /*self*/, event_type *event) { + func(event); + }; + return ret; + } else { + static_assert(std::is_same_v, "mismatch emitter type"); + ret = [func](emitter_type *self, event_type *event) { + return std::apply([self, func](auto &&...args) { func(self, args...); }, event->args); + }; + } + return ret; + } +}; +template +struct TgtEvtFnTrait> { + using src_func_type = std::function; + using param0_type = C; + constexpr static bool IS_MEMBER_FUNC = false; + template + static constexpr bool accept() { + if constexpr (sizeof...(ARGS) == 0) { + using event_type = typename TgtEvent::EventType; + return std::is_same_v; + } else { + return false; + } + } + template + static auto wrap(src_func_type func) { + using event_type = typename TgtEvent::EventType; + using emitter_type = typename TgtEvent::_emitter_type; + using persist_function_type = typename TgtEvent::_persist_function_type; + persist_function_type ret; + if constexpr (accept()) { + ret = [&func](emitter_type * /*self*/, event_type *event) { + func(event); + }; + } else { + static_assert(std::is_same_v, "mismatch emitter type"); + ret = [func](emitter_type *self, event_type *event) { + return std::apply([self, func](auto &&...args) { func(self, args...); }, event->args); + }; + } + return ret; + } +}; +template +struct TgtEvtFnTrait> { + using param0_type = C; + using src_func_type = std::function; + constexpr static bool IS_MEMBER_FUNC = false; + template + static constexpr bool accept() { + if constexpr (sizeof...(ARGS) == 0) { + using EventType = typename TgtEvent::EventType; + return std::is_same_v; + } else { + return false; + } + } + template + static auto wrap(src_func_type func) { + using event_type = typename TgtEvent::EventType; + using emitter_type = typename TgtEvent::_emitter_type; + using persist_function_type = typename TgtEvent::_persist_function_type; + persist_function_type ret; + if constexpr (accept()) { + ret = [func](emitter_type * /*self*/, event_type *event) { + func(event); + }; + } else { + static_assert(std::is_same_v, "mismatch emitter type"); + ret = [func](emitter_type *self, event_type *event) { + return std::apply([self, func](auto &&...args) { func(self, args...); }, event->args); + }; + } + return ret; + } +}; +template +struct TgtEvtFnTrait { + using src_func_type = R (C::*)(ARGS...); + using context_type = C; + using param0_type = typename intl::HeadType::head; + constexpr static bool IS_MEMBER_FUNC = true; + template + static constexpr bool accept() { + if constexpr (sizeof...(ARGS) == 1) { + using event_type = typename TgtEvent::EventType; + return std::is_same_v; + } else { + return false; + } + } + + template + static auto wrap(src_func_type func) { + using event_type = typename TgtEvent::EventType; + using emitter_type = typename TgtEvent::_emitter_type; + using persist_function_type = typename TgtEvent::_persist_function_type; + persist_function_type ret; + static_assert(std::is_same_v, "mismatch emitter type"); + if constexpr (accept()) { + ret = [&func](emitter_type *self, event_type *event) { + (self->*func)(event); + }; + } else { + ret = [func](emitter_type *self, event_type *event) { + return std::apply([&self, func](auto &&...args) { (self->*func)(args...); }, event->args); + }; + } + return ret; + } + template + static auto wrapWithContext(src_func_type func, context_type *ctx) { + using emitter_type = typename TgtEvent::_emitter_type; + using event_type = typename TgtEvent::EventType; + using persist_function_type = typename TgtEvent::_persist_function_type; + persist_function_type ret; + if constexpr (accept()) { + ret = [func, ctx](emitter_type * /*self*/, event_type *event) { + (ctx->*func)(event); + }; + } else { + ret = [func, ctx](emitter_type * /*self*/, event_type *event) { + return std::apply([ctx, func](auto... args) { (ctx->*func)(args...); }, event->args); + }; + } + return ret; + } +}; +template +struct TgtEvtFnTrait { + using src_func_type = R (C::*)(ARGS...) const; + using context_type = C; + using param0_type = typename intl::HeadType::head; + constexpr static bool IS_MEMBER_FUNC = true; + + template + static constexpr bool accept() { + if constexpr (sizeof...(ARGS) == 1) { + using event_type = typename TgtEvent::EventType; + return std::is_same_v; + } else { + return false; + } + } + + template + static auto wrap(src_func_type func) { + using EventType = typename TgtEvent::EventType; + using persist_function_type = typename TgtEvent::_persist_function_type; + using emitter_type = typename TgtEvent::_emitter_type; + persist_function_type ret; + static_assert(std::is_same_v, "mismatch emitter type"); + if constexpr (accept()) { + ret = [&func](context_type *self, EventType *event) { + (self->*func)(event); + }; + } else { + ret = [&func](context_type *self, EventType *event) { + return std::apply([self, func](auto &&...args) { (self->*func)(args...); }, event->args); + }; + } + return ret; + } + + template + static auto wrapWithContext(src_func_type func, context_type *ctx) { + using emitter_type = typename TgtEvent::_emitter_type; + using event_type = typename TgtEvent::EventType; + using persist_function_type = typename TgtEvent::_persist_function_type; + persist_function_type ret = [func, ctx](emitter_type * /*self*/, event_type *event) { + return std::apply([ctx, func](auto &&...args) { (ctx->*func)(args...); }, event->args); + }; + return ret; + } +}; + +template +constexpr T addAll(T first) { + return first; +} +template +constexpr T addAll(T first, Others... others) { + return first + addAll(others...); +} + +template +constexpr int TotalEvents = addAll(Parents::getEventCount()...); // NOLINT + +} // namespace intl +} // namespace event +} // namespace cc diff --git a/cocos/core/event/intl/EventTargetMacros.h b/cocos/core/event/intl/EventTargetMacros.h new file mode 100644 index 0000000..610ce30 --- /dev/null +++ b/cocos/core/event/intl/EventTargetMacros.h @@ -0,0 +1,64 @@ +// generated code + +#define TARGET_EVENT_ARG1(EventType, ArgType0) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0) + +#define TARGET_EVENT_ARG2(EventType, ArgType0, ArgType1) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1) + +#define TARGET_EVENT_ARG3(EventType, ArgType0, ArgType1, ArgType2) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2) + +#define TARGET_EVENT_ARG4(EventType, ArgType0, ArgType1, ArgType2, ArgType3) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3) + +#define TARGET_EVENT_ARG5(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4) + +#define TARGET_EVENT_ARG6(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5) + +#define TARGET_EVENT_ARG7(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6) + +#define TARGET_EVENT_ARG8(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7) + +#define TARGET_EVENT_ARG9(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8) + +#define TARGET_EVENT_ARG10(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9) + +#define TARGET_EVENT_ARG11(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10) + +#define TARGET_EVENT_ARG12(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11) + +#define TARGET_EVENT_ARG13(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12) + +#define TARGET_EVENT_ARG14(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13) + +#define TARGET_EVENT_ARG15(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14) + +#define TARGET_EVENT_ARG16(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15) + +#define TARGET_EVENT_ARG17(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16) + +#define TARGET_EVENT_ARG18(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17) + +#define TARGET_EVENT_ARG19(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17, ArgType18) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17, ArgType18) + +#define TARGET_EVENT_ARG20(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17, ArgType18, ArgType19) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17, ArgType18, ArgType19) + +#define TARGET_EVENT_ARG21(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17, ArgType18, ArgType19, ArgType20) \ + _DECLARE_TARGET_EVENT_INTER(EventType, ArgType0, ArgType1, ArgType2, ArgType3, ArgType4, ArgType5, ArgType6, ArgType7, ArgType8, ArgType9, ArgType10, ArgType11, ArgType12, ArgType13, ArgType14, ArgType15, ArgType16, ArgType17, ArgType18, ArgType19, ArgType20) diff --git a/cocos/core/event/intl/List.h b/cocos/core/event/intl/List.h new file mode 100644 index 0000000..459b9e6 --- /dev/null +++ b/cocos/core/event/intl/List.h @@ -0,0 +1,105 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include "base/Macros.h" +namespace cc { +namespace event { +namespace intl { + +template +bool listAppend(ListNode **head, ListNode *newNode) { + if (newNode->next != nullptr || newNode->prev != nullptr) { + CC_ABORT(); + return false; + } + if (*head == nullptr) { + newNode->next = newNode; + newNode->prev = newNode; + *head = newNode; + } else { + auto *first = *head; + auto *last = (*head)->prev; + newNode->prev = last; + newNode->next = first; + first->prev = newNode; + last->next = newNode; + } + return true; +} + +template +bool detachFromList(ListNode **head, ListNode *node) { + if (*head == nullptr || node->prev == nullptr || node->next == nullptr) { + CC_ABORT(); + return false; + } + if (node->prev == node && node->next == node) { // the only node + CC_ASSERT(node == *head); // should be the first + *head = nullptr; + } else { + auto *nextNode = node->next; + auto *prevNode = node->prev; + nextNode->prev = prevNode; + prevNode->next = nextNode; + if (node == *head) { + *head = nextNode; + } + } + node->prev = nullptr; + node->next = nullptr; + return true; +} + +} // namespace intl +} // namespace event +} // namespace cc + +#define EVENT_LIST_LOOP_BEGIN(tempVar, list) \ + if (list) { \ + auto *tempVar = list; \ + do { \ + auto *nextCopy = tempVar->next; + +#define EVENT_LIST_LOOP_END(tempVar, list) \ + tempVar = nextCopy; \ + } \ + while (tempVar != list) \ + ; \ + } + +#define EVENT_LIST_LOOP_REV_BEGIN(tempVar, list) \ + if (list) { \ + auto *tempVar = list->prev; \ + bool isLastListNode = false; \ + do { \ + auto *nextCopy = tempVar->prev; \ + isLastListNode = tempVar == list; + +#define EVENT_LIST_LOOP_REV_END(tempVar, list) \ + tempVar = nextCopy; \ + } \ + while (!isLastListNode) \ + ; \ + } diff --git a/cocos/core/geometry/AABB.cpp b/cocos/core/geometry/AABB.cpp new file mode 100644 index 0000000..898b051 --- /dev/null +++ b/cocos/core/geometry/AABB.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "AABB.h" +#include "base/Macros.h" +#include "cocos/core/geometry/Enums.h" +#include "cocos/core/geometry/Sphere.h" + +namespace cc { +namespace geometry { + +Sphere *AABB::toBoundingSphere(Sphere *out, const AABB &a) { + out->setCenter(a.getCenter()); + out->setRadius(a.getHalfExtents().length()); + return out; +} + +AABB *AABB::fromPoints(const Vec3 &minPos, const Vec3 &maxPos, AABB *dst) { + Vec3 center{(minPos + maxPos) * 0.5F}; + Vec3 halfExtents{(maxPos - minPos) * 0.5F}; + dst->setCenter(center); + dst->setHalfExtents(halfExtents); + return dst; +} + +AABB *AABB::merge(AABB *out, const AABB &a, const AABB &b) { + Vec3 minCornor; + Vec3 maxCorner; + Vec3::max(a.getCenter() + a.getHalfExtents(), b.getCenter() + b.getHalfExtents(), &maxCorner); + Vec3::min(a.getCenter() - a.getHalfExtents(), b.getCenter() - b.getHalfExtents(), &minCornor); + return AABB::fromPoints(minCornor, maxCorner, out); +} + +void AABB::merge(const cc::Vec3 &point) { + cc::Vec3 minPos = getCenter() - getHalfExtents(); + cc::Vec3 maxPos = getCenter() + getHalfExtents(); + if (point.x < minPos.x) { + minPos.x = point.x; + } + if (point.y < minPos.y) { + minPos.y = point.y; + } + if (point.z < minPos.z) { + minPos.z = point.z; + } + if (point.x > maxPos.x) { + maxPos.x = point.x; + } + if (point.y > maxPos.y) { + maxPos.y = point.y; + } + if (point.z > maxPos.z) { + maxPos.z = point.z; + } + + const Vec3 center = (minPos + maxPos) * 0.5F; + setCenter(center); + setHalfExtents(maxPos.x - center.x, maxPos.y - center.y, maxPos.z - center.z); +} + +void AABB::merge(const ccstd::vector &points) { + for (const auto &p : points) { + merge(p); + } +} + +void AABB::merge(const Frustum &frustum) { + const ccstd::array &vertices = frustum.vertices; + for (size_t i = 0; i < vertices.max_size(); ++i) { + merge(vertices[i]); + } +} + +bool AABB::aabbAabb(const AABB &aabb) const { + Vec3 aMin; + Vec3 aMax; + Vec3 bMin; + Vec3 bMax; + Vec3::subtract(getCenter(), getHalfExtents(), &aMin); + Vec3::add(getCenter(), getHalfExtents(), &aMax); + Vec3::subtract(aabb.getCenter(), aabb.getHalfExtents(), &bMin); + Vec3::add(aabb.getCenter(), aabb.getHalfExtents(), &bMax); + return (aMin.x <= bMax.x && aMax.x >= bMin.x) && + (aMin.y <= bMax.y && aMax.y >= bMin.y) && + (aMin.z <= bMax.z && aMax.z >= bMin.z); +} + +int AABB::aabbPlane(const Plane &plane) const { + auto r = getHalfExtents().x * std::abs(plane.n.x) + + getHalfExtents().y * std::abs(plane.n.y) + + getHalfExtents().z * std::abs(plane.n.z); + auto dot = Vec3::dot(plane.n, getCenter()); + if (dot + r < plane.d) { + return -1; + } + if (dot - r > plane.d) { + return 0; + } + return 1; +} + +bool AABB::aabbFrustum(const Frustum &frustum) const { + const auto &planes = frustum.planes; + const auto *self = this; + return std::all_of(planes.begin(), + planes.end(), + // frustum plane normal points to the inside + [self](const Plane *plane) { return self->aabbPlane(*plane) != -1; }); +} + +void AABB::getBoundary(cc::Vec3 *minPos, cc::Vec3 *maxPos) const { + *maxPos = getCenter() + getHalfExtents(); + *minPos = getCenter() - getHalfExtents(); +} + +void AABB::merge(const AABB &aabb) { + AABB::merge(this, aabb, *this); +} + +void AABB::set(const cc::Vec3 ¢erVal, const cc::Vec3 &halfExtentVal) { + setCenter(centerVal); + setHalfExtents(halfExtentVal); +} + +void AABB::transform(const Mat4 &m, AABB *out) const { + Vec3::transformMat4(center, m, &out->center); + transformExtentM4(&out->halfExtents, getHalfExtents(), m); +} + +bool AABB::contain(const cc::Vec3 &point) const { + cc::Vec3 minPos = getCenter() - getHalfExtents(); + cc::Vec3 maxPos = getCenter() + getHalfExtents(); + + return !(point.x > maxPos.x || point.x < minPos.x || + point.y > maxPos.y || point.y < minPos.y || + point.z > maxPos.z || point.z < minPos.z); +} + +// https://zeuxcg.org/2010/10/17/aabb-from-obb-with-component-wise-abs/ +void AABB::transformExtentM4(Vec3 *out, const Vec3 &extent, const Mat4 &m4) { + Mat3 m3Tmp; + m3Tmp.m[0] = std::abs(m4.m[0]); + m3Tmp.m[1] = std::abs(m4.m[1]); + m3Tmp.m[2] = std::abs(m4.m[2]); + m3Tmp.m[3] = std::abs(m4.m[4]); + m3Tmp.m[4] = std::abs(m4.m[5]); + m3Tmp.m[5] = std::abs(m4.m[6]); + m3Tmp.m[6] = std::abs(m4.m[8]); + m3Tmp.m[7] = std::abs(m4.m[9]); + m3Tmp.m[8] = std::abs(m4.m[10]); + out->transformMat3(extent, m3Tmp); +} + +AABB::AABB(float px, float py, float pz, float hw, float hh, float hl) : AABB() { + setCenter(px, py, pz); + setHalfExtents(hw, hh, hl); +} + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/AABB.h b/cocos/core/geometry/AABB.h new file mode 100644 index 0000000..27908df --- /dev/null +++ b/cocos/core/geometry/AABB.h @@ -0,0 +1,164 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/memory/Memory.h" +#include "base/std/container/vector.h" +#include "core/geometry/Enums.h" +#include "math/Mat3.h" +#include "math/Quaternion.h" +#include "math/Vec3.h" + +namespace cc { +namespace geometry { + +class Sphere; +class Frustum; +class Plane; + +class AABB final : public ShapeBase { +public: + /** + * @en + * create a new AABB + * @zh + * 创建一个新的 AABB 实例。 + * @param px - AABB 的原点的 X 坐标。 + * @param py - AABB 的原点的 Y 坐标。 + * @param pz - AABB 的原点的 Z 坐标。 + * @param hw - AABB 宽度的一半。 + * @param hh - AABB 高度的一半。 + * @param hl - AABB 长度的一半。 + * @returns 返回新创建的 AABB 实例。 + */ + static AABB *create(float px, float py, float pz, float hw, float hh, float hl) { + return ccnew AABB(px, py, pz, hw, hh, hl); + } + + /** + * @en + * Set the components of a AABB to the given values + * @zh + * 将 AABB 的属性设置为给定的值。 + * @param {AABB} out 接受操作的 AABB。 + * @param px - AABB 的原点的 X 坐标。 + * @param py - AABB 的原点的 Y 坐标。 + * @param pz - AABB 的原点的 Z 坐标。 + * @param hw - AABB 宽度的一半。 + * @param hh - AABB 高度的一半。 + * @param hl - AABB 长度度的一半。 + * @return {AABB} out 接受操作的 AABB。 + */ + + static AABB *set(AABB *out, float px, float py, + float pz, + float hw, + float hh, + float hl) { + out->setCenter(px, py, pz); + out->setHalfExtents(hw, hh, hl); + return out; + } + + /** + * @en + * Merge tow AABB. + * @zh + * 合并两个 AABB 到 out。 + * @param out 接受操作的 AABB。 + * @param a 输入的 AABB。 + * @param b 输入的 AABB。 + * @returns {AABB} out 接受操作的 AABB。 + */ + static AABB *merge(AABB *out, const AABB &a, const AABB &b); + + /** + * @en + * AABB to sphere + * @zh + * 包围盒转包围球 + * @param out 接受操作的 sphere。 + * @param a 输入的 AABB。 + */ + + static Sphere *toBoundingSphere(Sphere *out, const AABB &a); + + static AABB *fromPoints(const Vec3 &minPos, const Vec3 &maxPos, AABB *dst); + static void transformExtentM4(Vec3 *out, const Vec3 &extent, const Mat4 &m4); + + AABB(float px, float py, float pz, float hw, float hh, float hl); + AABB() : ShapeBase(ShapeEnum::SHAPE_AABB) {} + ~AABB() override = default; + + /** + * @en + * aabb-plane intersect detect. + * @zh + * 轴对齐包围盒和平面的相交性检测。 + * @param {AABB} aabb 轴对齐包围盒 + * @param {Plane} plane 平面 + * @return {number} inside(back) = -1, outside(front) = 0, intersect = 1 + */ + bool aabbAabb(const AABB &aabb) const; + + /** + * @en + * aabb-frustum intersect detect, faster but has false positive corner cases. + * @zh + * 轴对齐包围盒和锥台相交性检测,速度快,但有错误情况。 + * @param {AABB} aabb 轴对齐包围盒 + * @param {Frustum} frustum 锥台 + * @return {number} 0 或 非0 + */ + bool aabbFrustum(const Frustum &) const; + + int aabbPlane(const Plane &) const; + void getBoundary(cc::Vec3 *minPos, cc::Vec3 *maxPos) const; + void merge(const AABB &aabb); + void merge(const cc::Vec3 &point); + void merge(const ccstd::vector &points); + void merge(const Frustum &frustum); + void set(const cc::Vec3 ¢erVal, const cc::Vec3 &halfExtentVal); + void transform(const Mat4 &m, AABB *out) const; + bool contain(const cc::Vec3 &point) const; + inline void setCenter(float x, float y, float z) { center.set(x, y, z); } + inline void setCenter(const Vec3 ¢er) { this->center.set(center); } + inline void setValid(bool isValid) { _isValid = isValid; } + inline const Vec3 &getCenter() const { return center; } + inline bool isValid() const { return _isValid; } + inline void setHalfExtents(float x, float y, float z) { halfExtents.set(x, y, z); } + inline void setHalfExtents(const Vec3 &halfExtents) { this->halfExtents.set(halfExtents); } + inline const Vec3 &getHalfExtents() const { return halfExtents; } + + cc::Vec3 center; + cc::Vec3 halfExtents{1, 1, 1}; + +private: + bool _isValid{true}; +}; + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Capsule.cpp b/cocos/core/geometry/Capsule.cpp new file mode 100644 index 0000000..9911776 --- /dev/null +++ b/cocos/core/geometry/Capsule.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "cocos/core/geometry/Capsule.h" +#include "cocos/math/Vec3.h" +namespace cc { +namespace geometry { + +void Capsule::transform(const Mat4 &m, const Vec3 & /*pos*/, const Quaternion &rot, const Vec3 &scale, Capsule *out) const { + const auto maxComponent = mathutils::absMaxComponent(scale); + out->radius = this->radius * std::abs(maxComponent); + + const auto halfTotalWorldHeight = (this->halfHeight + this->radius) * std::abs(scale.y); + auto halfWorldHeight = halfTotalWorldHeight - out->radius; + if (halfWorldHeight < 0) halfWorldHeight = 0; + out->halfHeight = halfWorldHeight; + + out->center.transformMat4(this->center, m); + Quaternion::multiply(this->rotation, rot, &out->rotation); + out->updateCache(); +} + +void Capsule::updateCache() { + updateLocalCenter(); + ellipseCenter0.transformQuat(rotation); + ellipseCenter1.transformQuat(rotation); + ellipseCenter0 += center; + ellipseCenter1 += center; +} + +void Capsule::updateLocalCenter() { + switch (axis) { + case CenterEnum::X: + ellipseCenter0 = {halfHeight, 0, 0}; + ellipseCenter1 = {-halfHeight, 0, 0}; + break; + case CenterEnum::Y: + ellipseCenter0 = {0, halfHeight, 0}; + ellipseCenter1 = {0, -halfHeight, 0}; + break; + case CenterEnum::Z: + ellipseCenter0 = {0, 0, halfHeight}; + ellipseCenter1 = {0, 0, -halfHeight}; + break; + default: + CC_ABORT(); + } +} +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Capsule.h b/cocos/core/geometry/Capsule.h new file mode 100644 index 0000000..20ddbf3 --- /dev/null +++ b/cocos/core/geometry/Capsule.h @@ -0,0 +1,130 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/geometry/Enums.h" +#include "math/Mat4.h" +#include "math/Quaternion.h" +#include "math/Utils.h" +#include "math/Vec3.h" + +namespace cc { +namespace geometry { + +/** + * @en + * Basic Geometry: capsule. + * @zh + * 基础几何,胶囊体。 + */ +class Capsule final : public ShapeBase { + /** + * @en + * Gets the type of the shape. + * @zh + * 获取形状的类型。 + */ +public: + enum class CenterEnum { + X = 0, + Y = 1, + Z = 2 + }; + + /** + * @en + * Capsule sphere radius. + * @zh + * 胶囊体球部半径。 + */ + float radius{0.0F}; + + /** + * @en + * The distance between the center point of the capsule and the center of the sphere. + * @zh + * 胶囊体中心点和球部圆心的距离。 + */ + float halfHeight{0.0F}; + + /** + * @en + * Local orientation of capsule [0,1,2] => [x,y,z]. + * @zh + * 胶囊体的本地朝向,映射关系 [0,1,2] => [x,y,z]。 + */ + CenterEnum axis; + + /** + * @en + * The origin of the capsule. + * @zh + * 胶囊体的原点。 + */ + Vec3 center; + + /** + * @en + * The rotation of the capsule. + * @zh + * 胶囊体的旋转。 + */ + Quaternion rotation; + + /** cache, local center of ellipse */ + Vec3 ellipseCenter0; + Vec3 ellipseCenter1; + + explicit Capsule(float radius = 0.5, float halfHeight = 0.5, CenterEnum axis = CenterEnum::Y) : ShapeBase(ShapeEnum::SHAPE_CAPSULE) { + this->radius = radius; + this->halfHeight = halfHeight; + this->axis = axis; + this->center = {}; + this->rotation = {}; + ellipseCenter0 = {0, halfHeight, 0}; + ellipseCenter1 = {0, -halfHeight, 0}; + updateCache(); + } + + Capsule(const Capsule &other) = default; + Capsule(Capsule &&other) = default; + Capsule &operator=(const Capsule &other) = default; + Capsule &operator=(Capsule &&other) = default; + ~Capsule() override = default; + + /** + * @en + * Transform this capsule. + * @zh + * 变换此胶囊体。 + */ + void transform(const Mat4 &m, const Vec3 &pos, const Quaternion &rot, const Vec3 &scale, Capsule *out) const; + +private: + void updateCache(); + void updateLocalCenter(); +}; +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Curve.cpp b/cocos/core/geometry/Curve.cpp new file mode 100644 index 0000000..5bf3c30 --- /dev/null +++ b/cocos/core/geometry/Curve.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "cocos/core/geometry/Curve.h" +namespace cc { +namespace geometry { + +float OptimizedKey::evaluate(float tt) { + const t = tt - time; + return evalOptCurve(t, coefficient); +} + +float evalOptCurve(float t, const ccstd::vector &coefs) { + return (t * (t * (t * coefs[0] + coefs[1]) + coefs[2])) + coefs[3]; +} +} // namespace geometry +} // namespace cc \ No newline at end of file diff --git a/cocos/core/geometry/Curve.h b/cocos/core/geometry/Curve.h new file mode 100644 index 0000000..15b7c04 --- /dev/null +++ b/cocos/core/geometry/Curve.h @@ -0,0 +1,386 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace cc { +namespace geometry { + +constexpr auto LOOK_FORWARD = 3; + +/** + * @en + * A key frame in the curve. + * @zh + * 曲线中的一个关键帧。 + */ +struct Keyframe { + /** + * @en Current frame time. + * @zh 当前帧时间。 + */ + float time = 0; + + /** + * @en Current frame value. + * @zh 当前帧的值。 + */ + float value = 0; + + /** + * @en In tangent value. + * @zh 左切线。 + */ + float inTangent = 0; + + /** + * @en Out tangent value. + * @zh 右切线。 + */ + float outTangent = 0; +}; + +float evalOptCurve(float t, const ccstd::vector &coefs); + +struct OptimizedKey { + float index; + float time; + float endTime; + ccstd::vector coefficient; + OptimizedKey() { + index = -1; + time = 0; + endTime = 0; + coefficient.resize(4); + } + static float evaluate(float t); +}; + +/** + * @en + * Describe a curve in which three times Hermite interpolation is used for each adjacent key frame. + * @zh + * 描述一条曲线,其中每个相邻关键帧采用三次hermite插值计算。 + */ +class AnimationCurve { + // @serializable +private: + // _curve ! : RealCurve; + +public: + static ccstd::vector defaultKF = [ { + time : 0, + value : 1, + inTangent : 0, + outTangent : 0, + }, + { + time : 1, + value : 1, + inTangent : 0, + outTangent : 0, + } ]; + + /** + * For internal usage only. + * @internal + */ + get _internalCurve() { + return this._curve; + } + + /** + * @en + * The key frame of the curve. + * @zh + * 曲线的关键帧。 + */ + auto getKeyFrames() { + return Array.from(this._curve.keyframes()).map(([ time, value ]) = > { + const legacyKeyframe = ccnew Keyframe(); + legacyKeyframe.time = time; + legacyKeyframe.value = value.value; + legacyKeyframe.inTangent = value.leftTangent; + legacyKeyframe.outTangent = value.rightTangent; + return legacyKeyframe; + }); + } + + void setKeyFrames(value) { + this._curve.assignSorted(value.map((legacyCurve) = > [ + legacyCurve.time, + { + interpolationMode : RealInterpolationMode.CUBIC, + value : legacyCurve.value, + leftTangent : legacyCurve.inTangent, + rightTangent : legacyCurve.outTangent, + }, + ])); + } + + /** + * @en + * Loop mode [[WrapMode]] when the sampling time exceeds the left end. + * @zh + * 当采样时间超出左端时采用的循环模式[[WrapMode]]。 + */ + auto getPreWrapMode() { + return toLegacyWrapMode(this._curve.preExtrapolation); + } + + void sPreWrapMode(value) { + this._curve.preExtrapolation = fromLegacyWrapMode(value); + } + + /** + * @en + * Cycle mode [[WrapMode]] when the sampling time exceeds the right end. + * @zh + * 当采样时间超出右端时采用的循环模式[[WrapMode]]。 + */ + get postWrapMode() { + return toLegacyWrapMode(this._curve.postExtrapolation); + } + + set postWrapMode(value) { + this._curve.postExtrapolation = fromLegacyWrapMode(value); + } + +private + cachedKey : OptimizedKey; + + /** + * 构造函数。 + * @param keyFrames 关键帧。 + */ + constructor(keyFrames + : Keyframe[] | null | RealCurve = null) { + if (keyFrames instanceof RealCurve) { + this._curve = keyFrames; + } else { + const curve = ccnew RealCurve(); + this._curve = curve; + curve.preExtrapolation = ExtrapolationMode.LOOP; + curve.postExtrapolation = ExtrapolationMode.CLAMP; + if (!keyFrames) { + curve.assignSorted([ + [ 0.0, {interpolationMode : RealInterpolationMode.CUBIC, value : 1.0} ], + [ 1.0, {interpolationMode : RealInterpolationMode.CUBIC, value : 1.0} ], + ]); + } else { + curve.assignSorted(keyFrames.map((legacyKeyframe) = > [ legacyKeyframe.time, { + interpolationMode : RealInterpolationMode.CUBIC, + value : legacyKeyframe.value, + leftTangent : legacyKeyframe.inTangent, + rightTangent : legacyKeyframe.outTangent, + } ])); + } + } + this.cachedKey = ccnew OptimizedKey(); + } + + /** + * @en + * Add a keyframe. + * @zh + * 添加一个关键帧。 + * @param keyFrame 关键帧。 + */ +public + addKey(keyFrame + : Keyframe | null) { + if (!keyFrame) { + this._curve.clear(); + } else { + this._curve.addKeyFrame(keyFrame.time, { + interpolationMode : RealInterpolationMode.CUBIC, + value : keyFrame.value, + leftTangent : keyFrame.inTangent, + rightTangent : keyFrame.outTangent, + }); + } + } + + /** + * @ignore + * @param time + */ +public + evaluate_slow(time + : number) { + return this._curve.evaluate(time); + } + + /** + * @en + * Calculate the curve interpolation at a given point in time. + * @zh + * 计算给定时间点的曲线插值。 + * @param time 时间。 + */ +public + evaluate(time + : number) { + const {cachedKey, _curve : curve} = this; + const nKeyframes = curve.keyFramesCount; + const lastKeyframeIndex = nKeyframes - 1; + let wrappedTime = time; + const extrapolationMode = time < 0 ? curve.preExtrapolation : curve.postExtrapolation; + const startTime = curve.getKeyframeTime(0); + const endTime = curve.getKeyframeTime(lastKeyframeIndex); + switch (extrapolationMode) { + case ExtrapolationMode.LOOP: + wrappedTime = repeat(time - startTime, endTime - startTime) + startTime; + break; + case ExtrapolationMode.PING_PONG: + wrappedTime = pingPong(time - startTime, endTime - startTime) + startTime; + break; + case ExtrapolationMode.CLAMP: + default: + wrappedTime = clamp(time, startTime, endTime); + break; + } + if (wrappedTime >= cachedKey.time && wrappedTime < cachedKey.endTime) { + return cachedKey.evaluate(wrappedTime); + } + const leftIndex = this.findIndex(cachedKey, wrappedTime); + const rightIndex = Math.min(leftIndex + 1, lastKeyframeIndex); + this.calcOptimizedKey(cachedKey, leftIndex, rightIndex); + return cachedKey.evaluate(wrappedTime); + } + + /** + * @ignore + * @param optKey + * @param leftIndex + * @param rightIndex + */ +public + calcOptimizedKey(optKey + : OptimizedKey, leftIndex + : number, rightIndex + : number) { + const lhsTime = this._curve.getKeyframeTime(leftIndex); + const rhsTime = this._curve.getKeyframeTime(rightIndex); + const {value : lhsValue, leftTangent : lhsOutTangent} = this._curve.getKeyframeValue(leftIndex); + const {value : rhsValue, rightTangent : rhsInTangent} = this._curve.getKeyframeValue(rightIndex); + optKey.index = leftIndex; + optKey.time = lhsTime; + optKey.endTime = rhsTime; + + const dx = rhsTime - lhsTime; + const dy = rhsValue - lhsValue; + const length = 1 / (dx * dx); + const d1 = lhsOutTangent * dx; + const d2 = rhsInTangent * dx; + + optKey.coefficient[0] = (d1 + d2 - dy - dy) * length / dx; + optKey.coefficient[1] = (dy + dy + dy - d1 - d1 - d2) * length; + optKey.coefficient[2] = lhsOutTangent; + optKey.coefficient[3] = lhsValue; + } + + /** + * @ignore + * @param optKey + * @param t + */ +private + findIndex(optKey + : OptimizedKey, t + : number) { + const {_curve : curve} = this; + const nKeyframes = curve.keyFramesCount; + const cachedIndex = optKey.index; + if (cachedIndex != = -1) { + const cachedTime = curve.getKeyframeTime(cachedIndex); + if (t > cachedTime) { + for (let i = 0; i < LOOK_FORWARD; i++) { + const currIndex = cachedIndex + i; + if (currIndex + 1 < nKeyframes && curve.getKeyframeTime(currIndex + 1) > t) { + return currIndex; + } + } + } else { + for (let i = 0; i < LOOK_FORWARD; i++) { + const currIndex = cachedIndex - i; + if (currIndex >= 0 && curve.getKeyframeTime(currIndex - 1) <= t) { + return currIndex - 1; + } + } + } + } + let left = 0; + let right = nKeyframes; + let mid; + while (right - left > 1) { + mid = Math.floor((left + right) / 2); + if (curve.getKeyframeTime(mid) >= t) { + right = mid; + } else { + left = mid; + } + } + return left; + } +} + +function +fromLegacyWrapMode(legacyWrapMode + : WrapModeMask) : ExtrapolationMode { + switch (legacyWrapMode) { + default: + case WrapModeMask.Default: + case WrapModeMask.Normal: + case WrapModeMask.Clamp: return ExtrapolationMode.CLAMP; + case WrapModeMask.PingPong: return ExtrapolationMode.PING_PONG; + case WrapModeMask.Loop: return ExtrapolationMode.LOOP; + } +} + +function toLegacyWrapMode(extrapolationMode + : ExtrapolationMode) : WrapModeMask { + switch (extrapolationMode) { + default: + case ExtrapolationMode.LINEAR: + case ExtrapolationMode.CLAMP: return WrapModeMask.Clamp; + case ExtrapolationMode.PING_PONG: return WrapModeMask.PingPong; + case ExtrapolationMode.LOOP: return WrapModeMask.Loop; + } +} + +/** + * Same as but more effective than `new LegacyCurve()._internalCurve`. + */ +export function constructLegacyCurveAndConvert() { + const curve = ccnew RealCurve(); + curve.assignSorted([ + [ 0.0, {interpolationMode : RealInterpolationMode.CUBIC, value : 1.0} ], + [ 1.0, {interpolationMode : RealInterpolationMode.CUBIC, value : 1.0} ], + ]); + return curve; +} + +} // namespace geometry +} // namespace cc \ No newline at end of file diff --git a/cocos/core/geometry/Distance.cpp b/cocos/core/geometry/Distance.cpp new file mode 100644 index 0000000..041debc --- /dev/null +++ b/cocos/core/geometry/Distance.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "cocos/core/geometry/Distance.h" + +#include "cocos/core/geometry/AABB.h" +#include "cocos/core/geometry/Obb.h" +#include "cocos/core/geometry/Plane.h" +#include "cocos/math/Mat4.h" + +#include +#include "base/std/container/array.h" +#include "cocos/math/Utils.h" +#include "cocos/math/Vec3.h" + +namespace cc { +namespace geometry { + +float pointPlane(const Vec3 &point, const Plane &plane) { + return Vec3::dot(plane.n, point) - plane.d; +} + +Vec3 *ptPointPlane(Vec3 *out, const Vec3 &point, const Plane &plane) { + auto t = pointPlane(point, plane); + *out = point - t * plane.n; + return out; +} + +Vec3 *ptPointAabb(Vec3 *out, const Vec3 &point, const AABB &aabb) { + auto min = aabb.getCenter() - aabb.getHalfExtents(); + auto max = aabb.getCenter() + aabb.getHalfExtents(); + *out = {cc::mathutils::clamp(point.x, min.x, max.x), + cc::mathutils::clamp(point.y, min.y, max.y), + cc::mathutils::clamp(point.z, min.z, max.z)}; + return out; +} + +Vec3 *ptPointObb(Vec3 *out, const Vec3 &point, const OBB &obb) { + ccstd::array u = { + Vec3{obb.orientation.m[0], obb.orientation.m[1], obb.orientation.m[2]}, + Vec3{obb.orientation.m[3], obb.orientation.m[4], obb.orientation.m[5]}, + Vec3{obb.orientation.m[6], obb.orientation.m[7], obb.orientation.m[8]}, + }; + ccstd::array e = {obb.halfExtents.x, obb.halfExtents.y, obb.halfExtents.z}; + + auto d = point - obb.center; + float dist = 0.0F; + + // Start result at center of obb; make steps from there + *out = obb.center; + + // For each OBB axis... + for (int i = 0; i < 3; i++) { + // ...project d onto that axis to get the distance + // along the axis of d from the obb center + dist = Vec3::dot(d, u[i]); + // if distance farther than the obb extents, clamp to the obb + dist = cc::mathutils::clamp(dist, -e[i], e[i]); + + // Step that distance along the axis to get world coordinate + *out += (dist * u[i]); + } + return out; +} + +Vec3 *ptPointLine(Vec3 *out, const Vec3 &point, const Vec3 &linePointA, const Vec3 &linePointB) { + auto dir = linePointA - linePointB; + auto dirSquared = dir.lengthSquared(); + + if (dirSquared == 0.0F) { + // The point is at the segment start. + *out = linePointA; + } else { + // Calculate the projection of the point onto the line extending through the segment. + auto ap = point - linePointA; + auto t = Vec3::dot(ap, dir) / dirSquared; + *out = linePointA + cc::mathutils::clamp(t, 0.0F, 1.0F) * dir; + } + return out; +} + +} // namespace geometry +} // namespace cc \ No newline at end of file diff --git a/cocos/core/geometry/Distance.h b/cocos/core/geometry/Distance.h new file mode 100644 index 0000000..4ab1414 --- /dev/null +++ b/cocos/core/geometry/Distance.h @@ -0,0 +1,96 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace cc { + +class Mat4; +class Vec3; +namespace geometry { + +class Plane; +class OBB; +class AABB; +/** + * @en + * the distance between a point and a plane + * @zh + * 计算点和平面之间的距离。 + * @param {Vec3} point 点。 + * @param {Plane} plane 平面。 + * @return 距离。 + */ +float pointPlane(const Vec3 &point, const Plane &plane); + +/** + * @en + * the closest point on plane to a given point + * @zh + * 计算平面上最接近给定点的点。 + * @param out 最近点。 + * @param point 给定点。 + * @param plane 平面。 + * @return 最近点。 + */ +Vec3 *ptPointPlane(Vec3 *out, const Vec3 &point, const Plane &plane); + +/** + * @en + * the closest point on aabb to a given point + * @zh + * 计算 aabb 上最接近给定点的点。 + * @param {Vec3} out 最近点。 + * @param {Vec3} point 给定点。 + * @param {AABB} aabb 轴对齐包围盒。 + * @return {Vec3} 最近点。 + */ +Vec3 *ptPointAabb(Vec3 *out, const Vec3 &point, const AABB &aabb); + +/** + * @en + * the closest point on obb to a given point + * @zh + * 计算 obb 上最接近给定点的点。 + * @param {Vec3} out 最近点。 + * @param {Vec3} point 给定点。 + * @param {OBB} obb 方向包围盒。 + * @return {Vec3} 最近点。 + */ +Vec3 *ptPointObb(Vec3 *out, const Vec3 &point, const OBB &obb); + +/** + * @en + * Calculate the nearest point on the line to the given point. + * @zh + * 计算给定点距离直线上最近的一点。 + * @param out 最近点 + * @param point 给定点 + * @param linePointA 线上的某点 A + * @param linePointB 线上的某点 B + */ +Vec3 *ptPointLine(Vec3 *out, const Vec3 &point, const Vec3 &linePointA, const Vec3 &linePointB); + +} // namespace geometry +} // namespace cc \ No newline at end of file diff --git a/cocos/core/geometry/Enums.h b/cocos/core/geometry/Enums.h new file mode 100644 index 0000000..e6a97c4 --- /dev/null +++ b/cocos/core/geometry/Enums.h @@ -0,0 +1,69 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/RefCounted.h" + +namespace cc { +namespace geometry { + +enum class ShapeEnum { + SHAPE_RAY = (1 << 0), + SHAPE_LINE = (1 << 1), + SHAPE_SPHERE = (1 << 2), + SHAPE_AABB = (1 << 3), + SHAPE_OBB = (1 << 4), + SHAPE_PLANE = (1 << 5), + SHAPE_TRIANGLE = (1 << 6), + SHAPE_FRUSTUM = (1 << 7), + SHAPE_FRUSTUM_ACCURATE = (1 << 8), + SHAPE_CAPSULE = (1 << 9), + SHAPE_SPLINE = (1 << 10), + SHAPE_BAD = (1 << 11), +}; + +class ShapeBase : public RefCounted { +public: + ShapeBase(ShapeEnum type) : _type(type) {} + + /** + * @en + * Gets the type of the shape. + * @zh + * 获取形状的类型。 + */ + inline ShapeEnum getType() const { + CC_ASSERT_NE(_type, ShapeEnum::SHAPE_BAD); // shape is not initialized + return _type; + } + inline void setType(ShapeEnum type) { _type = type; } + +private: + ShapeEnum _type = ShapeEnum::SHAPE_BAD; +}; + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Frustum.cpp b/cocos/core/geometry/Frustum.cpp new file mode 100644 index 0000000..1c9c084 --- /dev/null +++ b/cocos/core/geometry/Frustum.cpp @@ -0,0 +1,215 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/geometry/Frustum.h" +#include +#include "core/geometry/Enums.h" +#include "scene/Define.h" + +namespace cc { +namespace geometry { +namespace { +const ccstd::vector VEC_VALS{ + {1, 1, 1}, + {-1, 1, 1}, + {-1, -1, 1}, + {1, -1, 1}, + {1, 1, -1}, + {-1, 1, -1}, + {-1, -1, -1}, + {1, -1, -1}}; +} // namespace + +void Frustum::createOrtho(Frustum *out, float width, + float height, + float near, + float far, + const Mat4 &transform) { + createOrthographic(out, width, height, near, far, transform); +} + +void Frustum::createOrthographic(Frustum *out, float width, + float height, + float near, + float far, + const Mat4 &transform) { + auto halfWidth = width / 2.0F; + auto halfHeight = height / 2.0F; + Vec3::transformMat4({halfWidth, halfHeight, -near}, transform, &out->vertices[0]); + Vec3::transformMat4({-halfWidth, halfHeight, -near}, transform, &out->vertices[1]); + Vec3::transformMat4({-halfWidth, -halfHeight, -near}, transform, &out->vertices[2]); + Vec3::transformMat4({halfWidth, -halfHeight, -near}, transform, &out->vertices[3]); + Vec3::transformMat4({halfWidth, halfHeight, -far}, transform, &out->vertices[4]); + Vec3::transformMat4({-halfWidth, halfHeight, -far}, transform, &out->vertices[5]); + Vec3::transformMat4({-halfWidth, -halfHeight, -far}, transform, &out->vertices[6]); + Vec3::transformMat4({halfWidth, -halfHeight, -far}, transform, &out->vertices[7]); + + out->updatePlanes(); +} + +Frustum *Frustum::createFromAABB(Frustum *out, const AABB &aabb) { + Vec3 minPos; + Vec3 maxPos; + aabb.getBoundary(&minPos, &maxPos); + + out->vertices[0].set(maxPos.x, maxPos.y, -minPos.z); + out->vertices[1].set(minPos.x, maxPos.y, -minPos.z); + out->vertices[2].set(minPos.x, minPos.y, -minPos.z); + out->vertices[3].set(maxPos.x, minPos.y, -minPos.z); + out->vertices[4].set(maxPos.x, maxPos.y, -maxPos.z); + out->vertices[5].set(minPos.x, maxPos.y, -maxPos.z); + out->vertices[6].set(minPos.x, minPos.y, -maxPos.z); + out->vertices[7].set(maxPos.x, minPos.y, -maxPos.z); + + out->updatePlanes(); + + return out; +} + +void Frustum::createPerspective(Frustum *out, float fov, + float aspect, + float near, + float far, + const Mat4 &transform) { + const float h = tanf(fov * 0.5F); + const float w = h * aspect; + const Vec3 nearTemp(near * w, near * h, near); + const Vec3 farTemp(far * w, far * h, far); + + out->vertices[0].transformMat4(Vec3(nearTemp.x, nearTemp.y, -nearTemp.z), transform); + out->vertices[1].transformMat4(Vec3(-nearTemp.x, nearTemp.y, -nearTemp.z), transform); + out->vertices[2].transformMat4(Vec3(-nearTemp.x, -nearTemp.y, -nearTemp.z), transform); + out->vertices[3].transformMat4(Vec3(nearTemp.x, -nearTemp.y, -nearTemp.z), transform); + out->vertices[4].transformMat4(Vec3(farTemp.x, farTemp.y, -farTemp.z), transform); + out->vertices[5].transformMat4(Vec3(-farTemp.x, farTemp.y, -farTemp.z), transform); + out->vertices[6].transformMat4(Vec3(-farTemp.x, -farTemp.y, -farTemp.z), transform); + out->vertices[7].transformMat4(Vec3(farTemp.x, -farTemp.y, -farTemp.z), transform); + + out->updatePlanes(); +} + +void Frustum::update(const Mat4 &m, const Mat4 &inv) { + // left plane + planes[0]->n.set(m.m[3] + m.m[0], m.m[7] + m.m[4], m.m[11] + m.m[8]); + planes[0]->d = -(m.m[15] + m.m[12]); + // right plane + planes[1]->n.set(m.m[3] - m.m[0], m.m[7] - m.m[4], m.m[11] - m.m[8]); + planes[1]->d = -(m.m[15] - m.m[12]); + // bottom plane + planes[2]->n.set(m.m[3] + m.m[1], m.m[7] + m.m[5], m.m[11] + m.m[9]); + planes[2]->d = -(m.m[15] + m.m[13]); + // top plane + planes[3]->n.set(m.m[3] - m.m[1], m.m[7] - m.m[5], m.m[11] - m.m[9]); + planes[3]->d = -(m.m[15] - m.m[13]); + // near plane + planes[4]->n.set(m.m[3] + m.m[2], m.m[7] + m.m[6], m.m[11] + m.m[10]); + planes[4]->d = -(m.m[15] + m.m[14]); + // far plane + planes[5]->n.set(m.m[3] - m.m[2], m.m[7] - m.m[6], m.m[11] - m.m[10]); + planes[5]->d = -(m.m[15] - m.m[14]); + + for (Plane *plane : planes) { + float invDist = 1 / plane->n.length(); + plane->n *= invDist; + plane->d *= invDist; + } + uint32_t i = 0; + for (const Vec3 &vec : VEC_VALS) { + vertices[i].transformMat4(vec, inv); + i++; + } +} + +void Frustum::transform(const Mat4 &mat) { + for (auto i = 0; i < 8; i++) { + vertices[i].transformMat4(vertices[i], mat); + } + updatePlanes(); +} + +void Frustum::createOrthographic(float width, float height, float near, float far, const Mat4 &transform) { + createOrthographic(this, width, height, near, far, transform); +} + +void Frustum::createOrtho(const float width, const float height, const float near, const float far, const Mat4 &transform) { + createOrthographic(width, height, near, far, transform); +} + +void Frustum::split(float near, float far, float aspect, float fov, const Mat4 &transform) { + createPerspective(this, fov, aspect, near, far, transform); +} + +void Frustum::updatePlanes() { + // left plane + planes[0]->define(vertices[1], vertices[6], vertices[5]); + // right plane + planes[1]->define(vertices[3], vertices[4], vertices[7]); + // bottom plane + planes[2]->define(vertices[6], vertices[3], vertices[7]); + // top plane + planes[3]->define(vertices[0], vertices[5], vertices[4]); + // near plane + planes[4]->define(vertices[2], vertices[0], vertices[3]); + // far plane + planes[5]->define(vertices[7], vertices[5], vertices[6]); +} + +Frustum::Frustum() : ShapeBase(ShapeEnum::SHAPE_FRUSTUM) { + init(); +} + +Frustum::Frustum(const Frustum &rhs) : ShapeBase(rhs) { + init(); + *this = rhs; +} + +Frustum::~Frustum() { + for (auto *plane : planes) { + plane->release(); + } +} + +Frustum &Frustum::operator=(const Frustum &rhs) { + if (this == &rhs) { + return *this; + } + + vertices = rhs.vertices; + + for (size_t i = 0; i < planes.size(); ++i) { // NOLINT(modernize-loop-convert) + Plane::copy(planes[i], *rhs.planes[i]); + } + + return *this; +} + +void Frustum::init() { + for (size_t i = 0; i < planes.size(); ++i) { // NOLINT(modernize-loop-convert) + planes[i] = ccnew Plane(); + planes[i]->addRef(); + } +} + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Frustum.h b/cocos/core/geometry/Frustum.h new file mode 100644 index 0000000..c9115f8 --- /dev/null +++ b/cocos/core/geometry/Frustum.h @@ -0,0 +1,207 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/memory/Memory.h" +#include "base/std/container/array.h" +#include "core/geometry/AABB.h" +#include "core/geometry/Enums.h" +#include "core/geometry/Plane.h" +#include "math/Mat4.h" +#include "math/Vec3.h" + +namespace cc { +namespace geometry { + +class Frustum final : public ShapeBase { +public: + /** + * @en + * Create a ortho frustum. + * @zh + * 创建一个正交视锥体。 + * @param out @en The result orthographic frustum. @zh 输出的正交视锥体。 + * @param width @en The width of the frustum. @zh 正交视锥体的宽度。 + * @param height @en The height of the frustum. @zh 正交视锥体的高度。 + * @param near @en The near plane of the frustum. @zh 正交视锥体的近平面值。 + * @param far @en The far plane of the frustum. @zh 正交视锥体的远平面值。 + * @param transform @en The transform matrix of the frustum. @zh 正交视锥体的变换矩阵。 + * + * @deprecated since 3.8.0, please use [createOrthographic] instead of it. + */ + static void createOrtho(Frustum *out, float width, + float height, + float near, + float far, + const Mat4 &transform); + + /** + * @en + * Create a ortho frustum. + * @zh + * 创建一个正交视锥体。 + * @param out @en The result orthographic frustum. @zh 输出的正交视锥体。 + * @param width @en The width of the frustum. @zh 正交视锥体的宽度。 + * @param height @en The height of the frustum. @zh 正交视锥体的高度。 + * @param near @en The near plane of the frustum. @zh 正交视锥体的近平面值。 + * @param far @en The far plane of the frustum. @zh 正交视锥体的远平面值。 + * @param transform @en The transform matrix of the frustum. @zh 正交视锥体的变换矩阵。 + */ + static void createOrthographic(Frustum *out, float width, + float height, + float near, + float far, + const Mat4 &transform); + + /** + * @en + * Create a perspective frustum. + * @zh + * 创建一个透视视锥体。 + * @param out @en The result perspective frustum. @zh 输出的透视视锥体。 + * @param fov @en The field of view of the frustum. @zh 视锥体的视野。 + * @param aspect @en The aspect ratio of the frustum. @zh 视锥体的宽高比。 + * @param near @en The near plane of the frustum. @zh 视锥体的近平面值。 + * @param far @en The far plane of the frustum. @zh 视锥体的远平面值。 + * @param transform @en The transform matrix of the frustum. @zh 视锥体的变换矩阵。 + */ + static void createPerspective(Frustum *out, float fov, + float aspect, + float near, + float far, + const Mat4 &transform); + + /** + * @en Create a frustum from an AABB box. + * @zh 从 AABB 包围盒中创建一个视锥体。 + * @param out @en The result frustum @zh 输出的视锥体对象。 + * @param aabb @en The AABB bounding box of the frustum @zh AABB 包围盒。 + * @return @en The out object @zh 返回视锥体. + * + * @deprecated since 3.8.0, please use [createOrthographic] instead of it. + */ + static Frustum *createFromAABB(Frustum *out, const AABB &aabb); + + /** + * @en + * Create a ccnew frustum. + * @zh + * 创建一个新的截锥体。 + * @return @en An empty frustum. @zh 一个空截椎体 + */ + static Frustum *create() { + return ccnew Frustum(); + } + + /** + * @en + * Clone a frustum. + * @zh + * 克隆一个截锥体。 + * @param f @en The frustum to clone from @zh 用于克隆的截锥体 + * @return @en The cloned frustum @zh 克隆出的新截锥体 + */ + static Frustum *clone(const Frustum &f) { + return Frustum::copy(ccnew Frustum(), f); + } + + /** + * @en + * Copy the values from one frustum to another. + * @zh + * 从一个视锥体拷贝到另一个视锥体。 + * @param out @en The result frustum @zh 用于存储拷贝数据的截锥体 + * @param f @en The frustum to copy from @zh 用于克隆的截锥体 + * @return @en The out object @zh 传入的 out 对象 + + */ + static Frustum *copy(Frustum *out, const Frustum &f) { + out->setType(f.getType()); + for (size_t i = 0; i < 6; ++i) { // NOLINT(modernize-loop-convert) + Plane::copy(out->planes[i], *(f.planes[i])); + } + out->vertices = f.vertices; + return out; + } + + Frustum(); + Frustum(const Frustum &rhs); + Frustum(Frustum &&rhs) = delete; + ~Frustum() override; + + // Can remove these operator override functions if not using Plane* in planes array. + Frustum &operator=(const Frustum &rhs); + Frustum &operator=(Frustum &&rhs) = delete; + + /** + * @en + * Transform this frustum. + * @zh + * 变换此截锥体。 + * @param mat 变换矩阵。 + */ + void transform(const Mat4 &); + + void createOrtho(float width, float height, float near, float far, const Mat4 &transform); + void createOrthographic(float width, float height, float near, float far, const Mat4 &transform); + + /** + * @en + * Set as a perspective frustum. + * @zh + * 设置为一个透视视锥体。 + * @param near @en The near plane of the frustum. @zh 视锥体的近平面值。 + * @param far @en The far plane of the frustum. @zh 视锥体的远平面值。 + * @param fov @en The field of view of the frustum. @zh 视锥体的视野。 + * @param aspect @en The aspect ratio of the frustum. @zh 视锥体的宽高比。 + * @param transform @en The transform matrix of the frustum. @zh 视锥体的变换矩阵。 + * + * @deprecated since 3.8.0, please use [createPerspective] instead of it. + */ + void split(float near, float far, float aspect, float fov, const Mat4 &transform); + void updatePlanes(); + void update(const Mat4 &m, const Mat4 &inv); + + /** + * @en + * Set whether to use accurate intersection testing function on this frustum. + * @zh + * 设置是否在此截锥体上使用精确的相交测试函数。 + * + * @deprecated since v3.8.0 no need to set accurate flag since it doesn't affect the calculation at all. + */ + inline void setAccurate(bool accurate) { + setType(accurate ? ShapeEnum::SHAPE_FRUSTUM_ACCURATE : ShapeEnum::SHAPE_FRUSTUM); + } + + ccstd::array vertices; + ccstd::array planes; + +private: + void init(); +}; + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Geometry.h b/cocos/core/geometry/Geometry.h new file mode 100644 index 0000000..5e4bd49 --- /dev/null +++ b/cocos/core/geometry/Geometry.h @@ -0,0 +1,41 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "AABB.h" +#include "Capsule.h" +// #include "Curve.h" +#include "Distance.h" +#include "Enums.h" +#include "Frustum.h" +#include "Intersect.h" +#include "Line.h" +#include "Obb.h" +#include "Plane.h" +#include "Ray.h" +#include "Spec.h" +#include "Sphere.h" +#include "Spline.h" +#include "Triangle.h" diff --git a/cocos/core/geometry/Intersect.cpp b/cocos/core/geometry/Intersect.cpp new file mode 100644 index 0000000..406f4ed --- /dev/null +++ b/cocos/core/geometry/Intersect.cpp @@ -0,0 +1,1100 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/geometry/Intersect.h" + +#include +#include +#include "3d/assets/Mesh.h" +#include "base/TemplateUtils.h" +#include "base/std/container/array.h" +#include "core/TypedArray.h" +#include "core/geometry/AABB.h" +#include "core/geometry/Capsule.h" +#include "core/geometry/Line.h" +#include "core/geometry/Obb.h" +#include "core/geometry/Plane.h" +#include "core/geometry/Ray.h" +#include "core/geometry/Spec.h" +#include "core/geometry/Sphere.h" +#include "core/geometry/Triangle.h" +#include "math/Mat3.h" +#include "math/Math.h" +#include "math/Vec3.h" +#include "renderer/gfx-base/GFXDef.h" +#include "scene/Model.h" + +namespace cc { +namespace geometry { + +float rayPlane(const Ray &ray, const Plane &plane) { + auto denom = Vec3::dot(ray.d, plane.n); + if (abs(denom) < math::EPSILON) { + return 0; + } + auto t = (plane.n * plane.d - ray.o).dot(plane.n) / denom; + return t < 0 ? 0 : t; +} + +// based on http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/raytri/ +float rayTriangle(const Ray &ray, const Triangle &triangle, bool doubleSided) { + Vec3 pvec{}; + Vec3 qvec{}; + auto ab = triangle.b - triangle.a; + auto ac = triangle.c - triangle.a; + Vec3::cross(ray.d, ac, &pvec); + float det = Vec3::dot(ab, pvec); + if (det < math::EPSILON && (!doubleSided || det > -math::EPSILON)) { + return 0; + } + float invDet = 1.0F / det; + auto ao = ray.o - triangle.a; + auto u = Vec3::dot(ao, pvec) * invDet; + if (u < 0 || u > 1) { + return 0; + } + Vec3::cross(ao, ab, &qvec); + auto v = Vec3::dot(ray.d, qvec) * invDet; + if (v < 0 || u + v > 1) { + return 0; + } + + auto t = Vec3::dot(ac, qvec) * invDet; + return t < 0 ? 0 : t; +} + +float raySphere(const Ray &ray, const Sphere &sphere) { + auto rSq = sphere.getRadius() * sphere.getRadius(); + auto e = sphere.getCenter() - ray.o; + auto eSq = e.lengthSquared(); + + auto aLength = Vec3::dot(e, ray.d); // assume ray direction already normalized + auto fSq = rSq - (eSq - aLength * aLength); + if (fSq < 0) { + return 0; + } + + auto f = sqrt(fSq); + auto t = eSq < rSq ? aLength + f : aLength - f; + return t < 0 ? 0 : t; +} + +float rayAABB2(const Ray &ray, const Vec3 &min, const Vec3 &max) { + const auto &o = ray.o; + auto d = ray.d; + auto ix = 1.0F / d.x; + auto iy = 1.0F / d.y; + auto iz = 1.0F / d.z; + auto tx1 = (min.x - o.x) * ix; + auto tx2 = (max.x - o.x) * ix; + auto ty1 = (min.y - o.y) * iy; + auto ty2 = (max.y - o.y) * iy; + auto tz1 = (min.z - o.z) * iz; + auto tz2 = (max.z - o.z) * iz; + auto tmin = std::max(std::max(std::min(tx1, tx2), std::min(ty1, ty2)), std::min(tz1, tz2)); + auto tmax = std::min(std::min(std::max(tx1, tx2), std::max(ty1, ty2)), std::max(tz1, tz2)); + if (tmax < 0 || tmin > tmax) { + return 0; + } + return tmin > 0 ? tmin : tmax; // ray origin inside aabb +} + +float rayAABB(const Ray &ray, const AABB &aabb) { + auto minV = aabb.getCenter() - aabb.getHalfExtents(); + auto maxV = aabb.getCenter() + aabb.getHalfExtents(); + return rayAABB2(ray, minV, maxV); +} + +float rayOBB(const Ray &ray, const OBB &obb) { + ccstd::array t; + ccstd::array size = {obb.halfExtents.x, obb.halfExtents.y, obb.halfExtents.z}; + const auto ¢er = obb.center; + auto const &d = ray.d; + + Vec3 x = {obb.orientation.m[0], obb.orientation.m[1], obb.orientation.m[2]}; + Vec3 y = {obb.orientation.m[3], obb.orientation.m[4], obb.orientation.m[5]}; + Vec3 z = {obb.orientation.m[6], obb.orientation.m[7], obb.orientation.m[8]}; + Vec3 p = center - ray.o; + + // The cos values of the ray on the X, Y, Z + ccstd::array f = {Vec3::dot(x, d), Vec3::dot(y, d), Vec3::dot(z, d)}; + + // The projection length of P on X, Y, Z + ccstd::array e{Vec3::dot(x, p), Vec3::dot(y, p), Vec3::dot(z, p)}; + + for (auto i = 0; i < 3; ++i) { + if (f[i] == 0) { + if (-e[i] - size[i] > 0 || -e[i] + size[i] < 0) { + return 0; + } + // Avoid div by 0! + f[i] = 0.000001F; + } + // min + t[i * 2 + 0] = (e[i] + size[i]) / f[i]; + // max + t[i * 2 + 1] = (e[i] - size[i]) / f[i]; + } + auto tmin = std::max(std::max(std::min(t[0], t[1]), std::min(t[2], t[3])), std::min(t[4], t[5])); + auto tmax = std::min(std::min(std::max(t[0], t[1]), std::max(t[2], t[3])), std::max(t[4], t[5])); + if (tmax < 0 || tmin > tmax) { + return 0; + } + return tmin > 0 ? tmin : tmax; // ray origin inside aabb +} + +float rayCapsule(const Ray &ray, const Capsule &capsule) { + Sphere sphere; + auto radiusSqr = capsule.radius * capsule.radius; + auto vRayNorm = ray.d.getNormalized(); + auto aa = capsule.ellipseCenter0; + auto bb = capsule.ellipseCenter1; + auto ba = bb - aa; + if (ba == Vec3::ZERO) { + sphere.setRadius(capsule.radius); + sphere.setCenter(capsule.ellipseCenter0); + return raySphere(ray, sphere); + } + + auto oa = ray.o - aa; + Vec3 VxBA; //NOLINT(readability-identifier-naming) + Vec3::cross(vRayNorm, ba, &VxBA); + auto a = VxBA.lengthSquared(); + if (a == 0.0F) { + sphere.setRadius(capsule.radius); + auto bo = bb - ray.o; + if (oa.lengthSquared() < bo.lengthSquared()) { + sphere.setCenter(capsule.ellipseCenter0); + } else { + sphere.setCenter(capsule.ellipseCenter1); + } + return raySphere(ray, sphere); + } + + Vec3 OAxBA; //NOLINT(readability-identifier-naming) + Vec3::cross(oa, ba, &OAxBA); + auto ab2 = ba.lengthSquared(); + auto b = 2.0F * Vec3::dot(VxBA, OAxBA); + auto c = OAxBA.lengthSquared() - (radiusSqr * ab2); + auto d = b * b - 4 * a * c; + + if (d < 0) { + return 0; + } + + auto t = (-b - std::sqrt(d)) / (2.0F * a); + if (t < 0.0F) { + sphere.setRadius(capsule.radius); + auto bo = bb - ray.o; + if (oa.lengthSquared() < bo.lengthSquared()) { + sphere.setCenter(capsule.ellipseCenter0); + } else { + sphere.setCenter(capsule.ellipseCenter1); + } + return raySphere(ray, sphere); + } + // Limit intersection between the bounds of the cylinder's end caps. + auto iPos = ray.o + vRayNorm * t; + auto iPosLen = iPos - aa; + auto tLimit = Vec3::dot(iPosLen, ba) / ab2; + + if (tLimit >= 0 && tLimit <= 1) { + return t; + } + if (tLimit < 0) { + sphere.setRadius(capsule.radius); + sphere.setCenter(capsule.ellipseCenter0); + return raySphere(ray, sphere); + } + if (tLimit > 1) { + sphere.setRadius(capsule.radius); + sphere.setCenter(capsule.ellipseCenter1); + return raySphere(ray, sphere); + } + return 0; +} + +namespace { +void fillResult(float *minDis, ERaycastMode m, float d, float i0, float i1, float i2, ccstd::optional> &r) { + if (m == ERaycastMode::CLOSEST) { + if (*minDis > d || *minDis == 0.0F) { + *minDis = d; + if (r) { + if (r->empty()) { + r->emplace_back(IRaySubMeshResult{d, static_cast(i0 / 3), static_cast(i1 / 3), static_cast(i2 / 3)}); + } else { + (*r)[0].distance = d; + (*r)[0].vertexIndex0 = static_cast(i0 / 3); + (*r)[0].vertexIndex1 = static_cast(i1 / 3); + (*r)[0].vertexIndex2 = static_cast(i2 / 3); + } + } + } + } else { + *minDis = d; + if (r) r->emplace_back(IRaySubMeshResult{d, static_cast(i0 / 3), static_cast(i1 / 3), static_cast(i2 / 3)}); + } +} + +float narrowphase(float *minDis, const Float32Array &vb, const IBArray &ib, gfx::PrimitiveMode pm, const Ray &ray, IRaySubMeshOptions *opt) { + Triangle tri; + auto ibSize = ccstd::visit(overloaded{ + [](const auto &arr) { + return arr.length(); + }, + [](const ccstd::monostate & /*unused*/) { + return static_cast(0); + }}, + ib); + + return ccstd::visit(overloaded{[&](const auto &ib) { + if (pm == gfx::PrimitiveMode::TRIANGLE_LIST) { + auto cnt = ibSize; + for (auto j = 0; j < cnt; j += 3) { + auto i0 = ib[j] * 3; + auto i1 = ib[j + 1] * 3; + auto i2 = ib[j + 2] * 3; + tri.a = {vb[i0], vb[i0 + 1], vb[i0 + 2]}; + tri.b = {vb[i1], vb[i1 + 1], vb[i1 + 2]}; + tri.c = {vb[i2], vb[i2 + 1], vb[i2 + 2]}; + auto dist = rayTriangle(ray, tri, opt->doubleSided); + if (dist == 0.0F || dist > opt->distance) continue; + fillResult(minDis, opt->mode, dist, static_cast(i0), static_cast(i1), static_cast(i2), opt->result); + if (opt->mode == ERaycastMode::ANY) return dist; + } + } else if (pm == gfx::PrimitiveMode::TRIANGLE_STRIP) { + auto cnt = ibSize - 2; + int32_t rev = 0; + for (auto j = 0; j < cnt; j += 1) { + auto i0 = ib[j - rev] * 3; + auto i1 = ib[j + rev + 1] * 3; + auto i2 = ib[j + 2] * 3; + tri.a = {vb[i0], vb[i0 + 1], vb[i0 + 2]}; + tri.b = {vb[i1], vb[i1 + 1], vb[i1 + 2]}; + tri.c = {vb[i2], vb[i2 + 1], vb[i2 + 2]}; + rev = ~rev; + auto dist = rayTriangle(ray, tri, opt->doubleSided); + if (dist == 0.0F || dist > opt->distance) continue; + fillResult(minDis, opt->mode, dist, static_cast(i0), static_cast(i1), static_cast(i2), opt->result); + if (opt->mode == ERaycastMode::ANY) return dist; + } + } else if (pm == gfx::PrimitiveMode::TRIANGLE_FAN) { + auto cnt = ibSize - 1; + auto i0 = ib[0] * 3; + tri.a = {vb[i0], vb[i0 + 1], vb[i0 + 2]}; + for (auto j = 1; j < cnt; j += 1) { + auto i1 = ib[j] * 3; + auto i2 = ib[j + 1] * 3; + tri.b = {vb[i1], vb[i1 + 1], vb[i1 + 2]}; + tri.c = {vb[i2], vb[i2 + 1], vb[i2 + 2]}; + auto dist = rayTriangle(ray, tri, opt->doubleSided); + if (dist == 0.0 || dist > opt->distance) continue; + fillResult(minDis, opt->mode, dist, static_cast(i0), static_cast(i1), static_cast(i2), opt->result); + if (opt->mode == ERaycastMode::ANY) return dist; + } + } + return *minDis; + }, + [](const ccstd::monostate & /*unused*/) { return 0.F; }}, + ib); +} +} // namespace + +float raySubMesh(const Ray &ray, const RenderingSubMesh &submesh, IRaySubMeshOptions *options) { + Triangle tri; + IRaySubMeshOptions deOpt; + deOpt.mode = ERaycastMode::ANY; + deOpt.distance = FLT_MAX; + deOpt.doubleSided = false; + float minDis = 0.0F; + auto &mesh = const_cast(submesh); + if (mesh.getGeometricInfo().positions.empty()) return minDis; + IRaySubMeshOptions *opt = options ? options : &deOpt; + auto min = mesh.getGeometricInfo().boundingBox.min; + auto max = mesh.getGeometricInfo().boundingBox.max; + if (rayAABB2(ray, min, max) != 0.0F) { + const auto &pm = mesh.getPrimitiveMode(); + const auto &info = mesh.getGeometricInfo(); + narrowphase(&minDis, info.positions, info.indices.value(), pm, ray, opt); + } + return minDis; +} + +float rayMesh(const Ray &ray, const Mesh &mesh, IRayMeshOptions *option) { + float minDis = 0.0F; + IRayMeshOptions deOpt; + deOpt.distance = std::numeric_limits::max(); + deOpt.doubleSided = false; + deOpt.mode = ERaycastMode::ANY; + IRayMeshOptions *opt = option ? option : &deOpt; + auto length = mesh.getSubMeshCount(); + auto min = mesh.getStruct().minPosition; + auto max = mesh.getStruct().maxPosition; + if (min.has_value() && max.has_value() && rayAABB2(ray, min.value(), max.value()) == 0.0F) return minDis; + for (uint32_t i = 0; i < length; i++) { + const auto &sm = const_cast(mesh).getRenderingSubMeshes()[i]; + float dis = raySubMesh(ray, *sm, opt); + if (dis != 0.0F) { + if (opt->mode == ERaycastMode::CLOSEST) { + if (minDis == 0.0F || minDis > dis) { + minDis = dis; + if (opt->subIndices.has_value()) { + if (opt->subIndices->empty()) { + opt->subIndices->resize(1); + } + opt->subIndices.value()[0] = i; + } + } + } else { + minDis = dis; + if (opt->subIndices.has_value()) opt->subIndices->emplace_back(i); + if (opt->mode == ERaycastMode::ANY) { + return dis; + } + } + } + } + if (minDis != 0.0F && opt->mode == ERaycastMode::CLOSEST) { + if (opt->result.has_value()) { + opt->result.value()[0].distance = minDis; + opt->result.value().resize(1); + } + if (opt->subIndices.has_value()) opt->subIndices.value().resize(1); + } + return minDis; +} + +float rayModel(const Ray &ray, const scene::Model &model, IRayModelOptions *option) { + float minDis = 0.0F; + IRayModelOptions deOpt; + deOpt.distance = std::numeric_limits::max(); + deOpt.doubleSided = false; + deOpt.mode = ERaycastMode::ANY; + + IRayModelOptions *opt = option ? option : &deOpt; + const auto *wb = model.getWorldBounds(); + if (wb && rayAABB(ray, *wb) == 0.0F) { + return minDis; + } + Ray modelRay{ray}; + if (model.getNode()) { + Mat4 m4 = model.getNode()->getWorldMatrix().getInversed(); + Vec3::transformMat4(ray.o, m4, &modelRay.o); + Vec3::transformMat4Normal(ray.d, m4, &modelRay.d); + } + const auto &subModels = model.getSubModels(); + for (auto i = 0; i < subModels.size(); i++) { + const auto &subMesh = subModels[i]->getSubMesh(); + float dis = raySubMesh(modelRay, *subMesh, opt); + if (dis != 0.0F) { + if (opt->mode == ERaycastMode::CLOSEST) { + if (minDis == 0.0F || minDis > dis) { + minDis = dis; + if (opt->subIndices.has_value()) { + if (opt->subIndices->empty()) { + opt->subIndices->resize(1); + } + opt->subIndices.value()[0] = i; + } + } + } else { + minDis = dis; + if (opt->subIndices.has_value()) opt->subIndices->emplace_back(i); + if (opt->mode == ERaycastMode::ANY) { + return dis; + } + } + } + } + if (minDis != 0.0F && opt->mode == ERaycastMode::CLOSEST) { + if (opt->result.has_value()) { + opt->result.value()[0].distance = minDis; + opt->result.value().resize(1); + } + if (opt->subIndices.has_value()) opt->subIndices->resize(1); + } + return minDis; +} + +float linePlane(const Line &line, const Plane &plane) { + auto ab = line.e - line.s; + auto t = (plane.d - Vec3::dot(line.s, plane.n)) / Vec3::dot(ab, plane.n); + return (t < 0 || t > 1) ? 0 : t; +} + +int lineTriangle(const Line &line, const Triangle &triangle, Vec3 *outPt) { + Vec3 n; + Vec3 e; + auto ab = triangle.b - triangle.a; + auto ac = triangle.c - triangle.a; + auto qp = line.s - line.e; + Vec3::cross(ab, ac, &n); + + auto det = Vec3::dot(qp, n); + + if (det <= 0.0F) { + return 0; + } + + auto ap = line.s - triangle.a; + auto t = Vec3::dot(ap, n); + if (t < 0 || t > det) { + return 0; + } + + Vec3::cross(qp, ap, &e); + auto v = Vec3::dot(ac, e); + if (v < 0.0F || v > det) { + return 0; + } + + auto w = -Vec3::dot(ab, e); + if (w < 0.0F || v + w > det) { + return 0; + } + + if (outPt) { + auto invDet = 1.0F / det; + v *= invDet; + w *= invDet; + auto u = 1.0F - v - w; + + // outPt = u*a + v*d + w*c; + *outPt = {triangle.a.x * u + triangle.b.x * v + triangle.c.x * w, + triangle.a.y * u + triangle.b.y * v + triangle.c.y * w, + triangle.a.z * u + triangle.b.z * v + triangle.c.z * w}; + } + + return 1; +} + +float lineAABB(const Line &line, const AABB &aabb) { + Ray ray; + ray.o.set(line.s); + ray.d = line.e - line.s; + ray.d.normalize(); + auto min = rayAABB(ray, aabb); + return min < line.length() ? min : 0.0F; +} + +float lineOBB(const Line &line, const OBB &obb) { + Ray ray; + ray.o.set(line.s); + ray.d = line.e - line.s; + ray.d.normalize(); + auto min = rayOBB(ray, obb); + return min < line.length() ? min : 0.0F; +} + +float lineSphere(const Line &line, const Sphere &sphere) { + Ray ray; + ray.o.set(line.s); + ray.d = line.e - line.s; + ray.d.normalize(); + auto min = raySphere(ray, sphere); + return min < line.length() ? min : 0.0F; +} + +bool aabbWithAABB(const AABB &aabb1, const AABB &aabb2) { + auto aMin = aabb1.getCenter() - aabb1.getHalfExtents(); + auto aMax = aabb1.getCenter() + aabb1.getHalfExtents(); + auto bMin = aabb2.getCenter() - aabb2.getHalfExtents(); + auto bMax = aabb2.getCenter() + aabb2.getHalfExtents(); + return (aMin.x <= bMax.x && aMax.x >= bMin.x) && (aMin.y <= bMax.y && aMax.y >= bMin.y) && (aMin.z <= bMax.z && aMax.z >= bMin.z); +} + +static void getAABBVertices(const Vec3 &min, const Vec3 &max, ccstd::array *out) { + *out = { + Vec3{min.x, max.y, max.z}, + Vec3{min.x, max.y, min.z}, + Vec3{min.x, min.y, max.z}, + Vec3{min.x, min.y, min.z}, + Vec3{max.x, max.y, max.z}, + Vec3{max.x, max.y, min.z}, + Vec3{max.x, min.y, max.z}, + Vec3{max.x, min.y, min.z}, + }; +} + +static void getOBBVertices(const Vec3 &c, const Vec3 &e, + const Vec3 &a1, const Vec3 &a2, const Vec3 &a3, + ccstd::array *out) { + *out = {Vec3{ + c.x + a1.x * e.x + a2.x * e.y + a3.x * e.z, + c.y + a1.y * e.x + a2.y * e.y + a3.y * e.z, + c.z + a1.z * e.x + a2.z * e.y + a3.z * e.z}, + Vec3{ + c.x - a1.x * e.x + a2.x * e.y + a3.x * e.z, + c.y - a1.y * e.x + a2.y * e.y + a3.y * e.z, + c.z - a1.z * e.x + a2.z * e.y + a3.z * e.z}, + Vec3{ + c.x + a1.x * e.x - a2.x * e.y + a3.x * e.z, + c.y + a1.y * e.x - a2.y * e.y + a3.y * e.z, + c.z + a1.z * e.x - a2.z * e.y + a3.z * e.z}, + Vec3{ + c.x + a1.x * e.x + a2.x * e.y - a3.x * e.z, + c.y + a1.y * e.x + a2.y * e.y - a3.y * e.z, + c.z + a1.z * e.x + a2.z * e.y - a3.z * e.z}, + Vec3{ + c.x - a1.x * e.x - a2.x * e.y - a3.x * e.z, + c.y - a1.y * e.x - a2.y * e.y - a3.y * e.z, + c.z - a1.z * e.x - a2.z * e.y - a3.z * e.z}, + Vec3{ + c.x + a1.x * e.x - a2.x * e.y - a3.x * e.z, + c.y + a1.y * e.x - a2.y * e.y - a3.y * e.z, + c.z + a1.z * e.x - a2.z * e.y - a3.z * e.z}, + Vec3{ + c.x - a1.x * e.x + a2.x * e.y - a3.x * e.z, + c.y - a1.y * e.x + a2.y * e.y - a3.y * e.z, + c.z - a1.z * e.x + a2.z * e.y - a3.z * e.z}, + Vec3{ + c.x - a1.x * e.x - a2.x * e.y + a3.x * e.z, + c.y - a1.y * e.x - a2.y * e.y + a3.y * e.z, + c.z - a1.z * e.x - a2.z * e.y + a3.z * e.z}}; +} + +struct Interval { + float min; + float max; +}; + +static Interval getInterval(const ccstd::array &vertices, const Vec3 &axis) { + auto min = std::numeric_limits::max(); + auto max = std::numeric_limits::min(); + for (auto i = 0; i < 8; ++i) { + auto projection = Vec3::dot(axis, vertices[i]); + min = std::min(projection, min); + max = std::max(projection, max); + } + return Interval{min, max}; +} + +int aabbWithOBB(const AABB &aabb, const OBB &obb) { + ccstd::array test; + ccstd::array vertices; + ccstd::array vertices2; + test[0] = {1, 0, 0}; + test[1] = {0, 1, 0}; + test[2] = {0, 0, 1}; + test[3] = {obb.orientation.m[0], obb.orientation.m[1], obb.orientation.m[2]}; + test[4] = {obb.orientation.m[3], obb.orientation.m[4], obb.orientation.m[5]}; + test[5] = {obb.orientation.m[6], obb.orientation.m[7], obb.orientation.m[8]}; + + for (auto i = 0; i < 3; ++i) { // Fill out rest of axis + Vec3::cross(test[i], test[3], &test[6 + i * 3 + 0]); + Vec3::cross(test[i], test[4], &test[6 + i * 3 + 1]); + Vec3::cross(test[i], test[5], &test[6 + i * 3 + 2]); + } + + auto min = aabb.getCenter() - aabb.getHalfExtents(); + auto max = aabb.getCenter() + aabb.getHalfExtents(); + getAABBVertices(min, max, &vertices); + getOBBVertices(obb.center, obb.halfExtents, test[3], test[4], test[5], &vertices2); + + for (auto j = 0; j < 15; ++j) { + auto a = getInterval(vertices, test[j]); + auto b = getInterval(vertices2, test[j]); + if (b.min > a.max || a.min > b.max) { + return 0; // Seperating axis found + } + } + + return 1; +} + +int aabbPlane(const AABB &aabb, const Plane &plane) { + auto r = aabb.getHalfExtents().x * std::abs(plane.n.x) + aabb.getHalfExtents().y * std::abs(plane.n.y) + aabb.getHalfExtents().z * std::abs(plane.n.z); + auto dot = Vec3::dot(plane.n, aabb.getCenter()); + if (dot + r < plane.d) { + return -1; + } + if (dot - r > plane.d) { + return 0; + } + return 1; +} + +int aabbFrustum(const AABB &aabb, const Frustum &frustum) { + for (const auto &plane : frustum.planes) { + // frustum plane normal points to the inside + if (aabbPlane(aabb, *plane) == -1) { + return 0; + } + } // completely outside + return 1; +} + +int aabbFrustumCompletelyInside(const AABB &aabb, const Frustum &frustum) { + for (const auto &plane : frustum.planes) { + // frustum plane normal points to the inside + if (aabbPlane(aabb, *plane) != 0) { + return 0; + } + } // completely inside + return 1; +} + +// https://cesium.com/blog/2017/02/02/tighter-frustum-culling-and-why-you-may-want-to-disregard-it/ +int aabbFrustumAccurate(const AABB &aabb, const Frustum &frustum) { + ccstd::array tmp; + int out1 = 0; + int out2 = 0; + int result = 0; + bool intersects = false; + // 1. aabb inside/outside frustum test + for (const auto &plane : frustum.planes) { + result = aabbPlane(aabb, *plane); + // frustum plane normal points to the inside + if (result == -1) { + return 0; // completely outside + } + if (result == 1) { + intersects = true; + } + } + if (!intersects) { + return 1; + } // completely inside + // in case of false positives + // 2. frustum inside/outside aabb test + for (auto i = 0; i < frustum.vertices.size(); i++) { + tmp[i] = frustum.vertices[i] - aabb.getCenter(); + } + out1 = 0; + out2 = 0; + for (auto i = 0; i < frustum.vertices.size(); i++) { + if (tmp[i].x > aabb.getHalfExtents().x) { + out1++; + } else if (tmp[i].x < -aabb.getHalfExtents().x) { + out2++; + } + } + if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) { + return 0; + } + out1 = 0; + out2 = 0; + for (auto i = 0; i < frustum.vertices.size(); i++) { + if (tmp[i].y > aabb.getHalfExtents().y) { + out1++; + } else if (tmp[i].y < -aabb.getHalfExtents().y) { + out2++; + } + } + if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) { + return 0; + } + out1 = 0; + out2 = 0; + for (auto i = 0; i < frustum.vertices.size(); i++) { + if (tmp[i].z > aabb.getHalfExtents().z) { + out1++; + } else if (tmp[i].z < -aabb.getHalfExtents().z) { + out2++; + } + } + if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) { + return 0; + } + return 1; +} + +bool obbPoint(const OBB &obb, const Vec3 &point) { + Mat3 m3; + auto lessThan = [](const Vec3 &a, const Vec3 &b) -> bool { + return std::abs(a.x) < b.x && std::abs(a.y) < b.y && std::abs(a.z) < b.z; + }; + auto tmp = point - obb.center; + Mat3::transpose(obb.orientation, &m3); + tmp.transformMat3(tmp, m3); + return lessThan(tmp, obb.halfExtents); +}; + +int obbPlane(const OBB &obb, const Plane &plane) { + auto absDot = [](const Vec3 &n, float x, float y, float z) -> float { + return std::abs(n.x * x + n.y * y + n.z * z); + }; + + // Real-Time Collision Detection, Christer Ericson, p. 163. + auto r = obb.halfExtents.x * absDot(plane.n, obb.orientation.m[0], obb.orientation.m[1], obb.orientation.m[2]) + + obb.halfExtents.y * absDot(plane.n, obb.orientation.m[3], obb.orientation.m[4], obb.orientation.m[5]) + + obb.halfExtents.z * absDot(plane.n, obb.orientation.m[6], obb.orientation.m[7], obb.orientation.m[8]); + + auto dot = Vec3::dot(plane.n, obb.center); + if (dot + r < plane.d) { + return -1; + } + if (dot - r > plane.d) { + return 0; + } + return 1; +} + +int obbFrustum(const OBB &obb, const Frustum &frustum) { + for (const auto &plane : frustum.planes) { + // frustum plane normal points to the inside + if (obbPlane(obb, *plane) == -1) { + return 0; + } + } // completely outside + return 1; +}; + +// https://cesium.com/blog/2017/02/02/tighter-frustum-culling-and-why-you-may-want-to-disregard-it/ +int obbFrustumAccurate(const OBB &obb, const Frustum &frustum) { + ccstd::array tmp = {}; + float dist = 0.0F; + size_t out1 = 0; + size_t out2 = 0; + auto dot = [](const Vec3 &n, float x, float y, float z) -> float { + return n.x * x + n.y * y + n.z * z; + }; + int result = 0; + auto intersects = false; + // 1. obb inside/outside frustum test + for (const auto &plane : frustum.planes) { + result = obbPlane(obb, *plane); + // frustum plane normal points to the inside + if (result == -1) { + return 0; // completely outside + } + if (result == 1) { + intersects = true; + } + } + if (!intersects) { + return 1; + } // completely inside + // in case of false positives + // 2. frustum inside/outside obb test + for (auto i = 0; i < frustum.vertices.size(); i++) { + tmp[i] = frustum.vertices[i] - obb.center; + } + out1 = 0; + out2 = 0; + for (auto i = 0; i < frustum.vertices.size(); i++) { + dist = dot(tmp[i], obb.orientation.m[0], obb.orientation.m[1], obb.orientation.m[2]); + if (dist > obb.halfExtents.x) { + out1++; + } else if (dist < -obb.halfExtents.x) { + out2++; + } + } + if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) { + return 0.0F; + } + out1 = 0; + out2 = 0; + for (auto i = 0; i < frustum.vertices.size(); i++) { + dist = dot(tmp[i], obb.orientation.m[3], obb.orientation.m[4], obb.orientation.m[5]); + if (dist > obb.halfExtents.y) { + out1++; + } else if (dist < -obb.halfExtents.y) { + out2++; + } + } + if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) { + return 0; + } + out1 = 0; + out2 = 0; + for (auto i = 0; i < frustum.vertices.size(); i++) { + dist = dot(tmp[i], obb.orientation.m[6], obb.orientation.m[7], obb.orientation.m[8]); + if (dist > obb.halfExtents.z) { + out1++; + } else if (dist < -obb.halfExtents.z) { + out2++; + } + } + if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) { + return 0; + } + return 1; +} + +int obbWithOBB(const OBB &obb1, const OBB &obb2) { + ccstd::array vertices; + ccstd::array vertices2; + ccstd::array test; + + test[0] = {obb1.orientation.m[0], obb1.orientation.m[1], obb1.orientation.m[2]}; + test[1] = {obb1.orientation.m[3], obb1.orientation.m[4], obb1.orientation.m[5]}; + test[2] = {obb1.orientation.m[6], obb1.orientation.m[7], obb1.orientation.m[8]}; + test[3] = {obb2.orientation.m[0], obb2.orientation.m[1], obb2.orientation.m[2]}; + test[4] = {obb2.orientation.m[3], obb2.orientation.m[4], obb2.orientation.m[5]}; + test[5] = {obb2.orientation.m[6], obb2.orientation.m[7], obb2.orientation.m[8]}; + + for (auto i = 0; i < 3; ++i) { // Fill out rest of axis + Vec3::cross(test[i], test[3], &test[6 + i * 3 + 0]); + Vec3::cross(test[i], test[4], &test[6 + i * 3 + 1]); + Vec3::cross(test[i], test[5], &test[6 + i * 3 + 2]); + } + + getOBBVertices(obb1.center, obb1.halfExtents, test[0], test[1], test[2], &vertices); + getOBBVertices(obb2.center, obb2.halfExtents, test[3], test[4], test[5], &vertices2); + + for (auto i = 0; i < 15; ++i) { + auto a = getInterval(vertices, test[i]); + auto b = getInterval(vertices2, test[i]); + if (b.min > a.max || a.min > b.max) { + return 0; // Seperating axis found + } + } + return 1; +} + +// https://github.com/diku-dk/bvh-tvcg18/blob/1fd3348c17bc8cf3da0b4ae60fdb8f2aa90a6ff0/FOUNDATION/GEOMETRY/GEOMETRY/include/overlap/geometry_overlap_obb_capsule.h +int obbCapsule(const OBB &obb, const Capsule &capsule) { + Sphere sphere{}; + ccstd::array v3Verts8{}; + ccstd::array v3Axis8{}; + auto h = capsule.ellipseCenter0.distanceSquared(capsule.ellipseCenter1); + if (h == 0.0F) { + sphere.setRadius(capsule.radius); + sphere.setCenter(capsule.ellipseCenter0); + return sphereOBB(sphere, obb); + } + Vec3 v3Tmp0 = { + obb.orientation.m[0], + obb.orientation.m[1], + obb.orientation.m[2]}; + Vec3 v3Tmp1 = { + obb.orientation.m[3], + obb.orientation.m[4], + obb.orientation.m[5]}; + Vec3 v3Tmp2 = { + obb.orientation.m[6], + obb.orientation.m[7], + obb.orientation.m[8]}; + getOBBVertices(obb.center, obb.halfExtents, v3Tmp0, v3Tmp1, v3Tmp2, &v3Verts8); + + auto axes = v3Axis8; + auto a0 = axes[0] = v3Tmp0; + auto a1 = axes[1] = v3Tmp1; + auto a2 = axes[2] = v3Tmp2; + auto cc = axes[3] = capsule.center - obb.center; + auto bb = axes[4] = capsule.ellipseCenter0 - capsule.ellipseCenter1; + cc.normalize(); + bb.normalize(); + Vec3::cross(a0, bb, &axes[5]); + Vec3::cross(a1, bb, &axes[6]); + Vec3::cross(a2, bb, &axes[7]); + + for (auto i = 0; i < 8; ++i) { + auto a = getInterval(v3Verts8, axes[i]); + auto d0 = Vec3::dot(axes[i], capsule.ellipseCenter0); + auto d1 = Vec3::dot(axes[i], capsule.ellipseCenter1); + auto dMin = std::min(d0, d1) - capsule.radius; + auto dMax = std::max(d0, d1) + capsule.radius; + if (dMin > a.max || a.min > dMax) { + return 0; // Seperating axis found + } + } + return 1; +} + +int spherePlane(const Sphere &sphere, const Plane &plane) { + auto dot = Vec3::dot(plane.n, sphere.getCenter()); + auto r = sphere.getRadius() * plane.n.length(); + if (dot + r < plane.d) { + return -1; + } + if (dot - r > plane.d) { + return 0; + } + return 1; +} + +int sphereFrustum(const Sphere &sphere, const Frustum &frustum) { + for (const auto &plane : frustum.planes) { + // frustum plane normal points to the inside + if (spherePlane(sphere, *plane) == -1) { + return 0; + } + } // completely outside + return 1; +} + +int sphereFrustumAccurate(const Sphere &sphere, const Frustum &frustum) { + const static ccstd::array MAP = {1, -1, 1, -1, 1, -1}; + for (auto i = 0; i < 6; i++) { + const auto &plane = frustum.planes[i]; + const auto &n = plane->n; + const auto &d = plane->d; + auto r = sphere.getRadius(); + const auto &c = sphere.getCenter(); + auto dot = Vec3::dot(n, c); + // frustum plane normal points to the inside + if (dot + r < d) { + return 0; // completely outside + } + if (dot - r > d) { + continue; + } + // in case of false positives + // has false negatives, still working on it + auto pt = c + n * r; + for (auto j = 0; j < 6; j++) { + if (j == i || j == i + MAP[i]) { + continue; + } + const auto &test = frustum.planes[j]; + if (Vec3::dot(test->n, pt) < test->d) { + return 0; + } + } + } + return 1; +} + +bool sphereWithSphere(const Sphere &sphere0, const Sphere &sphere1) { + auto r = sphere0.getRadius() + sphere1.getRadius(); + return sphere0.getCenter().distanceSquared(sphere1.getCenter()) < r * r; +} + +bool sphereAABB(const Sphere &sphere, const AABB &aabb) { + Vec3 pt; + ptPointAabb(&pt, sphere.getCenter(), aabb); + return sphere.getCenter().distanceSquared(pt) < sphere.getRadius() * sphere.getRadius(); +} + +bool sphereOBB(const Sphere &sphere, const OBB &obb) { + Vec3 pt; + ptPointObb(&pt, sphere.getCenter(), obb); + return sphere.getCenter().distanceSquared(pt) < sphere.getRadius() * sphere.getRadius(); +} + +bool sphereCapsule(const Sphere &sphere, const Capsule &capsule) { + auto r = sphere.getRadius() + capsule.radius; + auto squaredR = r * r; + auto h = capsule.ellipseCenter0.distanceSquared(capsule.ellipseCenter1); + if (h == 0.0F) { + return sphere.getCenter().distanceSquared(capsule.center) < squaredR; + } + auto v3Tmp0 = sphere.getCenter() - capsule.ellipseCenter0; + auto v3Tmp1 = capsule.ellipseCenter1 - capsule.ellipseCenter0; + auto t = Vec3::dot(v3Tmp0, v3Tmp1) / h; + if (t < 0) { + return sphere.getCenter().distanceSquared(capsule.ellipseCenter0) < squaredR; + } + if (t > 1) { + return sphere.getCenter().distanceSquared(capsule.ellipseCenter1) < squaredR; + } + v3Tmp0 = capsule.ellipseCenter0 + t * v3Tmp1; + return sphere.getCenter().distanceSquared(v3Tmp0) < squaredR; +} + +bool capsuleWithCapsule(const Capsule &capsuleA, const Capsule &capsuleB) { + auto u = capsuleA.ellipseCenter1 - capsuleA.ellipseCenter0; + auto v = capsuleB.ellipseCenter1 - capsuleB.ellipseCenter0; + auto w = capsuleA.ellipseCenter0 - capsuleB.ellipseCenter0; + auto a = Vec3::dot(u, u); // always >= 0 + auto b = Vec3::dot(u, v); + auto c = Vec3::dot(v, v); // always >= 0 + auto d = Vec3::dot(u, w); + auto e = Vec3::dot(v, w); + auto dd = a * c - b * b; // always >= 0 + float sN; + float sD = dd; // sc = sN / sD, default sD = dd >= 0 + float tN; + float tD = dd; // tc = tN / tD, default tD = dd >= 0 + + // compute the line parameters of the two closest points + if (dd < math::EPSILON) { // the lines are almost parallel + sN = 0.0F; // force using point P0 on segment S1 + sD = 1.0F; // to prevent possible division by 0.0 later + tN = e; + tD = c; + } else { // get the closest points on the infinite lines + sN = (b * e - c * d); + tN = (a * e - b * d); + if (sN < 0.0F) { // sc < 0 => the s=0 edge is visible + sN = 0.0F; + tN = e; + tD = c; + } else if (sN > sD) { // sc > 1 => the s=1 edge is visible + sN = sD; + tN = e + b; + tD = c; + } + } + + if (tN < 0.0F) { // tc < 0 => the t=0 edge is visible + tN = 0.0F; + // recompute sc for this edge + if (-d < 0.0F) { + sN = 0.0F; + } else if (-d > a) { + sN = sD; + } else { + sN = -d; + sD = a; + } + } else if (tN > tD) { // tc > 1 => the t=1 edge is visible + tN = tD; + // recompute sc for this edge + if ((-d + b) < 0.0F) { + sN = 0.0F; + } else if ((-d + b) > a) { + sN = sD; + } else { + sN = (-d + b); + sD = a; + } + } + // finally do the division to get sc and tc + auto sc = (std::abs(sN) < math::EPSILON ? 0.0F : sN / sD); + auto tc = (std::abs(tN) < math::EPSILON ? 0.0F : tN / tD); + + // get the difference of the two closest points + auto dP = w; + dP = dP + u * sc; + dP = dP - v * tc; + auto radius = capsuleA.radius + capsuleB.radius; + return dP.lengthSquared() < radius * radius; +} + +int dynObbFrustum(const OBB &obb, const Frustum &frustum) { + if (frustum.getType() == ShapeEnum::SHAPE_FRUSTUM_ACCURATE) { + return obbFrustumAccurate(obb, frustum); + } + return obbFrustum(obb, frustum); +} + +int dynSphereFrustum(const Sphere &sphere, const Frustum &frustum) { + if (frustum.getType() == ShapeEnum::SHAPE_FRUSTUM_ACCURATE) { + return sphereFrustumAccurate(sphere, frustum); + } + return sphereFrustum(sphere, frustum); +} + +int dynAabbFrustum(const AABB &aabb, const Frustum &frustum) { + if (frustum.getType() == ShapeEnum::SHAPE_FRUSTUM_ACCURATE) { + return aabbFrustumAccurate(aabb, frustum); + } + return aabbFrustum(aabb, frustum); +} + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Intersect.h b/cocos/core/geometry/Intersect.h new file mode 100644 index 0000000..3109790 --- /dev/null +++ b/cocos/core/geometry/Intersect.h @@ -0,0 +1,498 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" + +#include "core/assets/RenderingSubMesh.h" +#include "core/geometry/Distance.h" +#include "core/geometry/Enums.h" +#include "core/geometry/Frustum.h" +#include "core/geometry/Spec.h" + +#include + +namespace cc { + +class Vec3; +class Mat3; + +namespace scene { +class Model; +} + +namespace geometry { + +class Ray; +class Plane; +class OBB; +class AABB; +class Line; +class Frustum; +class Sphere; +class Triangle; +class Capsule; + +/** + * @en + * ray-plane intersect detect. + * @zh + * 射线与平面的相交性检测。 + * @param {Ray} ray 射线 + * @param {Plane} plane 平面 + * @return {number} 0 或 非0 + */ +float rayPlane(const Ray &ray, const Plane &plane); + +// based on http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/raytri/ +/** + * @en + * ray-triangle intersect detect. + * @zh + * 射线与三角形的相交性检测。 + * @param {Ray} ray 射线 + * @param {Triangle} triangle 三角形 + * @param {boolean} doubleSided 三角形是否为双面 + * @return {number} 0 或 非0 + */ +float rayTriangle(const Ray &ray, const Triangle &triangle, bool doubleSided = false); +/** + * @en + * ray-sphere intersect detect. + * @zh + * 射线和球的相交性检测。 + * @param {Ray} ray 射线 + * @param {Sphere} sphere 球 + * @return {number} 0 或 非0 + */ +float raySphere(const Ray &ray, const Sphere &sphere); + +float rayAABB2(const Ray &ray, const Vec3 &min, const Vec3 &max); + +/** + * @en + * ray-aabb intersect detect. + * @zh + * 射线和轴对齐包围盒的相交性检测。 + * @param {Ray} ray 射线 + * @param {AABB} aabb 轴对齐包围盒 + * @return {number} 0 或 非0 + */ +float rayAABB(const Ray &ray, const AABB &aabb); + +/** + * @en + * ray-obb intersect detect. + * @zh + * 射线和方向包围盒的相交性检测。 + * @param {Ray} ray 射线 + * @param {OBB} obb 方向包围盒 + * @return {number} 0 或 非0 + */ +float rayOBB(const Ray &ray, const OBB &obb); + +/** + * @en + * ray-capsule intersect detect. + * @zh + * 射线和胶囊体的相交性检测。 + * @param {Ray} ray 射线 + * @param {Capsule} capsule 胶囊体 + * @return {number} 0 或 非0 + */ +float rayCapsule(const Ray &ray, const Capsule &capsule); + +/** + * @en + * ray-subMesh intersect detect, in model space. + * @zh + * 在模型空间中,射线和子三角网格的相交性检测。 + * @param {Ray} ray + * @param {RenderingSubMesh} subMesh + * @param {IRaySubMeshOptions} options + * @return {number} 0 or !0 + */ +float raySubMesh(const Ray &ray, const RenderingSubMesh &submesh, IRaySubMeshOptions *options = nullptr); + +/** + * @en + * ray-mesh intersect detect, in model space. + * @zh + * 在模型空间中,射线和三角网格资源的相交性检测。 + * @param {Ray} ray + * @param {Mesh} mesh + * @param {IRayMeshOptions} options + * @return {number} 0 or !0 + */ +float rayMesh(const Ray &ray, const Mesh &mesh, IRayMeshOptions *option); + +/** + * @en + * ray-model intersect detect, in world space. + * @zh + * 在世界空间中,射线和渲染模型的相交性检测。 + * @param ray + * @param model + * @param options + * @return 0 or !0 + */ +float rayModel(const Ray &ray, const scene::Model &model, IRayModelOptions *option); + +/** + * @en + * line-plane intersect detect. + * @zh + * 线段与平面的相交性检测。 + * @param {line} line 线段 + * @param {Plane} plane 平面 + * @return {number} 0 或 非0 + */ +float linePlane(const Line &line, const Plane &plane); +/** + * @en + * line-triangle intersect detect. + * @zh + * 线段与三角形的相交性检测。 + * @param {line} line 线段 + * @param {Triangle} triangle 三角形 + * @param {Vec3} outPt 可选,相交点 + * @return {number} 0 或 非0 + */ +int lineTriangle(const Line &line, const Triangle &triangle, Vec3 *outPt = nullptr); + +/** + * @en + * line-aabb intersect detect. + * @zh + * 线段与轴对齐包围盒的相交性检测 + * @param line 线段 + * @param aabb 轴对齐包围盒 + * @return {number} 0 或 非0 + */ +float lineAABB(const Line &line, const AABB &aabb); + +/** + * @en + * line-obb intersect detect. + * @zh + * 线段与方向包围盒的相交性检测 + * @param line 线段 + * @param obb 方向包围盒 + * @return {number} 0 或 非0 + */ +float lineOBB(const Line &line, const OBB &obb); + +/** + * @en + * line-sphere intersect detect. + * @zh + * 线段与球的相交性检测 + * @param line 线段 + * @param sphere 球 + * @return {number} 0 或 非0 + */ +float lineSphere(const Line &line, const Sphere &sphere); + +/** + * @en + * aabb-aabb intersect detect. + * @zh + * 轴对齐包围盒和轴对齐包围盒的相交性检测。 + * @param {AABB} aabb1 轴对齐包围盒1 + * @param {AABB} aabb2 轴对齐包围盒2 + * @return {number} 0 或 非0 + */ +bool aabbWithAABB(const AABB &aabb1, const AABB &aabb2); + +/** + * @en + * aabb-obb intersect detect. + * @zh + * 轴对齐包围盒和方向包围盒的相交性检测。 + * @param {AABB} aabb 轴对齐包围盒 + * @param {OBB} obb 方向包围盒 + * @return {number} 0 或 非0 + */ +int aabbWithOBB(const AABB &aabb, const OBB &obb); + +/** + * @en + * aabb-plane intersect detect. + * @zh + * 轴对齐包围盒和平面的相交性检测。 + * @param {AABB} aabb 轴对齐包围盒 + * @param {Plane} plane 平面 + * @return {number} inside(back) = -1, outside(front) = 0, intersect = 1 + */ + +int aabbPlane(const AABB &aabb, const Plane &plane); +/** + * @en + * aabb-frustum intersect detect, faster but has false positive corner cases. + * @zh + * 轴对齐包围盒和锥台相交性检测,速度快,但有错误情况。 + * @param {AABB} aabb 轴对齐包围盒 + * @param {Frustum} frustum 锥台 + * @return {number} 0 或 非0 + */ + +int aabbFrustum(const AABB &aabb, const Frustum &frustum); +/** + * @en + * aabb-frustum intersect detect, faster but false while frustum is completely inside the aabb. + * @zh + * 轴对齐包围盒和锥台的相交性检测。速度快,但是当锥台完全在aabb中时就会判断出错。 + * @param {AABB} aabb 轴对齐包围盒 + * @param {Frustum} frustum 锥台 + * @return {number} aabb completely inside the frustum = 1, otherwise = 0 + */ +int aabbFrustumCompletelyInside(const AABB &aabb, const Frustum &frustum); + +// https://cesium.com/blog/2017/02/02/tighter-frustum-culling-and-why-you-may-want-to-disregard-it/ +/** + * @en + * aabb-frustum intersect, handles most of the false positives correctly. + * @zh + * 轴对齐包围盒和锥台相交性检测,正确处理大多数错误情况。 + * @param {AABB} aabb 轴对齐包围盒 + * @param {Frustum} frustum 锥台 + * @return {number} + */ +int aabbFrustumAccurate(const AABB &aabb, const Frustum &frustum); + +/** + * @en + * obb contains the point. + * @zh + * 方向包围盒和点的相交性检测。 + * @param {OBB} obb 方向包围盒 + * @param {Vec3} point 点 + * @return {boolean} true or false + */ +bool obbPoint(const OBB &obb, const Vec3 &point); + +/** + * @en + * obb-plane intersect detect. + * @zh + * 方向包围盒和平面的相交性检测。 + * @param {OBB} obb 方向包围盒 + * @param {Plane} plane 平面 + * @return {number} inside(back) = -1, outside(front) = 0, intersect = 1 + */ +int obbPlane(const OBB &obb, const Plane &plane); + +/** + * @en + * obb-frustum intersect, faster but has false positive corner cases. + * @zh + * 方向包围盒和锥台相交性检测,速度快,但有错误情况。 + * @param {OBB} obb 方向包围盒 + * @param {Frustum} frustum 锥台 + * @return {number} 0 或 非0 + */ +int obbFrustum(const OBB &obb, const Frustum &frustum); + +// https://cesium.com/blog/2017/02/02/tighter-frustum-culling-and-why-you-may-want-to-disregard-it/ +/** + * @en + * obb-frustum intersect, handles most of the false positives correctly. + * @zh + * 方向包围盒和锥台相交性检测,正确处理大多数错误情况。 + * @param {OBB} obb 方向包围盒 + * @param {Frustum} frustum 锥台 + * @return {number} 0 或 非0 + */ +int obbFrustumAccurate(const OBB &obb, const Frustum &frustum); + +/** + * @en + * obb-obb intersect detect. + * @zh + * 方向包围盒和方向包围盒的相交性检测。 + * @param {OBB} obb1 方向包围盒1 + * @param {OBB} obb2 方向包围盒2 + * @return {number} 0 或 非0 + */ +int obbWithOBB(const OBB &obb1, const OBB &obb2); + +// https://github.com/diku-dk/bvh-tvcg18/blob/1fd3348c17bc8cf3da0b4ae60fdb8f2aa90a6ff0/FOUNDATION/GEOMETRY/GEOMETRY/include/overlap/geometry_overlap_obb_capsule.h +/** + * @en + * obb-capsule intersect detect. + * @zh + * 方向包围盒和胶囊体的相交性检测。 + * @param obb 方向包围盒 + * @param capsule 胶囊体 + */ +int obbCapsule(const OBB &obb, const Capsule &capsule); + +/** + * @en + * sphere-plane intersect, not necessarily faster than obb-plane,due to the length calculation of the + * plane normal to factor out the unnomalized plane distance. + * @zh + * 球与平面的相交性检测。 + * @param {Sphere} sphere 球 + * @param {Plane} plane 平面 + * @return {number} inside(back) = -1, outside(front) = 0, intersect = 1 + */ +int spherePlane(const Sphere &sphere, const Plane &plane); +/** + * @en + * sphere-frustum intersect, faster but has false positive corner cases. + * @zh + * 球和锥台的相交性检测,速度快,但有错误情况。 + * @param {Sphere} sphere 球 + * @param {Frustum} frustum 锥台 + * @return {number} 0 或 非0 + */ +int sphereFrustum(const Sphere &sphere, const Frustum &frustum); + +// https://stackoverflow.com/questions/20912692/view-frustum-culling-corner-cases +/** + * @en + * sphere-frustum intersect, handles the false positives correctly. + * @zh + * 球和锥台的相交性检测,正确处理大多数错误情况。 + * @param {Sphere} sphere 球 + * @param {Frustum} frustum 锥台 + * @return {number} 0 或 非0 + */ +int sphereFrustumAccurate(const Sphere &sphere, const Frustum &frustum); + +/** + * @en + * sphere-sphere intersect detect. + * @zh + * 球和球的相交性检测。 + * @param {Sphere} sphere0 球0 + * @param {Sphere} sphere1 球1 + * @return {boolean} true or false + */ +bool sphereWithSphere(const Sphere &sphere0, const Sphere &sphere1); +/** + * @en + * sphere-aabb intersect detect. + * @zh + * 球和轴对齐包围盒的相交性检测。 + * @param {Sphere} sphere 球 + * @param {AABB} aabb 轴对齐包围盒 + * @return {boolean} true or false + */ +bool sphereAABB(const Sphere &sphere, const AABB &aabb); + +/** + * @en + * sphere-obb intersect detect. + * @zh + * 球和方向包围盒的相交性检测。 + * @param {Sphere} sphere 球 + * @param {OBB} obb 方向包围盒 + * @return {boolean} true or false + */ +bool sphereOBB(const Sphere &sphere, const OBB &obb); + +/** + * @en + * sphere-capsule intersect detect. + * @zh + * 球和胶囊体的相交性检测。 + */ +bool sphereCapsule(const Sphere &sphere, const Capsule &capsule); + +// http://www.geomalgorithms.com/a07-_distance.html +/** + * @en + * capsule-capsule intersect detect. + * @zh + * 胶囊体和胶囊体的相交性检测。 + */ +bool capsuleWithCapsule(const Capsule &capsuleA, const Capsule &capsuleB); + +template +auto intersects(const T1 & /*a*/, const T2 & /*b*/) { + static_assert(std::is_base_of::value, "type is not base of ShapeBase"); + static_assert(std::is_base_of::value, "type is not base of ShapeBase"); + CC_ABORT(); // mismatch +} + +#define DECLARE_EXCHANGABLE_INTERSECT(TYPE1, TYPE2, FN) \ + template <> \ + inline auto intersects(const TYPE1 &arg1, const TYPE2 &arg2) { \ + return FN(arg1, arg2); \ + } \ + template <> \ + inline auto intersects(const TYPE2 &arg2, const TYPE1 &arg1) { \ + return FN(arg1, arg2); \ + } + +#define DECLARE_SELF_INTERSECT(TYPE, FN) \ + template <> \ + inline auto intersects(const TYPE &arg1, const TYPE &arg2) { \ + return FN(arg1, arg2); \ + } + +int dynObbFrustum(const OBB &obb, const Frustum &frustum); +int dynSphereFrustum(const Sphere &sphere, const Frustum &frustum); +int dynAabbFrustum(const AABB &aabb, const Frustum &frustum); + +DECLARE_EXCHANGABLE_INTERSECT(Ray, Sphere, raySphere) +DECLARE_EXCHANGABLE_INTERSECT(Ray, AABB, rayAABB) +DECLARE_EXCHANGABLE_INTERSECT(Ray, OBB, rayOBB) +DECLARE_EXCHANGABLE_INTERSECT(Ray, Plane, rayPlane) +DECLARE_EXCHANGABLE_INTERSECT(Ray, Triangle, rayTriangle) +DECLARE_EXCHANGABLE_INTERSECT(Ray, Capsule, rayCapsule) + +DECLARE_EXCHANGABLE_INTERSECT(Line, Sphere, lineSphere) +DECLARE_EXCHANGABLE_INTERSECT(Line, AABB, lineAABB) +DECLARE_EXCHANGABLE_INTERSECT(Line, OBB, lineOBB) +DECLARE_EXCHANGABLE_INTERSECT(Line, Plane, linePlane) +DECLARE_EXCHANGABLE_INTERSECT(Line, Triangle, lineTriangle) + +DECLARE_SELF_INTERSECT(Sphere, sphereWithSphere) +DECLARE_EXCHANGABLE_INTERSECT(Sphere, AABB, sphereAABB) +DECLARE_EXCHANGABLE_INTERSECT(Sphere, OBB, sphereOBB) +DECLARE_EXCHANGABLE_INTERSECT(Sphere, Plane, spherePlane) +DECLARE_EXCHANGABLE_INTERSECT(Sphere, Frustum, dynSphereFrustum) +DECLARE_EXCHANGABLE_INTERSECT(Sphere, Capsule, sphereCapsule) + +DECLARE_SELF_INTERSECT(AABB, aabbWithAABB) +DECLARE_EXCHANGABLE_INTERSECT(AABB, OBB, aabbWithOBB) +DECLARE_EXCHANGABLE_INTERSECT(AABB, Plane, aabbPlane) +DECLARE_EXCHANGABLE_INTERSECT(AABB, Frustum, dynAabbFrustum) + +DECLARE_SELF_INTERSECT(OBB, obbWithOBB) +DECLARE_EXCHANGABLE_INTERSECT(OBB, Plane, obbPlane) +DECLARE_EXCHANGABLE_INTERSECT(OBB, Frustum, dynObbFrustum) +DECLARE_EXCHANGABLE_INTERSECT(OBB, Capsule, obbCapsule) + +DECLARE_SELF_INTERSECT(Capsule, capsuleWithCapsule) + +#undef DECLARE_SELF_INTERSECT +#undef DECLARE_EXCHANGABLE_INTERSECT + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Line.cpp b/cocos/core/geometry/Line.cpp new file mode 100644 index 0000000..dfed81e --- /dev/null +++ b/cocos/core/geometry/Line.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/geometry/Line.h" +#include "base/memory/Memory.h" + +namespace cc { +namespace geometry { + +Line *Line::create(float sx, + float sy, + float sz, + float ex, + float ey, + float ez) { + return ccnew Line(sx, sy, sz, ex, ey, ez); +} + +Line *Line::clone(const Line &a) { + return ccnew Line( + a.s.x, a.s.y, a.s.z, + a.e.x, a.e.y, a.e.z); +} + +Line *Line::copy(Line *out, const Line &a) { + out->s = a.s; + out->e = a.e; + return out; +} + +Line *Line::fromPoints(Line *out, const Vec3 &start, const Vec3 &end) { + out->s = start; + out->e = end; + return out; +} + +Line *Line::set(Line *out, + float sx, + float sy, + float sz, + float ex, + float ey, + float ez) { + out->s.x = sx; + out->s.y = sy; + out->s.z = sz; + out->e.x = ex; + out->e.y = ey; + out->e.z = ez; + + return out; +} + +Line::Line(float sx, float sy, float sz, float ex, float ey, float ez) : ShapeBase(ShapeEnum::SHAPE_LINE) { + s = {sx, sy, sz}; + e = {ex, ey, ez}; +} + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Line.h b/cocos/core/geometry/Line.h new file mode 100644 index 0000000..2738a79 --- /dev/null +++ b/cocos/core/geometry/Line.h @@ -0,0 +1,171 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/geometry/Enums.h" +#include "math/Vec3.h" +namespace cc { +namespace geometry { + +/** + * @en + * Basic Geometry: Line. + * @zh + * 基础几何 line。 + */ + +class Line final : public ShapeBase { +public: + /** + * @en + * create a new line + * @zh + * 创建一个新的 line。 + * @param sx 起点的 x 部分。 + * @param sy 起点的 y 部分。 + * @param sz 起点的 z 部分。 + * @param ex 终点的 x 部分。 + * @param ey 终点的 y 部分。 + * @param ez 终点的 z 部分。 + * @return + */ + + static Line *create(float sx, + float sy, + float sz, + float ex, + float ey, + float ez); + + /** + * @en + * Creates a new Line initialized with values from an existing Line + * @zh + * 克隆一个新的 line。 + * @param a 克隆的来源。 + * @return 克隆出的对象。 + */ + + static Line *clone(const Line &a); + + /** + * @en + * Copy the values from one Line to another + * @zh + * 复制一个线的值到另一个。 + * @param out 接受操作的对象。 + * @param a 复制的来源。 + * @return 接受操作的对象。 + */ + static Line *copy(Line *out, const Line &a); + + /** + * @en + * create a line from two points + * @zh + * 用两个点创建一个线。 + * @param out 接受操作的对象。 + * @param start 起点。 + * @param end 终点。 + * @return out 接受操作的对象。 + */ + + static Line *fromPoints(Line *out, const Vec3 &start, const Vec3 &end); + + /** + * @en + * Set the components of a Vec3 to the given values + * @zh + * 将给定线的属性设置为给定值。 + * @param out 接受操作的对象。 + * @param sx 起点的 x 部分。 + * @param sy 起点的 y 部分。 + * @param sz 起点的 z 部分。 + * @param ex 终点的 x 部分。 + * @param ey 终点的 y 部分。 + * @param ez 终点的 z 部分。 + * @return out 接受操作的对象。 + */ + static Line *set(Line *out, + float sx, + float sy, + float sz, + float ex, + float ey, + float ez); + + /** + * @zh + * 计算线的长度。 + * @param a 要计算的线。 + * @return 长度。 + */ + static inline float len(const Line &a) { + return a.s.distance(a.e); + } + + /** + * @zh + * 起点。 + */ + Vec3 s; + + /** + * @zh + * 终点。 + */ + Vec3 e; + + /** + * 构造一条线。 + * @param sx 起点的 x 部分。 + * @param sy 起点的 y 部分。 + * @param sz 起点的 z 部分。 + * @param ex 终点的 x 部分。 + * @param ey 终点的 y 部分。 + * @param ez 终点的 z 部分。 + */ + explicit Line(float sx = 0, float sy = 0, float sz = 0, float ex = 0, float ey = 0, float ez = -1); + + Line(const Line &) = default; + Line &operator=(const Line &) = default; + Line &operator=(Line &&) = default; + Line(Line &&) = default; + ~Line() override = default; + + /** + * @zh + * 计算线的长度。 + * @param a 要计算的线。 + * @return 长度。 + */ + + inline float length() const { + return s.distance(e); + } +}; + +} // namespace geometry +} // namespace cc \ No newline at end of file diff --git a/cocos/core/geometry/Obb.cpp b/cocos/core/geometry/Obb.cpp new file mode 100644 index 0000000..33b06cf --- /dev/null +++ b/cocos/core/geometry/Obb.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "cocos/core/geometry/Obb.h" +#include "base/memory/Memory.h" + +namespace { +void transformExtentM3(cc::Vec3 *out, const cc::Vec3 &extent, const cc::Mat3 &m3) { + cc::Mat3 m3Tmp; + m3Tmp.m[0] = std::abs(m3.m[0]); + m3Tmp.m[1] = std::abs(m3.m[1]); + m3Tmp.m[2] = std::abs(m3.m[2]); + m3Tmp.m[3] = std::abs(m3.m[3]); + m3Tmp.m[4] = std::abs(m3.m[4]); + m3Tmp.m[5] = std::abs(m3.m[5]); + m3Tmp.m[6] = std::abs(m3.m[6]); + m3Tmp.m[7] = std::abs(m3.m[7]); + m3Tmp.m[8] = std::abs(m3.m[8]); + out->transformMat3(extent, m3Tmp); +}; +} // namespace + +namespace cc { +namespace geometry { + +OBB *OBB::create( + float cx, float cy, float cz, + float hw, float hh, float hl, + float ox1, float ox2, float ox3, + float oy1, float oy2, float oy3, + float oz1, float oz2, float oz3) { + return ccnew OBB(cx, cy, cz, hw, hh, hl, + ox1, ox2, ox3, + oy1, oy2, oy3, + oz1, oz2, oz3); +} + +OBB *OBB::clone(const OBB &a) { + return ccnew OBB(a.center.x, a.center.y, a.center.z, + a.halfExtents.x, a.halfExtents.y, a.halfExtents.z, + a.orientation.m[0], a.orientation.m[1], a.orientation.m[2], + a.orientation.m[3], a.orientation.m[4], a.orientation.m[5], + a.orientation.m[6], a.orientation.m[7], a.orientation.m[8]); +} + +OBB *OBB::copy(OBB *out, const OBB &a) { + out->center = a.center; + out->halfExtents = a.halfExtents; + out->orientation = a.orientation; + return out; +} + +OBB *OBB::fromPoints(OBB *out, const Vec3 &minPos, const Vec3 &maxPos) { + out->center = 0.5F * (minPos + maxPos); + out->halfExtents = 0.5F * (maxPos - minPos); + Mat3::identity(out->orientation); + return out; +} + +OBB *OBB::set(OBB *out, + float cx, float cy, float cz, + float hw, float hh, float hl, + float ox1, float ox2, float ox3, + float oy1, float oy2, float oy3, + float oz1, float oz2, float oz3) { + out->center = {cx, cy, cz}; + out->halfExtents = {hw, hh, hl}; + out->orientation = {ox1, ox2, ox3, oy1, oy2, oy3, oz1, oz2, oz3}; + return out; +} + +OBB::OBB(float cx, float cy, float cz, + float hw, float hh, float hl, + float ox1, float ox2, float ox3, + float oy1, float oy2, float oy3, + float oz1, float oz2, float oz3) : ShapeBase(ShapeEnum::SHAPE_OBB) { + center = {cx, cy, cz}; + halfExtents = {hw, hh, hl}; + orientation = {ox1, ox2, ox3, oy1, oy2, oy3, oz1, oz2, oz3}; +} + +void OBB::getBoundary(Vec3 *minPos, Vec3 *maxPos) const { + Vec3 v3Tmp; + transformExtentM3(&v3Tmp, halfExtents, orientation); + *minPos = center - v3Tmp; + *maxPos = center + v3Tmp; +} + +void OBB::transform(const Mat4 &m, const Vec3 & /*pos*/, const Quaternion &rot, const Vec3 &scale, OBB *out) const { + Vec3::transformMat4(center, m, &out->center); + // parent shape doesn't contain rotations for now + Mat3::fromQuat(rot, &out->orientation); + Vec3::multiply(halfExtents, scale, &out->halfExtents); +} + +void OBB::translateAndRotate(const Mat4 &m, const Quaternion &rot, OBB *out) const { + Vec3::transformMat4(center, m, &out->center); + // parent shape doesn't contain rotations for now + Mat3::fromQuat(rot, &out->orientation); +} + +void OBB::setScale(const Vec3 &scale, OBB *out) const { + Vec3::multiply(halfExtents, scale, &out->halfExtents); +} + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Obb.h b/cocos/core/geometry/Obb.h new file mode 100644 index 0000000..83ba0bf --- /dev/null +++ b/cocos/core/geometry/Obb.h @@ -0,0 +1,192 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "cocos/core/geometry/Enums.h" +#include "cocos/math/Mat3.h" +#include "cocos/math/Quaternion.h" +#include "cocos/math/Vec3.h" + +namespace cc { +namespace geometry { +class OBB final : public ShapeBase { +public: + /** + * @en + * create a new obb + * @zh + * 创建一个新的 obb 实例。 + * @param cx 形状的相对于原点的 X 坐标。 + * @param cy 形状的相对于原点的 Y 坐标。 + * @param cz 形状的相对于原点的 Z 坐标。 + * @param hw - obb 宽度的一半。 + * @param hh - obb 高度的一半。 + * @param hl - obb 长度的一半。 + * @param ox1 方向矩阵参数。 + * @param ox2 方向矩阵参数。 + * @param ox3 方向矩阵参数。 + * @param oy1 方向矩阵参数。 + * @param oy2 方向矩阵参数。 + * @param oy3 方向矩阵参数。 + * @param oz1 方向矩阵参数。 + * @param oz2 方向矩阵参数。 + * @param oz3 方向矩阵参数。 + * @return 返回一个 obb。 + */ + static OBB *create( + float cx, float cy, float cz, + float hw, float hh, float hl, + float ox1, float ox2, float ox3, + float oy1, float oy2, float oy3, + float oz1, float oz2, float oz3); + + /** + * @en + * clone a new obb + * @zh + * 克隆一个 obb。 + * @param a 克隆的目标。 + * @returns 克隆出的新对象。 + */ + static OBB *clone(const OBB &a); + /** + * @en + * copy the values from one obb to another + * @zh + * 将从一个 obb 的值复制到另一个 obb。 + * @param {OBB} out 接受操作的 obb。 + * @param {OBB} a 被复制的 obb。 + * @return {OBB} out 接受操作的 obb。 + */ + static OBB *copy(OBB *out, const OBB &a); + + /** + * @en + * create a new obb from two corner points + * @zh + * 用两个点创建一个新的 obb。 + * @param out - 接受操作的 obb。 + * @param minPos - obb 的最小点。 + * @param maxPos - obb 的最大点。 + * @returns {OBB} out 接受操作的 obb。 + */ + static OBB *fromPoints(OBB *out, const Vec3 &minPos, const Vec3 &maxPos); + + /** + * @en + * Set the components of a obb to the given values + * @zh + * 将给定 obb 的属性设置为给定的值。 + * @param cx - obb 的原点的 X 坐标。 + * @param cy - obb 的原点的 Y 坐标。 + * @param cz - obb 的原点的 Z 坐标。 + * @param hw - obb 宽度的一半。 + * @param hh - obb 高度的一半。 + * @param hl - obb 长度的一半。 + * @param ox1 方向矩阵参数。 + * @param ox2 方向矩阵参数。 + * @param ox3 方向矩阵参数。 + * @param oy1 方向矩阵参数。 + * @param oy2 方向矩阵参数。 + * @param oy3 方向矩阵参数。 + * @param oz1 方向矩阵参数。 + * @param oz2 方向矩阵参数。 + * @param oz3 方向矩阵参数。 + * @return {OBB} out + */ + + static OBB *set(OBB *out, + float cx, float cy, float cz, + float hw, float hh, float hl, + float ox1, float ox2, float ox3, + float oy1, float oy2, float oy3, + float oz1, float oz2, float oz3); + + /** + * @zh + * 本地坐标的中心点。 + */ + Vec3 center; + + /** + * @zh + * 长宽高的一半。 + */ + Vec3 halfExtents; + + /** + * @zh + * 方向矩阵。 + */ + + Mat3 orientation; + + explicit OBB(float cx = 0, float cy = 0, float cz = 0, + float hw = 1, float hh = 1, float hl = 1, + float ox1 = 1, float ox2 = 0, float ox3 = 0, + float oy1 = 0, float oy2 = 1, float oy3 = 0, + float oz1 = 0, float oz2 = 0, float oz3 = 1); + + /** + * @en + * Get the bounding points of this shape + * @zh + * 获取 obb 的最小点和最大点。 + * @param {Vec3} minPos 最小点。 + * @param {Vec3} maxPos 最大点。 + */ + void getBoundary(Vec3 *minPos, Vec3 *maxPos) const; + /** + * Transform this shape + * @zh + * 将 out 根据这个 obb 的数据进行变换。 + * @param m 变换的矩阵。 + * @param pos 变换的位置部分。 + * @param rot 变换的旋转部分。 + * @param scale 变换的缩放部分。 + * @param out 变换的目标。 + */ + void transform(const Mat4 &m, const Vec3 &pos, const Quaternion &rot, const Vec3 &scale, OBB *out) const; + + /** + * @zh + * 将 out 根据这个 obb 的数据进行变换。 + * @param m 变换的矩阵。 + * @param rot 变换的旋转部分。 + * @param out 变换的目标。 + */ + void translateAndRotate(const Mat4 &m, const Quaternion &rot, OBB *out) const; + + /** + * @zh + * 将 out 根据这个 obb 的数据进行缩放。 + * @param scale 缩放值。 + * @param out 缩放的目标。 + */ + void setScale(const Vec3 &scale, OBB *out) const; +}; + +} // namespace geometry +} // namespace cc \ No newline at end of file diff --git a/cocos/core/geometry/Plane.cpp b/cocos/core/geometry/Plane.cpp new file mode 100644 index 0000000..aaaa77d --- /dev/null +++ b/cocos/core/geometry/Plane.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "cocos/core/geometry/Plane.h" +#include "base/memory/Memory.h" + +namespace cc { +namespace geometry { +Plane *Plane::create(float nx, float ny, float nz, float d) { + return ccnew Plane{nx, ny, nz, d}; +} + +Plane *Plane::clone(const Plane &p) { + return ccnew Plane{p.n.x, p.n.y, p.n.z, p.d}; +} + +Plane *Plane::copy(Plane *out, const Plane &p) { + out->n = p.n; + out->d = p.d; + return out; +} + +Plane *Plane::fromPoints(Plane *out, + const Vec3 &a, + const Vec3 &b, + const Vec3 &c) { + Vec3::cross(b - a, c - a, &out->n); + out->n.normalize(); + out->d = Vec3::dot(out->n, a); + return out; +} + +Plane *Plane::fromNormalAndPoint(Plane *out, const Vec3 &normal, const Vec3 &point) { + out->n = normal; + out->d = Vec3::dot(normal, point); + return out; +} + +Plane *Plane::normalize(Plane *out, const Plane &a) { + const auto len = a.n.length(); + out->n = a.n.getNormalized(); + if (len > 0) { + out->d = a.d / len; + } + return out; +} + +Plane *Plane::set(Plane *out, float nx, float ny, float nz, float d) { + out->n = {nx, ny, nz}; + out->d = d; + return out; +} + +Plane::Plane(float nx, float ny, float nz, float d) : Plane() { + n = {nx, ny, nz}; + this->d = d; +} + +void Plane::transform(const Mat4 &mat) { + Mat4 tempMat = mat.getInversed(); + tempMat.transpose(); + Vec4 tempVec4 = {n.x, n.y, n.z, -d}; + tempMat.transformVector(&tempVec4); + n.set(tempVec4.x, tempVec4.y, tempVec4.z); + d = -tempVec4.w; +} + +// Define from 3 vertices. +void Plane::define(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2) { + const Vec3 dist1 = v1 - v0; + const Vec3 dist2 = v2 - v0; + + Vec3 dist; + Vec3::cross(dist1, dist2, &dist); + define(dist, v0); +} +// Define from a normal vector and a point on the plane. +void Plane::define(const Vec3 &normal, const Vec3 &point) { + n = normal.getNormalized(); + d = n.dot(point); +} + +// Return signed distance to a point. +float Plane::distance(const Vec3 &point) const { + return n.dot(point) - d; +} + +Plane Plane::clone() const { + Plane plane; + plane.n.set(n); + plane.d = d; + + return plane; +} + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Plane.h b/cocos/core/geometry/Plane.h new file mode 100644 index 0000000..bff719d --- /dev/null +++ b/cocos/core/geometry/Plane.h @@ -0,0 +1,191 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "cocos/core/geometry/Enums.h" +#include "cocos/math/Mat4.h" +#include "cocos/math/Vec3.h" +#include "cocos/math/Vec4.h" + +namespace cc { +namespace geometry { +/** + * @en + * Basic Geometry: Plane. + * @zh + * 基础几何 Plane。 + */ +class Plane final : public ShapeBase { + /** + * @en + * create a new plane + * @zh + * 创建一个新的 plane。 + * @param nx 法向分量的 x 部分。 + * @param ny 法向分量的 y 部分。 + * @param nz 法向分量的 z 部分。 + * @param d 与原点的距离。 + * @return + */ +public: + static Plane *create(float nx, float ny, float nz, float d); + + /** + * @en + * clone a new plane + * @zh + * 克隆一个新的 plane。 + * @param p 克隆的来源。 + * @return 克隆出的对象。 + */ + static Plane *clone(const Plane &p); + + /** + * @en + * copy the values from one plane to another + * @zh + * 复制一个平面的值到另一个。 + * @param out 接受操作的对象。 + * @param p 复制的来源。 + * @return 接受操作的对象。 + */ + + static Plane *copy(Plane *out, const Plane &p); + + /** + * @en + * create a plane from three points + * @zh + * 用三个点创建一个平面。 + * @param out 接受操作的对象。 + * @param a 点 a。 + * @param b 点 b。 + * @param c 点 c。 + * @return out 接受操作的对象。 + */ + static Plane *fromPoints(Plane *out, + const Vec3 &a, + const Vec3 &b, + const Vec3 &c); + + /** + * @en + * Set the components of a plane to the given values + * @zh + * 将给定平面的属性设置为给定值。 + * @param out 接受操作的对象。 + * @param nx 法向分量的 x 部分。 + * @param ny 法向分量的 y 部分。 + * @param nz 法向分量的 z 部分。 + * @param d 与原点的距离。 + * @return out 接受操作的对象。 + */ + static Plane *set(Plane *out, float nx, float ny, float nz, float d); + + /** + * @en + * create plane from normal and point + * @zh + * 用一条法线和一个点创建平面。 + * @param out 接受操作的对象。 + * @param normal 平面的法线。 + * @param point 平面上的一点。 + * @return out 接受操作的对象。 + */ + static Plane *fromNormalAndPoint(Plane *out, const Vec3 &normal, const Vec3 &point); + + /** + * @en + * normalize a plane + * @zh + * 归一化一个平面。 + * @param out 接受操作的对象。 + * @param a 操作的源数据。 + * @return out 接受操作的对象。 + */ + static Plane *normalize(Plane *out, const Plane &a); + + // compatibility with vector interfaces + inline void setX(float val) { n.x = val; } + inline float getX() const { return n.x; } + inline void setY(float val) { n.y = val; } + inline float getY() const { return n.y; } + inline void setZ(float val) { n.z = val; } + inline float getZ() const { return n.z; } + inline void setW(float val) { d = val; } + inline float getW() const { return d; } + + /** + * @en + * Construct a plane. + * @zh + * 构造一个平面。 + * @param nx 法向分量的 x 部分。 + * @param ny 法向分量的 y 部分。 + * @param nz 法向分量的 z 部分。 + * @param d 与原点的距离。 + */ + explicit Plane(float nx, float ny, float nz, float d); + + Plane() : ShapeBase(ShapeEnum::SHAPE_PLANE) {} + // Plane(const Plane& other) = default; + // Plane(Plane&& other) = default; + // Plane& operator=(const Plane& other) = default; + // Plane& operator=(Plane&& other) = default; + // ~Plane() = default; + + /** + * @en + * transform this plane. + * @zh + * 变换一个平面。 + * @param mat + */ + void transform(const Mat4 &mat); + + /** + * @en + * The normal of the plane. + * @zh + * 法线向量。 + */ + Vec3 n{0.0F, 1.0F, 0.0F}; + + /** + * @en + * The distance from the origin to the plane. + * @zh + * 原点到平面的距离。 + */ + float d{0.0F}; + + void define(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2); + void define(const Vec3 &normal, const Vec3 &point); + float distance(const Vec3 &point) const; + Plane clone() const; +}; + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Ray.cpp b/cocos/core/geometry/Ray.cpp new file mode 100644 index 0000000..086d2c4 --- /dev/null +++ b/cocos/core/geometry/Ray.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/geometry/Ray.h" +#include "base/memory/Memory.h" + +namespace cc { +namespace geometry { +Ray *Ray::create(float ox, float oy, float oz, float dx, float dy, float dz) { + return ccnew Ray{ox, oy, oz, dx, dy, dz}; +} + +Ray *Ray::clone(const Ray &a) { + return ccnew Ray{ + a.o.x, a.o.y, a.o.z, + a.d.x, a.d.y, a.d.z}; +} + +Ray *Ray::copy(Ray *out, const Ray &a) { + out->o = a.o; + out->d = a.d; + return out; +} + +Ray *Ray::fromPoints(Ray *out, const Vec3 &origin, const Vec3 &target) { + out->o = origin; + out->d = (target - origin).getNormalized(); + return out; +} + +Ray *Ray::set(Ray *out, float ox, float oy, + float oz, + float dx, + float dy, + float dz) { + out->o.x = ox; + out->o.y = oy; + out->o.z = oz; + out->d.x = dx; + out->d.y = dy; + out->d.z = dz; + return out; +} + +Ray::Ray(float ox, float oy, float oz, + float dx, float dy, float dz) : ShapeBase(ShapeEnum::SHAPE_RAY) { + o = {ox, oy, oz}; + d = {dx, dy, dz}; +} + +void Ray::computeHit(Vec3 *out, float distance) const { + *out = o + d.getNormalized() * distance; +} +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Ray.h b/cocos/core/geometry/Ray.h new file mode 100644 index 0000000..ce5ee67 --- /dev/null +++ b/cocos/core/geometry/Ray.h @@ -0,0 +1,160 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/geometry/Enums.h" +#include "math/Vec3.h" + +namespace cc { +namespace geometry { + +/** + * @en + * Basic Geometry: ray. + * @zh + * 基础几何 射线。 + */ + +class Ray final : public ShapeBase { +public: + /** + * @en + * create a new ray + * @zh + * 创建一条射线。 + * @param {number} ox 起点的 x 部分。 + * @param {number} oy 起点的 y 部分。 + * @param {number} oz 起点的 z 部分。 + * @param {number} dx 方向的 x 部分。 + * @param {number} dy 方向的 y 部分。 + * @param {number} dz 方向的 z 部分。 + * @return {Ray} 射线。 + */ + static Ray *create(float ox = 0, float oy = 0, float oz = 0, float dx = 0, float dy = 0, float dz = 1); + + /** + * @en + * Creates a new ray initialized with values from an existing ray + * @zh + * 从一条射线克隆出一条新的射线。 + * @param {Ray} a 克隆的目标。 + * @return {Ray} 克隆出的新对象。 + */ + static Ray *clone(const Ray &a); + /** + * @en + * Copy the values from one ray to another + * @zh + * 将从一个 ray 的值复制到另一个 ray。 + * @param {Ray} out 接受操作的 ray。 + * @param {Ray} a 被复制的 ray。 + * @return {Ray} out 接受操作的 ray。 + */ + static Ray *copy(Ray *out, const Ray &a); + + /** + * @en + * create a ray from two points + * @zh + * 用两个点创建一条射线。 + * @param {Ray} out 接受操作的射线。 + * @param {Vec3} origin 射线的起点。 + * @param {Vec3} target 射线上的一点。 + * @return {Ray} out 接受操作的射线。 + */ + static Ray *fromPoints(Ray *out, const Vec3 &origin, const Vec3 &target); + + /** + * @en + * Set the components of a ray to the given values + * @zh + * 将给定射线的属性设置为给定的值。 + * @param {Ray} out 接受操作的射线。 + * @param {number} ox 起点的 x 部分。 + * @param {number} oy 起点的 y 部分。 + * @param {number} oz 起点的 z 部分。 + * @param {number} dx 方向的 x 部分。 + * @param {number} dy 方向的 y 部分。 + * @param {number} dz 方向的 z 部分。 + * @return {Ray} out 接受操作的射线。 + */ + + static Ray *set(Ray *out, float ox, float oy, + float oz, + float dx, + float dy, + float dz); + + /** + * @en + * The origin of the ray. + * @zh + * 起点。 + */ + Vec3 o; + + /** + * @en + * The direction of the ray. + * @zh + * 方向。 + */ + Vec3 d; + + /** + * @en + * Construct a ray; + * @zh + * 构造一条射线。 + * @param {number} ox 起点的 x 部分。 + * @param {number} oy 起点的 y 部分。 + * @param {number} oz 起点的 z 部分。 + * @param {number} dx 方向的 x 部分。 + * @param {number} dy 方向的 y 部分。 + * @param {number} dz 方向的 z 部分。 + */ + explicit Ray(float ox = 0, float oy = 0, float oz = 0, + float dx = 0, float dy = 0, float dz = -1); + + Ray(const Ray &) = default; + Ray &operator=(const Ray &) = default; + Ray &operator=(Ray &&) = default; + Ray(Ray &&) = default; + ~Ray() override = default; + + /** + * @en + * Compute a point with the distance between the origin. + * @zh + * 根据给定距离计算出射线上的一点。 + * @param out 射线上的另一点。 + * @param distance 给定距离。 + */ + + void computeHit(Vec3 *out, float distance) const; +}; + +} // namespace geometry +} // namespace cc \ No newline at end of file diff --git a/cocos/core/geometry/Spec.cpp b/cocos/core/geometry/Spec.cpp new file mode 100644 index 0000000..d3f2509 --- /dev/null +++ b/cocos/core/geometry/Spec.cpp @@ -0,0 +1,23 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ diff --git a/cocos/core/geometry/Spec.h b/cocos/core/geometry/Spec.h new file mode 100644 index 0000000..eca7f81 --- /dev/null +++ b/cocos/core/geometry/Spec.h @@ -0,0 +1,174 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include + +#include "base/std/container/vector.h" +#include "base/std/optional.h" + +namespace cc { +namespace geometry { + +/** + * @en + * The raycast mode. + * @zh + * 射线检测模式。 + */ +enum class ERaycastMode { + + /** + * @en + * Detect and record all data. + * @zh + * 检测并记录所有的数据。 + */ + ALL, + + /** + * @en + * Detect all, but record only the most recent data. + * @zh + * 检测所有,但只记录最近的数据。 + */ + CLOSEST, + + /** + * @en + * Once the test is successful, the test is stopped and the data is recorded only once. + * @zh + * 一旦检测成功就停止检测,只会记录一次数据。 + */ + ANY, +}; + +/** + * @en + * The storage structure of the raycast results. + * @zh + * 射线检测结果的存储结构。 + */ +struct IRaySubMeshResult { + /** + * @en + * The distance between the hit point and the ray. + * @zh + * 击中点和射线的距离。 + */ + float distance{0.0F}; + + /** + * @en + * The index of the triangle vertex 0。 + * @zh + * 三角形顶点0的索引。 + */ + uint32_t vertexIndex0{0}; + + /** + * @en + * The index of the triangle vertex 1。 + * @zh + * 三角形顶点1的索引 + */ + uint32_t vertexIndex1{0}; + + /** + * @en + * The index of the triangle vertex 2。 + * @zh + * 三角形顶点2的索引 + */ + uint32_t vertexIndex2{0}; +}; + +/** + * @en + * The optional param structure of the `raySubMesh`. + * @zh + * `raySubMesh`的可选参数结构。 + */ +struct IRaySubMeshOptions { + /** + * @en + * The raycast mode,`ANY` by default. + * @zh + * 射线检测模式:[0, 1, 2]=>[`ALL`, `CLOSEST`, `ANY`] + */ + ERaycastMode mode; + + /** + * @en + * The maximum distance of the raycast, `Infinity` by default. + * @zh + * 射线检测的最大距离,默认为`Infinity`。 + */ + float distance = FLT_MAX; + + /** + * @en + * An array used to store the results of a ray detection. + * @zh + * 用于存储射线检测结果的数组。 + */ + ccstd::optional> result{}; + + /** + * @en + * Whether to detect the double-sided or not,`false` by default. + * @zh + * 是否检测双面,默认为`false`。 + */ + bool doubleSided{false}; +}; + +/** + * @en + * The optional param structure of the `rayMesh`. + * @zh + * `rayMesh`的可选参数结构。 + */ +struct IRayMeshOptions : public IRaySubMeshOptions { + /** + * @en + * The index of the sub mesh. + * @zh + * 子网格的索引。 + */ + ccstd::optional> subIndices{}; +}; + +/** + * @en + * The optional param structure of the `rayModel`. + * @zh + * `rayModel`的可选参数结构。 + */ +using IRayModelOptions = IRayMeshOptions; + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Sphere.cpp b/cocos/core/geometry/Sphere.cpp new file mode 100644 index 0000000..f20d29a --- /dev/null +++ b/cocos/core/geometry/Sphere.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/geometry/Sphere.h" +#include +#include "core/geometry/AABB.h" +#include "math/Vec3.h" + +namespace cc { +namespace geometry { + +Sphere *Sphere::create(float cx, float cy, float cz, float radius) { + return ccnew Sphere(cx, cy, cz, radius); +} + +Sphere *Sphere::clone(const Sphere &p) { + return ccnew Sphere(p._center.x, p._center.y, p._center.z, p._radius); +} + +Sphere *Sphere::copy(Sphere *out, const Sphere &p) { + out->_center = p._center; + out->_radius = p._radius; + return out; +} + +Sphere *Sphere::fromPoints(Sphere *out, const Vec3 &minPos, const Vec3 &maxPos) { + out->_center = 0.5F * (minPos + maxPos); + out->_radius = 0.5F * (maxPos - minPos).length(); + return out; +} + +Sphere *Sphere::set(Sphere *out, float cx, float cy, float cz, float r) { + out->_center = {cx, cy, cz}; + out->_radius = r; + return out; +} + +Sphere *Sphere::mergePoint(Sphere *out, const Sphere &s, const Vec3 &point) { + // if sphere.radius Less than 0, + // Set this point as anchor, + // And set radius to 0. + if (s._radius < 0.0) { + out->_center = point; + out->_radius = 0.0F; + return out; + } + + auto offset = point - s._center; + auto dist = offset.length(); + + if (dist > s._radius) { + auto half = (dist - s._radius) * 0.5F; + out->_radius += half; + offset.scale(half / dist); + out->_center = out->_center + offset; + } + + return out; +} + +void Sphere::merge(const ccstd::vector &points) { + if (points.empty()) return; + _radius = -1.0F; + for (const auto &p : points) { + merge(p); + } +} + +Sphere *Sphere::mergeAABB(Sphere *out, const Sphere &s, const AABB &a) { + Vec3 aabbMin; + Vec3 aabbMax; + a.getBoundary(&aabbMin, &aabbMax); + Sphere::mergePoint(out, s, aabbMin); + Sphere::mergePoint(out, s, aabbMax); + return out; +} + +Sphere::Sphere(float cx, float cy, float cz, float radius) : ShapeBase(ShapeEnum::SHAPE_SPHERE) { + _center = {cx, cy, cz}; + _radius = radius; +} + +void Sphere::getBoundary(Vec3 *minPos, Vec3 *maxPos) const { + Vec3 half = {_radius, _radius, _radius}; + *minPos = _center - half; + *maxPos = _center + half; +} + +void Sphere::transform(const Mat4 &m, + const Vec3 & /*pos*/, + const Quaternion & /*rot*/, + const Vec3 &scale, + Sphere *out) const { + Vec3::transformMat4(_center, m, &out->_center); + out->_radius = _radius * mathutils::maxComponent(scale); +} + +int Sphere::intersect(const Plane &plane) const { + const float dot = plane.n.dot(_center); + const float r = _radius * plane.n.length(); + if (dot + r < plane.d) { + return -1; // Sphere is on the back of the plane + } + + if (dot - r > plane.d) { + return 0; // Sphere is on the front of the plane + } + + return 1; // intersect +} + +bool Sphere::intersect(const Frustum &frustum) const { + const auto &planes = frustum.planes; + const auto *self = this; + return std::all_of(planes.begin(), + planes.end(), + // frustum plane normal points to the inside + [self](const Plane *plane) { return self->intersect(*plane) != -1; }); +} + +void Sphere::mergePoint(const Vec3 &point) { + if (_radius < 0.0F) { + _center = point; + _radius = 0.0F; + return; + } + + auto offset = point - _center; + auto distance = offset.length(); + + if (distance > _radius) { + auto half = (distance - _radius) * 0.5F; + _radius += half; + offset.scale(half / distance); + _center += offset; + } +} + +void Sphere::define(const AABB &aabb) { + cc::Vec3 minPos; + cc::Vec3 maxPos; + aabb.getBoundary(&minPos, &maxPos); + + // Initialize sphere + _center.set(minPos); + _radius = 0.0F; + + // Calculate sphere + const cc::Vec3 offset = maxPos - _center; + const float dist = offset.length(); + + const float half = dist * 0.5F; + _radius += dist * 0.5F; + _center += (half / dist) * offset; +} + +void Sphere::mergeAABB(const AABB *aabb) { + cc::Vec3 minPos; + cc::Vec3 maxPos; + aabb->getBoundary(&minPos, &maxPos); + mergePoint(minPos); + mergePoint(maxPos); +} + +int Sphere::spherePlane(const Plane &plane) const { + return intersect(plane); +} + +bool Sphere::sphereFrustum(const Frustum &frustum) const { + return intersect(frustum); +} + +void Sphere::mergeFrustum(const Frustum &frustum) { + const ccstd::array &vertices = frustum.vertices; + for (size_t i = 0; i < vertices.max_size(); ++i) { + merge(vertices[i]); + } +} + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Sphere.h b/cocos/core/geometry/Sphere.h new file mode 100644 index 0000000..ed9d8a4 --- /dev/null +++ b/cocos/core/geometry/Sphere.h @@ -0,0 +1,201 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/geometry/Enums.h" +#include "core/geometry/Frustum.h" +#include "math/Utils.h" +#include "math/Vec3.h" + +namespace cc { +namespace geometry { + +class AABB; + +class Sphere final : public ShapeBase { +public: + /** + * @en + * create a new sphere + * @zh + * 创建一个新的 sphere 实例。 + * @param cx 形状的相对于原点的 X 坐标。 + * @param cy 形状的相对于原点的 Y 坐标。 + * @param cz 形状的相对于原点的 Z 坐标。 + * @param r 球体的半径 + * @return {Sphere} 返回一个 sphere。 + */ + static Sphere *create(float cx, float cy, float cz, float radius); + + /** + * @en + * clone a new sphere + * @zh + * 克隆一个新的 sphere 实例。 + * @param {Sphere} p 克隆的目标。 + * @return {Sphere} 克隆出的示例。 + */ + static Sphere *clone(const Sphere &p); + /** + * @en + * copy the values from one sphere to another + * @zh + * 将从一个 sphere 的值复制到另一个 sphere。 + * @param {Sphere} out 接受操作的 sphere。 + * @param {Sphere} a 被复制的 sphere。 + * @return {Sphere} out 接受操作的 sphere。 + */ + static Sphere *copy(Sphere *out, const Sphere &p); + + /** + * @en + * create a new bounding sphere from two corner points + * @zh + * 从两个点创建一个新的 sphere。 + * @param out - 接受操作的 sphere。 + * @param minPos - sphere 的最小点。 + * @param maxPos - sphere 的最大点。 + * @returns {Sphere} out 接受操作的 sphere。 + */ + static Sphere *fromPoints(Sphere *out, const Vec3 &minPos, const Vec3 &maxPos); + + /** + * @en + * Set the components of a sphere to the given values + * @zh + * 将球体的属性设置为给定的值。 + * @param {Sphere} out 接受操作的 sphere。 + * @param cx 形状的相对于原点的 X 坐标。 + * @param cy 形状的相对于原点的 Y 坐标。 + * @param cz 形状的相对于原点的 Z 坐标。 + * @param {number} r 半径。 + * @return {Sphere} out 接受操作的 sphere。 + * @function + */ + static Sphere *set(Sphere *out, float cx, float cy, float cz, float r); + /** + * @zh + * 球跟点合并 + */ + static Sphere *mergePoint(Sphere *out, const Sphere &s, const Vec3 &point); + + /** + * @zh + * 球跟立方体合并 + */ + static Sphere *mergeAABB(Sphere *out, const Sphere &s, const AABB &a); + explicit Sphere(float cx = 0, float cy = 0, float cz = 0, float radius = 1.0F); + + Sphere(const Sphere &) = default; + Sphere(Sphere &&) = delete; + ~Sphere() override = default; + Sphere &operator=(const Sphere &) = default; + Sphere &operator=(Sphere &&) = delete; + + inline float getRadius() const { return _radius; } + inline const Vec3 &getCenter() const { return _center; } + inline void setCenter(const Vec3 &val) { _center = val; } + inline void setRadius(float val) { _radius = val; } + + inline Sphere *clone() const { + return Sphere::clone(*this); + } + + inline Sphere *copy(Sphere *out) const { + return Sphere::copy(out, *this); + } + + void define(const AABB &aabb); + void mergeAABB(const AABB *aabb); + void mergePoint(const Vec3 &point); + void mergeFrustum(const Frustum &frustum); + inline void merge(const AABB *aabb) { mergeAABB(aabb); } + inline void merge(const Vec3 &point) { mergePoint(point); } + void merge(const ccstd::vector &points); + inline void merge(const Frustum &frustum) { mergeFrustum(frustum); } + bool intersect(const Frustum &frustum) const; + int intersect(const Plane &plane) const; + int spherePlane(const Plane &plane) const; + bool sphereFrustum(const Frustum &frustum) const; + + /** + * @en + * Get the bounding points of this shape + * @zh + * 获取此形状的边界点。 + * @param {Vec3} minPos 最小点。 + * @param {Vec3} maxPos 最大点。 + */ + void getBoundary(Vec3 *minPos, Vec3 *maxPos) const; + + /** + * @en + * Transform this shape + * @zh + * 将 out 根据这个 sphere 的数据进行变换。 + * @param m 变换的矩阵。 + * @param pos 变换的位置部分。 + * @param rot 变换的旋转部分。 + * @param scale 变换的缩放部分。 + * @param out 变换的目标。 + */ + void transform(const Mat4 &m, + const Vec3 & /*pos*/, + const Quaternion & /*rot*/, + const Vec3 &scale, + Sphere *out) const; + + /** + * @en + * Translate and rotate this sphere. + * @zh + * 将 out 根据这个 sphere 的数据进行变换。 + * @param m 变换的矩阵。 + * @param rot 变换的旋转部分。 + * @param out 变换的目标。 + */ + inline void translateAndRotate(const Mat4 &m, const Quaternion & /*rot*/, Sphere *out) const { + Vec3::transformMat4(_center, m, &out->_center); + } + + /** + * @en + * Scaling this sphere. + * @zh + * 将 out 根据这个 sphere 的数据进行缩放。 + * @param scale 缩放值。 + * @param out 缩放的目标。 + */ + inline void setScale(const Vec3 &scale, Sphere *out) const { + out->_radius = _radius * mathutils::maxComponent(scale); + } + + // private: // make public for js bindings + float _radius{-1.0}; + Vec3 _center; +}; + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Spline.cpp b/cocos/core/geometry/Spline.cpp new file mode 100644 index 0000000..2305c7b --- /dev/null +++ b/cocos/core/geometry/Spline.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/geometry/Spline.h" +#include +#include "base/Log.h" +#include "base/Macros.h" +#include "base/memory/Memory.h" +#include "math/Utils.h" + +namespace cc { +namespace geometry { + +Spline::Spline(SplineMode mode /*= SplineMode::CATMULL_ROM*/, ccstd::vector knots /*= {}*/) +: ShapeBase(ShapeEnum::SHAPE_SPLINE), _mode(mode), _knots(std::move(knots)) { +} + +Spline *Spline::create(SplineMode mode, const ccstd::vector &knots /*= {}*/) { + return ccnew Spline(mode, knots); +} + +Spline *Spline::clone(const Spline &s) { + return ccnew Spline(s._mode, s._knots); +} + +Spline *Spline::copy(Spline *out, const Spline &s) { + out->_mode = s._mode; + out->_knots = s._knots; + + return out; +} + +void Spline::setModeAndKnots(SplineMode mode, const ccstd::vector &knots) { + _mode = mode; + _knots = knots; +} + +void Spline::insertKnot(uint32_t index, const Vec3 &knot) { + if (index >= static_cast(_knots.size())) { + _knots.push_back(knot); + return; + } + + _knots.insert(_knots.begin() + index, knot); +} + +void Spline::removeKnot(uint32_t index) { + CC_ASSERT(index < static_cast(_knots.size())); + + _knots.erase(_knots.begin() + index); +} + +void Spline::setKnot(uint32_t index, const Vec3 &knot) { + CC_ASSERT(index < static_cast(_knots.size())); + + _knots[index] = knot; +} + +const Vec3 &Spline::getKnot(uint32_t index) const { + CC_ASSERT(index < static_cast(_knots.size())); + + return _knots[index]; +} +Vec3 &Spline::getKnot(uint32_t index) { + CC_ASSERT(index < static_cast(_knots.size())); + + return _knots[index]; +} + +Vec3 Spline::getPoint(float t, uint32_t index /*= SPLINE_WHOLE_INDEX*/) const { + t = mathutils::clamp(t, 0.0F, 1.0F); + + const auto segments = getSegments(); + if (segments == 0) { + return Vec3(0.0F, 0.0F, 0.0F); + } + + if (index == SPLINE_WHOLE_INDEX) { + const auto deltaT = 1.0F / static_cast(segments); + + index = static_cast(t / deltaT); + t = std::fmod(t, deltaT) / deltaT; + } + + if (index >= segments) { + return _knots.back(); + } + + switch (_mode) { + case SplineMode::LINEAR: + return calcLinear(_knots[index], _knots[index + 1], t); + case SplineMode::BEZIER: + return calcBezier(_knots[index * 4], _knots[index * 4 + 1], _knots[index * 4 + 2], _knots[index * 4 + 3], t); + case SplineMode::CATMULL_ROM: { + const auto v0 = index > 0 ? _knots[index - 1] : _knots[index]; + const auto v3 = index + 2 < static_cast(_knots.size()) ? _knots[index + 2] : _knots[index + 1]; + return calcCatmullRom(v0, _knots[index], _knots[index + 1], v3, t); + } + default: + return Vec3(0.0F, 0.0F, 0.0F); + } +} + +ccstd::vector Spline::getPoints(uint32_t num, uint32_t index /*= SPLINE_WHOLE_INDEX*/) const { + if (num == 0U) { + return {}; + } + + if (num == 1U) { + auto point = getPoint(0.0F, index); + return {point}; + } + + ccstd::vector points; + const float deltaT = 1.0F / (static_cast(num) - 1.0F); + + for (auto i = 0; i < num; i++) { + const auto t = static_cast(i) * deltaT; + const auto point = getPoint(t, index); + + points.push_back(point); + } + + return points; +} + +uint32_t Spline::getSegments() const { + const auto count = static_cast(_knots.size()); + switch (_mode) { + case SplineMode::LINEAR: + case SplineMode::CATMULL_ROM: + if (count < 2) { + CC_LOG_WARNING("Spline error: less than 2 knots."); + return 0; + } + + return count - 1; + case SplineMode::BEZIER: + if (count < 4 || count % 4 != 0) { + CC_LOG_WARNING("Spline error: less than 4 knots or not a multiple of 4."); + return 0; + } + + return count / 4; + default: + CC_ABORT(); + return 0; + } +} + +Vec3 Spline::calcLinear(const Vec3 &v0, const Vec3 &v1, float t) { + const auto result = v0 * (1.0F - t) + v1 * t; + + return result; +} + +Vec3 Spline::calcBezier(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2, const Vec3 &v3, float t) { + const auto s = 1.0F - t; + const auto result = v0 * s * s * s + + v1 * 3.0F * t * s * s + + v2 * 3.0F * t * t * s + + v3 * t * t * t; + + return result; +} + +Vec3 Spline::calcCatmullRom(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2, const Vec3 &v3, float t) { + const auto t2 = t * t; + const auto t3 = t2 * t; + const auto result = v0 * (-0.5F * t3 + t2 - 0.5F * t) + + v1 * (1.5F * t3 - 2.5F * t2 + 1.0F) + + v2 * (-1.5F * t3 + 2.0F * t2 + 0.5F * t) + + v3 * (0.5F * t3 - 0.5F * t2); + + return result; +} + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Spline.h b/cocos/core/geometry/Spline.h new file mode 100644 index 0000000..57a5e3b --- /dev/null +++ b/cocos/core/geometry/Spline.h @@ -0,0 +1,115 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/vector.h" +#include "core/geometry/Enums.h" +#include "math/Vec3.h" + +namespace cc { +namespace geometry { + +enum class SplineMode { + /** + * Broken line: + * Each knot is connected with a straight line from the beginning to the end to form a curve. At least two knots. + */ + LINEAR = 0, + + /** + * Piecewise Bezier curve: + * Every four knots form a curve. Total knots number must be a multiple of 4. + * Each curve passes only the first and fourth knots, and does not pass through the middle two control knots. + * + * If you need a whole continuous curve: + * (1) Suppose the four knots of the previous curve are A, B, C, D + * (2) The four knots of the next curve must be D, E, F, G + * (3) C and E need to be symmetrical about D + */ + BEZIER = 1, + + /** + * Catmull Rom curve: + * All knots(including start & end knots) form a whole continuous curve. At least two knots. + * The whole curve passes through all knots. + */ + CATMULL_ROM = 2 +}; + +constexpr uint32_t SPLINE_WHOLE_INDEX = 0xffffffff; + +/** + * @en + * Basic Geometry: Spline. + * @zh + * 基础几何 Spline。 + */ + +class Spline final : public ShapeBase { +public: + explicit Spline(SplineMode mode = SplineMode::CATMULL_ROM, ccstd::vector knots = {}); + + Spline(const Spline &) = default; + Spline(Spline &&) = default; + ~Spline() override = default; + Spline &operator=(const Spline &) = default; + Spline &operator=(Spline &&) = default; + + static Spline *create(SplineMode mode, const ccstd::vector &knots = {}); + static Spline *clone(const Spline &s); + static Spline *copy(Spline *out, const Spline &s); + + inline void setMode(SplineMode mode) { _mode = mode; } + inline SplineMode getMode() const { return _mode; } + inline void setKnots(const ccstd::vector &knots) { _knots = knots; } + inline const ccstd::vector &getKnots() const { return _knots; } + inline void clearKnots() { _knots.clear(); } + inline uint32_t getKnotCount() const { return static_cast(_knots.size()); } + inline void addKnot(const Vec3 &knot) { _knots.push_back(knot); } + void setModeAndKnots(SplineMode mode, const ccstd::vector &knots); + void insertKnot(uint32_t index, const Vec3 &knot); + void removeKnot(uint32_t index); + void setKnot(uint32_t index, const Vec3 &knot); + const Vec3 &getKnot(uint32_t index) const; + Vec3 &getKnot(uint32_t index); + + // get a point at t with repect to the `index` segment of curve or the whole curve. + Vec3 getPoint(float t, uint32_t index = SPLINE_WHOLE_INDEX) const; + + // get num points from 0 to 1 uniformly with repect to the `index` segment of curve or the whole curve + ccstd::vector getPoints(uint32_t num, uint32_t index = SPLINE_WHOLE_INDEX) const; + +private: + uint32_t getSegments() const; + static Vec3 calcLinear(const Vec3 &v0, const Vec3 &v1, float t); + static Vec3 calcBezier(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2, const Vec3 &v3, float t); + static Vec3 calcCatmullRom(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2, const Vec3 &v3, float t); + + SplineMode _mode{SplineMode::CATMULL_ROM}; + ccstd::vector _knots; // control points of the curve. +}; + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Triangle.cpp b/cocos/core/geometry/Triangle.cpp new file mode 100644 index 0000000..07c174c --- /dev/null +++ b/cocos/core/geometry/Triangle.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/geometry/Triangle.h" +#include "base/memory/Memory.h" + +namespace cc { +namespace geometry { + +Triangle *Triangle::create(float ax, float ay, float az, + float bx, float by, float bz, + float cx, float cy, float cz) { + return ccnew Triangle(ax, ay, az, bx, by, bz, cx, cy, cz); +} + +Triangle *Triangle::clone(const Triangle &t) { + return ccnew Triangle( + t.a.x, t.a.y, t.a.z, + t.b.x, t.b.y, t.b.z, + t.c.x, t.c.y, t.c.z); +} + +Triangle *Triangle::copy(Triangle *out, const Triangle &t) { + out->a = t.a; + out->b = t.b; + out->c = t.c; + return out; +} + +Triangle *Triangle::fromPoints(Triangle *out, const Vec3 &a, + const Vec3 &b, + const Vec3 &c) { + out->a = a; + out->b = b; + out->c = c; + return out; +} + +Triangle *Triangle::set(Triangle *out, + float ax, float ay, float az, + float bx, float by, float bz, + float cx, float cy, float cz) { + out->a.x = ax; + out->a.y = ay; + out->a.z = az; + + out->b.x = bx; + out->b.y = by; + out->b.z = bz; + + out->c.x = cx; + out->c.y = cy; + out->c.z = cz; + + return out; +} + +Triangle::Triangle(float ax, float ay, float az, + float bx, float by, float bz, + float cx, float cy, float cz) : ShapeBase(ShapeEnum::SHAPE_TRIANGLE) { + a = {ax, ay, az}; + b = {bx, by, bz}; + c = {cx, cy, cz}; +} +} // namespace geometry +} // namespace cc diff --git a/cocos/core/geometry/Triangle.h b/cocos/core/geometry/Triangle.h new file mode 100644 index 0000000..8ea3117 --- /dev/null +++ b/cocos/core/geometry/Triangle.h @@ -0,0 +1,175 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "core/geometry/Enums.h" + +#include "math/Vec3.h" + +namespace cc { +namespace geometry { + +/** + * @en + * Basic Geometry: Triangle. + * @zh + * 基础几何 三角形。 + */ + +class Triangle final : public ShapeBase { +public: + /** + * @en + * create a new triangle + * @zh + * 创建一个新的 triangle。 + * @param {number} ax a 点的 x 部分。 + * @param {number} ay a 点的 y 部分。 + * @param {number} az a 点的 z 部分。 + * @param {number} bx b 点的 x 部分。 + * @param {number} by b 点的 y 部分。 + * @param {number} bz b 点的 z 部分。 + * @param {number} cx c 点的 x 部分。 + * @param {number} cy c 点的 y 部分。 + * @param {number} cz c 点的 z 部分。 + * @return {Triangle} 一个新的 triangle。 + */ + static Triangle *create(float ax = 1, float ay = 0, float az = 0, + float bx = 0, float by = 0, float bz = 0, + float cx = 0, float cy = 0, float cz = 1); + + /** + * @en + * clone a new triangle + * @zh + * 克隆一个新的 triangle。 + * @param {Triangle} t 克隆的目标。 + * @return {Triangle} 克隆出的新对象。 + */ + + static Triangle *clone(const Triangle &t); + + /** + * @en + * copy the values from one triangle to another + * @zh + * 将一个 triangle 的值复制到另一个 triangle。 + * @param {Triangle} out 接受操作的 triangle。 + * @param {Triangle} t 被复制的 triangle。 + * @return {Triangle} out 接受操作的 triangle。 + */ + + static Triangle *copy(Triangle *out, const Triangle &t); + + /** + * @en + * Create a triangle from three points + * @zh + * 用三个点创建一个 triangle。 + * @param {Triangle} out 接受操作的 triangle。 + * @param {Vec3} a a 点。 + * @param {Vec3} b b 点。 + * @param {Vec3} c c 点。 + * @return {Triangle} out 接受操作的 triangle。 + */ + static Triangle *fromPoints(Triangle *out, const Vec3 &a, + const Vec3 &b, + const Vec3 &c); + + /** + * @en + * Set the components of a triangle to the given values + * @zh + * 将给定三角形的属性设置为给定值。 + * @param {Triangle} out 给定的三角形。 + * @param {number} ax a 点的 x 部分。 + * @param {number} ay a 点的 y 部分。 + * @param {number} az a 点的 z 部分。 + * @param {number} bx b 点的 x 部分。 + * @param {number} by b 点的 y 部分。 + * @param {number} bz b 点的 z 部分。 + * @param {number} cx c 点的 x 部分。 + * @param {number} cy c 点的 y 部分。 + * @param {number} cz c 点的 z 部分。 + * @return {Triangle} + * @function + */ + static Triangle *set(Triangle *out, + float ax, float ay, float az, + float bx, float by, float bz, + float cx, float cy, float cz); + + /** + * @en + * Point a. + * @zh + * 点 a。 + */ + + Vec3 a; + + /** + * @en + * Point b. + * @zh + * 点 b。 + */ + Vec3 b; + + /** + * @en + * Point c. + * @zh + * 点 c。 + */ + Vec3 c; + /** + * @en + * Construct a triangle. + * @zh + * 构造一个三角形。 + * @param {number} ax a 点的 x 部分。 + * @param {number} ay a 点的 y 部分。 + * @param {number} az a 点的 z 部分。 + * @param {number} bx b 点的 x 部分。 + * @param {number} by b 点的 y 部分。 + * @param {number} bz b 点的 z 部分。 + * @param {number} cx c 点的 x 部分。 + * @param {number} cy c 点的 y 部分。 + * @param {number} cz c 点的 z 部分。 + */ + explicit Triangle(float ax = 0, float ay = 0, float az = 0, + float bx = 1, float by = 0, float bz = 0, + float cx = 0, float cy = 1, float cz = 0); + + Triangle(const Triangle &) = default; + Triangle(Triangle &&) = default; + ~Triangle() override = default; + Triangle &operator=(const Triangle &) = default; + Triangle &operator=(Triangle &&) = default; + +}; // namespace geometry + +} // namespace geometry +} // namespace cc diff --git a/cocos/core/memop/CachedArray.h b/cocos/core/memop/CachedArray.h new file mode 100644 index 0000000..56f667d --- /dev/null +++ b/cocos/core/memop/CachedArray.h @@ -0,0 +1,166 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/Object.h" +#include "base/TypeDef.h" +#include "base/memory/Memory.h" + +namespace cc { + +template +class CachedArray final { +public: + explicit CachedArray(uint size = 1U) { + _size = 0; + _capacity = std::max(size, 1U); + _array = ccnew T[_capacity]; + } + + // The rule of five applies here + ~CachedArray() { + CC_SAFE_DELETE_ARRAY(_array); + } + + CachedArray(const CachedArray &other) + : _size(other._size), _capacity(other._capacity), _array(ccnew T[other._capacity]) { + memcpy(_array, other._array, _size * sizeof(T)); + } + + CachedArray &operator=(const CachedArray &other) { + if (this != &other) { + delete[] _array; + _size = other._size; + _capacity = other._capacity; + _array = ccnew T[_capacity]; + memcpy(_array, other._array, _size * sizeof(T)); + } + return *this; + } + + CachedArray(CachedArray &&other) noexcept : _size(other._size), _capacity(other._capacity), _array(other._array) { + other._size = 0; + other._capacity = 0; + other._array = nullptr; + } + + CachedArray &operator=(CachedArray &&other) noexcept { + if (this != &other) { + delete[] _array; + _size = other._size; + _capacity = other._capacity; + _array = other._array; + other._size = 0; + other._capacity = 0; + other._array = nullptr; + } + return *this; + } + + // Subscription operators + T &operator[](uint index) { + return _array[index]; + } + + const T &operator[](uint index) const { + return _array[index]; + } + + inline void clear() { _size = 0; } + inline uint size() const { return _size; } + inline T pop() { return _array[--_size]; } + + void reserve(uint size) { + if (size > _capacity) { + T *temp = _array; + _array = ccnew T[size]; + memcpy(_array, temp, _capacity * sizeof(T)); + _capacity = size; + delete[] temp; + } + } + + void push(T item) { + if (_size >= _capacity) { + T *temp = _array; + _array = ccnew T[_capacity * 2]; + memcpy(_array, temp, _capacity * sizeof(T)); + _capacity *= 2; + delete[] temp; + } + _array[_size++] = item; + } + + void concat(const CachedArray &array) { + if (_size + array._size > _capacity) { + T *temp = _array; + uint size = std::max(_capacity * 2, _size + array._size); + _array = ccnew T[size]; + memcpy(_array, temp, _size * sizeof(T)); + _capacity = size; + delete[] temp; + } + memcpy(_array + _size, array._array, array._size * sizeof(T)); + _size += array._size; + } + + void concat(T *array, uint count) { + if (_size + count > _capacity) { + T *temp = _array; + uint size = std::max(_capacity * 2, _size + count); + _array = ccnew T[size]; + memcpy(_array, temp, _size * sizeof(T)); + _capacity = size; + delete[] temp; + } + memcpy(_array + _size, array, count * sizeof(T)); + _size += count; + } + + void fastRemove(uint idx) { + if (idx >= _size) { + return; + } + _array[idx] = _array[--_size]; + } + + uint indexOf(T item) { + for (uint i = 0; i < _size; ++i) { + if (_array[i] == item) { + return i; + } + } + return UINT_MAX; + } + +private: + uint _size = 0; + uint _capacity = 0; + T *_array = nullptr; +}; + +} // namespace cc diff --git a/cocos/core/memop/Pool.h b/cocos/core/memop/Pool.h new file mode 100644 index 0000000..e836d74 --- /dev/null +++ b/cocos/core/memop/Pool.h @@ -0,0 +1,133 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include "base/TypeDef.h" +#include "base/std/container/vector.h" + +namespace cc { + +namespace memop { + +template +class Pool final { +public: + using CtorFunc = std::function; + using DtorFunc = std::function; + /** + * @en Constructor with the allocator of elements and initial pool size + * @zh 使用元素的构造器和初始大小的构造函数 + * @param ctor The allocator of elements in pool, it's invoked directly without `new` + * @param elementsPerBatch Initial pool size, this size will also be the incremental size when the pool is overloaded + */ + Pool(const CtorFunc &ctor, const DtorFunc &dtor, uint32_t elementsPerBatch) + : _ctor(ctor), + _dtor(dtor), + _elementsPerBatch(std::max(elementsPerBatch, static_cast(1))) { + CC_ASSERT(_ctor); + CC_ASSERT(_dtor); + _nextAvail = static_cast(_elementsPerBatch - 1); + + for (uint32_t i = 0; i < _elementsPerBatch; ++i) { + _freepool.push_back(ctor()); + } + } + + Pool(const Pool &) = delete; + Pool(Pool &&) = delete; + ~Pool() = default; + Pool &operator=(const Pool &) = delete; + Pool &operator=(Pool &&) = delete; + + /** + * @en Take an object out of the object pool. + * @zh 从对象池中取出一个对象。 + * @return An object ready for use. This function always return an object. + */ + T *alloc() { + if (_nextAvail < 0) { + _freepool.resize(_elementsPerBatch); + for (uint32_t i = 0; i < _elementsPerBatch; ++i) { + _freepool[i] = _ctor(); + } + _nextAvail = static_cast(_elementsPerBatch - 1); + } + + return _freepool[_nextAvail--]; + } + + /** + * @en Put an object back into the object pool. + * @zh 将一个对象放回对象池中。 + * @param obj The object to be put back into the pool + */ + void free(T *obj) { + ++_nextAvail; + if (_nextAvail >= _freepool.size()) { + _freepool.resize(_freepool.size() + 1); + } + _freepool[_nextAvail] = obj; + } + + /** + * @en Put multiple objects back into the object pool. + * @zh 将一组对象放回对象池中。 + * @param objs An array of objects to be put back into the pool + */ + void freeArray(const ccstd::vector &objs) { + _freepool.reserve(_nextAvail + 1 + objs.size()); + _freepool.insert(_freepool.begin() + _nextAvail + 1, + objs.begin(), objs.end()); + _nextAvail += static_cast(objs.size()); + } + + /** + * @en Destroy all elements and clear the pool. + * @zh 释放对象池中所有资源并清空缓存池。 + * @param dtor The destructor function, it will be invoked for all elements in the pool + */ + void destroy() { + for (int i = 0; i <= _nextAvail; ++i) { + _dtor(_freepool[i]); + } + _nextAvail = -1; + _freepool.clear(); + _freepool.shrink_to_fit(); + } + +private: + CtorFunc _ctor{nullptr}; + DtorFunc _dtor{nullptr}; + uint32_t _elementsPerBatch{0}; + index_t _nextAvail{-1}; + ccstd::vector _freepool; +}; + +} // namespace memop + +} // namespace cc diff --git a/cocos/core/memop/RecyclePool.h b/cocos/core/memop/RecyclePool.h new file mode 100644 index 0000000..c134b99 --- /dev/null +++ b/cocos/core/memop/RecyclePool.h @@ -0,0 +1,105 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/std/container/vector.h" + +namespace cc { + +/** + * @en Recyclable object pool. It's designed to be entirely reused each time. + * There is no put and get method, each time you get the [[data]], you can use all elements as new. + * You shouldn't simultaneously use the same RecyclePool in more than two overlapped logic. + * Its size can be automatically incremented or manually resized. + * @zh 循环对象池。这种池子被设计为每次使用都完整复用。 + * 它没有回收和提取的函数,通过获取 [[data]] 可以获取池子中所有元素,全部都应该被当做新对象来使用。 + * 开发者不应该在相互交叉的不同逻辑中同时使用同一个循环对象池。 + * 池子尺寸可以在池子满时自动扩充,也可以手动调整。 + * @see [[Pool]] + */ +template +class RecyclePool final { +public: + /** + * @en Constructor with the allocator of elements and initial pool size, all elements will be pre-allocated. + * @zh 使用元素的构造器和初始大小的构造函数,所有元素都会被预创建。 + * @param fn The allocator of elements in pool, it's invoked directly without `new` + * @param size Initial pool size + */ + RecyclePool(const std::function &fn, uint32_t size); + + RecyclePool(const RecyclePool &) = delete; + RecyclePool(RecyclePool &&) = delete; + ~RecyclePool() = default; + RecyclePool &operator=(const RecyclePool &) = delete; + RecyclePool &operator=(RecyclePool &&) = delete; + + /** + * @en The length of the object pool. + * @zh 对象池大小。 + */ + inline uint32_t getLength() const { return _count; } + + /** + * @en The underlying array of all pool elements. + * @zh 实际对象池数组。 + */ + inline const ccstd::vector &getData() const { return _data; } + + /** + * @en Resets the object pool. Only changes the length to 0 + * @zh 清空对象池。目前仅仅会设置尺寸为 0 + */ + inline void reset() { _count = 0; } + + /** + * @en Resize the object poo, and fills with new created elements. + * @zh 设置对象池大小,并填充新的元素。 + * @param size The new size of the pool + */ + void resize(uint32_t size); + + /** + * @en Expand the object pool, the size will be increment to current size times two, and fills with new created elements. + * @zh 扩充对象池容量,会自动扩充尺寸到两倍,并填充新的元素。 + * @param idx + */ + void add(); + + /** + * @en Remove an element of the object pool. This will also decrease size of the pool + * @zh 移除对象池中的一个元素,同时会减小池子尺寸。 + * @param idx The index of the element to be removed + */ + void removeAt(uint32_t idx); + +private: + uint32_t _count{0}; + ccstd::vector _data; +}; + +} // namespace cc diff --git a/cocos/core/platform/Debug.cpp b/cocos/core/platform/Debug.cpp new file mode 100644 index 0000000..0482e9b --- /dev/null +++ b/cocos/core/platform/Debug.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http =//www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Debug.h" + +namespace cc { +namespace debug { +const ccstd::string CONSOLE_LOG = "log"; +const ccstd::string CONSOLE_WARN = "warn"; +const ccstd::string CONSOLE_ERROR = "error"; +const ccstd::string CONSOLE_ASSET = "assert"; + +const ccstd::string &getPrefixTag(DebugMode mode) { + switch (mode) { + case DebugMode::VERBOSE: + return CONSOLE_LOG; + case DebugMode::WARN: + return CONSOLE_WARN; + case DebugMode::ERROR_MODE: + return CONSOLE_ERROR; + default: + return CONSOLE_ASSET; + } +} + +LogLevel getLogLevel(DebugMode mode) { + switch (mode) { + case DebugMode::VERBOSE: + return LogLevel::LEVEL_DEBUG; + case DebugMode::WARN: + return LogLevel::WARN; + case DebugMode::ERROR_MODE: + return LogLevel::ERR; + default: + return LogLevel::FATAL; + } +} + +ccstd::string getTypedFormatter(DebugMode mode, uint32_t id) { + const ccstd::string &tag = getPrefixTag(mode); + ccstd::string msg; +#if CC_DEBUG > 0 + if (debugInfos.find(id) == debugInfos.end()) { + msg = "unknown id"; + } else { + msg = debugInfos[id]; + } +#else + char szTmp[1024] = {0}; + snprintf(szTmp, sizeof(szTmp), "%s %d, please go to %s#%d to see details.", tag.c_str(), id, ERROR_MAP_URL.c_str(), id); + msg = szTmp; +#endif + + return msg; +} + +void printLog(DebugMode mode, const ccstd::string &fmt, ccstd::any *arr, int paramsLength) { + ccstd::string msg = fmt; + const ccstd::string &prefix = getPrefixTag(mode); + LogLevel logLevel = getLogLevel(mode); + + size_t pos; + for (int i = 1; i <= paramsLength; i++) { + pos = msg.find('%'); + bool needToReplace = false; + if (pos != ccstd::string::npos && pos != (msg.length() - 1) && (msg[pos + 1] == 'd' || msg[pos + 1] == 's' || msg[pos + 1] == 'f')) { + needToReplace = true; + } + const auto &elemTypeId = arr[i].type(); + if (elemTypeId == typeid(const ccstd::string)) { + const auto s = ccstd::any_cast(arr[i]); + if (needToReplace) { + msg.replace(pos, 2, s); + } else { + msg += " " + s; + } + } else if (elemTypeId == typeid(ccstd::string)) { + auto s = ccstd::any_cast(arr[i]); + if (needToReplace) { + msg.replace(pos, 2, s); + } else { + msg += " " + s; + } + } else if (elemTypeId == typeid(int)) { + int value = ccstd::any_cast(arr[i]); + if (needToReplace) { + msg.replace(pos, 2, std::to_string(value)); + } else { + msg += " " + std::to_string(value); + } + } else if (elemTypeId == typeid(unsigned int)) { + auto value = ccstd::any_cast(arr[i]); + if (needToReplace) { + msg.replace(pos, 2, std::to_string(value)); + } else { + msg += " " + std::to_string(value); + } + + } else if (elemTypeId == typeid(float)) { + auto value = ccstd::any_cast(arr[i]); + if (needToReplace) { + msg.replace(pos, 2, std::to_string(value)); + } else { + msg += " " + std::to_string(value); + } + } else if (elemTypeId == typeid(const char *)) { + ccstd::string s = ccstd::any_cast(arr[i]); + if (needToReplace) { + msg.replace(pos, 2, s); + } else { + msg += " " + s; + } + } else { + CC_LOG_ERROR("DebugInfos: unsupport params data type: '%s'", elemTypeId.name()); + CC_LOG_ERROR(" fmt: \"%s\", parameter index: %d", fmt.c_str(), i); + return; + } + } + + cc::Log::logMessage(cc::LogType::KERNEL, logLevel, "%s %s", prefix.c_str(), msg.c_str()); +} + +} // namespace debug +} // namespace cc diff --git a/cocos/core/platform/Debug.h b/cocos/core/platform/Debug.h new file mode 100644 index 0000000..afaa460 --- /dev/null +++ b/cocos/core/platform/Debug.h @@ -0,0 +1,226 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http =//www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Log.h" +#include "base/std/any.h" +#include "base/std/container/string.h" +#include "core/builtin/DebugInfos.h" + +namespace cc { +namespace debug { + +const ccstd::string ERROR_MAP_URL{"https://github.com/cocos-creator/engine/blob/3d/EngineErrorMap.md"}; + +enum class DebugMode { + + /** + * @en The debug mode none. + * @zh 禁止模式,禁止显示任何日志消息。 + */ + NONE = 0, + + /** + * @en The debug mode none. + * @zh 调试模式,显示所有日志消息。 + */ + VERBOSE = 1, + + /** + * @en Information mode, which display messages with level higher than "information" level. + * @zh 信息模式,显示“信息”级别以上的日志消息。 + */ + INFO = 2, + + /** + * @en Information mode, which display messages with level higher than "warning" level. + * @zh 警告模式,显示“警告”级别以上的日志消息。 + */ + WARN = 3, + + /** + * @en Information mode, which display only messages with "error" level. + * @zh 错误模式,仅显示“错误”级别的日志消息。 + */ + ERROR_MODE = 4, // ERROR has been defined by MACRO + + /** + * @en The debug mode info for web page. + * @zh 信息模式(仅 WEB 端有效),在画面上输出所有信息。 + */ + INFO_FOR_WEB_PAGE = 5, + + /** + * @en The debug mode warn for web page. + * @zh 警告模式(仅 WEB 端有效),在画面上输出 warn 级别以上的(包含 error)信息。 + */ + WARN_FOR_WEB_PAGE = 6, + + /** + * @en The debug mode error for web page. + * @zh 错误模式(仅 WEB 端有效),在画面上输出 error 信息。 + */ + ERROR_FOR_WEB_PAGE = 7, +}; + +/** + * @en Outputs a message to the Cocos Creator Console (editor) or Web Console (runtime). + * @zh 输出一条消息到 Cocos Creator 编辑器的 Console 或运行时 Web 端的 Console 中。 + * @param message - A JavaScript string containing zero or more substitution strings. + * @param optionalParams - JavaScript objects with which to replace substitution strings within msg. + * This gives you additional control over the format of the output. + */ + +template +void log(ccstd::any message, Args... optionalParams); + +/** + * @en + * Outputs a warning message to the Cocos Creator Console (editor) or Web Console (runtime). + * - In Cocos Creator, warning is yellow. + * - In Chrome, warning have a yellow warning icon with the message text. + * @zh + * 输出警告消息到 Cocos Creator 编辑器的 Console 或运行时 Web 端的 Console 中。
+ * - 在 Cocos Creator 中,警告信息显示是黄色的。
+ * - 在 Chrome 中,警告信息有着黄色的图标以及黄色的消息文本。
+ * @param message - A JavaScript string containing zero or more substitution strings. + * @param optionalParams - JavaScript objects with which to replace substitution strings within msg. + * This gives you additional control over the format of the output. + */ +template +void warn(ccstd::any message, Args... optionalParams); + +/** + * @en + * Outputs an error message to the Cocos Creator Console (editor) or Web Console (runtime).
+ * - In Cocos Creator, error is red.
+ * - In Chrome, error have a red icon along with red message text.
+ * @zh + * 输出错误消息到 Cocos Creator 编辑器的 Console 或运行时页面端的 Console 中。
+ * - 在 Cocos Creator 中,错误信息显示是红色的。
+ * - 在 Chrome 中,错误信息有红色的图标以及红色的消息文本。
+ * @param message - A JavaScript string containing zero or more substitution strings. + * @param optionalParams - JavaScript objects with which to replace substitution strings within msg. + * This gives you additional control over the format of the output. + */ +template +void error(ccstd::any message, Args... optionalParams); + +/** + * @en + * Assert the condition and output error messages if the condition is not true. + * @zh + * 对检查测试条件进行检查,如果条件不为 true 则输出错误消息 + * @param value - The condition to check on + * @param message - A JavaScript string containing zero or more substitution strings. + * @param optionalParams - JavaScript objects with which to replace substitution strings within msg. + * This gives you additional control over the format of the output. + */ + +template +void _assert(ccstd::any value, ccstd::string message, Args... optionalParams); // NOLINT //assert is a reserved word + +/** + * @en Outputs a message at the "debug" log level. + * @zh 输出一条“调试”日志等级的消息。 + */ +template +void debug(Args... data); + +void resetDebugSetting(DebugMode mode); + +/** + * @en Gets error message with the error id and possible parameters. + * @zh 通过 error id 和必要的参数来获取错误信息。 + */ +template +ccstd::string getError(uint32_t errorId, Args... param); + +/** + * @en Returns whether or not to display the FPS and debug information. + * @zh 是否显示 FPS 信息和部分调试信息。 + */ +bool isDisplayStats(); + +/** + * @en Sets whether display the FPS and debug informations on the bottom-left corner. + * @zh 设置是否在左下角显示 FPS 和部分调试。 + */ + +void setDisplayStats(bool displayStats); + +ccstd::string getTypedFormatter(DebugMode mode, uint32_t id); + +const ccstd::string &getPrefixTag(DebugMode mode); + +LogLevel getLogLevel(DebugMode mode); + +template +T unpackParams(T value) { + return value; +} + +void printLog(DebugMode mode, const ccstd::string &fmt, ccstd::any *arr, int paramsLength); + +template +void logID(uint32_t id, Args... optionalParams) { + ccstd::string msg = getTypedFormatter(DebugMode::VERBOSE, id); + int size = sizeof...(optionalParams); + ccstd::any arr[] = {0, unpackParams(optionalParams)...}; + printLog(DebugMode::VERBOSE, msg, arr, size); +} + +template +void warnID(uint32_t id, Args... optionalParams) { + ccstd::string msg = getTypedFormatter(DebugMode::WARN, id); + int size = sizeof...(optionalParams); + ccstd::any arr[] = {0, unpackParams(optionalParams)...}; + printLog(DebugMode::WARN, msg, arr, size); +} + +template +void errorID(uint32_t id, Args... optionalParams) { + ccstd::string msg = getTypedFormatter(DebugMode::ERROR_MODE, id); + int size = sizeof...(optionalParams); + ccstd::any arr[] = {0, unpackParams(optionalParams)...}; + printLog(DebugMode::ERROR_MODE, msg, arr, size); +} + +template +void assertID(bool condition, uint32_t id, Args... optionalParams) { + if (condition) { + return; + } + ccstd::string msg = getTypedFormatter(DebugMode::INFO, id); + int size = sizeof...(optionalParams); + ccstd::any arr[] = {0, unpackParams(optionalParams)...}; + printLog(DebugMode::INFO, msg, arr, size); + CC_ABORT(); +} + +void _throw(); // NOLINT // throw is a reserved word +} // namespace debug + +} // namespace cc diff --git a/cocos/core/platform/Macro.cpp b/cocos/core/platform/Macro.cpp new file mode 100644 index 0000000..b345947 --- /dev/null +++ b/cocos/core/platform/Macro.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http =//www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/platform/Macro.h" + +namespace cc { + +namespace macro { +const ccstd::vector SUPPORT_TEXTURE_FORMATS = { + ".astc", ".pkm", ".pvr", ".webp", ".jpg", ".jpeg", ".bmp", ".png"}; + +} +} // namespace cc \ No newline at end of file diff --git a/cocos/core/platform/Macro.h b/cocos/core/platform/Macro.h new file mode 100644 index 0000000..84ff741 --- /dev/null +++ b/cocos/core/platform/Macro.h @@ -0,0 +1,898 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/string.h" +#include "base/std/container/vector.h" +#include "math/Math.h" + +namespace cc { +namespace macro { +/** + * @en + * The image format supported by the engine defaults, and the supported formats may differ in different build platforms and device types. + * Currently all platform and device support ['.webp', '.jpg', '.jpeg', '.bmp', '.png'], ios mobile platform + * @zh + * 引擎默认支持的图片格式,支持的格式可能在不同的构建平台和设备类型上有所差别。 + * 目前所有平台和设备支持的格式有 ['.webp', '.jpg', '.jpeg', '.bmp', '.png']. The iOS mobile platform also supports the PVR format。 + */ +extern const ccstd::vector SUPPORT_TEXTURE_FORMATS; +/** + * @en Key map for keyboard event + * @zh 键盘事件的按键值。 + * @example {@link cocos/core/platform/CCCommon/KEY.js} + * @deprecated since v3.3 please use KeyCode instead + */ +enum class KEY { + /** + * @en None + * @zh 没有分配 + * @readonly + */ + NONE = 0, + + // android + /** + * @en The back key on mobile phone + * @zh 移动端返回键 + * @readonly + * @deprecated since v3.3 + */ + BACK = 6, + /** + * @en The menu key on mobile phone + * @zh 移动端菜单键 + * @readonly + * @deprecated since v3.3 + */ + MENU = 18, + + /** + * @en The backspace key + * @zh 退格键 + * @readonly + */ + BACKSPACE = 8, + + /** + * @en The tab key + * @zh Tab 键 + * @readonly + */ + TAB = 9, + + /** + * @en The enter key + * @zh 回车键 + * @readonly + */ + ENTER = 13, + + /** + * @en The shift key + * @zh Shift 键 + * @readonly + * @deprecated since v3.3, please use KeyCode.SHIFT_LEFT instead. + */ + SHIFT = 16, // should use shiftkey instead + + /** + * @en The ctrl key + * @zh Ctrl 键 + * @readonly + * @deprecated since v3.3, please use KeyCode.CTRL_LEFT instead. + */ + CTRL = 17, // should use ctrlkey + + /** + * @en The alt key + * @zh Alt 键 + * @readonly + * @deprecated since v3.3, please use KeyCode.ALT_LEFT instead. + */ + ALT = 18, // should use altkey + + /** + * @en The pause key + * @zh 暂停键 + * @readonly + */ + PAUSE = 19, + + /** + * @en The caps lock key + * @zh 大写锁定键 + * @readonly + */ + CAPSLOCK = 20, + + /** + * @en The esc key + * @zh ESC 键 + * @readonly + */ + ESCAPE = 27, + + /** + * @en The space key + * @zh 空格键 + * @readonly + */ + SPACE = 32, + + /** + * @en The page up key + * @zh 向上翻页键 + * @readonly + */ + PAGEUP = 33, + + /** + * @en The page down key + * @zh 向下翻页键 + * @readonly + */ + PAGEDOWN = 34, + + /** + * @en The end key + * @zh 结束键 + * @readonly + */ + END = 35, + + /** + * @en The home key + * @zh 主菜单键 + * @readonly + */ + HOME = 36, + + /** + * @en The left key + * @zh 向左箭头键 + * @readonly + */ + LEFT = 37, + + /** + * @en The up key + * @zh 向上箭头键 + * @readonly + */ + UP = 38, + + /** + * @en The right key + * @zh 向右箭头键 + * @readonly + */ + RIGHT = 39, + + /** + * @en The down key + * @zh 向下箭头键 + * @readonly + */ + DOWN = 40, + + /** + * @en The select key + * @zh Select 键 + * @readonly + * @deprecated since v3.3 + */ + SELECT = 41, + + /** + * @en The insert key + * @zh 插入键 + * @readonly + */ + INSERT = 45, + + /** + * @en The Delete key + * @zh 删除键 + * @readonly + * DELETE may be used as platform's macro, such as winnt.h,thus use DELETE_ instead here. + */ + DELETE_ = 46, // NOLINT(readability-identifier-naming) + + /** + * @en The a key + * @zh A 键 + * @readonly + */ + A = 65, + + /** + * @en The b key + * @zh B 键 + * @readonly + */ + B = 66, + + /** + * @en The c key + * @zh C 键 + * @readonly + */ + C = 67, + + /** + * @en The d key + * @zh D 键 + * @readonly + */ + D = 68, + + /** + * @en The e key + * @zh E 键 + * @readonly + */ + E = 69, + + /** + * @en The f key + * @zh F 键 + * @readonly + */ + F = 70, + + /** + * @en The g key + * @zh G 键 + * @readonly + */ + G = 71, + + /** + * @en The h key + * @zh H 键 + * @readonly + */ + H = 72, + + /** + * @en The i key + * @zh I 键 + * @readonly + */ + I = 73, + + /** + * @en The j key + * @zh J 键 + * @readonly + */ + J = 74, + + /** + * @en The k key + * @zh K 键 + * @readonly + */ + K = 75, + + /** + * @en The l key + * @zh L 键 + * @readonly + */ + L = 76, + + /** + * @en The m key + * @zh M 键 + * @readonly + */ + M = 77, + + /** + * @en The n key + * @zh N 键 + * @readonly + */ + N = 78, + + /** + * @en The o key + * @zh O 键 + * @readonly + */ + O = 79, + + /** + * @en The p key + * @zh P 键 + * @readonly + */ + P = 80, + + /** + * @en The q key + * @zh Q 键 + * @readonly + */ + Q = 81, + + /** + * @en The r key + * @zh R 键 + * @readonly + */ + R = 82, + + /** + * @en The s key + * @zh S 键 + * @readonly + */ + S = 83, + + /** + * @en The t key + * @zh T 键 + * @readonly + */ + T = 84, + + /** + * @en The u key + * @zh U 键 + * @readonly + */ + U = 85, + + /** + * @en The v key + * @zh V 键 + * @readonly + */ + V = 86, + + /** + * @en The w key + * @zh W 键 + * @readonly + */ + W = 87, + + /** + * @en The x key + * @zh X 键 + * @readonly + */ + X = 88, + + /** + * @en The y key + * @zh Y 键 + * @readonly + */ + Y = 89, + + /** + * @en The z key + * @zh Z 键 + * @readonly + */ + Z = 90, + + /** + * @en The numeric keypad 0 + * @zh 数字键盘 0 + * @readonly + */ + NUM0 = 96, + + /** + * @en The numeric keypad 1 + * @zh 数字键盘 1 + * @readonly + */ + NUM1 = 97, + + /** + * @en The numeric keypad 2 + * @zh 数字键盘 2 + * @readonly + */ + NUM2 = 98, + + /** + * @en The numeric keypad 3 + * @zh 数字键盘 3 + * @readonly + */ + NUM3 = 99, + + /** + * @en The numeric keypad 4 + * @zh 数字键盘 4 + * @readonly + */ + NUM4 = 100, + + /** + * @en The numeric keypad 5 + * @zh 数字键盘 5 + * @readonly + */ + NUM5 = 101, + + /** + * @en The numeric keypad 6 + * @zh 数字键盘 6 + * @readonly + */ + NUM6 = 102, + + /** + * @en The numeric keypad 7 + * @zh 数字键盘 7 + * @readonly + */ + NUM7 = 103, + + /** + * @en The numeric keypad 8 + * @zh 数字键盘 8 + * @readonly + */ + NUM8 = 104, + + /** + * @en The numeric keypad 9 + * @zh 数字键盘 9 + * @readonly + */ + NUM9 = 105, + + /** + * @en The numeric keypad 'delete' + * @zh 数字键盘删除键 + * @readonly + */ + NUMDEL = 110, + + /** + * @en The F1 function key + * @zh F1 功能键 + * @readonly + */ + F1 = 112, // f1-f12 dont work on ie + + /** + * @en The F2 function key + * @zh F2 功能键 + * @readonly + */ + F2 = 113, + + /** + * @en The F3 function key + * @zh F3 功能键 + * @readonly + */ + F3 = 114, + + /** + * @en The F4 function key + * @zh F4 功能键 + * @readonly + */ + F4 = 115, + + /** + * @en The F5 function key + * @zh F5 功能键 + * @readonly + */ + F5 = 116, + + /** + * @en The F6 function key + * @zh F6 功能键 + * @readonly + */ + F6 = 117, + + /** + * @en The F7 function key + * @zh F7 功能键 + * @readonly + */ + F7 = 118, + + /** + * @en The F8 function key + * @zh F8 功能键 + * @readonly + */ + F8 = 119, + + /** + * @en The F9 function key + * @zh F9 功能键 + * @readonly + */ + F9 = 120, + + /** + * @en The F10 function key + * @zh F10 功能键 + * @readonly + */ + F10 = 121, + + /** + * @en The F11 function key + * @zh F11 功能键 + * @readonly + */ + F11 = 122, + + /** + * @en The F12 function key + * @zh F12 功能键 + * @readonly + */ + F12 = 123, + + /** + * @en The numlock key + * @zh 数字锁定键 + * @readonly + */ + NUMLOCK = 144, + + /** + * @en The scroll lock key + * @zh 滚动锁定键 + * @readonly + */ + SCROLLLOCK = 145, + + /** + * @en The ';' key. + * @zh 分号键 + * @readonly + */ + SEMICOLON = 186, + + /** + * @en The '=' key. + * @zh 等于号键 + * @readonly + */ + EQUAL = 187, + + /** + * @en The ',' key. + * @zh 逗号键 + * @readonly + */ + COMMA = 188, + + /** + * @en The dash '-' key. + * @zh 中划线键 + * @readonly + */ + DASH = 189, + + /** + * @en The '.' key + * @zh 句号键 + * @readonly + */ + PERIOD = 190, + + /** + * @en The forward slash key + * @zh 正斜杠键 + * @readonly + */ + FORWARDSLASH = 191, + + /** + * @en The grave key + * @zh 按键 ` + * @readonly + */ + GRAVE = 192, + + /** + * @en The '[' key + * @zh 按键 [ + * @readonly + */ + OPENBRACKET = 219, + + /** + * @en The '\' key + * @zh 反斜杠键 + * @readonly + */ + BACKSLASH = 220, + + /** + * @en The ']' key + * @zh 按键 ] + * @readonly + */ + CLOSEBRACKET = 221, + + /** + * @en The quote key + * @zh 单引号键 + * @readonly + */ + QUOTE = 222, + + /** + * @en The dpad left key + * @zh 导航键 向左 + * @readonly + * @deprecated since v3.3 + */ + DPAD_LEFT = 1000, + + /** + * @en The dpad right key + * @zh 导航键 向右 + * @readonly + * @deprecated since v3.3 + */ + DPAD_RIGHT = 1001, + + /** + * @en The dpad up key + * @zh 导航键 向上 + * @readonly + * @deprecated since v3.3 + */ + DPAD_UP = 1003, + + /** + * @en The dpad down key + * @zh 导航键 向下 + * @readonly + * @deprecated since v3.3 + */ + DPAD_DOWN = 1004, + + /** + * @en The dpad center key + * @zh 导航键 确定键 + * @readonly + * @deprecated since v3.3 + */ + DPAD_CENTER = 1005, +}; + +/** + * PI / 180 + */ +static const float RAD{math::PI / 180}; + +/** + * One degree + */ +// static const float DEG; +static const float DEG{math::PI / 180}; + +/** + * A maximum value of number + */ +static const uint32_t REPEAT_FOREVER{UINT32_MAX}; + +/** + * A minimal float value + */ +// FLT_EPSILON: 0.0000001192092896, + +// static const float FLTEPSILON {0.0000001192092896}; //FLT_EPSILON is keyword in cpp +// Possible device orientations +/** + * @en Oriented vertically + * @zh 竖屏朝向 + */ +static const int32_t ORIENTATION_PORTRAIT{1}; + +/** + * @en Oriented horizontally + * @zh 横屏朝向 + */ +static const int32_t ORIENTATION_LANDSCAPE{2}; + +/** + * @en Oriented automatically + * @zh 自动适配朝向 + */ +static const int32_t ORIENTATION_AUTO{3}; + +/** + *

+ * If enabled, the texture coordinates will be calculated by using this formula:
+ * - texCoord.left = (rect.x*2+1) / (texture.wide*2);
+ * - texCoord.right = texCoord.left + (rect.width*2-2)/(texture.wide*2);
+ *
+ * The same for bottom and top.
+ *
+ * This formula prevents artifacts by using 99% of the texture.
+ * The "correct" way to prevent artifacts is by expand the texture's border with the same color by 1 pixel
+ *
+ * Affected component:
+ * - TMXLayer
+ *
+ * Enabled by default. To disabled set it to 0.
+ * To modify it, in Web engine please refer to CCMacro.js, in JSB please refer to CCConfig.h + *

+ * Currently not useful in 3D engine + */ +// FIX_ARTIFACTS_BY_STRECHING_TEXEL_TMX: true, + +/** + * @en + * Whether or not enabled tiled map auto culling. If you set the TiledMap skew or rotation, + * then need to manually disable this, otherwise, the rendering will be wrong. + * Currently not useful in 3D engine + * @zh + * 是否开启瓦片地图的自动裁减功能。瓦片地图如果设置了 skew, rotation 的话,需要手动关闭,否则渲染会出错。 + * 在 3D 引擎中暂时无效。 + * @default true + */ +static const bool ENABLE_TILEDMAP_CULLING{true}; + +/** + * @en + * The timeout to determine whether a touch is no longer active and should be removed. + * The reason to add this timeout is due to an issue in X5 browser core, + * when X5 is presented in wechat on Android, if a touch is glissed from the bottom up, and leave the page area, + * no touch cancel event is triggered, and the touch will be considered active forever. + * After multiple times of this action, our maximum touches number will be reached and all new touches will be ignored. + * So this new mechanism can remove the touch that should be inactive if it's not updated during the last 5000 milliseconds. + * Though it might remove a real touch if it's just not moving for the last 5 seconds which is not easy with the sensibility of mobile touch screen. + * You can modify this value to have a better behavior if you find it's not enough. + * @zh + * 用于甄别一个触点对象是否已经失效并且可以被移除的延时时长 + * 添加这个时长的原因是 X5 内核在微信浏览器中出现的一个 bug。 + * 在这个环境下,如果用户将一个触点从底向上移出页面区域,将不会触发任何 touch cancel 或 touch end 事件,而这个触点会被永远当作停留在页面上的有效触点。 + * 重复这样操作几次之后,屏幕上的触点数量将达到我们的事件系统所支持的最高触点数量,之后所有的触摸事件都将被忽略。 + * 所以这个新的机制可以在触点在一定时间内没有任何更新的情况下视为失效触点并从事件系统中移除。 + * 当然,这也可能移除一个真实的触点,如果用户的触点真的在一定时间段内完全没有移动(这在当前手机屏幕的灵敏度下会很难)。 + * 你可以修改这个值来获得你需要的效果,默认值是 5000 毫秒。 + * @default 5000 + */ +static const uint32_t TOUCH_TIMEOUT{5000}; +/** + * @en + * Boolean that indicates if the canvas contains an alpha channel, default sets to false for better performance. + * Though if you want to make your canvas background transparent and show other dom elements at the background, + * you can set it to true before {{game.init}}. + * Web only. + * @zh + * 用于设置 Canvas 背景是否支持 alpha 通道,默认为 false,这样可以有更高的性能表现。 + * 如果你希望 Canvas 背景是透明的,并显示背后的其他 DOM 元素,你可以在 {{game.init}} 之前将这个值设为 true。 + * 仅支持 Web + * @default false + */ +static const bool ENABLE_TRANSPARENT_CANVAS{false}; + +/** + * @en + * Boolean that indicates if the WebGL context is created with `antialias` option turned on, default value is false. + * Set it to true could make your game graphics slightly smoother, like texture hard edges when rotated. + * Whether to use this really depend on your game design and targeted platform, + * device with retina display usually have good detail on graphics with or without this option, + * you probably don't want antialias if your game style is pixel art based. + * Also, it could have great performance impact with some browser / device using software MSAA. + * You can set it to true before {{game.init}}. + * Web only. + * @zh + * 用于设置在创建 WebGL Context 时是否开启抗锯齿选项,默认值是 false。 + * 将这个选项设置为 true 会让你的游戏画面稍稍平滑一些,比如旋转硬边贴图时的锯齿。是否开启这个选项很大程度上取决于你的游戏和面向的平台。 + * 在大多数拥有 retina 级别屏幕的设备上用户往往无法区分这个选项带来的变化;如果你的游戏选择像素艺术风格,你也多半不会想开启这个选项。 + * 同时,在少部分使用软件级别抗锯齿算法的设备或浏览器上,这个选项会对性能产生比较大的影响。 + * 你可以在 {{game.init}} 之前设置这个值,否则它不会生效。 + * 仅支持 Web + * @default true + */ +static const bool ENABLE_WEBGL_ANTIALIAS{true}; + +/** + * @en + * Whether to clear the original image cache after uploaded a texture to GPU. + * If cleared, [Dynamic Atlas](https://docs.cocos.com/creator/manual/en/advanced-topics/dynamic-atlas.html) will not be supported. + * Normally you don't need to enable this option on the web platform, because Image object doesn't consume too much memory. + * But on Wechat Game platform, the current version cache decoded data in Image object, which has high memory usage. + * So we enabled this option by default on Wechat, so that we can release Image cache immediately after uploaded to GPU. + * Currently not useful in 3D engine + * @zh + * 是否在将贴图上传至 GPU 之后删除原始图片缓存,删除之后图片将无法进行 [动态合图](https://docs.cocos.com/creator/manual/zh/advanced-topics/dynamic-atlas.html)。 + * 在 Web 平台,你通常不需要开启这个选项,因为在 Web 平台 Image 对象所占用的内存很小。 + * 但是在微信小游戏平台的当前版本,Image 对象会缓存解码后的图片数据,它所占用的内存空间很大。 + * 所以我们在微信平台默认开启了这个选项,这样我们就可以在上传 GL 贴图之后立即释放 Image 对象的内存,避免过高的内存占用。 + * 在 3D 引擎中暂时无效。 + * @default false + */ +static const bool CLEANUP_IMAGE_CACHE{false}; + +/** + * @en + * Whether to enable multi-touch. + * @zh + * 是否开启多点触摸 + * @default true + */ +static const bool ENABLE_MULTI_TOUCH{true}; + +/** + * @en + * The maximum size of the canvas pool used by Label, please adjust according to the number of label component in the same scene of the project + * @zh + * Label 使用的 canvas pool 的最大大小,请根据项目同场景的 label 数量进行调整 + * @default 20 + */ +static const uint32_t MAX_LABEL_CANVAS_POOL_SIZE{20}; + +/** + * @en + * Boolean that indicates if enable highp precision data in structure with fragment shader. + * Enable this option will make the variables defined by the HIGHP_VALUE_STRUCT_DEFINE macro in the shader more accurate, such as position. + * Enable this option can avoid some distorted lighting effects. That depends on whether your game has abnormal lighting effects on this platform. + * There will be a slight performance loss if enable this option, but the impact is not significant. + * Only affect WebGL backend + * @zh + * 用于设置是否在片元着色器中使用结构体的时候,允许其中的数据使用highp精度 + * 将这个选项设置为 true 会让shader中使用HIGHP_VALUE_STRUCT_DEFINE宏定义的变量精度更高,比如位置信息等,避免出现一些失真的光照效果。是否开启这个选项很大程度上取决于你的游戏在此平台上是否出现了异常的表现。 + * 开启后会有轻微的性能损失,但影响不大。 + * 仅影响 WebGL 后端 + * @default false + */ +static const bool ENABLE_WEBGL_HIGHP_STRUCT_VALUES{false}; + +/** + * @zh Batcher2D 中内存增量的大小(KB) + * 这个值决定了当场景中存在的 2d 渲染组件的顶点数量超过当前 batcher2D 中可容纳的顶点数量时,内存扩充的增加量 + * 这个值越大,共用同一个 meshBuffer 的 2d 渲染组件数量会更多,但每次扩充所占用的内存也会更大 + * 默认值在标准格式([[vfmtPosUvColor]])下可容纳 4096 个顶点(4096*9*4/1024),你可以增加容量来提升每个批次可容纳的元素数量 + * @en The MeshBuffer chunk size in Batcher2D (KB) + * This value determines the increase in memory expansion, + * when the number of vertices of 2d rendering components present in the scene exceeds the number of vertices, + * that can be accommodated in the current batcher2D. + * The larger this value is, the more 2d rendering components will share the same meshBuffer, but the more memory will be used for each expansion + * The default size can contain 4096 standard vertex ([[vfmtPosUvColor]]) in one buffer, + * you can user larger buffer size to increase the elements count per 2d draw batch. + * @default 144 KB + */ +static const uint32_t BATCHER2D_MEM_INCREMENT{144}; +} // namespace macro + +} // namespace cc diff --git a/cocos/core/scene-graph/Layers.cpp b/cocos/core/scene-graph/Layers.cpp new file mode 100644 index 0000000..d3f2509 --- /dev/null +++ b/cocos/core/scene-graph/Layers.cpp @@ -0,0 +1,23 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ diff --git a/cocos/core/scene-graph/Layers.h b/cocos/core/scene-graph/Layers.h new file mode 100644 index 0000000..2589f3b --- /dev/null +++ b/cocos/core/scene-graph/Layers.h @@ -0,0 +1,121 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/std/container/vector.h" + +namespace cc { + +/** + * @zh 节点层管理器,层数据是以掩码数据方式存储在 [[Node.layer]] 中,用于射线检测、物理碰撞和用户自定义脚本逻辑。 + * 每个节点可属于一个或多个层,可通过 “包含式” 或 “排除式” 两种检测器进行层检测。 + * @en Node's layer manager, it's stored as bit mask data in [[Node.layer]]. + * Layer information is widely used in raycast, physics and user logic. + * Every node can be assigned to multiple layers with different bit masks, you can setup layer with inclusive or exclusive operation. + */ +class Layers final { +public: + Layers() = delete; + ~Layers() = delete; + + // built-in layers, users can use 0~19 bits, 20~31 are system preserve bits. + enum class LayerList : uint32_t { + NONE = 0, + IGNORE_RAYCAST = (1 << 20), + GIZMOS = (1 << 21), + EDITOR = (1 << 22), + UI_3D = (1 << 23), + SCENE_GIZMO = (1 << 24), + UI_2D = (1 << 25), + + PROFILER = (1 << 28), + DEFAULT = (1 << 30), + ALL = 0xffffffff, + }; + + using Enum = LayerList; + + /** + * @en + * Make a layer mask accepting nothing but the listed layers + * @zh + * 创建一个包含式层检测器,只接受列表中的层 + * @param includes All accepted layers + * @return A filter which can detect all accepted layers + */ + static uint32_t makeMaskInclude(const ccstd::vector &includes) { + uint32_t mask = 0; + for (uint32_t inc : includes) { + mask |= inc; + } + return mask; + } + + /** + * @en + * Make a layer mask accepting everything but the listed layers + * @zh + * 创建一个排除式层检测器,只拒绝列表中的层 + * @param excludes All excluded layers + * @return A filter which can detect for excluded layers + */ + static uint32_t makeMaskExclude(const ccstd::vector &excludes) { + return ~makeMaskInclude(excludes); + } + + /** + * @zh 添加一个新层,用户可编辑 0 - 19 位为用户自定义层 + * @en Add a new layer, user can use layers from bit position 0 to 19, other bits are reserved. + * @param name Layer's name + * @param bitNum Layer's bit position + */ + static void addLayer(const std::string &name, uint32_t bitNum); + + /** + * @en Remove a layer, user can remove layers from bit position 0 to 19, other bits are reserved. + * @zh 移除一个层,用户可编辑 0 - 19 位为用户自定义层 + * @param bitNum Layer's bit position + */ + static void deleteLayer(uint32_t bitNum); + + /** + * @en Given a layer name, returns the layer index as defined by either a Builtin or a User Layer in the Tags and Layers manager. + * @zh 给定层名称,返回由标记和层管理器中的内置层或用户层定义的层索引。 + * @param name layer's name + */ + static uint32_t nameToLayer(const std::string &name); + + /** + * @en Given a layer number, returns the name of the layer as defined in either a Builtin or a User Layer in the Tags and Layers manager. + * @zh 给定层数,返回在标记和层管理器中的内置层或用户层中定义的层名称。 + * @param bitNum layer's value + */ + static std::string layerToName(uint32_t bitNum); +}; + +CC_ENUM_BITWISE_OPERATORS(Layers::LayerList); + +} // namespace cc diff --git a/cocos/core/scene-graph/Node.cpp b/cocos/core/scene-graph/Node.cpp new file mode 100644 index 0000000..cf21dd6 --- /dev/null +++ b/cocos/core/scene-graph/Node.cpp @@ -0,0 +1,885 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/scene-graph/Node.h" +#include "base/StringUtil.h" +#include "core/data/Object.h" +#include "core/memop/CachedArray.h" +#include "core/platform/Debug.h" +#include "core/scene-graph/NodeEnum.h" +#include "core/scene-graph/Scene.h" +#include "core/utils/IDGenerator.h" +#include "math/Utils.h" + +namespace cc { + +// static variables + +uint32_t Node::clearFrame{0}; +uint32_t Node::clearRound{1000}; +const uint32_t Node::TRANSFORM_ON{1 << 0}; +uint32_t Node::globalFlagChangeVersion{0}; + +namespace { +const ccstd::string EMPTY_NODE_NAME; +IDGenerator idGenerator("Node"); +} // namespace + +Node::Node() : Node(EMPTY_NODE_NAME) { +} + +Node::Node(const ccstd::string &name) { +#define NODE_SHARED_MEMORY_BYTE_LENGTH (20) + static_assert(offsetof(Node, _padding) + sizeof(_padding) - offsetof(Node, _eventMask) == NODE_SHARED_MEMORY_BYTE_LENGTH, "Wrong shared memory size"); + _sharedMemoryActor.initialize(&_eventMask, NODE_SHARED_MEMORY_BYTE_LENGTH); +#undef NODE_SHARED_MEMORY_BYTE_LENGTH + + _id = idGenerator.getNewId(); + if (name.empty()) { + _name.append("New Node"); + } else { + _name = name; + } + // _eventProcessor = ccnew NodeEventProcessor(this); +} + +Node::~Node() { + if (!_children.empty()) { + // Reset children's _parent to nullptr to avoid dangerous pointer + for (const auto &child : _children) { + child->_parent = nullptr; + } + } +} + +void Node::onBatchCreated(bool dontChildPrefab) { + // onBatchCreated was implemented in TS, so code should never go here. + CC_ABORT(); + emit(dontChildPrefab); +} + +Node *Node::instantiate(Node *cloned, bool isSyncedNode) { + if (!cloned) { + CC_ABORT(); + // TODO(): cloned = legacyCC.instantiate._clone(this, this); + return nullptr; + } + // TODO(): + // const newPrefabInfo = cloned._prefab; + // if (EDITOR && newPrefabInfo) { + // if (cloned == = newPrefabInfo.root) { + // // newPrefabInfo.fileId = ''; + // } else { + // // var PrefabUtils = Editor.require('scene://utils/prefab'); + // // PrefabUtils.unlinkPrefab(cloned); + // } + //} + // if (EDITOR && legacyCC.GAME_VIEW) { + // const syncing = newPrefabInfo&& cloned == = newPrefabInfo.root && newPrefabInfo.sync; + // if (!syncing) { + // cloned._name += ' (Clone)'; + // } + //} + cloned->_parent = nullptr; + cloned->onBatchCreated(isSyncedNode); + return cloned; +} + +void Node::onHierarchyChangedBase(Node *oldParent) { // NOLINT(misc-unused-parameters) + Node *newParent = _parent; + auto *scene = dynamic_cast(newParent); + if (isPersistNode() && scene == nullptr) { + emit(); +#if CC_EDITOR + debug::warnID(1623); +#endif + } +#if CC_EDITOR + auto *curScene = getScene(); + const bool inCurrentSceneBefore = oldParent && oldParent->isChildOf(curScene); + const bool inCurrentSceneNow = newParent && newParent->isChildOf(curScene); + if (!inCurrentSceneBefore && inCurrentSceneNow) { + // attached + this->notifyEditorAttached(true); + } else if (inCurrentSceneBefore && !inCurrentSceneNow) { + // detached + this->notifyEditorAttached(false); + } + // conflict detection + // _Scene.DetectConflict.afterAddChild(this); +#endif + + bool shouldActiveNow = isActive() && !!(newParent && newParent->isActiveInHierarchy()); + if (isActiveInHierarchy() != shouldActiveNow) { + // Director::getInstance()->getNodeActivator()->activateNode(this, shouldActiveNow); // TODO(xwx): use TS temporarily + emit(shouldActiveNow); + } +} + +void Node::setActive(bool isActive) { + uint8_t isActiveU8 = isActive ? 1 : 0; + if (_active != isActiveU8) { + _active = isActiveU8; + Node *parent = _parent; + if (parent) { + bool couldActiveInScene = parent->isActiveInHierarchy(); + if (couldActiveInScene) { + // Director::getInstance()->getNodeActivator()->activateNode(this, isActive); // TODO(xwx): use TS temporarily + emit(isActive); + } + } + } +} + +void Node::setParent(Node *parent, bool isKeepWorld /* = false */) { + if (isKeepWorld) { + updateWorldTransform(); + } + + if (_parent == parent) { + return; + } + + Node *oldParent = _parent; + Node *newParent = parent; +#if CC_DEBUG > 0 + if (oldParent && (oldParent->_objFlags & Flags::DEACTIVATING) == Flags::DEACTIVATING) { + debug::errorID(3821); + } +#endif + _parent = newParent; + _siblingIndex = 0; + onSetParent(oldParent, isKeepWorld); + emit(oldParent); + if (oldParent) { + if (!(oldParent->_objFlags & Flags::DESTROYING)) { + index_t removeAt = getIdxOfChild(oldParent->_children, this); + // TODO(): DEV + /*if (DEV && removeAt < 0) { + errorID(1633); + return; + }*/ + if (removeAt < 0) { + return; + } + oldParent->_children.erase(oldParent->_children.begin() + removeAt); + oldParent->updateSiblingIndex(); + oldParent->emit(this); + } + } + if (newParent) { +#if CC_DEBUG > 0 + if ((newParent->_objFlags & Flags::DEACTIVATING) == Flags::DEACTIVATING) { + debug::errorID(3821); + } +#endif + newParent->_children.emplace_back(this); + _siblingIndex = static_cast(newParent->_children.size() - 1); + newParent->emit(this); + } + onHierarchyChanged(oldParent); +} + +void Node::walk(const WalkCallback &preFunc) { + walk(preFunc, nullptr); +} + +void Node::walk(const WalkCallback &preFunc, const WalkCallback &postFunc) { // NOLINT(misc-no-recursion) + if (preFunc) { + preFunc(this); + } + + for (const auto &child : _children) { + if (child) { + child->walk(preFunc, postFunc); + } + } + + if (postFunc) { + postFunc(this); + } +} + +// Component *Node::addComponent(Component *comp) { +// comp->_node = this; // cjh TODO: shared_ptr +// _components.emplace_back(comp); +// +// if (isActiveInHierarchy()) { +// NodeActivator::activateComp(comp); +// } +// +// return comp; +// } +// +// void Node::removeComponent(Component *comp) { +// auto iteComp = std::find(_components.begin(), _components.end(), comp); +// if (iteComp != _components.end()) { +// _components.erase(iteComp); +// } +// } + +bool Node::onPreDestroyBase() { + Flags destroyingFlag = Flags::DESTROYING; + _objFlags |= destroyingFlag; + bool destroyByParent = (!!_parent) && (!!(_parent->_objFlags & destroyingFlag)); +#if CC_EDITOR + if (!destroyByParent) { + this->notifyEditorAttached(false); + } +#endif + if (isPersistNode()) { + emit(); + } + if (!destroyByParent) { + if (_parent) { + emit(this); + index_t childIdx = getIdxOfChild(_parent->_children, this); + if (childIdx != -1) { + _parent->_children.erase(_parent->_children.begin() + childIdx); + } + _siblingIndex = 0; + _parent->updateSiblingIndex(); + _parent->emit(this); + } + } + + // NOTE: The following code is not needed now since we override Node._onPreDestroy in node.jsb.ts + // and the logic will be done in TS. + // emit(NodeEventType::NODE_DESTROYED, this); + // for (const auto &child : _children) { + // child->destroyImmediate(); + // } + // + // emit(EventTypesToJS::NODE_DESTROY_COMPONENTS); + + offAll(); + return destroyByParent; +} + +Node *Node::getChildByName(const ccstd::string &name) const { + if (name.empty()) { + CC_LOG_INFO("Invalid name"); + return nullptr; + } + for (const auto &child : _children) { + if (child->_name == name) { + return child; + } + } + return nullptr; +} + +void Node::setScene(Node *node) { + node->updateScene(); +} + +void Node::updateScene() { + if (_parent == nullptr) { + return; + } + _scene = _parent->_scene; + emit(_scene); +} + +/* static */ +index_t Node::getIdxOfChild(const ccstd::vector> &child, Node *target) { + auto iteChild = std::find(child.begin(), child.end(), target); + if (iteChild != child.end()) { + return static_cast(iteChild - child.begin()); + } + return CC_INVALID_INDEX; +} + +Node *Node::getChildByUuid(const ccstd::string &uuid) const { + if (uuid.empty()) { + CC_LOG_INFO("Invalid uuid"); + return nullptr; + } + for (const auto &child : _children) { + if (child->_id == uuid) { + return child; + } + } + return nullptr; +} + +bool Node::isChildOf(Node *parent) const { + const Node *child = this; + do { + if (child == parent) { + return true; + } + child = child->_parent; + } while (child); + return false; +} + +void Node::removeAllChildren() { + for (auto i = static_cast(_children.size() - 1); i >= 0; --i) { + if (_children[i]) { + _children[i]->setParent(nullptr); + } + } + _children.clear(); +} + +void Node::setSiblingIndex(index_t index) { + if (!_parent) { + return; + } + if (!!(_parent->_objFlags & Flags::DEACTIVATING)) { + debug::errorID(3821); + return; + } + ccstd::vector> &siblings = _parent->_children; + index = index != -1 ? index : static_cast(siblings.size()) - 1; + index_t oldIdx = getIdxOfChild(siblings, this); + if (index != oldIdx) { + if (oldIdx != CC_INVALID_INDEX) { + siblings.erase(siblings.begin() + oldIdx); + } + if (index < siblings.size()) { + siblings.insert(siblings.begin() + index, this); + } else { + siblings.emplace_back(this); + } + _parent->updateSiblingIndex(); + emit(index); + } +} + +Node *Node::getChildByPath(const ccstd::string &path) const { + size_t end = 0; + ccstd::vector segments = StringUtil::split(path, "/"); + auto *lastNode = const_cast(this); + for (const ccstd::string &segment : segments) { + if (segment.empty()) { + continue; + } + Node *next{nullptr}; + if (lastNode) { + for (const auto &child : lastNode->_children) { + if (child->_name == segment) { + next = child; + break; + } + } + lastNode = next; + } else { + break; + } + } + return lastNode; +} + +// +void Node::setPositionInternal(float x, float y, float z, bool calledFromJS) { + _localPosition.set(x, y, z); + invalidateChildren(TransformBit::POSITION); + + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::POSITION); + } + + if (!calledFromJS) { + notifyLocalPositionUpdated(); + } +} + +void Node::setRotationInternal(float x, float y, float z, float w, bool calledFromJS) { + _localRotation.set(x, y, z, w); + _eulerDirty = true; + + invalidateChildren(TransformBit::ROTATION); + + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::ROTATION); + } + + if (!calledFromJS) { + notifyLocalRotationUpdated(); + } +} + +void Node::setRotationFromEuler(float x, float y, float z) { + _euler.set(x, y, z); + Quaternion::fromEuler(x, y, z, &_localRotation); + _eulerDirty = false; + invalidateChildren(TransformBit::ROTATION); + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::ROTATION); + } + + notifyLocalRotationUpdated(); +} + +void Node::setScaleInternal(float x, float y, float z, bool calledFromJS) { + _localScale.set(x, y, z); + + invalidateChildren(TransformBit::SCALE); + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::SCALE); + } + + if (!calledFromJS) { + notifyLocalScaleUpdated(); + } +} +void Node::updateWorldTransform() { // NOLINT(misc-no-recursion) + uint32_t dirtyBits = 0; + updateWorldTransformRecursive(dirtyBits); +} + +void Node::updateWorldTransformRecursive(uint32_t &dirtyBits) { // NOLINT(misc-no-recursion) + const uint32_t currDirtyBits = _transformFlags; + if (!currDirtyBits) { + return; + } + + Node *parent = getParent(); + if (parent && parent->_transformFlags) { + parent->updateWorldTransformRecursive(dirtyBits); + } + dirtyBits |= currDirtyBits; + if (parent) { + if (dirtyBits & static_cast(TransformBit::POSITION)) { + _worldPosition.transformMat4(_localPosition, parent->_worldMatrix); + _worldMatrix.m[12] = _worldPosition.x; + _worldMatrix.m[13] = _worldPosition.y; + _worldMatrix.m[14] = _worldPosition.z; + } + if (dirtyBits & static_cast(TransformBit::RS)) { + Mat4::fromRTS(_localRotation, _localPosition, _localScale, &_worldMatrix); + Mat4::multiply(parent->_worldMatrix, _worldMatrix, &_worldMatrix); + const bool rotChanged = dirtyBits & static_cast(TransformBit::ROTATION); + Quaternion *rotTmp = rotChanged ? &_worldRotation : nullptr; + Mat4::toRTS(_worldMatrix, rotTmp, nullptr, &_worldScale); + } + } else { + if (dirtyBits & static_cast(TransformBit::POSITION)) { + _worldPosition.set(_localPosition); + _worldMatrix.m[12] = _worldPosition.x; + _worldMatrix.m[13] = _worldPosition.y; + _worldMatrix.m[14] = _worldPosition.z; + } + if (dirtyBits & static_cast(TransformBit::RS)) { + if (dirtyBits & static_cast(TransformBit::ROTATION)) { + _worldRotation.set(_localRotation); + } + if (dirtyBits & static_cast(TransformBit::SCALE)) { + _worldScale.set(_localScale); + } + Mat4::fromRTS(_worldRotation, _worldPosition, _worldScale, &_worldMatrix); + } + } + _transformFlags = (static_cast(TransformBit::NONE)); +} + +const Mat4 &Node::getWorldMatrix() const { // NOLINT(misc-no-recursion) + const_cast(this)->updateWorldTransform(); + return _worldMatrix; +} + +Mat4 Node::getWorldRS() { + updateWorldTransform(); + Mat4 target{_worldMatrix}; + target.m[12] = target.m[13] = target.m[14] = 0; + return target; +} + +Mat4 Node::getWorldRT() { + updateWorldTransform(); + Mat4 target; + Mat4::fromRT(_worldRotation, _worldPosition, &target); + return target; +} + +void Node::invalidateChildren(TransformBit dirtyBit) { // NOLINT(misc-no-recursion) + auto curDirtyBit{static_cast(dirtyBit)}; + const uint32_t hasChangedFlags = getChangedFlags(); + const uint32_t transformFlags = _transformFlags; + if (isValid() && (transformFlags & hasChangedFlags & curDirtyBit) != curDirtyBit) { + _transformFlags = (transformFlags | curDirtyBit); + setChangedFlags(hasChangedFlags | curDirtyBit); + + for (Node *child : getChildren()) { + child->invalidateChildren(dirtyBit | TransformBit::POSITION); + } + } +} + +void Node::setWorldPosition(float x, float y, float z) { + _worldPosition.set(x, y, z); + if (_parent) { + _parent->updateWorldTransform(); + Mat4 invertWMat{_parent->_worldMatrix}; + invertWMat.inverse(); + _localPosition.transformMat4(_worldPosition, invertWMat); + } else { + _localPosition.set(_worldPosition); + } + notifyLocalPositionUpdated(); + + invalidateChildren(TransformBit::POSITION); + + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::POSITION); + } +} + +const Vec3 &Node::getWorldPosition() const { + const_cast(this)->updateWorldTransform(); + return _worldPosition; +} + +void Node::setWorldRotation(float x, float y, float z, float w) { + _worldRotation.set(x, y, z, w); + if (_parent) { + _parent->updateWorldTransform(); + _localRotation.set(_parent->_worldRotation.getConjugated()); + _localRotation.multiply(_worldRotation); + } else { + _localRotation.set(_worldRotation); + } + + _eulerDirty = true; + + notifyLocalRotationUpdated(); + + invalidateChildren(TransformBit::ROTATION); + + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::ROTATION); + } +} + +const Quaternion &Node::getWorldRotation() const { // NOLINT(misc-no-recursion) + const_cast(this)->updateWorldTransform(); + return _worldRotation; +} + +void Node::setWorldScale(float x, float y, float z) { + if (_parent != nullptr) { + updateWorldTransform(); // ensure reentryability + Vec3 oldWorldScale = _worldScale; + _worldScale.set(x, y, z); + Mat3 localRS; + Mat3 localRotInv; + Mat4 worldMatrixTmp = _worldMatrix; + Vec3 rescaleFactor = _worldScale / oldWorldScale; + // apply new world scale to temp world matrix + worldMatrixTmp.scale(rescaleFactor); // need opt + // get temp local matrix + Mat4 tmpLocalTransform = _parent->getWorldMatrix().getInversed() * worldMatrixTmp; + // convert to Matrix 3 x 3 + Mat3::fromMat4(tmpLocalTransform, &localRS); + Mat3::fromQuat(_localRotation.getConjugated(), &localRotInv); + // remove rotation part of the local matrix + Mat3::multiply(localRotInv, localRS, &localRS); + + // extract scaling part from local matrix + _localScale.x = Vec3{localRS.m[0], localRS.m[1], localRS.m[2]}.length(); + _localScale.y = Vec3{localRS.m[3], localRS.m[4], localRS.m[5]}.length(); + _localScale.z = Vec3{localRS.m[6], localRS.m[7], localRS.m[8]}.length(); + } else { + _worldScale.set(x, y, z); + _localScale = _worldScale; + } + + notifyLocalScaleUpdated(); + + invalidateChildren(TransformBit::SCALE); + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::SCALE); + } +} + +const Vec3 &Node::getWorldScale() const { + const_cast(this)->updateWorldTransform(); + return _worldScale; +} + +void Node::setForward(const Vec3 &dir) { + const float len = dir.length(); + Vec3 v3Temp = dir * (-1.F / len); + Quaternion qTemp{Quaternion::identity()}; + Quaternion::fromViewUp(v3Temp, &qTemp); + setWorldRotation(qTemp); +} + +void Node::setAngle(float val) { + _euler.set(0, 0, val); + Quaternion::createFromAngleZ(val, &_localRotation); + _eulerDirty = false; + invalidateChildren(TransformBit::ROTATION); + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::ROTATION); + } + + notifyLocalRotationUpdated(); +} + +void Node::onSetParent(Node *oldParent, bool keepWorldTransform) { + if (_parent) { + if ((oldParent == nullptr || oldParent->_scene != _parent->_scene) && _parent->_scene != nullptr) { + walk(setScene); + } + } + + if (keepWorldTransform) { + if (_parent) { + _parent->updateWorldTransform(); + if (mathutils::approx(_parent->_worldMatrix.determinant(), 0.F, mathutils::EPSILON)) { + CC_LOG_WARNING("14300"); + _transformFlags |= static_cast(TransformBit::TRS); + updateWorldTransform(); + } else { + Mat4 tmpMat4 = _parent->_worldMatrix.getInversed() * _worldMatrix; + Mat4::toRTS(tmpMat4, &_localRotation, &_localPosition, &_localScale); + } + } else { + _localPosition.set(_worldPosition); + _localRotation.set(_worldRotation); + _localScale.set(_worldScale); + } + + notifyLocalPositionRotationScaleUpdated(); + _eulerDirty = true; + } + invalidateChildren(TransformBit::TRS); +} + +void Node::rotate(const Quaternion &rot, NodeSpace ns /* = NodeSpace::LOCAL*/, bool calledFromJS /* = false*/) { + Quaternion qTempA{rot}; + qTempA.normalize(); + if (ns == NodeSpace::LOCAL) { + _localRotation *= qTempA; + } else if (ns == NodeSpace::WORLD) { + Quaternion qTempB{Quaternion::identity()}; + qTempB = qTempA * getWorldRotation(); + qTempA = _worldRotation; + qTempA.inverse(); + qTempB = qTempA * qTempB; + _localRotation = _localRotation * qTempB; + } + _eulerDirty = true; + invalidateChildren(TransformBit::ROTATION); + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::ROTATION); + } + + if (!calledFromJS) { + notifyLocalRotationUpdated(); + } +} + +void Node::lookAt(const Vec3 &pos, const Vec3 &up) { + Vec3 vTemp = getWorldPosition(); + Quaternion qTemp{Quaternion::identity()}; + vTemp -= pos; + vTemp.normalize(); + Quaternion::fromViewUp(vTemp, up, &qTemp); + setWorldRotation(qTemp); +} + +Vec3 Node::inverseTransformPoint(const Vec3 &p) { // NOLINT(misc-no-recursion) + Vec3 out(p); + inverseTransformPointRecursive(out); + return out; +} + +void Node::inverseTransformPointRecursive(Vec3 &out) const { // NOLINT(misc-no-recursion) + auto *parent = getParent(); + if (!parent) { + return; + } + parent->inverseTransformPointRecursive(out); + Vec3::transformInverseRTS(out, getRotation(), getPosition(), getScale(), &out); +} + +void Node::setMatrix(const Mat4 &val) { + val.decompose(&_localScale, &_localRotation, &_localPosition); + notifyLocalPositionRotationScaleUpdated(); + + invalidateChildren(TransformBit::TRS); + _eulerDirty = true; + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::TRS); + } +} + +void Node::setWorldRotationFromEuler(float x, float y, float z) { + Quaternion::fromEuler(x, y, z, &_worldRotation); + if (_parent) { + _parent->updateWorldTransform(); + _localRotation = _parent->_worldRotation.getConjugated() * _worldRotation; + } else { + _localRotation = _worldRotation; + } + _eulerDirty = true; + + invalidateChildren(TransformBit::ROTATION); + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::ROTATION); + } + + notifyLocalRotationUpdated(); +} + +void Node::setRTSInternal(Quaternion *rot, Vec3 *pos, Vec3 *scale, bool calledFromJS) { + uint32_t dirtyBit = 0; + if (rot) { + dirtyBit |= static_cast(TransformBit::ROTATION); + _localRotation = *rot; + _eulerDirty = true; + } + if (pos) { + _localPosition = *pos; + dirtyBit |= static_cast(TransformBit::POSITION); + } + if (scale) { + _localScale = *scale; + dirtyBit |= static_cast(TransformBit::SCALE); + } + + if (!calledFromJS) { + notifyLocalPositionRotationScaleUpdated(); + } + + if (dirtyBit) { + invalidateChildren(static_cast(dirtyBit)); + if (_eventMask & TRANSFORM_ON) { + emit(static_cast(dirtyBit)); + } + } +} + +void Node::resetChangedFlags() { + globalFlagChangeVersion++; +} + +void Node::clearNodeArray() { + if (clearFrame < clearRound) { + clearFrame++; + } else { + clearFrame = 0; + } +} + +ccstd::string Node::getPathInHierarchy() const { + ccstd::string result = getName(); + Node *curNode = getParent(); + while (curNode && curNode->getParent()) { + result.insert(0, "/").insert(0, curNode->getName()); + curNode = curNode->getParent(); + } + return result; +} + +void Node::translate(const Vec3 &trans, NodeSpace ns) { + Vec3 v3Temp{trans}; + if (ns == NodeSpace::LOCAL) { + v3Temp.transformQuat(_localRotation); + _localPosition.x += v3Temp.x; + _localPosition.y += v3Temp.y; + _localPosition.z += v3Temp.z; + } else if (ns == NodeSpace::WORLD) { + if (_parent) { + Quaternion qTemp = _parent->getWorldRotation(); + qTemp.inverse(); + v3Temp.transformQuat(qTemp); + Vec3 scale{_worldScale}; + _localPosition.x += v3Temp.x / scale.x; + _localPosition.y += v3Temp.y / scale.y; + _localPosition.z += v3Temp.z / scale.z; + } else { + _localPosition.x += trans.x; + _localPosition.y += trans.y; + _localPosition.z += trans.z; + } + } + + notifyLocalPositionUpdated(); + + invalidateChildren(TransformBit::POSITION); + if (_eventMask & TRANSFORM_ON) { + emit(TransformBit::POSITION); + } +} + +bool Node::onPreDestroy() { + bool result = onPreDestroyBase(); + // TODO(Lenovo): bookOfChange free + return result; +} + +void Node::onHierarchyChanged(Node *oldParent) { + emit(); + onHierarchyChangedBase(oldParent); +} + +/* static */ +// Node *Node::find(const ccstd::string &path, Node *referenceNode /* = nullptr*/) { +// return cc::find(path, referenceNode); +// } + +// For deserialization +// void Node::_setChild(index_t i, Node *child) { +// if (i < _children.size()) { +// _children[i] = child; +// } else { +// CC_LOG_ERROR("Invalid index (%d) for Node children (size: %u)", i, static_cast(_children.size())); +// } +//} +// +// Node *Node::_getChild(index_t i) { +// if (i < _children.size()) { +// return _children[i]; +// } +// CC_LOG_ERROR("Invalid index (%d) for Node children (size: %u)", i, static_cast(_children.size())); +// return nullptr; +//} +// +// void Node::_setChildrenSize(uint32_t size) { +// _children.resize(size); +//} +// +// uint32_t Node::_getChildrenSize() { +// return _children.size(); +//} +// +void Node::_setChildren(ccstd::vector> &&children) { + _children = std::move(children); +} + +void Node::destruct() { + CCObject::destruct(); + _children.clear(); + _scene = nullptr; + _userData = nullptr; +} + +// + +} // namespace cc diff --git a/cocos/core/scene-graph/Node.h b/cocos/core/scene-graph/Node.h new file mode 100644 index 0000000..38baeed --- /dev/null +++ b/cocos/core/scene-graph/Node.h @@ -0,0 +1,685 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Ptr.h" +#include "base/std/any.h" +#include "bindings/utils/BindingUtils.h" +// #include "core/components/Component.h" +// #include "core/event/Event.h" +#include "core/data/Object.h" +#include "core/event/EventTarget.h" +#include "core/scene-graph/Layers.h" +#include "core/scene-graph/NodeEnum.h" +#include "math/Mat3.h" +#include "math/Mat4.h" +#include "math/Quaternion.h" +#include "math/Vec3.h" +#include "math/Vec4.h" + +namespace cc { + +class Scene; +/** + * Event types emitted by Node + */ +/** + * Bit masks for Node transformation parts + */ +using TransformDirtyBit = TransformBit; + +class Node : public CCObject { + IMPL_EVENT_TARGET_WITH_PARENT(Node, getParent) + DECLARE_TARGET_EVENT_BEGIN(Node) + TARGET_EVENT_ARG0(TouchStart) + TARGET_EVENT_ARG0(TouchMove) + TARGET_EVENT_ARG0(TouchEnd) + TARGET_EVENT_ARG0(TouchCancel) + TARGET_EVENT_ARG0(MouseDown) + TARGET_EVENT_ARG0(MouseMove) + TARGET_EVENT_ARG0(MouseUp) + TARGET_EVENT_ARG0(MouseWheel) + TARGET_EVENT_ARG0(MouseEnter) + TARGET_EVENT_ARG0(MouseLeave) + TARGET_EVENT_ARG0(KeyDown) + TARGET_EVENT_ARG0(KeyUp) + TARGET_EVENT_ARG0(DeviceMotion) + TARGET_EVENT_ARG1(TransformChanged, TransformBit) + TARGET_EVENT_ARG0(SceneChangedForPersist) + TARGET_EVENT_ARG0(SizeChanged) + TARGET_EVENT_ARG0(AnchorChanged) + TARGET_EVENT_ARG0(ColorChanged) + TARGET_EVENT_ARG1(ChildAdded, Node *) + TARGET_EVENT_ARG1(ChildRemoved, Node *) + TARGET_EVENT_ARG1(ParentChanged, Node *) + TARGET_EVENT_ARG0(MobilityChanged) + TARGET_EVENT_ARG1(LayerChanged, uint32_t) + TARGET_EVENT_ARG0(SiblingOrderChanged) + TARGET_EVENT_ARG1(SiblingIndexChanged, index_t) + TARGET_EVENT_ARG0(ActiveInHierarchyChanged) + TARGET_EVENT_ARG0(Reattach) + TARGET_EVENT_ARG0(RemovePersistRootNode) + TARGET_EVENT_ARG0(UITransformDirty) + TARGET_EVENT_ARG1(ActiveNode, bool) + TARGET_EVENT_ARG1(BatchCreated, bool) + TARGET_EVENT_ARG1(SceneUpdated, cc::Scene *) + TARGET_EVENT_ARG3(LocalPositionUpdated, float, float, float) + TARGET_EVENT_ARG4(LocalRotationUpdated, float, float, float, float) + TARGET_EVENT_ARG3(LocalScaleUpdated, float, float, float) + TARGET_EVENT_ARG10(LocalRTSUpdated, float, float, float, float, float, float, float, float, float, float) + TARGET_EVENT_ARG1(EditorAttached, bool) + TARGET_EVENT_ARG0(LightProbeBakingChanged) + DECLARE_TARGET_EVENT_END() +public: + class UserData : public RefCounted { + public: + ~UserData() override = default; + + protected: + UserData() = default; + }; + + using Super = CCObject; + + static const uint32_t TRANSFORM_ON; + + static Node *instantiate(Node *cloned, bool isSyncedNode); + static void setScene(Node *); + + /** + * @en Finds a node by hierarchy path, the path is case-sensitive. + * It will traverse the hierarchy by splitting the path using '/' character. + * This function will still returns the node even if it is inactive. + * It is recommended to not use this function every frame instead cache the result at startup. + * @zh 通过路径从节点树中查找节点的方法,路径是大小写敏感的,并且通过 `/` 来分隔节点层级。 + * 即使节点的状态是未启用的也可以找到,建议将结果缓存,而不是每次需要都去查找。 + * @param path The path of the target node + * @param referenceNode If given, the search will be limited in the sub node tree of the reference node + */ + // static Node *find(const ccstd::string &path, Node *referenceNode = nullptr); + + /** + * @en Determine whether the given object is a normal Node. Will return false if [[Scene]] given. + * @zh 指定对象是否是普通的节点?如果传入 [[Scene]] 会返回 false。 + */ + template + static bool isNode(T *obj); + + static void resetChangedFlags(); + static void clearNodeArray(); + + Node(); + explicit Node(const ccstd::string &name); + ~Node() override; + + virtual void onPostActivated(bool active) {} + + void setParent(Node *parent, bool isKeepWorld = false); + inline void modifyParent(Node *parent) { _parent = parent; } + + inline Scene *getScene() const { return _scene; }; + + using WalkCallback = std::function; + void walk(const WalkCallback &preFunc); + void walk(const WalkCallback &preFunc, const WalkCallback &postFunc); + + bool destroy() override { + if (CCObject::destroy()) { + setActive(false); + return true; + } + return false; + } + + void destruct() override; + + inline void destroyAllChildren() { + for (const auto &child : _children) { + child->destroy(); + } + } + + inline void updateSiblingIndex() { + index_t i = 0; + for (const auto &child : _children) { + child->_siblingIndex = i++; + } + emit(); + } + + inline void addChild(Node *node) { node->setParent(this); } + + inline void removeChild(Node *node) const { + auto idx = getIdxOfChild(_children, node); + if (idx != -1) { + node->setParent(nullptr); + } + } + inline void removeFromParent() { + if (_parent) { + _parent->removeChild(this); + } + } + void removeAllChildren(); + + bool isChildOf(Node *parent) const; + + void setActive(bool isActive); + + void setSiblingIndex(index_t index); + + inline bool isPersistNode() const { + return static_cast(_objFlags & Flags::DONT_DESTROY) > 0; + } + + inline void setPersistNode(bool val) { + val ? _objFlags |= Flags::DONT_DESTROY : _objFlags &= ~Flags::DONT_DESTROY; + } + + inline const ccstd::string &getUuid() const { + return _id; + } + + inline bool isActive() const { return _active != 0; } + + inline bool isActiveInHierarchy() const { return _activeInHierarchy != 0; } + inline void setActiveInHierarchy(bool v) { + _activeInHierarchy = (v ? 1 : 0); + } + + inline const ccstd::vector> &getChildren() const { return _children; } + inline Node *getParent() const { return _parent; } + // inline NodeEventProcessor *getEventProcessor() const { return _eventProcessor; } + + Node *getChildByUuid(const ccstd::string &uuid) const; + Node *getChildByName(const ccstd::string &name) const; + Node *getChildByPath(const ccstd::string &path) const; + inline index_t getSiblingIndex() const { return _siblingIndex; } + inline UserData *getUserData() { return _userData.get(); } + inline void setUserData(UserData *data) { _userData = data; } + inline void insertChild(Node *child, index_t siblingIndex) { + child->setParent(this); + child->setSiblingIndex(siblingIndex); + } + + void invalidateChildren(TransformBit dirtyBit); + + void translate(const Vec3 &, NodeSpace ns = NodeSpace::LOCAL); + void rotate(const Quaternion &rot, NodeSpace ns = NodeSpace::LOCAL, bool calledFromJS = false); + inline void rotateForJS(float x, float y, float z, float w, NodeSpace ns = NodeSpace::LOCAL) { + rotate(Quaternion(x, y, z, w), ns, true); + } + void lookAt(const Vec3 &pos, const Vec3 &up = Vec3::UNIT_Y); + + void pauseSystemEvents(bool recursive) {} // cjh TODO: + void resumeSystemEvents(bool recursive) {} // cjh TODO: + + ccstd::string getPathInHierarchy() const; + // =============================== + // transform + // =============================== + + /** + * @en Set position in local coordinate system + * @zh 设置本地坐标 + * @param position Target position + */ + inline void setPosition(const Vec3 &pos) { setPosition(pos.x, pos.y, pos.z); } + inline void setPosition(float x, float y) { setPosition(x, y, _localPosition.z); } + inline void setPosition(float x, float y, float z) { setPositionInternal(x, y, z, false); } + inline void setPositionInternal(float x, float y, bool calledFromJS) { setPositionInternal(x, y, _localPosition.z, calledFromJS); } + void setPositionInternal(float x, float y, float z, bool calledFromJS); + // It is invoked after deserialization. It only sets position value, not triggers other logic. + inline void setPositionForJS(float x, float y, float z) { _localPosition.set(x, y, z); } + /** + * @en Get position in local coordinate system, please try to pass `out` vector and reuse it to avoid garbage. + * @zh 获取本地坐标,注意,尽可能传递复用的 [[Vec3]] 以避免产生垃圾。 + * @param out Set the result to out vector + * @return If `out` given, the return value equals to `out`, otherwise a new vector will be generated and return + */ + inline const Vec3 &getPosition() const { return _localPosition; } + + /** + * @en Set rotation in local coordinate system with a quaternion representing the rotation + * @zh 用四元数设置本地旋转 + * @param rotation Rotation in quaternion + */ + inline void setRotation(const Quaternion &rotation) { setRotation(rotation.x, rotation.y, rotation.z, rotation.w); } + inline void setRotation(float x, float y, float z, float w) { setRotationInternal(x, y, z, w, false); } + void setRotationInternal(float x, float y, float z, float w, bool calledFromJS); + inline void setRotationForJS(float x, float y, float z, float w) { _localRotation.set(x, y, z, w); } + + inline void setEulerAngles(const Vec3 &val) { setRotationFromEuler(val.x, val.y, val.z); } + inline void setRotationFromEuler(const Vec3 &val) { setRotationFromEuler(val.x, val.y, val.z); } + inline void setRotationFromEuler(float x, float y) { setRotationFromEuler(x, y, _euler.z); } + void setRotationFromEuler(float x, float y, float z); + inline void setRotationFromEulerForJS(float x, float y, float z) { _euler.set(x, y, z); } + /** + * @en Get rotation as quaternion in local coordinate system, please try to pass `out` quaternion and reuse it to avoid garbage. + * @zh 获取本地旋转,注意,尽可能传递复用的 [[Quat]] 以避免产生垃圾。 + * @param out Set the result to out quaternion + * @return If `out` given, the return value equals to `out`, otherwise a new quaternion will be generated and return + */ + inline const Quaternion &getRotation() const { return _localRotation; } + + /** + * @en Set scale in local coordinate system + * @zh 设置本地缩放 + * @param scale Target scale + */ + inline void setScale(const Vec3 &scale) { setScale(scale.x, scale.y, scale.z); } + inline void setScale(float x, float y) { setScale(x, y, _localScale.z); } + inline void setScale(float x, float y, float z) { setScaleInternal(x, y, z, false); } + inline void setScaleInternal(float x, float y, bool calledFromJS) { setScaleInternal(x, y, _localScale.z, calledFromJS); } + void setScaleInternal(float x, float y, float z, bool calledFromJS); + inline void setScaleForJS(float x, float y, float z) { _localScale.set(x, y, z); } + /** + * @en Get scale in local coordinate system, please try to pass `out` vector and reuse it to avoid garbage. + * @zh 获取本地缩放,注意,尽可能传递复用的 [[Vec3]] 以避免产生垃圾。 + * @param out Set the result to out vector + * @return If `out` given, the return value equals to `out`, otherwise a new vector will be generated and return + */ + inline const Vec3 &getScale() const { return _localScale; } + + /** + * @en Inversely transform a point from world coordinate system to local coordinate system. + * @zh 逆向变换一个空间点,一般用于将世界坐标转换到本地坐标系中。 + * @param p A position in world coordinate system + * @return The result point in local coordinate system will be stored in this vector + */ + Vec3 inverseTransformPoint(const Vec3 &p); + + /** + * @en Set position in world coordinate system + * @zh 设置世界坐标 + * @param position Target position + */ + inline void setWorldPosition(const Vec3 &pos) { setWorldPosition(pos.x, pos.y, pos.z); } + void setWorldPosition(float x, float y, float z); + + /** + * @en Get position in world coordinate system, please try to pass `out` vector and reuse it to avoid garbage. + * @zh 获取世界坐标,注意,尽可能传递复用的 [[Vec3]] 以避免产生垃圾。 + * @param out Set the result to out vector + * @return If `out` given, the return value equals to `out`, otherwise a new vector will be generated and return + */ + const Vec3 &getWorldPosition() const; + + /** + * @en Set rotation in world coordinate system with a quaternion representing the rotation + * @zh 用四元数设置世界坐标系下的旋转 + * @param rotation Rotation in quaternion + */ + inline void setWorldRotation(const Quaternion &rotation) { setWorldRotation(rotation.x, rotation.y, rotation.z, rotation.w); } + void setWorldRotation(float x, float y, float z, float w); + /** + * @en Get rotation as quaternion in world coordinate system, please try to pass `out` quaternion and reuse it to avoid garbage. + * @zh 获取世界坐标系下的旋转,注意,尽可能传递复用的 [[Quat]] 以避免产生垃圾。 + * @param out Set the result to out quaternion + * @return If `out` given, the return value equals to `out`, otherwise a new quaternion will be generated and return + */ + const Quaternion &getWorldRotation() const; + + /** + * @en Set rotation in world coordinate system with euler angles + * @zh 用欧拉角设置世界坐标系下的旋转 + * @param x X axis rotation + * @param y Y axis rotation + * @param z Z axis rotation + */ + inline void setWorldScale(const Vec3 &scale) { setWorldScale(scale.x, scale.y, scale.z); } + void setWorldScale(float x, float y, float z); + + /** + * @en Get scale in world coordinate system, please try to pass `out` vector and reuse it to avoid garbage. + * @zh 获取世界缩放,注意,尽可能传递复用的 [[Vec3]] 以避免产生垃圾。 + * @param out Set the result to out vector + * @return If `out` given, the return value equals to `out`, otherwise a new vector will be generated and return + */ + const Vec3 &getWorldScale() const; + + void setWorldRotationFromEuler(float x, float y, float z); + + /** + * @en Local transformation matrix + * @zh 本地坐标系变换矩阵 + */ + void setMatrix(const Mat4 &val); + + /** + * @en Update the world transform information if outdated + * @zh 更新节点的世界变换信息 + */ + void updateWorldTransform(); + + /** + * @en Get a world transform matrix + * @zh 获取世界变换矩阵 + * @param out Set the result to out matrix + * @return If `out` given, the return value equals to `out`, otherwise a new matrix will be generated and return + */ + const Mat4 &getWorldMatrix() const; + + /** + * @en Get a world transform matrix with only rotation and scale + * @zh 获取只包含旋转和缩放的世界变换矩阵 + * @param out Set the result to out matrix + * @return If `out` given, the return value equals to `out`, otherwise a new matrix will be generated and return + */ + Mat4 getWorldRS(); + + /** + * @en Get a world transform matrix with only rotation and translation + * @zh 获取只包含旋转和位移的世界变换矩阵 + * @param out Set the result to out matrix + * @return If `out` given, the return value equals to `out`, otherwise a new matrix will be generated and return + */ + Mat4 getWorldRT(); + + /** + * @en Set local transformation with rotation, position and scale separately. + * @zh 一次性设置所有局部变换(平移、旋转、缩放)信息 + * @param rot The rotation + * @param pos The position + * @param scale The scale + */ + void setRTSInternal(Quaternion *rot, Vec3 *pos, Vec3 *scale, bool calledFromJS); + inline void setRTS(Quaternion *rot, Vec3 *pos, Vec3 *scale) { setRTSInternal(rot, pos, scale, false); } + + void setForward(const Vec3 &dir); + + void setAngle(float); + + inline const Vec3 &getEulerAngles() { + if (_eulerDirty) { + Quaternion::toEuler(_localRotation, false, &_euler); + _eulerDirty = false; + } + return _euler; + } + + inline float getAngle() const { + return _euler.z; + } + + inline Vec3 getForward() const { + Vec3 forward{0, 0, -1}; + forward.transformQuat(getWorldRotation()); + return forward; + } + + inline Vec3 getUp() const { + Vec3 up{0, 1, 0}; + up.transformQuat(getWorldRotation()); + return up; + } + + inline Vec3 getRight() const { + Vec3 right{1, 0, 0}; + right.transformQuat(getWorldRotation()); + return right; + } + + inline bool isStatic() const { + return _isStatic != 0; + } + + inline void setStatic(bool v) { + _isStatic = v ? 1 : 0; + } + + inline MobilityMode getMobility() const { + return _mobility; + } + + inline void setMobility(MobilityMode m) { + _mobility = m; + emit(); + } + + /** + * @en Whether the node's transformation have changed during the current frame. + * @zh 这个节点的空间变换信息在当前帧内是否有变过? + */ + inline uint32_t getChangedFlags() const { + return _hasChangedFlagsVersion == globalFlagChangeVersion ? _hasChangedFlags : 0; + } + inline void setChangedFlags(uint32_t value) { + _hasChangedFlagsVersion = globalFlagChangeVersion; + _hasChangedFlags = value; + } + /** + * @zh 节点的变换改动版本号。 + * @en The transformation change version number of the node. + */ + inline uint32_t getFlagChangedVersion() const { + return _hasChangedFlagsVersion; + } + + inline bool isTransformDirty() const { return _transformFlags != static_cast(TransformBit::NONE); } + inline void setLayer(uint32_t layer) { + _layer = layer; + emit(layer); + } + inline uint32_t getLayer() const { return _layer; } + + // inline NodeUiProperties *getUIProps() const { return _uiProps.get(); } + + // // ------------------ Component code start ----------------------------- + // // TODO(Lenovo): + // + // template ::value>> + // static Component *findComponent(Node * /*node*/) { + // // cjh TODO: + // CC_ABORT(); + // return nullptr; + // } + // + // template ::value>> + // static Component *findComponents(Node * /*node*/, const ccstd::vector & /*components*/) { + // // cjh TODO: + // CC_ABORT(); + // return nullptr; + // } + // + // template ::value>> + // static Component *findChildComponent(const ccstd::vector & /*children*/) { + // // cjh TODO: + // CC_ABORT(); + // return nullptr; + // } + // + // template ::value>> + // static void findChildComponents(const ccstd::vector & /*children*/, ccstd::vector & /*components*/) { + // // cjh TODO: + // CC_ABORT(); + // } + // + // template , T>> + // T *addComponent() { + // T *comp = new T(); + // return static_cast(addComponent(comp)); + // } + // + // template ::value>> + // void removeComponent() { + // for (auto iter = _components.begin(); iter != _components.end(); ++iter) { + // if (dynamic_cast(*iter) != nullptr) { + // _components.erase(iter); + // } + // } + // } + // + // Component *addComponent(Component *comp); + // void removeComponent(Component *comp); + // + // template ::value>> + // Component *getComponent() const { + // for (auto *component : _components) { + // if (dynamic_cast(component) != nullptr) { + // return component; + // } + // } + // return nullptr; + // } + // + // // TODO(Lenovo): + // template ::value>> + // ccstd::vector getComponents() const { + // // cjh TODO: + // CC_ABORT(); + // return {}; + // }; + // + // template ::value>> + // Component *getComponentInChildren(const T & /*comp*/) const { + // // cjh TODO: + // CC_ABORT(); + // return nullptr; + // } + // + // template ::value>> + // ccstd::vector getComponentsInChildren() const { + // // cjh TODO: + // CC_ABORT(); + // return {}; + // } + // + // inline ccstd::vector getComponents() const { return _components; } + // + // void checkMultipleComp(Component *comp) {} + // ccstd::vector _components; + // + // friend void componentCorrupted(Node *node, Component *comp, uint32_t index); + // ------------------ Component code end ----------------------------- + + // For deserialization + // void _setChild(index_t i, Node *child); + // Node * _getChild(index_t i); + // void _setChildrenSize(uint32_t size); + // uint32_t _getChildrenSize(); + void _setChildren(ccstd::vector> &&children); // NOLINT + + inline se::Object *_getSharedArrayBufferObject() const { return _sharedMemoryActor.getSharedArrayBufferObject(); } // NOLINT + + bool onPreDestroy() override; + bool onPreDestroyBase(); + + // For deserialization + ccstd::string _id; + Node *_parent{nullptr}; + MobilityMode _mobility = MobilityMode::Static; + +private: + static index_t getIdxOfChild(const ccstd::vector> &, Node *); + + virtual void onBatchCreated(bool dontChildPrefab); + virtual void updateScene(); + + void onSetParent(Node *oldParent, bool keepWorldTransform); + void onHierarchyChanged(Node *); + void onHierarchyChangedBase(Node *oldParent); + + void inverseTransformPointRecursive(Vec3 &out) const; + void updateWorldTransformRecursive(uint32_t &superDirtyBits); + + inline void notifyLocalPositionUpdated() { + emit(_localPosition.x, _localPosition.y, _localPosition.z); + } + + inline void notifyLocalRotationUpdated() { + emit(_localRotation.x, _localRotation.y, _localRotation.z, _localRotation.w); + } + + inline void notifyLocalScaleUpdated() { + emit(_localScale.x, _localScale.y, _localScale.z); + } + + inline void notifyLocalPositionRotationScaleUpdated() { + emit( + _localPosition.x, _localPosition.y, _localPosition.z, + _localRotation.x, _localRotation.y, _localRotation.z, _localRotation.w, + _localScale.x, _localScale.y, _localScale.z); + } + +#if CC_EDITOR + inline void notifyEditorAttached(bool attached) { + emit(attached); + } +#endif + + // increase on every frame, used to identify the frame + static uint32_t globalFlagChangeVersion; + + static uint32_t clearFrame; + static uint32_t clearRound; + + Scene *_scene{nullptr}; + IntrusivePtr _userData; + + ccstd::vector> _children; + bindings::NativeMemorySharedToScriptActor _sharedMemoryActor; + // local transform + Vec3 _localPosition{Vec3::ZERO}; + Vec3 _localScale{Vec3::ONE}; + Quaternion _localRotation{Quaternion::identity()}; + Vec3 _euler{0, 0, 0}; + + // world transform + Vec3 _worldPosition{Vec3::ZERO}; + Vec3 _worldScale{Vec3::ONE}; + Quaternion _worldRotation{Quaternion::identity()}; + Mat4 _worldMatrix{Mat4::IDENTITY}; + + // Shared memory with JS + // NOTE: TypeArray created in node.jsb.ts _ctor should have the same memory layout + uint32_t _eventMask{0}; // Uint32: 0 + uint32_t _layer{static_cast(Layers::LayerList::DEFAULT)}; // Uint32: 1 + uint32_t _transformFlags{static_cast(TransformBit::TRS)}; // Uint32: 2 + index_t _siblingIndex{0}; // Int32: 0 + uint8_t _activeInHierarchy{0}; // Uint8: 0 + uint8_t _active{1}; // Uint8: 1 + uint8_t _isStatic{0}; // Uint8: 2 + uint8_t _padding{0}; // Uint8: 3 + + /* set _hasChangedFlagsVersion to globalFlagChangeVersion when `_hasChangedFlags` updated. + * `globalFlagChangeVersion == _hasChangedFlagsVersion` means that "_hasChangedFlags is dirty in current frametime". + */ + uint32_t _hasChangedFlagsVersion{0}; + uint32_t _hasChangedFlags{0}; + + bool _eulerDirty{false}; + + friend class NodeActivator; + friend class Scene; + + CC_DISALLOW_COPY_MOVE_ASSIGN(Node); +}; + +template +bool Node::isNode(T *obj) { + return dynamic_cast(obj) != nullptr && dynamic_cast(obj) == nullptr; +} +} // namespace cc diff --git a/cocos/core/scene-graph/NodeEnum.cpp b/cocos/core/scene-graph/NodeEnum.cpp new file mode 100644 index 0000000..b628b49 --- /dev/null +++ b/cocos/core/scene-graph/NodeEnum.cpp @@ -0,0 +1,23 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ diff --git a/cocos/core/scene-graph/NodeEnum.h b/cocos/core/scene-graph/NodeEnum.h new file mode 100644 index 0000000..9d4d7cf --- /dev/null +++ b/cocos/core/scene-graph/NodeEnum.h @@ -0,0 +1,69 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace cc { + +enum class NodeSpace { + LOCAL, + WORLD +}; + +enum class TransformBit : uint32_t { + NONE = 0, + POSITION = (1 << 0), + ROTATION = (1 << 1), + SCALE = (1 << 2), + RS = ROTATION | SCALE, + TRS = POSITION | ROTATION | SCALE, + TRS_MASK = ~TRS, +}; +CC_ENUM_BITWISE_OPERATORS(TransformBit); + +/** + * @en Node's mobility + * @zh 节点的移动性 + */ +enum class MobilityMode { + /** + * @en Static node + * @zh 静态节点 + */ + Static = 0, + + /** + * @en Stationary node + * @zh 固定节点 + */ + Stationary = 1, + + /** + * @en Movable node + * @zh 可移动节点 + */ + Movable = 2, +}; + +} // namespace cc diff --git a/cocos/core/scene-graph/Scene.cpp b/cocos/core/scene-graph/Scene.cpp new file mode 100644 index 0000000..8239c37 --- /dev/null +++ b/cocos/core/scene-graph/Scene.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/scene-graph/Scene.h" +#include "core/scene-graph/SceneGlobals.h" +// #include "core/Director.h" +#include "core/Root.h" +//#include "core/scene-graph/NodeActivator.h" +#include "engine/EngineEvents.h" + +namespace cc { + +Scene::Scene(const ccstd::string &name) +: Node(name) { + // _activeInHierarchy is initalized to 'false', so doesn't need to set it to false again + // _activeInHierarchy = false; + if (Root::getInstance() != nullptr) { + _renderScene = Root::getInstance()->createScene({}); + } + _globals = ccnew SceneGlobals(); +} + +Scene::Scene() : Scene("") {} + +Scene::~Scene() = default; + +void Scene::setSceneGlobals(SceneGlobals *globals) { _globals = globals; } + +void Scene::load() { + events::SceneLoad::broadcast(); + if (!_inited) { + //cjh if (TEST) { + // CC_ASSERT(!_activeInHierarchy, 'Should deactivate ActionManager by default'); + // } + // expandNestedPrefabInstanceNode(this); // TODO(xwx): expandNestedPrefabInstanceNode not implement yet + // applyTargetOverrides(this); // TODO(xwx): applyTargetOverrides not implement yet + //cjh _onBatchCreated is implemented in TS now, so comment the following line + // onBatchCreated(false); //cjh EDITOR && _prefabSyncedInLiveReload); + _inited = true; + } + _scene = this; + // static method can't use this as parameter type + walk(Node::setScene); +} + +void Scene::activate(bool active /* = true */) { // NOLINT(misc-unused-parameters) +#if CC_EDITOR + this->notifyEditorAttached(active); +#endif + //cjh + // Director::getInstance()->getNodeActivator()->activateNode(this, active); + // The test environment does not currently support the renderer + // if (!TEST) { + _globals->activate(this); + if (_renderScene) { + _renderScene->activate(); + } + // } +} + +void Scene::onBatchCreated(bool dontSyncChildPrefab) { + // Moved from Node::onBatchCreated, Node::onBatchCreated only emits event to JS now. + if (_parent) { + index_t idx = getIdxOfChild(_parent->_children, this); + if (idx != CC_INVALID_INDEX) { + _siblingIndex = idx; + } + } + // + + auto len = static_cast(_children.size()); + for (int32_t i = 0; i < len; ++i) { + _children[i]->setSiblingIndex(i); + _children[i]->onBatchCreated(dontSyncChildPrefab); + } +} + +bool Scene::destroy() { + bool success = Super::destroy(); + if (success) { + for (auto &child : _children) { + child->setActive(false); + } + } + + if (_renderScene != nullptr) { + Root::getInstance()->destroyScene(_renderScene); + } + + _active = false; + setActiveInHierarchy(false); + return success; +} + +} // namespace cc diff --git a/cocos/core/scene-graph/Scene.h b/cocos/core/scene-graph/Scene.h new file mode 100644 index 0000000..b6b51a3 --- /dev/null +++ b/cocos/core/scene-graph/Scene.h @@ -0,0 +1,77 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/scene-graph/Node.h" + +namespace cc { +class SceneGlobals; +namespace scene { +class RenderScene; +} + +class Scene final : public Node { +public: + using Super = Node; + explicit Scene(const ccstd::string &name); + Scene(); + ~Scene() override; + + inline scene::RenderScene *getRenderScene() const { return _renderScene; } + inline SceneGlobals *getSceneGlobals() const { return _globals.get(); } + void setSceneGlobals(SceneGlobals *globals); + inline bool isAutoReleaseAssets() const { return _autoReleaseAssets; } + inline void setAutoReleaseAssets(bool val) { _autoReleaseAssets = val; } + + void load(); + void activate(bool active = true); + + void onBatchCreated(bool dontSyncChildPrefab) override; + bool destroy() override; + +protected: + void updateScene() override { _scene = this; } + + IntrusivePtr _renderScene; + /** + * @en Per-scene level rendering info + * @zh 场景级别的渲染信息 + */ + // @serializable + IntrusivePtr _globals; + bool _inited{false}; + + /** + * @en Indicates whether all (directly or indirectly) static referenced assets of this scene are releasable by default after scene unloading. + * @zh 指示该场景中直接或间接静态引用到的所有资源是否默认在场景切换后自动释放。 + */ + // @serializable + // @editable + bool _autoReleaseAssets{false}; + + CC_DISALLOW_COPY_MOVE_ASSIGN(Scene); +}; + +} // namespace cc diff --git a/cocos/core/scene-graph/SceneGlobals.cpp b/cocos/core/scene-graph/SceneGlobals.cpp new file mode 100644 index 0000000..6947fcb --- /dev/null +++ b/cocos/core/scene-graph/SceneGlobals.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/scene-graph/SceneGlobals.h" +#include "core/Root.h" +#include "gi/light-probe/LightProbe.h" +#include "renderer/pipeline/PipelineSceneData.h" +#include "renderer/pipeline/custom/RenderInterfaceTypes.h" +#include "scene/Ambient.h" +#include "scene/Fog.h" +#include "scene/Octree.h" +#include "scene/Shadow.h" +#include "scene/Skin.h" +#include "scene/Skybox.h" +#include "scene/PostSettings.h" + +namespace cc { + +SceneGlobals::SceneGlobals() { + _ambientInfo = ccnew scene::AmbientInfo(); + _shadowInfo = ccnew scene::ShadowsInfo(); + _skyboxInfo = ccnew scene::SkyboxInfo(); + _fogInfo = ccnew scene::FogInfo(); + _octreeInfo = ccnew scene::OctreeInfo(); + _lightProbeInfo = ccnew gi::LightProbeInfo(); + _bakedWithStationaryMainLight = false; + _bakedWithHighpLightmap = false; + _skinInfo = ccnew scene::SkinInfo(); + _postSettingsInfo = ccnew scene::PostSettingsInfo(); +} + +void SceneGlobals::activate(Scene *scene) { + auto *sceneData = Root::getInstance()->getPipeline()->getPipelineSceneData(); + if (_ambientInfo != nullptr) { + _ambientInfo->activate(sceneData->getAmbient()); + } + + if (_skyboxInfo != nullptr) { + _skyboxInfo->activate(sceneData->getSkybox()); + } + + if (_shadowInfo != nullptr) { + _shadowInfo->activate(sceneData->getShadows()); + } + + if (_fogInfo != nullptr) { + _fogInfo->activate(sceneData->getFog()); + } + + if (_octreeInfo != nullptr) { + _octreeInfo->activate(sceneData->getOctree()); + } + + if (_lightProbeInfo != nullptr && sceneData->getLightProbes() != nullptr) { + _lightProbeInfo->activate(scene, sceneData->getLightProbes()); + } + + if (_skinInfo != nullptr && sceneData->getSkin()) { + _skinInfo->activate(sceneData->getSkin()); + } + + if (_postSettingsInfo != nullptr && sceneData->getPostSettings()) { + _postSettingsInfo->activate(sceneData->getPostSettings()); + } + + Root::getInstance()->onGlobalPipelineStateChanged(); +} + +void SceneGlobals::setAmbientInfo(scene::AmbientInfo *info) { + _ambientInfo = info; +} + +void SceneGlobals::setShadowsInfo(scene::ShadowsInfo *info) { + _shadowInfo = info; +} + +void SceneGlobals::setSkyboxInfo(scene::SkyboxInfo *info) { + _skyboxInfo = info; +} + +void SceneGlobals::setFogInfo(scene::FogInfo *info) { + _fogInfo = info; +} + +void SceneGlobals::setOctreeInfo(scene::OctreeInfo *info) { + _octreeInfo = info; +} + +void SceneGlobals::setLightProbeInfo(gi::LightProbeInfo *info) { + _lightProbeInfo = info; +} + +void SceneGlobals::setBakedWithStationaryMainLight(bool value) { + _bakedWithStationaryMainLight = value; +} + +void SceneGlobals::setBakedWithHighpLightmap(bool value) { + _bakedWithHighpLightmap = value; +} + +void SceneGlobals::setSkinInfo(scene::SkinInfo *info) { + _skinInfo = info; +} + +void SceneGlobals::setPostSettingsInfo(scene::PostSettingsInfo *info) { + _postSettingsInfo = info; +} + +} // namespace cc diff --git a/cocos/core/scene-graph/SceneGlobals.h b/cocos/core/scene-graph/SceneGlobals.h new file mode 100644 index 0000000..b16f688 --- /dev/null +++ b/cocos/core/scene-graph/SceneGlobals.h @@ -0,0 +1,88 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Ptr.h" +#include "base/RefCounted.h" + +namespace cc { +class Scene; +namespace scene { +class AmbientInfo; +class ShadowsInfo; +class SkyboxInfo; +class FogInfo; +class OctreeInfo; +class SkinInfo; +class PostSettingsInfo; +} // namespace scene + +namespace gi { +class LightProbeInfo; +} + +class SceneGlobals : public RefCounted { +public: + SceneGlobals(); + ~SceneGlobals() override = default; + + void activate(Scene *scene); + + inline scene::AmbientInfo *getAmbientInfo() const { return _ambientInfo.get(); } + inline scene::ShadowsInfo *getShadowsInfo() const { return _shadowInfo.get(); } + inline scene::SkyboxInfo *getSkyboxInfo() const { return _skyboxInfo.get(); } + inline scene::FogInfo *getFogInfo() const { return _fogInfo.get(); } + inline scene::OctreeInfo *getOctreeInfo() const { return _octreeInfo.get(); } + inline gi::LightProbeInfo *getLightProbeInfo() const { return _lightProbeInfo.get(); } + inline bool getBakedWithStationaryMainLight() const { return _bakedWithStationaryMainLight; } + inline bool getBakedWithHighpLightmap() const { return _bakedWithHighpLightmap; } + inline scene::SkinInfo *getSkinInfo() const { return _skinInfo.get(); } + inline scene::PostSettingsInfo *getPostSettingsInfo() const { return _postSettingsInfo.get(); } + + void setAmbientInfo(scene::AmbientInfo *info); + void setShadowsInfo(scene::ShadowsInfo *info); + void setSkyboxInfo(scene::SkyboxInfo *info); + void setFogInfo(scene::FogInfo *info); + void setOctreeInfo(scene::OctreeInfo *info); + void setLightProbeInfo(gi::LightProbeInfo *info); + void setBakedWithStationaryMainLight(bool value); + void setBakedWithHighpLightmap(bool value); + void setSkinInfo(scene::SkinInfo *info); + void setPostSettingsInfo(scene::PostSettingsInfo *info); + +private: + IntrusivePtr _ambientInfo; + IntrusivePtr _shadowInfo; + IntrusivePtr _skyboxInfo; + IntrusivePtr _fogInfo; + IntrusivePtr _octreeInfo; + IntrusivePtr _lightProbeInfo; + IntrusivePtr _skinInfo; + IntrusivePtr _postSettingsInfo; + bool _bakedWithStationaryMainLight; + bool _bakedWithHighpLightmap; +}; + +} // namespace cc diff --git a/cocos/core/scene-graph/SceneGraphModuleHeader.h b/cocos/core/scene-graph/SceneGraphModuleHeader.h new file mode 100644 index 0000000..338ed22 --- /dev/null +++ b/cocos/core/scene-graph/SceneGraphModuleHeader.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +//#include "core/scene-graph/ComponentScheduler.h" +//#include "core/scene-graph/Find.h" +#include "core/scene-graph/Layers.h" +#include "core/scene-graph/Node.h" +//#include "core/scene-graph/NodeActivator.h" +#include "core/scene-graph/NodeEnum.h" +// #include "core/scene-graph/NodeEvent.h" +// #include "core/scene-graph/NodeEventProcessor.h" +//#include "core/scene-graph/NodeUIProperties.h" +#include "core/scene-graph/Scene.h" +#include "core/scene-graph/SceneGlobals.h" diff --git a/cocos/core/utils/IDGenerator.cpp b/cocos/core/utils/IDGenerator.cpp new file mode 100644 index 0000000..651cf8d --- /dev/null +++ b/cocos/core/utils/IDGenerator.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/utils/IDGenerator.h" +#include "base/Random.h" +#include "boost/uuid/uuid.hpp" +#include "boost/uuid/uuid_generators.hpp" +#include "boost/uuid/uuid_io.hpp" + +namespace cc { + +IDGenerator globalIdGenerator("global"); + +IDGenerator::IDGenerator(const ccstd::string &category) { + // Tnit with a random id to emphasize that the returns id should not be stored in persistence data. + _id = static_cast(RandomHelper::randomInt(0, 998)); + _prefix = (category + nonUuidMark); +} + +ccstd::string IDGenerator::getNewId() { +#if CC_EDITOR + if (_prefix == "Node." || _prefix == "Comp.") { + static boost::uuids::random_generator_mt19937 generator; + boost::uuids::uuid id = generator(); + return boost::uuids::to_string(id); + } +#endif + return _prefix + std::to_string(++_id); +} +} // namespace cc diff --git a/cocos/core/utils/IDGenerator.h b/cocos/core/utils/IDGenerator.h new file mode 100644 index 0000000..7af146b --- /dev/null +++ b/cocos/core/utils/IDGenerator.h @@ -0,0 +1,57 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/string.h" + +namespace cc { + +static const char *nonUuidMark = "."; + +/** + * ID generator for runtime. + */ +class IDGenerator { +public: + /** + * @param [category] You can specify a unique category to avoid id collision with other instance of IdGenerator. + */ + explicit IDGenerator(const ccstd::string &category); + + ccstd::string getNewId(); + +private: + uint32_t _id{0}; + + ccstd::string _prefix; +}; + +/* +* The global id generator might have a conflict problem once every 365 days, +* if the game runs at 60 FPS and each frame 4760273 counts of new id are requested. +*/ +extern IDGenerator globalIdGenerator; + +} // namespace cc diff --git a/cocos/core/utils/ImageUtils.cpp b/cocos/core/utils/ImageUtils.cpp new file mode 100644 index 0000000..7359536 --- /dev/null +++ b/cocos/core/utils/ImageUtils.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/utils/ImageUtils.h" +#include "base/Log.h" +#include "renderer/gfx-base/GFXDef-common.h" +namespace { +uint8_t *convertRGB2RGBA(uint32_t length, uint8_t *src) { + auto *dst = reinterpret_cast(malloc(length)); + for (uint32_t i = 0; i < length; i += 4) { + dst[i] = *src++; + dst[i + 1] = *src++; + dst[i + 2] = *src++; + dst[i + 3] = 255; + } + return dst; +} + +uint8_t *convertIA2RGBA(uint32_t length, uint8_t *src) { + auto *dst = reinterpret_cast(malloc(length)); + for (uint32_t i = 0; i < length; i += 4) { + dst[i] = *src; + dst[i + 1] = *src; + dst[i + 2] = *src++; + dst[i + 3] = *src++; + } + return dst; +} + +uint8_t *convertI2RGBA(uint32_t length, uint8_t *src) { + auto *dst = reinterpret_cast(malloc(length)); + for (uint32_t i = 0; i < length; i += 4) { + dst[i] = *src; + dst[i + 1] = *src; + dst[i + 2] = *src++; + dst[i + 3] = 255; + } + return dst; +} +} // namespace + +namespace cc { +void ImageUtils::convert2RGBA(Image *image) { + if (!image->_isCompressed && image->_renderFormat != gfx::Format::RGBA8) { + image->_dataLen = image->_width * image->_height * 4; + uint8_t *dst = nullptr; + uint32_t length = static_cast(image->_dataLen); + uint8_t *src = image->_data; + switch (image->_renderFormat) { + case gfx::Format::A8: + case gfx::Format::LA8: + dst = convertIA2RGBA(length, src); + break; + case gfx::Format::L8: + case gfx::Format::R8: + case gfx::Format::R8I: + dst = convertI2RGBA(length, src); + break; + case gfx::Format::RGB8: + dst = convertRGB2RGBA(length, src); + break; + default: + CC_LOG_INFO("cannot convert to RGBA: unknown image format"); + break; + } + if (dst != image->_data) free(image->_data); + image->_data = dst; + image->_renderFormat = gfx::Format::RGBA8; + } +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/core/utils/ImageUtils.h b/cocos/core/utils/ImageUtils.h new file mode 100644 index 0000000..034781f --- /dev/null +++ b/cocos/core/utils/ImageUtils.h @@ -0,0 +1,35 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "cocos/platform/Image.h" + +namespace cc { +class ImageUtils { +public: + static void convert2RGBA(Image *image); +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/core/utils/Path.cpp b/cocos/core/utils/Path.cpp new file mode 100644 index 0000000..bfeebcd --- /dev/null +++ b/cocos/core/utils/Path.cpp @@ -0,0 +1,216 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "core/utils/Path.h" +#include +#include "base/StringUtil.h" + +namespace cc { + +namespace { +const ccstd::string EMPTY_STRING; + +ccstd::string &removeLastSlash(ccstd::string &path) { + if (!path.empty()) { + if (path[path.length() - 1] == '/' || path[path.length() - 1] == '\\') { + path = path.substr(0, path.length() - 1); + } else if (path.length() > 1 && path[path.length() - 1] == '\\' && path[path.length() - 2] == '\\') { + path = path.substr(0, path.length() - 2); + } + } + return path; +} + +} // namespace + +ccstd::string join(const ccstd::vector &segments) { + ccstd::string result; + + for (const auto &segment : segments) { + if (!result.empty()) { + result += "/"; + } + result += segment; + removeLastSlash(result); + } + + return result; +} + +ccstd::string extname(const ccstd::string &path) { + if (path.empty()) { + return EMPTY_STRING; + } + + ccstd::string newPath = path; + size_t index = path.find_first_of('?'); + if (index != ccstd::string::npos && index > 0) { + newPath = newPath.substr(0, index); + } + + index = newPath.find_last_of('.'); + if (index == ccstd::string::npos) { + return EMPTY_STRING; + } + + return newPath.substr(index); +} + +ccstd::string mainFileName(const ccstd::string &fileName) { + if (!fileName.empty()) { + size_t idx = fileName.find_last_of('.'); + if (idx != ccstd::string::npos) { + return fileName.substr(0, idx); + } + } + + return fileName; +} + +ccstd::string basename(const ccstd::string &path, const ccstd::string &extName /* = ""*/) { + ccstd::string newPath = path; + size_t index = path.find_first_of('?'); + if (index != ccstd::string::npos && index > 0) { + newPath = newPath.substr(0, index); + } + + removeLastSlash(newPath); + + index = newPath.find_last_of("/\\"); + if (index == ccstd::string::npos) { + return newPath; + } + + ccstd::string baseName = newPath.substr(index + 1); + + if (!extName.empty() && extName.length() < newPath.length()) { + ccstd::string extInPath = newPath.substr(newPath.length() - extName.length()); + ccstd::string expectedExtName = extName; + if (StringUtil::tolower(extInPath) == StringUtil::tolower(expectedExtName)) { + baseName = baseName.substr(0, baseName.length() - extName.length()); + } + } + + return baseName; +} + +ccstd::string dirname(const ccstd::string &path) { + size_t index = path.find_last_of("/\\"); + if (index == ccstd::string::npos) { + return ""; + } + + ccstd::string dir = path.substr(0, index); + removeLastSlash(dir); + return dir; +} + +ccstd::string changeExtname(const ccstd::string &path, const ccstd::string &extName /* = ""*/) { + size_t index = path.find_first_of('?'); + ccstd::string newPath = path; + ccstd::string tempStr; + if (index != ccstd::string::npos && index > 0) { + tempStr = path.substr(index); + newPath = path.substr(0, index); + } + + index = newPath.find_last_of('.'); + if (index == ccstd::string::npos) { + return newPath + extName + tempStr; + } + + return newPath.substr(0, index) + extName + tempStr; +} + +ccstd::string changeBasename(const ccstd::string &path, const ccstd::string &baseName, bool isSameExt /* = false*/) { + if (baseName.find_last_of('.') == 0) { + return changeExtname(path, baseName); + } + + size_t index = path.find_last_of('?'); + ccstd::string tempStr; + ccstd::string newPath = path; + const ccstd::string ext = isSameExt ? extname(path) : ""; + if (index != ccstd::string::npos && index > 0) { + tempStr = path.substr(index); + newPath = path.substr(0, index); + } + + index = newPath.find_last_of("/\\"); + if (index == ccstd::string::npos) { + index = 0; + } else if (index > 0) { + ++index; + } + + return newPath.substr(0, index) + baseName + ext + tempStr; +} + +ccstd::string normalize(const ccstd::string &url) { + ccstd::string oldUrl = url; + ccstd::string newUrl = url; + + // remove all ../ + do { + oldUrl = newUrl; + size_t index = newUrl.find("../"); + if (index == ccstd::string::npos) { + index = newUrl.find("..\\"); + } + size_t previousSlashIndex = ccstd::string::npos; + size_t previousTwiceSlashIndex = ccstd::string::npos; + if (index != ccstd::string::npos && index > 0) { + previousSlashIndex = newUrl.find_last_of("/\\", index - 1); + if (previousSlashIndex != ccstd::string::npos) { + previousTwiceSlashIndex = newUrl.find_last_of("/\\", previousSlashIndex - 1); + } + } + + if (previousTwiceSlashIndex == ccstd::string::npos) { + if (previousSlashIndex != ccstd::string::npos) { + newUrl = newUrl.substr(index + strlen("../")); + } + } else if (previousSlashIndex != ccstd::string::npos) { + newUrl = newUrl.substr(0, previousTwiceSlashIndex) + '/' + newUrl.substr(index + strlen("../")); + } + } while (oldUrl.length() != newUrl.length()); + + return newUrl; +} + +ccstd::string stripSep(const ccstd::string &path) { + ccstd::string result = path; + removeLastSlash(result); + return result; +} + +char getSeperator() { +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) + return '\\'; +#else + return '/'; +#endif +} + +} // namespace cc diff --git a/cocos/core/utils/Path.h b/cocos/core/utils/Path.h new file mode 100644 index 0000000..450bb78 --- /dev/null +++ b/cocos/core/utils/Path.h @@ -0,0 +1,87 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +namespace cc { + +/** + * @en Join strings to be a path. + * @zh 拼接字符串为路径。 + * @example {@link cocos/core/utils/CCPath/join.js} + */ +ccstd::string join(const ccstd::vector &segments); + +/** + * @en Get the ext name of a path including '.', like '.png'. + * @zh 返回 Path 的扩展名,包括 '.',例如 '.png'。 + * @example {@link cocos/core/utils/CCPath/extname.js} + */ +ccstd::string extname(const ccstd::string &path); + +/** + * @en Get the main name of a file name. + * @zh 获取文件名的主名称。 + * @deprecated + */ +ccstd::string mainFileName(const ccstd::string &fileName); + +/** + * @en Get the file name of a file path. + * @zh 获取文件路径的文件名。 + * @example {@link cocos/core/utils/CCPath/basename.js} + */ +ccstd::string basename(const ccstd::string &path, const ccstd::string &extName = ""); + +/** + * @en Get dirname of a file path. + * @zh 获取文件路径的目录名。 + * @example {@link cocos/core/utils/CCPath/dirname.js} + */ +ccstd::string dirname(const ccstd::string &path); + +/** + * @en Change extname of a file path. + * @zh 更改文件路径的扩展名。 + * @example {@link cocos/core/utils/CCPath/changeExtname.js} + */ +ccstd::string changeExtname(const ccstd::string &path, const ccstd::string &extName = ""); + +/** + * @en Change file name of a file path. + * @zh 更改文件路径的文件名。 + * @example {@link cocos/core/utils/CCPath/changeBasename.js} + */ +ccstd::string changeBasename(const ccstd::string &path, const ccstd::string &baseName, bool isSameExt = false); + +ccstd::string normalize(const ccstd::string &url); + +ccstd::string stripSep(const ccstd::string &path); + +char getSeperator(); + +} // namespace cc diff --git a/cocos/core/utils/Pool.h b/cocos/core/utils/Pool.h new file mode 100644 index 0000000..119871f --- /dev/null +++ b/cocos/core/utils/Pool.h @@ -0,0 +1,184 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +/** + * @en + * A fixed-length object pool designed for general type.
+ * The implementation of this object pool is very simple, + * it can helps you to improve your game performance for objects which need frequent release and recreate operations
+ * @zh + * 长度固定的对象缓存池,可以用来缓存各种对象类型。
+ * 这个对象池的实现非常精简,它可以帮助您提高游戏性能,适用于优化对象的反复创建和销毁。 + * @class js.Pool + * @example + * ``` + * + * Example 1: + * + * function Details () { + * uuidList = []; + * }; + * Details.prototype.reset = function () { + * uuidList.length = 0; + * }; + * Details.pool = new js.Pool(function (obj) { + * obj.reset(); + * }, 5); + * Details.pool.get = function () { + * return _get() || new Details(); + * }; + * + * var detail = Details.pool.get(); + * ... + * Details.pool.put(detail); + * + * Example 2: + * + * function Details (buffer) { + * uuidList = buffer; + * }; + * ... + * Details.pool.get = function (buffer) { + * var cached = _get(); + * if (cached) { + * cached.uuidList = buffer; + * return cached; + * } + * else { + * return new Details(buffer); + * } + * }; + * + * var detail = Details.pool.get( [] ); + * ... + * ``` + */ + +#include +#include + +#include "base/Macros.h" + +template +class Pool { +public: + using CleanUpFunction = std::function; + + /** + * @en + * Get and initialize an object from pool. This method defaults to null and requires the user to implement it. + * @zh + * 获取并初始化对象池中的对象。这个方法默认为空,需要用户自己实现。 + * @param args - parameters to used to initialize the object + */ + std::function get{}; + + /** + * 使用构造函数来创建一个指定对象类型的对象池,您可以传递一个回调函数,用于处理对象回收时的清理逻辑。 + * @method constructor + * @param {Function} [cleanupFunc] - the callback method used to process the cleanup logic when the object is recycled. + * @param {Object} cleanupFunc.obj + * @param {Number} size - initializes the length of the array + */ + Pool(const CleanUpFunction &cleanup, int32_t size) { + _count = 0; + _pool.resize(size); + _cleanup = cleanup; + } + + /** + * 使用构造函数来创建一个指定对象类型的对象池,您可以传递一个回调函数,用于处理对象回收时的清理逻辑。 + * @method constructor + * @param {Function} [cleanupFunc] - the callback method used to process the cleanup logic when the object is recycled. + * @param {Object} cleanupFunc.obj + * @param {Number} size - initializes the length of the array + */ + explicit Pool(int32_t size) + : Pool{nullptr, size} {} + + ~Pool() = default; + + /** + * @en + * Get an object from pool, if no available object in the pool, null will be returned. + * @zh + * 获取对象池中的对象,如果对象池没有可用对象,则返回空。 + */ + T _get() { //NOLINT(readability-identifier-naming) + if (_count > 0) { + --_count; + T cache = _pool[_count]; + _pool[_count] = nullptr; + return cache; + } + return nullptr; + } + + /** + * @en Put an object into the pool. + * @zh 向对象池返还一个不再需要的对象。 + */ + void put(T &obj) { + if (_count < static_cast(_pool.size())) { + if (_cleanup != nullptr && !_cleanup(obj)) { + return; + } + _pool[_count] = obj; + ++_count; + } + } + + /** + * @en Resize the pool. + * @zh 设置对象池容量。 + */ + void resize(int32_t length) { + if (length >= 0) { + _pool.resize(length); + if (_count > length) { + _count = length; + } + } + } + + int32_t getCount() const { return _count; } + +private: + ccstd::vector _pool; + CleanUpFunction _cleanup{nullptr}; + + /** + * @en + * The current number of available objects, the default is 0, it will gradually increase with the recycle of the object, + * the maximum will not exceed the size specified when the constructor is called. + * @zh + * 当前可用对象数量,一开始默认是 0,随着对象的回收会逐渐增大,最大不会超过调用构造函数时指定的 size。 + * @default 0 + */ + int32_t _count{0}; + + CC_DISALLOW_COPY_MOVE_ASSIGN(Pool); +}; diff --git a/cocos/editor-support/IOBuffer.cpp b/cocos/editor-support/IOBuffer.cpp new file mode 100644 index 0000000..ca71361 --- /dev/null +++ b/cocos/editor-support/IOBuffer.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "IOBuffer.h" + +MIDDLEWARE_BEGIN + +void IOBuffer::resize(std::size_t newLen, bool needCopy) { + if (_bufferSize < newLen) { + auto *newBuffer = new uint8_t[newLen]; + memset(newBuffer, 0, newLen); + if (needCopy) { + memcpy(newBuffer, _buffer, _bufferSize); + } + + delete[] _buffer; + _buffer = newBuffer; + _bufferSize = newLen; + _outRange = false; + } +} + +MIDDLEWARE_END diff --git a/cocos/editor-support/IOBuffer.h b/cocos/editor-support/IOBuffer.h new file mode 100644 index 0000000..f30320c --- /dev/null +++ b/cocos/editor-support/IOBuffer.h @@ -0,0 +1,237 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "MiddlewareMacro.h" +#include "base/Macros.h" + +MIDDLEWARE_BEGIN +/** + * Use for such as vertex buffer or index buffer,write bytes in it. + * If write data out of range,will sign it in _outRange property. + * User decide to enlarge it or not. + */ +class IOBuffer { +public: + explicit IOBuffer(std::size_t defaultSize) { + _bufferSize = defaultSize; + _buffer = new uint8_t[_bufferSize]; + memset(_buffer, 0, _bufferSize); + } + + IOBuffer() = default; + + virtual ~IOBuffer() { + if (_buffer) { + delete[] _buffer; + _buffer = nullptr; + } + } + + inline void writeUint32(std::size_t pos, uint32_t val) { + if (_bufferSize < pos + sizeof(val)) { + _outRange = true; + return; + } + auto *buffer = reinterpret_cast(_buffer + pos); + *buffer = val; + } + + inline void writeFloat32(std::size_t pos, float val) { + if (_bufferSize < pos + sizeof(val)) { + _outRange = true; + return; + } + auto *buffer = reinterpret_cast(_buffer + pos); + *buffer = val; + } + + inline void writeBytes(const char *bytes, std::size_t bytesLen) { + if (_bufferSize < _curPos + bytesLen) { + _outRange = true; + return; + } + memcpy(_buffer + _curPos, bytes, bytesLen); + _curPos += bytesLen; + } + + inline void writeUint32(uint32_t val) { + if (_bufferSize < _curPos + sizeof(val)) { + _outRange = true; + return; + } + auto *buffer = reinterpret_cast(_buffer + _curPos); + *buffer = val; + _curPos += sizeof(val); + } + + inline void writeFloat32(float val) { + if (_bufferSize < _curPos + sizeof(val)) { + _outRange = true; + return; + } + auto *buffer = reinterpret_cast(_buffer + _curPos); + *buffer = val; + _curPos += sizeof(val); + } + + inline void writeUint16(uint16_t val) { + if (_bufferSize < _curPos + sizeof(val)) { + _outRange = true; + return; + } + auto *buffer = reinterpret_cast(_buffer + _curPos); + *buffer = val; + _curPos += sizeof(val); + } + + inline uint32_t readUint32() { + auto *buffer = reinterpret_cast(_buffer + _readPos); + _readPos += sizeof(uint32_t); + return *buffer; + } + + inline uint16_t readUint16() { + auto *buffer = reinterpret_cast(_buffer + _readPos); + _readPos += sizeof(uint16_t); + return *buffer; + } + + inline float readFloat32() { + auto *buffer = reinterpret_cast(_buffer + _readPos); + _readPos += sizeof(float); + return *buffer; + } + + inline char readUint8() { + char *buffer = reinterpret_cast(_buffer + _readPos); + _readPos += sizeof(char); + return *buffer; + } + + inline void reset() { + _curPos = 0; + _readPos = 0; + } + + inline void clear() { + memset(_buffer, 0, _bufferSize); + } + + inline void move(int pos) { + if (_bufferSize < _curPos + pos) { + _outRange = true; + return; + } + _curPos += pos; + } + + inline std::size_t length() const { + return _curPos; + } + + inline std::size_t getCurPos() const { + return _curPos; + } + + inline uint8_t *getCurBuffer() const { + return _buffer + _curPos; + } + + inline uint8_t *getBuffer() const { + return _buffer; + } + + inline std::size_t getCapacity() const { + return _bufferSize; + } + + inline bool isOutRange() const { + return _outRange; + } + + inline int checkSpace(std::size_t needSize, bool needCopy = false) { + auto needLen = _curPos + needSize; + auto isFull = 0; + if (_maxSize > 0 && needLen > _maxSize) { + isFull = 1; + if (_fullCallback) { + _fullCallback(); + } + } + if (!needCopy) { + _curPos = 0; + } + if (_bufferSize < needLen) { + int len = static_cast(needLen); + std::size_t fitSize = static_cast(floorf(1.25F * len)); + resize(fitSize, needCopy); + if (_resizeCallback) { + _resizeCallback(); + } + } + + return isFull; + } + + void setMaxSize(std::size_t maxSize) { + _maxSize = maxSize; + } + + using fullCallback = std::function; + void setFullCallback(fullCallback callback) { + _fullCallback = std::move(callback); + } + + using resizeCallback = std::function; + void setResizeCallback(resizeCallback callback) { + _resizeCallback = std::move(callback); + } + + /** + * @brief Resize buffer + * @param[in] newLen New size you want to adjustment. + * @param[in] needCopy If true,will copy old data to new buffer,default false. + */ + virtual void resize(std::size_t newLen, bool needCopy); + +protected: + uint8_t *_buffer = nullptr; + std::size_t _bufferSize = 0; + std::size_t _curPos = 0; + std::size_t _readPos = 0; + bool _outRange = false; + std::size_t _maxSize = 0; + fullCallback _fullCallback = nullptr; + resizeCallback _resizeCallback = nullptr; +}; + +MIDDLEWARE_END diff --git a/cocos/editor-support/IOTypedArray.cpp b/cocos/editor-support/IOTypedArray.cpp new file mode 100644 index 0000000..0cef50a --- /dev/null +++ b/cocos/editor-support/IOTypedArray.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "IOTypedArray.h" +#include "TypedArrayPool.h" + +#define USE_TYPEARRAY_POOL 0 + +MIDDLEWARE_BEGIN + +IOTypedArray::IOTypedArray(se::Object::TypedArrayType arrayType, std::size_t defaultSize, bool usePool) { + _arrayType = arrayType; + _bufferSize = defaultSize; + _usePool = usePool; + + if (_usePool) { + _typeArray = TypedArrayPool::getInstance()->pop(_arrayType, _bufferSize); + } else { + se::AutoHandleScope hs; + _typeArray = se::Object::createTypedArray(_arrayType, nullptr, _bufferSize); + _typeArray->root(); + } + + se::AutoHandleScope hs; + _typeArray->getTypedArrayData(&_buffer, &_bufferSize); +} + +IOTypedArray::~IOTypedArray() { + if (_usePool) { + TypedArrayPool::getInstance()->push(_arrayType, _bufferSize, _typeArray); + } else { + _typeArray->unroot(); + _typeArray->decRef(); + } + _typeArray = nullptr; + _buffer = nullptr; +} + +void IOTypedArray::resize(std::size_t newLen, bool needCopy) { + if (_bufferSize >= newLen) return; + + se::Object *newTypeBuffer = nullptr; + + if (_usePool) { + newTypeBuffer = TypedArrayPool::getInstance()->pop(_arrayType, newLen); + } else { + se::AutoHandleScope hs; + newTypeBuffer = se::Object::createTypedArray(_arrayType, nullptr, newLen); + newTypeBuffer->root(); + } + + uint8_t *newBuffer = nullptr; + se::AutoHandleScope hs; + newTypeBuffer->getTypedArrayData(&newBuffer, static_cast(&newLen)); + + if (needCopy) { + memcpy(newBuffer, _buffer, _bufferSize); + } + + if (_usePool) { + TypedArrayPool::getInstance()->push(_arrayType, _bufferSize, _typeArray); + } else { + _typeArray->unroot(); + _typeArray->decRef(); + } + + _typeArray = newTypeBuffer; + _buffer = newBuffer; + _bufferSize = newLen; + _outRange = false; +} + +MIDDLEWARE_END diff --git a/cocos/editor-support/IOTypedArray.h b/cocos/editor-support/IOTypedArray.h new file mode 100644 index 0000000..9566ce3 --- /dev/null +++ b/cocos/editor-support/IOTypedArray.h @@ -0,0 +1,59 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "IOBuffer.h" +#include "MiddlewareMacro.h" +#include "SeApi.h" +#include "base/Macros.h" + +MIDDLEWARE_BEGIN +/** + * Inherit from IOBuffer. + */ +class IOTypedArray : public IOBuffer { +public: + /** + * @brief constructor + * @param[in] arrayType TypeArray type + * @param[in] defaultSize TypeArray capacity + * @param[in] usePool If true,will get TypeArray from pool,or create TypeArray,default false. + */ + IOTypedArray(se::Object::TypedArrayType arrayType, std::size_t defaultSize, bool usePool = false); + ~IOTypedArray() override; + + inline se::Object *getTypeArray() const { + return _typeArray; + } + + void resize(std::size_t newLen, bool needCopy) override; + +private: + se::Object::TypedArrayType _arrayType = se::Object::TypedArrayType::NONE; + se::Object *_typeArray = nullptr; + bool _usePool = false; +}; + +MIDDLEWARE_END diff --git a/cocos/editor-support/MeshBuffer.cpp b/cocos/editor-support/MeshBuffer.cpp new file mode 100644 index 0000000..c931d67 --- /dev/null +++ b/cocos/editor-support/MeshBuffer.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "MeshBuffer.h" +#include "2d/renderer/UIMeshBuffer.h" + +MIDDLEWARE_BEGIN + +static const ccstd::vector ATTRIBUTES_V3F_T2F_C4B{ + gfx::Attribute{gfx::ATTR_NAME_POSITION, gfx::Format::RGB32F}, + gfx::Attribute{gfx::ATTR_NAME_TEX_COORD, gfx::Format::RG32F}, + gfx::Attribute{gfx::ATTR_NAME_COLOR, gfx::Format::RGBA8, true}, +}; + +static const ccstd::vector ATTRIBUTES_V3F_T2F_C4B_C4B{ + gfx::Attribute{gfx::ATTR_NAME_POSITION, gfx::Format::RGB32F}, + gfx::Attribute{gfx::ATTR_NAME_TEX_COORD, gfx::Format::RG32F}, + gfx::Attribute{gfx::ATTR_NAME_COLOR, gfx::Format::RGBA8, true}, + gfx::Attribute{gfx::ATTR_NAME_COLOR2, gfx::Format::RGBA8, true}, +}; + +MeshBuffer::MeshBuffer(int vertexFormat) +: MeshBuffer(vertexFormat, INIT_INDEX_BUFFER_SIZE, MAX_VERTEX_BUFFER_SIZE) { +} + +MeshBuffer::MeshBuffer(int vertexFormat, size_t indexSize, size_t vertexSize) +: _vertexFormat(vertexFormat), _ib(indexSize), _vb(vertexSize * vertexFormat * sizeof(float)) { + _vb.setMaxSize(MAX_VERTEX_BUFFER_SIZE * _vertexFormat * sizeof(float)); + _ib.setMaxSize(100 * INIT_INDEX_BUFFER_SIZE); + _vb.setFullCallback([this] { + uploadVB(); + uploadIB(); + _vb.reset(); + _ib.reset(); + next(); + }); + + init(); +} + +MeshBuffer::~MeshBuffer() { + clear(); +} + +void MeshBuffer::clear() { + size_t num = _vbArr.size(); + for (size_t i = 0; i < num; i++) { + delete _ibArr[i]; + delete _vbArr[i]; + } + _ibArr.clear(); + _vbArr.clear(); + cleanUIMeshBuffer(); +} + +void MeshBuffer::init() { + auto *rIB = new IOTypedArray(se::Object::TypedArrayType::UINT16, _ib.getCapacity()); + _ibArr.push_back(rIB); + + auto *rVB = new IOTypedArray(se::Object::TypedArrayType::FLOAT32, _vb.getCapacity()); + _vbArr.push_back(rVB); + cleanUIMeshBuffer(); + addUIMeshBuffer(); +} + +void MeshBuffer::uploadVB() { + auto length = _vb.length(); + if (length == 0) return; + + auto *rVB = _vbArr[_bufferPos]; + rVB->reset(); + rVB->writeBytes(reinterpret_cast(_vb.getBuffer()), _vb.length()); + auto *uiMeshBuffer = _uiMeshBufferArr[_bufferPos]; + uiMeshBuffer->setVData(reinterpret_cast(rVB->getBuffer())); + uiMeshBuffer->setByteOffset(static_cast(_vb.length())); +} + +void MeshBuffer::uploadIB() { + auto length = _ib.length(); + if (length == 0) return; + + auto *rIB = _ibArr[_bufferPos]; + rIB->reset(); + if (rIB->getCapacity() < length) { + rIB->resize(length, false); + } + rIB->writeBytes(reinterpret_cast(_ib.getBuffer()), _ib.length()); + auto *uiMeshBuffer = _uiMeshBufferArr[_bufferPos]; + uiMeshBuffer->setIData(reinterpret_cast(rIB->getBuffer())); +} + +void MeshBuffer::next() { + _bufferPos++; + if (_ibArr.size() <= _bufferPos) { + auto *rIB = new IOTypedArray(se::Object::TypedArrayType::UINT16, _ib.getCapacity()); + _ibArr.push_back(rIB); + } + + if (_vbArr.size() <= _bufferPos) { + auto *rVB = new IOTypedArray(se::Object::TypedArrayType::FLOAT32, _vb.getCapacity()); + _vbArr.push_back(rVB); + } + + if (_uiMeshBufferArr.size() <= _bufferPos) { + addUIMeshBuffer(); + } +} + +void MeshBuffer::reset() { + _bufferPos = 0; + _vb.reset(); + _ib.reset(); +} + +void MeshBuffer::addUIMeshBuffer() { + UIMeshBuffer *uiMeshBuffer = new UIMeshBuffer(); + ccstd::vector attrs; + if (_vertexFormat == VF_XYZUVC) { + attrs = ATTRIBUTES_V3F_T2F_C4B; + } else { + attrs = ATTRIBUTES_V3F_T2F_C4B_C4B; + } + uiMeshBuffer->initialize(std::move(attrs), true); + _uiMeshBufferArr.push_back(uiMeshBuffer); +} + +void MeshBuffer::cleanUIMeshBuffer() { + for (auto *buf : _uiMeshBufferArr) { + delete buf; + } + _uiMeshBufferArr.clear(); +} + +cc::UIMeshBuffer *MeshBuffer::getUIMeshBuffer() const { + return _uiMeshBufferArr[_bufferPos]; +} + +const ccstd::vector &MeshBuffer::uiMeshBuffers() const { + return _uiMeshBufferArr; +} + +MIDDLEWARE_END diff --git a/cocos/editor-support/MeshBuffer.h b/cocos/editor-support/MeshBuffer.h new file mode 100644 index 0000000..a49eea5 --- /dev/null +++ b/cocos/editor-support/MeshBuffer.h @@ -0,0 +1,115 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "IOBuffer.h" +#include "IOTypedArray.h" +#include "base/std/container/vector.h" + +namespace cc { +class UIMeshBuffer; +}; + +MIDDLEWARE_BEGIN + +class MeshBuffer { +public: + explicit MeshBuffer(int vertexFormat); + MeshBuffer(int vertexFormat, size_t indexSize, size_t vertexSize); + + virtual ~MeshBuffer(); + + se_object_ptr getVBTypedArray(std::size_t bufferPos) { + if (_vbArr.size() <= bufferPos) return nullptr; + return _vbArr[bufferPos]->getTypeArray(); + } + + se_object_ptr getIBTypedArray(std::size_t bufferPos) { + if (_ibArr.size() <= bufferPos) return nullptr; + return _ibArr[bufferPos]->getTypeArray(); + } + + uint8_t *getVBFromBufferArray(std::size_t index) { + if (_vbArr.size() <= index) return nullptr; + return _vbArr[index]->getBuffer(); + } + + uint8_t *getIBFromBufferArray(std::size_t index) { + if (_ibArr.size() <= index) return nullptr; + return _ibArr[index]->getBuffer(); + } + + std::size_t getVBTypedArrayLength(std::size_t bufferPos) { + if (_vbArr.size() <= bufferPos) return 0; + return _vbArr[bufferPos]->length(); + } + + std::size_t getIBTypedArrayLength(std::size_t bufferPos) { + if (_ibArr.size() <= bufferPos) return 0; + return _ibArr[bufferPos]->length(); + } + + std::size_t getBufferCount() const { + return _bufferPos + 1; + } + + uint16_t getBufferPos() const { + return _bufferPos; + } + + IOBuffer &getVB() { + return _vb; + } + + IOBuffer &getIB() { + return _ib; + } + + cc::UIMeshBuffer *getUIMeshBuffer() const; + + const ccstd::vector &uiMeshBuffers() const; + + void uploadVB(); + void uploadIB(); + void reset(); + +private: + void next(); + void clear(); + void init(); + void afterCleanupHandle(); + void addUIMeshBuffer(); + void cleanUIMeshBuffer(); + + ccstd::vector _ibArr; + ccstd::vector _vbArr; + ccstd::vector _uiMeshBufferArr; + uint16_t _bufferPos = 0; + IOBuffer _vb; + IOBuffer _ib; + int _vertexFormat = 0; +}; + +MIDDLEWARE_END diff --git a/cocos/editor-support/MiddlewareMacro.h b/cocos/editor-support/MiddlewareMacro.h new file mode 100644 index 0000000..d9b6ed1 --- /dev/null +++ b/cocos/editor-support/MiddlewareMacro.h @@ -0,0 +1,56 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +// index buffer init capacity. IB_SCALE = 4 +#define INIT_INDEX_BUFFER_SIZE 524280 +// max vertex buffer size +#define MAX_VERTEX_BUFFER_SIZE 65535 +// render info int capacity +#define INIT_RENDER_INFO_BUFFER_SIZE 1024000 + +// fill debug data max capacity +#define MAX_DEBUG_BUFFER_SIZE 409600 +// type array pool min size +#define MIN_TYPE_ARRAY_SIZE 1024 + +#ifndef MIDDLEWARE_BEGIN + #define MIDDLEWARE_BEGIN \ + namespace cc { \ + namespace middleware { +#endif // MIDDLEWARE_BEGIN + +#ifndef MIDDLEWARE_END + #define MIDDLEWARE_END \ + } \ + } +#endif // MIDDLEWARE_END + +#ifndef USING_NS_MW + #define USING_NS_MW using namespace cc::middleware +#endif + +// R G B A �ֱ�ռ��32λ +#define VF_XYZUVC 6 +#define VF_XYZUVCC 7 diff --git a/cocos/editor-support/MiddlewareManager.cpp b/cocos/editor-support/MiddlewareManager.cpp new file mode 100644 index 0000000..56b205d --- /dev/null +++ b/cocos/editor-support/MiddlewareManager.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "MiddlewareManager.h" +#include +#include "2d/renderer/Batcher2d.h" +#include "SeApi.h" +#include "core/Root.h" + +MIDDLEWARE_BEGIN + +MiddlewareManager *MiddlewareManager::instance = nullptr; + +MiddlewareManager::MiddlewareManager() : _renderInfo(se::Object::TypedArrayType::UINT32), + _attachInfo(se::Object::TypedArrayType::FLOAT32) { +} + +MiddlewareManager::~MiddlewareManager() { + for (auto it : _mbMap) { + auto *buffer = it.second; + + delete buffer; + } + _mbMap.clear(); +} + +MeshBuffer *MiddlewareManager::getMeshBuffer(int format) { + MeshBuffer *mb = _mbMap[format]; + if (!mb) { + mb = new MeshBuffer(format); + _mbMap[format] = mb; + } + return mb; +} + +void MiddlewareManager::clearRemoveList() { + for (auto *editor : _removeList) { + auto it = std::find(_updateList.begin(), _updateList.end(), editor); + if (it != _updateList.end()) { + _updateList.erase(it); + } + } + + _removeList.clear(); +} + +void MiddlewareManager::update(float dt) { + isUpdating = true; + + _attachInfo.reset(); + auto *attachBuffer = _attachInfo.getBuffer(); + if (attachBuffer) { + attachBuffer->writeUint32(0); + } + + for (auto *editor : _updateList) { + if (!_removeList.empty()) { + auto removeIt = std::find(_removeList.begin(), _removeList.end(), editor); + if (removeIt == _removeList.end()) { + if (editor) { + editor->update(dt); + } + } + } else { + if (editor) { + editor->update(dt); + } + } + } + + isUpdating = false; + + clearRemoveList(); +} + +void MiddlewareManager::render(float dt) { + for (auto it : _mbMap) { + auto *buffer = it.second; + if (buffer) { + buffer->reset(); + } + } + + isRendering = true; + + for (auto *editor : _updateList) { + if (!_removeList.empty()) { + auto removeIt = std::find(_removeList.begin(), _removeList.end(), editor); + if (removeIt == _removeList.end()) { + editor->render(dt); + } + } else { + editor->render(dt); + } + } + + isRendering = false; + + for (auto it : _mbMap) { + auto *buffer = it.second; + if (buffer) { + buffer->uploadIB(); + buffer->uploadVB(); + } + + uint16_t accID = 65534; + auto *batch2d = Root::getInstance()->getBatcher2D(); + if (it.first == VF_XYZUVCC) { + accID = 65535; + } + ccstd::vector uiMeshArray; + auto &uiBufArray = buffer->uiMeshBuffers(); + for (auto &item : uiBufArray) { + uiMeshArray.push_back((UIMeshBuffer *)item); + } + batch2d->syncMeshBuffersToNative(accID, std::move(uiMeshArray)); + } + + clearRemoveList(); +} + +void MiddlewareManager::addTimer(IMiddleware *editor) { + auto it0 = std::find(_updateList.begin(), _updateList.end(), editor); + if (it0 != _updateList.end()) { + return; + } + + auto it1 = std::find(_removeList.begin(), _removeList.end(), editor); + if (it1 != _removeList.end()) { + _removeList.erase(it1); + } + _updateList.push_back(editor); +} + +void MiddlewareManager::removeTimer(IMiddleware *editor) { + if (isUpdating || isRendering) { + _removeList.push_back(editor); + } else { + auto it = std::find(_updateList.begin(), _updateList.end(), editor); + if (it != _updateList.end()) { + _updateList.erase(it); + } + } +} + +se_object_ptr MiddlewareManager::getVBTypedArray(int format, int bufferPos) { + MeshBuffer *mb = _mbMap[format]; + if (!mb) return nullptr; + return mb->getVBTypedArray(bufferPos); +} + +se_object_ptr MiddlewareManager::getIBTypedArray(int format, int bufferPos) { + MeshBuffer *mb = _mbMap[format]; + if (!mb) return nullptr; + return mb->getIBTypedArray(bufferPos); +} + +SharedBufferManager *MiddlewareManager::getRenderInfoMgr() { + return &_renderInfo; +} + +SharedBufferManager *MiddlewareManager::getAttachInfoMgr() { + return &_attachInfo; +} + +std::size_t MiddlewareManager::getVBTypedArrayLength(int format, std::size_t bufferPos) { + MeshBuffer *mb = _mbMap[format]; + if (!mb) return 0; + return mb->getVBTypedArrayLength(bufferPos); +} + +std::size_t MiddlewareManager::getIBTypedArrayLength(int format, std::size_t bufferPos) { + MeshBuffer *mb = _mbMap[format]; + if (!mb) return 0; + return mb->getIBTypedArrayLength(bufferPos); +} + +std::size_t MiddlewareManager::getBufferCount(int format) { + MeshBuffer *mb = getMeshBuffer(format); + if (!mb) return 0; + return mb->getBufferCount(); +} + +MIDDLEWARE_END diff --git a/cocos/editor-support/MiddlewareManager.h b/cocos/editor-support/MiddlewareManager.h new file mode 100644 index 0000000..9c326db --- /dev/null +++ b/cocos/editor-support/MiddlewareManager.h @@ -0,0 +1,127 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "MeshBuffer.h" +#include "MiddlewareMacro.h" +#include "SharedBufferManager.h" +#include "base/RefCounted.h" + +MIDDLEWARE_BEGIN + +/** + * All middleware must implement IMiddleware interface. + */ +class IMiddleware { +public: + IMiddleware() = default; + virtual ~IMiddleware() = default; + virtual void update(float dt) = 0; + virtual void render(float dt) = 0; +}; + +/** + * Update all middleware,and fill vertex and index into buffer, + * and then upload vertex buffer,index buffer to opengl. + */ +class MiddlewareManager { +public: + static MiddlewareManager *getInstance() { + if (instance == nullptr) { + instance = new MiddlewareManager; + } + + return instance; + } + + static void destroyInstance() { + if (instance) { + delete instance; + instance = nullptr; + } + } + + static uint8_t generateModuleID() { + static uint8_t uniqueId = 0; + uniqueId++; + return uniqueId; + } + + /** + * @brief update all elements + * @param[in] dt Delta time. + */ + void update(float dt); + + /** + * @brief render all elements + */ + void render(float dt); + + /** + * @brief Third party module add in _updateMap,it will update perframe. + * @param[in] editor Module must implement IMiddleware interface. + */ + void addTimer(IMiddleware *editor); + + /** + * @brief Third party module remove from _updateMap,it will stop update. + * @param[in] editor Module must implement IMiddleware interface. + */ + void removeTimer(IMiddleware *editor); + + MeshBuffer *getMeshBuffer(int format); + + se_object_ptr getVBTypedArray(int format, int bufferPos); + se_object_ptr getIBTypedArray(int format, int bufferPos); + std::size_t getBufferCount(int format); + std::size_t getVBTypedArrayLength(int format, std::size_t bufferPos); + std::size_t getIBTypedArrayLength(int format, std::size_t bufferPos); + + SharedBufferManager *getRenderInfoMgr(); + SharedBufferManager *getAttachInfoMgr(); + + MiddlewareManager(); + ~MiddlewareManager(); + + // If manager is traversing _updateMap, will set the flag untill traverse is finished. + bool isRendering = false; + bool isUpdating = false; + +private: + void clearRemoveList(); + + ccstd::vector _updateList; + ccstd::vector _removeList; + std::map _mbMap; + + SharedBufferManager _renderInfo; + SharedBufferManager _attachInfo; + + static MiddlewareManager *instance; +}; +MIDDLEWARE_END diff --git a/cocos/editor-support/SharedBufferManager.cpp b/cocos/editor-support/SharedBufferManager.cpp new file mode 100644 index 0000000..b80f745 --- /dev/null +++ b/cocos/editor-support/SharedBufferManager.cpp @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "SharedBufferManager.h" +#include "base/memory/Memory.h" + +MIDDLEWARE_BEGIN + +SharedBufferManager::SharedBufferManager(se::Object::TypedArrayType arrayType) : _arrayType(arrayType) { + init(); +} + +SharedBufferManager::~SharedBufferManager() { + CC_SAFE_DELETE(_buffer); +} + +void SharedBufferManager::init() { + if (!_buffer) { + _buffer = new IOTypedArray(_arrayType, INIT_RENDER_INFO_BUFFER_SIZE); + _buffer->setResizeCallback([this] { + if (_resizeCallback) { + _resizeCallback(); + } + }); + } +} + +MIDDLEWARE_END diff --git a/cocos/editor-support/SharedBufferManager.h b/cocos/editor-support/SharedBufferManager.h new file mode 100644 index 0000000..931a3e0 --- /dev/null +++ b/cocos/editor-support/SharedBufferManager.h @@ -0,0 +1,64 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "IOTypedArray.h" + +MIDDLEWARE_BEGIN + +class SharedBufferManager { +public: + explicit SharedBufferManager(se::Object::TypedArrayType arrayType); + virtual ~SharedBufferManager(); + + void reset() { + _buffer->reset(); + } + + IOTypedArray *getBuffer() { + return _buffer; + } + + using resizeCallback = std::function; + void setResizeCallback(resizeCallback callback) { + _resizeCallback = std::move(callback); + } + + se_object_ptr getSharedBuffer() const { + return _buffer->getTypeArray(); + } + +private: + void init(); + void afterCleanupHandle(); + + se::Object::TypedArrayType _arrayType; + IOTypedArray *_buffer = nullptr; + resizeCallback _resizeCallback = nullptr; +}; + +MIDDLEWARE_END diff --git a/cocos/editor-support/TypedArrayPool.cpp b/cocos/editor-support/TypedArrayPool.cpp new file mode 100644 index 0000000..94c75d5 --- /dev/null +++ b/cocos/editor-support/TypedArrayPool.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "TypedArrayPool.h" +#include +#include +#include "MiddlewareMacro.h" +#include "base/Log.h" +#include "base/Macros.h" +#include "cocos/bindings/event/EventDispatcher.h" + +#define POOL_DEBUG 0 + +#if POOL_DEBUG > 0 + #define PoolLog(...) \ + do { \ + fprintf(stdout, __VA_ARGS__); \ + fflush(stdout); \ + } while (false) +#else + #define POOL_LOG(...) +#endif + +MIDDLEWARE_BEGIN + +const static std::size_t MAX_POOL_SIZE = 50; + +TypedArrayPool *TypedArrayPool::instance = nullptr; + +TypedArrayPool::TypedArrayPool() { + _closeListener.bind([this]() { + clearPool(); + }); +} + +TypedArrayPool::~TypedArrayPool() { + clearPool(); +} + +void TypedArrayPool::clearPool() { + POOL_LOG("*****clearPool TypeArray pool begin"); + + //map + for (auto &it : _pool) { + //map + fitMap &mapPool = *(it.second); + for (auto &itMapPool : mapPool) { + //vector + objPool &itFitPool = *(itMapPool.second); + POOL_LOG("clear arrayType:%d,fitSize:%lu,objSize:%lu\n", it->first, itMapPool->first, itFitPool.size()); + for (auto &itFit : itFitPool) { + itFit->unroot(); + itFit->decRef(); + } + delete &itFitPool; + } + delete &mapPool; + } + _pool.clear(); + + POOL_LOG("*****clearPool TypeArray pool end"); +} + +void TypedArrayPool::dump() { + //map + for (auto &it : _pool) { + //map + fitMap &mapPool = *(it.second); + for (auto &itMapPool : mapPool) { + //vector + CC_UNUSED objPool &itFitPool = *(itMapPool.second); + POOL_LOG("arrayType:%d,fitSize:%lu,objSize:%lu\n", it->first, itMapPool->first, itFitPool.size()); + } + } +} + +se::Object *TypedArrayPool::pop(arrayType type, std::size_t size) { + auto fitSize = static_cast(std::ceil(static_cast(size) / float(MIN_TYPE_ARRAY_SIZE)) * MIN_TYPE_ARRAY_SIZE); + objPool *objPoolPtr = getObjPool(type, fitSize); + + if (!objPoolPtr->empty()) { + se::Object *obj = objPoolPtr->back(); + objPoolPtr->pop_back(); + POOL_LOG("TypedArrayPool:pop result:success,type:%d,fitSize:%lu,objSize:%lu\n", (int)type, fitSize, objPoolPtr->size()); + return obj; + } + + POOL_LOG("TypedArrayPool:pop result:empty,type:%d,fitSize:%lu,objSize:%lu\n", (int)type, fitSize, objPoolPtr->size()); + se::AutoHandleScope hs; + auto *typeArray = se::Object::createTypedArray(type, nullptr, fitSize); + typeArray->root(); + return typeArray; +} + +TypedArrayPool::objPool *TypedArrayPool::getObjPool(arrayType type, std::size_t fitSize) { + auto it = _pool.find(type); + fitMap *fitMapPtr = nullptr; + if (it == _pool.end()) { + fitMapPtr = new fitMap(); + _pool[type] = fitMapPtr; + } else { + fitMapPtr = it->second; + } + + auto itPool = fitMapPtr->find(fitSize); + objPool *objPoolPtr = nullptr; + if (itPool == fitMapPtr->end()) { + objPoolPtr = new objPool(); + (*fitMapPtr)[fitSize] = objPoolPtr; + } else { + objPoolPtr = itPool->second; + } + + return objPoolPtr; +} + +void TypedArrayPool::push(arrayType type, std::size_t arrayCapacity, se::Object *object) { + if (object == nullptr) return; + + // If script engine is cleaning,delete object directly + if (!_allowPush) { + object->unroot(); + object->decRef(); + object = nullptr; + POOL_LOG("TypedArrayPool:push result:not allow,type:%d,arrayCapacity:%lu\n", (int)type, arrayCapacity); + return; + } + + objPool *objPoolPtr = getObjPool(type, arrayCapacity); + auto it = std::find(objPoolPtr->begin(), objPoolPtr->end(), object); + if (it != objPoolPtr->end()) { + POOL_LOG("TypedArrayPool:push result:repeat\n"); + return; + } + + if (objPoolPtr->size() < MAX_POOL_SIZE) { + objPoolPtr->push_back(object); + POOL_LOG("TypedArrayPool:push result:success,type:%d,arrayCapacity:%lu,objSize:%lu\n", (int)type, arrayCapacity, objPoolPtr->size()); + } else { + object->unroot(); + object->decRef(); + object = nullptr; + } +} + +MIDDLEWARE_END diff --git a/cocos/editor-support/TypedArrayPool.h b/cocos/editor-support/TypedArrayPool.h new file mode 100644 index 0000000..903853c --- /dev/null +++ b/cocos/editor-support/TypedArrayPool.h @@ -0,0 +1,96 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include +#include +#include "MiddlewareMacro.h" +#include "SeApi.h" +#include "engine/EngineEvents.h" + +MIDDLEWARE_BEGIN +/** + * TypeArray Pool for IOTypedArray + */ +class TypedArrayPool { +private: + static TypedArrayPool *instance; + +public: + static TypedArrayPool *getInstance() { + if (instance == nullptr) { + instance = new TypedArrayPool(); + } + return instance; + } + + static void destroyInstance() { + if (instance) { + delete instance; + instance = nullptr; + } + } + +private: + using arrayType = se::Object::TypedArrayType; + using objPool = ccstd::vector; + using fitMap = std::map; + using typeMap = std::map; + + objPool *getObjPool(arrayType type, std::size_t size); + + TypedArrayPool(); + ~TypedArrayPool(); + + void clearPool(); + /** + * @brief Print all pool data + */ + void dump(); + + void afterCleanupHandle(); + void afterInitHandle(); + + typeMap _pool; + bool _allowPush = true; + + cc::events::Close::Listener _closeListener; + +public: + /** + * @brief pop a js TypeArray by given type and size + * @param[in] type TypeArray type. + * @param[in] size. + * @return a js TypeArray Object. + */ + se::Object *pop(arrayType type, std::size_t size); + /** + * @brief push a TypeArray back to pool. + * @param[in] type TypeArray type. + * @param[in] arrayCapacity TypeArray capacity. + * @param[in] object TypeArray which want to put in pool. + */ + void push(arrayType type, std::size_t arrayCapacity, se::Object *object); +}; +MIDDLEWARE_END diff --git a/cocos/editor-support/dragonbones-creator-support/ArmatureCache.cpp b/cocos/editor-support/dragonbones-creator-support/ArmatureCache.cpp new file mode 100644 index 0000000..1c040ec --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/ArmatureCache.cpp @@ -0,0 +1,419 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2020 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "ArmatureCache.h" +#include "CCFactory.h" +#include "base/TypeDef.h" +#include "base/memory/Memory.h" + +USING_NS_MW; // NOLINT(google-build-using-namespace) + +using namespace cc; // NOLINT(google-build-using-namespace) + +DRAGONBONES_NAMESPACE_BEGIN + +float ArmatureCache::FrameTime = 1.0F / 60.0F; +float ArmatureCache::MaxCacheTime = 120.0F; + +ArmatureCache::SegmentData::SegmentData() = default; + +ArmatureCache::SegmentData::~SegmentData() { + CC_SAFE_RELEASE_NULL(_texture); +} + +void ArmatureCache::SegmentData::setTexture(cc::middleware::Texture2D *value) { + CC_SAFE_ADD_REF(value); + CC_SAFE_RELEASE(_texture); + _texture = value; +} + +cc::middleware::Texture2D *ArmatureCache::SegmentData::getTexture() const { + return _texture; +} + +ArmatureCache::FrameData::FrameData() = default; + +ArmatureCache::FrameData::~FrameData() { + for (auto &bone : _bones) { + delete bone; + } + _bones.clear(); + + for (auto &color : _colors) { + delete color; + } + _colors.clear(); + + for (auto &segment : _segments) { + delete segment; + } + _segments.clear(); +} + +ArmatureCache::BoneData *ArmatureCache::FrameData::buildBoneData(std::size_t index) { + if (index > _bones.size()) return nullptr; + if (index == _bones.size()) { + auto *boneData = new BoneData; + _bones.push_back(boneData); + } + return _bones[index]; +} + +std::size_t ArmatureCache::FrameData::getBoneCount() const { + return _bones.size(); +} + +ArmatureCache::ColorData *ArmatureCache::FrameData::buildColorData(std::size_t index) { + if (index > _colors.size()) return nullptr; + if (index == _colors.size()) { + auto *colorData = new ColorData; + _colors.push_back(colorData); + } + return _colors[index]; +} + +std::size_t ArmatureCache::FrameData::getColorCount() const { + return _colors.size(); +} + +ArmatureCache::SegmentData *ArmatureCache::FrameData::buildSegmentData(std::size_t index) { + if (index > _segments.size()) return nullptr; + if (index == _segments.size()) { + auto *segmentData = new SegmentData; + _segments.push_back(segmentData); + } + return _segments[index]; +} + +std::size_t ArmatureCache::FrameData::getSegmentCount() const { + return _segments.size(); +} + +ArmatureCache::AnimationData::AnimationData() = default; + +ArmatureCache::AnimationData::~AnimationData() { + reset(); +} + +void ArmatureCache::AnimationData::reset() { + for (auto &frame : _frames) { + delete frame; + } + _frames.clear(); + _isComplete = false; + _totalTime = 0.0F; +} + +bool ArmatureCache::AnimationData::needUpdate(int toFrameIdx) const { + return !_isComplete && _totalTime <= MaxCacheTime && (toFrameIdx == -1 || _frames.size() < toFrameIdx + 1); +} + +ArmatureCache::FrameData *ArmatureCache::AnimationData::buildFrameData(std::size_t frameIdx) { + if (frameIdx > _frames.size()) { + return nullptr; + } + if (frameIdx == _frames.size()) { + auto *frameData = new FrameData(); + _frames.push_back(frameData); + } + return _frames[frameIdx]; +} + +ArmatureCache::FrameData *ArmatureCache::AnimationData::getFrameData(std::size_t frameIdx) const { + if (frameIdx >= _frames.size()) { + return nullptr; + } + return _frames[frameIdx]; +} + +std::size_t ArmatureCache::AnimationData::getFrameCount() const { + return _frames.size(); +} + +ArmatureCache::ArmatureCache(const std::string &armatureName, const std::string &armatureKey, const std::string &atlasUUID) { + _armatureDisplay = dragonBones::CCFactory::getFactory()->buildArmatureDisplay(armatureName, armatureKey, "", atlasUUID); + if (_armatureDisplay) { + _armatureDisplay->addRef(); + } +} + +ArmatureCache::~ArmatureCache() { + if (_armatureDisplay) { + _armatureDisplay->release(); + _armatureDisplay = nullptr; + } + + for (auto &animationCache : _animationCaches) { + delete animationCache.second; + } + _animationCaches.clear(); +} + +ArmatureCache::AnimationData *ArmatureCache::buildAnimationData(const std::string &animationName) { + if (!_armatureDisplay) return nullptr; + + AnimationData *aniData = nullptr; + auto it = _animationCaches.find(animationName); + if (it == _animationCaches.end()) { + auto *armature = _armatureDisplay->getArmature(); + auto *animation = armature->getAnimation(); + auto hasAni = animation->hasAnimation(animationName); + if (!hasAni) return nullptr; + + aniData = new AnimationData(); + aniData->_animationName = animationName; + _animationCaches[animationName] = aniData; + } else { + aniData = it->second; + } + return aniData; +} + +ArmatureCache::AnimationData *ArmatureCache::getAnimationData(const std::string &animationName) { + auto it = _animationCaches.find(animationName); + if (it == _animationCaches.end()) { + return nullptr; + } + return it->second; +} + +void ArmatureCache::updateToFrame(const std::string &animationName, int toFrameIdx /*= -1*/) { + auto it = _animationCaches.find(animationName); + if (it == _animationCaches.end()) { + return; + } + + AnimationData *animationData = it->second; + if (!animationData || !animationData->needUpdate(toFrameIdx)) { + return; + } + + if (_curAnimationName != animationName) { + updateToFrame(_curAnimationName); + _curAnimationName = animationName; + } + + auto *armature = _armatureDisplay->getArmature(); + auto *animation = armature->getAnimation(); + + // init animation + if (animationData->getFrameCount() == 0) { + animation->play(animationName, 1); + } + + do { + armature->advanceTime(FrameTime); + renderAnimationFrame(animationData); + animationData->_totalTime += FrameTime; + if (animation->isCompleted()) { + animationData->_isComplete = true; + } + } while (animationData->needUpdate(toFrameIdx)); +} + +void ArmatureCache::renderAnimationFrame(AnimationData *animationData) { + std::size_t frameIndex = animationData->getFrameCount(); + _frameData = animationData->buildFrameData(frameIndex); + + _preColor = Color4B(0, 0, 0, 0); + _color = Color4B(255, 255, 255, 255); + + _preBlendMode = -1; + _preTextureIndex = -1; + _curTextureIndex = -1; + _preISegWritePos = -1; + _curISegLen = 0; + _curVSegLen = 0; + _materialLen = 0; + + auto *armature = _armatureDisplay->getArmature(); + traverseArmature(armature); + + if (_preISegWritePos != -1) { + SegmentData *preSegmentData = _frameData->buildSegmentData(_materialLen - 1); + preSegmentData->indexCount = _curISegLen; + preSegmentData->vertexFloatCount = _curVSegLen; + } + + auto colorCount = _frameData->getColorCount(); + if (colorCount > 0) { + ColorData *preColorData = _frameData->buildColorData(colorCount - 1); + preColorData->vertexFloatOffset = static_cast(_frameData->vb.getCurPos()) / sizeof(float); + } + + _frameData = nullptr; +} + +void ArmatureCache::traverseArmature(Armature *armature, float parentOpacity /*= 1.0f*/) { + middleware::IOBuffer &vb = _frameData->vb; + middleware::IOBuffer &ib = _frameData->ib; + + const auto &bones = armature->getBones(); + Bone *bone = nullptr; + const auto &slots = armature->getSlots(); + CCSlot *slot = nullptr; + // range [0.0, 1.0] + Color4B preColor(0, 0, 0, 0); + Color4B color; + middleware::Texture2D *texture = nullptr; + + auto flush = [&]() { + // fill pre segment count field + if (_preISegWritePos != -1) { + SegmentData *preSegmentData = _frameData->buildSegmentData(_materialLen - 1); + preSegmentData->indexCount = _curISegLen; + preSegmentData->vertexFloatCount = _curVSegLen; + } + + SegmentData *segmentData = _frameData->buildSegmentData(_materialLen); + segmentData->setTexture(texture); + segmentData->blendMode = static_cast(slot->_blendMode); + + // save new segment count pos field + _preISegWritePos = static_cast(ib.getCurPos() / sizeof(uint16_t)); + // reset pre blend mode to current + _preBlendMode = static_cast(slot->_blendMode); + // reset pre texture index to current + _preTextureIndex = _curTextureIndex; + // reset index segmentation count + _curISegLen = 0; + // reset vertex segmentation count + _curVSegLen = 0; + // material length increased + _materialLen++; + }; + + for (auto *i : bones) { + bone = i; + auto boneCount = _frameData->getBoneCount(); + BoneData *boneData = _frameData->buildBoneData(boneCount); + auto &boneOriginMat = bone->globalTransformMatrix; + auto &matm = boneData->globalTransformMatrix.m; + matm[0] = boneOriginMat.a; + matm[1] = boneOriginMat.b; + matm[4] = -boneOriginMat.c; + matm[5] = -boneOriginMat.d; + matm[12] = boneOriginMat.tx; + matm[13] = boneOriginMat.ty; + } + + for (auto *i : slots) { + slot = dynamic_cast(i); // TODO(zhakasi): refine the logic + if (slot == nullptr) { + return; + } + if (!slot->getVisible()) { + continue; + } + slot->updateWorldMatrix(); + + Mat4 *worldMatrix = &slot->worldMatrix; + + // If slots has child armature,will traverse child first. + Armature *childArmature = slot->getChildArmature(); + if (childArmature != nullptr) { + traverseArmature(childArmature, parentOpacity * static_cast(slot->color.a) / 255.0F); + continue; + } + + texture = slot->getTexture(); + if (!texture) continue; + _curTextureIndex = texture->getRealTextureIndex(); + + auto vbSize = slot->triangles.vertCount * sizeof(middleware::V3F_T2F_C4B); + vb.checkSpace(vbSize, true); + + // If texture or blendMode change,will change material. + if (_preTextureIndex != _curTextureIndex || _preBlendMode != static_cast(slot->_blendMode)) { + flush(); + } + + // Calculation vertex color. + color.a = static_cast(slot->color.a * parentOpacity); + color.r = static_cast(slot->color.r); + color.g = static_cast(slot->color.g); + color.b = static_cast(slot->color.b); + + if (preColor != color) { + preColor = color; + auto colorCount = _frameData->getColorCount(); + if (colorCount > 0) { + ColorData *preColorData = _frameData->buildColorData(colorCount - 1); + preColorData->vertexFloatOffset = vb.getCurPos() / sizeof(float); + } + ColorData *colorData = _frameData->buildColorData(colorCount); + colorData->color = color; + } + + // Transform component matrix to global matrix + middleware::Triangles &triangles = slot->triangles; + middleware::V3F_T2F_C4B *worldTriangles = slot->worldVerts; + + for (int v = 0, w = 0, vn = triangles.vertCount; v < vn; ++v, w += 2) { + middleware::V3F_T2F_C4B *vertex = triangles.verts + v; + middleware::V3F_T2F_C4B *worldVertex = worldTriangles + v; + + vertex->vertex.z = 0; //reset for z value + worldVertex->vertex.transformMat4(vertex->vertex, *worldMatrix); + + worldVertex->color.r = color.r; + worldVertex->color.g = color.g; + worldVertex->color.b = color.b; + worldVertex->color.a = color.a; + } + + vb.writeBytes(reinterpret_cast(worldTriangles), vbSize); + + auto ibSize = triangles.indexCount * sizeof(uint16_t); + ib.checkSpace(ibSize, true); + + auto vertexOffset = _curVSegLen / VF_XYZUVC; + for (int ii = 0, nn = triangles.indexCount; ii < nn; ii++) { + ib.writeUint16(triangles.indices[ii] + vertexOffset); + } + + _curISegLen += triangles.indexCount; + _curVSegLen += static_cast(vbSize / sizeof(float)); + } // End slot traverse +} + +void ArmatureCache::resetAllAnimationData() { + for (auto &animationCache : _animationCaches) { + animationCache.second->reset(); + } +} + +void ArmatureCache::resetAnimationData(const std::string &animationName) { + for (auto &animationCache : _animationCaches) { + if (animationCache.second->_animationName == animationName) { + animationCache.second->reset(); + break; + } + } +} + +CCArmatureDisplay *ArmatureCache::getArmatureDisplay() { + return _armatureDisplay; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones-creator-support/ArmatureCache.h b/cocos/editor-support/dragonbones-creator-support/ArmatureCache.h new file mode 100644 index 0000000..c6045dd --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/ArmatureCache.h @@ -0,0 +1,158 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2020 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "CCArmatureDisplay.h" +#include "IOBuffer.h" +#include "base/RefCounted.h" + +DRAGONBONES_NAMESPACE_BEGIN + +class ArmatureCache : public cc::RefCounted { +public: + struct SegmentData { + friend class ArmatureCache; + + SegmentData(); + ~SegmentData(); + + void setTexture(cc::middleware::Texture2D *value); + cc::middleware::Texture2D *getTexture() const; + + public: + int blendMode = 0; + std::size_t indexCount = 0; + std::size_t vertexFloatCount = 0; + + private: + cc::middleware::Texture2D *_texture = nullptr; + }; + + struct BoneData { + cc::Mat4 globalTransformMatrix; + }; + + struct ColorData { + cc::middleware::Color4B color; + std::size_t vertexFloatOffset = 0; + }; + + struct FrameData { + friend class ArmatureCache; + + FrameData(); + ~FrameData(); + + const std::vector &getBones() const { + return _bones; + } + std::size_t getBoneCount() const; + + const std::vector &getColors() const { + return _colors; + } + std::size_t getColorCount() const; + + const std::vector &getSegments() const { + return _segments; + } + std::size_t getSegmentCount() const; + + private: + // if segment data is empty, it will build new one. + SegmentData *buildSegmentData(std::size_t index); + // if color data is empty, it will build new one. + ColorData *buildColorData(std::size_t index); + // if bone data is empty, it will build new one. + BoneData *buildBoneData(std::size_t index); + + std::vector _bones; + std::vector _colors; + std::vector _segments; + + public: + cc::middleware::IOBuffer ib; + cc::middleware::IOBuffer vb; + }; + + struct AnimationData { + friend class ArmatureCache; + + AnimationData(); + ~AnimationData(); + void reset(); + + FrameData *getFrameData(std::size_t frameIdx) const; + std::size_t getFrameCount() const; + + bool isComplete() const { return _isComplete; } + bool needUpdate(int toFrameIdx) const; + + private: + // if frame is empty, it will build new one. + FrameData *buildFrameData(std::size_t frameIdx); + + std::string _animationName; + bool _isComplete = false; + float _totalTime = 0.0F; + std::vector _frames; + }; + + ArmatureCache(const std::string &armatureName, const std::string &armatureKey, const std::string &atlasUUID); + ~ArmatureCache() override; + + void updateToFrame(const std::string &animationName, int toFrameIdx = -1); + // if animation data is empty, it will build new one. + AnimationData *buildAnimationData(const std::string &animationName); + AnimationData *getAnimationData(const std::string &animationName); + CCArmatureDisplay *getArmatureDisplay(); + + void resetAllAnimationData(); + void resetAnimationData(const std::string &animationName); + +private: + void renderAnimationFrame(AnimationData *animationData); + void traverseArmature(Armature *armature, float parentOpacity = 1.0F); + +public: + static float FrameTime; // NOLINT + static float MaxCacheTime; // NOLINT + +private: + FrameData *_frameData = nullptr; + cc::middleware::Color4F _preColor = cc::middleware::Color4F(-1.0F, -1.0F, -1.0F, -1.0F); + cc::middleware::Color4F _color = cc::middleware::Color4F(1.0F, 1.0F, 1.0F, 1.0F); + CCArmatureDisplay *_armatureDisplay = nullptr; + int _preBlendMode = -1; + int _preTextureIndex = -1; + int _curTextureIndex = -1; + int _preISegWritePos = -1; + int _curISegLen = 0; + int _curVSegLen = 0; + int _materialLen = 0; + std::string _curAnimationName; + std::map _animationCaches; +}; + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones-creator-support/ArmatureCacheMgr.cpp b/cocos/editor-support/dragonbones-creator-support/ArmatureCacheMgr.cpp new file mode 100644 index 0000000..9dbe14d --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/ArmatureCacheMgr.cpp @@ -0,0 +1,52 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2020 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "ArmatureCacheMgr.h" +#include "base/DeferredReleasePool.h" + +DRAGONBONES_NAMESPACE_BEGIN + +ArmatureCacheMgr *ArmatureCacheMgr::_instance = nullptr; +ArmatureCache *ArmatureCacheMgr::buildArmatureCache(const std::string &armatureName, const std::string &armatureKey, const std::string &atlasUUID) { + ArmatureCache *animation = _caches.at(armatureKey); + if (!animation) { + animation = new ArmatureCache(armatureName, armatureKey, atlasUUID); + animation->addRef(); + _caches.insert(armatureKey, animation); + cc::DeferredReleasePool::add(animation); + } + return animation; +} + +void ArmatureCacheMgr::removeArmatureCache(const std::string &armatureKey) { + for (auto it = _caches.begin(); it != _caches.end();) { + auto found = it->first.find(armatureKey); + if (found != std::string::npos) { + it = _caches.erase(it); + } else { + it++; + } + } +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones-creator-support/ArmatureCacheMgr.h b/cocos/editor-support/dragonbones-creator-support/ArmatureCacheMgr.h new file mode 100644 index 0000000..714bd39 --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/ArmatureCacheMgr.h @@ -0,0 +1,56 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2020 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "ArmatureCache.h" +#include "base/RefMap.h" +#include "dragonbones/DragonBonesHeaders.h" + +DRAGONBONES_NAMESPACE_BEGIN + +class ArmatureCacheMgr { +public: + static ArmatureCacheMgr *getInstance() { + if (_instance == nullptr) { + _instance = new ArmatureCacheMgr(); + } + return _instance; + } + + static void destroyInstance() { + if (_instance) { + delete _instance; + _instance = nullptr; + } + } + + void removeArmatureCache(const std::string &armatureKey); + ArmatureCache *buildArmatureCache(const std::string &armatureName, const std::string &armatureKey, const std::string &atlasUUID); + +private: + static ArmatureCacheMgr *_instance; + cc::RefMap _caches; +}; + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones-creator-support/CCArmatureCacheDisplay.cpp b/cocos/editor-support/dragonbones-creator-support/CCArmatureCacheDisplay.cpp new file mode 100644 index 0000000..70367fe --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCArmatureCacheDisplay.cpp @@ -0,0 +1,464 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "CCArmatureCacheDisplay.h" +#include "2d/renderer/RenderDrawInfo.h" +#include "2d/renderer/RenderEntity.h" +#include "ArmatureCacheMgr.h" +#include "CCFactory.h" +#include "MiddlewareManager.h" +#include "SharedBufferManager.h" +#include "base/TypeDef.h" +#include "base/memory/Memory.h" +#include "gfx-base/GFXDef.h" +#include "math/Math.h" +#include "renderer/core/MaterialInstance.h" + +using namespace cc; // NOLINT(google-build-using-namespace) +using namespace cc::gfx; // NOLINT(google-build-using-namespace) + +static const std::string TECH_STAGE = "opaque"; +static const std::string TEXTURE_KEY = "texture"; +static const std::string START_EVENT = "start"; +static const std::string LOOP_COMPLETE_EVENT = "loopComplete"; +static const std::string COMPLETE_EVENT = "complete"; + +DRAGONBONES_NAMESPACE_BEGIN + +USING_NS_MW; // NOLINT(google-build-using-namespace) +CCArmatureCacheDisplay::CCArmatureCacheDisplay(const std::string &armatureName, const std::string &armatureKey, const std::string &atlasUUID, bool isShare) { + _eventObject = BaseObject::borrowObject(); + + if (isShare) { + _armatureCache = ArmatureCacheMgr::getInstance()->buildArmatureCache(armatureName, armatureKey, atlasUUID); + _armatureCache->addRef(); + } else { + _armatureCache = new ArmatureCache(armatureName, armatureKey, atlasUUID); + _armatureCache->addRef(); + } + + // store global TypedArray begin and end offset + _sharedBufferOffset = new IOTypedArray(se::Object::TypedArrayType::UINT32, sizeof(uint32_t) * 2); +} + +CCArmatureCacheDisplay::~CCArmatureCacheDisplay() { + dispose(); + + if (_sharedBufferOffset) { + delete _sharedBufferOffset; + _sharedBufferOffset = nullptr; + } + for (auto *draw : _drawInfoArray) { + CC_SAFE_DELETE(draw); + } + + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } +} + +void CCArmatureCacheDisplay::dispose() { + if (_armatureCache) { + _armatureCache->release(); + _armatureCache = nullptr; + } + if (_eventObject) { + _eventObject->returnToPool(); + _eventObject = nullptr; + } + + stopSchedule(); +} + +void CCArmatureCacheDisplay::update(float dt) { + auto gTimeScale = dragonBones::CCFactory::getFactory()->getTimeScale(); + dt *= _timeScale * gTimeScale; + + if (_isAniComplete || !_animationData) { + if (_animationData && !_animationData->isComplete()) { + _armatureCache->updateToFrame(_animationName); + } + return; + } + + if (_accTime <= 0.00001 && _playCount == 0) { + _eventObject->type = EventObject::START; + dispatchDBEvent(START_EVENT, _eventObject); + } + + _accTime += dt; + int frameIdx = floor(_accTime / ArmatureCache::FrameTime); + if (!_animationData->isComplete()) { + _armatureCache->updateToFrame(_animationName, frameIdx); + } + + int finalFrameIndex = static_cast(_animationData->getFrameCount()) - 1; + if (_animationData->isComplete() && frameIdx >= finalFrameIndex) { + _playCount++; + _accTime = 0.0F; + if (_playTimes > 0 && _playCount >= _playTimes) { + frameIdx = finalFrameIndex; + _playCount = 0; + _isAniComplete = true; + } else { + frameIdx = 0; + } + + _eventObject->type = EventObject::COMPLETE; + dispatchDBEvent(COMPLETE_EVENT, _eventObject); + + _eventObject->type = EventObject::LOOP_COMPLETE; + dispatchDBEvent(LOOP_COMPLETE_EVENT, _eventObject); + } + _curFrameIndex = frameIdx; +} + +void CCArmatureCacheDisplay::render(float /*dt*/) { + if (!_animationData) return; + ArmatureCache::FrameData *frameData = _animationData->getFrameData(_curFrameIndex); + if (!frameData) return; + + auto *mgr = MiddlewareManager::getInstance(); + if (!mgr->isRendering) return; + auto *entity = _entity; + entity->clearDynamicRenderDrawInfos(); + + const auto &segments = frameData->getSegments(); + const auto &colors = frameData->getColors(); + + _sharedBufferOffset->reset(); + _sharedBufferOffset->clear(); + + auto *attachMgr = mgr->getAttachInfoMgr(); + auto *attachInfo = attachMgr->getBuffer(); + if (!attachInfo) return; + + // store attach info offset + _sharedBufferOffset->writeUint32(static_cast(attachInfo->getCurPos()) / sizeof(uint32_t)); + + if (segments.empty() || colors.empty()) return; + + middleware::MeshBuffer *mb = mgr->getMeshBuffer(VF_XYZUVC); + middleware::IOBuffer &vb = mb->getVB(); + middleware::IOBuffer &ib = mb->getIB(); + const auto &srcVB = frameData->vb; + const auto &srcIB = frameData->ib; + auto &nodeWorldMat = entity->getNode()->getWorldMatrix(); + + int colorOffset = 0; + ArmatureCache::ColorData *nowColor = colors[colorOffset++]; + auto maxVFOffset = nowColor->vertexFloatOffset; + + Color4B color; + + float tempR = 0.0F; + float tempG = 0.0F; + float tempB = 0.0F; + float tempA = 0.0F; + float multiplier = 1.0F; + std::size_t srcVertexBytesOffset = 0; + std::size_t srcIndexBytesOffset = 0; + std::size_t vertexBytes = 0; + std::size_t indexBytes = 0; + BlendMode blendMode = BlendMode::Normal; + std::size_t dstVertexOffset = 0; + std::size_t dstIndexOffset = 0; + float *dstVertexBuffer = nullptr; + unsigned int *dstColorBuffer = nullptr; + uint16_t *dstIndexBuffer = nullptr; + bool needColor = false; + int curBlendSrc = -1; + int curBlendDst = -1; + cc::Texture2D *curTexture = nullptr; + RenderDrawInfo *curDrawInfo = nullptr; + + if (abs(_nodeColor.r - 1.0F) > 0.0001F || + abs(_nodeColor.g - 1.0F) > 0.0001F || + abs(_nodeColor.b - 1.0F) > 0.0001F || + abs(_nodeColor.a - 1.0F) > 0.0001F || + _premultipliedAlpha) { + needColor = true; + } + + auto handleColor = [&](ArmatureCache::ColorData *colorData) { + tempA = colorData->color.a * _nodeColor.a; + multiplier = _premultipliedAlpha ? tempA / 255.0f : 1.0f; + tempR = _nodeColor.r * multiplier; + tempG = _nodeColor.g * multiplier; + tempB = _nodeColor.b * multiplier; + + color.a = (uint8_t)floorf(tempA); + color.r = (uint8_t)floorf(colorData->color.r * tempR); + color.g = (uint8_t)floorf(colorData->color.g * tempG); + color.b = (uint8_t)floorf(colorData->color.b * tempB); + }; + + handleColor(nowColor); + int segmentCount = 0; + for (auto *segment : segments) { + vertexBytes = segment->vertexFloatCount * sizeof(float); + curDrawInfo = requestDrawInfo(segmentCount++); + entity->addDynamicRenderDrawInfo(curDrawInfo); + // fill new texture index + curTexture = static_cast(segment->getTexture()->getRealTexture()); + gfx::Texture *texture = curTexture->getGFXTexture(); + gfx::Sampler *sampler = curTexture->getGFXSampler(); + curDrawInfo->setTexture(texture); + curDrawInfo->setSampler(sampler); + + blendMode = static_cast(segment->blendMode); + switch (blendMode) { + case BlendMode::Add: + curBlendSrc = static_cast(_premultipliedAlpha ? BlendFactor::ONE : BlendFactor::SRC_ALPHA); + curBlendDst = static_cast(BlendFactor::ONE); + break; + case BlendMode::Multiply: + curBlendSrc = static_cast(BlendFactor::DST_COLOR); + curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_ALPHA); + break; + case BlendMode::Screen: + curBlendSrc = static_cast(BlendFactor::ONE); + curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_COLOR); + break; + default: + curBlendSrc = static_cast(_premultipliedAlpha ? BlendFactor::ONE : BlendFactor::SRC_ALPHA); + curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_ALPHA); + break; + } + // fill new blend src and dst + auto *material = requestMaterial(curBlendSrc, curBlendDst); + curDrawInfo->setMaterial(material); + + // fill vertex buffer + vb.checkSpace(vertexBytes, true); + dstVertexOffset = vb.getCurPos() / sizeof(V3F_T2F_C4B); + dstVertexBuffer = reinterpret_cast(vb.getCurBuffer()); + dstColorBuffer = reinterpret_cast(vb.getCurBuffer()); + vb.writeBytes(reinterpret_cast(srcVB.getBuffer()) + srcVertexBytesOffset, vertexBytes); + // batch handle + cc::Vec3 *point = nullptr; + + for (auto posIndex = 0; posIndex < segment->vertexFloatCount; posIndex += VF_XYZUVC) { + point = reinterpret_cast(dstVertexBuffer + posIndex); + point->z = 0; + point->transformMat4(*point, nodeWorldMat); + } + // handle vertex color + if (needColor) { + auto frameFloatOffset = srcVertexBytesOffset / sizeof(float); + for (auto colorIndex = 0; colorIndex < segment->vertexFloatCount; colorIndex += VF_XYZUVC, frameFloatOffset += VF_XYZUVC) { + if (frameFloatOffset >= maxVFOffset) { + nowColor = colors[colorOffset++]; + handleColor(nowColor); + maxVFOffset = nowColor->vertexFloatOffset; + } + memcpy(dstColorBuffer + colorIndex + 5, &color, sizeof(color)); + } + } + + // move src vertex buffer offset + srcVertexBytesOffset += vertexBytes; + + // fill index buffer + indexBytes = segment->indexCount * sizeof(uint16_t); + ib.checkSpace(indexBytes, true); + dstIndexOffset = static_cast(ib.getCurPos()) / sizeof(uint16_t); + dstIndexBuffer = reinterpret_cast(ib.getCurBuffer()); + ib.writeBytes(reinterpret_cast(srcIB.getBuffer()) + srcIndexBytesOffset, indexBytes); + for (auto indexPos = 0; indexPos < segment->indexCount; indexPos++) { + dstIndexBuffer[indexPos] += dstVertexOffset; + } + srcIndexBytesOffset += indexBytes; + + // fill new index and vertex buffer id + UIMeshBuffer *uiMeshBuffer = mb->getUIMeshBuffer(); + curDrawInfo->setMeshBuffer(uiMeshBuffer); + + // fill new index offset + curDrawInfo->setIndexOffset(dstIndexOffset); + // fill new indice segamentation count + curDrawInfo->setIbCount(segment->indexCount); + } + + if (_useAttach) { + const auto &bonesData = frameData->getBones(); + auto boneCount = frameData->getBoneCount(); + + for (int i = 0; i < boneCount; i++) { + auto *bone = bonesData[i]; + attachInfo->checkSpace(sizeof(cc::Mat4), true); + attachInfo->writeBytes(reinterpret_cast(&bone->globalTransformMatrix), sizeof(cc::Mat4)); + } + } +} +void CCArmatureCacheDisplay::beginSchedule() { + MiddlewareManager::getInstance()->addTimer(this); +} + +void CCArmatureCacheDisplay::stopSchedule() { + MiddlewareManager::getInstance()->removeTimer(this); + + if (_sharedBufferOffset) { + _sharedBufferOffset->reset(); + _sharedBufferOffset->clear(); + } +} + +void CCArmatureCacheDisplay::onEnable() { + beginSchedule(); +} + +void CCArmatureCacheDisplay::onDisable() { + stopSchedule(); +} + +Armature *CCArmatureCacheDisplay::getArmature() const { + auto *armatureDisplay = _armatureCache->getArmatureDisplay(); + return armatureDisplay->getArmature(); +} + +Animation *CCArmatureCacheDisplay::getAnimation() const { + auto *armature = getArmature(); + return armature->getAnimation(); +} + +void CCArmatureCacheDisplay::playAnimation(const std::string &name, int playTimes) { + _playTimes = playTimes; + _animationName = name; + _animationData = _armatureCache->buildAnimationData(_animationName); + _isAniComplete = false; + _accTime = 0.0F; + _playCount = 0; + _curFrameIndex = 0; +} + +void CCArmatureCacheDisplay::addDBEventListener(const std::string &type) { + _listenerIDMap[type] = true; +} + +void CCArmatureCacheDisplay::removeDBEventListener(const std::string &type) { + auto it = _listenerIDMap.find(type); + if (it != _listenerIDMap.end()) { + _listenerIDMap.erase(it); + } +} + +void CCArmatureCacheDisplay::dispatchDBEvent(const std::string &type, EventObject *value) { + auto it = _listenerIDMap.find(type); + if (it == _listenerIDMap.end()) { + return; + } + + if (_dbEventCallback) { + _dbEventCallback(value); + } +} + +void CCArmatureCacheDisplay::updateAnimationCache(const std::string &animationName) { + _armatureCache->resetAnimationData(animationName); +} + +void CCArmatureCacheDisplay::updateAllAnimationCache() { + _armatureCache->resetAllAnimationData(); +} + +void CCArmatureCacheDisplay::setColor(float r, float g, float b, float a) { + _nodeColor.r = r / 255.0F; + _nodeColor.g = g / 255.0F; + _nodeColor.b = b / 255.0F; + _nodeColor.a = a / 255.0F; +} + +void CCArmatureCacheDisplay::setAttachEnabled(bool enabled) { + _useAttach = enabled; +} + +void CCArmatureCacheDisplay::setBatchEnabled(bool enabled) { + if (enabled != _enableBatch) { + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } + _materialCaches.clear(); + _enableBatch = enabled; + } +} + +se_object_ptr CCArmatureCacheDisplay::getSharedBufferOffset() const { + if (_sharedBufferOffset) { + return _sharedBufferOffset->getTypeArray(); + } + return nullptr; +} + +void CCArmatureCacheDisplay::setRenderEntity(cc::RenderEntity *entity) { + _entity = entity; +} + +void CCArmatureCacheDisplay::setMaterial(cc::Material *material) { + _material = material; + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } + _materialCaches.clear(); +} + +cc::RenderDrawInfo *CCArmatureCacheDisplay::requestDrawInfo(int idx) { + if (_drawInfoArray.size() < idx + 1) { + cc::RenderDrawInfo *draw = new cc::RenderDrawInfo(); + draw->setDrawInfoType(static_cast(RenderDrawInfoType::MIDDLEWARE)); + _drawInfoArray.push_back(draw); + } + return _drawInfoArray[idx]; +} + +cc::Material *CCArmatureCacheDisplay::requestMaterial(uint16_t blendSrc, uint16_t blendDst) { + uint32_t key = static_cast(blendSrc) << 16 | static_cast(blendDst); + if (_materialCaches.find(key) == _materialCaches.end()) { + const IMaterialInstanceInfo info{ + (Material *)_material, + 0}; + MaterialInstance *materialInstance = new MaterialInstance(info); + PassOverrides overrides; + BlendStateInfo stateInfo; + stateInfo.blendColor = gfx::Color{1.0F, 1.0F, 1.0F, 1.0F}; + BlendTargetInfo targetInfo; + targetInfo.blendEq = gfx::BlendOp::ADD; + targetInfo.blendAlphaEq = gfx::BlendOp::ADD; + targetInfo.blendSrc = (gfx::BlendFactor)blendSrc; + targetInfo.blendDst = (gfx::BlendFactor)blendDst; + targetInfo.blendSrcAlpha = (gfx::BlendFactor)blendSrc; + targetInfo.blendDstAlpha = (gfx::BlendFactor)blendDst; + BlendTargetInfoList targetList{targetInfo}; + stateInfo.targets = targetList; + overrides.blendState = stateInfo; + materialInstance->overridePipelineStates(overrides); + const MacroRecord macros{{"USE_LOCAL", false}}; + materialInstance->recompileShaders(macros); + _materialCaches[key] = materialInstance; + } + return _materialCaches[key]; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones-creator-support/CCArmatureCacheDisplay.h b/cocos/editor-support/dragonbones-creator-support/CCArmatureCacheDisplay.h new file mode 100644 index 0000000..ac02507 --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCArmatureCacheDisplay.h @@ -0,0 +1,129 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +#include "ArmatureCache.h" +#include "CCArmatureDisplay.h" +#include "base/RefCounted.h" +#include "dragonbones/DragonBonesHeaders.h" + +namespace cc { +class RenderEntity; +class RenderDrawInfo; +class Material; +}; // namespace cc + +DRAGONBONES_NAMESPACE_BEGIN + +class CCArmatureCacheDisplay : public cc::RefCounted, public cc::middleware::IMiddleware { +public: + CCArmatureCacheDisplay(const std::string &armatureName, const std::string &armatureKey, const std::string &atlasUUID, bool isShare); + ~CCArmatureCacheDisplay() override; + void dispose(); + + void update(float dt) override; + void render(float dt) override; + + void setTimeScale(float scale) { + _timeScale = scale; + } + + float getTimeScale() const { + return _timeScale; + } + + void beginSchedule(); + void stopSchedule(); + void onEnable(); + void onDisable(); + + Armature *getArmature() const; + Animation *getAnimation() const; + + void setColor(float r, float g, float b, float a); + void setBatchEnabled(bool enabled); + void setAttachEnabled(bool enabled); + + void setOpacityModifyRGB(bool value) { + _premultipliedAlpha = value; + } + + using dbEventCallback = std::function; + void setDBEventCallback(dbEventCallback callback) { + _dbEventCallback = std::move(callback); + } + void addDBEventListener(const std::string &type); + void removeDBEventListener(const std::string &type); + void dispatchDBEvent(const std::string &type, EventObject *value); + + void playAnimation(const std::string &name, int playTimes); + void updateAnimationCache(const std::string &animationName); + void updateAllAnimationCache(); + + /** + * @return shared buffer offset, it's a Uint32Array + * format |render info offset|attach info offset| + */ + se_object_ptr getSharedBufferOffset() const; + + cc::RenderDrawInfo *requestDrawInfo(int idx); + cc::Material *requestMaterial(uint16_t blendSrc, uint16_t blendDst); + void setMaterial(cc::Material *material); + void setRenderEntity(cc::RenderEntity *entity); + +private: + float _timeScale = 1; + int _curFrameIndex = -1; + float _accTime = 0.0F; + int _playCount = 0; + int _playTimes = 0; + bool _isAniComplete = true; + std::string _animationName; + + Armature *_armature = nullptr; + ArmatureCache::AnimationData *_animationData = nullptr; + std::map _listenerIDMap; + + bool _useAttach = false; + bool _enableBatch = true; + cc::middleware::Color4F _nodeColor = cc::middleware::Color4F::WHITE; + + bool _premultipliedAlpha = false; + dbEventCallback _dbEventCallback = nullptr; + ArmatureCache *_armatureCache = nullptr; + EventObject *_eventObject; + + cc::middleware::IOTypedArray *_sharedBufferOffset = nullptr; + + cc::RenderEntity *_entity = nullptr; + cc::Material *_material = nullptr; + ccstd::vector _drawInfoArray; + ccstd::unordered_map _materialCaches; +}; + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones-creator-support/CCArmatureDisplay.cpp b/cocos/editor-support/dragonbones-creator-support/CCArmatureDisplay.cpp new file mode 100644 index 0000000..d5478ed --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCArmatureDisplay.cpp @@ -0,0 +1,466 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "dragonbones-creator-support/CCArmatureDisplay.h" +#include "2d/renderer/RenderDrawInfo.h" +#include "2d/renderer/RenderEntity.h" +#include "MiddlewareMacro.h" +#include "SharedBufferManager.h" +#include "base/DeferredReleasePool.h" +#include "base/TypeDef.h" +#include "base/memory/Memory.h" +#include "dragonbones-creator-support/CCSlot.h" +#include "gfx-base/GFXDef.h" +#include "math/Math.h" +#include "math/Vec3.h" +#include "renderer/core/MaterialInstance.h" + +USING_NS_MW; // NOLINT(google-build-using-namespace) +using namespace cc; // NOLINT(google-build-using-namespace) + +static const std::string TECH_STAGE = "opaque"; +static const std::string TEXTURE_KEY = "texture"; +namespace cc { + +} +DRAGONBONES_NAMESPACE_BEGIN + +CCArmatureDisplay *CCArmatureDisplay::create() { + return new (std::nothrow) CCArmatureDisplay(); +} + +CCArmatureDisplay::CCArmatureDisplay() { + _sharedBufferOffset = new IOTypedArray(se::Object::TypedArrayType::UINT32, sizeof(uint32_t) * 2); +} + +CCArmatureDisplay::~CCArmatureDisplay() { + dispose(); + + if (_debugBuffer) { + delete _debugBuffer; + _debugBuffer = nullptr; + } + + if (_sharedBufferOffset) { + delete _sharedBufferOffset; + _sharedBufferOffset = nullptr; + } + for (auto *draw : _drawInfoArray) { + CC_SAFE_DELETE(draw); + } + + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } +} + +void CCArmatureDisplay::dispose() { + if (_armature != nullptr) { + _armature->dispose(); + _armature = nullptr; + } +} + +void CCArmatureDisplay::dbInit(Armature *armature) { + _armature = armature; +} + +void CCArmatureDisplay::dbClear() { + _armature = nullptr; + release(); +} + +void CCArmatureDisplay::dbUpdate() {} + +void CCArmatureDisplay::dbRender() { + _sharedBufferOffset->reset(); + _sharedBufferOffset->clear(); + + if (this->_armature->getParent()) { + return; + } + if (!_entity) { + return; + } + auto *entity = _entity; + entity->clearDynamicRenderDrawInfos(); + + auto *mgr = MiddlewareManager::getInstance(); + if (!mgr->isRendering) return; + + auto *attachMgr = mgr->getAttachInfoMgr(); + auto *attachInfo = attachMgr->getBuffer(); + if (!attachInfo) return; + + // store attach info offset + _sharedBufferOffset->writeUint32(static_cast(attachInfo->getCurPos()) / sizeof(uint32_t)); + + _preBlendMode = -1; + _preISegWritePos = -1; + _curISegLen = 0; + + _debugSlotsLen = 0; + _materialLen = 0; + _preTexture = nullptr; + _curTexture = nullptr; + _curDrawInfo = nullptr; + + // Traverse all aramture to fill vertex and index buffer. + traverseArmature(_armature); + + if (_curDrawInfo) _curDrawInfo->setIbCount(_curISegLen); + + if (_useAttach || _debugDraw) { + const auto &bones = _armature->getBones(); + std::size_t count = bones.size(); + + cc::Mat4 boneMat = cc::Mat4::IDENTITY; + + if (_debugDraw) { + // If enable debug draw,then init debug buffer. + if (_debugBuffer == nullptr) { + _debugBuffer = new IOTypedArray(se::Object::TypedArrayType::FLOAT32, MAX_DEBUG_BUFFER_SIZE); + } + _debugBuffer->reset(); + _debugBuffer->writeFloat32(static_cast(count) * 4); + } + + for (int i = 0; i < count; i++) { + Bone *bone = static_cast(bones[i]); + + float boneLen = 5; + if (bone->_boneData->length > boneLen) { + boneLen = bone->_boneData->length; + } + + boneMat.m[0] = bone->globalTransformMatrix.a; + boneMat.m[1] = bone->globalTransformMatrix.b; + boneMat.m[4] = -bone->globalTransformMatrix.c; + boneMat.m[5] = -bone->globalTransformMatrix.d; + boneMat.m[12] = bone->globalTransformMatrix.tx; + boneMat.m[13] = bone->globalTransformMatrix.ty; + attachInfo->checkSpace(sizeof(boneMat), true); + attachInfo->writeBytes(reinterpret_cast(&boneMat), sizeof(boneMat)); + + if (_debugDraw) { + float bx = bone->globalTransformMatrix.tx; + float by = bone->globalTransformMatrix.ty; + float endx = bx + bone->globalTransformMatrix.a * boneLen; + float endy = by + bone->globalTransformMatrix.b * boneLen; + _debugBuffer->writeFloat32(bx); + _debugBuffer->writeFloat32(by); + _debugBuffer->writeFloat32(endx); + _debugBuffer->writeFloat32(endy); + } + } + + if (_debugBuffer && _debugBuffer->isOutRange()) { + _debugBuffer->writeFloat32(0, 0); + CC_LOG_INFO("Dragonbones debug data is too large,debug buffer has no space to put in it!!!!!!!!!!"); + CC_LOG_INFO("You can adjust MAX_DEBUG_BUFFER_SIZE in MiddlewareMacro"); + } + } +} + +const cc::Vec2 &CCArmatureDisplay::convertToRootSpace(float x, float y) const { + auto *slot = reinterpret_cast(_armature->getParent()); + if (!slot) { + _tmpVec2.set(x, y); + return _tmpVec2; + } + + slot->updateWorldMatrix(); + cc::Mat4 &worldMatrix = slot->worldMatrix; + _tmpVec2.x = x * worldMatrix.m[0] + y * worldMatrix.m[4] + worldMatrix.m[12]; + _tmpVec2.y = x * worldMatrix.m[1] + y * worldMatrix.m[5] + worldMatrix.m[13]; + return _tmpVec2; +} + +CCArmatureDisplay *CCArmatureDisplay::getRootDisplay() { + Slot *slot = _armature->getParent(); + if (!slot) { + return this; + } + + Slot *parentSlot = slot->_armature->getParent(); + while (parentSlot) { + slot = parentSlot; + parentSlot = parentSlot->_armature->getParent(); + } + return static_cast(slot->_armature->getDisplay()); +} + +void CCArmatureDisplay::traverseArmature(Armature *armature, float parentOpacity) { + static cc::Mat4 matrixTemp; + auto &nodeWorldMat = _entity->getNode()->getWorldMatrix(); + + // data store in buffer which 0 to 3 is render order, left data is node world matrix + const auto &slots = armature->getSlots(); + auto *mgr = MiddlewareManager::getInstance(); + + middleware::MeshBuffer *mb = mgr->getMeshBuffer(VF_XYZUVC); + IOBuffer &vb = mb->getVB(); + IOBuffer &ib = mb->getIB(); + + float realOpacity = _nodeColor.a; + + auto *attachMgr = mgr->getAttachInfoMgr(); + auto *attachInfo = attachMgr->getBuffer(); + if (!attachInfo) return; + + // range [0.0, 255.0] + Color4B color(0, 0, 0, 0); + CCSlot *slot = nullptr; + int isFull = 0; + + auto flush = [&]() { + // fill pre segment count field + if (_curDrawInfo) _curDrawInfo->setIbCount(_curISegLen); + + _curDrawInfo = requestDrawInfo(_materialLen); + _entity->addDynamicRenderDrawInfo(_curDrawInfo); + // prepare to fill new segment field + switch (slot->_blendMode) { + case BlendMode::Add: + _curBlendSrc = static_cast(_premultipliedAlpha ? gfx::BlendFactor::ONE : gfx::BlendFactor::SRC_ALPHA); + _curBlendDst = static_cast(gfx::BlendFactor::ONE); + break; + case BlendMode::Multiply: + _curBlendSrc = static_cast(gfx::BlendFactor::DST_COLOR); + _curBlendDst = static_cast(gfx::BlendFactor::ONE_MINUS_SRC_ALPHA); + break; + case BlendMode::Screen: + _curBlendSrc = static_cast(gfx::BlendFactor::ONE); + _curBlendDst = static_cast(gfx::BlendFactor::ONE_MINUS_SRC_COLOR); + break; + default: + _curBlendSrc = static_cast(_premultipliedAlpha ? gfx::BlendFactor::ONE : gfx::BlendFactor::SRC_ALPHA); + _curBlendDst = static_cast(gfx::BlendFactor::ONE_MINUS_SRC_ALPHA); + break; + } + + auto *material = requestMaterial(_curBlendSrc, _curBlendDst); + _curDrawInfo->setMaterial(material); + gfx::Texture *texture = _curTexture->getGFXTexture(); + gfx::Sampler *sampler = _curTexture->getGFXSampler(); + _curDrawInfo->setTexture(texture); + _curDrawInfo->setSampler(sampler); + UIMeshBuffer *uiMeshBuffer = mb->getUIMeshBuffer(); + _curDrawInfo->setMeshBuffer(uiMeshBuffer); + _curDrawInfo->setIndexOffset(static_cast(ib.getCurPos()) / sizeof(uint16_t)); + + // reset pre blend mode to current + _preBlendMode = static_cast(slot->_blendMode); + // reset pre texture index to current + _preTexture = _curTexture; + + // reset index segmentation count + _curISegLen = 0; + // material length increased + _materialLen++; + }; + + for (auto *i : slots) { + isFull = 0; + slot = dynamic_cast(i); //TODO(zhakasi): refine the logic + if (slot == nullptr) { + return; + } + if (!slot->getVisible()) { + continue; + } + + slot->updateWorldMatrix(); + + // If slots has child armature,will traverse child first. + Armature *childArmature = slot->getChildArmature(); + if (childArmature != nullptr) { + traverseArmature(childArmature, parentOpacity * static_cast(slot->color.a) / 255.0F); + continue; + } + + if (!slot->getTexture()) continue; + _curTexture = static_cast(slot->getTexture()->getRealTexture()); + auto vbSize = slot->triangles.vertCount * sizeof(middleware::V3F_T2F_C4B); + isFull |= vb.checkSpace(vbSize, true); + + // If texture or blendMode change,will change material. + if (_preTexture != _curTexture || _preBlendMode != static_cast(slot->_blendMode) || isFull) { + flush(); + } + + // Calculation vertex color. + color.a = (uint8_t)(realOpacity * static_cast(slot->color.a) * parentOpacity); + float multiplier = _premultipliedAlpha ? color.a / 255.0F : 1.0F; + color.r = _nodeColor.r * slot->color.r * multiplier; + color.g = _nodeColor.g * slot->color.g * multiplier; + color.b = _nodeColor.b * slot->color.b * multiplier; + + // Transform component matrix to global matrix + middleware::Triangles &triangles = slot->triangles; + cc::Mat4 *worldMatrix = &slot->worldMatrix; + cc::Mat4::multiply(nodeWorldMat, *worldMatrix, &matrixTemp); + worldMatrix = &matrixTemp; + + middleware::V3F_T2F_C4B *worldTriangles = slot->worldVerts; + + for (int v = 0, w = 0, vn = triangles.vertCount; v < vn; ++v, w += 2) { + middleware::V3F_T2F_C4B *vertex = triangles.verts + v; + middleware::V3F_T2F_C4B *worldVertex = worldTriangles + v; + + vertex->vertex.z = 0; //reset for z value + worldVertex->vertex.transformMat4(vertex->vertex, *worldMatrix); + + worldVertex->color = color; + } + + // Fill MiddlewareManager vertex buffer + auto vertexOffset = vb.getCurPos() / sizeof(middleware::V3F_T2F_C4B); + vb.writeBytes(reinterpret_cast(worldTriangles), vbSize); + + auto ibSize = triangles.indexCount * sizeof(uint16_t); + ib.checkSpace(ibSize, true); + // If vertex buffer current offset is zero,fill it directly or recalculate vertex offset. + if (vertexOffset > 0) { + for (int ii = 0, nn = triangles.indexCount; ii < nn; ii++) { + ib.writeUint16(triangles.indices[ii] + vertexOffset); + } + } else { + ib.writeBytes(reinterpret_cast(triangles.indices), ibSize); + } + + // Record this turn index segmentation count,it will store in material buffer in the end. + _curISegLen += triangles.indexCount; + } +} + +bool CCArmatureDisplay::hasDBEventListener(const std::string &type) const { + auto it = _listenerIDMap.find(type); + return it != _listenerIDMap.end(); +} + +void CCArmatureDisplay::addDBEventListener(const std::string &type, const std::function & /*listener*/) { + _listenerIDMap[type] = true; +} + +void CCArmatureDisplay::dispatchDBEvent(const std::string &type, EventObject *value) { + auto it = _listenerIDMap.find(type); + if (it == _listenerIDMap.end()) { + return; + } + + if (_dbEventCallback) { + _dbEventCallback(value); + } +} + +void CCArmatureDisplay::removeDBEventListener(const std::string &type, const std::function & /*listener*/) { + auto it = _listenerIDMap.find(type); + if (it != _listenerIDMap.end()) { + _listenerIDMap.erase(it); + } +} + +se_object_ptr CCArmatureDisplay::getDebugData() const { + if (_debugBuffer) { + return _debugBuffer->getTypeArray(); + } + return nullptr; +} + +void CCArmatureDisplay::setColor(float r, float g, float b, float a) { + _nodeColor.r = r / 255.0F; + _nodeColor.g = g / 255.0F; + _nodeColor.b = b / 255.0F; + _nodeColor.a = a / 255.0F; +} + +se_object_ptr CCArmatureDisplay::getSharedBufferOffset() const { + if (_sharedBufferOffset) { + return _sharedBufferOffset->getTypeArray(); + } + return nullptr; +} + +void CCArmatureDisplay::setBatchEnabled(bool enabled) { + if (enabled != _enableBatch) { + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } + _materialCaches.clear(); + _enableBatch = enabled; + } +} + +void CCArmatureDisplay::setRenderEntity(cc::RenderEntity *entity) { + _entity = entity; +} + +void CCArmatureDisplay::setMaterial(cc::Material *material) { + _material = material; + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } + _materialCaches.clear(); +} + +cc::RenderDrawInfo *CCArmatureDisplay::requestDrawInfo(int idx) { + if (_drawInfoArray.size() < idx + 1) { + cc::RenderDrawInfo *draw = new cc::RenderDrawInfo(); + draw->setDrawInfoType(static_cast(RenderDrawInfoType::MIDDLEWARE)); + _drawInfoArray.push_back(draw); + } + return _drawInfoArray[idx]; +} + +cc::Material *CCArmatureDisplay::requestMaterial(uint16_t blendSrc, uint16_t blendDst) { + uint32_t key = static_cast(blendSrc) << 16 | static_cast(blendDst); + if (_materialCaches.find(key) == _materialCaches.end()) { + const IMaterialInstanceInfo info{ + (Material *)_material, + 0}; + MaterialInstance *materialInstance = new MaterialInstance(info); + PassOverrides overrides; + BlendStateInfo stateInfo; + stateInfo.blendColor = gfx::Color{1.0F, 1.0F, 1.0F, 1.0F}; + BlendTargetInfo targetInfo; + targetInfo.blendEq = gfx::BlendOp::ADD; + targetInfo.blendAlphaEq = gfx::BlendOp::ADD; + targetInfo.blendSrc = (gfx::BlendFactor)blendSrc; + targetInfo.blendDst = (gfx::BlendFactor)blendDst; + targetInfo.blendSrcAlpha = (gfx::BlendFactor)blendSrc; + targetInfo.blendDstAlpha = (gfx::BlendFactor)blendDst; + BlendTargetInfoList targetList{targetInfo}; + stateInfo.targets = targetList; + overrides.blendState = stateInfo; + materialInstance->overridePipelineStates(overrides); + const MacroRecord macros{{"USE_LOCAL", false}}; + materialInstance->recompileShaders(macros); + _materialCaches[key] = materialInstance; + } + return _materialCaches[key]; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones-creator-support/CCArmatureDisplay.h b/cocos/editor-support/dragonbones-creator-support/CCArmatureDisplay.h new file mode 100644 index 0000000..4e0e876 --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCArmatureDisplay.h @@ -0,0 +1,211 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef DRAGONBONES_CC_ARMATURE_DISPLAY_CONTAINER_H +#define DRAGONBONES_CC_ARMATURE_DISPLAY_CONTAINER_H + +#include +#include +#include +#include "IOTypedArray.h" +#include "MiddlewareManager.h" +#include "base/RefCounted.h" +#include "base/RefMap.h" +#include "bindings/event/EventDispatcher.h" +#include "dragonbones-creator-support/CCSlot.h" +#include "dragonbones/DragonBonesHeaders.h" +#include "middleware-adapter.h" + +namespace cc { +class RenderEntity; +class RenderDrawInfo; +class Material; +class Texture2D; +}; // namespace cc + +DRAGONBONES_NAMESPACE_BEGIN + +/** + * CCArmatureDisplay is a armature tree.It can add or remove a childArmature. + * It will not save vertices and indices.Only CCSlot will save these info. + * And CCArmatureDisplay will traverse all tree node and calculate render data. + */ +class CCArmatureDisplay : public cc::RefCounted, public virtual IArmatureProxy { + DRAGONBONES_DISALLOW_COPY_AND_ASSIGN(CCArmatureDisplay) + +public: + /** + * @internal + */ + static CCArmatureDisplay *create(); + +private: + void traverseArmature(Armature *armature, float parentOpacity = 1.0F); + +protected: + bool _debugDraw = false; + Armature *_armature = nullptr; + +public: + CCArmatureDisplay(); + ~CCArmatureDisplay() override; + + /** + * @inheritDoc + */ + void dbInit(Armature *armature) override; + /** + * @inheritDoc + */ + void dbClear() override; + /** + * @inheritDoc + */ + void dbUpdate() override; + /** + * @inheritDoc + */ + void dbRender() override; + /** + * @inheritDoc + */ + void dispose() override; + /** + * @inheritDoc + */ + bool hasDBEventListener(const std::string &type) const override; + /** + * @inheritDoc + */ + void dispatchDBEvent(const std::string &type, EventObject *value) override; + /** + * @inheritDoc + */ + void addDBEventListener(const std::string &type, const std::function &listener) override; + /** + * @inheritDoc + */ + void removeDBEventListener(const std::string &type, const std::function &listener) override; + + using dbEventCallback = std::function; + void setDBEventCallback(dbEventCallback callback) { + _dbEventCallback = std::move(callback); + } + + /** + * @inheritDoc + */ + inline Armature *getArmature() const override { + return _armature; + } + /** + * @inheritDoc + */ + inline Animation *getAnimation() const override { + return _armature->getAnimation(); + } + + /** + * @return debug data,it's a Float32Array, + * format |debug bones length|[beginX|beginY|toX|toY|...loop...] + */ + se_object_ptr getDebugData() const; + /** + * @return shared buffer offset, it's a Uint32Array + * format |render info offset|attach info offset| + */ + se_object_ptr getSharedBufferOffset() const; + + void setColor(float r, float g, float b, float a); + + void setDebugBonesEnabled(bool enabled) { + _debugDraw = enabled; + } + + void setBatchEnabled(bool enabled); + + void setAttachEnabled(bool enabled) { + _useAttach = enabled; + } + + void setOpacityModifyRGB(bool value) { + _premultipliedAlpha = value; + } + + /** + * @brief Convert component position to global position. + * @param[in] pos Component position + * @return Global position + */ + const cc::Vec2 &convertToRootSpace(float x, float y) const; + + /** + * @return root display,if this diplay is root,then return itself. + */ + CCArmatureDisplay *getRootDisplay(); + + cc::RenderDrawInfo *requestDrawInfo(int idx); + cc::Material *requestMaterial(uint16_t blendSrc, uint16_t blendDst); + void setMaterial(cc::Material *material); + void setRenderEntity(cc::RenderEntity *entity); + +private: + std::map _listenerIDMap; + int _preBlendMode = -1; + int _curBlendSrc = -1; + int _curBlendDst = -1; + cc::Texture2D *_preTexture = nullptr; + cc::Texture2D *_curTexture = nullptr; + cc::RenderDrawInfo *_curDrawInfo = nullptr; + + int _preISegWritePos = -1; + int _curISegLen = 0; + + int _debugSlotsLen = 0; + int _materialLen = 0; + + bool _useAttach = false; + bool _premultipliedAlpha = false; + bool _enableBatch = false; + + // NOTE: We bind Vec2 to make JS deserialization works, we need to return const reference in convertToRootSpace method, + // because returning Vec2 JSB object on stack to JS will let JS get mess data. + mutable cc::Vec2 _tmpVec2; + // + cc::middleware::Color4F _nodeColor = cc::middleware::Color4F::WHITE; + dbEventCallback _dbEventCallback = nullptr; + + cc::middleware::IOTypedArray *_sharedBufferOffset = nullptr; + cc::middleware::IOTypedArray *_debugBuffer = nullptr; + + cc::RenderEntity *_entity = nullptr; + cc::Material *_material = nullptr; + ccstd::vector _drawInfoArray; + ccstd::unordered_map _materialCaches; +}; + +DRAGONBONES_NAMESPACE_END + +#endif // DRAGONBONES_CC_ARMATURE_DISPLAY_CONTAINER_H diff --git a/cocos/editor-support/dragonbones-creator-support/CCDragonBonesHeaders.h b/cocos/editor-support/dragonbones-creator-support/CCDragonBonesHeaders.h new file mode 100644 index 0000000..b877468 --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCDragonBonesHeaders.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef DRAGONBONES_CC_HEADERS_H +#define DRAGONBONES_CC_HEADERS_H + +#include "dragonbones-creator-support/ArmatureCacheMgr.h" +#include "dragonbones-creator-support/CCArmatureCacheDisplay.h" +#include "dragonbones-creator-support/CCArmatureDisplay.h" +#include "dragonbones-creator-support/CCFactory.h" +#include "dragonbones-creator-support/CCSlot.h" +#include "dragonbones-creator-support/CCTextureAtlasData.h" + +#endif // DRAGONBONES_CC_HEADERS_H diff --git a/cocos/editor-support/dragonbones-creator-support/CCFactory.cpp b/cocos/editor-support/dragonbones-creator-support/CCFactory.cpp new file mode 100644 index 0000000..6af3c7e --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCFactory.cpp @@ -0,0 +1,210 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "dragonbones-creator-support/CCFactory.h" +#include "dragonbones-creator-support/CCArmatureDisplay.h" +#include "dragonbones-creator-support/CCSlot.h" +#include "dragonbones-creator-support/CCTextureAtlasData.h" +#include "platform/FileUtils.h" + +using namespace cc; + +DRAGONBONES_NAMESPACE_BEGIN + +DragonBones *CCFactory::_dragonBonesInstance = nullptr; +CCFactory *CCFactory::_factory = nullptr; + +TextureAtlasData *CCFactory::_buildTextureAtlasData(TextureAtlasData *textureAtlasData, void *textureAtlas) const { + if (textureAtlasData != nullptr) { + const auto pos = _prevPath.find_last_of("/"); + if (pos != std::string::npos) { + const auto basePath = _prevPath.substr(0, pos + 1); + textureAtlasData->imagePath = basePath + textureAtlasData->imagePath; + } + + if (textureAtlas != nullptr) { + static_cast(textureAtlasData)->setRenderTexture(static_cast(textureAtlas)); + } + } else { + textureAtlasData = BaseObject::borrowObject(); + } + + return textureAtlasData; +} + +Armature *CCFactory::_buildArmature(const BuildArmaturePackage &dataPackage) const { + const auto armature = BaseObject::borrowObject(); + const auto armatureDisplay = CCArmatureDisplay::create(); + + // will release when armature destructor + armatureDisplay->addRef(); + + armature->init( + dataPackage.armature, + armatureDisplay, armatureDisplay, _dragonBones); + + return armature; +} + +Slot *CCFactory::_buildSlot(const BuildArmaturePackage &dataPackage, const SlotData *slotData, Armature *armature) const { + const auto slot = BaseObject::borrowObject(); + + slot->init( + slotData, armature, + slot, slot); + + return slot; +} + +DragonBonesData *CCFactory::loadDragonBonesData(const std::string &filePath, const std::string &name, float scale) { + if (!name.empty()) { + const auto existedData = getDragonBonesData(name); + if (existedData) { + return existedData; + } + } + + const auto fullpath = cc::FileUtils::getInstance()->fullPathForFilename(filePath); + if (cc::FileUtils::getInstance()->isFileExist(filePath)) { + const auto pos = fullpath.find(".json"); + + if (pos != std::string::npos) { + const auto data = cc::FileUtils::getInstance()->getStringFromFile(filePath); + + return parseDragonBonesData(data.c_str(), name, scale); + } else { + cc::Data cocos2dData; + cc::FileUtils::getInstance()->getContents(fullpath, &cocos2dData); + uint8_t *binary = cocos2dData.takeBuffer(); + // NOTE: binary is freed in DragonBonesData::_onClear + return parseDragonBonesData(reinterpret_cast(binary), name, scale); + } + } + + return nullptr; +} + +DragonBonesData *CCFactory::parseDragonBonesDataByPath(const std::string &filePath, const std::string &name, float scale) { + if (!name.empty()) { + const auto existedData = getDragonBonesData(name); + if (existedData) { + return existedData; + } + } + + const auto dbbinPos = filePath.find(".dbbin"); + if (dbbinPos != std::string::npos) { + const auto fullpath = cc::FileUtils::getInstance()->fullPathForFilename(filePath); + if (cc::FileUtils::getInstance()->isFileExist(filePath)) { + cc::Data cocos2dData; + cc::FileUtils::getInstance()->getContents(fullpath, &cocos2dData); + uint8_t *binary = cocos2dData.takeBuffer(); + // NOTE: binary is freed in DragonBonesData::_onClear + return parseDragonBonesData(reinterpret_cast(binary), name, scale); + } + } else { + return parseDragonBonesData(filePath.c_str(), name, scale); + } + + return nullptr; +} + +DragonBonesData *CCFactory::getDragonBonesDataByUUID(const std::string &uuid) { + DragonBonesData *bonesData = nullptr; + for (auto it = _dragonBonesDataMap.begin(); it != _dragonBonesDataMap.end();) { + if (it->first.find(uuid) != std::string::npos) { + bonesData = it->second; + break; + } else { + it++; + } + } + return bonesData; +} + +void CCFactory::removeDragonBonesDataByUUID(const std::string &uuid, bool disposeData) { + for (auto it = _dragonBonesDataMap.begin(); it != _dragonBonesDataMap.end();) { + if (it->first.find(uuid) != std::string::npos) { + if (disposeData) { + it->second->returnToPool(); + } + it = _dragonBonesDataMap.erase(it); + } else { + it++; + } + } +} + +TextureAtlasData *CCFactory::loadTextureAtlasData(const std::string &filePath, const std::string &name, float scale) { + _prevPath = cc::FileUtils::getInstance()->fullPathForFilename(filePath); + const auto data = cc::FileUtils::getInstance()->getStringFromFile(_prevPath); + if (data.empty()) { + return nullptr; + } + + return static_cast(BaseFactory::parseTextureAtlasData(data.c_str(), nullptr, name, scale)); +} + +CCArmatureDisplay *CCFactory::buildArmatureDisplay(const std::string &armatureName, const std::string &dragonBonesName, const std::string &skinName, const std::string &textureAtlasName) const { + const auto armature = buildArmature(armatureName, dragonBonesName, skinName, textureAtlasName); + if (armature != nullptr) { + return static_cast(armature->getDisplay()); + } + + return nullptr; +} + +void CCFactory::removeTextureAtlasDataByIndex(const std::string &name, int textureIndex) { + const auto iterator = _textureAtlasDataMap.find(name); + if (iterator != _textureAtlasDataMap.end()) { + auto &textureAtlasDataList = iterator->second; + for (auto it = textureAtlasDataList.begin(); it != textureAtlasDataList.end(); it++) { + middleware::Texture2D *texture = ((CCTextureAtlasData *)*it)->getRenderTexture(); + if (texture && texture->getRealTextureIndex() == textureIndex) { + textureAtlasDataList.erase(it); + break; + } + } + if (textureAtlasDataList.size() == 0) { + _textureAtlasDataMap.erase(iterator); + } + } +} + +CCTextureAtlasData *CCFactory::getTextureAtlasDataByIndex(const std::string &name, int textureIndex) const { + const auto iterator = _textureAtlasDataMap.find(name); + if (iterator != _textureAtlasDataMap.end()) { + auto &textureAtlasDataList = iterator->second; + for (auto it = textureAtlasDataList.begin(); it != textureAtlasDataList.end(); it++) { + middleware::Texture2D *texture = ((CCTextureAtlasData *)*it)->getRenderTexture(); + if (texture && texture->getRealTextureIndex() == textureIndex) { + return (CCTextureAtlasData *)*it; + } + } + } + return nullptr; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones-creator-support/CCFactory.h b/cocos/editor-support/dragonbones-creator-support/CCFactory.h new file mode 100644 index 0000000..6c319bf --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCFactory.h @@ -0,0 +1,255 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef DRAGONBONES_CC_FACTORY_H +#define DRAGONBONES_CC_FACTORY_H + +#include "MiddlewareManager.h" +#include "dragonbones-creator-support/CCArmatureDisplay.h" +#include "dragonbones/DragonBonesHeaders.h" + +DRAGONBONES_NAMESPACE_BEGIN + +class CCTextureAtlasData; + +/** + * The Cocos2d factory. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * Cocos2d 工厂。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class CCFactory : public BaseFactory, public cc::middleware::IMiddleware { + DRAGONBONES_DISALLOW_COPY_AND_ASSIGN(CCFactory) + +private: + static DragonBones *_dragonBonesInstance; + static CCFactory *_factory; + +public: + static bool isInit() { + return _factory != nullptr; + } + + /** + * A global factory instance that can be used directly. + * @version DragonBones 4.7 + * @language en_US + */ + /** + * 一个可以直接使用的全局工厂实例。 + * @version DragonBones 4.7 + * @language zh_CN + */ + static CCFactory *getFactory() { + if (CCFactory::_factory == nullptr) { + CCFactory::_factory = new CCFactory(); + } + + return CCFactory::_factory; + } + + static void destroyFactory() { + if (_dragonBonesInstance) { + delete _dragonBonesInstance; + _dragonBonesInstance = nullptr; + } + + if (_factory) { + delete _factory; + _factory = nullptr; + } + } + +protected: + std::string _prevPath; + +public: + /** + * @inheritDoc + */ + CCFactory() : _prevPath() { + if (_dragonBonesInstance == nullptr) { + const auto eventManager = CCArmatureDisplay::create(); + eventManager->addRef(); + + _dragonBonesInstance = new DragonBones(eventManager); + + cc::middleware::MiddlewareManager::getInstance()->addTimer(this); + } + + _dragonBones = _dragonBonesInstance; + } + + virtual void update(float dt) override { + _dragonBonesInstance->advanceTime(dt); + } + + virtual void render(float dt) override { + _dragonBonesInstance->render(); + } + + /** + * @note When script engine clean up is trigger,will stop dragonbones timer. + */ + void stopSchedule() { + cc::middleware::MiddlewareManager::getInstance()->removeTimer(this); + } + + /** + * @note Destructor call by jsb_dragonbones_manual,when script engine clean up is trigger. + */ + virtual ~CCFactory() { + clear(false); + } + +protected: + virtual TextureAtlasData *_buildTextureAtlasData(TextureAtlasData *textureAtlasData, void *textureAtlas) const override; + virtual Armature *_buildArmature(const BuildArmaturePackage &dataPackage) const override; + virtual Slot *_buildSlot(const BuildArmaturePackage &dataPackage, const SlotData *slotData, Armature *armature) const override; + +public: + virtual DragonBonesData *loadDragonBonesData(const std::string &filePath, const std::string &name = "", float scale = 1.0f); + /** + * - Load and parse a texture atlas data and texture from the local and cache them to the factory. + * @param filePath - The file path of texture atlas data. + * @param name - Specify a cache name for the instance so that the instance can be obtained through this name. (If not set, use the instance name instead) + * @param scale - Specify a scaling value for the map set. (Not scaled by default) + * @returns The TextureAtlasData instance. + * @version DragonBones 4.5 + * @example + *
+     *     factory.loadTextureAtlasData("hero_tex.json");
+     * 
+ * @language en_US + */ + /** + * - 从本地加载并解析一个贴图集数据和贴图并缓存到工厂中。 + * @param filePath - 贴图集数据文件路径。 + * @param name - 为该实例指定一个缓存名称,以便可以通过此名称获取该实例。 (如果未设置,则使用该实例中的名称) + * @param scale - 为贴图集指定一个缩放值。 (默认不缩放) + * @returns TextureAtlasData 实例。 + * @version DragonBones 4.5 + * @example + *
+     *     factory.loadTextureAtlasData("hero_tex.json");
+     * 
+ * @language zh_CN + */ + virtual TextureAtlasData *loadTextureAtlasData(const std::string &filePath, const std::string &name = "", float scale = 1.0f); + /** + * - Create a armature from cached DragonBonesData instances and TextureAtlasData instances, then use the {@link #clock} to update it. + * The difference is that the armature created by {@link #buildArmature} is not WorldClock instance update. + * @param armatureName - The armature data name. + * @param dragonBonesName - The cached name of the DragonBonesData instance. (If not set, all DragonBonesData instances are retrieved, and when multiple DragonBonesData instances contain a the same name armature data, it may not be possible to accurately create a specific armature) + * @param skinName - The skin name, you can set a different ArmatureData name to share it's skin data. (If not set, use the default skin data) + * @returns The armature display container. + * @version DragonBones 4.5 + * @example + *
+     *     let armatureDisplay = factory.buildArmatureDisplay("armatureName", "dragonBonesName");
+     * 
+ * @language en_US + */ + /** + * - 通过缓存的 DragonBonesData 实例和 TextureAtlasData 实例创建一个骨架,并用 {@link #clock} 更新该骨架。 + * 区别在于由 {@link #buildArmature} 创建的骨架没有 WorldClock 实例驱动。 + * @param armatureName - 骨架数据名称。 + * @param dragonBonesName - DragonBonesData 实例的缓存名称。 (如果未设置,将检索所有的 DragonBonesData 实例,当多个 DragonBonesData 实例中包含同名的骨架数据时,可能无法准确的创建出特定的骨架) + * @param skinName - 皮肤名称,可以设置一个其他骨架数据名称来共享其皮肤数据。 (如果未设置,则使用默认的皮肤数据) + * @returns 骨架的显示容器。 + * @version DragonBones 4.5 + * @example + *
+     *     let armatureDisplay = factory.buildArmatureDisplay("armatureName", "dragonBonesName");
+     * 
+ * @language zh_CN + */ + virtual CCArmatureDisplay *buildArmatureDisplay(const std::string &armatureName, const std::string &dragonBonesName = "", const std::string &skinName = "", const std::string &textureAtlasName = "") const; + /** + * - A global sound event manager. + * Sound events can be listened to uniformly from the manager. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 全局声音事件管理器。 + * 声音事件可以从该管理器统一侦听。 + * @version DragonBones 4.5 + * @language zh_CN + */ + virtual CCArmatureDisplay *getSoundEventManager() const { + return dynamic_cast(static_cast(_dragonBones->getEventManager())); + } + + /** + * Deprecated, please refer to {@link #clock}. + * @deprecated + * @language en_US + */ + /** + * 已废弃,请参考 {@link #clock}。 + * @deprecated + * @language zh_CN + */ + static WorldClock *getClock() { + return _dragonBonesInstance->getClock(); + } + + void add(Armature *armature) { + _dragonBonesInstance->getClock()->add(armature); + } + + void remove(Armature *armature) { + _dragonBonesInstance->getClock()->remove(armature); + } + + void setTimeScale(float timeScale) { + _dragonBonesInstance->getClock()->timeScale = timeScale; + } + + float getTimeScale() { + return _dragonBonesInstance->getClock()->timeScale; + } + + DragonBones *getDragonBones() { + return _dragonBonesInstance; + } + + DragonBonesData *getDragonBonesDataByUUID(const std::string &uuid); + + void removeTextureAtlasDataByIndex(const std::string &name, int textureIndex); + void removeDragonBonesDataByUUID(const std::string &uuid, bool disposeData = true); + + CCTextureAtlasData *getTextureAtlasDataByIndex(const std::string &name, int textureIndex) const; + DragonBonesData *parseDragonBonesDataByPath(const std::string &filePath, const std::string &name = "", float scale = 1.0f); +}; + +DRAGONBONES_NAMESPACE_END + +#endif // DRAGONBONES_CC_FACTORY_H diff --git a/cocos/editor-support/dragonbones-creator-support/CCSlot.cpp b/cocos/editor-support/dragonbones-creator-support/CCSlot.cpp new file mode 100644 index 0000000..b902db3 --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCSlot.cpp @@ -0,0 +1,455 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "dragonbones-creator-support/CCSlot.h" +#include "dragonbones-creator-support/CCArmatureDisplay.h" +#include "dragonbones-creator-support/CCTextureAtlasData.h" + +using namespace cc; + +DRAGONBONES_NAMESPACE_BEGIN + +void CCSlot::_onClear() { + Slot::_onClear(); + disposeTriangles(); + _localMatrix.setIdentity(); + worldMatrix.setIdentity(); + _worldMatDirty = true; +} + +void CCSlot::disposeTriangles() { + if (worldVerts) { + delete[] worldVerts; + worldVerts = nullptr; + } + if (triangles.verts) { + delete[] triangles.verts; + triangles.verts = nullptr; + } + if (triangles.indices) { + delete[] triangles.indices; + triangles.indices = nullptr; + } + triangles.indexCount = 0; + triangles.vertCount = 0; +} + +void CCSlot::adjustTriangles(const unsigned vertexCount, const unsigned indicesCount) { + if (triangles.vertCount < vertexCount) { + if (triangles.verts) { + delete[] triangles.verts; + } + triangles.verts = new middleware::V3F_T2F_C4B[vertexCount]; + + if (worldVerts) { + delete[] worldVerts; + } + worldVerts = new middleware::V3F_T2F_C4B[vertexCount]; + } + triangles.vertCount = vertexCount; + + if (triangles.indexCount < indicesCount) { + if (triangles.indices) { + delete[] triangles.indices; + } + triangles.indices = new unsigned short[indicesCount]; + } + triangles.indexCount = indicesCount; +} + +void CCSlot::_initDisplay(void *value, bool isRetain) { +} + +void CCSlot::_disposeDisplay(void *value, bool isRelease) { +} + +void CCSlot::_onUpdateDisplay() { +} + +void CCSlot::_addDisplay() { + _visible = true; +} + +void CCSlot::_replaceDisplay(void *value, bool isArmatureDisplay) { +} + +void CCSlot::_removeDisplay() { + _visible = false; +} + +void CCSlot::_updateZOrder() { +} + +void CCSlot::_updateVisible() { + _visible = _parent->getVisible(); +} + +middleware::Texture2D *CCSlot::getTexture() const { + const auto currentTextureData = static_cast(_textureData); + if (!currentTextureData || !currentTextureData->spriteFrame) { + return nullptr; + } + return currentTextureData->spriteFrame->getTexture(); +} + +void CCSlot::_updateFrame() { + const auto currentVerticesData = (_deformVertices != nullptr && _display == _meshDisplay) ? _deformVertices->verticesData : nullptr; + const auto currentTextureData = static_cast(_textureData); + + if (_displayIndex >= 0 && _display != nullptr && currentTextureData != nullptr) { + if (currentTextureData->spriteFrame != nullptr) { + const auto ®ion = currentTextureData->region; + const auto texture = currentTextureData->spriteFrame->getTexture(); + const auto textureWidth = texture->getPixelsWide(); + const auto textureHeight = texture->getPixelsHigh(); + + if (currentVerticesData != nullptr) // Mesh. + { + const auto data = currentVerticesData->data; + const auto intArray = data->intArray; + const auto floatArray = data->floatArray; + const unsigned vertexCount = intArray[currentVerticesData->offset + (unsigned)BinaryOffset::MeshVertexCount]; + const unsigned triangleCount = intArray[currentVerticesData->offset + (unsigned)BinaryOffset::MeshTriangleCount]; + int vertexOffset = intArray[currentVerticesData->offset + (unsigned)BinaryOffset::MeshFloatOffset]; + + if (vertexOffset < 0) { + vertexOffset += 65536; // Fixed out of bouds bug. + } + + const unsigned uvOffset = vertexOffset + vertexCount * 2; + const unsigned indicesCount = triangleCount * 3; + adjustTriangles(vertexCount, indicesCount); + auto vertices = triangles.verts; + auto vertexIndices = triangles.indices; + + boundsRect.x = 999999.0f; + boundsRect.y = 999999.0f; + boundsRect.width = -999999.0f; + boundsRect.height = -999999.0f; + + for (std::size_t i = 0, l = vertexCount * 2; i < l; i += 2) { + const auto iH = i / 2; + const auto x = floatArray[vertexOffset + i]; + const auto y = floatArray[vertexOffset + i + 1]; + auto u = floatArray[uvOffset + i]; + auto v = floatArray[uvOffset + i + 1]; + middleware::V3F_T2F_C4B &vertexData = vertices[iH]; + vertexData.vertex.x = x; + vertexData.vertex.y = -y; + + if (currentTextureData->rotated) { + vertexData.texCoord.u = (region.x + (1.0f - v) * region.width) / textureWidth; + vertexData.texCoord.v = (region.y + u * region.height) / textureHeight; + } else { + vertexData.texCoord.u = (region.x + u * region.width) / textureWidth; + vertexData.texCoord.v = (region.y + v * region.height) / textureHeight; + } + + vertexData.color = cc::middleware::Color4B::WHITE; + + if (boundsRect.x > x) { + boundsRect.x = x; + } + + if (boundsRect.width < x) { + boundsRect.width = x; + } + + if (boundsRect.y > -y) { + boundsRect.y = -y; + } + + if (boundsRect.height < -y) { + boundsRect.height = -y; + } + } + + boundsRect.width -= boundsRect.x; + boundsRect.height -= boundsRect.y; + + for (std::size_t i = 0; i < triangleCount * 3; ++i) { + vertexIndices[i] = intArray[currentVerticesData->offset + (unsigned)BinaryOffset::MeshVertexIndices + i]; + } + + const auto isSkinned = currentVerticesData->weight != nullptr; + if (isSkinned) { + _identityTransform(); + } + } else { + adjustTriangles(4, 6); + + auto vertices = triangles.verts; + auto vertexIndices = triangles.indices; + + float l = region.x / textureWidth; + float b = (region.y + region.height) / textureHeight; + float r = (region.x + region.width) / textureWidth; + float t = region.y / textureHeight; + + vertices[0].texCoord.u = l; + vertices[0].texCoord.v = b; + vertices[1].texCoord.u = r; + vertices[1].texCoord.v = b; + vertices[2].texCoord.u = l; + vertices[2].texCoord.v = t; + vertices[3].texCoord.u = r; + vertices[3].texCoord.v = t; + + vertices[0].vertex.x = vertices[2].vertex.x = 0; + vertices[1].vertex.x = vertices[3].vertex.x = region.width; + vertices[0].vertex.y = vertices[1].vertex.y = 0; + vertices[2].vertex.y = vertices[3].vertex.y = region.height; + + vertexIndices[0] = 0; + vertexIndices[1] = 1; + vertexIndices[2] = 2; + vertexIndices[3] = 1; + vertexIndices[4] = 3; + vertexIndices[5] = 2; + } + + memcpy(worldVerts, triangles.verts, triangles.vertCount * sizeof(middleware::V3F_T2F_C4B)); + + _visibleDirty = true; + _blendModeDirty = true; // Relpace texture will override blendMode and color. + _colorDirty = true; + + return; + } + } +} + +void CCSlot::_updateMesh() { + const auto scale = _armature->_armatureData->scale; + const auto &deformVertices = _deformVertices->vertices; + const auto &bones = _deformVertices->bones; + const auto verticesData = _deformVertices->verticesData; + const auto weightData = verticesData->weight; + + const auto hasFFD = !deformVertices.empty(); + const auto textureData = static_cast(_textureData); + const auto vertices = triangles.verts; + + boundsRect.x = 999999.0f; + boundsRect.y = 999999.0f; + boundsRect.width = -999999.0f; + boundsRect.height = -999999.0f; + + if (!textureData) { + return; + } + + if (weightData != nullptr) { + const auto data = verticesData->data; + const auto intArray = data->intArray; + const auto floatArray = data->floatArray; + const auto vertexCount = (std::size_t)intArray[verticesData->offset + (unsigned)BinaryOffset::MeshVertexCount]; + int weightFloatOffset = intArray[weightData->offset + (unsigned)BinaryOffset::WeigthFloatOffset]; + + if (vertexCount > triangles.vertCount) { + return; + } + + if (weightFloatOffset < 0) { + weightFloatOffset += 65536; // Fixed out of bouds bug. + } + + for ( + std::size_t i = 0, iB = weightData->offset + (unsigned)BinaryOffset::WeigthBoneIndices + bones.size(), iV = (std::size_t)weightFloatOffset, iF = 0; + i < vertexCount; + ++i) { + const auto boneCount = (std::size_t)intArray[iB++]; + auto xG = 0.0f, yG = 0.0f; + for (std::size_t j = 0; j < boneCount; ++j) { + const auto boneIndex = (unsigned)intArray[iB++]; + const auto bone = bones[boneIndex]; + if (bone != nullptr) { + const auto &matrix = bone->globalTransformMatrix; + const auto weight = floatArray[iV++]; + auto xL = floatArray[iV++] * scale; + auto yL = floatArray[iV++] * scale; + + if (hasFFD) { + xL += deformVertices[iF++]; + yL += deformVertices[iF++]; + } + + xG += (matrix.a * xL + matrix.c * yL + matrix.tx) * weight; + yG += (matrix.b * xL + matrix.d * yL + matrix.ty) * weight; + } + } + + auto &vertex = vertices[i]; + auto &vertexPosition = vertex.vertex; + + vertexPosition.x = xG; + vertexPosition.y = -yG; + + if (boundsRect.x > xG) { + boundsRect.x = xG; + } + + if (boundsRect.width < xG) { + boundsRect.width = xG; + } + + if (boundsRect.y > -yG) { + boundsRect.y = -yG; + } + + if (boundsRect.height < -yG) { + boundsRect.height = -yG; + } + } + } else if (hasFFD) { + const auto data = verticesData->data; + const auto intArray = data->intArray; + const auto floatArray = data->floatArray; + const auto vertexCount = (std::size_t)intArray[verticesData->offset + (unsigned)BinaryOffset::MeshVertexCount]; + std::size_t vertexOffset = (std::size_t)intArray[verticesData->offset + (unsigned)BinaryOffset::MeshFloatOffset]; + + if (vertexCount > triangles.vertCount) { + return; + } + + if (vertexOffset < 0) { + vertexOffset += 65536; // Fixed out of bouds bug. + } + + for (std::size_t i = 0, l = vertexCount * 2; i < l; i += 2) { + const auto iH = i / 2; + const auto xG = floatArray[vertexOffset + i] * scale + deformVertices[i]; + const auto yG = floatArray[vertexOffset + i + 1] * scale + deformVertices[i + 1]; + + auto &vertex = vertices[iH]; + auto &vertexPosition = vertex.vertex; + + vertexPosition.x = xG; + vertexPosition.y = -yG; + + if (boundsRect.x > xG) { + boundsRect.x = xG; + } + + if (boundsRect.width < xG) { + boundsRect.width = xG; + } + + if (boundsRect.y > -yG) { + boundsRect.y = -yG; + } + + if (boundsRect.height < -yG) { + boundsRect.height = -yG; + } + } + } + + boundsRect.width -= boundsRect.x; + boundsRect.height -= boundsRect.y; + + if (weightData != nullptr) { + _identityTransform(); + } +} + +void CCSlot::_updateTransform() { + _localMatrix.m[0] = globalTransformMatrix.a; + _localMatrix.m[1] = globalTransformMatrix.b; + _localMatrix.m[4] = -globalTransformMatrix.c; + _localMatrix.m[5] = -globalTransformMatrix.d; + + if (_childArmature) { + _localMatrix.m[12] = globalTransformMatrix.tx; + _localMatrix.m[13] = globalTransformMatrix.ty; + } else { + _localMatrix.m[12] = globalTransformMatrix.tx - (globalTransformMatrix.a * _pivotX - globalTransformMatrix.c * _pivotY); + _localMatrix.m[13] = globalTransformMatrix.ty - (globalTransformMatrix.b * _pivotX - globalTransformMatrix.d * _pivotY); + } + + _worldMatDirty = true; +} + +void CCSlot::updateWorldMatrix() { + if (!_armature) return; + + CCSlot *parent = (CCSlot *)_armature->getParent(); + if (parent) { + parent->updateWorldMatrix(); + } + + if (_worldMatDirty) { + calculWorldMatrix(); + + Armature *childArmature = getChildArmature(); + if (!childArmature) return; + + auto &slots = childArmature->getSlots(); + for (int i = 0; i < slots.size(); i++) { + CCSlot *slot = (CCSlot *)slots[i]; + slot->_worldMatDirty = true; + } + } +} + +void CCSlot::calculWorldMatrix() { + CCSlot *parent = (CCSlot *)_armature->getParent(); + if (parent) { + worldMatrix = parent->worldMatrix * _localMatrix; + } else { + worldMatrix = _localMatrix; + } + + _worldMatDirty = false; +} + +void CCSlot::_identityTransform() { + _localMatrix.m[0] = 1.0f; + _localMatrix.m[1] = 0.0f; + _localMatrix.m[4] = -0.0f; + _localMatrix.m[5] = -1.0f; + _localMatrix.m[12] = 0.0f; + _localMatrix.m[13] = 0.0f; + + _worldMatDirty = true; +} + +void CCSlot::_updateBlendMode() { + if (_childArmature != nullptr) { + for (const auto slot : _childArmature->getSlots()) { + slot->_blendMode = _blendMode; + slot->_updateBlendMode(); + } + } +} + +void CCSlot::_updateColor() { + color.r = _colorTransform.redMultiplier * 255.0f; + color.g = _colorTransform.greenMultiplier * 255.0f; + color.b = _colorTransform.blueMultiplier * 255.0f; + color.a = _colorTransform.alphaMultiplier * 255.0f; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones-creator-support/CCSlot.h b/cocos/editor-support/dragonbones-creator-support/CCSlot.h new file mode 100644 index 0000000..9cdaaff --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCSlot.h @@ -0,0 +1,95 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef DRAGONBONES_CC_SLOT_H +#define DRAGONBONES_CC_SLOT_H + +#include "dragonbones/DragonBonesHeaders.h" +#include "math/Geometry.h" +#include "math/Mat4.h" +#include "middleware-adapter.h" + +DRAGONBONES_NAMESPACE_BEGIN + +/** + * The Cocos2d slot. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * Cocos2d 插槽。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class CCSlot : public Slot { + BIND_CLASS_TYPE_A(CCSlot); + +public: + // Global matrix (相对于骨骼部件) + cc::Mat4 worldMatrix; + // Global matrix dirty flag + bool _worldMatDirty = true; + // Slot triangles + cc::middleware::Triangles triangles; + // Slot vertex transform to World vertex + cc::middleware::V3F_T2F_C4B *worldVerts = nullptr; + cc::middleware::Color4B color; + cc::Rect boundsRect; + +private: + cc::Mat4 _localMatrix; + +private: + void disposeTriangles(); + void calculWorldMatrix(); + void adjustTriangles(const unsigned vertexCount, const unsigned indicesCount); + +protected: + virtual void _onClear() override; + virtual void _initDisplay(void *value, bool isRetain) override; + virtual void _disposeDisplay(void *value, bool isRelease) override; + virtual void _onUpdateDisplay() override; + virtual void _addDisplay() override; + virtual void _replaceDisplay(void *value, bool isArmatureDisplay) override; + virtual void _removeDisplay() override; + virtual void _updateZOrder() override; + +public: + virtual void _updateVisible() override; + virtual void _updateBlendMode() override; + virtual void _updateColor() override; + void updateWorldMatrix(); + cc::middleware::Texture2D *getTexture() const; + +protected: + virtual void _updateFrame() override; + virtual void _updateMesh() override; + virtual void _updateTransform() override; + virtual void _identityTransform() override; +}; + +DRAGONBONES_NAMESPACE_END + +#endif // DRAGONBONES_CC_SLOT_H diff --git a/cocos/editor-support/dragonbones-creator-support/CCTextureAtlasData.cpp b/cocos/editor-support/dragonbones-creator-support/CCTextureAtlasData.cpp new file mode 100644 index 0000000..ec9341a --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCTextureAtlasData.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "dragonbones-creator-support/CCTextureAtlasData.h" + +using namespace cc; + +DRAGONBONES_NAMESPACE_BEGIN + +void CCTextureAtlasData::_onClear() { + TextureAtlasData::_onClear(); + + if (_renderTexture != nullptr) { + _renderTexture->release(); + _renderTexture = nullptr; + } +} + +TextureData *CCTextureAtlasData::createTexture() const { + return (TextureData *)BaseObject::borrowObject(); +} + +void CCTextureAtlasData::setRenderTexture(middleware::Texture2D *value) { + if (_renderTexture == value) { + return; + } + + _renderTexture = value; + + if (_renderTexture != nullptr) { + _renderTexture->addRef(); + + for (const auto &pair : textures) { + const auto textureData = static_cast(pair.second); + + if (textureData->spriteFrame == nullptr) { + cc::Rect rect( + textureData->region.x, textureData->region.y, + textureData->rotated ? textureData->region.height : textureData->region.width, + textureData->rotated ? textureData->region.width : textureData->region.height); + cc::Vec2 offset(0.0f, 0.0f); + cc::Size originSize(rect.width, rect.height); + textureData->spriteFrame = middleware::SpriteFrame::createWithTexture(_renderTexture, rect, textureData->rotated, offset, originSize); // TODO multiply textureAtlas + textureData->spriteFrame->addRef(); + } + } + } else { + for (const auto &pair : textures) { + const auto textureData = static_cast(pair.second); + + if (textureData->spriteFrame != nullptr) { + textureData->spriteFrame->release(); + textureData->spriteFrame = nullptr; + } + } + } +} + +void CCTextureData::_onClear() { + TextureData::_onClear(); + + if (spriteFrame != nullptr) { + spriteFrame->release(); + spriteFrame = nullptr; + } +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones-creator-support/CCTextureAtlasData.h b/cocos/editor-support/dragonbones-creator-support/CCTextureAtlasData.h new file mode 100644 index 0000000..2643ec5 --- /dev/null +++ b/cocos/editor-support/dragonbones-creator-support/CCTextureAtlasData.h @@ -0,0 +1,104 @@ +/**************************************************************************** + Copyright (c) 2012-2020 DragonBones team and other contributors + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef DRAGONBONES_CC_TEXTUREATLAS_DATA_H +#define DRAGONBONES_CC_TEXTUREATLAS_DATA_H + +#include "dragonbones/DragonBonesHeaders.h" +#include "middleware-adapter.h" + +DRAGONBONES_NAMESPACE_BEGIN + +/** + * The Cocos2d texture atlas data. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * Cocos2d 贴图集数据。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class CCTextureAtlasData : public TextureAtlasData { + BIND_CLASS_TYPE_B(CCTextureAtlasData); + +private: + cc::middleware::Texture2D *_renderTexture; + +public: + CCTextureAtlasData() : _renderTexture(nullptr) { + _onClear(); + } + virtual ~CCTextureAtlasData() { + _onClear(); + } + +protected: + virtual void _onClear() override; + +public: + /** + * @inheritDoc + */ + virtual TextureData *createTexture() const override; + /** + * The Cocos2d texture. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * Cocos2d 贴图。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline cc::middleware::Texture2D *getRenderTexture() const { + return _renderTexture; + } + void setRenderTexture(cc::middleware::Texture2D *value); +}; + +/** + * @internal + */ +class CCTextureData : public TextureData { + BIND_CLASS_TYPE_B(CCTextureData); + +public: + cc::middleware::SpriteFrame *spriteFrame; + + CCTextureData() : spriteFrame(nullptr) { + _onClear(); + } + virtual ~CCTextureData() { + _onClear(); + } + +protected: + virtual void _onClear() override; +}; + +DRAGONBONES_NAMESPACE_END + +#endif // DRAGONBONES_CC_TEXTUREATLAS_DATA_H diff --git a/cocos/editor-support/dragonbones/DragonBonesHeaders.h b/cocos/editor-support/dragonbones/DragonBonesHeaders.h new file mode 100644 index 0000000..9fa5643 --- /dev/null +++ b/cocos/editor-support/dragonbones/DragonBonesHeaders.h @@ -0,0 +1,82 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_HEADERS_H +#define DRAGONBONES_HEADERS_H + +// core +#include "core/BaseObject.h" +#include "core/DragonBones.h" + +// geom +#include "geom/ColorTransform.h" +#include "geom/Matrix.h" +#include "geom/Point.h" +#include "geom/Rectangle.h" +#include "geom/Transform.h" + +// model +#include "model/AnimationConfig.h" +#include "model/AnimationData.h" +#include "model/ArmatureData.h" +#include "model/BoundingBoxData.h" +#include "model/CanvasData.h" +#include "model/ConstraintData.h" +#include "model/DisplayData.h" +#include "model/DragonBonesData.h" +#include "model/SkinData.h" +#include "model/TextureAtlasData.h" +#include "model/UserData.h" + +// armature +#include "armature/Armature.h" +#include "armature/Bone.h" +#include "armature/Constraint.h" +#include "armature/DeformVertices.h" +#include "armature/IArmatureProxy.h" +#include "armature/Slot.h" +#include "armature/TransformObject.h" + +// animation +#include "animation/Animation.h" +#include "animation/AnimationState.h" +#include "animation/BaseTimelineState.h" +#include "animation/IAnimatable.h" +#include "animation/TimelineState.h" +#include "animation/WorldClock.h" + +// event +#include "event/EventObject.h" +#include "event/IEventDispatcher.h" + +#ifndef EGRET_WASM + + // parser + #include "parser/BinaryDataParser.h" + #include "parser/DataParser.h" + #include "parser/JSONDataParser.h" + + // factory + #include "factory/BaseFactory.h" +#endif // EGRET_WASM + +#endif // DRAGONBONES_HEADERS_H diff --git a/cocos/editor-support/dragonbones/animation/Animation.cpp b/cocos/editor-support/dragonbones/animation/Animation.cpp new file mode 100644 index 0000000..daa790e --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/Animation.cpp @@ -0,0 +1,485 @@ +#include "Animation.h" +#include "../armature/Armature.h" +#include "../armature/Bone.h" +#include "../armature/Slot.h" +#include "../model/AnimationConfig.h" +#include "../model/AnimationData.h" +#include "../model/DisplayData.h" +#include "AnimationState.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void Animation::_onClear() { + for (const auto animationState : _animationStates) { + animationState->returnToPool(); + } + + if (_animationConfig != nullptr) { + _animationConfig->returnToPool(); + } + + timeScale = 1.0f; + + _animationDirty = false; + _inheritTimeScale = 1.0f; + _animations.clear(); + _animationNames.clear(); + _animationStates.clear(); + _armature = nullptr; + _animationConfig = nullptr; + _lastAnimationState = nullptr; +} + +void Animation::_fadeOut(AnimationConfig* animationConfig) { + switch (animationConfig->fadeOutMode) { + case AnimationFadeOutMode::SameLayer: + for (const auto animationState : _animationStates) { + if (animationState->layer == (unsigned)animationConfig->layer) { + animationState->fadeOut(animationConfig->fadeOutTime, animationConfig->pauseFadeOut); + } + } + break; + + case AnimationFadeOutMode::SameGroup: + for (const auto animationState : _animationStates) { + if (animationState->group == animationConfig->group) { + animationState->fadeOut(animationConfig->fadeOutTime, animationConfig->pauseFadeOut); + } + } + break; + + case AnimationFadeOutMode::SameLayerAndGroup: + for (const auto animationState : _animationStates) { + if (animationState->layer == (unsigned)animationConfig->layer && animationState->group == animationConfig->group) { + animationState->fadeOut(animationConfig->fadeOutTime, animationConfig->pauseFadeOut); + } + } + break; + + case AnimationFadeOutMode::All: + for (const auto animationState : _animationStates) { + animationState->fadeOut(animationConfig->fadeOutTime, animationConfig->pauseFadeOut); + } + break; + + case AnimationFadeOutMode::None: + case AnimationFadeOutMode::Single: + default: + break; + } +} + +void Animation::init(Armature* armature) { + if (_armature != nullptr) { + return; + } + + _armature = armature; + _animationConfig = BaseObject::borrowObject(); +} + +void Animation::advanceTime(float passedTime) { + if (passedTime < 0.0f) { + passedTime = -passedTime; + } + + if (_armature->inheritAnimation && _armature->_parent != nullptr) // Inherit parent animation timeScale. + { + _inheritTimeScale = _armature->_parent->_armature->getAnimation()->_inheritTimeScale * timeScale; + } else { + _inheritTimeScale = timeScale; + } + + if (_inheritTimeScale != 1.0f) { + passedTime *= _inheritTimeScale; + } + + const auto animationStateCount = _animationStates.size(); + if (animationStateCount == 1) { + const auto animationState = _animationStates[0]; + if (animationState->_fadeState > 0 && animationState->_subFadeState > 0) { + _armature->_dragonBones->bufferObject(animationState); + _animationStates.clear(); + _lastAnimationState = nullptr; + } else { + const auto animationData = animationState->_animationData; + const auto cacheFrameRate = animationData->cacheFrameRate; + if (_animationDirty && cacheFrameRate > 0.0f) // Update cachedFrameIndices. + { + _animationDirty = false; + + for (const auto bone : _armature->getBones()) { + bone->_cachedFrameIndices = animationData->getBoneCachedFrameIndices(bone->getName()); + } + + for (const auto slot : _armature->getSlots()) { + const auto rawDisplayDatas = slot->getRawDisplayDatas(); + if (rawDisplayDatas != nullptr && !(*rawDisplayDatas).empty()) { + const auto rawDsplayData = (*rawDisplayDatas)[0]; + if (rawDsplayData != nullptr) { + if (rawDsplayData->parent == _armature->getArmatureData()->defaultSkin) { + slot->_cachedFrameIndices = animationData->getSlotCachedFrameIndices(slot->getName()); + continue; + } + } + } + + slot->_cachedFrameIndices = nullptr; + } + } + + animationState->advanceTime(passedTime, cacheFrameRate); + } + } else if (animationStateCount > 1) { + for (std::size_t i = 0, r = 0; i < animationStateCount; ++i) { + const auto animationState = _animationStates[i]; + if (animationState->_fadeState > 0 && animationState->_subFadeState > 0) { + r++; + _armature->_dragonBones->bufferObject(animationState); + _animationDirty = true; + if (_lastAnimationState == animationState) { + _lastAnimationState = nullptr; + } + } else { + if (r > 0) { + _animationStates[i - r] = animationState; + } + + animationState->advanceTime(passedTime, 0.0f); + } + + if (i == animationStateCount - 1 && r > 0) { + _animationStates.resize(animationStateCount - r); + if (_lastAnimationState == nullptr && !_animationStates.empty()) { + _lastAnimationState = _animationStates[_animationStates.size() - 1]; + } + } + } + + _armature->_cacheFrameIndex = -1; + } else { + _armature->_cacheFrameIndex = -1; + } +} + +void Animation::reset() { + for (const auto animationState : _animationStates) { + animationState->returnToPool(); + } + + _animationDirty = false; + _animationConfig->clear(); + _animationStates.clear(); + _lastAnimationState = nullptr; +} + +void Animation::stop(const std::string& animationName) { + if (!animationName.empty()) { + const auto animationState = getState(animationName); + if (animationState != nullptr) { + animationState->stop(); + } + } else { + for (const auto animationState : _animationStates) { + animationState->stop(); + } + } +} + +AnimationState* Animation::playConfig(AnimationConfig* animationConfig) { + const auto& animationName = animationConfig->animation; + if (_animations.find(animationName) == _animations.end()) { + DRAGONBONES_ASSERT( + false, + "Non-existent animation.\n" + + " DragonBones name: " + this->_armature->getArmatureData().parent->name + + " Armature name: " + this->_armature->name + + " Animation name: " + animationName); + + return nullptr; + } + + const auto animationData = _animations[animationName]; + + if (animationConfig->fadeOutMode == AnimationFadeOutMode::Single) { + for (const auto animationState : _animationStates) { + if (animationState->_animationData == animationData) { + return animationState; + } + } + } + + if (animationConfig->fadeInTime < 0.0f) { + if (_animationStates.empty()) { + animationConfig->fadeInTime = 0.0f; + } else { + animationConfig->fadeInTime = animationData->fadeInTime; + } + } + + if (animationConfig->fadeOutTime < 0.0f) { + animationConfig->fadeOutTime = animationConfig->fadeInTime; + } + + if (animationConfig->timeScale <= -100.0f) { + animationConfig->timeScale = 1.0f / animationData->scale; + } + + if (animationData->frameCount > 1) { + if (animationConfig->position < 0.0f) { + animationConfig->position = fmod(animationConfig->position, animationData->duration); + animationConfig->position = animationData->duration - animationConfig->position; + } else if (animationConfig->position == animationData->duration) { + animationConfig->position -= 0.000001f; // Play a little time before end. + } else if (animationConfig->position > animationData->duration) { + animationConfig->position = fmod(animationConfig->position, animationData->duration); + } + + if (animationConfig->duration > 0.0f && animationConfig->position + animationConfig->duration > animationData->duration) { + animationConfig->duration = animationData->duration - animationConfig->position; + } + + if (animationConfig->playTimes < 0) { + animationConfig->playTimes = animationData->playTimes; + } + } else { + animationConfig->playTimes = 1; + animationConfig->position = 0.0f; + if (animationConfig->duration > 0.0f) { + animationConfig->duration = 0.0f; + } + } + + if (animationConfig->duration == 0.0f) { + animationConfig->duration = -1.0f; + } + + _fadeOut(animationConfig); + + const auto animationState = BaseObject::borrowObject(); + animationState->init(_armature, animationData, animationConfig); + _animationDirty = true; + _armature->_cacheFrameIndex = -1; + + if (!_animationStates.empty()) { + auto added = false; + for (std::size_t i = 0, l = _animationStates.size(); i < l; ++i) { + if (animationState->layer > _animationStates[i]->layer) { + added = true; + auto parentInerator = std::find(_animationStates.begin(), _animationStates.end(), _animationStates[i]); + _animationStates.insert(parentInerator, animationState); + break; + } else if (i != l - 1 && animationState->layer > _animationStates[i + 1]->layer) { + added = true; + auto parentInerator = std::find(_animationStates.begin(), _animationStates.end(), _animationStates[i]); + _animationStates.insert(parentInerator + 1, animationState); + break; + } + } + + if (!added) { + _animationStates.push_back(animationState); + } + } else { + _animationStates.push_back(animationState); + } + + // Child armature play same name animation. + for (const auto slot : _armature->getSlots()) { + const auto childArmature = slot->getChildArmature(); + if ( + childArmature != nullptr && childArmature->inheritAnimation && + childArmature->getAnimation()->hasAnimation(animationName) && + childArmature->getAnimation()->getState(animationName) == nullptr) { + childArmature->getAnimation()->fadeIn(animationName); // + } + } + + if (animationConfig->fadeInTime <= 0.0f) // Blend animation state, update armature. + { + _armature->advanceTime(0.0f); + } + + _lastAnimationState = animationState; + + return animationState; +} + +AnimationState* Animation::play(const std::string& animationName, int playTimes) { + _animationConfig->clear(); + _animationConfig->resetToPose = true; + _animationConfig->playTimes = playTimes; + _animationConfig->fadeInTime = 0.0f; + _animationConfig->animation = animationName; + + if (!animationName.empty()) { + playConfig(_animationConfig); + } else if (_lastAnimationState == nullptr) { + const auto defaultAnimation = _armature->_armatureData->defaultAnimation; + if (defaultAnimation != nullptr) { + _animationConfig->animation = defaultAnimation->name; + playConfig(_animationConfig); + } + } else if (!_lastAnimationState->isPlaying() && !_lastAnimationState->isCompleted()) { + _lastAnimationState->play(); + } else { + _animationConfig->animation = _lastAnimationState->name; + playConfig(_animationConfig); + } + + return _lastAnimationState; +} +#ifdef EGRET_WASM +AnimationState* Animation::fadeIn( + const std::string& animationName, float fadeInTime, int playTimes, + int layer, const std::string& group, int fadeOutMode /*AnimationFadeOutMode*/ +#else +AnimationState* Animation::fadeIn( + const std::string& animationName, float fadeInTime, int playTimes, + int layer, const std::string& group, AnimationFadeOutMode fadeOutMode +#endif // EGRET_WASM +) { + _animationConfig->clear(); + _animationConfig->fadeOutMode = (AnimationFadeOutMode)fadeOutMode; + _animationConfig->playTimes = playTimes; + _animationConfig->layer = layer; + _animationConfig->fadeInTime = fadeInTime; + _animationConfig->animation = animationName; + _animationConfig->group = group; + + return playConfig(_animationConfig); +} + +AnimationState* Animation::gotoAndPlayByTime(const std::string& animationName, float time, int playTimes) { + _animationConfig->clear(); + _animationConfig->resetToPose = true; + _animationConfig->playTimes = playTimes; + _animationConfig->position = time; + _animationConfig->fadeInTime = 0.0f; + _animationConfig->animation = animationName; + + return playConfig(_animationConfig); +} + +AnimationState* Animation::gotoAndPlayByFrame(const std::string& animationName, unsigned frame, int playTimes) { + _animationConfig->clear(); + _animationConfig->resetToPose = true; + _animationConfig->playTimes = playTimes; + _animationConfig->fadeInTime = 0.0f; + _animationConfig->animation = animationName; + + const auto animationData = mapFind(_animations, animationName); + if (animationData != nullptr) { + _animationConfig->position = animationData->duration * frame / animationData->frameCount; + } + + return playConfig(_animationConfig); +} + +AnimationState* Animation::gotoAndPlayByProgress(const std::string& animationName, float progress, int playTimes) { + _animationConfig->clear(); + _animationConfig->resetToPose = true; + _animationConfig->playTimes = playTimes; + _animationConfig->fadeInTime = 0.0f; + _animationConfig->animation = animationName; + + const auto animationData = mapFind(_animations, animationName); + if (animationData != nullptr) { + _animationConfig->position = animationData->duration * (progress > 0.0f ? progress : 0.0f); + } + + return playConfig(_animationConfig); +} + +AnimationState* Animation::gotoAndStopByTime(const std::string& animationName, float time) { + const auto animationState = gotoAndPlayByTime(animationName, time, 1); + if (animationState != nullptr) { + animationState->stop(); + } + + return animationState; +} + +AnimationState* Animation::gotoAndStopByFrame(const std::string& animationName, unsigned frame) { + const auto animationState = gotoAndPlayByFrame(animationName, frame, 1); + if (animationState != nullptr) { + animationState->stop(); + } + + return animationState; +} + +AnimationState* Animation::gotoAndStopByProgress(const std::string& animationName, float progress) { + const auto animationState = gotoAndPlayByProgress(animationName, progress, 1); + if (animationState != nullptr) { + animationState->stop(); + } + + return animationState; +} + +AnimationState* Animation::getState(const std::string& animationName) const { + int i = _animationStates.size(); + while (i--) { + const auto animationState = _animationStates[i]; + if (animationState->name == animationName) { + return animationState; + } + } + + return nullptr; +} + +bool Animation::hasAnimation(const std::string& animationName) const { + return _animations.find(animationName) != _animations.end(); +} + +bool Animation::isPlaying() const { + for (const auto animationState : _animationStates) { + if (animationState->isPlaying()) { + return true; + } + } + + return false; +} + +bool Animation::isCompleted() const { + for (const auto animationState : _animationStates) { + if (!animationState->isCompleted()) { + return false; + } + } + + return !_animationStates.empty(); +} + +const std::string& Animation::getLastAnimationName() const { + if (_lastAnimationState != nullptr) { + return _lastAnimationState->name; + } + + static const std::string DEFAULT_NAME = ""; + return DEFAULT_NAME; +} + +void Animation::setAnimations(const std::map& value) { + if (_animations == value) { + return; + } + + _animationNames.clear(); + _animations.clear(); + + for (const auto& pair : value) { + _animationNames.push_back(pair.first); + _animations[pair.first] = pair.second; + } +} + +AnimationConfig* Animation::getAnimationConfig() const { + _animationConfig->clear(); + return _animationConfig; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/animation/Animation.h b/cocos/editor-support/dragonbones/animation/Animation.h new file mode 100644 index 0000000..8c3ca02 --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/Animation.h @@ -0,0 +1,469 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_ANIMATION_H +#define DRAGONBONES_ANIMATION_H + +#include "../core/BaseObject.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The animation player is used to play the animation data and manage the animation states. + * @see dragonBones.AnimationData + * @see dragonBones.AnimationState + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 动画播放器用来播放动画数据和管理动画状态。 + * @see dragonBones.AnimationData + * @see dragonBones.AnimationState + * @version DragonBones 3.0 + * @language zh_CN + */ +class Animation final : public BaseObject { + BIND_CLASS_TYPE_B(Animation); + +public: + /** + * - The play speed of all animations. [0: Stop, (0~1): Slow, 1: Normal, (1~N): Fast] + * @default 1.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 所有动画的播放速度。 [0: 停止播放, (0~1): 慢速播放, 1: 正常播放, (1~N): 快速播放] + * @default 1.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float timeScale; + +private: + bool _animationDirty; + float _inheritTimeScale; + std::vector _animationNames; + std::vector _animationStates; + std::map _animations; + Armature* _armature; + AnimationConfig* _animationConfig; + AnimationState* _lastAnimationState; + +public: + Animation() : _animationConfig(nullptr) { + _onClear(); + } + ~Animation() { + _onClear(); + } + +private: + void _fadeOut(AnimationConfig* animationConfig); + +protected: + virtual void _onClear() override; + +public: + /** + * @internal + */ + void init(Armature* armature); + /** + * @internal + */ + void advanceTime(float passedTime); + /** + * - Clear all animations states. + * @see dragonBones.AnimationState + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 清除所有的动画状态。 + * @see dragonBones.AnimationState + * @version DragonBones 4.5 + * @language zh_CN + */ + void reset(); + /** + * - Pause a specific animation state. + * @param animationName - The name of animation state. (If not set, it will pause all animations) + * @see dragonBones.AnimationState + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 暂停指定动画状态的播放。 + * @param animationName - 动画状态名称。 (如果未设置,则暂停所有动画) + * @see dragonBones.AnimationState + * @version DragonBones 3.0 + * @language zh_CN + */ + void stop(const std::string& animationName = ""); + /** + * - Play animation with a specific animation config. + * The API is still in the experimental phase and may encounter bugs or stability or compatibility issues when used. + * @param animationConfig - The animation config. + * @returns The playing animation state. + * @see dragonBones.AnimationConfig + * @beta + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 通过指定的动画配置来播放动画。 + * 该 API 仍在实验阶段,使用时可能遭遇 bug 或稳定性或兼容性问题。 + * @param animationConfig - 动画配置。 + * @returns 播放的动画状态。 + * @see dragonBones.AnimationConfig + * @beta + * @version DragonBones 5.0 + * @language zh_CN + */ + AnimationState* playConfig(AnimationConfig* animationConfig); + /** + * - Play a specific animation. + * @param animationName - The name of animation data. (If not set, The default animation will be played, or resume the animation playing from pause status, or replay the last playing animation) + * @param playTimes - Playing repeat times. [-1: Use default value of the animation data, 0: No end loop playing, [1~N]: Repeat N times] (default: -1) + * @returns The playing animation state. + * @example + * TypeScript style, for reference only. + *
+     *     armature.animation.play("walk");
+     * 
+ * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 播放指定动画。 + * @param animationName - 动画数据名称。 (如果未设置,则播放默认动画,或将暂停状态切换为播放状态,或重新播放之前播放的动画) + * @param playTimes - 循环播放次数。 [-1: 使用动画数据默认值, 0: 无限循环播放, [1~N]: 循环播放 N 次] (默认: -1) + * @returns 播放的动画状态。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     armature.animation.play("walk");
+     * 
+ * @version DragonBones 3.0 + * @language zh_CN + */ + AnimationState* play(const std::string& animationName = "", int playTimes = -1); + /** + * - Fade in a specific animation. + * @param animationName - The name of animation data. + * @param fadeInTime - The fade in time. [-1: Use the default value of animation data, [0~N]: The fade in time (In seconds)] (Default: -1) + * @param playTimes - playing repeat times. [-1: Use the default value of animation data, 0: No end loop playing, [1~N]: Repeat N times] (Default: -1) + * @param layer - The blending layer, the animation states in high level layer will get the blending weights with high priority, when the total blending weights are more than 1.0, there will be no more weights can be allocated to the other animation states. (Default: 0) + * @param group - The blending group name, it is typically used to specify the substitution of multiple animation states blending. (Default: null) + * @param fadeOutMode - The fade out mode, which is typically used to specify alternate mode of multiple animation states blending. (Default: AnimationFadeOutMode.SameLayerAndGroup) + * @returns The playing animation state. + * @example + * TypeScript style, for reference only. + *
+     *     armature.animation.fadeIn("walk", 0.3, 0, 0, "normalGroup").resetToPose = false;
+     *     armature.animation.fadeIn("attack", 0.3, 1, 0, "attackGroup").resetToPose = false;
+     * 
+ * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 淡入播放指定的动画。 + * @param animationName - 动画数据名称。 + * @param fadeInTime - 淡入时间。 [-1: 使用动画数据默认值, [0~N]: 淡入时间 (以秒为单位)] (默认: -1) + * @param playTimes - 播放次数。 [-1: 使用动画数据默认值, 0: 无限循环播放, [1~N]: 循环播放 N 次] (默认: -1) + * @param layer - 混合图层,图层高的动画状态会优先获取混合权重,当混合权重分配总和超过 1.0 时,剩余的动画状态将不能再获得权重分配。 (默认: 0) + * @param group - 混合组名称,该属性通常用来指定多个动画状态混合时的相互替换关系。 (默认: null) + * @param fadeOutMode - 淡出模式,该属性通常用来指定多个动画状态混合时的相互替换模式。 (默认: AnimationFadeOutMode.SameLayerAndGroup) + * @returns 播放的动画状态。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     armature.animation.fadeIn("walk", 0.3, 0, 0, "normalGroup").resetToPose = false;
+     *     armature.animation.fadeIn("attack", 0.3, 1, 0, "attackGroup").resetToPose = false;
+     * 
+ * @version DragonBones 4.5 + * @language zh_CN + */ + AnimationState* fadeIn( + const std::string& animationName, float fadeInTime = -1.f, int playTimes = -1, + int layer = 0, const std::string& group = "", AnimationFadeOutMode fadeOutMode = AnimationFadeOutMode::SameLayerAndGroup); + /** + * - Play a specific animation from the specific time. + * @param animationName - The name of animation data. + * @param time - The start time point of playing. (In seconds) + * @param playTimes - Playing repeat times. [-1: Use the default value of animation data, 0: No end loop playing, [1~N]: Repeat N times] (Default: -1) + * @returns The played animation state. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 从指定时间开始播放指定的动画。 + * @param animationName - 动画数据名称。 + * @param time - 播放开始的时间。 (以秒为单位) + * @param playTimes - 循环播放次数。 [-1: 使用动画数据默认值, 0: 无限循环播放, [1~N]: 循环播放 N 次] (默认: -1) + * @returns 播放的动画状态。 + * @version DragonBones 4.5 + * @language zh_CN + */ + AnimationState* gotoAndPlayByTime(const std::string& animationName, float time = 0.f, int playTimes = -1); + /** + * - Play a specific animation from the specific frame. + * @param animationName - The name of animation data. + * @param frame - The start frame of playing. + * @param playTimes - Playing repeat times. [-1: Use the default value of animation data, 0: No end loop playing, [1~N]: Repeat N times] (Default: -1) + * @returns The played animation state. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 从指定帧开始播放指定的动画。 + * @param animationName - 动画数据名称。 + * @param frame - 播放开始的帧数。 + * @param playTimes - 播放次数。 [-1: 使用动画数据默认值, 0: 无限循环播放, [1~N]: 循环播放 N 次] (默认: -1) + * @returns 播放的动画状态。 + * @version DragonBones 4.5 + * @language zh_CN + */ + AnimationState* gotoAndPlayByFrame(const std::string& animationName, unsigned frame = 0, int playTimes = -1); + /** + * - Play a specific animation from the specific progress. + * @param animationName - The name of animation data. + * @param progress - The start progress value of playing. + * @param playTimes - Playing repeat times. [-1: Use the default value of animation data, 0: No end loop playing, [1~N]: Repeat N times] (Default: -1) + * @returns The played animation state. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 从指定进度开始播放指定的动画。 + * @param animationName - 动画数据名称。 + * @param progress - 开始播放的进度。 + * @param playTimes - 播放次数。 [-1: 使用动画数据默认值, 0: 无限循环播放, [1~N]: 循环播放 N 次] (默认: -1) + * @returns 播放的动画状态。 + * @version DragonBones 4.5 + * @language zh_CN + */ + AnimationState* gotoAndPlayByProgress(const std::string& animationName, float progress = 0.f, int playTimes = -1); + /** + * - Stop a specific animation at the specific time. + * @param animationName - The name of animation data. + * @param time - The stop time. (In seconds) + * @returns The played animation state. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 在指定时间停止指定动画播放 + * @param animationName - 动画数据名称。 + * @param time - 停止的时间。 (以秒为单位) + * @returns 播放的动画状态。 + * @version DragonBones 4.5 + * @language zh_CN + */ + AnimationState* gotoAndStopByTime(const std::string& animationName, float time = 0.f); + /** + * - Stop a specific animation at the specific frame. + * @param animationName - The name of animation data. + * @param frame - The stop frame. + * @returns The played animation state. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 在指定帧停止指定动画的播放 + * @param animationName - 动画数据名称。 + * @param frame - 停止的帧数。 + * @returns 播放的动画状态。 + * @version DragonBones 4.5 + * @language zh_CN + */ + AnimationState* gotoAndStopByFrame(const std::string& animationName, unsigned frame = 0); + /** + * - Stop a specific animation at the specific progress. + * @param animationName - The name of animation data. + * @param progress - The stop progress value. + * @returns The played animation state. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 在指定的进度停止指定的动画播放。 + * @param animationName - 动画数据名称。 + * @param progress - 停止进度。 + * @returns 播放的动画状态。 + * @version DragonBones 4.5 + * @language zh_CN + */ + AnimationState* gotoAndStopByProgress(const std::string& animationName, float progress = 0.f); + /** + * - Get a specific animation state. + * @param animationName - The name of animation state. + * @example + * TypeScript style, for reference only. + *
+     *     armature.animation.play("walk");
+     *     let walkState = armature.animation.getState("walk");
+     *     walkState.timeScale = 0.5;
+     * 
+ * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取指定的动画状态 + * @param animationName - 动画状态名称。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     armature.animation.play("walk");
+     *     let walkState = armature.animation.getState("walk");
+     *     walkState.timeScale = 0.5;
+     * 
+ * @version DragonBones 3.0 + * @language zh_CN + */ + AnimationState* getState(const std::string& animationName) const; + /** + * - Check whether a specific animation data is included. + * @param animationName - The name of animation data. + * @see dragonBones.AnimationData + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 检查是否包含指定的动画数据 + * @param animationName - 动画数据名称。 + * @see dragonBones.AnimationData + * @version DragonBones 3.0 + * @language zh_CN + */ + bool hasAnimation(const std::string& animationName) const; + /** + * - Get all the animation states. + * @version DragonBones 5.1 + * @language en_US + */ + /** + * - 获取所有的动画状态 + * @version DragonBones 5.1 + * @language zh_CN + */ + inline const std::vector& getStates() const { + return _animationStates; + } + /** + * - Check whether there is an animation state is playing + * @see dragonBones.AnimationState + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 检查是否有动画状态正在播放 + * @see dragonBones.AnimationState + * @version DragonBones 3.0 + * @language zh_CN + */ + bool isPlaying() const; + /** + * - Check whether all the animation states' playing were finished. + * @see dragonBones.AnimationState + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 检查是否所有的动画状态均已播放完毕。 + * @see dragonBones.AnimationState + * @version DragonBones 3.0 + * @language zh_CN + */ + bool isCompleted() const; + /** + * - The name of the last playing animation state. + * @see #lastAnimationState + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 上一个播放的动画状态名称 + * @see #lastAnimationState + * @version DragonBones 3.0 + * @language zh_CN + */ + const std::string& getLastAnimationName() const; + /** + * - The name of all animation data + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 所有动画数据的名称 + * @version DragonBones 4.5 + * @language zh_CN + */ + inline const std::vector& getAnimationNames() const { + return _animationNames; + } + /** + * - All animation data. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 所有的动画数据。 + * @version DragonBones 4.5 + * @language zh_CN + */ + inline const std::map& getAnimations() const { + return _animations; + } + void setAnimations(const std::map& value); + /** + * - An AnimationConfig instance that can be used quickly. + * @see dragonBones.AnimationConfig + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 一个可以快速使用的动画配置实例。 + * @see dragonBones.AnimationConfig + * @version DragonBones 5.0 + * @language zh_CN + */ + AnimationConfig* getAnimationConfig() const; + /** + * - The last playing animation state + * @see dragonBones.AnimationState + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 上一个播放的动画状态 + * @see dragonBones.AnimationState + * @version DragonBones 3.0 + * @language zh_CN + */ + inline AnimationState* getLastAnimationState() const { + return _lastAnimationState; + } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_ANIMATION_H diff --git a/cocos/editor-support/dragonbones/animation/AnimationState.cpp b/cocos/editor-support/dragonbones/animation/AnimationState.cpp new file mode 100644 index 0000000..8b0efb6 --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/AnimationState.cpp @@ -0,0 +1,796 @@ +#include "AnimationState.h" +#include "../armature/Armature.h" +#include "../armature/Bone.h" +#include "../armature/Constraint.h" +#include "../armature/Slot.h" +#include "../event/EventObject.h" +#include "../event/IEventDispatcher.h" +#include "../model/AnimationConfig.h" +#include "../model/AnimationData.h" +#include "../model/DisplayData.h" +#include "TimelineState.h" +#include "WorldClock.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void AnimationState::_onClear() { + for (const auto timeline : _boneTimelines) { + timeline->returnToPool(); + } + + for (const auto timeline : _slotTimelines) { + timeline->returnToPool(); + } + + for (const auto timeline : _constraintTimelines) { + timeline->returnToPool(); + } + + for (const auto& pair : _bonePoses) { + pair.second->returnToPool(); + } + + if (_actionTimeline != nullptr) { + _actionTimeline->returnToPool(); + } + + if (_zOrderTimeline != nullptr) { + _zOrderTimeline->returnToPool(); + } + + actionEnabled = false; + additiveBlending = false; + displayControl = false; + resetToPose = false; + playTimes = 1; + layer = 0; + timeScale = 1.0f; + weight = 1.0f; + autoFadeOutTime = 0.0f; + fadeTotalTime = 0.0f; + name = ""; + group = ""; + + _timelineDirty = 2; + _playheadState = 0; + _fadeState = -1; + _subFadeState = -1; + _position = 0.0f; + _duration = 0.0f; + _fadeTime = 0.0f; + _time = 0.0f; + _fadeProgress = 0.0f; + _weightResult = 0.0f; + _boneMask.clear(); + _boneTimelines.clear(); + _slotTimelines.clear(); + _constraintTimelines.clear(); + _poseTimelines.clear(); + _bonePoses.clear(); + _animationData = nullptr; + _armature = nullptr; + _actionTimeline = nullptr; + _zOrderTimeline = nullptr; +} + +void AnimationState::_updateTimelines() { + { // Update constraint timelines. + std::map> constraintTimelines; + for (const auto timeline : _constraintTimelines) // Create constraint timelines map. + { + constraintTimelines[timeline->constraint->getName()].push_back(timeline); + } + + for (const auto constraint : _armature->_constraints) { + const auto& timelineName = constraint->getName(); + const auto timelineDatas = _animationData->getConstraintTimelines(timelineName); + const auto iterator = constraintTimelines.find(timelineName); + + if (iterator != constraintTimelines.end()) // Remove constraint timeline from map. + { + constraintTimelines.erase(iterator); + } else // Create new constraint timeline. + { + if (timelineDatas != nullptr) { + for (const auto timelineData : *timelineDatas) { + switch (timelineData->type) { + case TimelineType::IKConstraint: { + const auto timeline = BaseObject::borrowObject(); + timeline->constraint = constraint; + timeline->init(_armature, this, timelineData); + _constraintTimelines.push_back(timeline); + break; + } + + default: + break; + } + } + } else if (resetToPose) // Pose timeline. + { + const auto timeline = BaseObject::borrowObject(); + timeline->constraint = constraint; + timeline->init(_armature, this, nullptr); + _constraintTimelines.push_back(timeline); + _poseTimelines.push_back(std::make_pair(timeline, BaseTimelineType::Constraint)); + } + } + } + } +} + +void AnimationState::_updateBoneAndSlotTimelines() { + { // Update bone timelines. + std::map> boneTimelines; + for (const auto timeline : _boneTimelines) // Create bone timelines map. + { + boneTimelines[timeline->bone->getName()].push_back(timeline); + } + + for (const auto bone : _armature->getBones()) { + const auto& timelineName = bone->getName(); + if (!containsBoneMask(timelineName)) { + continue; + } + + const auto timelineDatas = _animationData->getBoneTimelines(timelineName); + const auto iterator = boneTimelines.find(timelineName); + + if (iterator != boneTimelines.end()) // Remove bone timeline from map. + { + boneTimelines.erase(iterator); + } else // Create new bone timeline. + { + const auto bonePose = _bonePoses.find(timelineName) != _bonePoses.end() ? _bonePoses[timelineName] : (_bonePoses[timelineName] = BaseObject::borrowObject()); + + if (timelineDatas != nullptr) { + for (const auto timelineData : *timelineDatas) { + switch (timelineData->type) { + case TimelineType::BoneAll: { + const auto timeline = BaseObject::borrowObject(); + timeline->bone = bone; + timeline->bonePose = bonePose; + timeline->init(_armature, this, timelineData); + _boneTimelines.push_back(timeline); + break; + } + + case TimelineType::BoneTranslate: { + const auto timeline = BaseObject::borrowObject(); + timeline->bone = bone; + timeline->bonePose = bonePose; + timeline->init(_armature, this, timelineData); + _boneTimelines.push_back(timeline); + break; + } + + case TimelineType::BoneRotate: { + const auto timeline = BaseObject::borrowObject(); + timeline->bone = bone; + timeline->bonePose = bonePose; + timeline->init(_armature, this, timelineData); + _boneTimelines.push_back(timeline); + break; + } + + case TimelineType::BoneScale: { + const auto timeline = BaseObject::borrowObject(); + timeline->bone = bone; + timeline->bonePose = bonePose; + timeline->init(_armature, this, timelineData); + _boneTimelines.push_back(timeline); + break; + } + + default: + break; + } + } + } else if (resetToPose) // Pose timeline. + { + const auto timeline = BaseObject::borrowObject(); + timeline->bone = bone; + timeline->bonePose = bonePose; + timeline->init(_armature, this, nullptr); + _boneTimelines.push_back(timeline); + _poseTimelines.push_back(std::make_pair(timeline, BaseTimelineType::Bone)); + } + } + } + + for (const auto& pair : boneTimelines) // Remove bone timelines. + { + for (const auto timeline : pair.second) { + _boneTimelines.erase(std::find(_boneTimelines.begin(), _boneTimelines.end(), timeline)); + timeline->returnToPool(); + } + } + } + + { // Update slot timelines. + std::map> slotTimelines; + std::vector ffdFlags; + for (const auto timeline : _slotTimelines) // Create slot timelines map. + { + slotTimelines[timeline->slot->getName()].push_back(timeline); + } + + for (const auto slot : _armature->getSlots()) { + const auto& boneName = slot->getParent()->getName(); + if (!containsBoneMask(boneName)) { + continue; + } + + const auto& timelineName = slot->getName(); + const auto timelineDatas = _animationData->getSlotTimelines(timelineName); + const auto iterator = slotTimelines.find(timelineName); + + if (iterator != slotTimelines.end()) // Remove slot timeline from map. + { + slotTimelines.erase(iterator); + } else // Create new slot timeline. + { + auto displayIndexFlag = false; + auto colorFlag = false; + ffdFlags.clear(); + + if (timelineDatas != nullptr) { + for (const auto timelineData : *timelineDatas) { + switch (timelineData->type) { + case TimelineType::SlotDisplay: { + const auto timeline = BaseObject::borrowObject(); + timeline->slot = slot; + timeline->init(_armature, this, timelineData); + _slotTimelines.push_back(timeline); + displayIndexFlag = true; + break; + } + + case TimelineType::SlotColor: { + const auto timeline = BaseObject::borrowObject(); + timeline->slot = slot; + timeline->init(_armature, this, timelineData); + _slotTimelines.push_back(timeline); + colorFlag = true; + break; + } + + case TimelineType::SlotDeform: { + const auto timeline = BaseObject::borrowObject(); + timeline->slot = slot; + timeline->init(_armature, this, timelineData); + _slotTimelines.push_back(timeline); + ffdFlags.push_back(timeline->vertexOffset); + break; + } + + default: + break; + } + } + } + + if (resetToPose) // Pose timeline. + { + if (!displayIndexFlag) { + const auto timeline = BaseObject::borrowObject(); + timeline->slot = slot; + timeline->init(_armature, this, nullptr); + _slotTimelines.push_back(timeline); + _poseTimelines.push_back(std::make_pair(timeline, BaseTimelineType::Slot)); + } + + if (!colorFlag) { + const auto timeline = BaseObject::borrowObject(); + timeline->slot = slot; + timeline->init(_armature, this, nullptr); + _slotTimelines.push_back(timeline); + _poseTimelines.push_back(std::make_pair(timeline, BaseTimelineType::Slot)); + } + + if (slot->getRawDisplayDatas() != nullptr) { + for (const auto displayData : *(slot->getRawDisplayDatas())) { + if (displayData != nullptr && displayData->type == DisplayType::Mesh) { + const auto meshOffset = static_cast(displayData)->vertices.offset; + if (std::find(ffdFlags.cbegin(), ffdFlags.cend(), meshOffset) == ffdFlags.cend()) { + const auto timeline = BaseObject::borrowObject(); + timeline->vertexOffset = meshOffset; + timeline->slot = slot; + timeline->init(_armature, this, nullptr); + _slotTimelines.push_back(timeline); + _poseTimelines.push_back(std::make_pair(timeline, BaseTimelineType::Slot)); + } + } + } + } + } + } + } + + for (const auto& pair : slotTimelines) // Remove slot timelines. + { + for (const auto timeline : pair.second) { + _slotTimelines.erase(std::find(_slotTimelines.begin(), _slotTimelines.end(), timeline)); + timeline->returnToPool(); + } + } + } +} + +void AnimationState::_advanceFadeTime(float passedTime) { + const auto isFadeOut = _fadeState > 0; + + if (_subFadeState < 0) { + _subFadeState = 0; + + const auto eventType = isFadeOut ? EventObject::FADE_OUT : EventObject::FADE_IN; + if (_armature->getProxy()->hasDBEventListener(eventType)) { + const auto eventObject = BaseObject::borrowObject(); + eventObject->type = eventType; + eventObject->armature = _armature; + eventObject->animationState = this; + _armature->_dragonBones->bufferEvent(eventObject); + } + } + + if (passedTime < 0.0f) { + passedTime = -passedTime; + } + + _fadeTime += passedTime; + + if (_fadeTime >= fadeTotalTime) { + _subFadeState = 1; + _fadeProgress = isFadeOut ? 0.0f : 1.0f; + } else if (_fadeTime > 0.0f) { + _fadeProgress = isFadeOut ? (1.0f - _fadeTime / fadeTotalTime) : (_fadeTime / fadeTotalTime); + } else { + _fadeProgress = isFadeOut ? 1.0f : 0.0f; + } + + if (_subFadeState > 0) { + if (!isFadeOut) { + _playheadState |= 1; // x1 + _fadeState = 0; + } + + const auto eventType = isFadeOut ? EventObject::FADE_OUT_COMPLETE : EventObject::FADE_IN_COMPLETE; + if (_armature->getProxy()->hasDBEventListener(eventType)) { + const auto eventObject = BaseObject::borrowObject(); + eventObject->type = eventType; + eventObject->armature = _armature; + eventObject->animationState = this; + _armature->_dragonBones->bufferEvent(eventObject); + } + } +} + +void AnimationState::init(Armature* parmature, AnimationData* panimationData, AnimationConfig* animationConfig) { + if (_armature != nullptr) { + return; + } + + _armature = parmature; + _animationData = panimationData; + // + resetToPose = animationConfig->resetToPose; + additiveBlending = animationConfig->additiveBlending; + displayControl = animationConfig->displayControl; + actionEnabled = animationConfig->actionEnabled; + layer = animationConfig->layer; + playTimes = animationConfig->playTimes; + timeScale = animationConfig->timeScale; + fadeTotalTime = animationConfig->fadeInTime; + autoFadeOutTime = animationConfig->autoFadeOutTime; + weight = animationConfig->weight; + name = !animationConfig->name.empty() ? animationConfig->name : animationConfig->animation; + group = animationConfig->group; + + if (animationConfig->pauseFadeIn) { + _playheadState = 2; // 10 + } else { + _playheadState = 3; // 11 + } + + if (animationConfig->duration < 0.0f) { + _position = 0.0f; + _duration = _animationData->duration; + + if (animationConfig->position != 0.0f) { + if (timeScale >= 0.0f) { + _time = animationConfig->position; + } else { + _time = animationConfig->position - _duration; + } + } else { + _time = 0.0f; + } + } else { + _position = animationConfig->position; + _duration = animationConfig->duration; + _time = 0.0f; + } + + if (timeScale < 0.0f && _time == 0.0f) { + _time = -0.000001f; // Can not cross last frame event. + } + + if (fadeTotalTime <= 0.0f) { + _fadeProgress = 0.999999f; + } + + if (!animationConfig->boneMask.empty()) { + _boneMask.resize(animationConfig->boneMask.size()); + for (std::size_t i = 0, l = _boneMask.size(); i < l; ++i) { + _boneMask[i] = animationConfig->boneMask[i]; + } + } + + _actionTimeline = BaseObject::borrowObject(); + _actionTimeline->init(_armature, this, _animationData->actionTimeline); // + _actionTimeline->currentTime = _time; + if (_actionTimeline->currentTime < 0.0f) { + _actionTimeline->currentTime = _duration - _actionTimeline->currentTime; + } + + if (_animationData->zOrderTimeline != nullptr) { + _zOrderTimeline = BaseObject::borrowObject(); + _zOrderTimeline->init(_armature, this, _animationData->zOrderTimeline); + } +} + +void AnimationState::advanceTime(float passedTime, float cacheFrameRate) { + // Update fade time. + if (_fadeState != 0 || _subFadeState != 0) { + _advanceFadeTime(passedTime); + } + + // Update time. + if (_playheadState == 3) // 11 + { + if (timeScale != 1.0f) { + passedTime *= timeScale; + } + + _time += passedTime; + } + + if (_timelineDirty != 0) { + if (_timelineDirty == 2) { + _updateTimelines(); + } + + _timelineDirty = 0; + _updateBoneAndSlotTimelines(); + } + + if (weight == 0.0f) { + return; + } + + const auto isCacheEnabled = _fadeState == 0 && cacheFrameRate > 0.0f; + auto isUpdateTimeline = true; + auto isUpdateBoneTimeline = true; + auto time = _time; + _weightResult = weight * _fadeProgress; + + if (_actionTimeline->playState <= 0) { + _actionTimeline->update(time); // Update main timeline. + } + + if (isCacheEnabled) // Cache time internval. + { + const auto internval = cacheFrameRate * 2.0f; + _actionTimeline->currentTime = (unsigned)(_actionTimeline->currentTime * internval) / internval; + } + + if (_zOrderTimeline != nullptr && _zOrderTimeline->playState <= 0) // Update zOrder timeline. + { + _zOrderTimeline->update(time); + } + + if (isCacheEnabled) // Update cache. + { + const auto cacheFrameIndex = (unsigned)(_actionTimeline->currentTime * cacheFrameRate); // uint + if ((unsigned)_armature->_cacheFrameIndex == cacheFrameIndex) // Same cache. + { + isUpdateTimeline = false; + isUpdateBoneTimeline = false; + } else { + _armature->_cacheFrameIndex = cacheFrameIndex; + if (_animationData->cachedFrames[cacheFrameIndex]) // Cached. + { + isUpdateBoneTimeline = false; + } else // Cache. + { + _animationData->cachedFrames[cacheFrameIndex] = true; + } + } + } + + if (isUpdateTimeline) { + if (isUpdateBoneTimeline) // Update bone timelines. + { + for (std::size_t i = 0, l = _boneTimelines.size(); i < l; ++i) { + const auto timeline = _boneTimelines[i]; + + if (timeline->playState <= 0) { + timeline->update(time); + } + + if (i == l - 1 || timeline->bone != _boneTimelines[i + 1]->bone) { + const auto state = timeline->bone->_blendState.update(_weightResult, layer); + if (state != 0) { + timeline->blend(state); + } + } + } + } + + if (displayControl) { + for (std::size_t i = 0, l = _slotTimelines.size(); i < l; ++i) { + const auto timeline = _slotTimelines[i]; + const auto& displayController = timeline->slot->displayController; + + if ( + displayController.empty() || + displayController == name || + displayController == group) { + if (timeline->playState <= 0) { + timeline->update(time); + } + } + } + } + + for (std::size_t i = 0, l = _constraintTimelines.size(); i < l; ++i) { + const auto timeline = _constraintTimelines[i]; + if (timeline->playState <= 0) { + timeline->update(time); + } + } + } + + if (_fadeState == 0) { + if (_subFadeState > 0) { + _subFadeState = 0; + + if (!_poseTimelines.empty()) { + for (const auto& pair : _poseTimelines) { + const auto timeline = pair.first; + if (pair.second == BaseTimelineType::Bone) { + _boneTimelines.erase(std::find(_boneTimelines.begin(), _boneTimelines.end(), timeline)); + } else if (pair.second == BaseTimelineType::Slot) { + _slotTimelines.erase(std::find(_slotTimelines.begin(), _slotTimelines.end(), timeline)); + } else if (pair.second == BaseTimelineType::Constraint) { + _constraintTimelines.erase(std::find(_constraintTimelines.begin(), _constraintTimelines.end(), timeline)); + } + + timeline->returnToPool(); + } + + _poseTimelines.clear(); + } + } + + if (_actionTimeline->playState > 0) { + if (autoFadeOutTime >= 0.0f) // Auto fade out. + { + fadeOut(autoFadeOutTime); + } + } + } +} + +void AnimationState::play() { + _playheadState = 3; // 11 +} + +void AnimationState::stop() { + _playheadState &= 1; // 0x +} + +void AnimationState::fadeOut(float fadeOutTime, bool pausePlayhead) { + if (fadeOutTime < 0.0f) { + fadeOutTime = 0.0f; + } + + if (pausePlayhead) { + _playheadState &= 2; // x0 + } + + if (_fadeState > 0) { + if (fadeOutTime > fadeTotalTime - _fadeTime) // If the animation is already in fade out, the new fade out will be ignored. + { + return; + } + } else { + _fadeState = 1; + _subFadeState = -1; + + if (fadeOutTime <= 0.0f || _fadeProgress <= 0.0f) { + _fadeProgress = 0.000001f; // Modify fade progress to different value. + } + + for (const auto timeline : _boneTimelines) { + timeline->fadeOut(); + } + + for (const auto timeline : _slotTimelines) { + timeline->fadeOut(); + } + } + + displayControl = false; // + fadeTotalTime = _fadeProgress > 0.000001f ? fadeOutTime / _fadeProgress : 0.0f; + _fadeTime = fadeTotalTime * (1.0f - _fadeProgress); +} + +bool AnimationState::containsBoneMask(const std::string& boneName) const { + return _boneMask.empty() || std::find(_boneMask.cbegin(), _boneMask.cend(), boneName) != _boneMask.cend(); +} + +void AnimationState::addBoneMask(const std::string& boneName, bool recursive) { + const auto currentBone = _armature->getBone(boneName); + if (currentBone == nullptr) { + return; + } + + if (std::find(_boneMask.cbegin(), _boneMask.cend(), boneName) == _boneMask.cend()) { + _boneMask.push_back(boneName); + } + + if (recursive) // Add recursive mixing. + { + for (const auto bone : _armature->getBones()) { + if (std::find(_boneMask.cbegin(), _boneMask.cend(), bone->getName()) == _boneMask.cend() && currentBone->contains(bone)) { + _boneMask.push_back(bone->getName()); + } + } + } + + _timelineDirty = 1; +} + +void AnimationState::removeBoneMask(const std::string& boneName, bool recursive) { + { + auto iterator = std::find(_boneMask.begin(), _boneMask.end(), boneName); + if (iterator != _boneMask.cend()) // Remove mixing. + { + _boneMask.erase(iterator); + } + } + + if (recursive) { + const auto currentBone = _armature->getBone(boneName); + if (currentBone != nullptr) { + const auto& bones = _armature->getBones(); + if (!_boneMask.empty()) // Remove recursive mixing. + { + for (const auto bone : bones) { + auto iterator = std::find(_boneMask.begin(), _boneMask.end(), bone->getName()); + if (iterator != _boneMask.end() && currentBone->contains(bone)) { + _boneMask.erase(iterator); + } + } + } else // Add unrecursive mixing. + { + for (const auto bone : bones) { + if (bone == currentBone) { + continue; + } + + if (!currentBone->contains(bone)) { + _boneMask.push_back(bone->getName()); + } + } + } + } + } + + _timelineDirty = 1; +} + +void AnimationState::removeAllBoneMask() { + _boneMask.clear(); + _timelineDirty = 1; +} + +bool AnimationState::isPlaying() const { + return (_playheadState & 2) != 0 && _actionTimeline->playState <= 0; +} + +bool AnimationState::isCompleted() const { + return _actionTimeline->playState > 0; +} + +unsigned AnimationState::getCurrentPlayTimes() const { + return _actionTimeline->currentPlayTimes; +} + +float AnimationState::getCurrentTime() const { + return _actionTimeline->currentTime; +} + +void AnimationState::setCurrentTime(float value) { + const auto currentPlayTimes = _actionTimeline->currentPlayTimes - (_actionTimeline->playState > 0 ? 1 : 0); + if (value < 0.0f || _duration < value) { + value = fmod(value, _duration) + currentPlayTimes * _duration; + if (value < 0.0f) { + value += _duration; + } + } + + if (playTimes > 0 && (unsigned)currentPlayTimes == playTimes - 1 && value == _duration) { + value = _duration - 0.000001f; + } + + if (_time == value) { + return; + } + + _time = value; + _actionTimeline->setCurrentTime(_time); + + if (_zOrderTimeline != nullptr) { + _zOrderTimeline->playState = -1; + } + + for (const auto timeline : _boneTimelines) { + timeline->playState = -1; + } + + for (const auto timeline : _slotTimelines) { + timeline->playState = -1; + } +} + +void BonePose::_onClear() { + current.identity(); + delta.identity(); + result.identity(); +} + +int BlendState::update(float weight, int p_layer) { + if (dirty) { + if (leftWeight > 0.0f) { + if (layer != p_layer) { + if (layerWeight >= leftWeight) { + leftWeight = 0.0f; + + return 0; + } else { + layer = p_layer; + leftWeight -= layerWeight; + layerWeight = 0.0f; + } + } + } else { + return 0; + } + + weight *= leftWeight; + layerWeight += weight; + blendWeight = weight; + + return 2; + } + + dirty = true; + layer = p_layer; + layerWeight = weight; + leftWeight = 1.0f; + blendWeight = weight; + + return 1; +} + +void BlendState::clear() { + dirty = false; + layer = 0; + leftWeight = 0.0f; + layerWeight = 0.0f; + blendWeight = 0.0f; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/animation/AnimationState.h b/cocos/editor-support/dragonbones/animation/AnimationState.h new file mode 100644 index 0000000..ba02b34 --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/AnimationState.h @@ -0,0 +1,522 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_ANIMATION_STATE_H +#define DRAGONBONES_ANIMATION_STATE_H + +#include "../core/BaseObject.h" +#include "../geom/Transform.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The animation state is generated when the animation data is played. + * @see dragonBones.Animation + * @see dragonBones.AnimationData + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 动画状态由播放动画数据时产生。 + * @see dragonBones.Animation + * @see dragonBones.AnimationData + * @version DragonBones 3.0 + * @language zh_CN + */ +class AnimationState : public BaseObject { + BIND_CLASS_TYPE_B(AnimationState); + +private: + enum class BaseTimelineType { + Bone, + Slot, + Constraint + }; + +public: + /** + * @private + */ + bool actionEnabled; + /** + * @private + */ + bool additiveBlending; + /** + * - Whether the animation state has control over the display object properties of the slots. + * Sometimes blend a animation state does not want it to control the display object properties of the slots, + * especially if other animation state are controlling the display object properties of the slots. + * @default true + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 动画状态是否对插槽的显示对象属性有控制权。 + * 有时混合一个动画状态并不希望其控制插槽的显示对象属性, + * 尤其是其他动画状态正在控制这些插槽的显示对象属性时。 + * @default true + * @version DragonBones 5.0 + * @language zh_CN + */ + bool displayControl; + /** + * - Whether to reset the objects without animation to the armature pose when the animation state is start to play. + * This property should usually be set to false when blend multiple animation states. + * @default true + * @version DragonBones 5.1 + * @language en_US + */ + /** + * - 开始播放动画状态时是否将没有动画的对象重置为骨架初始值。 + * 通常在混合多个动画状态时应该将该属性设置为 false。 + * @default true + * @version DragonBones 5.1 + * @language zh_CN + */ + bool resetToPose; + /** + * - The play times. [0: Loop play, [1~N]: Play N times] + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 播放次数。 [0: 无限循环播放, [1~N]: 循环播放 N 次] + * @version DragonBones 3.0 + * @language zh_CN + */ + unsigned playTimes; + /** + * - The blend layer. + * High layer animation state will get the blend weight first. + * When the blend weight is assigned more than 1, the remaining animation states will no longer get the weight assigned. + * @readonly + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 混合图层。 + * 图层高的动画状态会优先获取混合权重。 + * 当混合权重分配超过 1 时,剩余的动画状态将不再获得权重分配。 + * @readonly + * @version DragonBones 5.0 + * @language zh_CN + */ + unsigned layer; + /** + * - The play speed. + * The value is an overlay relationship with {@link dragonBones.Animation#timeScale}. + * [(-N~0): Reverse play, 0: Stop play, (0~1): Slow play, 1: Normal play, (1~N): Fast play] + * @default 1.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 播放速度。 + * 该值与 {@link dragonBones.Animation#timeScale} 是叠加关系。 + * [(-N~0): 倒转播放, 0: 停止播放, (0~1): 慢速播放, 1: 正常播放, (1~N): 快速播放] + * @default 1.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float timeScale; + /** + * - The blend weight. + * @default 1.0 + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 混合权重。 + * @default 1.0 + * @version DragonBones 5.0 + * @language zh_CN + */ + float weight; + /** + * - The auto fade out time when the animation state play completed. + * [-1: Do not fade out automatically, [0~N]: The fade out time] (In seconds) + * @default -1.0 + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 动画状态播放完成后的自动淡出时间。 + * [-1: 不自动淡出, [0~N]: 淡出时间] (以秒为单位) + * @default -1.0 + * @version DragonBones 5.0 + * @language zh_CN + */ + float autoFadeOutTime; + /** + * @private + */ + float fadeTotalTime; + /** + * - The name of the animation state. (Can be different from the name of the animation data) + * @readonly + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 动画状态名称。 (可以不同于动画数据) + * @readonly + * @version DragonBones 5.0 + * @language zh_CN + */ + std::string name; + /** + * - The blend group name of the animation state. + * This property is typically used to specify the substitution of multiple animation states blend. + * @readonly + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 混合组名称。 + * 该属性通常用来指定多个动画状态混合时的相互替换关系。 + * @readonly + * @version DragonBones 5.0 + * @language zh_CN + */ + std::string group; + +public: + /** + * - xx: Play Enabled, Fade Play Enabled + * @internal + */ + int _playheadState; + /** + * -1: Fade in, 0: Fade complete, 1: Fade out; + * @internal + */ + int _fadeState; + /** + * -1: Fade start, 0: Fading, 1: Fade complete; + * @internal + */ + int _subFadeState; + /** + * @internal + */ + float _position; + /** + * @internal + */ + float _duration; + /** + * @internal + */ + float _fadeProgress; + /** + * @internal + */ + float _weightResult; + /** + * @internal + */ + AnimationData* _animationData; + /** + * @internal + */ + ActionTimelineState* _actionTimeline; + +private: + unsigned _timelineDirty; + float _fadeTime; + float _time; + std::vector _boneMask; + std::vector _boneTimelines; + std::vector _slotTimelines; + std::vector _constraintTimelines; + std::vector> _poseTimelines; + std::map _bonePoses; + Armature* _armature; + ZOrderTimelineState* _zOrderTimeline; + +public: + AnimationState() : _actionTimeline(nullptr), + _zOrderTimeline(nullptr) { + _onClear(); + } + virtual ~AnimationState() { + _onClear(); + } + +protected: + virtual void _onClear() override; + +private: + void _updateTimelines(); + void _updateBoneAndSlotTimelines(); + void _advanceFadeTime(float passedTime); + +public: + /** + * @internal + */ + void init(Armature* armature, AnimationData* animationData, AnimationConfig* animationConfig); + /** + * @internal + */ + void advanceTime(float passedTime, float cacheFrameRate); + /** + * - Continue play. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 继续播放。 + * @version DragonBones 3.0 + * @language zh_CN + */ + void play(); + /** + * - Stop play. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 暂停播放。 + * @version DragonBones 3.0 + * @language zh_CN + */ + void stop(); + /** + * - Fade out the animation state. + * @param fadeOutTime - The fade out time. (In seconds) + * @param pausePlayhead - Whether to pause the animation playing when fade out. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 淡出动画状态。 + * @param fadeOutTime - 淡出时间。 (以秒为单位) + * @param pausePlayhead - 淡出时是否暂停播放。 + * @version DragonBones 3.0 + * @language zh_CN + */ + void fadeOut(float fadeOutTime, bool pausePlayhead = true); + /** + * - Check if a specific bone mask is included. + * @param boneName - The bone name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 检查是否包含特定骨骼遮罩。 + * @param boneName - 骨骼名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + bool containsBoneMask(const std::string& boneName) const; + /** + * - Add a specific bone mask. + * @param boneName - The bone name. + * @param recursive - Whether or not to add a mask to the bone's sub-bone. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 添加特定的骨骼遮罩。 + * @param boneName - 骨骼名称。 + * @param recursive - 是否为该骨骼的子骨骼添加遮罩。 + * @version DragonBones 3.0 + * @language zh_CN + */ + void addBoneMask(const std::string& boneName, bool recursive = true); + /** + * - Remove the mask of a specific bone. + * @param boneName - The bone name. + * @param recursive - Whether to remove the bone's sub-bone mask. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 删除特定骨骼的遮罩。 + * @param boneName - 骨骼名称。 + * @param recursive - 是否删除该骨骼的子骨骼遮罩。 + * @version DragonBones 3.0 + * @language zh_CN + */ + void removeBoneMask(const std::string& boneName, bool recursive = true); + /** + * - Remove all bone masks. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 删除所有骨骼遮罩。 + * @version DragonBones 3.0 + * @language zh_CN + */ + void removeAllBoneMask(); + /** + * - Whether the animation state is fading in. + * @version DragonBones 5.1 + * @language en_US + */ + /** + * - 是否正在淡入。 + * @version DragonBones 5.1 + * @language zh_CN + */ + inline bool isFadeIn() const { + return _fadeState < 0; + } + /** + * - Whether the animation state is fading out. + * @version DragonBones 5.1 + * @language en_US + */ + /** + * - 是否正在淡出。 + * @version DragonBones 5.1 + * @language zh_CN + */ + inline bool isFadeOut() const { + return _fadeState > 0; + } + /** + * - Whether the animation state is fade completed. + * @version DragonBones 5.1 + * @language en_US + */ + /** + * - 是否淡入或淡出完毕。 + * @version DragonBones 5.1 + * @language zh_CN + */ + inline bool isFadeComplete() const { + return _fadeState == 0; + } + /** + * - Whether the animation state is playing. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 是否正在播放。 + * @version DragonBones 3.0 + * @language zh_CN + */ + bool isPlaying() const; + /** + * - Whether the animation state is play completed. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 是否播放完毕。 + * @version DragonBones 3.0 + * @language zh_CN + */ + bool isCompleted() const; + /** + * - The times has been played. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 已经循环播放的次数。 + * @version DragonBones 3.0 + * @language zh_CN + */ + unsigned getCurrentPlayTimes() const; + /** + * - The total time. (In seconds) + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 总播放时间。 (以秒为单位) + * @version DragonBones 3.0 + * @language zh_CN + */ + inline float getTotalTime() const { + return _duration; + } + /** + * - The time is currently playing. (In seconds) + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 当前播放的时间。 (以秒为单位) + * @version DragonBones 3.0 + * @language zh_CN + */ + float getCurrentTime() const; + void setCurrentTime(float value); + inline const std::string& getName() const { + return name; + } + + /** + * - The animation data. + * @see dragonBones.AnimationData + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 动画数据。 + * @see dragonBones.AnimationData + * @version DragonBones 3.0 + * @language zh_CN + */ + inline const AnimationData* getAnimationData() const { + return _animationData; + } +}; +/** + * @internal + */ +class BonePose : public BaseObject { + BIND_CLASS_TYPE_A(BonePose); + +public: + Transform current; + Transform delta; + Transform result; + +protected: + virtual void _onClear() override; +}; +/** + * @internal + */ +class BlendState { +public: + bool dirty; + int layer; + float leftWeight; + float layerWeight; + float blendWeight; + + /** + * -1: First blending, 0: No blending, 1: Blending. + */ + int update(float weight, int p_layer); + void clear(); +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_ANIMATION_STATE_H diff --git a/cocos/editor-support/dragonbones/animation/BaseTimelineState.cpp b/cocos/editor-support/dragonbones/animation/BaseTimelineState.cpp new file mode 100644 index 0000000..7d26c71 --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/BaseTimelineState.cpp @@ -0,0 +1,282 @@ +#include "BaseTimelineState.h" +#include "../armature/Armature.h" +#include "../armature/Bone.h" +#include "../armature/Slot.h" +#include "../model/AnimationData.h" +#include "../model/ArmatureData.h" +#include "../model/DragonBonesData.h" +#include "AnimationState.h" +#include "TimelineState.h" + +#include + +DRAGONBONES_NAMESPACE_BEGIN + +void TimelineState::_onClear() { + playState = -1; + currentPlayTimes = -1; + currentTime = -1.0f; + + _tweenState = TweenState::None; + _frameRate = 0; + _frameValueOffset = 0; + _frameCount = 0; + _frameOffset = 0; + _frameIndex = -1; + _frameRateR = 0.0f; + _position = 0.0f; + _duration = 0.0f; + _timeScale = 1.0f; + _timeOffset = 0.0f; + _dragonBonesData = nullptr; + _animationData = nullptr; + _timelineData = nullptr; + _armature = nullptr; + _animationState = nullptr; + _actionTimeline = nullptr; + _frameArray = nullptr; + _frameIntArray = nullptr; + _frameFloatArray = nullptr; + _timelineArray = nullptr; + _frameIndices = nullptr; +} + +bool TimelineState::_setCurrentTime(float passedTime) { + const auto prevState = playState; + const auto prevPlayTimes = currentPlayTimes; + const auto prevTime = currentTime; + + if (_actionTimeline != nullptr && _frameCount <= 1) // No frame or only one frame. + { + playState = _actionTimeline->playState >= 0 ? 1 : -1; + currentPlayTimes = 1; + currentTime = _actionTimeline->currentTime; + } else if (_actionTimeline == nullptr || _timeScale != 1.0f || _timeOffset != 0.0f) // Action timeline or has scale and offset. + { + const auto playTimes = _animationState->playTimes; + const auto totalTime = playTimes * _duration; + + passedTime *= _timeScale; + if (_timeOffset != 0.0f) { + passedTime += _timeOffset * _animationData->duration; + } + + if (playTimes > 0 && (passedTime >= totalTime || passedTime <= -totalTime)) { + if (playState <= 0 && _animationState->_playheadState == 3) { + playState = 1; + } + + currentPlayTimes = playTimes; + if (passedTime < 0.0f) { + currentTime = 0.0f; + } else { + currentTime = _duration + 0.000001f; // Precision problem + } + } else { + if (playState != 0 && _animationState->_playheadState == 3) { + playState = 0; + } + + if (passedTime < 0.0f) { + passedTime = -passedTime; + currentPlayTimes = (int)(passedTime / _duration); + currentTime = _duration - fmod(passedTime, _duration); + } else { + currentPlayTimes = (int)(passedTime / _duration); + currentTime = fmod(passedTime, _duration); + } + } + + currentTime += _position; + } else // Multi frames. + { + playState = _actionTimeline->playState; + currentPlayTimes = _actionTimeline->currentPlayTimes; + currentTime = _actionTimeline->currentTime; + } + + if (currentPlayTimes == prevPlayTimes && currentTime == prevTime) { + return false; + } + + // Clear frame flag when timeline start or loopComplete. + if ( + (prevState < 0 && playState != prevState) || + (playState <= 0 && currentPlayTimes != prevPlayTimes)) { + _frameIndex = -1; + } + + return true; +} + +void TimelineState::init(Armature* armature, AnimationState* animationState, TimelineData* timelineData) { + _armature = armature; + _animationState = animationState; + _timelineData = timelineData; + _actionTimeline = _animationState->_actionTimeline; + + if (this == _actionTimeline) { + _actionTimeline = nullptr; // + } + + _animationData = _animationState->_animationData; + + _frameRate = _animationData->parent->frameRate; + _frameRateR = 1.0f / _frameRate; + _position = _animationState->_position; + _duration = _animationState->_duration; + _dragonBonesData = _animationData->parent->parent; + + if (_timelineData != nullptr) { + _frameIntArray = _dragonBonesData->frameIntArray; + _frameFloatArray = _dragonBonesData->frameFloatArray; + _frameArray = _dragonBonesData->frameArray; + _timelineArray = _dragonBonesData->timelineArray; + _frameIndices = &(_dragonBonesData->frameIndices); + + _frameCount = _timelineArray[_timelineData->offset + (unsigned)BinaryOffset::TimelineKeyFrameCount]; + _frameValueOffset = _timelineArray[_timelineData->offset + (unsigned)BinaryOffset::TimelineFrameValueOffset]; + _timeScale = 100.0f / _timelineArray[_timelineData->offset + (unsigned)BinaryOffset::TimelineScale]; + _timeOffset = _timelineArray[_timelineData->offset + (unsigned)BinaryOffset::TimelineOffset] * 0.01f; + } +} + +void TimelineState::update(float passedTime) { + if (_setCurrentTime(passedTime)) { + if (_frameCount > 1) { + const auto timelineFrameIndex = (unsigned)(currentTime * _frameRate); + const auto frameIndex = (*_frameIndices)[_timelineData->frameIndicesOffset + timelineFrameIndex]; + if ((unsigned)_frameIndex != frameIndex) { + _frameIndex = frameIndex; + _frameOffset = _animationData->frameOffset + _timelineArray[_timelineData->offset + (unsigned)BinaryOffset::TimelineFrameOffset + _frameIndex]; + + _onArriveAtFrame(); + } + } else if (_frameIndex < 0) { + _frameIndex = 0; + if (_timelineData != nullptr) // May be pose timeline. + { + _frameOffset = _animationData->frameOffset + _timelineArray[_timelineData->offset + (unsigned)BinaryOffset::TimelineFrameOffset]; + } + + _onArriveAtFrame(); + } + + if (_tweenState != TweenState::None) { + _onUpdateFrame(); + } + } +} + +void TweenTimelineState::_onClear() { + TimelineState::_onClear(); + + _tweenType = TweenType::None; + _curveCount = 0; + _framePosition = 0.0f; + _frameDurationR = 0.0f; + _tweenProgress = 0.0f; + _tweenEasing = 0.0f; +} + +void TweenTimelineState::_onArriveAtFrame() { + if ( + _frameCount > 1 && + ((unsigned)_frameIndex != _frameCount - 1 || + _animationState->playTimes == 0 || + _animationState->getCurrentPlayTimes() < _animationState->playTimes - 1)) { + _tweenType = (TweenType)_frameArray[_frameOffset + (unsigned)BinaryOffset::FrameTweenType]; // TODO recode ture tween type. + _tweenState = _tweenType == TweenType::None ? TweenState::Once : TweenState::Always; + + if (_tweenType == TweenType::Curve) { + _curveCount = _frameArray[_frameOffset + (unsigned)BinaryOffset::FrameTweenEasingOrCurveSampleCount]; + } else if (_tweenType != TweenType::None && _tweenType != TweenType::Line) { + _tweenEasing = _frameArray[_frameOffset + (unsigned)BinaryOffset::FrameTweenEasingOrCurveSampleCount] * 0.01; + } + + _framePosition = _frameArray[_frameOffset] * _frameRateR; + + if ((unsigned)_frameIndex == _frameCount - 1) { + _frameDurationR = 1.0f / (_animationData->duration - _framePosition); + } else { + const auto nextFrameOffset = _animationData->frameOffset + _timelineArray[_timelineData->offset + (unsigned)BinaryOffset::TimelineFrameOffset + _frameIndex + 1]; + const auto frameDuration = _frameArray[nextFrameOffset] * _frameRateR - _framePosition; + + if (frameDuration > 0.0f) // Fixed animation data bug. + { + _frameDurationR = 1.0f / frameDuration; + } else { + _frameDurationR = 0.0f; + } + } + } else { + _tweenState = TweenState::Once; + } +} + +void TweenTimelineState::_onUpdateFrame() { + if (_tweenState == TweenState::Always) { + _tweenProgress = (currentTime - _framePosition) * _frameDurationR; + + if (_tweenType == TweenType::Curve) { + _tweenProgress = TweenTimelineState::_getEasingCurveValue(_tweenProgress, _frameArray, _curveCount, _frameOffset + (unsigned)BinaryOffset::FrameCurveSamples); + } else if (_tweenType != TweenType::Line) { + _tweenProgress = TweenTimelineState::_getEasingValue(_tweenType, _tweenProgress, _tweenEasing); + } + } else { + _tweenProgress = 0.0f; + } +} + +void BoneTimelineState::_onClear() { + TweenTimelineState::_onClear(); + + bone = nullptr; + bonePose = nullptr; +} + +void BoneTimelineState::blend(int state) { + const auto blendWeight = bone->_blendState.blendWeight; + auto& animationPose = bone->animationPose; + const auto& result = bonePose->result; + + if (state == 2) { + animationPose.x += result.x * blendWeight; + animationPose.y += result.y * blendWeight; + animationPose.rotation += result.rotation * blendWeight; + animationPose.skew += result.skew * blendWeight; + animationPose.scaleX += (result.scaleX - 1.0f) * blendWeight; + animationPose.scaleY += (result.scaleY - 1.0f) * blendWeight; + } else if (blendWeight != 1.0f) { + animationPose.x = result.x * blendWeight; + animationPose.y = result.y * blendWeight; + animationPose.rotation = result.rotation * blendWeight; + animationPose.skew = result.skew * blendWeight; + animationPose.scaleX = (result.scaleX - 1.0f) * blendWeight + 1.0f; + animationPose.scaleY = (result.scaleY - 1.0f) * blendWeight + 1.0f; + } else { + animationPose.x = result.x; + animationPose.y = result.y; + animationPose.rotation = result.rotation; + animationPose.skew = result.skew; + animationPose.scaleX = result.scaleX; + animationPose.scaleY = result.scaleY; + } + + if (_animationState->_fadeState != 0 || _animationState->_subFadeState != 0) { + bone->_transformDirty = true; + } +} + +void SlotTimelineState::_onClear() { + TweenTimelineState::_onClear(); + + slot = nullptr; +} + +void ConstraintTimelineState::_onClear() { + TweenTimelineState::_onClear(); + + constraint = nullptr; +} +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/animation/BaseTimelineState.h b/cocos/editor-support/dragonbones/animation/BaseTimelineState.h new file mode 100644 index 0000000..829e614 --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/BaseTimelineState.h @@ -0,0 +1,187 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_BASE_TIMELINE_STATE_H +#define DRAGONBONES_BASE_TIMELINE_STATE_H + +#include "../core/BaseObject.h" +#include "../geom/Transform.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * @internal + */ +class TimelineState : public BaseObject { + ABSTRACT_CLASS(TimelineState) + +protected: + /** + * @internal + */ + enum class TweenState { + None = 0, + Once = 1, + Always = 2 + }; + +public: + /** + * -1: start, 0: play, 1: complete; + */ + int playState; + int currentPlayTimes; + float currentTime; + +protected: + TweenState _tweenState; + unsigned _frameRate; + unsigned _frameValueOffset; + unsigned _frameCount; + unsigned _frameOffset; + int _frameIndex; + float _frameRateR; + float _position; + float _duration; + float _timeScale; + float _timeOffset; + DragonBonesData* _dragonBonesData; + AnimationData* _animationData; + TimelineData* _timelineData; + Armature* _armature; + AnimationState* _animationState; + TimelineState* _actionTimeline; + const int16_t* _frameArray; + const int16_t* _frameIntArray; + const float* _frameFloatArray; + const uint16_t* _timelineArray; + const std::vector* _frameIndices; + +protected: + virtual void _onClear() override; + virtual void _onArriveAtFrame() = 0; + virtual void _onUpdateFrame() = 0; + bool _setCurrentTime(float passedTime); + +public: + virtual void init(Armature* armature, AnimationState* animationState, TimelineData* timelineData); + virtual void fadeOut() {} + virtual void update(float passedTime); +}; +/** + * @internal + */ +class TweenTimelineState : public TimelineState { + ABSTRACT_CLASS(TweenTimelineState) + +private: + inline static float _getEasingValue(TweenType tweenType, float progress, float easing) { + auto value = progress; + switch (tweenType) { + case TweenType::QuadIn: + value = std::pow(progress, 2.0f); + break; + + case TweenType::QuadOut: + value = 1.0f - std::pow(1.0f - progress, 2.0f); + break; + + case TweenType::QuadInOut: + value = 0.5f * (1.0f - std::cos(progress * Transform::PI)); + break; + default: + break; + } + + return (value - progress) * easing + progress; + } + + inline static float _getEasingCurveValue(float progress, const int16_t* samples, unsigned count, unsigned offset) { + if (progress <= 0.0f) { + return 0.0f; + } else if (progress >= 1.0f) { + return 1.0f; + } + + const auto segmentCount = count + 1; // + 2 - 1 + const auto valueIndex = (unsigned)(progress * segmentCount); + const auto fromValue = valueIndex == 0 ? 0.0f : samples[offset + valueIndex - 1]; + const auto toValue = (valueIndex == segmentCount - 1) ? 10000.0f : samples[offset + valueIndex]; + + return (fromValue + (toValue - fromValue) * (progress * segmentCount - valueIndex)) * 0.0001f; + } + +protected: + TweenType _tweenType; + unsigned _curveCount; + float _framePosition; + float _frameDurationR; + float _tweenProgress; + float _tweenEasing; + +protected: + virtual void _onClear() override; + virtual void _onArriveAtFrame() override; + virtual void _onUpdateFrame() override; +}; +/** + * @internal + */ +class BoneTimelineState : public TweenTimelineState { + ABSTRACT_CLASS(BoneTimelineState) + +public: + Bone* bone; + BonePose* bonePose; + +protected: + virtual void _onClear() override; + +public: + void blend(int state); +}; +/** + * @internal + */ +class SlotTimelineState : public TweenTimelineState { + ABSTRACT_CLASS(SlotTimelineState) + +public: + Slot* slot; + +protected: + virtual void _onClear() override; +}; +/** + * @internal + */ +class ConstraintTimelineState : public TweenTimelineState { + ABSTRACT_CLASS(ConstraintTimelineState) + +public: + Constraint* constraint; + +protected: + virtual void _onClear() override; +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_BASE_TIMELINE_STATE_H diff --git a/cocos/editor-support/dragonbones/animation/IAnimatable.h b/cocos/editor-support/dragonbones/animation/IAnimatable.h new file mode 100644 index 0000000..cdfc005 --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/IAnimatable.h @@ -0,0 +1,101 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_ANIMATEBLE_H +#define DRAGONBONES_ANIMATEBLE_H + +#include "../core/DragonBones.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - Play animation interface. (Both Armature and Wordclock implement the interface) + * Any instance that implements the interface can be added to the Worldclock instance and advance time by Worldclock instance uniformly. + * @see dragonBones.WorldClock + * @see dragonBones.Armature + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 播放动画接口。 (Armature 和 WordClock 都实现了该接口) + * 任何实现了此接口的实例都可以添加到 WorldClock 实例中,由 WorldClock 实例统一更新时间。 + * @see dragonBones.WorldClock + * @see dragonBones.Armature + * @version DragonBones 3.0 + * @language zh_CN + */ +class IAnimatable { + ABSTRACT_CLASS(IAnimatable) + +public: + /** + * - Advance time. + * @param passedTime - Passed time. (In seconds) + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 更新时间。 + * @param passedTime - 前进的时间。 (以秒为单位) + * @version DragonBones 3.0 + * @language zh_CN + */ + virtual void advanceTime(float passedTime) = 0; + + /** + * - Render. + * @version Cocos creator 2.3 + * @language en_US + */ + /** + * - 渲染。 + * @version Cocos creator 2.3 + * @language zh_CN + */ + virtual void render() = 0; + /** + * - The Wordclock instance to which the current belongs. + * @example + * TypeScript style, for reference only. + *
+     *     armature.clock = factory.clock; // Add armature to clock.
+     *     armature.clock = null; // Remove armature from clock.
+     * 
+ * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 当前所属的 WordClock 实例。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     armature.clock = factory.clock; // 将骨架添加到时钟。
+     *     armature.clock = null; // 将骨架从时钟移除。
+     * 
+ * @version DragonBones 5.0 + * @language zh_CN + */ + virtual WorldClock* getClock() const = 0; + virtual void setClock(WorldClock* value) = 0; +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_ANIMATEBLE_H diff --git a/cocos/editor-support/dragonbones/animation/TimelineState.cpp b/cocos/editor-support/dragonbones/animation/TimelineState.cpp new file mode 100644 index 0000000..02ef69b --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/TimelineState.cpp @@ -0,0 +1,852 @@ +#include "TimelineState.h" +#include "../armature/Armature.h" +#include "../armature/Bone.h" +#include "../armature/Constraint.h" +#include "../armature/DeformVertices.h" +#include "../armature/Slot.h" +#include "../event/EventObject.h" +#include "../event/IEventDispatcher.h" +#include "../model/AnimationData.h" +#include "../model/ArmatureData.h" +#include "../model/ConstraintData.h" +#include "../model/DisplayData.h" +#include "../model/DragonBonesData.h" +#include "../model/UserData.h" +#include "Animation.h" +#include "AnimationState.h" +#include "WorldClock.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void ActionTimelineState::_onCrossFrame(unsigned frameIndex) const { + const auto eventDispatcher = _armature->getProxy(); + + if (_animationState->actionEnabled) { + const auto frameOffset = _animationData->frameOffset + _timelineArray[_timelineData->offset + (unsigned)BinaryOffset::TimelineFrameOffset + frameIndex]; + const unsigned actionCount = _frameArray[frameOffset + 1]; + const auto& actions = _animationData->parent->actions; // May be the animaton data not belong to this armature data. + + for (std::size_t i = 0; i < actionCount; ++i) { + const auto actionIndex = _frameArray[frameOffset + 2 + i]; + const auto action = actions[actionIndex]; + + if (action->type == ActionType::Play) { + const auto eventObject = BaseObject::borrowObject(); + // eventObject->time = _frameArray[frameOffset] * _frameRateR; // Precision problem + eventObject->time = _frameArray[frameOffset] / _frameRate; + eventObject->animationState = _animationState; + EventObject::actionDataToInstance(action, eventObject, _armature); + _armature->_bufferAction(eventObject, true); + } else { + const auto eventType = action->type == ActionType::Frame ? EventObject::FRAME_EVENT : EventObject::SOUND_EVENT; + if (action->type == ActionType::Sound || eventDispatcher->hasDBEventListener(eventType)) { + const auto eventObject = BaseObject::borrowObject(); + // eventObject->time = _frameArray[frameOffset] * _frameRateR; // Precision problem + eventObject->time = _frameArray[frameOffset] / _frameRate; + eventObject->animationState = _animationState; + EventObject::actionDataToInstance(action, eventObject, _armature); + _armature->_dragonBones->bufferEvent(eventObject); + } + } + } + } +} + +void ActionTimelineState::update(float passedTime) { + const auto prevState = playState; + auto prevPlayTimes = currentPlayTimes; + auto prevTime = currentTime; + + if (_setCurrentTime(passedTime)) { + const auto eventDispatcher = _armature->getProxy(); + if (prevState < 0) { + if (playState != prevState) { + if (_animationState->displayControl && _animationState->resetToPose) // Reset zorder to pose. + { + _armature->_sortZOrder(nullptr, 0); + } + + prevPlayTimes = currentPlayTimes; + + if (eventDispatcher->hasDBEventListener(EventObject::START)) { + const auto eventObject = BaseObject::borrowObject(); + eventObject->type = EventObject::START; + eventObject->armature = _armature; + eventObject->animationState = _animationState; + _armature->_dragonBones->bufferEvent(eventObject); + } + } else { + return; + } + } + + const auto isReverse = _animationState->timeScale < 0.0f; + EventObject* loopCompleteEvent = nullptr; + EventObject* completeEvent = nullptr; + + if (currentPlayTimes != prevPlayTimes) { + if (eventDispatcher->hasDBEventListener(EventObject::LOOP_COMPLETE)) { + loopCompleteEvent = BaseObject::borrowObject(); + loopCompleteEvent->type = EventObject::LOOP_COMPLETE; + loopCompleteEvent->armature = _armature; + loopCompleteEvent->animationState = _animationState; + } + + if (playState > 0) { + if (eventDispatcher->hasDBEventListener(EventObject::COMPLETE)) { + completeEvent = BaseObject::borrowObject(); + completeEvent->type = EventObject::COMPLETE; + completeEvent->armature = _armature; + completeEvent->animationState = _animationState; + } + } + } + + if (_frameCount > 1) { + const auto timelineData = _timelineData; + const auto timelineFrameIndex = (unsigned)(currentTime * _frameRate); // uint + const auto frameIndex = (*_frameIndices)[timelineData->frameIndicesOffset + timelineFrameIndex]; + + if ((unsigned)_frameIndex != frameIndex) // Arrive at frame. + { + auto crossedFrameIndex = _frameIndex; + _frameIndex = frameIndex; + + if (_timelineArray != nullptr) { + _frameOffset = _animationData->frameOffset + _timelineArray[timelineData->offset + (unsigned)BinaryOffset::TimelineFrameOffset + _frameIndex]; + + if (isReverse) { + if (crossedFrameIndex < 0) { + const auto prevFrameIndex = (unsigned)(prevTime * _frameRate); + crossedFrameIndex = (*_frameIndices)[timelineData->frameIndicesOffset + prevFrameIndex]; + + if (currentPlayTimes == prevPlayTimes) // Start. + { + if ((unsigned)crossedFrameIndex == frameIndex) // Uncrossed. + { + crossedFrameIndex = -1; + } + } + } + + while (crossedFrameIndex >= 0) { + const auto frameOffset = _animationData->frameOffset + _timelineArray[timelineData->offset + (unsigned)BinaryOffset::TimelineFrameOffset + crossedFrameIndex]; + const auto framePosition = (float)_frameArray[frameOffset] / _frameRate; + + if ( + _position <= framePosition && + framePosition <= _position + _duration) // Support interval play. + { + _onCrossFrame(crossedFrameIndex); + } + + if (loopCompleteEvent != nullptr && crossedFrameIndex == 0) // Add loop complete event after first frame. + { + _armature->_dragonBones->bufferEvent(loopCompleteEvent); + loopCompleteEvent = nullptr; + } + + if (crossedFrameIndex > 0) { + crossedFrameIndex--; + } else { + crossedFrameIndex = _frameCount - 1; + } + + if ((unsigned)crossedFrameIndex == frameIndex) { + break; + } + } + } else { + if (crossedFrameIndex < 0) { + const auto prevFrameIndex = (unsigned)(prevTime * _frameRate); + crossedFrameIndex = (*_frameIndices)[timelineData->frameIndicesOffset + prevFrameIndex]; + const auto frameOffset = _animationData->frameOffset + _timelineArray[timelineData->offset + (unsigned)BinaryOffset::TimelineFrameOffset + crossedFrameIndex]; + const auto framePosition = (float)_frameArray[frameOffset] / _frameRate; + + if (currentPlayTimes == prevPlayTimes) // Start. + { + if (prevTime <= framePosition) // Crossed. + { + if (crossedFrameIndex > 0) { + crossedFrameIndex--; + } else { + crossedFrameIndex = _frameCount - 1; + } + } else if ((unsigned)crossedFrameIndex == frameIndex) // Uncrossed. + { + crossedFrameIndex = -1; + } + } + } + + while (crossedFrameIndex >= 0) { + if ((unsigned)crossedFrameIndex < _frameCount - 1) { + crossedFrameIndex++; + } else { + crossedFrameIndex = 0; + } + + const auto frameOffset = _animationData->frameOffset + _timelineArray[timelineData->offset + (unsigned)BinaryOffset::TimelineFrameOffset + crossedFrameIndex]; + const auto framePosition = (float)_frameArray[frameOffset] / _frameRate; + + if ( + _position <= framePosition && + framePosition <= _position + _duration) // Support interval play. + { + _onCrossFrame(crossedFrameIndex); + } + + if (loopCompleteEvent != nullptr && crossedFrameIndex == 0) // Add loop complete event before first frame. + { + _armature->_dragonBones->bufferEvent(loopCompleteEvent); + loopCompleteEvent = nullptr; + } + + if ((unsigned)crossedFrameIndex == frameIndex) { + break; + } + } + } + } + } + } else if (_frameIndex < 0) { + _frameIndex = 0; + if (_timelineData != nullptr) { + _frameOffset = _animationData->frameOffset + _timelineArray[_timelineData->offset + (unsigned)BinaryOffset::TimelineFrameOffset]; + // Arrive at frame. + const auto framePosition = (float)_frameArray[_frameOffset] / _frameRate; + + if (currentPlayTimes == prevPlayTimes) // Start. + { + if (prevTime <= framePosition) { + _onCrossFrame(_frameIndex); + } + } else if (_position <= framePosition) // Loop complete. + { + if (!isReverse && loopCompleteEvent != nullptr) // Add loop complete event before first frame. + { + _armature->_dragonBones->bufferEvent(loopCompleteEvent); + loopCompleteEvent = nullptr; + } + + _onCrossFrame(_frameIndex); + } + } + } + + if (loopCompleteEvent != nullptr) { + _armature->_dragonBones->bufferEvent(loopCompleteEvent); + } + + if (completeEvent != nullptr) { + _armature->_dragonBones->bufferEvent(completeEvent); + } + } +} + +void ActionTimelineState::setCurrentTime(float value) { + _setCurrentTime(value); + _frameIndex = -1; +} + +void ZOrderTimelineState::_onArriveAtFrame() { + if (playState >= 0) { + const auto count = _frameArray[_frameOffset + 1]; + if (count > 0) { + _armature->_sortZOrder(_frameArray, _frameOffset + 2); + } else { + _armature->_sortZOrder(nullptr, 0); + } + } +} + +void BoneAllTimelineState::_onArriveAtFrame() { + BoneTimelineState::_onArriveAtFrame(); + + if (_timelineData != nullptr) { + auto valueOffset = _animationData->frameFloatOffset + _frameValueOffset + _frameIndex * 6; // ...(timeline value offset)|xxxxxx|xxxxxx|(Value offset)xxxxx|(Next offset)xxxxx|xxxxxx|xxxxxx|... + const auto scale = _armature->_armatureData->scale; + const auto frameFloatArray = _frameFloatArray; + auto& current = bonePose->current; + auto& delta = bonePose->delta; + // + current.x = frameFloatArray[valueOffset++] * scale; + current.y = frameFloatArray[valueOffset++] * scale; + current.rotation = frameFloatArray[valueOffset++]; + current.skew = frameFloatArray[valueOffset++]; + current.scaleX = frameFloatArray[valueOffset++]; + current.scaleY = frameFloatArray[valueOffset++]; + + if (_tweenState == TweenState::Always) { + if ((unsigned)_frameIndex == _frameCount - 1) { + valueOffset = _animationData->frameFloatOffset + _frameValueOffset; + } + + delta.x = frameFloatArray[valueOffset++] * scale - current.x; + delta.y = frameFloatArray[valueOffset++] * scale - current.y; + delta.rotation = frameFloatArray[valueOffset++] - current.rotation; + delta.skew = frameFloatArray[valueOffset++] - current.skew; + delta.scaleX = frameFloatArray[valueOffset++] - current.scaleX; + delta.scaleY = frameFloatArray[valueOffset++] - current.scaleY; + } else { + delta.x = 0.0f; + delta.y = 0.0f; + delta.rotation = 0.0f; + delta.skew = 0.0f; + delta.scaleX = 0.0f; + delta.scaleY = 0.0f; + } + } else { + auto& current = bonePose->current; + auto& delta = bonePose->delta; + current.x = 0.0f; + current.y = 0.0f; + current.rotation = 0.0f; + current.skew = 0.0f; + current.scaleX = 1.0f; + current.scaleY = 1.0f; + delta.x = 0.0f; + delta.y = 0.0f; + delta.rotation = 0.0f; + delta.skew = 0.0f; + delta.scaleX = 0.0f; + delta.scaleY = 0.0f; + } +} + +void BoneAllTimelineState::_onUpdateFrame() { + BoneTimelineState::_onUpdateFrame(); + + auto& current = bonePose->current; + auto& delta = bonePose->delta; + auto& result = bonePose->result; + + bone->_transformDirty = true; + if (_tweenState != TweenState::Always) { + _tweenState = TweenState::None; + } + + result.x = current.x + delta.x * _tweenProgress; + result.y = current.y + delta.y * _tweenProgress; + result.rotation = current.rotation + delta.rotation * _tweenProgress; + result.skew = current.skew + delta.skew * _tweenProgress; + result.scaleX = current.scaleX + delta.scaleX * _tweenProgress; + result.scaleY = current.scaleY + delta.scaleY * _tweenProgress; +} + +void BoneAllTimelineState::fadeOut() { + auto& result = bonePose->result; + result.rotation = Transform::normalizeRadian(result.rotation); + result.skew = Transform::normalizeRadian(result.skew); +} + +void BoneTranslateTimelineState::_onArriveAtFrame() { + BoneTimelineState::_onArriveAtFrame(); + + if (_timelineData != nullptr) { + auto valueOffset = _animationData->frameFloatOffset + _frameValueOffset + _frameIndex * 2; + const auto scale = _armature->_armatureData->scale; + const auto frameFloatArray = _frameFloatArray; + auto& current = bonePose->current; + auto& delta = bonePose->delta; + + current.x = frameFloatArray[valueOffset++] * scale; + current.y = frameFloatArray[valueOffset++] * scale; + + if (_tweenState == TweenState::Always) { + if ((unsigned)_frameIndex == _frameCount - 1) { + valueOffset = _animationData->frameFloatOffset + _frameValueOffset; // + 0 * 2 + } + + delta.x = frameFloatArray[valueOffset++] * scale - current.x; + delta.y = frameFloatArray[valueOffset++] * scale - current.y; + } else { + delta.x = 0.0f; + delta.y = 0.0f; + } + } else { + auto& current = bonePose->current; + auto& delta = bonePose->delta; + current.x = 0.0f; + current.y = 0.0f; + delta.x = 0.0f; + delta.y = 0.0f; + } +} + +void BoneTranslateTimelineState::_onUpdateFrame() { + BoneTimelineState::_onUpdateFrame(); + + auto& current = bonePose->current; + auto& delta = bonePose->delta; + auto& result = bonePose->result; + + bone->_transformDirty = true; + if (_tweenState != TweenState::Always) { + _tweenState = TweenState::None; + } + + result.x = current.x + delta.x * _tweenProgress; + result.y = current.y + delta.y * _tweenProgress; +} + +void BoneRotateTimelineState::_onArriveAtFrame() { + BoneTimelineState::_onArriveAtFrame(); + + if (_timelineData != nullptr) { + auto valueOffset = _animationData->frameFloatOffset + _frameValueOffset + _frameIndex * 2; + const auto frameFloatArray = _frameFloatArray; + auto& current = bonePose->current; + auto& delta = bonePose->delta; + + current.rotation = frameFloatArray[valueOffset++]; + current.skew = frameFloatArray[valueOffset++]; + + if (_tweenState == TweenState::Always) { + if ((unsigned)_frameIndex == _frameCount - 1) { + valueOffset = _animationData->frameFloatOffset + _frameValueOffset; // + 0 * 2 + delta.rotation = Transform::normalizeRadian(frameFloatArray[valueOffset++] - current.rotation); + } else { + delta.rotation = frameFloatArray[valueOffset++] - current.rotation; + } + + delta.skew = frameFloatArray[valueOffset++] - current.skew; + } else { + delta.rotation = 0.0f; + delta.skew = 0.0f; + } + } else { + auto& current = bonePose->current; + auto& delta = bonePose->delta; + current.rotation = 0.0f; + current.skew = 0.0f; + delta.rotation = 0.0f; + delta.skew = 0.0f; + } +} + +void BoneRotateTimelineState::_onUpdateFrame() { + BoneTimelineState::_onUpdateFrame(); + + auto& current = bonePose->current; + auto& delta = bonePose->delta; + auto& result = bonePose->result; + + bone->_transformDirty = true; + if (_tweenState != TweenState::Always) { + _tweenState = TweenState::None; + } + + result.rotation = current.rotation + delta.rotation * _tweenProgress; + result.skew = current.skew + delta.skew * _tweenProgress; +} + +void BoneRotateTimelineState::fadeOut() { + auto& result = bonePose->result; + result.rotation = Transform::normalizeRadian(result.rotation); + result.skew = Transform::normalizeRadian(result.skew); +} + +void BoneScaleTimelineState::_onArriveAtFrame() { + BoneTimelineState::_onArriveAtFrame(); + + if (_timelineData != nullptr) { + auto valueOffset = _animationData->frameFloatOffset + _frameValueOffset + _frameIndex * 2; + const auto frameFloatArray = _frameFloatArray; + auto& current = bonePose->current; + auto& delta = bonePose->delta; + + current.scaleX = frameFloatArray[valueOffset++]; + current.scaleY = frameFloatArray[valueOffset++]; + + if (_tweenState == TweenState::Always) { + if ((unsigned)_frameIndex == _frameCount - 1) { + valueOffset = _animationData->frameFloatOffset + _frameValueOffset; // + 0 * 2 + } + + delta.scaleX = frameFloatArray[valueOffset++] - current.scaleX; + delta.scaleY = frameFloatArray[valueOffset++] - current.scaleY; + } else { + delta.scaleX = 0.0f; + delta.scaleY = 0.0f; + } + } else { + auto& current = bonePose->current; + auto& delta = bonePose->delta; + current.scaleX = 1.0f; + current.scaleY = 1.0f; + delta.scaleX = 0.0f; + delta.scaleY = 0.0f; + } +} + +void BoneScaleTimelineState::_onUpdateFrame() { + BoneTimelineState::_onUpdateFrame(); + + auto& current = bonePose->current; + auto& delta = bonePose->delta; + auto& result = bonePose->result; + + bone->_transformDirty = true; + if (_tweenState != TweenState::Always) { + _tweenState = TweenState::None; + } + + result.scaleX = current.scaleX + delta.scaleX * _tweenProgress; + result.scaleY = current.scaleY + delta.scaleY * _tweenProgress; +} + +void SlotDislayTimelineState::_onArriveAtFrame() { + if (playState >= 0) { + const auto displayIndex = _timelineData != nullptr ? _frameArray[_frameOffset + 1] : slot->_slotData->displayIndex; + if (slot->getDisplayIndex() != displayIndex) { + slot->_setDisplayIndex(displayIndex, true); + } + } +} + +void SlotColorTimelineState::_onClear() { + SlotTimelineState::_onClear(); + + _dirty = false; +} + +void SlotColorTimelineState::_onArriveAtFrame() { + SlotTimelineState::_onArriveAtFrame(); + + if (_timelineData != nullptr) { + const auto intArray = _dragonBonesData->intArray; + const auto frameIntArray = _frameIntArray; + const auto valueOffset = _animationData->frameIntOffset + _frameValueOffset + _frameIndex * 1; // ...(timeline value offset)|x|x|(Value offset)|(Next offset)|x|x|... + int colorOffset = frameIntArray[valueOffset]; + + if (colorOffset < 0) { + colorOffset += 65536; // Fixed out of bouds bug. + } + + _current[0] = intArray[colorOffset++]; + _current[1] = intArray[colorOffset++]; + _current[2] = intArray[colorOffset++]; + _current[3] = intArray[colorOffset++]; + _current[4] = intArray[colorOffset++]; + _current[5] = intArray[colorOffset++]; + _current[6] = intArray[colorOffset++]; + _current[7] = intArray[colorOffset++]; + + if (_tweenState == TweenState::Always) { + if ((unsigned)_frameIndex == _frameCount - 1) { + colorOffset = frameIntArray[_animationData->frameIntOffset + _frameValueOffset]; + } else { + colorOffset = frameIntArray[valueOffset + 1 * 1]; + } + + if (colorOffset < 0) { + colorOffset += 65536; // Fixed out of bouds bug. + } + + _delta[0] = intArray[colorOffset++] - _current[0]; + _delta[1] = intArray[colorOffset++] - _current[1]; + _delta[2] = intArray[colorOffset++] - _current[2]; + _delta[3] = intArray[colorOffset++] - _current[3]; + _delta[4] = intArray[colorOffset++] - _current[4]; + _delta[5] = intArray[colorOffset++] - _current[5]; + _delta[6] = intArray[colorOffset++] - _current[6]; + _delta[7] = intArray[colorOffset++] - _current[7]; + } + } else { + const auto color = slot->_slotData->color; + + _current[0] = color->alphaMultiplier * 100.0f; + _current[1] = color->redMultiplier * 100.0f; + _current[2] = color->greenMultiplier * 100.0f; + _current[3] = color->blueMultiplier * 100.0f; + _current[4] = color->alphaOffset; + _current[5] = color->redOffset; + _current[6] = color->greenOffset; + _current[7] = color->blueOffset; + } +} + +void SlotColorTimelineState::_onUpdateFrame() { + SlotTimelineState::_onUpdateFrame(); + + _dirty = true; + if (_tweenState != TweenState::Always) { + _tweenState = TweenState::None; + } + + _result[0] = (_current[0] + _delta[0] * _tweenProgress) * 0.01f; + _result[1] = (_current[1] + _delta[1] * _tweenProgress) * 0.01f; + _result[2] = (_current[2] + _delta[2] * _tweenProgress) * 0.01f; + _result[3] = (_current[3] + _delta[3] * _tweenProgress) * 0.01f; + _result[4] = _current[4] + _delta[4] * _tweenProgress; + _result[5] = _current[5] + _delta[5] * _tweenProgress; + _result[6] = _current[6] + _delta[6] * _tweenProgress; + _result[7] = _current[7] + _delta[7] * _tweenProgress; +} + +void SlotColorTimelineState::fadeOut() { + _tweenState = TweenState::None; + _dirty = false; +} + +void SlotColorTimelineState::update(float passedTime) { + SlotTimelineState::update(passedTime); + + // Fade animation. + if (_tweenState != TweenState::None || _dirty) { + auto& result = slot->_colorTransform; + + if (_animationState->_fadeState != 0 || _animationState->_subFadeState != 0) { + if ( + result.alphaMultiplier != _result[0] || + result.redMultiplier != _result[1] || + result.greenMultiplier != _result[2] || + result.blueMultiplier != _result[3] || + result.alphaOffset != _result[4] || + result.redOffset != _result[5] || + result.greenOffset != _result[6] || + result.blueOffset != _result[7]) { + const auto fadeProgress = pow(_animationState->_fadeProgress, 2); + + result.alphaMultiplier += (_result[0] - result.alphaMultiplier) * fadeProgress; + result.redMultiplier += (_result[1] - result.redMultiplier) * fadeProgress; + result.greenMultiplier += (_result[2] - result.greenMultiplier) * fadeProgress; + result.blueMultiplier += (_result[3] - result.blueMultiplier) * fadeProgress; + result.alphaOffset += (_result[4] - result.alphaOffset) * fadeProgress; + result.redOffset += (_result[5] - result.redOffset) * fadeProgress; + result.greenOffset += (_result[6] - result.greenOffset) * fadeProgress; + result.blueOffset += (_result[7] - result.blueOffset) * fadeProgress; + + slot->_colorDirty = true; + } + } else if (_dirty) { + _dirty = false; + if ( + result.alphaMultiplier != _result[0] || + result.redMultiplier != _result[1] || + result.greenMultiplier != _result[2] || + result.blueMultiplier != _result[3] || + result.alphaOffset != _result[4] || + result.redOffset != _result[5] || + result.greenOffset != _result[6] || + result.blueOffset != _result[7]) { + result.alphaMultiplier = _result[0]; + result.redMultiplier = _result[1]; + result.greenMultiplier = _result[2]; + result.blueMultiplier = _result[3]; + result.alphaOffset = _result[4]; + result.redOffset = _result[5]; + result.greenOffset = _result[6]; + result.blueOffset = _result[7]; + + slot->_colorDirty = true; + } + } + } +} + +void DeformTimelineState::_onClear() { + SlotTimelineState::_onClear(); + + vertexOffset = 0; + + _dirty = false; + _frameFloatOffset = 0; + _deformCount = 0; + _valueCount = 0; + _valueOffset = 0; + _current.clear(); + _delta.clear(); + _result.clear(); +} + +void DeformTimelineState::_onArriveAtFrame() { + SlotTimelineState::_onArriveAtFrame(); + + if (_timelineData != nullptr) { + const auto valueOffset = _animationData->frameFloatOffset + _frameValueOffset + _frameIndex * _valueCount; + const auto scale = _armature->_armatureData->scale; + const auto frameFloatArray = _frameFloatArray; + + if (_tweenState == TweenState::Always) { + auto nextValueOffset = valueOffset + _valueCount; + if ((unsigned)_frameIndex == _frameCount - 1) { + nextValueOffset = _animationData->frameFloatOffset + _frameValueOffset; + } + + for (std::size_t i = 0; i < _valueCount; ++i) { + _delta[i] = frameFloatArray[nextValueOffset + i] * scale - (_current[i] = frameFloatArray[valueOffset + i] * scale); + } + } else { + for (std::size_t i = 0; i < _valueCount; ++i) { + _current[i] = frameFloatArray[valueOffset + i] * scale; + } + } + } else { + for (std::size_t i = 0; i < _valueCount; ++i) { + _current[i] = 0.0f; + } + } +} + +void DeformTimelineState::_onUpdateFrame() { + SlotTimelineState::_onUpdateFrame(); + + _dirty = true; + if (_tweenState != TweenState::Always) { + _tweenState = TweenState::None; + } + + for (std::size_t i = 0; i < _valueCount; ++i) { + _result[i] = _current[i] + _delta[i] * _tweenProgress; + } +} + +void DeformTimelineState::init(Armature* armature, AnimationState* animationState, TimelineData* timelineData) { + SlotTimelineState::init(armature, animationState, timelineData); + + if (_timelineData != nullptr) { + const auto frameIntOffset = _animationData->frameIntOffset + _timelineArray[_timelineData->offset + (unsigned)BinaryOffset::TimelineFrameValueCount]; + vertexOffset = _frameIntArray[frameIntOffset + (unsigned)BinaryOffset::DeformVertexOffset]; + + if (vertexOffset < 0) { + vertexOffset += 65536; // Fixed out of bouds bug. + } + + _deformCount = _frameIntArray[frameIntOffset + (unsigned)BinaryOffset::DeformCount]; + _valueCount = _frameIntArray[frameIntOffset + (unsigned)BinaryOffset::DeformValueCount]; + _valueOffset = _frameIntArray[frameIntOffset + (unsigned)BinaryOffset::DeformValueOffset]; + _frameFloatOffset = _frameIntArray[frameIntOffset + (unsigned)BinaryOffset::DeformFloatOffset] + _animationData->frameFloatOffset; + } else { + const auto deformVertices = slot->_deformVertices; + _deformCount = deformVertices != nullptr ? deformVertices->vertices.size() : 0; + _valueCount = _deformCount; + _valueOffset = 0; + _frameFloatOffset = 0; + } + + _current.resize(_valueCount); + _delta.resize(_valueCount, 0.0f); + _result.resize(_valueCount); +} + +void DeformTimelineState::fadeOut() { + _tweenState = TweenState::None; + _dirty = false; +} + +void DeformTimelineState::update(float passedTime) { + const auto deformVertices = slot->_deformVertices; + if (deformVertices == nullptr || deformVertices->verticesData == nullptr || deformVertices->verticesData->offset != vertexOffset) { + return; + } else if (_timelineData != nullptr && _dragonBonesData != deformVertices->verticesData->data) { + return; + } + + SlotTimelineState::update(passedTime); + + // Fade animation. + if (_tweenState != TweenState::None || _dirty) { + auto& result = deformVertices->vertices; + + if (_animationState->_fadeState != 0 || _animationState->_subFadeState != 0) { + const auto fadeProgress = pow(_animationState->_fadeProgress, 2); + + if (_timelineData != nullptr) { + for (std::size_t i = 0; i < _deformCount; ++i) { + if (i < _valueOffset) { + result[i] += (_frameFloatArray[_frameFloatOffset + i] - result[i]) * fadeProgress; + } else if (i < _valueOffset + _valueCount) { + result[i] += (_result[i - _valueOffset] - result[i]) * fadeProgress; + } else { + result[i] += (_frameFloatArray[_frameFloatOffset + i - _valueCount] - result[i]) * fadeProgress; + } + } + } else { + _deformCount = result.size(); + + for (std::size_t i = 0; i < _deformCount; ++i) { + result[i] += (0.0f - result[i]) * fadeProgress; + } + } + + deformVertices->verticesDirty = true; + } else if (_dirty) { + _dirty = false; + + if (_timelineData != nullptr) { + for (std::size_t i = 0; i < _deformCount; ++i) { + if (i < _valueOffset) { + result[i] = _frameFloatArray[_frameFloatOffset + i]; + } else if (i < _valueOffset + _valueCount) { + result[i] = _result[i - _valueOffset]; + } else { + result[i] = _frameFloatArray[_frameFloatOffset + i - _valueCount]; + } + } + } else { + _deformCount = result.size(); + + for (std::size_t i = 0; i < _deformCount; ++i) { + result[i] = 0.0f; + } + } + + deformVertices->verticesDirty = true; + } + } +} + +void IKConstraintTimelineState::_onClear() { + ConstraintTimelineState::_onClear(); + + _current = 0.0f; + _delta = 0.0f; +} + +void IKConstraintTimelineState::_onArriveAtFrame() { + ConstraintTimelineState::_onArriveAtFrame(); + + const auto ikConstraint = static_cast(constraint); + + if (_timelineData != nullptr) { + auto valueOffset = _animationData->frameIntOffset + _frameValueOffset + _frameIndex * 2; + const auto frameIntArray = _frameIntArray; + const auto bendPositive = frameIntArray[valueOffset++] != 0; + _current = frameIntArray[valueOffset++] * 0.01f; + + if (_tweenState == TweenState::Always) { + if ((unsigned)_frameIndex == _frameCount - 1) { + valueOffset = _animationData->frameIntOffset + _frameValueOffset; // + 0 * 2 + } + + _delta = frameIntArray[valueOffset + 1] * 0.01f - _current; + } else { + _delta = 0.0f; + } + + ikConstraint->_bendPositive = bendPositive; + } else { + const auto ikConstraintData = static_cast(ikConstraint->_constraintData); + _current = ikConstraintData->weight; + _delta = 0.0f; + ikConstraint->_bendPositive = ikConstraintData->bendPositive; + } + + ikConstraint->invalidUpdate(); +} + +void IKConstraintTimelineState::_onUpdateFrame() { + ConstraintTimelineState::_onUpdateFrame(); + + if (_tweenState != TweenState::Always) { + _tweenState = TweenState::None; + } + + const auto ikConstraint = static_cast(constraint); + ikConstraint->_weight = _current + _delta * _tweenProgress; + ikConstraint->invalidUpdate(); +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/animation/TimelineState.h b/cocos/editor-support/dragonbones/animation/TimelineState.h new file mode 100644 index 0000000..60f4382 --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/TimelineState.h @@ -0,0 +1,192 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_TIMELINE_STATE_H +#define DRAGONBONES_TIMELINE_STATE_H + +#include "BaseTimelineState.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * @internal + */ +class ActionTimelineState : public TimelineState { + BIND_CLASS_TYPE_A(ActionTimelineState); + + void _onCrossFrame(unsigned frameIndex) const; + +protected: + virtual void _onArriveAtFrame() override {} + virtual void _onUpdateFrame() override {} + +public: + void update(float passedTime) override; + void setCurrentTime(float value); +}; +/** + * @internal + */ +class ZOrderTimelineState : public TimelineState { + BIND_CLASS_TYPE_A(ZOrderTimelineState); + +protected: + virtual void _onArriveAtFrame() override; + virtual void _onUpdateFrame() override {} +}; +/** + * @internal + */ +class BoneAllTimelineState : public BoneTimelineState { + BIND_CLASS_TYPE_A(BoneAllTimelineState); + +protected: + virtual void _onArriveAtFrame() override; + virtual void _onUpdateFrame() override; + +public: + virtual void fadeOut() override; +}; +/** + * @internal + */ +class BoneTranslateTimelineState : public BoneTimelineState { + BIND_CLASS_TYPE_A(BoneTranslateTimelineState); + +protected: + virtual void _onArriveAtFrame() override; + virtual void _onUpdateFrame() override; +}; +/** + * @internal + */ +class BoneRotateTimelineState : public BoneTimelineState { + BIND_CLASS_TYPE_A(BoneRotateTimelineState); + +protected: + virtual void _onArriveAtFrame() override; + virtual void _onUpdateFrame() override; + +public: + virtual void fadeOut() override; +}; +/** + * @internal + */ +class BoneScaleTimelineState : public BoneTimelineState { + BIND_CLASS_TYPE_A(BoneScaleTimelineState); + +protected: + virtual void _onArriveAtFrame() override; + virtual void _onUpdateFrame() override; +}; +/** + * @internal + */ +class SlotDislayTimelineState : public SlotTimelineState { + BIND_CLASS_TYPE_A(SlotDislayTimelineState); + +protected: + virtual void _onArriveAtFrame() override; +}; +/** + * @internal + */ +class SlotColorTimelineState : public SlotTimelineState { + BIND_CLASS_TYPE_B(SlotColorTimelineState); + +private: + bool _dirty; + int* _current; + int* _delta; + float* _result; + +public: + SlotColorTimelineState() : _current(new int[8]{0}), + _delta(new int[8]{0}), + _result(new float[8]{0.0f}) { + _onClear(); + } + ~SlotColorTimelineState() { + _onClear(); + + delete[] _current; + delete[] _delta; + delete[] _result; + } + +protected: + void _onClear() override; + void _onArriveAtFrame() override; + void _onUpdateFrame() override; + +public: + void fadeOut() override; + void update(float passedTime) override; +}; +/** + * @internal + */ +class DeformTimelineState : public SlotTimelineState { + BIND_CLASS_TYPE_A(DeformTimelineState); + +public: + unsigned vertexOffset; + +private: + bool _dirty; + unsigned _frameFloatOffset; + unsigned _deformCount; + unsigned _valueCount; + unsigned _valueOffset; + std::vector _current; + std::vector _delta; + std::vector _result; + +protected: + virtual void _onClear() override; + virtual void _onArriveAtFrame() override; + virtual void _onUpdateFrame() override; + +public: + virtual void init(Armature* armature, AnimationState* animationState, TimelineData* timelineData) override; + virtual void fadeOut() override; + virtual void update(float passedTime) override; +}; + +/** + * @internal + */ +class IKConstraintTimelineState : public ConstraintTimelineState { + BIND_CLASS_TYPE_A(IKConstraintTimelineState); + +private: + float _current; + float _delta; + +protected: + virtual void _onClear() override; + virtual void _onArriveAtFrame() override; + virtual void _onUpdateFrame() override; +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_TIMELINE_STATE_H diff --git a/cocos/editor-support/dragonbones/animation/WorldClock.cpp b/cocos/editor-support/dragonbones/animation/WorldClock.cpp new file mode 100644 index 0000000..d825a63 --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/WorldClock.cpp @@ -0,0 +1,125 @@ +#include "WorldClock.h" +#include + +DRAGONBONES_NAMESPACE_BEGIN + +WorldClock WorldClock::clock; + +void WorldClock::advanceTime(float passedTime) { + if (passedTime < 0.0f || passedTime != passedTime) { + passedTime = 0.0f; + } + + const auto currentTime = 0.0f; + + if (passedTime < 0.0f) { + passedTime = currentTime - _systemTime; + } + + _systemTime = currentTime; + + if (timeScale != 1.0f) { + passedTime *= timeScale; + } + + if (passedTime == 0.0f) { + return; + } + + if (passedTime < 0.0f) { + time -= passedTime; + } else { + time += passedTime; + } + + std::size_t i = 0, r = 0, l = _animatebles.size(); + for (; i < l; ++i) { + const auto animatable = _animatebles[i]; + if (animatable != nullptr) { + if (r > 0) { + _animatebles[i - r] = animatable; + _animatebles[i] = nullptr; + } + + animatable->advanceTime(passedTime); + } else { + r++; + } + } + + if (r > 0) { + l = _animatebles.size(); + for (; i < l; ++i) { + const auto animateble = _animatebles[i]; + if (animateble != nullptr) { + _animatebles[i - r] = animateble; + } else { + r++; + } + } + + _animatebles.resize(l - r); + } +} + +bool WorldClock::contains(const IAnimatable* value) const { + if (value == this) { + return false; + } + + auto ancestor = value; + while (ancestor != this && ancestor != nullptr) { + ancestor = ancestor->getClock(); + } + + return ancestor == this; +} + +void WorldClock::add(IAnimatable* value) { + if (std::find(_animatebles.begin(), _animatebles.end(), value) == _animatebles.end()) { + _animatebles.push_back(value); + value->setClock(this); + } +} + +void WorldClock::remove(IAnimatable* value) { + const auto iterator = std::find(_animatebles.begin(), _animatebles.end(), value); + if (iterator != _animatebles.end()) { + *iterator = nullptr; + value->setClock(nullptr); + } +} + +void WorldClock::render() { + for (const auto animatable : _animatebles) { + if (animatable != nullptr) { + animatable->render(); + } + } +} + +void WorldClock::clear() { + for (const auto animatable : _animatebles) { + if (animatable != nullptr) { + animatable->setClock(nullptr); + } + } +} + +void WorldClock::setClock(WorldClock* value) { + if (_clock == value) { + return; + } + + if (_clock != nullptr) { + _clock->remove(this); + } + + _clock = value; + + if (_clock != nullptr) { + _clock->add(this); + } +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/animation/WorldClock.h b/cocos/editor-support/dragonbones/animation/WorldClock.h new file mode 100644 index 0000000..8038d8e --- /dev/null +++ b/cocos/editor-support/dragonbones/animation/WorldClock.h @@ -0,0 +1,204 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_WORLD_CLOCK_H +#define DRAGONBONES_WORLD_CLOCK_H + +#include "../core/DragonBones.h" +#include "IAnimatable.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - Worldclock provides clock support for animations, advance time for each IAnimatable object added to the instance. + * @see dragonBones.IAnimateble + * @see dragonBones.Armature + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - WorldClock 对动画提供时钟支持,为每个加入到该实例的 IAnimatable 对象更新时间。 + * @see dragonBones.IAnimateble + * @see dragonBones.Armature + * @version DragonBones 3.0 + * @language zh_CN + */ +class WorldClock : public IAnimatable { + DRAGONBONES_DISALLOW_COPY_AND_ASSIGN(WorldClock) + +public: + /** + * - Deprecated, please refer to {@link dragonBones.BaseFactory#clock}. + * @deprecated + * @language en_US + */ + /** + * - 已废弃,请参考 {@link dragonBones.BaseFactory#clock}。 + * @deprecated + * @language zh_CN + */ + static WorldClock clock; + +public: + /** + * - Current time. (In seconds) + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 当前的时间。 (以秒为单位) + * @version DragonBones 3.0 + * @language zh_CN + */ + float time; + /** + * - The play speed, used to control animation speed-shift play. + * [0: Stop play, (0~1): Slow play, 1: Normal play, (1~N): Fast play] + * @default 1.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 播放速度,用于控制动画变速播放。 + * [0: 停止播放, (0~1): 慢速播放, 1: 正常播放, (1~N): 快速播放] + * @default 1.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float timeScale; + +private: + float _systemTime; + std::vector _animatebles; + WorldClock* _clock; + +public: + /** + * - Creating a Worldclock instance. Typically, you do not need to create Worldclock instance. + * When multiple Worldclock instances are running at different speeds, can achieving some specific animation effects, such as bullet time. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 创建一个 WorldClock 实例。通常并不需要创建 WorldClock 实例。 + * 当多个 WorldClock 实例使用不同的速度运行时,可以实现一些特殊的动画效果,比如子弹时间等。 + * @version DragonBones 3.0 + * @language zh_CN + */ + WorldClock(float timeValue = 0.0f) : time(timeValue), + timeScale(1.0f), + _systemTime(0.0f), + _animatebles(), + _clock(nullptr) { + _systemTime = 0.0f; + } + virtual ~WorldClock() { + clear(); + } + /** + * - Advance time for all IAnimatable instances. + * @param passedTime - Passed time. [-1: Automatically calculates the time difference between the current frame and the previous frame, [0~N): Passed time] (In seconds) + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 为所有的 IAnimatable 实例更新时间。 + * @param passedTime - 前进的时间。 [-1: 自动计算当前帧与上一帧的时间差, [0~N): 前进的时间] (以秒为单位) + * @version DragonBones 3.0 + * @language zh_CN + */ + virtual void advanceTime(float passedTime) override; + /** + * - render all IAnimatable instances. + * @version Cocos creator 2.3 + * @language en_US + */ + /** + * - 渲染所有的 IAnimatable 实例。 + * @version Cocos creator 2.3 + * @language zh_CN + */ + virtual void render() override; + + /** + * - Check whether contains a specific instance of IAnimatable. + * @param value - The IAnimatable instance. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 检查是否包含特定的 IAnimatable 实例。 + * @param value - IAnimatable 实例。 + * @version DragonBones 3.0 + * @language zh_CN + */ + bool contains(const IAnimatable* value) const; + /** + * - Add IAnimatable instance. + * @param value - The IAnimatable instance. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 添加 IAnimatable 实例。 + * @param value - IAnimatable 实例。 + * @version DragonBones 3.0 + * @language zh_CN + */ + void add(IAnimatable* value); + /** + * - Removes a specified IAnimatable instance. + * @param value - The IAnimatable instance. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 移除特定的 IAnimatable 实例。 + * @param value - IAnimatable 实例。 + * @version DragonBones 3.0 + * @language zh_CN + */ + void remove(IAnimatable* value); + /** + * - Clear all IAnimatable instances. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 清除所有的 IAnimatable 实例。 + * @version DragonBones 3.0 + * @language zh_CN + */ + void clear(); + /** + * @inheritDoc + */ + inline virtual WorldClock* getClock() const override { + return _clock; + } + virtual void setClock(WorldClock* value) override; + +public: // For WebAssembly. + static WorldClock* getStaticClock() { return &clock; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_WORLD_CLOCK_H diff --git a/cocos/editor-support/dragonbones/armature/Armature.cpp b/cocos/editor-support/dragonbones/armature/Armature.cpp new file mode 100644 index 0000000..da4d2dc --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/Armature.cpp @@ -0,0 +1,445 @@ +#include "Armature.h" +#include "../animation/Animation.h" +#include "../animation/WorldClock.h" +#include "../event/EventObject.h" +#include "../model/TextureAtlasData.h" +#include "../model/UserData.h" +#include "Bone.h" +#include "Constraint.h" +#include "IArmatureProxy.h" +#include "Slot.h" + +DRAGONBONES_NAMESPACE_BEGIN + +bool Armature::_onSortSlots(Slot* a, Slot* b) { + return a->_zOrder < b->_zOrder ? true : false; +} + +void Armature::_onClear() { + if (_clock != nullptr) // Remove clock before slots clear. + { + _clock->remove(this); + } + + for (const auto bone : _bones) { + bone->returnToPool(); + } + + for (const auto slot : _slots) { + slot->returnToPool(); + } + + for (const auto constraint : _constraints) { + constraint->returnToPool(); + } + + for (const auto action : _actions) { + action->returnToPool(); + } + + if (_animation != nullptr) { + _animation->returnToPool(); + } + + if (_proxy != nullptr) { + _proxy->dbClear(); + } + + if (_replaceTextureAtlasData != nullptr) { + _replaceTextureAtlasData->returnToPool(); + } + + inheritAnimation = true; + userData = nullptr; + + _debugDraw = false; + _lockUpdate = false; + _slotsDirty = false; + _zOrderDirty = false; + _flipX = false; + _flipY = false; + _cacheFrameIndex = -1; + _bones.clear(); + _slots.clear(); + _constraints.clear(); + _actions.clear(); + _armatureData = nullptr; + _animation = nullptr; + _proxy = nullptr; + _display = nullptr; + _replaceTextureAtlasData = nullptr; + _replacedTexture = nullptr; + _dragonBones = nullptr; + _clock = nullptr; + _parent = nullptr; +} + +void Armature::_sortZOrder(const int16_t* slotIndices, unsigned offset) { + const auto& slotDatas = _armatureData->sortedSlots; + const auto isOriginal = slotIndices == nullptr; + + if (_zOrderDirty || !isOriginal) { + for (std::size_t i = 0, l = slotDatas.size(); i < l; ++i) { + const auto slotIndex = isOriginal ? i : (std::size_t)slotIndices[offset + i]; + if (slotIndex < 0 || slotIndex >= l) { + continue; + } + + const auto slotData = slotDatas[slotIndex]; + const auto slot = getSlot(slotData->name); + if (slot != nullptr) { + slot->_setZorder(i); + } + } + + _slotsDirty = true; + _zOrderDirty = !isOriginal; + } +} + +void Armature::_addBone(Bone* value) { + if (std::find(_bones.begin(), _bones.end(), value) == _bones.end()) { + _bones.push_back(value); + } +} + +void Armature::_addSlot(Slot* value) { + if (std::find(_slots.begin(), _slots.end(), value) == _slots.end()) { + _slots.push_back(value); + } +} + +void Armature::_addConstraint(Constraint* value) { + if (std::find(_constraints.cbegin(), _constraints.cend(), value) == _constraints.cend()) { + _constraints.push_back(value); + } +} + +void Armature::_bufferAction(EventObject* action, bool append) { + if (std::find(_actions.cbegin(), _actions.cend(), action) == _actions.cend()) { + if (append) { + _actions.push_back(action); + } else { + _actions.insert(_actions.begin(), action); + } + } +} + +void Armature::dispose() { + if (_armatureData != nullptr) { + _lockUpdate = true; + _dragonBones->bufferObject(this); + } +} + +void Armature::init(ArmatureData* armatureData, IArmatureProxy* proxy, void* display, DragonBones* dragonBones) { + if (_armatureData != nullptr) { + return; + } + + _armatureData = armatureData; + _animation = BaseObject::borrowObject(); + _proxy = proxy; + _display = display; + _dragonBones = dragonBones; + + _proxy->dbInit(this); + _animation->init(this); + _animation->setAnimations(_armatureData->animations); +} + +void Armature::advanceTime(float passedTime) { + if (_lockUpdate) { + return; + } + + if (_armatureData == nullptr) { + DRAGONBONES_ASSERT(false, "The armature has been disposed."); + return; + } else if (_armatureData->parent == nullptr) { + DRAGONBONES_ASSERT(false, "The armature data has been disposed.\nPlease make sure dispose armature before call factory.clear()."); + return; + } + + const auto prevCacheFrameIndex = _cacheFrameIndex; + + // Update animation. + _animation->advanceTime(passedTime); + + // Sort slots. + if (_slotsDirty) { + _slotsDirty = false; + std::sort(_slots.begin(), _slots.end(), Armature::_onSortSlots); + } + + // Update bones and slots. + if (_cacheFrameIndex < 0 || _cacheFrameIndex != prevCacheFrameIndex) { + for (const auto bone : _bones) { + bone->update(_cacheFrameIndex); + } + + for (const auto slot : _slots) { + slot->update(_cacheFrameIndex); + } + } + + // Do actions. + if (!_actions.empty()) { + _lockUpdate = true; + + for (const auto action : _actions) { + const auto actionData = action->actionData; + + if (actionData != nullptr) { + if (actionData->type == ActionType::Play) { + if (action->slot != nullptr) { + const auto childArmature = action->slot->getChildArmature(); + if (childArmature != nullptr) { + childArmature->getAnimation()->fadeIn(actionData->name); + } + } else if (action->bone != nullptr) { + for (const auto slot : getSlots()) { + if (slot->getParent() == action->bone) { + const auto childArmature = slot->getChildArmature(); + if (childArmature != nullptr) { + childArmature->getAnimation()->fadeIn(actionData->name); + } + } + } + } else { + _animation->fadeIn(actionData->name); + } + } + } + + action->returnToPool(); + } + + _actions.clear(); + _lockUpdate = false; + } + + _proxy->dbUpdate(); +} + +void Armature::render() { + if (_proxy) { + _proxy->dbRender(); + } +} + +void Armature::invalidUpdate(const std::string& boneName, bool updateSlot) { + if (!boneName.empty()) { + const auto bone = getBone(boneName); + if (bone != nullptr) { + bone->invalidUpdate(); + + if (updateSlot) { + for (const auto slot : _slots) { + if (slot->getParent() == bone) { + slot->invalidUpdate(); + } + } + } + } + } else { + for (const auto bone : _bones) { + bone->invalidUpdate(); + } + + if (updateSlot) { + for (const auto slot : _slots) { + slot->invalidUpdate(); + } + } + } +} + +Slot* Armature::containsPoint(float x, float y) const { + for (const auto slot : _slots) { + if (slot->containsPoint(x, y)) { + return slot; + } + } + + return nullptr; +} + +Slot* Armature::intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA, + Point* intersectionPointB, + Point* normalRadians) const { + const auto isV = xA == xB; + auto dMin = 0.0f; + auto dMax = 0.0f; + auto intXA = 0.0f; + auto intYA = 0.0f; + auto intXB = 0.0f; + auto intYB = 0.0f; + auto intAN = 0.0f; + auto intBN = 0.0f; + Slot* intSlotA = nullptr; + Slot* intSlotB = nullptr; + + for (const auto& slot : _slots) { + auto intersectionCount = slot->intersectsSegment(xA, yA, xB, yB, intersectionPointA, intersectionPointB, normalRadians); + if (intersectionCount > 0) { + if (intersectionPointA != nullptr || intersectionPointB != nullptr) { + if (intersectionPointA != nullptr) { + float d = isV ? intersectionPointA->y - yA : intersectionPointA->x - xA; + if (d < 0.0f) { + d = -d; + } + + if (intSlotA == nullptr || d < dMin) { + dMin = d; + intXA = intersectionPointA->x; + intYA = intersectionPointA->y; + intSlotA = slot; + + if (normalRadians) { + intAN = normalRadians->x; + } + } + } + + if (intersectionPointB != nullptr) { + float d = intersectionPointB->x - xA; + if (d < 0.0f) { + d = -d; + } + + if (intSlotB == nullptr || d > dMax) { + dMax = d; + intXB = intersectionPointB->x; + intYB = intersectionPointB->y; + intSlotB = slot; + + if (normalRadians != nullptr) { + intBN = normalRadians->y; + } + } + } + } else { + intSlotA = slot; + break; + } + } + } + + if (intSlotA != nullptr && intersectionPointA != nullptr) { + intersectionPointA->x = intXA; + intersectionPointA->y = intYA; + + if (normalRadians != nullptr) { + normalRadians->x = intAN; + } + } + + if (intSlotB != nullptr && intersectionPointB != nullptr) { + intersectionPointB->x = intXB; + intersectionPointB->y = intYB; + + if (normalRadians != nullptr) { + normalRadians->y = intBN; + } + } + + return intSlotA; +} + +Bone* Armature::getBone(const std::string& name) const { + for (const auto& bone : _bones) { + if (bone->getName() == name) { + return bone; + } + } + + return nullptr; +} + +Bone* Armature::getBoneByDisplay(void* display) const { + const auto slot = getSlotByDisplay(display); + + return slot != nullptr ? slot->getParent() : nullptr; +} + +Slot* Armature::getSlot(const std::string& name) const { + for (const auto slot : _slots) { + if (slot->getName() == name) { + return slot; + } + } + + return nullptr; +} + +Slot* Armature::getSlotByDisplay(void* display) const { + if (display != nullptr) { + for (const auto slot : _slots) { + if (slot->getDisplay() == display) { + return slot; + } + } + } + + return nullptr; +} + +void Armature::setCacheFrameRate(unsigned value) { + if (_armatureData->cacheFrameRate != value) { + _armatureData->cacheFrames(value); + + for (const auto& slot : _slots) { + const auto childArmature = slot->getChildArmature(); + if (childArmature != nullptr && childArmature->getCacheFrameRate() == 0) { + childArmature->setCacheFrameRate(value); + } + } + } +} + +void Armature::setClock(WorldClock* value) { + if (_clock == value) { + return; + } + + if (_clock) { + _clock->remove(this); + } + + _clock = value; + + if (_clock) { + _clock->add(this); + } + + // Update childArmature clock. + for (const auto& slot : _slots) { + const auto childArmature = slot->getChildArmature(); + if (childArmature != nullptr) { + childArmature->setClock(_clock); + } + } +} + +void Armature::setReplacedTexture(void* value) { + if (_replacedTexture == value) { + return; + } + + if (_replaceTextureAtlasData != nullptr) { + _replaceTextureAtlasData->returnToPool(); + _replaceTextureAtlasData = nullptr; + } + + _replacedTexture = value; + + for (const auto& slot : _slots) { + slot->invalidUpdate(); + slot->update(-1); + } +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/armature/Armature.h b/cocos/editor-support/dragonbones/armature/Armature.h new file mode 100644 index 0000000..23f6269 --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/Armature.h @@ -0,0 +1,538 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_ARMATURE_H +#define DRAGONBONES_ARMATURE_H + +#include "../animation/IAnimatable.h" +#include "../core/BaseObject.h" +#include "../model/ArmatureData.h" +#include "IArmatureProxy.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - Armature is the core of the skeleton animation system. + * @see dragonBones.ArmatureData + * @see dragonBones.Bone + * @see dragonBones.Slot + * @see dragonBones.Animation + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 骨架是骨骼动画系统的核心。 + * @see dragonBones.ArmatureData + * @see dragonBones.Bone + * @see dragonBones.Slot + * @see dragonBones.Animation + * @version DragonBones 3.0 + * @language zh_CN + */ +class Armature : public virtual IAnimatable, public BaseObject { + BIND_CLASS_TYPE_B(Armature); + +private: + static bool _onSortSlots(Slot* a, Slot* b); + +public: + /** + * - Whether to inherit the animation control of the parent armature. + * True to try to have the child armature play an animation with the same name when the parent armature play the animation. + * @default true + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 是否继承父骨架的动画控制。 + * 如果该值为 true,当父骨架播放动画时,会尝试让子骨架播放同名动画。 + * @default true + * @version DragonBones 4.5 + * @language zh_CN + */ + bool inheritAnimation; + /** + * @private + */ + void* userData; + +public: + /** + * @internal + */ + int _cacheFrameIndex; + /** + * @internal + */ + ArmatureData* _armatureData; + /** + * @internal + */ + DragonBones* _dragonBones; + /** + * @internal + */ + Slot* _parent; + /** + * @internal + */ + TextureAtlasData* _replaceTextureAtlasData; + /** + * @internal + */ + std::vector _constraints; + +protected: + bool _debugDraw; + bool _lockUpdate; + bool _slotsDirty; + bool _zOrderDirty; + bool _flipX; + bool _flipY; + std::vector _bones; + std::vector _slots; + std::vector _actions; + Animation* _animation; + IArmatureProxy* _proxy; + void* _display; + WorldClock* _clock; + void* _replacedTexture; + +public: + Armature() : _animation(nullptr), + _proxy(nullptr), + _clock(nullptr), + _replaceTextureAtlasData(nullptr) { + _onClear(); + } + virtual ~Armature() { + _onClear(); + } + +protected: + virtual void _onClear() override; + +private: + void _sortSlots(); + +public: + /** + * @internal + */ + void _sortZOrder(const int16_t* slotIndices, unsigned offset); + /** + * @internal + */ + void _addBone(Bone* value); + /** + * @internal + */ + void _addSlot(Slot* value); + /** + * @internal + */ + void _addConstraint(Constraint* value); + /** + * @internal + */ + void _bufferAction(EventObject* action, bool append); + /** + * - Dispose the armature. (Return to the object pool) + * @example + * TypeScript style, for reference only. + *
+     *     removeChild(armature.display);
+     *     armature.dispose();
+     * 
+ * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 释放骨架。 (回收到对象池) + * @example + * TypeScript 风格,仅供参考。 + *
+     *     removeChild(armature.display);
+     *     armature.dispose();
+     * 
+ * @version DragonBones 3.0 + * @language zh_CN + */ + void dispose(); + /** + * @internal + */ + void init(ArmatureData* armatureData, IArmatureProxy* proxy, void* display, DragonBones* dragonBones); + /** + * @inheritDoc + */ + void advanceTime(float passedTime) override; + /** + * @inheritDoc + */ + void render() override; + /** + * - Forces a specific bone or its owning slot to update the transform or display property in the next frame. + * @param boneName - The bone name. (If not set, all bones will be update) + * @param updateSlot - Whether to update the bone's slots. (Default: false) + * @see dragonBones.Bone#invalidUpdate() + * @see dragonBones.Slot#invalidUpdate() + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 强制特定骨骼或其拥有的插槽在下一帧更新变换或显示属性。 + * @param boneName - 骨骼名称。 (如果未设置,将更新所有骨骼) + * @param updateSlot - 是否更新骨骼的插槽。 (默认: false) + * @see dragonBones.Bone#invalidUpdate() + * @see dragonBones.Slot#invalidUpdate() + * @version DragonBones 3.0 + * @language zh_CN + */ + void invalidUpdate(const std::string& boneName = "", bool updateSlot = false); + /** + * - Check whether a specific point is inside a custom bounding box in a slot. + * The coordinate system of the point is the inner coordinate system of the armature. + * Custom bounding boxes need to be customized in Dragonbones Pro. + * @param x - The horizontal coordinate of the point. + * @param y - The vertical coordinate of the point. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 检查特定点是否在某个插槽的自定义边界框内。 + * 点的坐标系为骨架内坐标系。 + * 自定义边界框需要在 DragonBones Pro 中自定义。 + * @param x - 点的水平坐标。 + * @param y - 点的垂直坐标。 + * @version DragonBones 5.0 + * @language zh_CN + */ + Slot* containsPoint(float x, float y) const; + /** + * - Check whether a specific segment intersects a custom bounding box for a slot in the armature. + * The coordinate system of the segment and intersection is the inner coordinate system of the armature. + * Custom bounding boxes need to be customized in Dragonbones Pro. + * @param xA - The horizontal coordinate of the beginning of the segment. + * @param yA - The vertical coordinate of the beginning of the segment. + * @param xB - The horizontal coordinate of the end point of the segment. + * @param yB - The vertical coordinate of the end point of the segment. + * @param intersectionPointA - The first intersection at which a line segment intersects the bounding box from the beginning to the end. (If not set, the intersection point will not calculated) + * @param intersectionPointB - The first intersection at which a line segment intersects the bounding box from the end to the beginning. (If not set, the intersection point will not calculated) + * @param normalRadians - The normal radians of the tangent of the intersection boundary box. [x: Normal radian of the first intersection tangent, y: Normal radian of the second intersection tangent] (If not set, the normal will not calculated) + * @returns The slot of the first custom bounding box where the segment intersects from the start point to the end point. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 检查特定线段是否与骨架的某个插槽的自定义边界框相交。 + * 线段和交点的坐标系均为骨架内坐标系。 + * 自定义边界框需要在 DragonBones Pro 中自定义。 + * @param xA - 线段起点的水平坐标。 + * @param yA - 线段起点的垂直坐标。 + * @param xB - 线段终点的水平坐标。 + * @param yB - 线段终点的垂直坐标。 + * @param intersectionPointA - 线段从起点到终点与边界框相交的第一个交点。 (如果未设置,则不计算交点) + * @param intersectionPointB - 线段从终点到起点与边界框相交的第一个交点。 (如果未设置,则不计算交点) + * @param normalRadians - 交点边界框切线的法线弧度。 [x: 第一个交点切线的法线弧度, y: 第二个交点切线的法线弧度] (如果未设置,则不计算法线) + * @returns 线段从起点到终点相交的第一个自定义边界框的插槽。 + * @version DragonBones 5.0 + * @language zh_CN + */ + Slot* intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA = nullptr, + Point* intersectionPointB = nullptr, + Point* normalRadians = nullptr) const; + /** + * - Get a specific bone. + * @param name - The bone name. + * @see dragonBones.Bone + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取特定的骨骼。 + * @param name - 骨骼名称。 + * @see dragonBones.Bone + * @version DragonBones 3.0 + * @language zh_CN + */ + Bone* getBone(const std::string& name) const; + /** + * - Get a specific bone by the display. + * @param display - The display object. + * @see dragonBones.Bone + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 通过显示对象获取特定的骨骼。 + * @param display - 显示对象。 + * @see dragonBones.Bone + * @version DragonBones 3.0 + * @language zh_CN + */ + Bone* getBoneByDisplay(void* display) const; + /** + * - Get a specific slot. + * @param name - The slot name. + * @see dragonBones.Slot + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取特定的插槽。 + * @param name - 插槽名称。 + * @see dragonBones.Slot + * @version DragonBones 3.0 + * @language zh_CN + */ + Slot* getSlot(const std::string& name) const; + /** + * - Get a specific slot by the display. + * @param display - The display object. + * @see dragonBones.Slot + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 通过显示对象获取特定的插槽。 + * @param display - 显示对象。 + * @see dragonBones.Slot + * @version DragonBones 3.0 + * @language zh_CN + */ + Slot* getSlotByDisplay(void* display) const; + /** + * - Get all bones. + * @see dragonBones.Bone + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取所有的骨骼。 + * @see dragonBones.Bone + * @version DragonBones 3.0 + * @language zh_CN + */ + inline const std::vector& getBones() const { + return _bones; + } + /** + * - Get all slots. + * @see dragonBones.Slot + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取所有的插槽。 + * @see dragonBones.Slot + * @version DragonBones 3.0 + * @language zh_CN + */ + inline const std::vector& getSlots() const { + return _slots; + } + /** + * - Whether to flip the armature horizontally. + * @version DragonBones 5.5 + * @language en_US + */ + /** + * - 是否将骨架水平翻转。 + * @version DragonBones 5.5 + * @language zh_CN + */ + bool getFlipX() const { + return _flipX; + } + void setFlipX(bool value) { + _flipX = value; + invalidUpdate(""); + } + + /** + * - Whether to flip the armature vertically. + * @version DragonBones 5.5 + * @language en_US + */ + /** + * - 是否将骨架垂直翻转。 + * @version DragonBones 5.5 + * @language zh_CN + */ + bool getFlipY() const { + return _flipY; + } + void setFlipY(bool value) { + _flipY = value; + invalidUpdate(""); + } + /** + * - The animation cache frame rate, which turns on the animation cache when the set value is greater than 0. + * There is a certain amount of memory overhead to improve performance by caching animation data in memory. + * The frame rate should not be set too high, usually with the frame rate of the animation is similar and lower than the program running frame rate. + * When the animation cache is turned on, some features will fail, such as the offset property of bone. + * @example + * TypeScript style, for reference only. + *
+     *     armature.cacheFrameRate = 24;
+     * 
+ * @see dragonBones.DragonBonesData#frameRate + * @see dragonBones.ArmatureData#frameRate + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 动画缓存帧率,当设置的值大于 0 的时,将会开启动画缓存。 + * 通过将动画数据缓存在内存中来提高运行性能,会有一定的内存开销。 + * 帧率不宜设置的过高,通常跟动画的帧率相当且低于程序运行的帧率。 + * 开启动画缓存后,某些功能将会失效,比如骨骼的 offset 属性等。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     armature.cacheFrameRate = 24;
+     * 
+ * @see dragonBones.DragonBonesData#frameRate + * @see dragonBones.ArmatureData#frameRate + * @version DragonBones 4.5 + * @language zh_CN + */ + inline unsigned getCacheFrameRate() const { + return _armatureData->cacheFrameRate; + } + void setCacheFrameRate(unsigned value); + /** + * - The armature name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 骨架名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline const std::string& getName() const { + return _armatureData->name; + } + /** + * - The armature data. + * @see dragonBones.ArmatureData + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 骨架数据。 + * @see dragonBones.ArmatureData + * @version DragonBones 4.5 + * @language zh_CN + */ + inline const ArmatureData* getArmatureData() const { + return _armatureData; + } + /** + * - The animation player. + * @see dragonBones.Animation + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 动画播放器。 + * @see dragonBones.Animation + * @version DragonBones 3.0 + * @language zh_CN + */ + inline Animation* getAnimation() const { + return _animation; + } + /** + * @pivate + */ + inline IArmatureProxy* getProxy() const { + return _proxy; + } + /** + * - The EventDispatcher instance of the armature. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 该骨架的 EventDispatcher 实例。 + * @version DragonBones 4.5 + * @language zh_CN + */ + inline IEventDispatcher* getEventDispatcher() const { + return _proxy; + } + /** + * - The display container. + * The display of the slot is displayed as the parent. + * Depending on the rendering engine, the type will be different, usually the DisplayObjectContainer type. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 显示容器实例。 + * 插槽的显示对象都会以此显示容器为父级。 + * 根据渲染引擎的不同,类型会不同,通常是 DisplayObjectContainer 类型。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline void* getDisplay() const { + return _display; + } + /** + * @private + */ + inline void* getReplacedTexture() const { + return _replacedTexture; + } + void setReplacedTexture(void* value); + /** + * @inheritDoc + */ + inline WorldClock* getClock() const override { + return _clock; + } + void setClock(WorldClock* value) override; + /** + * - Get the parent slot which the armature belongs to. + * @see dragonBones.Slot + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 该骨架所属的父插槽。 + * @see dragonBones.Slot + * @version DragonBones 4.5 + * @language zh_CN + */ + inline Slot* getParent() const { + return _parent; + } + +public: // For WebAssembly. + IAnimatable* getAnimatable() const { return (IAnimatable*)this; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_ARMATURE_H diff --git a/cocos/editor-support/dragonbones/armature/Bone.cpp b/cocos/editor-support/dragonbones/armature/Bone.cpp new file mode 100644 index 0000000..fcb8706 --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/Bone.cpp @@ -0,0 +1,331 @@ +#include "Bone.h" +#include "../animation/AnimationState.h" +#include "../geom/Matrix.h" +#include "../geom/Transform.h" +#include "Armature.h" +#include "Constraint.h" +#include "Slot.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void Bone::_onClear() { + TransformObject::_onClear(); + + offsetMode = OffsetMode::Additive; + animationPose.identity(); + + _transformDirty = false; + _childrenTransformDirty = false; + _localDirty = true; + _hasConstraint = false; + _visible = true; + _cachedFrameIndex = -1; + _blendState.clear(); + _boneData = nullptr; + _parent = nullptr; + _cachedFrameIndices = nullptr; +} + +void Bone::_updateGlobalTransformMatrix(bool isCache) { + const auto flipX = _armature->getFlipX(); + const auto flipY = _armature->getFlipY() == DragonBones::yDown; + auto inherit = _parent != nullptr; + auto rotation = 0.0f; + + if (offsetMode == OffsetMode::Additive) { + if (origin != nullptr) { + // global = *origin; // Copy. + // global.add(offset).add(animationPose); + global.x = origin->x + offset.x + animationPose.x; + global.scaleX = origin->scaleX * offset.scaleX * animationPose.scaleX; + global.scaleY = origin->scaleY * offset.scaleY * animationPose.scaleY; + if (DragonBones::yDown) { + global.y = origin->y + offset.y + animationPose.y; + global.skew = origin->skew + offset.skew + animationPose.skew; + global.rotation = origin->rotation + offset.rotation + animationPose.rotation; + } else { + global.y = origin->y - offset.y + animationPose.y; + global.skew = origin->skew - offset.skew + animationPose.skew; + global.rotation = origin->rotation - offset.rotation + animationPose.rotation; + } + } else { + global = offset; // Copy. + if (!DragonBones::yDown) { + global.y = -global.y; + global.skew = -global.skew; + global.rotation = -global.rotation; + } + global.add(animationPose); + } + } else if (offsetMode == OffsetMode::None) { + if (origin != nullptr) { + global = *origin; + global.add(animationPose); + } else { + global = animationPose; + } + } else { + inherit = false; + global = offset; + if (!DragonBones::yDown) { + global.y = -global.y; + global.skew = -global.skew; + global.rotation = -global.rotation; + } + } + + if (inherit) { + const auto& parentMatrix = _parent->globalTransformMatrix; + if (_boneData->inheritScale) { + if (!_boneData->inheritRotation) { + _parent->updateGlobalTransform(); + + if (flipX && flipY) { + rotation = global.rotation - (_parent->global.rotation + Transform::PI); + } else if (flipX) { + rotation = global.rotation + _parent->global.rotation + Transform::PI; + } else if (flipY) { + rotation = global.rotation + _parent->global.rotation; + } else { + rotation = global.rotation - _parent->global.rotation; + } + + global.rotation = rotation; + } + + global.toMatrix(globalTransformMatrix); + globalTransformMatrix.concat(parentMatrix); + + if (_boneData->inheritTranslation) { + global.x = globalTransformMatrix.tx; + global.y = globalTransformMatrix.ty; + } else { + globalTransformMatrix.tx = global.x; + globalTransformMatrix.ty = global.y; + } + + if (isCache) { + global.fromMatrix(globalTransformMatrix); + } else { + _globalDirty = true; + } + } else { + if (_boneData->inheritTranslation) { + const auto x = global.x; + const auto y = global.y; + global.x = parentMatrix.a * x + parentMatrix.c * y + parentMatrix.tx; + global.y = parentMatrix.b * x + parentMatrix.d * y + parentMatrix.ty; + } else { + if (flipX) { + global.x = -global.x; + } + + if (flipY) { + global.y = -global.y; + } + } + + if (_boneData->inheritRotation) { + _parent->updateGlobalTransform(); + + if (_parent->global.scaleX < 0.0f) { + rotation = global.rotation + _parent->global.rotation + Transform::PI; + } else { + rotation = global.rotation + _parent->global.rotation; + } + + if (parentMatrix.a * parentMatrix.d - parentMatrix.b * parentMatrix.c < 0.0f) { + rotation -= global.rotation * 2.0f; + + if (flipX != flipY || _boneData->inheritReflection) { + global.skew += Transform::PI; + } + + if (!DragonBones::yDown) { + global.skew = -global.skew; + } + } + + global.rotation = rotation; + } else if (flipX || flipY) { + if (flipX && flipY) { + rotation = global.rotation + Transform::PI; + } else { + if (flipX) { + rotation = Transform::PI - global.rotation; + } else { + rotation = -global.rotation; + } + + global.skew += Transform::PI; + } + + global.rotation = rotation; + } + + global.toMatrix(globalTransformMatrix); + } + } else { + if (flipX || flipY) { + if (flipX) { + global.x = -global.x; + } + + if (flipY) { + global.y = -global.y; + } + + if (flipX && flipY) { + rotation = global.rotation + Transform::PI; + } else { + if (flipX) { + rotation = Transform::PI - global.rotation; + } else { + rotation = -global.rotation; + } + + global.skew += Transform::PI; + } + + global.rotation = rotation; + } + + global.toMatrix(globalTransformMatrix); + } +} + +void Bone::init(const BoneData* boneData, Armature* armatureValue) { + if (_boneData != nullptr) { + return; + } + + _boneData = boneData; + _armature = armatureValue; + + if (_boneData->parent != nullptr) { + _parent = _armature->getBone(_boneData->parent->name); + } + + _armature->_addBone(this); + // + origin = &(_boneData->transform); +} + +void Bone::update(int cacheFrameIndex) { + _blendState.dirty = false; + + if (cacheFrameIndex >= 0 && _cachedFrameIndices != nullptr) { + const auto cachedFrameIndex = (*_cachedFrameIndices)[cacheFrameIndex]; + if (cachedFrameIndex >= 0 && _cachedFrameIndex == cachedFrameIndex) // Same cache. + { + _transformDirty = false; + } else if (cachedFrameIndex >= 0) // Has been Cached. + { + _transformDirty = true; + _cachedFrameIndex = cachedFrameIndex; + } else { + if (_hasConstraint) // Update constraints. + { + for (const auto constraint : _armature->_constraints) { + if (constraint->_root == this) { + constraint->update(); + } + } + } + + if (_transformDirty || (_parent != nullptr && _parent->_childrenTransformDirty)) // Dirty. + { + _transformDirty = true; + _cachedFrameIndex = -1; + } else if (_cachedFrameIndex >= 0) // Same cache, but not set index yet. + { + _transformDirty = false; + (*_cachedFrameIndices)[cacheFrameIndex] = _cachedFrameIndex; + } else // Dirty. + { + _transformDirty = true; + _cachedFrameIndex = -1; + } + } + } else { + if (_hasConstraint) // Update constraints. + { + for (const auto constraint : _armature->_constraints) { + if (constraint->_root == this) { + constraint->update(); + } + } + } + + if (_transformDirty || (_parent != nullptr && _parent->_childrenTransformDirty)) // Dirty. + { + cacheFrameIndex = -1; + _transformDirty = true; + _cachedFrameIndex = -1; + } + } + + if (_transformDirty) { + _transformDirty = false; + _childrenTransformDirty = true; + // + if (_cachedFrameIndex < 0) { + const auto isCache = cacheFrameIndex >= 0; + if (_localDirty) { + _updateGlobalTransformMatrix(isCache); + } + + if (isCache && _cachedFrameIndices != nullptr) { + _cachedFrameIndex = (*_cachedFrameIndices)[cacheFrameIndex] = _armature->_armatureData->setCacheFrame(globalTransformMatrix, global); + } + } else { + _armature->_armatureData->getCacheFrame(globalTransformMatrix, global, _cachedFrameIndex); + } + // + } else if (_childrenTransformDirty) { + _childrenTransformDirty = false; + } + + _localDirty = true; +} + +void Bone::updateByConstraint() { + if (_localDirty) { + _localDirty = false; + + if (_transformDirty || (_parent != nullptr && _parent->_childrenTransformDirty)) { + _updateGlobalTransformMatrix(true); + } + + _transformDirty = true; + } +} + +bool Bone::contains(const Bone* value) const { + if (value == this) { + return false; + } + + auto ancestor = value; + while (ancestor != this && ancestor != nullptr) { + ancestor = ancestor->getParent(); + } + + return ancestor == this; +} + +void Bone::setVisible(bool value) { + if (_visible == value) { + return; + } + + _visible = value; + + for (const auto& slot : _armature->getSlots()) { + if (slot->getParent() == this) { + slot->_updateVisible(); + } + } +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/armature/Bone.h b/cocos/editor-support/dragonbones/armature/Bone.h new file mode 100644 index 0000000..fc0053c --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/Bone.h @@ -0,0 +1,229 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_BONE_H +#define DRAGONBONES_BONE_H + +#include "../animation/AnimationState.h" +#include "../model/ArmatureData.h" +#include "TransformObject.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - Bone is one of the most important logical units in the armature animation system, + * and is responsible for the realization of translate, rotation, scaling in the animations. + * A armature can contain multiple bones. + * @see dragonBones.BoneData + * @see dragonBones.Armature + * @see dragonBones.Slot + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 骨骼在骨骼动画体系中是最重要的逻辑单元之一,负责动画中的平移、旋转、缩放的实现。 + * 一个骨架中可以包含多个骨骼。 + * @see dragonBones.BoneData + * @see dragonBones.Armature + * @see dragonBones.Slot + * @version DragonBones 3.0 + * @language zh_CN + */ +class Bone final : public TransformObject { + BIND_CLASS_TYPE_A(Bone); + +public: + /** + * - The offset mode. + * @see #offset + * @version DragonBones 5.5 + * @language en_US + */ + /** + * - 偏移模式。 + * @see #offset + * @version DragonBones 5.5 + * @language zh_CN + */ + OffsetMode offsetMode; + /** + * @internal + */ + Transform animationPose; + /** + * @internal + */ + bool _transformDirty; + /** + * @internal + */ + bool _childrenTransformDirty; + /** + * @internal + */ + bool _hasConstraint; + /** + * @internal + */ + BlendState _blendState; + /** + * @internal + */ + const BoneData* _boneData; + /** + * @internal + */ + std::vector* _cachedFrameIndices; + +protected: + bool _localDirty; + bool _visible; + int _cachedFrameIndex; + /** + * @private + */ + Bone* _parent; + +protected: + void _onClear() override; + void _updateGlobalTransformMatrix(bool isCache); + +public: + /** + * @internal + */ + void init(const BoneData* boneData, Armature* armatureValue); + /** + * @internal + */ + void update(int cacheFrameIndex); + /** + * @internal + */ + void updateByConstraint(); + /** + * - Forces the bone to update the transform in the next frame. + * When the bone is not animated or its animation state is finished, the bone will not continue to update, + * and when the skeleton must be updated for some reason, the method needs to be called explicitly. + * @example + * TypeScript style, for reference only. + *
+     *     let bone = armature.getBone("arm");
+     *     bone.offset.scaleX = 2.0;
+     *     bone.invalidUpdate();
+     * 
+ * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 强制骨骼在下一帧更新变换。 + * 当该骨骼没有动画状态或其动画状态播放完成时,骨骼将不在继续更新,而此时由于某些原因必须更新骨骼时,则需要显式调用该方法。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     let bone = armature.getBone("arm");
+     *     bone.offset.scaleX = 2.0;
+     *     bone.invalidUpdate();
+     * 
+ * @version DragonBones 3.0 + * @language zh_CN + */ + inline void invalidUpdate() { + _transformDirty = true; + } + /** + * - Check whether the bone contains a specific bone. + * @see dragonBones.Bone + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 检查该骨骼是否包含特定的骨骼。 + * @see dragonBones.Bone + * @version DragonBones 3.0 + * @language zh_CN + */ + bool contains(const Bone* value) const; + /** + * - The bone data. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 骨骼数据。 + * @version DragonBones 4.5 + * @language zh_CN + */ + inline const BoneData* getBoneData() const { + return _boneData; + } + /** + * - The visible of all slots in the bone. + * @default true + * @see dragonBones.Slot#visible + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 此骨骼所有插槽的可见。 + * @default true + * @see dragonBones.Slot#visible + * @version DragonBones 3.0 + * @language zh_CN + */ + inline bool getVisible() const { + return _visible; + } + void setVisible(bool value); + /** + * - The bone name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 骨骼名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline const std::string& getName() const { + return _boneData->name; + } + /** + * - The parent bone to which it belongs. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 所属的父骨骼。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline Bone* getParent() const { + return _parent; + } + +public: // For WebAssembly. + inline int getOffsetMode() const { return (int)offsetMode; } + inline void setOffsetMode(int value) { offsetMode = (OffsetMode)value; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_BONE_H diff --git a/cocos/editor-support/dragonbones/armature/Constraint.cpp b/cocos/editor-support/dragonbones/armature/Constraint.cpp new file mode 100644 index 0000000..9bc4e05 --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/Constraint.cpp @@ -0,0 +1,160 @@ +// +// Created by liangshuochen on 12/06/2017. +// + +#include "Constraint.h" +#include "Armature.h" +#include "Bone.h" + +DRAGONBONES_NAMESPACE_BEGIN + +Matrix Constraint::_helpMatrix; +Transform Constraint::_helpTransform; +Point Constraint::_helpPoint; + +void Constraint::_onClear() { + _constraintData = nullptr; + _armature = nullptr; + _target = nullptr; + _root = nullptr; + _bone = nullptr; +} + +void IKConstraint::_onClear() { + Constraint::_onClear(); + + _scaleEnabled = false; + _bendPositive = false; + _weight = 1.0f; +} + +void IKConstraint::_computeA() { + const auto& ikGlobal = _target->global; + auto& global = _root->global; + auto& globalTransformMatrix = _root->globalTransformMatrix; + + auto radian = std::atan2(ikGlobal.y - global.y, ikGlobal.x - global.x); + if (global.scaleX < 0.0f) { + radian += Transform::PI; + } + + global.rotation += Transform::normalizeRadian(radian - global.rotation) * _weight; + global.toMatrix(globalTransformMatrix); +} + +void IKConstraint::_computeB() { + const auto boneLength = _bone->_boneData->length; + const auto parent = _root; + const auto& ikGlobal = _target->global; + auto& parentGlobal = parent->global; + auto& global = _bone->global; + auto& globalTransformMatrix = _bone->globalTransformMatrix; + + const auto x = globalTransformMatrix.a * boneLength; + const auto y = globalTransformMatrix.b * boneLength; + const auto lLL = x * x + y * y; + const auto lL = sqrt(lLL); + auto dX = global.x - parentGlobal.x; + auto dY = global.y - parentGlobal.y; + const auto lPP = dX * dX + dY * dY; + const auto lP = sqrt(lPP); + const auto rawRadian = global.rotation; + const auto rawParentRadian = parentGlobal.rotation; + const auto rawRadianA = std::atan2(dY, dX); + + dX = ikGlobal.x - parentGlobal.x; + dY = ikGlobal.y - parentGlobal.y; + const auto lTT = dX * dX + dY * dY; + const auto lT = sqrt(lTT); + + auto radianA = 0.0f; + if (lL + lP <= lT || lT + lL <= lP || lT + lP <= lL) { + radianA = std::atan2(ikGlobal.y - parentGlobal.y, ikGlobal.x - parentGlobal.x); + if (lL + lP <= lT) { + } else if (lP < lL) { + radianA += Transform::PI; + } + } else { + const auto h = (lPP - lLL + lTT) / (2.0f * lTT); + const auto r = sqrt(lPP - h * h * lTT) / lT; + const auto hX = parentGlobal.x + (dX * h); + const auto hY = parentGlobal.y + (dY * h); + const auto rX = -dY * r; + const auto rY = dX * r; + + auto isPPR = false; + const auto parentParent = parent->getParent(); + if (parentParent != nullptr) { + auto parentParentMatrix = parentParent->globalTransformMatrix; + isPPR = parentParentMatrix.a * parentParentMatrix.d - parentParentMatrix.b * parentParentMatrix.c < 0.0f; + } + + if (isPPR != _bendPositive) { + global.x = hX - rX; + global.y = hY - rY; + } else { + global.x = hX + rX; + global.y = hY + rY; + } + + radianA = std::atan2(global.y - parentGlobal.y, global.x - parentGlobal.x); + } + + const auto dR = Transform::normalizeRadian(radianA - rawRadianA); + parentGlobal.rotation = rawParentRadian + dR * _weight; + parentGlobal.toMatrix(parent->globalTransformMatrix); + // + const auto currentRadianA = rawRadianA + dR * _weight; + global.x = parentGlobal.x + cos(currentRadianA) * lP; + global.y = parentGlobal.y + sin(currentRadianA) * lP; + // + auto radianB = std::atan2(ikGlobal.y - global.y, ikGlobal.x - global.x); + if (global.scaleX < 0.0f) { + radianB += Transform::PI; + } + + global.rotation = parentGlobal.rotation + rawRadian - rawParentRadian + Transform::normalizeRadian(radianB - dR - rawRadian) * _weight; + global.toMatrix(globalTransformMatrix); +} + +void IKConstraint::init(ConstraintData* constraintData, Armature* armature) { + if (_constraintData != nullptr) { + return; + } + + _constraintData = constraintData; + _armature = armature; + _target = _armature->getBone(_constraintData->target->name); + _root = _armature->getBone(_constraintData->root->name); + _bone = _constraintData->bone != nullptr ? _armature->getBone(_constraintData->bone->name) : nullptr; + + { + const auto ikConstraintData = static_cast(_constraintData); + _bendPositive = ikConstraintData->bendPositive; + _scaleEnabled = ikConstraintData->scaleEnabled; + _weight = ikConstraintData->weight; + } + + _root->_hasConstraint = true; +} + +void IKConstraint::update() { + _root->updateByConstraint(); + + if (_bone != nullptr) { + _bone->updateByConstraint(); + _computeB(); + } else { + _computeA(); + } +} + +void IKConstraint::invalidUpdate() { + _root->invalidUpdate(); + + if (_bone != nullptr) { + _bone->invalidUpdate(); + } +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/armature/Constraint.h b/cocos/editor-support/dragonbones/armature/Constraint.h new file mode 100644 index 0000000..07b0835 --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/Constraint.h @@ -0,0 +1,116 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +// +// Created by liangshuochen on 12/06/2017. +// + +#ifndef DRAGONBONESCPP_CONSTRAINTS_H +#define DRAGONBONESCPP_CONSTRAINTS_H + +#include "../core/BaseObject.h" +#include "../geom/Matrix.h" +#include "../geom/Point.h" +#include "../geom/Transform.h" +#include "../model/ArmatureData.h" +#include "../model/ConstraintData.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * @internal + */ +class Constraint : public BaseObject { + ABSTRACT_CLASS(Constraint) + +protected: + static Matrix _helpMatrix; + static Transform _helpTransform; + static Point _helpPoint; + +public: + /** + * - For timeline state. + * @internal + */ + ConstraintData* _constraintData; + /** + * - For sort bones. + * @internal + */ + Bone* _target; + /** + * - For sort bones. + * @internal + */ + Bone* _root; + +protected: + Armature* _armature; + Bone* _bone; + + virtual void _onClear() override; + +public: + virtual void init(ConstraintData* constraintData, Armature* armature) = 0; + virtual void update() = 0; + virtual void invalidUpdate() = 0; + + inline const std::string& getName() { + return _constraintData->name; + } +}; +/** + * @internal + */ +class IKConstraint : public Constraint { + BIND_CLASS_TYPE_A(IKConstraint); + +public: + /** + * - For timeline state. + * @internal + */ + bool _bendPositive; + /** + * - For timeline state. + * @internal + */ + float _weight; + +private: + bool _scaleEnabled; + +protected: + virtual void _onClear() override; + +private: + void _computeA(); + void _computeB(); + +public: + virtual void init(ConstraintData* constraintData, Armature* armature) override; + virtual void update() override; + virtual void invalidUpdate() override; +}; + +DRAGONBONES_NAMESPACE_END +#endif //DRAGONBONESCPP_CONSTRAINTS_H diff --git a/cocos/editor-support/dragonbones/armature/DeformVertices.cpp b/cocos/editor-support/dragonbones/armature/DeformVertices.cpp new file mode 100644 index 0000000..fc3cf16 --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/DeformVertices.cpp @@ -0,0 +1,61 @@ +#include "DeformVertices.h" +#include "../armature/Armature.h" +#include "../armature/Bone.h" +#include "../model/DisplayData.h" +#include "../model/DragonBonesData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void DeformVertices::_onClear() { + verticesDirty = false; + vertices.clear(); + bones.clear(); + verticesData = nullptr; +} + +void DeformVertices::init(const VerticesData* verticesDataValue, Armature* armature) { + verticesData = verticesDataValue; + + if (verticesData != nullptr) { + unsigned vertexCount = 0; + if (verticesData->weight != nullptr) { + vertexCount = verticesData->weight->count * 2; + } else { + vertexCount = verticesData->data->intArray[verticesData->offset + (unsigned)BinaryOffset::MeshVertexCount] * 2; + } + + verticesDirty = true; + vertices.resize(vertexCount); + bones.clear(); + // + for (std::size_t i = 0, l = vertices.size(); i < l; ++i) { + vertices[i] = 0.0f; + } + + if (verticesData->weight != nullptr) { + for (std::size_t i = 0, l = verticesData->weight->bones.size(); i < l; ++i) { + const auto bone = armature->getBone(verticesData->weight->bones[i]->name); + if (bone != nullptr) { + bones.push_back(bone); + } + } + } + } else { + verticesDirty = false; + vertices.clear(); + bones.clear(); + verticesData = nullptr; + } +} + +bool DeformVertices::isBonesUpdate() const { + for (const auto bone : bones) { + if (bone != nullptr && bone->_childrenTransformDirty) { + return true; + } + } + + return false; +} + +DRAGONBONES_NAMESPACE_END \ No newline at end of file diff --git a/cocos/editor-support/dragonbones/armature/DeformVertices.h b/cocos/editor-support/dragonbones/armature/DeformVertices.h new file mode 100644 index 0000000..9df25b3 --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/DeformVertices.h @@ -0,0 +1,51 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_DEFORMVERTICES_H +#define DRAGONBONES_DEFORMVERTICES_H + +#include "../core/BaseObject.h" + +DRAGONBONES_NAMESPACE_BEGIN + +/** + * @internal + */ +class DeformVertices : public BaseObject { + BIND_CLASS_TYPE_A(DeformVertices); + +public: + bool verticesDirty; + std::vector vertices; + std::vector bones; + const VerticesData* verticesData; + +protected: + virtual void _onClear() override; + +public: + void init(const VerticesData* weightData, Armature* armature); + bool isBonesUpdate() const; +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_DEFORMVERTICES_H \ No newline at end of file diff --git a/cocos/editor-support/dragonbones/armature/IArmatureProxy.h b/cocos/editor-support/dragonbones/armature/IArmatureProxy.h new file mode 100644 index 0000000..7d4a36b --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/IArmatureProxy.h @@ -0,0 +1,111 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_IARMATURE_PROXY_H +#define DRAGONBONES_IARMATURE_PROXY_H + +#include "../core/DragonBones.h" +#include "../event/IEventDispatcher.h" +#include "MiddlewareManager.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The armature proxy interface, the docking engine needs to implement it concretely. + * @see dragonBones.Armature + * @version DragonBones 5.0 + * @language en_US + */ +/** + * - 骨架代理接口,对接的引擎需要对其进行具体实现。 + * @see dragonBones.Armature + * @version DragonBones 5.0 + * @language zh_CN + */ +class IArmatureProxy : public IEventDispatcher { + ABSTRACT_CLASS(IArmatureProxy); + +public: + /** + * @internal + */ + virtual void dbInit(Armature* armature) = 0; + /** + * @internal + */ + virtual void dbClear() = 0; + /** + * @internal + */ + virtual void dbUpdate() = 0; + /** + * @internal + */ + virtual void dbRender() = 0; + /** + * - Dispose the instance and the Armature instance. (The Armature instance will return to the object pool) + * @example + * TypeScript style, for reference only. + *
+     *     removeChild(armatureDisplay);
+     *     armatureDisplay.dispose();
+     * 
+ * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 释放该实例和骨架。 (骨架会回收到对象池) + * @example + * TypeScript 风格,仅供参考。 + *
+     *     removeChild(armatureDisplay);
+     *     armatureDisplay.dispose();
+     * 
+ * @version DragonBones 4.5 + * @language zh_CN + */ + virtual void dispose() = 0; + /** + * - The armature. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 骨架。 + * @version DragonBones 4.5 + * @language zh_CN + */ + virtual Armature* getArmature() const = 0; + /** + * - The animation player. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 动画播放器。 + * @version DragonBones 3.0 + * @language zh_CN + */ + virtual Animation* getAnimation() const = 0; +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_IARMATURE_PROXY_H diff --git a/cocos/editor-support/dragonbones/armature/Slot.cpp b/cocos/editor-support/dragonbones/armature/Slot.cpp new file mode 100644 index 0000000..3dfc055 --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/Slot.cpp @@ -0,0 +1,736 @@ +#include "Slot.h" +#include "../animation/Animation.h" +#include "../event/EventObject.h" +#include "../model/BoundingBoxData.h" +#include "../model/DisplayData.h" +#include "../model/DragonBonesData.h" +#include "../model/SkinData.h" +#include "../model/TextureAtlasData.h" +#include "../model/UserData.h" +#include "Armature.h" +#include "Bone.h" +#include "DeformVertices.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void Slot::_onClear() { + TransformObject::_onClear(); + + std::vector> disposeDisplayList; + for (const auto& pair : this->_displayList) { + if ( + pair.first != nullptr && pair.first != _rawDisplay && pair.first != _meshDisplay && + std::find(disposeDisplayList.cbegin(), disposeDisplayList.cend(), pair) == disposeDisplayList.cend()) { + disposeDisplayList.push_back(pair); + } + } + + for (const auto& pair : disposeDisplayList) { + if (pair.second == DisplayType::Armature) { + static_cast(pair.first)->returnToPool(); + } else { + _disposeDisplay(pair.first, true); + } + } + + if (_deformVertices != nullptr) { + _deformVertices->returnToPool(); + } + + if (_meshDisplay && _meshDisplay != _rawDisplay) { + _disposeDisplay(_meshDisplay, false); + } + + if (_rawDisplay) { + _disposeDisplay(_rawDisplay, false); + } + + displayController = ""; + + _displayDirty = false; + _zOrderDirty = false; + _blendModeDirty = false; + _colorDirty = false; + _transformDirty = false; + _visible = true; + _blendMode = BlendMode::Normal; + _displayIndex = -1; + _animationDisplayIndex = -1; + _zOrder = 0; + _cachedFrameIndex = -1; + _pivotX = 0.0f; + _pivotY = 0.0f; + _localMatrix.identity(); + _colorTransform.identity(); + _displayList.clear(); + _displayDatas.clear(); + _slotData = nullptr; + _rawDisplayDatas = nullptr; // + _displayData = nullptr; + _boundingBoxData = nullptr; + _textureData = nullptr; + _deformVertices = nullptr; + _rawDisplay = nullptr; + _meshDisplay = nullptr; + _display = nullptr; + _childArmature = nullptr; + _parent = nullptr; + _cachedFrameIndices = nullptr; +} + +DisplayData* Slot::_getDefaultRawDisplayData(unsigned displayIndex) const { + const auto defaultSkin = _armature->_armatureData->defaultSkin; + if (defaultSkin != nullptr) { + const auto defaultRawDisplayDatas = defaultSkin->getDisplays(_slotData->name); + if (defaultRawDisplayDatas != nullptr) { + return displayIndex < defaultRawDisplayDatas->size() ? (*defaultRawDisplayDatas)[displayIndex] : nullptr; + } + } + + return nullptr; +} + +void Slot::_updateDisplayData() { + const auto prevDisplayData = _displayData; + const auto prevVerticesData = _deformVertices != nullptr ? _deformVertices->verticesData : nullptr; + const auto prevTextureData = _textureData; + + DisplayData* rawDisplayData = nullptr; + VerticesData* currentVerticesData = nullptr; + + _displayData = nullptr; + _boundingBoxData = nullptr; + _textureData = nullptr; + + if (_displayIndex >= 0) { + if (_rawDisplayDatas != nullptr) { + rawDisplayData = (unsigned)_displayIndex < _rawDisplayDatas->size() ? (*_rawDisplayDatas)[_displayIndex] : nullptr; + } + + if (rawDisplayData == nullptr) { + rawDisplayData = _getDefaultRawDisplayData(_displayIndex); + } + + if ((unsigned)_displayIndex < _displayDatas.size()) { + _displayData = _displayDatas[_displayIndex]; + } + } + + // Update texture and mesh data. + if (_displayData != nullptr) { + if (_displayData->type == DisplayType::Mesh) { + currentVerticesData = &static_cast(_displayData)->vertices; + } else if (_displayData->type == DisplayType::Path) { + // TODO + } else if (rawDisplayData != nullptr) { + if (rawDisplayData->type == DisplayType::Mesh) { + currentVerticesData = &static_cast(rawDisplayData)->vertices; + } else if (rawDisplayData->type == DisplayType::Path) { + // TODO + } + } + + if (_displayData->type == DisplayType::BoundingBox) { + _boundingBoxData = static_cast(_displayData)->boundingBox; + } else if (rawDisplayData != nullptr) { + if (rawDisplayData->type == DisplayType::BoundingBox) { + _boundingBoxData = static_cast(rawDisplayData)->boundingBox; + } + } + + if (_displayData->type == DisplayType::Image) { + _textureData = static_cast(_displayData)->texture; + } else if (_displayData->type == DisplayType::Mesh) { + _textureData = static_cast(_displayData)->texture; + } + } + + // Update bounding box data. + if (_displayData != nullptr && _displayData->type == DisplayType::BoundingBox) { + _boundingBoxData = static_cast(_displayData)->boundingBox; + } else if (rawDisplayData != nullptr && rawDisplayData->type == DisplayType::BoundingBox) { + _boundingBoxData = static_cast(rawDisplayData)->boundingBox; + } else { + _boundingBoxData = nullptr; + } + + if (_displayData != prevDisplayData || currentVerticesData != prevVerticesData || _textureData != prevTextureData) { + if (currentVerticesData == nullptr && _textureData != nullptr) { + const auto imageDisplayData = static_cast(_displayData); + const auto scale = _textureData->parent->scale * _armature->_armatureData->scale; + const auto frame = _textureData->frame; + + _pivotX = imageDisplayData->pivot.x; + _pivotY = imageDisplayData->pivot.y; + + const auto& rect = frame != nullptr ? *frame : _textureData->region; + float width = rect.width; + float height = rect.height; + + if (_textureData->rotated && frame == nullptr) { + width = rect.height; + height = rect.width; + } + + _pivotX *= width * scale; + _pivotY *= height * scale; + + if (frame != nullptr) { + _pivotX += frame->x * scale; + _pivotY += frame->y * scale; + } + + // Update replace pivot. + if (_displayData != nullptr && rawDisplayData != nullptr && _displayData != rawDisplayData) { + rawDisplayData->transform.toMatrix(_helpMatrix); + _helpMatrix.invert(); + _helpMatrix.transformPoint(0.0f, 0.0f, _helpPoint); + _pivotX -= _helpPoint.x; + _pivotY -= _helpPoint.y; + + _displayData->transform.toMatrix(_helpMatrix); + _helpMatrix.invert(); + _helpMatrix.transformPoint(0.0f, 0.0f, _helpPoint); + _pivotX += _helpPoint.x; + _pivotY += _helpPoint.y; + } + + if (!DragonBones::yDown) { + _pivotY = (_textureData->rotated ? _textureData->region.width : _textureData->region.height) * scale - _pivotY; + } + } else { + _pivotX = 0.0f; + _pivotY = 0.0f; + } + + // Update original transform. + if (rawDisplayData != nullptr) { + origin = &rawDisplayData->transform; + } else if (_displayData != nullptr) { + origin = &_displayData->transform; + } else { + origin = nullptr; + } + + // Update vertices. + if (currentVerticesData != prevVerticesData) { + if (_deformVertices == nullptr) { + _deformVertices = BaseObject::borrowObject(); + } + + _deformVertices->init(currentVerticesData, _armature); + } else if (_deformVertices != nullptr && _textureData != prevTextureData) // Update mesh after update frame. + { + _deformVertices->verticesDirty = true; + } + + _displayDirty = true; + _transformDirty = true; + } +} + +void Slot::_updateDisplay() { + const auto prevDisplay = _display != nullptr ? _display : _rawDisplay; + const auto prevChildArmature = _childArmature; + + // Update display and child armature. + if (_displayIndex >= 0 && (std::size_t)_displayIndex < _displayList.size()) { + const auto& displayPair = _displayList[_displayIndex]; + _display = displayPair.first; + if (_display != nullptr && displayPair.second == DisplayType::Armature) { + _childArmature = static_cast(displayPair.first); + _display = _childArmature->getDisplay(); + } else { + _childArmature = nullptr; + } + } else { + _display = nullptr; + _childArmature = nullptr; + } + + const auto currentDisplay = _display != nullptr ? _display : _rawDisplay; + if (currentDisplay != prevDisplay) { + _onUpdateDisplay(); + _replaceDisplay(prevDisplay, prevChildArmature != nullptr); + + _transformDirty = true; + _visibleDirty = true; + _blendModeDirty = true; + _colorDirty = true; + } + + // Update frame. + if (currentDisplay == _rawDisplay || currentDisplay == _meshDisplay) { + _updateFrame(); + } + + // Update child armature. + if (_childArmature != prevChildArmature) { + if (prevChildArmature != nullptr) { + prevChildArmature->_parent = nullptr; // Update child armature parent. + prevChildArmature->setClock(nullptr); + if (prevChildArmature->inheritAnimation) { + prevChildArmature->getAnimation()->reset(); + } + } + + if (_childArmature != nullptr) { + _childArmature->_parent = this; // Update child armature parent. + _childArmature->setClock(_armature->getClock()); + if (_childArmature->inheritAnimation) // Set child armature cache frameRate. + { + if (_childArmature->getCacheFrameRate() == 0) { + const auto chacheFrameRate = this->_armature->getCacheFrameRate(); + if (chacheFrameRate != 0) { + _childArmature->setCacheFrameRate(chacheFrameRate); + } + } + + // Child armature action. + std::vector* actions = nullptr; + if (_displayData != nullptr && _displayData->type == DisplayType::Armature) { + actions = &(static_cast(_displayData)->actions); + } else if (_displayIndex >= 0 && _rawDisplayDatas != nullptr) { + auto rawDisplayData = (unsigned)_displayIndex < _rawDisplayDatas->size() ? (*_rawDisplayDatas)[_displayIndex] : nullptr; + + if (rawDisplayData == nullptr) { + rawDisplayData = _getDefaultRawDisplayData(_displayIndex); + } + + if (rawDisplayData != nullptr && rawDisplayData->type == DisplayType::Armature) { + actions = &(static_cast(rawDisplayData)->actions); + } + } + + if (actions != nullptr && !actions->empty()) { + for (const auto action : *actions) { + const auto eventObject = BaseObject::borrowObject(); + EventObject::actionDataToInstance(action, eventObject, _armature); + eventObject->slot = this; + _armature->_bufferAction(eventObject, false); + } + } else { + _childArmature->getAnimation()->play(); + } + } + } + } +} + +void Slot::_updateGlobalTransformMatrix(bool isCache) { + const auto& parentMatrix = _parent->globalTransformMatrix; + globalTransformMatrix = _localMatrix; // Copy. + globalTransformMatrix.concat(parentMatrix); + + if (isCache) { + global.fromMatrix(globalTransformMatrix); + } else { + _globalDirty = true; + } +} + +bool Slot::_setDisplayIndex(int value, bool isAnimation) { + if (isAnimation) { + if (_animationDisplayIndex == value) { + return false; + } + + _animationDisplayIndex = value; + } + + if (_displayIndex == value) { + return false; + } + + _displayIndex = value; + _displayDirty = true; + + _updateDisplayData(); + + return _displayDirty; +} + +bool Slot::_setZorder(int value) { + if (_zOrder == value) { + //return false; + } + + _zOrder = value; + _zOrderDirty = true; + + return _zOrderDirty; +} + +bool Slot::_setColor(const ColorTransform& value) { + _colorTransform = value; // copy + _colorDirty = true; + + return true; +} + +bool Slot::_setDisplayList(const std::vector>& value) { + if (!value.empty()) { + if (_displayList.size() != value.size()) { + _displayList.resize(value.size()); + } + + for (std::size_t i = 0, l = value.size(); i < l; ++i) { + const auto& eachPair = value[i]; + if ( + eachPair.first != nullptr && eachPair.first != _rawDisplay && eachPair.first != _meshDisplay && + eachPair.second != DisplayType::Armature && + std::find(_displayList.cbegin(), _displayList.cend(), eachPair) == _displayList.cend()) { + _initDisplay(eachPair.first, true); + } + + _displayList[i].first = eachPair.first; + _displayList[i].second = eachPair.second; + } + } else if (!_displayList.empty()) { + _displayList.clear(); + } + + if (_displayIndex >= 0 && (std::size_t)_displayIndex < _displayList.size()) { + _displayDirty = _display != _displayList[_displayIndex].first; + } else { + _displayDirty = _display != nullptr; + } + + _updateDisplayData(); + + return _displayDirty; +} + +void Slot::init(const SlotData* slotData, Armature* armatureValue, void* rawDisplay, void* meshDisplay) { + if (_slotData != nullptr) { + return; + } + + _slotData = slotData; + // + _visibleDirty = true; + _blendModeDirty = true; + _colorDirty = true; + _blendMode = _slotData->blendMode; + _zOrder = _slotData->zOrder; + _colorTransform = *(_slotData->color); + _rawDisplay = rawDisplay; + _meshDisplay = meshDisplay; + // + _armature = armatureValue; + // + const auto slotParent = _armature->getBone(_slotData->parent->name); + if (slotParent != nullptr) { + _parent = slotParent; + } else { + // Never; + } + + _armature->_addSlot(this); + // + _initDisplay(_rawDisplay, false); + if (_rawDisplay != _meshDisplay) { + _initDisplay(_meshDisplay, false); + } + + _onUpdateDisplay(); + _addDisplay(); +} + +void Slot::update(int cacheFrameIndex) { + if (_displayDirty) { + _displayDirty = false; + _updateDisplay(); + + // TODO remove slot offset. + if (_transformDirty) // Update local matrix. (Only updated when both display and transform are dirty.) + { + if (origin != nullptr) { + global = *origin; // Copy. + global.add(offset).toMatrix(_localMatrix); + } else { + global = offset; // Copy. + global.toMatrix(_localMatrix); + } + } + } + + if (_zOrderDirty) { + _zOrderDirty = false; + _updateZOrder(); + } + + if (cacheFrameIndex >= 0 && _cachedFrameIndices != nullptr) { + const auto cachedFrameIndex = (*_cachedFrameIndices)[cacheFrameIndex]; + if (cachedFrameIndex >= 0 && _cachedFrameIndex == cachedFrameIndex) // Same cache. + { + _transformDirty = false; + } else if (cachedFrameIndex >= 0) // Has been Cached. + { + _transformDirty = true; + _cachedFrameIndex = cachedFrameIndex; + } else if (_transformDirty || _parent->_childrenTransformDirty) // Dirty. + { + _transformDirty = true; + _cachedFrameIndex = -1; + } else if (_cachedFrameIndex >= 0) // Same cache, but not set index yet. + { + _transformDirty = false; + (*_cachedFrameIndices)[cacheFrameIndex] = _cachedFrameIndex; + } else // Dirty. + { + _transformDirty = true; + _cachedFrameIndex = -1; + } + } else if (_transformDirty || this->_parent->_childrenTransformDirty) { + cacheFrameIndex = -1; + _transformDirty = true; + _cachedFrameIndex = -1; + } + + if (_display == nullptr) { + return; + } + + if (_visibleDirty) { + _visibleDirty = false; + _updateVisible(); + } + + if (_blendModeDirty) { + _blendModeDirty = false; + _updateBlendMode(); + } + + if (_colorDirty) { + _colorDirty = false; + _updateColor(); + } + + if (_deformVertices != nullptr && _deformVertices->verticesData != nullptr && _display == _meshDisplay) { + const auto isSkinned = _deformVertices->verticesData->weight != nullptr; + + if ( + _deformVertices->verticesDirty || + (isSkinned && _deformVertices->isBonesUpdate())) { + _deformVertices->verticesDirty = false; + _updateMesh(); + } + + if (isSkinned) // Compatible. + { + return; + } + } + + if (_transformDirty) { + _transformDirty = false; + + if (_cachedFrameIndex < 0) { + const auto isCache = cacheFrameIndex >= 0; + _updateGlobalTransformMatrix(isCache); + + if (isCache && _cachedFrameIndices != nullptr) { + _cachedFrameIndex = (*_cachedFrameIndices)[cacheFrameIndex] = _armature->_armatureData->setCacheFrame(globalTransformMatrix, global); + } + } else { + _armature->_armatureData->getCacheFrame(globalTransformMatrix, global, _cachedFrameIndex); + } + + _updateTransform(); + } +} + +void Slot::updateTransformAndMatrix() { + if (_transformDirty) { + _transformDirty = false; + _updateGlobalTransformMatrix(false); + } +} + +void Slot::replaceDisplayData(DisplayData* displayData, int displayIndex) { + if (displayIndex < 0) { + if (_displayIndex < 0) { + displayIndex = 0; + } else { + displayIndex = _displayIndex; + } + } + + if (_displayDatas.size() <= (unsigned)displayIndex) { + _displayDatas.resize(displayIndex + 1, nullptr); + } + + _displayDatas[displayIndex] = displayData; +} + +bool Slot::containsPoint(float x, float y) { + if (_boundingBoxData == nullptr) { + return false; + } + + updateTransformAndMatrix(); + + _helpMatrix = globalTransformMatrix; // Copy. + _helpMatrix.invert(); + _helpMatrix.transformPoint(x, y, _helpPoint); + + return _boundingBoxData->containsPoint(_helpPoint.x, _helpPoint.y); +} + +int Slot::intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA, + Point* intersectionPointB, + Point* normalRadians) { + if (_boundingBoxData == nullptr) { + return 0; + } + + updateTransformAndMatrix(); + _helpMatrix = globalTransformMatrix; + _helpMatrix.invert(); + _helpMatrix.transformPoint(xA, yA, _helpPoint); + xA = _helpPoint.x; + yA = _helpPoint.y; + _helpMatrix.transformPoint(xB, yB, _helpPoint); + xB = _helpPoint.x; + yB = _helpPoint.y; + + const auto intersectionCount = _boundingBoxData->intersectsSegment(xA, yA, xB, yB, intersectionPointA, intersectionPointB, normalRadians); + if (intersectionCount > 0) { + if (intersectionCount == 1 || intersectionCount == 2) { + if (intersectionPointA != nullptr) { + globalTransformMatrix.transformPoint(intersectionPointA->x, intersectionPointA->y, *intersectionPointA); + if (intersectionPointB != nullptr) { + intersectionPointB->x = intersectionPointA->x; + intersectionPointB->y = intersectionPointA->y; + } + } else if (intersectionPointB != nullptr) { + globalTransformMatrix.transformPoint(intersectionPointB->x, intersectionPointB->y, *intersectionPointB); + } + } else { + if (intersectionPointA != nullptr) { + globalTransformMatrix.transformPoint(intersectionPointA->x, intersectionPointA->y, *intersectionPointA); + } + + if (intersectionPointB != nullptr) { + globalTransformMatrix.transformPoint(intersectionPointB->x, intersectionPointB->y, *intersectionPointB); + } + } + + if (normalRadians != nullptr) { + globalTransformMatrix.transformPoint(cos(normalRadians->x), sin(normalRadians->x), _helpPoint, true); + normalRadians->x = atan2(_helpPoint.y, _helpPoint.x); + + globalTransformMatrix.transformPoint(cos(normalRadians->y), sin(normalRadians->y), _helpPoint, true); + normalRadians->y = atan2(_helpPoint.y, _helpPoint.x); + } + } + + return intersectionCount; +} + +void Slot::setVisible(bool value) { + if (_visible == value) { + return; + } + + _visible = value; + _updateVisible(); +} + +void Slot::setDisplayIndex(int value) { + if (_setDisplayIndex(value)) { + update(-1); + } +} + +//TODO lsc check +void Slot::setDisplayList(const std::vector>& value) { + const auto backupDisplayList = _displayList; // copy + auto disposeDisplayList = backupDisplayList; // copy + disposeDisplayList.clear(); + + if (_setDisplayList(value)) { + update(-1); + } + + for (const auto& pair : backupDisplayList) { + if ( + pair.first != nullptr && pair.first != _rawDisplay && pair.first != _meshDisplay && + std::find(_displayList.cbegin(), _displayList.cend(), pair) == _displayList.cend() && + std::find(disposeDisplayList.cbegin(), disposeDisplayList.cend(), pair) == disposeDisplayList.cend()) { + disposeDisplayList.push_back(pair); + } + } + + for (const auto& pair : disposeDisplayList) { + if (pair.second == DisplayType::Armature) { + static_cast(pair.first)->returnToPool(); + } else { + _disposeDisplay(pair.first, true); + } + } +} + +void Slot::setRawDisplayDatas(const std::vector* value) { + if (_rawDisplayDatas == value) { + return; + } + + _displayDirty = true; + _rawDisplayDatas = value; + + if (_rawDisplayDatas != nullptr) { + _displayDatas.resize(_rawDisplayDatas->size()); + + for (std::size_t i = 0, l = _displayDatas.size(); i < l; ++i) { + auto rawDisplayData = (*_rawDisplayDatas)[i]; + + if (rawDisplayData == nullptr) { + rawDisplayData = _getDefaultRawDisplayData(i); + } + + _displayDatas[i] = rawDisplayData; + } + } else { + _displayDatas.clear(); + } +} + +void Slot::setDisplay(void* value, DisplayType displayType) { + if (_display == value) { + return; + } + + const auto displayListLength = _displayList.size(); + if (_displayIndex < 0 && displayListLength == 0) // Emprty + { + _displayIndex = 0; + } + + if (_displayIndex < 0) { + return; + } else { + auto relpaceDisplayList = _displayList; // copy + if (displayListLength <= (std::size_t)_displayIndex) { + relpaceDisplayList.resize(_displayIndex + 1); + } + + relpaceDisplayList[_displayIndex].first = value; + relpaceDisplayList[_displayIndex].second = displayType; + + setDisplayList(relpaceDisplayList); + } +} + +void Slot::setChildArmature(Armature* value) { + if (_childArmature == value) { + return; + } + + setDisplay(value, DisplayType::Armature); +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/armature/Slot.h b/cocos/editor-support/dragonbones/armature/Slot.h new file mode 100644 index 0000000..e2d00a9 --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/Slot.h @@ -0,0 +1,490 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_SLOT_H +#define DRAGONBONES_SLOT_H + +#include "TransformObject.h" + +#include "../geom/ColorTransform.h" +#include "../model/ArmatureData.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The slot attached to the armature, controls the display status and properties of the display object. + * A bone can contain multiple slots. + * A slot can contain multiple display objects, displaying only one of the display objects at a time, + * but you can toggle the display object into frame animation while the animation is playing. + * The display object can be a normal texture, or it can be a display of a child armature, a grid display object, + * and a custom other display object. + * @see dragonBones.Armature + * @see dragonBones.Bone + * @see dragonBones.SlotData + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 插槽附着在骨骼上,控制显示对象的显示状态和属性。 + * 一个骨骼上可以包含多个插槽。 + * 一个插槽中可以包含多个显示对象,同一时间只能显示其中的一个显示对象,但可以在动画播放的过程中切换显示对象实现帧动画。 + * 显示对象可以是普通的图片纹理,也可以是子骨架的显示容器,网格显示对象,还可以是自定义的其他显示对象。 + * @see dragonBones.Armature + * @see dragonBones.Bone + * @see dragonBones.SlotData + * @version DragonBones 3.0 + * @language zh_CN + */ +class Slot : public TransformObject { +public: + /** + * - Displays the animated state or mixed group name controlled by the object, set to null to be controlled by all animation states. + * @default null + * @see dragonBones.AnimationState#displayControl + * @see dragonBones.AnimationState#name + * @see dragonBones.AnimationState#group + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 显示对象受到控制的动画状态或混合组名称,设置为 null 则表示受所有的动画状态控制。 + * @default null + * @see dragonBones.AnimationState#displayControl + * @see dragonBones.AnimationState#name + * @see dragonBones.AnimationState#group + * @version DragonBones 4.5 + * @language zh_CN + */ + std::string displayController; + +public: + /** + * @internal + */ + bool _colorDirty; + BlendMode _blendMode; + /** + * @internal + */ + int _zOrder; + /** + * @internal + */ + float _pivotX; + /** + * @internal + */ + float _pivotY; + + /** + * @internal + */ + ColorTransform _colorTransform; + /** + * @internal + */ + const SlotData* _slotData; + /** + * @internal + */ + DisplayData* _displayData; + /** + * @internal + */ + DeformVertices* _deformVertices; + void* _rawDisplay; + void* _meshDisplay; + /** + * @internal + */ + std::vector* _cachedFrameIndices; + +protected: + bool _displayDirty; + bool _zOrderDirty; + bool _visibleDirty; + bool _blendModeDirty; + bool _transformDirty; + bool _visible; + int _displayIndex; + int _animationDisplayIndex; + int _cachedFrameIndex; + Matrix _localMatrix; + std::vector _displayDatas; + std::vector> _displayList; + const std::vector* _rawDisplayDatas; + BoundingBoxData* _boundingBoxData; + TextureData* _textureData; + void* _display; + Armature* _childArmature; + /** + * @private + */ + Bone* _parent; + +public: + Slot() : _deformVertices(nullptr), + _rawDisplay(nullptr), + _meshDisplay(nullptr) {} + virtual ~Slot(){}; + +protected: + virtual void _onClear() override; + + virtual void _initDisplay(void* value, bool isRetain) = 0; + virtual void _disposeDisplay(void* value, bool isRelease) = 0; + virtual void _onUpdateDisplay() = 0; + virtual void _addDisplay() = 0; + virtual void _replaceDisplay(void* value, bool isArmatureDisplay) = 0; + virtual void _removeDisplay() = 0; + virtual void _updateZOrder() = 0; + virtual void _updateFrame() = 0; + virtual void _updateMesh() = 0; + virtual void _updateTransform() = 0; + virtual void _identityTransform() = 0; + /** + * - Support default skin data. + */ + DisplayData* _getDefaultRawDisplayData(unsigned displayIndex) const; + void _updateDisplay(); + void _updateDisplayData(); + void _updateGlobalTransformMatrix(bool isCache); + +public: + /** + * @internal + */ + virtual void _updateVisible() = 0; + virtual void _updateBlendMode() = 0; + virtual void _updateColor() = 0; + +public: + /** + * @internal + */ + bool _setDisplayIndex(int value, bool isAnimation = false); + /** + * @internal + */ + bool _setZorder(int value); + /** + * @internal + */ + bool _setColor(const ColorTransform& value); + /** + * @internal + */ + bool _setDisplayList(const std::vector>& value); + +public: + /** + * @internal + */ + void init(const SlotData* slotData, Armature* armatureValue, void* rawDisplay, void* meshDisplay); + /** + * @internal + */ + void update(int cacheFrameIndex); + /** + * @private + */ + void updateTransformAndMatrix(); + /** + * @private + */ + void replaceDisplayData(DisplayData* displayData, int displayIndex); + /** + * - Check whether a specific point is inside a custom bounding box in the slot. + * The coordinate system of the point is the inner coordinate system of the armature. + * Custom bounding boxes need to be customized in Dragonbones Pro. + * @param x - The horizontal coordinate of the point. + * @param y - The vertical coordinate of the point. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 检查特定点是否在插槽的自定义边界框内。 + * 点的坐标系为骨架内坐标系。 + * 自定义边界框需要在 DragonBones Pro 中自定义。 + * @param x - 点的水平坐标。 + * @param y - 点的垂直坐标。 + * @version DragonBones 5.0 + * @language zh_CN + */ + bool containsPoint(float x, float y); + /** + * - Check whether a specific segment intersects a custom bounding box for the slot. + * The coordinate system of the segment and intersection is the inner coordinate system of the armature. + * Custom bounding boxes need to be customized in Dragonbones Pro. + * @param xA - The horizontal coordinate of the beginning of the segment. + * @param yA - The vertical coordinate of the beginning of the segment. + * @param xB - The horizontal coordinate of the end point of the segment. + * @param yB - The vertical coordinate of the end point of the segment. + * @param intersectionPointA - The first intersection at which a line segment intersects the bounding box from the beginning to the end. (If not set, the intersection point will not calculated) + * @param intersectionPointB - The first intersection at which a line segment intersects the bounding box from the end to the beginning. (If not set, the intersection point will not calculated) + * @param normalRadians - The normal radians of the tangent of the intersection boundary box. [x: Normal radian of the first intersection tangent, y: Normal radian of the second intersection tangent] (If not set, the normal will not calculated) + * @returns Intersection situation. [1: Disjoint and segments within the bounding box, 0: Disjoint, 1: Intersecting and having a nodal point and ending in the bounding box, 2: Intersecting and having a nodal point and starting at the bounding box, 3: Intersecting and having two intersections, N: Intersecting and having N intersections] + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 检查特定线段是否与插槽的自定义边界框相交。 + * 线段和交点的坐标系均为骨架内坐标系。 + * 自定义边界框需要在 DragonBones Pro 中自定义。 + * @param xA - 线段起点的水平坐标。 + * @param yA - 线段起点的垂直坐标。 + * @param xB - 线段终点的水平坐标。 + * @param yB - 线段终点的垂直坐标。 + * @param intersectionPointA - 线段从起点到终点与边界框相交的第一个交点。 (如果未设置,则不计算交点) + * @param intersectionPointB - 线段从终点到起点与边界框相交的第一个交点。 (如果未设置,则不计算交点) + * @param normalRadians - 交点边界框切线的法线弧度。 [x: 第一个交点切线的法线弧度, y: 第二个交点切线的法线弧度] (如果未设置,则不计算法线) + * @returns 相交的情况。 [-1: 不相交且线段在包围盒内, 0: 不相交, 1: 相交且有一个交点且终点在包围盒内, 2: 相交且有一个交点且起点在包围盒内, 3: 相交且有两个交点, N: 相交且有 N 个交点] + * @version DragonBones 5.0 + * @language zh_CN + */ + int intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA = nullptr, + Point* intersectionPointB = nullptr, + Point* normalRadians = nullptr); + /** + * - Forces the slot to update the state of the display object in the next frame. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 强制插槽在下一帧更新显示对象的状态。 + * @version DragonBones 4.5 + * @language zh_CN + */ + inline void invalidUpdate() { + _displayDirty = true; + _transformDirty = true; + } + /** + * - The visible of slot's display object. + * @default true + * @version DragonBones 5.6 + * @language en_US + */ + /** + * - 插槽的显示对象的可见。 + * @default true + * @version DragonBones 5.6 + * @language zh_CN + */ + inline bool getVisible() const { + return _visible; + } + void setVisible(bool value); + /** + * - The index of the display object displayed in the display list. + * @example + * TypeScript style, for reference only. + *
+     *     let slot = armature.getSlot("weapon");
+     *     slot.displayIndex = 3;
+     *     slot.displayController = "none";
+     * 
+ * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 此时显示的显示对象在显示列表中的索引。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     let slot = armature.getSlot("weapon");
+     *     slot.displayIndex = 3;
+     *     slot.displayController = "none";
+     * 
+ * @version DragonBones 4.5 + * @language zh_CN + */ + inline int getDisplayIndex() const { + return _displayIndex; + } + void setDisplayIndex(int value); + + /** + * - The slot name. + * @see dragonBones.SlotData#name + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 插槽名称。 + * @see dragonBones.SlotData#name + * @version DragonBones 3.0 + * @language zh_CN + */ + inline const std::string& getName() const { + return _slotData->name; + } + /** + * - Contains a display list of display objects or child armatures. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 包含显示对象或子骨架的显示列表。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline std::vector> getDisplayList() const { + return _displayList; + } + void setDisplayList(const std::vector>& value); + /** + * @private + */ + inline const std::vector* getRawDisplayDatas() const { + return _rawDisplayDatas; + } + void setRawDisplayDatas(const std::vector* value); + /** + * - The slot data. + * @see dragonBones.SlotData + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 插槽数据。 + * @see dragonBones.SlotData + * @version DragonBones 4.5 + * @language zh_CN + */ + const SlotData* getSlotData() const { + return _slotData; + } + /** + * - The custom bounding box data for the slot at current time. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 插槽此时的自定义包围盒数据。 + * @version DragonBones 5.0 + * @language zh_CN + */ + inline BoundingBoxData* getBoundingBoxData() const { + return _boundingBoxData; + } + /** + * @private + */ + inline void* getRawDisplay() const { + return _rawDisplay; + } + /** + * @private + */ + inline void* getMeshDisplay() const { + return _meshDisplay; + } + /** + * - The display object that the slot displays at this time. + * @example + * TypeScript style, for reference only. + *
+     *     let slot = armature.getSlot("text");
+     *     slot.display = new yourEngine.TextField();
+     * 
+ * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 插槽此时显示的显示对象。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     let slot = armature.getSlot("text");
+     *     slot.display = new yourEngine.TextField();
+     * 
+ * @version DragonBones 3.0 + * @language zh_CN + */ + inline void* getDisplay() const { + return _display; + } + /** + * - Deprecated, please refer to {@link #display}. + * @deprecated + * @language en_US + */ + /** + * - 已废弃,请参考 {@link #display}。 + * @deprecated + * @language zh_CN + */ + void setDisplay(void* value, DisplayType displayType); + /** + * - The child armature that the slot displayed at current time. + * @example + * TypeScript style, for reference only. + *
+     *     let slot = armature.getSlot("weapon");
+     * let prevChildArmature = slot.childArmature;
+     * if (prevChildArmature) {
+     * prevChildArmature.dispose();
+     *     }
+     *     slot.childArmature = factory.buildArmature("weapon_blabla", "weapon_blabla_project");
+     * 
+ * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 插槽此时显示的子骨架。 + * 注意,被替换的对象或子骨架并不会被回收,根据语言和引擎的不同,需要额外处理。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     let slot = armature.getSlot("weapon");
+     * let prevChildArmature = slot.childArmature;
+     * if (prevChildArmature) {
+     * prevChildArmature.dispose();
+     *     }
+     *     slot.childArmature = factory.buildArmature("weapon_blabla", "weapon_blabla_project");
+     * 
+ * @version DragonBones 3.0 + * @language zh_CN + */ + inline Armature* getChildArmature() const { + return _childArmature; + } + void setChildArmature(Armature* value); + /** + * - The parent bone to which it belongs. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 所属的父骨骼。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline Bone* getParent() const { + return _parent; + } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_SLOT_H diff --git a/cocos/editor-support/dragonbones/armature/TransformObject.cpp b/cocos/editor-support/dragonbones/armature/TransformObject.cpp new file mode 100644 index 0000000..b473785 --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/TransformObject.cpp @@ -0,0 +1,31 @@ +// +// Created by liangshuochen on 12/06/2017. +// + +#include "TransformObject.h" + +DRAGONBONES_NAMESPACE_BEGIN + +Matrix TransformObject::_helpMatrix; +Transform TransformObject::_helpTransform; +Point TransformObject::_helpPoint; + +void TransformObject::_onClear() { + globalTransformMatrix.identity(); + global.identity(); + offset.identity(); + origin = nullptr; + userData = nullptr; + + _globalDirty = false; + _armature = nullptr; +} + +void TransformObject::updateGlobalTransform() { + if (_globalDirty) { + _globalDirty = false; + global.fromMatrix(globalTransformMatrix); + } +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/armature/TransformObject.h b/cocos/editor-support/dragonbones/armature/TransformObject.h new file mode 100644 index 0000000..b926651 --- /dev/null +++ b/cocos/editor-support/dragonbones/armature/TransformObject.h @@ -0,0 +1,159 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_TRANSFORM_OBJECT_H +#define DRAGONBONES_TRANSFORM_OBJECT_H + +#include "../core/BaseObject.h" +#include "../geom/Matrix.h" +#include "../geom/Transform.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The base class of the transform object. + * @see dragonBones.Transform + * @version DragonBones 4.5 + * @language en_US + */ +/** + * - 变换对象的基类。 + * @see dragonBones.Transform + * @version DragonBones 4.5 + * @language zh_CN + */ +class TransformObject : public BaseObject { + ABSTRACT_CLASS(TransformObject); + +protected: + static Matrix _helpMatrix; + static Transform _helpTransform; + static Point _helpPoint; + +public: + /** + * - A matrix relative to the armature coordinate system. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 相对于骨架坐标系的矩阵。 + * @version DragonBones 3.0 + * @language zh_CN + */ + Matrix globalTransformMatrix; + /** + * - A transform relative to the armature coordinate system. + * @see #updateGlobalTransform() + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 相对于骨架坐标系的变换。 + * @see #updateGlobalTransform() + * @version DragonBones 3.0 + * @language zh_CN + */ + Transform global; + /** + * - The offset transform relative to the armature or the parent bone coordinate system. + * @see #dragonBones.Bone#invalidUpdate() + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 相对于骨架或父骨骼坐标系的偏移变换。 + * @see #dragonBones.Bone#invalidUpdate() + * @version DragonBones 3.0 + * @language zh_CN + */ + Transform offset; + /** + * @private + */ + const Transform* origin; + /** + * @private + */ + void* userData; + +public: + /** + * @internal + */ + Armature* _armature; + +protected: + bool _globalDirty; + +protected: + virtual void _onClear() override; + +public: + /** + * - For performance considerations, rotation or scale in the {@link #global} attribute of the bone or slot is not always properly accessible, + * some engines do not rely on these attributes to update rendering, such as Egret. + * The use of this method ensures that the access to the {@link #global} property is correctly rotation or scale. + * @example + * TypeScript style, for reference only. + *
+     *     bone.updateGlobalTransform();
+     *     let rotation = bone.global.rotation;
+     * 
+ * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 出于性能的考虑,骨骼或插槽的 {@link #global} 属性中的旋转或缩放并不总是正确可访问的,有些引擎并不依赖这些属性更新渲染,比如 Egret。 + * 使用此方法可以保证访问到 {@link #global} 属性中正确的旋转或缩放。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     bone.updateGlobalTransform();
+     *     let rotation = bone.global.rotation;
+     * 
+ * @version DragonBones 3.0 + * @language zh_CN + */ + void updateGlobalTransform(); + /** + * - The armature to which it belongs. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 所属的骨架。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline Armature* getArmature() const { + return _armature; + } + +public: // For WebAssembly. + Matrix* getGlobalTransformMatrix() { return &globalTransformMatrix; } + Transform* getGlobal() { return &global; } + Transform* getOffset() { return &offset; } + const Transform* getOrigin() const { return origin; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_TRANSFORM_OBJECT_H diff --git a/cocos/editor-support/dragonbones/core/BaseObject.cpp b/cocos/editor-support/dragonbones/core/BaseObject.cpp new file mode 100644 index 0000000..83a33d1 --- /dev/null +++ b/cocos/editor-support/dragonbones/core/BaseObject.cpp @@ -0,0 +1,124 @@ +#include "BaseObject.h" +DRAGONBONES_NAMESPACE_BEGIN + +std::vector BaseObject::__allDragonBonesObjects; +unsigned BaseObject::_hashCode = 0; +unsigned BaseObject::_defaultMaxCount = 3000; +std::map BaseObject::_maxCountMap; +std::map> BaseObject::_poolsMap; +BaseObject::RecycleOrDestroyCallback BaseObject::_recycleOrDestroyCallback = nullptr; + +void BaseObject::_returnObject(BaseObject* object) { + const auto classType = object->getClassTypeIndex(); + const auto maxCountIterator = _maxCountMap.find(classType); + const auto maxCount = maxCountIterator != _maxCountMap.end() ? maxCountIterator->second : _defaultMaxCount; + auto& pool = _poolsMap[classType]; + // If script engine gc,then alway push object into pool,not immediately delete + // Because object will be referenced more then one place possibly,if delete it immediately,will + // crash. + if (!DragonBones::checkInPool || pool.size() < maxCount) { + if (!object->_isInPool) { + object->_isInPool = true; + pool.push_back(object); + if (_recycleOrDestroyCallback != nullptr) + _recycleOrDestroyCallback(object, 0); + } else { + // If script engine gc,repeat push into pool will happen. + if (DragonBones::checkInPool) { + DRAGONBONES_ASSERT(false, "The object is already in the pool."); + } + } + } else { + delete object; + } +} + +void BaseObject::setObjectRecycleOrDestroyCallback(const std::function& cb) { + _recycleOrDestroyCallback = cb; +} + +void BaseObject::setMaxCount(std::size_t classType, unsigned maxCount) { + if (classType > 0) { + const auto iterator = _poolsMap.find(classType); + if (iterator != _poolsMap.end()) { + auto& pool = iterator->second; + if (pool.size() > (size_t)maxCount) { + for (auto i = (size_t)maxCount, l = pool.size(); i < l; ++i) { + delete pool[i]; + } + + pool.resize(maxCount); + } + } + + _maxCountMap[classType] = maxCount; + } else { + _defaultMaxCount = maxCount; + for (auto& pair : _poolsMap) { + auto& pool = pair.second; + if (pool.size() > (size_t)maxCount) { + for (auto i = (size_t)maxCount, l = pool.size(); i < l; ++i) { + delete pool[i]; + } + + pool.resize(maxCount); + } + + if (_maxCountMap.find(pair.first) != _maxCountMap.end()) { + _maxCountMap[pair.first] = maxCount; + } + } + } +} +void BaseObject::clearPool(std::size_t classType) { + if (classType > 0) { + const auto iterator = _poolsMap.find(classType); + if (iterator != _poolsMap.end()) { + auto& pool = iterator->second; + if (!pool.empty()) { + for (auto object : pool) { + delete object; + } + + pool.clear(); + } + } + } else { + for (auto& pair : _poolsMap) { + auto& pool = pair.second; + if (!pool.empty()) { + for (auto object : pool) { + delete object; + } + + pool.clear(); + } + } + } +} + +BaseObject::BaseObject() +: hashCode(BaseObject::_hashCode++), _isInPool(false) { + __allDragonBonesObjects.push_back(this); +} + +BaseObject::~BaseObject() { + if (_recycleOrDestroyCallback != nullptr) + _recycleOrDestroyCallback(this, 1); + + auto iter = std::find(__allDragonBonesObjects.begin(), __allDragonBonesObjects.end(), this); + if (iter != __allDragonBonesObjects.end()) { + __allDragonBonesObjects.erase(iter); + } +} + +void BaseObject::returnToPool() { + _onClear(); + BaseObject::_returnObject(this); +} + +std::vector& BaseObject::getAllObjects() { + return __allDragonBonesObjects; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/core/BaseObject.h b/cocos/editor-support/dragonbones/core/BaseObject.h new file mode 100644 index 0000000..fcf0168 --- /dev/null +++ b/cocos/editor-support/dragonbones/core/BaseObject.h @@ -0,0 +1,160 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_BASE_OBJECT_H +#define DRAGONBONES_BASE_OBJECT_H + +#include +#include "DragonBones.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The BaseObject is the base class for all objects in the DragonBones framework. + * All BaseObject instances are cached to the object pool to reduce the performance consumption of frequent requests for memory or memory recovery. + * @version DragonBones 4.5 + * @language en_US + */ +/** + * - 基础对象,通常 DragonBones 的对象都继承自该类。 + * 所有基础对象的实例都会缓存到对象池,以减少频繁申请内存或内存回收的性能消耗。 + * @version DragonBones 4.5 + * @language zh_CN + */ +class BaseObject { +public: + typedef std::function RecycleOrDestroyCallback; + +private: + static unsigned _hashCode; + static unsigned _defaultMaxCount; + static std::map _maxCountMap; + static std::map> _poolsMap; + static void _returnObject(BaseObject* object); + + static RecycleOrDestroyCallback _recycleOrDestroyCallback; + +public: + static void setObjectRecycleOrDestroyCallback(const RecycleOrDestroyCallback& cb); + /** + * - Set the maximum cache count of the specify object pool. + * @param objectConstructor - The specify class. (Set all object pools max cache count if not set) + * @param maxCount - Max count. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 设置特定对象池的最大缓存数量。 + * @param objectConstructor - 特定的类。 (不设置则设置所有对象池的最大缓存数量) + * @param maxCount - 最大缓存数量。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static void setMaxCount(std::size_t classTypeIndex, unsigned maxCount); + /** + * - Clear the cached instances of a specify object pool. + * @param objectConstructor - Specify class. (Clear all cached instances if not set) + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 清除特定对象池的缓存实例。 + * @param objectConstructor - 特定的类。 (不设置则清除所有缓存的实例) + * @version DragonBones 4.5 + * @language zh_CN + */ + static void clearPool(std::size_t classTypeIndex = 0); + template + /** + * - Get an instance of the specify class from object pool. + * @param objectConstructor - The specify class. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 从对象池中获取特定类的实例。 + * @param objectConstructor - 特定的类。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static T* borrowObject() { + const auto classTypeIndex = T::getTypeIndex(); + const auto iterator = _poolsMap.find(classTypeIndex); + if (iterator != _poolsMap.end()) { + auto& pool = iterator->second; + if (!pool.empty()) { + const auto object = static_cast(pool.back()); + pool.pop_back(); + object->_isInPool = false; + return object; + } + } + + const auto object = new (std::nothrow) T(); + + return object; + } + + static std::vector& getAllObjects(); + +public: + /** + * - A unique identification number assigned to the object. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 分配给此实例的唯一标识号。 + * @version DragonBones 4.5 + * @language zh_CN + */ + const unsigned hashCode; + +private: + static std::vector __allDragonBonesObjects; + bool _isInPool; + +public: + virtual ~BaseObject(); + +protected: + BaseObject(); + + virtual void _onClear() = 0; + +public: + virtual std::size_t getClassTypeIndex() const = 0; + /** + * - Clear the object and return it back to object pool。 + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 清除该实例的所有数据并将其返还对象池。 + * @version DragonBones 4.5 + * @language zh_CN + */ + void returnToPool(); + inline bool isInPool() const { return _isInPool; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_BASE_OBJECT_H diff --git a/cocos/editor-support/dragonbones/core/DragonBones.cpp b/cocos/editor-support/dragonbones/core/DragonBones.cpp new file mode 100644 index 0000000..27e4ca2 --- /dev/null +++ b/cocos/editor-support/dragonbones/core/DragonBones.cpp @@ -0,0 +1,84 @@ +#include "DragonBones.h" +#include "../animation/Animation.h" +#include "../animation/WorldClock.h" +#include "../armature/Armature.h" +#include "../event/EventObject.h" +#include "../event/IEventDispatcher.h" + +DRAGONBONES_NAMESPACE_BEGIN + +const std::string DragonBones::VEISION = "5.6.300"; + +bool DragonBones::yDown = false; +bool DragonBones::debug = false; +bool DragonBones::debugDraw = false; +bool DragonBones::webAssembly = false; +bool DragonBones::checkInPool = true; + +DragonBones::DragonBones(IEventDispatcher* eventManager) : _events(), + _clock(nullptr), + _eventManager(eventManager) { + _clock = new WorldClock(); + _eventManager = eventManager; +} + +DragonBones::~DragonBones() { + if (_clock != nullptr) { + delete _clock; + } + + _clock = nullptr; + delete _eventManager; + _eventManager = nullptr; +} + +void DragonBones::advanceTime(float passedTime) { + if (!_objectsMap.empty()) { + for (auto it = _objectsMap.begin(); it != _objectsMap.end(); it++) { + auto object = it->first; + if (object) { + object->returnToPool(); + } + } + _objectsMap.clear(); + } + + if (!_events.empty()) { + for (std::size_t i = 0; i < _events.size(); ++i) { + const auto eventObject = _events[i]; + const auto armature = eventObject->armature; + if (armature->_armatureData != nullptr) { + armature->getProxy()->dispatchDBEvent(eventObject->type, eventObject); + if (eventObject->type == EventObject::SOUND_EVENT) { + _eventManager->dispatchDBEvent(eventObject->type, eventObject); + } + } + + bufferObject(eventObject); + } + + _events.clear(); + } + + _clock->advanceTime(passedTime); +} + +void DragonBones::render() { + _clock->render(); +} + +void DragonBones::bufferEvent(EventObject* value) { + _events.push_back(value); +} + +void DragonBones::bufferObject(BaseObject* object) { + if (object == nullptr || object->isInPool()) return; + // Just mark object will be put in pool next frame, 'true' is useless. + _objectsMap[object] = true; +} + +WorldClock* DragonBones::getClock() { + return _clock; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/core/DragonBones.h b/cocos/editor-support/dragonbones/core/DragonBones.h new file mode 100644 index 0000000..342a3a1 --- /dev/null +++ b/cocos/editor-support/dragonbones/core/DragonBones.h @@ -0,0 +1,470 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_H +#define DRAGONBONES_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// dragonBones assert +#define DRAGONBONES_ASSERT(cond, msg) \ + do { \ + assert(cond); \ + } while (0) + +// namespace dragonBones {} +#define DRAGONBONES_NAMESPACE_BEGIN namespace dragonBones { +#define DRAGONBONES_NAMESPACE_END } + +// using dragonBones namespace +#define DRAGONBONES_USING_NAME_SPACE using namespace dragonBones + +#define ABSTRACT_CLASS(CLASS) \ +public: \ + CLASS() {} \ + virtual ~CLASS(){}; + +#define BIND_CLASS_TYPE(CLASS) \ +public: \ + static std::size_t getTypeIndex() { \ + static const auto typeIndex = typeid(CLASS).hash_code(); \ + return typeIndex; \ + } \ + virtual std::size_t getClassTypeIndex() const override { \ + return CLASS::getTypeIndex(); \ + } + +#define BIND_CLASS_TYPE_A(CLASS) \ +public: \ + static std::size_t getTypeIndex() { \ + static const auto typeIndex = typeid(CLASS).hash_code(); \ + return typeIndex; \ + } \ + virtual std::size_t getClassTypeIndex() const override { \ + return CLASS::getTypeIndex(); \ + } \ + \ +public: \ + CLASS() { _onClear(); } \ + ~CLASS() { _onClear(); } \ + \ +private: \ + CLASS(const CLASS&); \ + void operator=(const CLASS&) + +#define BIND_CLASS_TYPE_B(CLASS) \ +public: \ + static std::size_t getTypeIndex() { \ + static const auto typeIndex = typeid(CLASS).hash_code(); \ + return typeIndex; \ + } \ + virtual std::size_t getClassTypeIndex() const override { \ + return CLASS::getTypeIndex(); \ + } \ + \ +private: \ + CLASS(const CLASS&); \ + void operator=(const CLASS&) + +#define DRAGONBONES_DISALLOW_COPY_AND_ASSIGN(CLASS) \ +private: \ + CLASS(const CLASS&); \ + void operator=(const CLASS&); + +DRAGONBONES_NAMESPACE_BEGIN + +/** + * @internal + */ +enum class BinaryOffset { + WeigthBoneCount = 0, + WeigthFloatOffset = 1, + WeigthBoneIndices = 2, + + MeshVertexCount = 0, + MeshTriangleCount = 1, + MeshFloatOffset = 2, + MeshWeightOffset = 3, + MeshVertexIndices = 4, + + TimelineScale = 0, + TimelineOffset = 1, + TimelineKeyFrameCount = 2, + TimelineFrameValueCount = 3, + TimelineFrameValueOffset = 4, + TimelineFrameOffset = 5, + + FramePosition = 0, + FrameTweenType = 1, + FrameTweenEasingOrCurveSampleCount = 2, + FrameCurveSamples = 3, + + DeformVertexOffset = 0, + DeformCount = 1, + DeformValueCount = 2, + DeformValueOffset = 3, + DeformFloatOffset = 4 +}; + +/** + * @internal + */ +enum class ArmatureType { + Armature = 0, + MovieClip = 1, + Stage = 2 +}; + +/** + * - Offset mode. + * @version DragonBones 5.5 + * @language en_US + */ +/** + * - 偏移模式。 + * @version DragonBones 5.5 + * @language zh_CN + */ +enum class OffsetMode { + None, + Additive, + Override +}; + +/** + * @private + */ +enum class DisplayType { + Image = 0, + Armature = 1, + Mesh = 2, + BoundingBox = 3, + Path = 4 +}; + +/** + * - Bounding box type. + * @version DragonBones 5.0 + * @language en_US + */ +/** + * - 边界框类型。 + * @version DragonBones 5.0 + * @language zh_CN + */ +enum class BoundingBoxType { + Rectangle = 0, + Ellipse = 1, + Polygon = 2 +}; + +/** + * @internal + */ +enum class ActionType { + Play = 0, + Frame = 10, + Sound = 11 +}; + +/** + * @internal + */ +enum class BlendMode { + Normal = 0, + Add = 1, + Alpha = 2, + Darken = 3, + Difference = 4, + Erase = 5, + HardLight = 6, + Invert = 7, + Layer = 8, + Lighten = 9, + Multiply = 10, + Overlay = 11, + Screen = 12, + Subtract = 13 +}; + +/** + * @internal + */ +enum class TweenType { + None = 0, + Line = 1, + Curve = 2, + QuadIn = 3, + QuadOut = 4, + QuadInOut = 5 +}; + +/** + * @internal + */ +enum class TimelineType { + Action = 0, + ZOrder = 1, + + BoneAll = 10, + + BoneTranslate = 11, + BoneRotate = 12, + BoneScale = 13, + + SlotDisplay = 20, + SlotColor = 21, + SlotDeform = 22, + + IKConstraint = 30, + + AnimationTime = 40, + AnimationWeight = 41 +}; + +/** + * - Animation fade out mode. + * @version DragonBones 4.5 + * @language en_US + */ +/** + * - 动画淡出模式。 + * @version DragonBones 4.5 + * @language zh_CN + */ +enum class AnimationFadeOutMode { + /** + * - Do not fade out of any animation states. + * @language en_US + */ + /** + * - 不淡出任何的动画状态。 + * @language zh_CN + */ + None, + /** + * - Fade out the animation states of the same layer. + * @language en_US + */ + /** + * - 淡出同层的动画状态。 + * @language zh_CN + */ + SameLayer, + /** + * - Fade out the animation states of the same group. + * @language en_US + */ + /** + * - 淡出同组的动画状态。 + * @language zh_CN + */ + SameGroup, + /** + * - Fade out the animation states of the same layer and group. + * @language en_US + */ + /** + * - 淡出同层并且同组的动画状态。 + * @language zh_CN + */ + SameLayerAndGroup, + /** + * - Fade out of all animation states. + * @language en_US + */ + /** + * - 淡出所有的动画状态。 + * @language zh_CN + */ + All, + /** + * - Does not replace the animation state with the same name. + * @language en_US + */ + /** + * - 不替换同名的动画状态。 + * @language zh_CN + */ + Single +}; + +enum class TextureFormat { + DEFAULT, + RGBA8888, + BGRA8888, + RGBA4444, + RGB888, + RGB565, + RGBA5551 +}; + +template +std::string to_string(const T& value) { + std::ostringstream stream; + stream << value; + return stream.str(); +} + +template +inline int indexOf(const std::vector& vector, const T& value) { + for (std::size_t i = 0, l = vector.size(); i < l; ++i) { + if (vector[i] == value) { + return (int)i; + } + } + + return -1; +} + +template +inline T* mapFind(const std::map& map, const std::string& key) { + auto iterator = map.find(key); + return (iterator != map.end()) ? iterator->second : nullptr; +} + +template +inline T* mapFindB(std::map& map, const std::string& key) { + auto iterator = map.find(key); + return (iterator != map.end()) ? &iterator->second : nullptr; +} + +class Matrix; +class Transform; +class ColorTransform; +class Point; +class Rectangle; + +class BaseObject; + +class UserData; +class ActionData; +class DragonBonesData; +class ArmatureData; +class CanvasData; +class BoneData; +class SlotData; +class SkinData; +class ConstraintData; +class IKConstraintData; +class DisplayData; +class ImageDisplayData; +class ArmatureDisplayData; +class MeshDisplayData; +class VerticesData; +class WeightData; +class BoundingBoxDisplayData; +class BoundingBoxData; +class RectangleBoundingBoxData; +class EllipseBoundingBoxData; +class PolygonBoundingBoxData; +class AnimationData; +class TimelineData; +class AnimationConfig; +class TextureAtlasData; +class TextureData; + +class IArmatureProxy; +class Armature; +class TransformObject; +class Bone; +class Slot; +class Constraint; +class IKConstraint; +class DeformVertices; + +class IAnimatable; +class WorldClock; +class Animation; +class AnimationState; +class BonePose; +class BlendState; +class TimelineState; +class TweenTimelineState; +class BoneTimelineState; +class SlotTimelineState; +class ConstraintTimelineState; +class ActionTimelineState; +class ZOrderTimelineState; +class BoneAllTimelineState; +class SlotDislayTimelineState; +class SlotColorTimelineState; +class DeformTimelineState; +class IKConstraintTimelineState; + +class IEventDispatcher; +class EventObject; + +class DataParser; +class JSONDataParser; +class BinaryDataParser; + +class BaseFactory; +class BuildArmaturePackage; + +/** + * @private + */ +class DragonBones { + DRAGONBONES_DISALLOW_COPY_AND_ASSIGN(DragonBones) + +public: + static const std::string VEISION; + + static bool yDown; + static bool debug; + static bool debugDraw; + static bool webAssembly; + static bool checkInPool; + +private: + std::map _objectsMap; + std::vector _events; + WorldClock* _clock; + IEventDispatcher* _eventManager; + +public: + DragonBones(IEventDispatcher* eventManager); + virtual ~DragonBones(); + + void advanceTime(float passedTime); + void render(); + void bufferEvent(EventObject* value); + void bufferObject(BaseObject* object); + + WorldClock* getClock(); + IEventDispatcher* getEventManager() const { + return _eventManager; + } +}; + +DRAGONBONES_NAMESPACE_END + +#endif // DRAGONBONES_H diff --git a/cocos/editor-support/dragonbones/event/EventObject.cpp b/cocos/editor-support/dragonbones/event/EventObject.cpp new file mode 100644 index 0000000..58d12b7 --- /dev/null +++ b/cocos/editor-support/dragonbones/event/EventObject.cpp @@ -0,0 +1,50 @@ +#include "EventObject.h" +#include "../armature/Armature.h" +#include "../model/UserData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +const char* EventObject::START = "start"; +const char* EventObject::LOOP_COMPLETE = "loopComplete"; +const char* EventObject::COMPLETE = "complete"; +const char* EventObject::FADE_IN = "fadeIn"; +const char* EventObject::FADE_IN_COMPLETE = "fadeInComplete"; +const char* EventObject::FADE_OUT = "fadeOut"; +const char* EventObject::FADE_OUT_COMPLETE = "fadeOutComplete"; +const char* EventObject::FRAME_EVENT = "frameEvent"; +const char* EventObject::SOUND_EVENT = "soundEvent"; + +void EventObject::actionDataToInstance(const ActionData* data, EventObject* instance, Armature* armature) { + if (data->type == ActionType::Play) { + instance->type = EventObject::FRAME_EVENT; + } else { + instance->type = data->type == ActionType::Frame ? EventObject::FRAME_EVENT : EventObject::SOUND_EVENT; + } + + instance->name = data->name; + instance->armature = armature; + instance->actionData = data; + instance->data = data->data; + + if (data->bone != nullptr) { + instance->bone = armature->getBone(data->bone->name); + } + + if (data->slot != nullptr) { + instance->slot = armature->getSlot(data->slot->name); + } +} + +void EventObject::_onClear() { + time = 0.0f; + type = ""; + name = ""; + armature = nullptr; + bone = nullptr; + slot = nullptr; + animationState = nullptr; + actionData = nullptr; + data = nullptr; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/event/EventObject.h b/cocos/editor-support/dragonbones/event/EventObject.h new file mode 100644 index 0000000..3eb42bf --- /dev/null +++ b/cocos/editor-support/dragonbones/event/EventObject.h @@ -0,0 +1,266 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_EVENT_OBJECT_H +#define DRAGONBONES_EVENT_OBJECT_H + +#include "../core/BaseObject.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The properties of the object carry basic information about an event, + * which are passed as parameter or parameter's parameter to event listeners when an event occurs. + * @version DragonBones 4.5 + * @language en_US + */ +/** + * - 事件对象,包含有关事件的基本信息,当发生事件时,该实例将作为参数或参数的参数传递给事件侦听器。 + * @version DragonBones 4.5 + * @language zh_CN + */ +class EventObject : public BaseObject { + BIND_CLASS_TYPE_A(EventObject); + +public: + /** + * - Animation start play. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 动画开始播放。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static const char* START; + /** + * - Animation loop play complete once. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 动画循环播放完成一次。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static const char* LOOP_COMPLETE; + /** + * - Animation play complete. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 动画播放完成。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static const char* COMPLETE; + /** + * - Animation fade in start. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 动画淡入开始。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static const char* FADE_IN; + /** + * - Animation fade in complete. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 动画淡入完成。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static const char* FADE_IN_COMPLETE; + /** + * - Animation fade out start. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 动画淡出开始。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static const char* FADE_OUT; + /** + * - Animation fade out complete. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 动画淡出完成。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static const char* FADE_OUT_COMPLETE; + /** + * - Animation frame event. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 动画帧事件。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static const char* FRAME_EVENT; + /** + * - Animation frame sound event. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 动画帧声音事件。 + * @version DragonBones 4.5 + * @language zh_CN + */ + static const char* SOUND_EVENT; + + /** + * @internal + */ + static void actionDataToInstance(const ActionData* data, EventObject* instance, Armature* armature); + +public: + /** + * - If is a frame event, the value is used to describe the time that the event was in the animation timeline. (In seconds) + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 如果是帧事件,此值用来描述该事件在动画时间轴中所处的时间。(以秒为单位) + * @version DragonBones 4.5 + * @language zh_CN + */ + float time; + /** + * - The event type。 + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 事件类型。 + * @version DragonBones 4.5 + * @language zh_CN + */ + std::string type; + /** + * - The event name. (The frame event name or the frame sound name) + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 事件名称。 (帧事件的名称或帧声音的名称) + * @version DragonBones 4.5 + * @language zh_CN + */ + std::string name; + /** + * - The armature that dispatch the event. + * @see dragonBones.Armature + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 发出该事件的骨架。 + * @see dragonBones.Armature + * @version DragonBones 4.5 + * @language zh_CN + */ + Armature* armature; + /** + * - The bone that dispatch the event. + * @see dragonBones.Bone + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 发出该事件的骨骼。 + * @see dragonBones.Bone + * @version DragonBones 4.5 + * @language zh_CN + */ + Bone* bone; + /** + * - The slot that dispatch the event. + * @see dragonBones.Slot + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 发出该事件的插槽。 + * @see dragonBones.Slot + * @version DragonBones 4.5 + * @language zh_CN + */ + Slot* slot; + /** + * - The animation state that dispatch the event. + * @see dragonBones.AnimationState + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 发出该事件的动画状态。 + * @see dragonBones.AnimationState + * @version DragonBones 4.5 + * @language zh_CN + */ + AnimationState* animationState; + /** + * @private + */ + const ActionData* actionData; + /** + * - The custom data. + * @see dragonBones.CustomData + * @private + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 自定义数据。 + * @see dragonBones.CustomData + * @private + * @version DragonBones 5.0 + * @language zh_CN + */ + UserData* data; + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + Armature* getArmature() const { return armature; } + Bone* getBone() const { return bone; } + Slot* getSlot() const { return slot; } + AnimationState* getAnimationState() const { return animationState; } + UserData* getData() const { return data; } +}; +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_EVENT_OBJECT_H diff --git a/cocos/editor-support/dragonbones/event/IEventDispatcher.h b/cocos/editor-support/dragonbones/event/IEventDispatcher.h new file mode 100644 index 0000000..34dfdd0 --- /dev/null +++ b/cocos/editor-support/dragonbones/event/IEventDispatcher.h @@ -0,0 +1,112 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_IEVENT_DISPATCHER_H +#define DRAGONBONES_IEVENT_DISPATCHER_H + +#include "../core/DragonBones.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The event dispatcher interface. + * Dragonbones event dispatch usually relies on docking engine to implement, which defines the event method to be implemented when docking the engine. + * @version DragonBones 4.5 + * @language en_US + */ +/** + * - 事件派发接口。 + * DragonBones 的事件派发通常依赖于对接的引擎来实现,该接口定义了对接引擎时需要实现的事件方法。 + * @version DragonBones 4.5 + * @language zh_CN + */ +class IEventDispatcher { + ABSTRACT_CLASS(IEventDispatcher) + +public: + /** + * - Checks whether the object has any listeners registered for a specific type of event。 + * @param type - Event type. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 检查是否为特定的事件类型注册了任何侦听器。 + * @param type - 事件类型。 + * @version DragonBones 4.5 + * @language zh_CN + */ + virtual bool hasDBEventListener(const std::string& type) const = 0; + /** + * - Dispatches an event into the event flow. + * @param type - Event type. + * @param eventObject - Event object. + * @see dragonBones.EventObject + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 分派特定的事件到事件流中。 + * @param type - 事件类型。 + * @param eventObject - 事件数据。 + * @see dragonBones.EventObject + * @version DragonBones 4.5 + * @language zh_CN + */ + virtual void dispatchDBEvent(const std::string& type, EventObject* value) = 0; + /** + * - Add an event listener object so that the listener receives notification of an event. + * @param type - Event type. + * @param listener - Event listener. + * @param thisObject - The listener function's "this". + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 添加特定事件类型的事件侦听器,以使侦听器能够接收事件通知。 + * @param type - 事件类型。 + * @param listener - 事件侦听器。 + * @param thisObject - 侦听函数绑定的 this 对象。 + * @version DragonBones 4.5 + * @language zh_CN + */ + virtual void addDBEventListener(const std::string& type, const std::function& listener) = 0; + /** + * - Removes a listener from the object. + * @param type - Event type. + * @param listener - Event listener. + * @param thisObject - The listener function's "this". + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 删除特定事件类型的侦听器。 + * @param type - 事件类型。 + * @param listener - 事件侦听器。 + * @param thisObject - 侦听函数绑定的 this 对象。 + * @version DragonBones 4.5 + * @language zh_CN + */ + virtual void removeDBEventListener(const std::string& type, const std::function& listener) = 0; +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_IEVENT_DISPATCHER_H diff --git a/cocos/editor-support/dragonbones/factory/BaseFactory.cpp b/cocos/editor-support/dragonbones/factory/BaseFactory.cpp new file mode 100644 index 0000000..9b07a94 --- /dev/null +++ b/cocos/editor-support/dragonbones/factory/BaseFactory.cpp @@ -0,0 +1,529 @@ +#include "BaseFactory.h" + +DRAGONBONES_NAMESPACE_BEGIN + +JSONDataParser BaseFactory::_jsonParser; +BinaryDataParser BaseFactory::_binaryParser; + +TextureData* BaseFactory::_getTextureData(const std::string& textureAtlasName, const std::string& textureName) const { + const auto iterator = _textureAtlasDataMap.find(textureAtlasName); + if (iterator != _textureAtlasDataMap.end()) { + for (const auto textureAtlasData : iterator->second) { + const auto textureData = textureAtlasData->getTexture(textureName); + if (textureData != nullptr) { + return textureData; + } + } + } + + if (autoSearch) { + for (const auto& pair : _textureAtlasDataMap) { + for (const auto textureAtlasData : pair.second) { + if (textureAtlasData->autoSearch) { + const auto textureData = textureAtlasData->getTexture(textureName); + if (textureData != nullptr) { + return textureData; + } + } + } + } + } + + return nullptr; +} + +bool BaseFactory::_fillBuildArmaturePackage( + BuildArmaturePackage& dataPackage, + const std::string& dragonBonesName, const std::string& armatureName, const std::string& skinName, const std::string& textureAtlasName) const { + std::string mapName = dragonBonesName; + DragonBonesData* dragonBonesData = nullptr; + ArmatureData* armatureData = nullptr; + + if (!mapName.empty()) { + const auto iterator = _dragonBonesDataMap.find(mapName); + if (iterator != _dragonBonesDataMap.end()) { + dragonBonesData = iterator->second; + armatureData = dragonBonesData->getArmature(armatureName); + } + } + + if (armatureData == nullptr && (mapName.empty() || autoSearch)) { + for (const auto& pair : _dragonBonesDataMap) { + dragonBonesData = pair.second; + if (mapName.empty() || dragonBonesData->autoSearch) { + armatureData = dragonBonesData->getArmature(armatureName); + if (armatureData != nullptr) { + mapName = pair.first; + break; + } + } + } + } + + if (armatureData != nullptr) { + dataPackage.dataName = mapName; + dataPackage.textureAtlasName = textureAtlasName; + dataPackage.data = dragonBonesData; + dataPackage.armature = armatureData; + dataPackage.skin = nullptr; + + if (!skinName.empty()) { + dataPackage.skin = armatureData->getSkin(skinName); + if (dataPackage.skin == nullptr && autoSearch) { + for (const auto& pair : _dragonBonesDataMap) { + const auto skinDragonBonesData = pair.second; + const auto skinArmatureData = skinDragonBonesData->getArmature(skinName); + if (skinArmatureData != nullptr) { + dataPackage.skin = skinArmatureData->defaultSkin; + break; + } + } + } + } + + if (dataPackage.skin == nullptr) { + dataPackage.skin = armatureData->defaultSkin; + } + + return true; + } + + return false; +} + +void BaseFactory::_buildBones(const BuildArmaturePackage& dataPackage, Armature* armature) const { + for (const auto boneData : dataPackage.armature->sortedBones) { + const auto bone = BaseObject::borrowObject(); + bone->init(boneData, armature); + } + + for (const auto& pair : dataPackage.armature->constraints) { + // TODO more constraint type. + const auto constraint = BaseObject::borrowObject(); + constraint->init(pair.second, armature); + armature->_addConstraint(constraint); + } +} + +void BaseFactory::_buildSlots(const BuildArmaturePackage& dataPackage, Armature* armature) const { + const auto currentSkin = dataPackage.skin; + const auto defaultSkin = dataPackage.armature->defaultSkin; + if (currentSkin == nullptr || defaultSkin == nullptr) { + return; + } + + std::map*> skinSlots; + for (auto& pair : defaultSkin->displays) { + auto& displays = pair.second; + skinSlots[pair.first] = &displays; + } + + if (currentSkin != defaultSkin) { + for (auto& pair : currentSkin->displays) { + auto& displays = pair.second; + skinSlots[pair.first] = &displays; + } + } + + for (const auto slotData : dataPackage.armature->sortedSlots) { + const auto displayDatas = skinSlots[slotData->name]; + const auto slot = _buildSlot(dataPackage, slotData, armature); + slot->setRawDisplayDatas(displayDatas); + + if (displayDatas != nullptr) { + std::vector> displayList; + + for (const auto displayData : *displayDatas) { + if (displayData != nullptr) { + displayList.push_back(_getSlotDisplay(&dataPackage, displayData, nullptr, slot)); + } else { + displayList.push_back(std::make_pair(nullptr, DisplayType::Image)); + } + } + + slot->_setDisplayList(displayList); + } + + slot->_setDisplayIndex(slotData->displayIndex, true); + } +} + +Armature* BaseFactory::_buildChildArmature(const BuildArmaturePackage* dataPackage, Slot* slot, DisplayData* displayData) const { + return buildArmature(displayData->path, dataPackage != nullptr ? dataPackage->dataName : "", "", dataPackage != nullptr ? dataPackage->textureAtlasName : ""); +} + +std::pair BaseFactory::_getSlotDisplay(const BuildArmaturePackage* dataPackage, DisplayData* displayData, DisplayData* rawDisplayData, Slot* slot) const { + std::string dataName = ""; + + if (dataPackage != nullptr) { + dataName = dataPackage->dataName; + } else { + for (const auto& pair : _dragonBonesDataMap) { + if (pair.second == displayData->parent->parent->parent) { + dataName = pair.first; + } + } + + if (dataName.empty()) { + dataName = displayData->parent->parent->parent->name; + } + } + + std::pair display(nullptr, DisplayType::Image); + switch (displayData->type) { + case DisplayType::Image: { + auto imageDisplayData = static_cast(displayData); + + if (dataPackage != nullptr && !dataPackage->textureAtlasName.empty()) { + imageDisplayData->texture = _getTextureData(dataPackage->textureAtlasName, displayData->path); + } + + if (imageDisplayData->texture == nullptr) { + imageDisplayData->texture = _getTextureData(dataName, displayData->path); + } + + display.first = slot->_rawDisplay; + display.second = DisplayType::Image; + break; + } + + case DisplayType::Mesh: { + auto meshDisplayData = static_cast(displayData); + + if (dataPackage != nullptr && !dataPackage->textureAtlasName.empty()) { + meshDisplayData->texture = _getTextureData(dataPackage->textureAtlasName, meshDisplayData->path); + } + + if (meshDisplayData->texture == nullptr) { + meshDisplayData->texture = _getTextureData(dataName, meshDisplayData->path); + } + + if (_isSupportMesh()) { + display.first = slot->_meshDisplay; + display.second = DisplayType::Mesh; + } else { + display.first = slot->_rawDisplay; + display.second = DisplayType::Image; + } + break; + } + + case DisplayType::Armature: { + auto armatureDisplayData = static_cast(displayData); + const auto childArmature = _buildChildArmature(dataPackage, slot, displayData); + if (childArmature != nullptr) { + childArmature->inheritAnimation = armatureDisplayData->inheritAnimation; + if (!childArmature->inheritAnimation) { + const auto actions = !armatureDisplayData->actions.empty() ? &(armatureDisplayData->actions) : &(childArmature->_armatureData->defaultActions); + if (!actions->empty()) { + for (const auto action : *actions) { + childArmature->getAnimation()->fadeIn(action->name); + } + } else { + childArmature->getAnimation()->play(); + } + } + + armatureDisplayData->armature = childArmature->_armatureData; // + } + + display.first = childArmature; + display.second = DisplayType::Armature; + break; + } + + case DisplayType::BoundingBox: + break; + + default: + break; + } + + return display; +} + +DragonBonesData* BaseFactory::parseDragonBonesData(const char* rawData, const std::string& name, float scale) { + DRAGONBONES_ASSERT(rawData != nullptr, ""); + + DataParser* dataParser = nullptr; + + if ( + rawData[0] == 'D' && + rawData[1] == 'B' && + rawData[2] == 'D' && + rawData[3] == 'T') { + dataParser = &_binaryParser; + } else { + dataParser = _dataParser; + } + + const auto dragonBonesData = dataParser->parseDragonBonesData(rawData, scale); + + while (true) { + const auto textureAtlasData = _buildTextureAtlasData(nullptr, nullptr); + if (dataParser->parseTextureAtlasData(nullptr, *textureAtlasData, scale)) { + addTextureAtlasData(textureAtlasData, name); + } else { + textureAtlasData->returnToPool(); + break; + } + } + + if (dragonBonesData != nullptr) { + addDragonBonesData(dragonBonesData, name); + } + + return dragonBonesData; +} + +TextureAtlasData* BaseFactory::parseTextureAtlasData(const char* rawData, void* textureAtlas, const std::string& name, float scale) { + const auto textureAtlasData = _buildTextureAtlasData(nullptr, nullptr); + _dataParser->parseTextureAtlasData(rawData, *textureAtlasData, scale); + _buildTextureAtlasData(textureAtlasData, textureAtlas); + addTextureAtlasData(textureAtlasData, name); + + return textureAtlasData; +} + +void BaseFactory::addDragonBonesData(DragonBonesData* data, const std::string& name) { + const auto& mapName = !name.empty() ? name : data->name; + if (_dragonBonesDataMap.find(mapName) != _dragonBonesDataMap.cend()) { + if (_dragonBonesDataMap[name] == data) { + return; + } + + DRAGONBONES_ASSERT(false, "Can not add same name data: " + name); + return; + } + + _dragonBonesDataMap[mapName] = data; +} + +void BaseFactory::removeDragonBonesData(const std::string& name, bool disposeData) { + const auto iterator = _dragonBonesDataMap.find(name); + if (iterator != _dragonBonesDataMap.cend()) { + if (disposeData) { + iterator->second->returnToPool(); + } + + _dragonBonesDataMap.erase(iterator); + } +} + +void BaseFactory::addTextureAtlasData(TextureAtlasData* data, const std::string& name) { + const auto& mapName = !name.empty() ? name : data->name; + auto& textureAtlasList = _textureAtlasDataMap[mapName]; + if (std::find(textureAtlasList.cbegin(), textureAtlasList.cend(), data) == textureAtlasList.cend()) { + textureAtlasList.push_back(data); + } +} + +void BaseFactory::removeTextureAtlasData(const std::string& name, bool disposeData) { + const auto iterator = _textureAtlasDataMap.find(name); + if (iterator != _textureAtlasDataMap.end()) { + if (disposeData) { + for (const auto textureAtlasData : iterator->second) { + textureAtlasData->returnToPool(); + } + } + + _textureAtlasDataMap.erase(iterator); + } +} + +ArmatureData* BaseFactory::getArmatureData(const std::string& name, const std::string& dragonBonesName) const { + BuildArmaturePackage dataPackage; + if (!_fillBuildArmaturePackage(dataPackage, dragonBonesName, name, "", "")) { + return nullptr; + } + + return dataPackage.armature; +} + +void BaseFactory::clear(bool disposeData) { + if (disposeData) { + for (const auto& pair : _dragonBonesDataMap) { + pair.second->returnToPool(); + } + + for (const auto& pair : _textureAtlasDataMap) { + for (const auto textureAtlasData : pair.second) { + textureAtlasData->returnToPool(); + } + } + } + + _dragonBonesDataMap.clear(); + _textureAtlasDataMap.clear(); +} + +Armature* BaseFactory::buildArmature(const std::string& armatureName, const std::string& dragonBonesName, const std::string& skinName, const std::string& textureAtlasName) const { + BuildArmaturePackage dataPackage; + if (!_fillBuildArmaturePackage(dataPackage, dragonBonesName, armatureName, skinName, textureAtlasName)) { + DRAGONBONES_ASSERT(false, "No armature data: " + armatureName + ", " + (!dragonBonesName.empty() ? dragonBonesName : "")); + return nullptr; + } + + const auto armature = _buildArmature(dataPackage); + _buildBones(dataPackage, armature); + _buildSlots(dataPackage, armature); + armature->invalidUpdate("", true); + armature->advanceTime(0.0f); // Update armature pose. + + return armature; +} + +void BaseFactory::replaceDisplay(Slot* slot, DisplayData* displayData, int displayIndex) const { + if (displayIndex < 0) { + displayIndex = slot->getDisplayIndex(); + } + + if (displayIndex < 0) { + displayIndex = 0; + } + + slot->replaceDisplayData(displayData, displayIndex); + + auto displayList = slot->getDisplayList(); // Copy. + if (displayList.size() <= (unsigned)displayIndex) { + displayList.resize(displayIndex + 1, std::make_pair(nullptr, DisplayType::Image)); + } + + if (displayData != nullptr) { + const auto rawDisplayDatas = slot->getRawDisplayDatas(); + displayList[displayIndex] = _getSlotDisplay( + nullptr, + displayData, + rawDisplayDatas != nullptr && (unsigned)displayIndex < rawDisplayDatas->size() ? rawDisplayDatas->at(displayIndex) : nullptr, + slot); + } else { + displayList[displayIndex] = std::make_pair(nullptr, DisplayType::Image); + } + + slot->setDisplayList(displayList); +} + +bool BaseFactory::replaceSlotDisplay(const std::string& dragonBonesName, const std::string& armatureName, const std::string& slotName, const std::string& displayName, Slot* slot, int displayIndex) const { + DRAGONBONES_ASSERT(slot, "Arguments error."); + + const auto armatureData = getArmatureData(armatureName, dragonBonesName); + if (!armatureData || !armatureData->defaultSkin) { + return false; + } + + const auto displayData = armatureData->defaultSkin->getDisplay(slotName, displayName); + if (!displayData) { + return false; + } + + replaceDisplay(slot, displayData, displayIndex); + + return true; +} + +bool BaseFactory::replaceSlotDisplayList(const std::string& dragonBonesName, const std::string& armatureName, const std::string& slotName, Slot* slot) const { + DRAGONBONES_ASSERT(slot, "Arguments error."); + + const auto armatureData = getArmatureData(armatureName, dragonBonesName); + if (!armatureData || !armatureData->defaultSkin) { + return false; + } + + const auto displays = armatureData->defaultSkin->getDisplays(slotName); + if (!displays) { + return false; + } + + auto displayIndex = 0; + for (const auto displayData : *displays) { + replaceDisplay(slot, displayData, displayIndex++); + } + + return true; +} + +bool BaseFactory::replaceSkin(Armature* armature, SkinData* skin, bool isOverride, const std::vector& exclude) const { + DRAGONBONES_ASSERT(armature && skin, "Arguments error."); + + auto success = false; + const auto defaultSkin = skin->parent->defaultSkin; + + for (const auto slot : armature->getSlots()) { + if (std::find(exclude.cbegin(), exclude.cend(), slot->getName()) != exclude.cend()) { + continue; + } + + auto displays = skin->getDisplays(slot->getName()); + if (displays == nullptr) { + if (defaultSkin != nullptr && skin != defaultSkin) { + displays = defaultSkin->getDisplays(slot->getName()); + } + + if (isOverride) { + std::vector> displayList; + slot->setRawDisplayDatas(nullptr); + slot->setDisplayList(displayList); + } + continue; + } + + auto displayList = slot->getDisplayList(); // Copy. + displayList.resize(displays->size(), std::make_pair(nullptr, DisplayType::Image)); + for (std::size_t i = 0, l = displays->size(); i < l; ++i) { + const auto displayData = displays->at(i); + if (displayData != nullptr) { + displayList[i] = _getSlotDisplay(nullptr, displayData, nullptr, slot); + } else { + displayList[i] = std::make_pair(nullptr, DisplayType::Image); + } + } + + success = true; + slot->setRawDisplayDatas(displays); + slot->setDisplayList(displayList); + } + + return success; +} + +bool BaseFactory::replaceAnimation(Armature* armature, ArmatureData* armatureData, bool isReplaceAll) const { + const auto skinData = armatureData->defaultSkin; + if (skinData == nullptr) { + return false; + } + + if (isReplaceAll) { + armature->getAnimation()->setAnimations(armatureData->animations); + } else { + auto animations = armature->getAnimation()->getAnimations(); // Copy. + for (const auto& pair : armatureData->animations) { + animations[pair.first] = pair.second; + } + + armature->getAnimation()->setAnimations(animations); + } + + for (const auto slot : armature->getSlots()) { + unsigned index = 0; + for (const auto& pair : slot->getDisplayList()) { + if (pair.second == DisplayType::Armature) { + auto displayDatas = skinData->getDisplays(slot->getName()); + if (displayDatas != nullptr && index < displayDatas->size()) { + const auto displayData = (*displayDatas)[index]; + if (displayData != nullptr && displayData->type == DisplayType::Armature) { + const auto childArmatureData = getArmatureData(displayData->path, displayData->parent->parent->parent->name); + if (childArmatureData != nullptr) { + replaceAnimation((Armature*)pair.first, childArmatureData, isReplaceAll); + } + } + } + } + + index++; + } + } + + return true; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/factory/BaseFactory.h b/cocos/editor-support/dragonbones/factory/BaseFactory.h new file mode 100644 index 0000000..02cc89b --- /dev/null +++ b/cocos/editor-support/dragonbones/factory/BaseFactory.h @@ -0,0 +1,575 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_BASE_FACTORY_H +#define DRAGONBONES_BASE_FACTORY_H + +#include "../animation/Animation.h" +#include "../armature/Armature.h" +#include "../armature/Bone.h" +#include "../armature/Constraint.h" +#include "../armature/Slot.h" +#include "../parser/BinaryDataParser.h" +#include "../parser/JSONDataParser.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - Base class for the factory that create the armatures. (Typically only one global factory instance is required) + * The factory instance create armatures by parsed and added DragonBonesData instances and TextureAtlasData instances. + * Once the data has been parsed, it has been cached in the factory instance and does not need to be parsed again until it is cleared by the factory instance. + * @see dragonBones.DragonBonesData + * @see dragonBones.TextureAtlasData + * @see dragonBones.ArmatureData + * @see dragonBones.Armature + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 创建骨架的工厂基类。 (通常只需要一个全局工厂实例) + * 工厂通过解析并添加的 DragonBonesData 实例和 TextureAtlasData 实例来创建骨架。 + * 当数据被解析过之后,已经添加到工厂中,在没有被工厂清理之前,不需要再次解析。 + * @see dragonBones.DragonBonesData + * @see dragonBones.TextureAtlasData + * @see dragonBones.ArmatureData + * @see dragonBones.Armature + * @version DragonBones 3.0 + * @language zh_CN + */ +class BaseFactory { +protected: + static JSONDataParser _jsonParser; + static BinaryDataParser _binaryParser; + +public: + /** + * @private + */ + bool autoSearch; + +protected: + std::map _dragonBonesDataMap; + std::map> _textureAtlasDataMap; + DragonBones* _dragonBones; + DataParser* _dataParser; + +public: + /** + * - Create a factory instance. (typically only one global factory instance is required) + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 创建一个工厂实例。 (通常只需要一个全局工厂实例) + * @version DragonBones 3.0 + * @language zh_CN + */ + BaseFactory(DataParser* dataParser = nullptr) : autoSearch(false), + _dragonBonesDataMap(), + _textureAtlasDataMap(), + _dragonBones(nullptr), + _dataParser(nullptr) { + _dataParser = dataParser != nullptr ? dataParser : &BaseFactory::_jsonParser; + } + virtual ~BaseFactory() { + clear(); + + _dragonBones = nullptr; + _dataParser = nullptr; + } + +protected: + virtual inline bool _isSupportMesh() const { + return true; + } + virtual TextureData* _getTextureData(const std::string& textureAtlasName, const std::string& textureName) const; + virtual bool _fillBuildArmaturePackage( + BuildArmaturePackage& dataPackage, + const std::string& dragonBonesName, const std::string& armatureName, const std::string& skinName, const std::string& textureAtlasName) const; + virtual void _buildBones(const BuildArmaturePackage& dataPackage, Armature* armature) const; + /** + * @private + */ + virtual void _buildSlots(const BuildArmaturePackage& dataPackage, Armature* armature) const; + virtual Armature* _buildChildArmature(const BuildArmaturePackage* dataPackage, Slot* slot, DisplayData* displayData) const; + virtual std::pair _getSlotDisplay(const BuildArmaturePackage* dataPackage, DisplayData* displayData, DisplayData* rawDisplayData, Slot* slot) const; + virtual TextureAtlasData* _buildTextureAtlasData(TextureAtlasData* textureAtlasData, void* textureAtlas) const = 0; + virtual Armature* _buildArmature(const BuildArmaturePackage& dataPackage) const = 0; + virtual Slot* _buildSlot(const BuildArmaturePackage& dataPackage, const SlotData* slotData, Armature* armature) const = 0; + +public: + /** + * - Parse the raw data to a DragonBonesData instance and cache it to the factory. + * @param rawData - The raw data. + * @param name - Specify a cache name for the instance so that the instance can be obtained through this name. (If not set, use the instance name instead) + * @param scale - Specify a scaling value for all armatures. (Default: 1.0) + * @returns DragonBonesData instance + * @see #getDragonBonesData() + * @see #addDragonBonesData() + * @see #removeDragonBonesData() + * @see dragonBones.DragonBonesData + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 将原始数据解析为 DragonBonesData 实例,并缓存到工厂中。 + * @param rawData - 原始数据。 + * @param name - 为该实例指定一个缓存名称,以便可以通过此名称获取该实例。 (如果未设置,则使用该实例中的名称) + * @param scale - 为所有的骨架指定一个缩放值。 (默认: 1.0) + * @returns DragonBonesData 实例 + * @see #getDragonBonesData() + * @see #addDragonBonesData() + * @see #removeDragonBonesData() + * @see dragonBones.DragonBonesData + * @version DragonBones 4.5 + * @language zh_CN + */ + virtual DragonBonesData* parseDragonBonesData(const char* rawData, const std::string& name = "", float scale = 1.0f); + /** + * - Parse the raw texture atlas data and the texture atlas object to a TextureAtlasData instance and cache it to the factory. + * @param rawData - The raw texture atlas data. + * @param textureAtlas - The texture atlas object. + * @param name - Specify a cache name for the instance so that the instance can be obtained through this name. (If not set, use the instance name instead) + * @param scale - Specify a scaling value for the map set. (Default: 1.0) + * @returns TextureAtlasData instance + * @see #getTextureAtlasData() + * @see #addTextureAtlasData() + * @see #removeTextureAtlasData() + * @see dragonBones.TextureAtlasData + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 将原始贴图集数据和贴图集对象解析为 TextureAtlasData 实例,并缓存到工厂中。 + * @param rawData - 原始贴图集数据。 + * @param textureAtlas - 贴图集对象。 + * @param name - 为该实例指定一个缓存名称,以便可以通过此名称获取该实例。 (如果未设置,则使用该实例中的名称) + * @param scale - 为贴图集指定一个缩放值。 (默认: 1.0) + * @returns TextureAtlasData 实例 + * @see #getTextureAtlasData() + * @see #addTextureAtlasData() + * @see #removeTextureAtlasData() + * @see dragonBones.TextureAtlasData + * @version DragonBones 4.5 + * @language zh_CN + */ + virtual TextureAtlasData* parseTextureAtlasData(const char* rawData, void* textureAtlas, const std::string& name = "", float scale = 1.0f); + /** + * - Get a specific DragonBonesData instance. + * @param name - The DragonBonesData instance cache name. + * @returns DragonBonesData instance + * @see #parseDragonBonesData() + * @see #addDragonBonesData() + * @see #removeDragonBonesData() + * @see dragonBones.DragonBonesData + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取特定的 DragonBonesData 实例。 + * @param name - DragonBonesData 实例的缓存名称。 + * @returns DragonBonesData 实例 + * @see #parseDragonBonesData() + * @see #addDragonBonesData() + * @see #removeDragonBonesData() + * @see dragonBones.DragonBonesData + * @version DragonBones 3.0 + * @language zh_CN + */ + inline DragonBonesData* getDragonBonesData(const std::string& name) const { + return mapFind(_dragonBonesDataMap, name); + } + /** + * - Cache a DragonBonesData instance to the factory. + * @param data - The DragonBonesData instance. + * @param name - Specify a cache name for the instance so that the instance can be obtained through this name. (if not set, use the instance name instead) + * @see #parseDragonBonesData() + * @see #getDragonBonesData() + * @see #removeDragonBonesData() + * @see dragonBones.DragonBonesData + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 将 DragonBonesData 实例缓存到工厂中。 + * @param data - DragonBonesData 实例。 + * @param name - 为该实例指定一个缓存名称,以便可以通过此名称获取该实例。 (如果未设置,则使用该实例中的名称) + * @see #parseDragonBonesData() + * @see #getDragonBonesData() + * @see #removeDragonBonesData() + * @see dragonBones.DragonBonesData + * @version DragonBones 3.0 + * @language zh_CN + */ + virtual void addDragonBonesData(DragonBonesData* data, const std::string& name = ""); + /** + * - Remove a DragonBonesData instance. + * @param name - The DragonBonesData instance cache name. + * @param disposeData - Whether to dispose data. (Default: true) + * @see #parseDragonBonesData() + * @see #getDragonBonesData() + * @see #addDragonBonesData() + * @see dragonBones.DragonBonesData + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 移除 DragonBonesData 实例。 + * @param name - DragonBonesData 实例缓存名称。 + * @param disposeData - 是否释放数据。 (默认: true) + * @see #parseDragonBonesData() + * @see #getDragonBonesData() + * @see #addDragonBonesData() + * @see dragonBones.DragonBonesData + * @version DragonBones 3.0 + * @language zh_CN + */ + virtual void removeDragonBonesData(const std::string& name, bool disposeData = true); + /** + * - Get a list of specific TextureAtlasData instances. + * @param name - The TextureAtlasData cahce name. + * @see #parseTextureAtlasData() + * @see #addTextureAtlasData() + * @see #removeTextureAtlasData() + * @see dragonBones.TextureAtlasData + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取特定的 TextureAtlasData 实例列表。 + * @param name - TextureAtlasData 实例缓存名称。 + * @see #parseTextureAtlasData() + * @see #addTextureAtlasData() + * @see #removeTextureAtlasData() + * @see dragonBones.TextureAtlasData + * @version DragonBones 3.0 + * @language zh_CN + */ + inline std::vector* getTextureAtlasData(const std::string& name) { + return mapFindB(_textureAtlasDataMap, name); + } + /** + * - Cache a TextureAtlasData instance to the factory. + * @param data - The TextureAtlasData instance. + * @param name - Specify a cache name for the instance so that the instance can be obtained through this name. (if not set, use the instance name instead) + * @see #parseTextureAtlasData() + * @see #getTextureAtlasData() + * @see #removeTextureAtlasData() + * @see dragonBones.TextureAtlasData + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 将 TextureAtlasData 实例缓存到工厂中。 + * @param data - TextureAtlasData 实例。 + * @param name - 为该实例指定一个缓存名称,以便可以通过此名称获取该实例。 (如果未设置,则使用该实例中的名称) + * @see #parseTextureAtlasData() + * @see #getTextureAtlasData() + * @see #removeTextureAtlasData() + * @see dragonBones.TextureAtlasData + * @version DragonBones 3.0 + * @language zh_CN + */ + virtual void addTextureAtlasData(TextureAtlasData* data, const std::string& name = ""); + /** + * - Remove a TextureAtlasData instance. + * @param name - The TextureAtlasData instance cache name. + * @param disposeData - Whether to dispose data. + * @see #parseTextureAtlasData() + * @see #getTextureAtlasData() + * @see #addTextureAtlasData() + * @see dragonBones.TextureAtlasData + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 移除 TextureAtlasData 实例。 + * @param name - TextureAtlasData 实例的缓存名称。 + * @param disposeData - 是否释放数据。 + * @see #parseTextureAtlasData() + * @see #getTextureAtlasData() + * @see #addTextureAtlasData() + * @see dragonBones.TextureAtlasData + * @version DragonBones 3.0 + * @language zh_CN + */ + virtual void removeTextureAtlasData(const std::string& name, bool disposeData = true); + /** + * - Get a specific armature data. + * @param name - The armature data name. + * @param dragonBonesName - The cached name for DragonbonesData instance. + * @see dragonBones.ArmatureData + * @version DragonBones 5.1 + * @language en_US + */ + /** + * - 获取特定的骨架数据。 + * @param name - 骨架数据名称。 + * @param dragonBonesName - DragonBonesData 实例的缓存名称。 + * @see dragonBones.ArmatureData + * @version DragonBones 5.1 + * @language zh_CN + */ + virtual ArmatureData* getArmatureData(const std::string& name, const std::string& dragonBonesName = "") const; + /** + * - Clear all cached DragonBonesData instances and TextureAtlasData instances. + * @param disposeData - Whether to dispose data. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 清除缓存的所有 DragonBonesData 实例和 TextureAtlasData 实例。 + * @param disposeData - 是否释放数据。 + * @version DragonBones 4.5 + * @language zh_CN + */ + virtual void clear(bool disposeData = true); + /** + * - Create a armature from cached DragonBonesData instances and TextureAtlasData instances. + * Note that when the created armature that is no longer in use, you need to explicitly dispose {@link #dragonBones.Armature#dispose()}. + * @param armatureName - The armature data name. + * @param dragonBonesName - The cached name of the DragonBonesData instance. (If not set, all DragonBonesData instances are retrieved, and when multiple DragonBonesData instances contain a the same name armature data, it may not be possible to accurately create a specific armature) + * @param skinName - The skin name, you can set a different ArmatureData name to share it's skin data. (If not set, use the default skin data) + * @returns The armature. + * @example + * TypeScript style, for reference only. + *
+     *     let armature = factory.buildArmature("armatureName", "dragonBonesName");
+     *     armature.clock = factory.clock;
+     * 
+ * @see dragonBones.DragonBonesData + * @see dragonBones.ArmatureData + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 通过缓存的 DragonBonesData 实例和 TextureAtlasData 实例创建一个骨架。 + * 注意,创建的骨架不再使用时,需要显式释放 {@link #dragonBones.Armature#dispose()}。 + * @param armatureName - 骨架数据名称。 + * @param dragonBonesName - DragonBonesData 实例的缓存名称。 (如果未设置,将检索所有的 DragonBonesData 实例,当多个 DragonBonesData 实例中包含同名的骨架数据时,可能无法准确的创建出特定的骨架) + * @param skinName - 皮肤名称,可以设置一个其他骨架数据名称来共享其皮肤数据。(如果未设置,则使用默认的皮肤数据) + * @returns 骨架。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     let armature = factory.buildArmature("armatureName", "dragonBonesName");
+     *     armature.clock = factory.clock;
+     * 
+ * @see dragonBones.DragonBonesData + * @see dragonBones.ArmatureData + * @version DragonBones 3.0 + * @language zh_CN + */ + virtual Armature* buildArmature(const std::string& armatureName, const std::string& dragonBonesName = "", const std::string& skinName = "", const std::string& textureAtlasName = "") const; + /** + * @private + */ + virtual void replaceDisplay(Slot* slot, DisplayData* displayData, int displayIndex) const; + /** + * - Replaces the current display data for a particular slot with a specific display data. + * Specify display data with "dragonBonesName/armatureName/slotName/displayName". + * @param dragonBonesName - The DragonBonesData instance cache name. + * @param armatureName - The armature data name. + * @param slotName - The slot data name. + * @param displayName - The display data name. + * @param slot - The slot. + * @param displayIndex - The index of the display data that is replaced. (If it is not set, replaces the current display data) + * @example + * TypeScript style, for reference only. + *
+     *     let slot = armature.getSlot("weapon");
+     *     factory.replaceSlotDisplay("dragonBonesName", "armatureName", "slotName", "displayName", slot);
+     * 
+ * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 用特定的显示对象数据替换特定插槽当前的显示对象数据。 + * 用 "dragonBonesName/armatureName/slotName/displayName" 指定显示对象数据。 + * @param dragonBonesName - DragonBonesData 实例的缓存名称。 + * @param armatureName - 骨架数据名称。 + * @param slotName - 插槽数据名称。 + * @param displayName - 显示对象数据名称。 + * @param slot - 插槽。 + * @param displayIndex - 被替换的显示对象数据的索引。 (如果未设置,则替换当前的显示对象数据) + * @example + * TypeScript 风格,仅供参考。 + *
+     *     let slot = armature.getSlot("weapon");
+     *     factory.replaceSlotDisplay("dragonBonesName", "armatureName", "slotName", "displayName", slot);
+     * 
+ * @version DragonBones 4.5 + * @language zh_CN + */ + virtual bool replaceSlotDisplay( + const std::string& dragonBonesName, const std::string& armatureName, const std::string& slotName, const std::string& displayName, + Slot* slot, int displayIndex = -1) const; + /** + * @private + */ + virtual bool replaceSlotDisplayList( + const std::string& dragonBonesName, const std::string& armatureName, const std::string& slotName, + Slot* slot) const; + /** + * - Share specific skin data with specific armature. + * @param armature - The armature. + * @param skin - The skin data. + * @param isOverride - Whether it completely override the original skin. (Default: false) + * @param exclude - A list of slot names that do not need to be replace. + * @example + * TypeScript style, for reference only. + *
+     *     let armatureA = factory.buildArmature("armatureA", "dragonBonesA");
+     *     let armatureDataB = factory.getArmatureData("armatureB", "dragonBonesB");
+     *     if (armatureDataB && armatureDataB.defaultSkin) {
+     *     factory.replaceSkin(armatureA, armatureDataB.defaultSkin, false, ["arm_l", "weapon_l"]);
+     *     }
+     * 
+ * @see dragonBones.Armature + * @see dragonBones.SkinData + * @version DragonBones 5.6 + * @language en_US + */ + /** + * - 将特定的皮肤数据共享给特定的骨架使用。 + * @param armature - 骨架。 + * @param skin - 皮肤数据。 + * @param isOverride - 是否完全覆盖原来的皮肤。 (默认: false) + * @param exclude - 不需要被替换的插槽名称列表。 + * @example + * TypeScript 风格,仅供参考。 + *
+     *     let armatureA = factory.buildArmature("armatureA", "dragonBonesA");
+     *     let armatureDataB = factory.getArmatureData("armatureB", "dragonBonesB");
+     *     if (armatureDataB && armatureDataB.defaultSkin) {
+     *     factory.replaceSkin(armatureA, armatureDataB.defaultSkin, false, ["arm_l", "weapon_l"]);
+     *     }
+     * 
+ * @see dragonBones.Armature + * @see dragonBones.SkinData + * @version DragonBones 5.6 + * @language zh_CN + */ + virtual bool replaceSkin(Armature* armature, SkinData* skin, bool isOverride, const std::vector& exclude) const; + /** + * - Replaces the existing animation data for a specific armature with the animation data for the specific armature data. + * This enables you to make a armature template so that other armature without animations can share it's animations. + * @param armature - The armtaure. + * @param armatureData - The armature data. + * @param isOverride - Whether to completely overwrite the original animation. (Default: false) + * @example + * TypeScript style, for reference only. + *
+     *     let armatureA = factory.buildArmature("armatureA", "dragonBonesA");
+     *     let armatureDataB = factory.getArmatureData("armatureB", "dragonBonesB");
+     *     if (armatureDataB) {
+     *     factory.replaceAnimation(armatureA, armatureDataB);
+     *     }
+     * 
+ * @see dragonBones.Armature + * @see dragonBones.ArmatureData + * @version DragonBones 5.6 + * @language en_US + */ + /** + * - 用特定骨架数据的动画数据替换特定骨架现有的动画数据。 + * 这样就能实现制作一个骨架动画模板,让其他没有制作动画的骨架共享该动画。 + * @param armature - 骨架。 + * @param armatureData - 骨架数据。 + * @param isOverride - 是否完全覆盖原来的动画。(默认: false) + * @example + * TypeScript 风格,仅供参考。 + *
+     *     let armatureA = factory.buildArmature("armatureA", "dragonBonesA");
+     *     let armatureDataB = factory.getArmatureData("armatureB", "dragonBonesB");
+     *     if (armatureDataB) {
+     *     factory.replaceAnimation(armatureA, armatureDataB);
+     *     }
+     * 
+ * @see dragonBones.Armature + * @see dragonBones.ArmatureData + * @version DragonBones 5.6 + * @language zh_CN + */ + virtual bool replaceAnimation(Armature* armature, ArmatureData* armatureData, bool isReplaceAll = true) const; + /** + * @private + */ + inline const std::map>& getAllTextureAtlasData() const { + return _textureAtlasDataMap; + } + /** + * @private + */ + inline const std::map& getAllDragonBonesData() const { + return _dragonBonesDataMap; + } + /** + * - An Worldclock instance updated by engine. + * @version DragonBones 5.7 + * @language en_US + */ + /** + * - 由引擎驱动的 WorldClock 实例。 + * @version DragonBones 5.7 + * @language zh_CN + */ + inline WorldClock* getClock() const { + return _dragonBones->getClock(); + } + + /** + * - Deprecated, please refer to {@link #replaceSkin}. + * @deprecated + * @language en_US + */ + /** + * - 已废弃,请参考 {@link #replaceSkin}。 + * @deprecated + * @language zh_CN + */ + inline bool changeSkin(Armature* armature, SkinData* skin, const std::vector& exclude) const { + return replaceSkin(armature, skin, false, exclude); + } +}; +/** + * @internal + */ +class BuildArmaturePackage { + DRAGONBONES_DISALLOW_COPY_AND_ASSIGN(BuildArmaturePackage) + +public: + std::string dataName; + std::string textureAtlasName; + DragonBonesData* data; + ArmatureData* armature; + SkinData* skin; + + BuildArmaturePackage() : dataName(), + textureAtlasName(), + data(nullptr), + armature(nullptr), + skin(nullptr) {} + ~BuildArmaturePackage() {} +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_BASE_FACTORY_H diff --git a/cocos/editor-support/dragonbones/geom/ColorTransform.h b/cocos/editor-support/dragonbones/geom/ColorTransform.h new file mode 100644 index 0000000..a72b5ad --- /dev/null +++ b/cocos/editor-support/dragonbones/geom/ColorTransform.h @@ -0,0 +1,74 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_COLOR_TRANSFORM_H +#define DRAGONBONES_COLOR_TRANSFORM_H + +#include "../core/DragonBones.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * @internal + */ +class ColorTransform { +public: + float alphaMultiplier; + float redMultiplier; + float greenMultiplier; + float blueMultiplier; + int alphaOffset; + int redOffset; + int greenOffset; + int blueOffset; + + ColorTransform() : alphaMultiplier(1.0f), + redMultiplier(1.0f), + greenMultiplier(1.0f), + blueMultiplier(1.0f), + alphaOffset(0), + redOffset(0), + greenOffset(0), + blueOffset(0) {} + ColorTransform(const ColorTransform &value) { + operator=(value); + } + ~ColorTransform() {} + + inline void operator=(const ColorTransform &value) { + alphaMultiplier = value.alphaMultiplier; + redMultiplier = value.redMultiplier; + greenMultiplier = value.greenMultiplier; + blueMultiplier = value.blueMultiplier; + alphaOffset = value.alphaOffset; + redOffset = value.redOffset; + greenOffset = value.greenOffset; + blueOffset = value.blueOffset; + } + + inline void identity() { + alphaMultiplier = redMultiplier = greenMultiplier = blueMultiplier = 1.0f; + alphaOffset = redOffset = greenOffset = blueOffset = 0; + } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_COLOR_TRANSFORM_H diff --git a/cocos/editor-support/dragonbones/geom/Matrix.h b/cocos/editor-support/dragonbones/geom/Matrix.h new file mode 100644 index 0000000..3f9e2ef --- /dev/null +++ b/cocos/editor-support/dragonbones/geom/Matrix.h @@ -0,0 +1,302 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_MATRIX_H +#define DRAGONBONES_MATRIX_H + +#include "../core/DragonBones.h" +#include "Point.h" +#include "Rectangle.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - 2D Transform matrix. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 2D 转换矩阵。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class Matrix { +public: + /** + * - The value that affects the positioning of pixels along the x axis when scaling or rotating an image. + * @default 1.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 缩放或旋转图像时影响像素沿 x 轴定位的值。 + * @default 1.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float a; + /** + * - The value that affects the positioning of pixels along the y axis when rotating or skewing an image. + * @default 0.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 旋转或倾斜图像时影响像素沿 y 轴定位的值。 + * @default 0.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float b; + /** + * - The value that affects the positioning of pixels along the x axis when rotating or skewing an image. + * @default 0.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 旋转或倾斜图像时影响像素沿 x 轴定位的值。 + * @default 0.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float c; + /** + * - The value that affects the positioning of pixels along the y axis when scaling or rotating an image. + * @default 1.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 缩放或旋转图像时影响像素沿 y 轴定位的值。 + * @default 1.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float d; + /** + * - The distance by which to translate each point along the x axis. + * @default 0.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 沿 x 轴平移每个点的距离。 + * @default 0.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float tx; + /** + * - The distance by which to translate each point along the y axis. + * @default 0.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 沿 y 轴平移每个点的距离。 + * @default 0.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float ty; + + Matrix() : a(1.0f), + b(0.0f), + c(0.0f), + d(1.0f), + tx(0.0f), + ty(0.0f) {} + /** + * @private + */ + Matrix(const Matrix& value) { + operator=(value); + } + ~Matrix() {} + + inline void operator=(const Matrix& value) { + a = value.a; + b = value.b; + c = value.c; + d = value.d; + tx = value.tx; + ty = value.ty; + } + /** + * - Convert to unit matrix. + * The resulting matrix has the following properties: a=1, b=0, c=0, d=1, tx=0, ty=0. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 转换为单位矩阵。 + * 该矩阵具有以下属性:a=1、b=0、c=0、d=1、tx=0、ty=0。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline void identity() { + a = d = 1.0f; + b = c = 0.0f; + tx = ty = 0.0f; + } + /** + * - Multiplies the current matrix with another matrix. + * @param value - The matrix that needs to be multiplied. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 将当前矩阵与另一个矩阵相乘。 + * @param value - 需要相乘的矩阵。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline void concat(const Matrix& value) { + const auto aA = a; + const auto bA = b; + const auto cA = c; + const auto dA = d; + const auto txA = tx; + const auto tyA = ty; + const auto aB = value.a; + const auto bB = value.b; + const auto cB = value.c; + const auto dB = value.d; + const auto txB = value.tx; + const auto tyB = value.ty; + + a = aA * aB + bA * cB; + b = aA * bB + bA * dB; + c = cA * aB + dA * cB; + d = cA * bB + dA * dB; + tx = aB * txA + cB * tyA + txB; + ty = dB * tyA + bB * txA + tyB; + } + /** + * - Convert to inverse matrix. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 转换为逆矩阵。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline void invert() { + const auto aA = a; + const auto bA = b; + const auto cA = c; + const auto dA = d; + const auto txA = tx; + const auto tyA = ty; + const auto n = aA * dA - bA * cA; + + a = dA / n; + b = -bA / n; + c = -cA / n; + d = aA / n; + tx = (cA * tyA - dA * txA) / n; + ty = -(aA * tyA - bA * txA) / n; + } + /** + * - Apply a matrix transformation to a specific point. + * @param x - X coordinate. + * @param y - Y coordinate. + * @param result - The point after the transformation is applied. + * @param delta - Whether to ignore tx, ty's conversion to point. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 将矩阵转换应用于特定点。 + * @param x - 横坐标。 + * @param y - 纵坐标。 + * @param result - 应用转换之后的点。 + * @param delta - 是否忽略 tx,ty 对点的转换。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline void transformPoint(float x, float y, Point& result, bool delta = false) const { + result.x = a * x + c * y; + result.y = b * x + d * y; + + if (!delta) { + result.x += tx; + result.y += ty; + } + } + /** + * @private + */ + inline void transformRectangle(Rectangle& rectangle, bool delta = false) const { + const auto offsetX = delta ? 0.0f : this->tx; + const auto offsetY = delta ? 0.0f : this->ty; + + const auto x = rectangle.x; + const auto y = rectangle.y; + const auto xMax = x + rectangle.width; + const auto yMax = y + rectangle.height; + + auto x0 = a * x + c * y + offsetX; + auto y0 = b * x + d * y + offsetY; + auto x1 = a * xMax + c * y + offsetX; + auto y1 = b * xMax + d * y + offsetY; + auto x2 = a * xMax + c * yMax + offsetX; + auto y2 = b * xMax + d * yMax + offsetY; + auto x3 = a * x + c * yMax + offsetX; + auto y3 = b * x + d * yMax + offsetY; + auto tmp = 0.0f; + + if (x0 > x1) { + tmp = x0; + x0 = x1; + x1 = tmp; + } + + if (x2 > x3) { + tmp = x2; + x2 = x3; + x3 = tmp; + } + + rectangle.x = std::floor(x0 < x2 ? x0 : x2); + rectangle.width = std::ceil((x1 > x3 ? x1 : x3) - rectangle.x); + + if (y0 > y1) { + tmp = y0; + y0 = y1; + y1 = tmp; + } + + if (y2 > y3) { + tmp = y2; + y2 = y3; + y3 = tmp; + } + + rectangle.y = std::floor(y0 < y2 ? y0 : y2); + rectangle.height = std::ceil((y1 > y3 ? y1 : y3) - rectangle.y); + } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_MATRIX_H diff --git a/cocos/editor-support/dragonbones/geom/Point.cpp b/cocos/editor-support/dragonbones/geom/Point.cpp new file mode 100644 index 0000000..b584e4b --- /dev/null +++ b/cocos/editor-support/dragonbones/geom/Point.cpp @@ -0,0 +1,10 @@ +#include "Point.h" + +DRAGONBONES_NAMESPACE_BEGIN + +Point Point::helpPointA; +Point Point::helpPointB; +Point Point::helpPointC; +Point Point::helpPointD; + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/geom/Point.h b/cocos/editor-support/dragonbones/geom/Point.h new file mode 100644 index 0000000..b136cb3 --- /dev/null +++ b/cocos/editor-support/dragonbones/geom/Point.h @@ -0,0 +1,115 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_POINT_H +#define DRAGONBONES_POINT_H + +#include "../core/DragonBones.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The Point object represents a location in a two-dimensional coordinate system. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - Point 对象表示二维坐标系统中的某个位置。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class Point { +public: + static Point helpPointA; + static Point helpPointB; + static Point helpPointC; + static Point helpPointD; + +public: + /** + * - The horizontal coordinate. + * @default 0.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 该点的水平坐标。 + * @default 0.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float x; + /** + * - The vertical coordinate. + * @default 0.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 该点的垂直坐标。 + * @default 0.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float y; + + Point() : x(0.0f), + y(0.0f) {} + /** + * - Creates a new point. If you pass no parameters to this method, a point is created at (0,0). + * @param x - The horizontal coordinate. + * @param y - The vertical coordinate. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 创建一个 egret.Point 对象.若不传入任何参数,将会创建一个位于(0,0)位置的点。 + * @param x - 该对象的x属性值,默认为 0.0。 + * @param y - 该对象的y属性值,默认为 0.0。 + * @version DragonBones 3.0 + * @language zh_CN + */ + Point(const Point& value) { + operator=(value); + } + ~Point() {} + + inline void operator=(const Point& value) { + x = value.x; + y = value.y; + } + + /** + * @private + */ + inline void clear() { + x = y = 0.0f; + } + +public: // For WebAssembly. + static Point* getHelpPointA() { return &helpPointA; } + static Point* getHelpPointB() { return &helpPointB; } + static Point* getHelpPointC() { return &helpPointC; } + static Point* getHelpPointD() { return &helpPointD; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_POINT_H diff --git a/cocos/editor-support/dragonbones/geom/Rectangle.h b/cocos/editor-support/dragonbones/geom/Rectangle.h new file mode 100644 index 0000000..97a10b6 --- /dev/null +++ b/cocos/editor-support/dragonbones/geom/Rectangle.h @@ -0,0 +1,131 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_RECTANGLE_H +#define DRAGONBONES_RECTANGLE_H + +#include "../core/DragonBones.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - A Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its + * width and its height.
+ * The x, y, width, and height properties of the Rectangle class are independent of each other; changing the value of + * one property has no effect on the others. However, the right and bottom properties are integrally related to those + * four properties. For example, if you change the value of the right property, the value of the width property changes; + * if you change the bottom property, the value of the height property changes. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - Rectangle 对象是按其位置(由它左上角的点 (x, y) 确定)以及宽度和高度定义的区域。
+ * Rectangle 类的 x、y、width 和 height 属性相互独立;更改一个属性的值不会影响其他属性。 + * 但是,right 和 bottom 属性与这四个属性是整体相关的。例如,如果更改 right 属性的值,则 width + * 属性的值将发生变化;如果更改 bottom 属性,则 height 属性的值将发生变化。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class Rectangle { +public: + /** + * - The x coordinate of the top-left corner of the rectangle. + * @default 0.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 矩形左上角的 x 坐标。 + * @default 0.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float x; + /** + * - The y coordinate of the top-left corner of the rectangle. + * @default 0.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 矩形左上角的 y 坐标。 + * @default 0.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float y; + /** + * - The width of the rectangle, in pixels. + * @default 0.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 矩形的宽度(以像素为单位)。 + * @default 0.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float width; + /** + * - 矩形的高度(以像素为单位)。 + * @default 0.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - The height of the rectangle, in pixels. + * @default 0.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float height; + + Rectangle() : x(0.0f), + y(0.0f), + width(0.0f), + height(0.0f) {} + /** + * @private + */ + Rectangle(const Rectangle& value) { + operator=(value); + } + ~Rectangle() {} + + inline void operator=(const Rectangle& value) { + x = value.x; + y = value.y; + width = value.width; + height = value.height; + } + + /** + * @private + */ + void clear() { + x = y = 0.0f; + width = height = 0.0f; + } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_RECTANGLE_H diff --git a/cocos/editor-support/dragonbones/geom/Transform.cpp b/cocos/editor-support/dragonbones/geom/Transform.cpp new file mode 100644 index 0000000..3291964 --- /dev/null +++ b/cocos/editor-support/dragonbones/geom/Transform.cpp @@ -0,0 +1,13 @@ + +#include "Transform.h" + +DRAGONBONES_NAMESPACE_BEGIN + +const float Transform::PI = 3.14159265358979323846f; +const float Transform::PI_D = PI * 2.0f; +const float Transform::PI_H = PI * 0.5f; +const float Transform::PI_Q = PI * 0.25f; +const float Transform::DEG_RAD = PI / 180.f; +const float Transform::RAD_DEG = 180.f / PI; + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/geom/Transform.h b/cocos/editor-support/dragonbones/geom/Transform.h new file mode 100644 index 0000000..b0327bf --- /dev/null +++ b/cocos/editor-support/dragonbones/geom/Transform.h @@ -0,0 +1,269 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_TRANSFORM_H +#define DRAGONBONES_TRANSFORM_H + +#include "../core/DragonBones.h" +#include "Matrix.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - 2D Transform. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 2D 变换。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class Transform final { +public: + /** + * @private + */ + static const float PI; + /** + * @private + */ + static const float PI_D; + /** + * @private + */ + static const float PI_H; + /** + * @private + */ + static const float PI_Q; + /** + * @private + */ + static const float DEG_RAD; + /** + * @private + */ + static const float RAD_DEG; + + /** + * @private + */ + static float normalizeRadian(float value) { + value = fmod(value + Transform::PI, Transform::PI * 2.0f); + value += value > 0.0f ? -Transform::PI : Transform::PI; + + return value; + } + +public: + /** + * - Horizontal translate. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 水平位移。 + * @version DragonBones 3.0 + * @language zh_CN + */ + float x; + /** + * - Vertical translate. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 垂直位移。 + * @version DragonBones 3.0 + * @language zh_CN + */ + float y; + /** + * - Skew. (In radians) + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 倾斜。 (以弧度为单位) + * @version DragonBones 3.0 + * @language zh_CN + */ + float skew; + /** + * - rotation. (In radians) + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 旋转。 (以弧度为单位) + * @version DragonBones 3.0 + * @language zh_CN + */ + float rotation; + /** + * - Horizontal Scaling. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 水平缩放。 + * @version DragonBones 3.0 + * @language zh_CN + */ + float scaleX; + /** + * - Vertical scaling. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 垂直缩放。 + * @version DragonBones 3.0 + * @language zh_CN + */ + float scaleY; + + Transform() : x(0.0f), + y(0.0f), + skew(0.0f), + rotation(0.0f), + scaleX(1.0f), + scaleY(1.0f) {} + /** + * @private + */ + Transform(const Transform& value) { + operator=(value); + } + ~Transform() {} + + inline void operator=(const Transform& value) { + x = value.x; + y = value.y; + skew = value.skew; + rotation = value.rotation; + scaleX = value.scaleX; + scaleY = value.scaleY; + } + /** + * @private + */ + inline Transform& identity() { + x = y = skew = rotation = 0.0f; + scaleX = scaleY = 1.0f; + + return *this; + } + /** + * @private + */ + inline Transform& add(const Transform& value) { + x += value.x; + y += value.y; + skew += value.skew; + rotation += value.rotation; + scaleX *= value.scaleX; + scaleY *= value.scaleY; + + return *this; + } + /** + * @private + */ + inline Transform& minus(const Transform& value) { + x -= value.x; + y -= value.y; + skew -= value.skew; + rotation -= value.rotation; + scaleX /= value.scaleX; + scaleY /= value.scaleY; + + return *this; + } + /** + * @private + */ + inline Transform& fromMatrix(const Matrix& matrix) { + const auto backupScaleX = scaleX, backupScaleY = scaleY; + + x = matrix.tx; + y = matrix.ty; + + rotation = std::atan(matrix.b / matrix.a); + auto skewX = std::atan(-matrix.c / matrix.d); + + scaleX = (rotation > -PI_Q && rotation < PI_Q) ? matrix.a / std::cos(rotation) : matrix.b / std::sin(rotation); + scaleY = (skewX > -PI_Q && skewX < PI_Q) ? matrix.d / std::cos(skewX) : -matrix.c / std::sin(skewX); + + if (backupScaleX >= 0.0f && scaleX < 0.0f) { + scaleX = -scaleX; + rotation = rotation - PI; + } + + if (backupScaleY >= 0.0f && scaleY < 0.0f) { + scaleY = -scaleY; + skewX = skewX - PI; + } + + skew = skewX - rotation; + + return *this; + } + /** + * @private + */ + inline Transform& toMatrix(Matrix& matrix) { + if (rotation == 0.0f) { + matrix.a = 1.0f; + matrix.b = 0.0f; + } else { + matrix.a = std::cos(rotation); + matrix.b = std::sin(rotation); + } + + if (skew == 0.0f) { + matrix.c = -matrix.b; + matrix.d = matrix.a; + } else { + matrix.c = -std::sin(skew + rotation); + matrix.d = std::cos(skew + rotation); + } + + if (scaleX != 1.0f) { + matrix.a *= scaleX; + matrix.b *= scaleX; + } + + if (scaleY != 1.0f) { + matrix.c *= scaleY; + matrix.d *= scaleY; + } + + matrix.tx = x; + matrix.ty = y; + + return *this; + } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_TRANSFORM_H diff --git a/cocos/editor-support/dragonbones/model/AnimationConfig.cpp b/cocos/editor-support/dragonbones/model/AnimationConfig.cpp new file mode 100644 index 0000000..b3a2bc8 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/AnimationConfig.cpp @@ -0,0 +1,124 @@ +#include "AnimationConfig.h" +#include "../armature/Armature.h" +#include "../armature/Bone.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void AnimationConfig::_onClear() { + pauseFadeOut = true; + fadeOutMode = AnimationFadeOutMode::All; + fadeOutTweenType = TweenType::Line; + fadeOutTime = -1.0f; + + actionEnabled = true; + additiveBlending = false; + displayControl = true; + pauseFadeIn = true; + resetToPose = true; + fadeInTweenType = TweenType::Line; + playTimes = -1; + layer = 0; + position = 0.0f; + duration = -1.0f; + timeScale = -100.0f; + weight = 1.0f; + fadeInTime = -1.0f; + autoFadeOutTime = -1.0f; + name = ""; + animation = ""; + group = ""; + boneMask.clear(); +} + +void AnimationConfig::clear() { + _onClear(); +} + +void AnimationConfig::copyFrom(AnimationConfig* value) { + pauseFadeOut = value->pauseFadeOut; + fadeOutMode = value->fadeOutMode; + autoFadeOutTime = value->autoFadeOutTime; + fadeOutTweenType = value->fadeOutTweenType; + + actionEnabled = value->actionEnabled; + additiveBlending = value->additiveBlending; + displayControl = value->displayControl; + pauseFadeIn = value->pauseFadeIn; + resetToPose = value->resetToPose; + playTimes = value->playTimes; + layer = value->layer; + position = value->position; + duration = value->duration; + timeScale = value->timeScale; + weight = value->weight; + fadeInTime = value->fadeInTime; + fadeOutTime = value->fadeOutTime; + fadeInTweenType = value->fadeInTweenType; + name = value->name; + animation = value->animation; + group = value->group; + boneMask = value->boneMask; +} + +bool AnimationConfig::containsBoneMask(const std::string& boneName) const { + return boneMask.empty() || std::find(boneMask.cbegin(), boneMask.cend(), boneName) != boneMask.cend(); +} + +void AnimationConfig::addBoneMask(Armature* armature, const std::string& boneName, bool recursive) { + const auto currentBone = armature->getBone(boneName); + if (currentBone == nullptr) { + return; + } + + if (std::find(boneMask.cbegin(), boneMask.cend(), boneName) == boneMask.cend()) // Add mixing + { + boneMask.push_back(boneName); + } + + if (recursive) // Add recursive mixing. + { + for (const auto bone : armature->getBones()) { + if (std::find(boneMask.cbegin(), boneMask.cend(), bone->getName()) == boneMask.cend() && currentBone->contains(bone)) { + boneMask.push_back(bone->getName()); + } + } + } +} + +void AnimationConfig::removeBoneMask(Armature* armature, const std::string& boneName, bool recursive) { + { + auto iterator = std::find(boneMask.begin(), boneMask.end(), boneName); + if (iterator != boneMask.end()) // Remove mixing. + { + boneMask.erase(iterator); + } + } + + if (recursive) { + const auto currentBone = armature->getBone(boneName); + if (currentBone != nullptr) { + if (!boneMask.empty()) // Remove recursive mixing. + { + for (const auto bone : armature->getBones()) { + auto iterator = std::find(boneMask.begin(), boneMask.end(), bone->getName()); + if (iterator != boneMask.end() && currentBone->contains(bone)) { + boneMask.erase(iterator); + } + } + } else // Add unrecursive mixing. + { + for (const auto bone : armature->getBones()) { + if (bone == currentBone) { + continue; + } + + if (!currentBone->contains(bone)) { + boneMask.push_back(bone->getName()); + } + } + } + } + } +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/AnimationConfig.h b/cocos/editor-support/dragonbones/model/AnimationConfig.h new file mode 100644 index 0000000..727d2fd --- /dev/null +++ b/cocos/editor-support/dragonbones/model/AnimationConfig.h @@ -0,0 +1,319 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_ANIMATION_CONFIG_H +#define DRAGONBONES_ANIMATION_CONFIG_H + +#include "../core/BaseObject.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The animation config is used to describe all the information needed to play an animation state. + * The API is still in the experimental phase and may encounter bugs or stability or compatibility issues when used. + * @see dragonBones.AnimationState + * @beta + * @version DragonBones 5.0 + * @language en_US + */ +/** + * - 动画配置用来描述播放一个动画状态所需要的全部信息。 + * 该 API 仍在实验阶段,使用时可能遭遇 bug 或稳定性或兼容性问题。 + * @see dragonBones.AnimationState + * @beta + * @version DragonBones 5.0 + * @language zh_CN + */ +class AnimationConfig : public BaseObject { + BIND_CLASS_TYPE_A(AnimationConfig); + +public: + /** + * @private + */ + bool pauseFadeOut; + /** + * - Fade out the pattern of other animation states when the animation state is fade in. + * This property is typically used to specify the substitution of multiple animation states blend. + * @default dragonBones.AnimationFadeOutMode.All + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 淡入动画状态时淡出其他动画状态的模式。 + * 该属性通常用来指定多个动画状态混合时的相互替换关系。 + * @default dragonBones.AnimationFadeOutMode.All + * @version DragonBones 5.0 + * @language zh_CN + */ + AnimationFadeOutMode fadeOutMode; + /** + * @private + */ + TweenType fadeOutTweenType; + /** + * @private + */ + float fadeOutTime; + /** + * @private + */ + bool actionEnabled; + /** + * @private + */ + bool additiveBlending; + /** + * - Whether the animation state has control over the display property of the slots. + * Sometimes blend a animation state does not want it to control the display properties of the slots, + * especially if other animation state are controlling the display properties of the slots. + * @default true + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 动画状态是否对插槽的显示对象属性有控制权。 + * 有时混合一个动画状态并不希望其控制插槽的显示对象属性, + * 尤其是其他动画状态正在控制这些插槽的显示对象属性时。 + * @default true + * @version DragonBones 5.0 + * @language zh_CN + */ + bool displayControl; + /** + * @private + */ + bool pauseFadeIn; + /** + * - Whether to reset the objects without animation to the armature pose when the animation state is start to play. + * This property should usually be set to false when blend multiple animation states. + * @default true + * @version DragonBones 5.1 + * @language en_US + */ + /** + * - 开始播放动画状态时是否将没有动画的对象重置为骨架初始值。 + * 通常在混合多个动画状态时应该将该属性设置为 false。 + * @default true + * @version DragonBones 5.1 + * @language zh_CN + */ + bool resetToPose; + /** + * @private + */ + TweenType fadeInTweenType; + /** + * - The play times. [0: Loop play, [1~N]: Play N times] + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 播放次数。 [0: 无限循环播放, [1~N]: 循环播放 N 次] + * @version DragonBones 3.0 + * @language zh_CN + */ + int playTimes; + /** + * - The blend layer. + * High layer animation state will get the blend weight first. + * When the blend weight is assigned more than 1, the remaining animation states will no longer get the weight assigned. + * @readonly + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 混合图层。 + * 图层高的动画状态会优先获取混合权重。 + * 当混合权重分配超过 1 时,剩余的动画状态将不再获得权重分配。 + * @readonly + * @version DragonBones 5.0 + * @language zh_CN + */ + int layer; + /** + * - The start time of play. (In seconds) + * @default 0.0 + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 播放的开始时间。 (以秒为单位) + * @default 0.0 + * @version DragonBones 5.0 + * @language zh_CN + */ + float position; + /** + * - The duration of play. + * [-1: Use the default value of the animation data, 0: Stop play, (0~N]: The duration] (In seconds) + * @default -1.0 + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 播放的持续时间。 + * [-1: 使用动画数据默认值, 0: 动画停止, (0~N]: 持续时间] (以秒为单位) + * @default -1.0 + * @version DragonBones 5.0 + * @language zh_CN + */ + float duration; + /** + * - The play speed. + * The value is an overlay relationship with {@link dragonBones.Animation#timeScale}. + * [(-N~0): Reverse play, 0: Stop play, (0~1): Slow play, 1: Normal play, (1~N): Fast play] + * @default 1.0 + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 播放速度。 + * 该值与 {@link dragonBones.Animation#timeScale} 是叠加关系。 + * [(-N~0): 倒转播放, 0: 停止播放, (0~1): 慢速播放, 1: 正常播放, (1~N): 快速播放] + * @default 1.0 + * @version DragonBones 3.0 + * @language zh_CN + */ + float timeScale; + /** + * - The blend weight. + * @default 1.0 + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 混合权重。 + * @default 1.0 + * @version DragonBones 5.0 + * @language zh_CN + */ + float weight; + /** + * - The fade in time. + * [-1: Use the default value of the animation data, [0~N]: The fade in time] (In seconds) + * @default -1.0 + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 淡入时间。 + * [-1: 使用动画数据默认值, [0~N]: 淡入时间] (以秒为单位) + * @default -1.0 + * @version DragonBones 5.0 + * @language zh_CN + */ + float fadeInTime; + /** + * - The auto fade out time when the animation state play completed. + * [-1: Do not fade out automatically, [0~N]: The fade out time] (In seconds) + * @default -1.0 + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 动画状态播放完成后的自动淡出时间。 + * [-1: 不自动淡出, [0~N]: 淡出时间] (以秒为单位) + * @default -1.0 + * @version DragonBones 5.0 + * @language zh_CN + */ + float autoFadeOutTime; + /** + * - The name of the animation state. (Can be different from the name of the animation data) + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 动画状态名称。 (可以不同于动画数据) + * @version DragonBones 5.0 + * @language zh_CN + */ + std::string name; + /** + * - The animation data name. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 动画数据名称。 + * @version DragonBones 5.0 + * @language zh_CN + */ + std::string animation; + /** + * - The blend group name of the animation state. + * This property is typically used to specify the substitution of multiple animation states blend. + * @readonly + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 混合组名称。 + * 该属性通常用来指定多个动画状态混合时的相互替换关系。 + * @readonly + * @version DragonBones 5.0 + * @language zh_CN + */ + std::string group; + /** + * @private + */ + std::vector boneMask; + +protected: + virtual void _onClear() override; + +public: + /** + * @private + */ + void clear(); + /** + * @private + */ + void copyFrom(AnimationConfig* value); + /** + * @private + */ + bool containsBoneMask(const std::string& boneName) const; + /** + * @private + */ + void addBoneMask(Armature* armature, const std::string& boneName, bool recursive); + /** + * @private + */ + void removeBoneMask(Armature* armature, const std::string& boneName, bool recursive); + +public: // For WebAssembly. + int getFadeOutMode() const { return (int)fadeOutMode; } + void setFadeOutMode(int value) { fadeOutMode = (AnimationFadeOutMode)value; } + + int getFadeOutTweenType() const { return (int)fadeOutTweenType; } + void setFadeOutTweenType(int value) { fadeOutTweenType = (TweenType)value; } + + int getFadeInTweenType() const { return (int)fadeInTweenType; } + void setFadeInTweenType(int value) { fadeInTweenType = (TweenType)value; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_ANIMATION_CONFIG_H diff --git a/cocos/editor-support/dragonbones/model/AnimationData.cpp b/cocos/editor-support/dragonbones/model/AnimationData.cpp new file mode 100644 index 0000000..89ccde3 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/AnimationData.cpp @@ -0,0 +1,102 @@ +#include "AnimationData.h" +#include "ArmatureData.h" +#include "ConstraintData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void AnimationData::_onClear() { + for (const auto& pair : boneTimelines) { + for (const auto timeline : pair.second) { + timeline->returnToPool(); + } + } + + for (const auto& pair : slotTimelines) { + for (const auto timeline : pair.second) { + timeline->returnToPool(); + } + } + + for (const auto& pair : constraintTimelines) { + for (const auto timeline : pair.second) { + timeline->returnToPool(); + } + } + + if (actionTimeline != nullptr) { + actionTimeline->returnToPool(); + } + + if (zOrderTimeline != nullptr) { + zOrderTimeline->returnToPool(); + } + + frameIntOffset = 0; + frameFloatOffset = 0; + frameOffset = 0; + frameCount = 0; + playTimes = 0; + duration = 0.0f; + scale = 1.0f; + fadeInTime = 0.0f; + cacheFrameRate = 0.0f; + name = ""; + cachedFrames.clear(); + boneTimelines.clear(); + slotTimelines.clear(); + constraintTimelines.clear(); + boneCachedFrameIndices.clear(); + slotCachedFrameIndices.clear(); + parent = nullptr; + actionTimeline = nullptr; + zOrderTimeline = nullptr; +} + +void AnimationData::cacheFrames(unsigned frameRate) { + if (cacheFrameRate > 0.0f) // TODO clear cache. + { + return; + } + + cacheFrameRate = std::max(std::ceil(frameRate * scale), 1.0f); + const auto cacheFrameCount = std::ceil(cacheFrameRate * duration) + 1; // Cache one more frame. + + cachedFrames.resize(cacheFrameCount, false); + + for (const auto bone : parent->sortedBones) { + boneCachedFrameIndices[bone->name].resize(cacheFrameCount, -1); + } + + for (const auto slot : parent->sortedSlots) { + slotCachedFrameIndices[slot->name].resize(cacheFrameCount, -1); + } +} + +void AnimationData::addBoneTimeline(BoneData* bone, TimelineData* value) { + auto& timelines = boneTimelines[bone->name]; + if (std::find(timelines.cbegin(), timelines.cend(), value) == timelines.cend()) { + timelines.push_back(value); + } +} + +void AnimationData::addSlotTimeline(SlotData* slot, TimelineData* value) { + auto& timelines = slotTimelines[slot->name]; + if (std::find(timelines.cbegin(), timelines.cend(), value) == timelines.cend()) { + timelines.push_back(value); + } +} + +void AnimationData::addConstraintTimeline(ConstraintData* constraint, TimelineData* value) { + auto& timelines = constraintTimelines[constraint->name]; + if (std::find(timelines.cbegin(), timelines.cend(), value) == timelines.cend()) { + timelines.push_back(value); + } +} + +void TimelineData::_onClear() { + type = TimelineType::BoneAll; + offset = 0; + frameIndicesOffset = -1; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/AnimationData.h b/cocos/editor-support/dragonbones/model/AnimationData.h new file mode 100644 index 0000000..9c42fa1 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/AnimationData.h @@ -0,0 +1,246 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_ANIMATION_DATA_H +#define DRAGONBONES_ANIMATION_DATA_H + +#include "ArmatureData.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The animation data. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 动画数据。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class AnimationData : public BaseObject { + BIND_CLASS_TYPE_B(AnimationData); + +public: + /** + * - FrameIntArray. + * @internal + */ + unsigned frameIntOffset; + /** + * - FrameFloatArray. + * @internal + */ + unsigned frameFloatOffset; + /** + * - FrameArray. + * @internal + */ + unsigned frameOffset; + /** + * - The frame count of the animation. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 动画的帧数。 + * @version DragonBones 3.0 + * @language zh_CN + */ + unsigned frameCount; + /** + * - The play times of the animation. [0: Loop play, [1~N]: Play N times] + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 动画的播放次数。 [0: 无限循环播放, [1~N]: 循环播放 N 次] + * @version DragonBones 3.0 + * @language zh_CN + */ + unsigned playTimes; + /** + * - The duration of the animation. (In seconds) + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 动画的持续时间。 (以秒为单位) + * @version DragonBones 3.0 + * @language zh_CN + */ + float duration; + /** + * @private + */ + float scale; + /** + * - The fade in time of the animation. (In seconds) + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 动画的淡入时间。 (以秒为单位) + * @version DragonBones 3.0 + * @language zh_CN + */ + float fadeInTime; + /** + * @private + */ + float cacheFrameRate; + /** + * - The animation name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 动画名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::string name; + /** + * @private + */ + std::vector cachedFrames; + /** + * @private + */ + std::map> boneTimelines; + /** + * @private + */ + std::map> slotTimelines; + /** + * @private + */ + std::map> constraintTimelines; + /** + * @private + */ + std::map> boneCachedFrameIndices; + /** + * @private + */ + std::map> slotCachedFrameIndices; + /** + * @private + */ + TimelineData* actionTimeline; + /** + * @private + */ + TimelineData* zOrderTimeline; + /** + * @private + */ + ArmatureData* parent; + AnimationData() : actionTimeline(nullptr), + zOrderTimeline(nullptr) { + _onClear(); + } + ~AnimationData() { + _onClear(); + } + +protected: + virtual void _onClear() override; + +public: + /** + * @internal + */ + void cacheFrames(unsigned frameRate); + /** + * @private + */ + void addBoneTimeline(BoneData* bone, TimelineData* value); + /** + * @private + */ + void addSlotTimeline(SlotData* slot, TimelineData* value); + /** + * @private + */ + void addConstraintTimeline(ConstraintData* constraint, TimelineData* value); + /** + * @private + */ + std::vector* getBoneTimelines(const std::string& timelineName) { + return mapFindB(boneTimelines, timelineName); + } + /** + * @private + */ + inline std::vector* getSlotTimelines(const std::string& timelineName) { + return mapFindB(slotTimelines, timelineName); + } + /** + * @private + */ + inline std::vector* getConstraintTimelines(const std::string& timelineName) { + return mapFindB(constraintTimelines, timelineName); + } + /** + * @private + */ + inline std::vector* getBoneCachedFrameIndices(const std::string& boneName) { + return mapFindB(boneCachedFrameIndices, boneName); + } + /** + * @private + */ + inline std::vector* getSlotCachedFrameIndices(const std::string& slotName) { + return mapFindB(slotCachedFrameIndices, slotName); + } + +public: // For WebAssembly. + TimelineData* getActionTimeline() const { return actionTimeline; } + void setActionTimeline(TimelineData* pactionTimeline) { actionTimeline = pactionTimeline; } + + TimelineData* getZOrderTimeline() const { return zOrderTimeline; } + void setZOrderTimeline(TimelineData* value) { zOrderTimeline = value; } + + ArmatureData* getParent() const { return parent; } + void setParent(ArmatureData* value) { parent = value; } +}; +/** + * @internal + */ +class TimelineData : public BaseObject { + BIND_CLASS_TYPE_A(TimelineData); + +public: + TimelineType type; + unsigned offset; + int frameIndicesOffset; + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + int getType() const { return (int)type; } + void setType(int value) { type = (TimelineType)value; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_ANIMATION_DATA_H diff --git a/cocos/editor-support/dragonbones/model/ArmatureData.cpp b/cocos/editor-support/dragonbones/model/ArmatureData.cpp new file mode 100644 index 0000000..4767560 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/ArmatureData.cpp @@ -0,0 +1,274 @@ +#include "ArmatureData.h" +#include "AnimationData.h" +#include "CanvasData.h" +#include "ConstraintData.h" +#include "DisplayData.h" +#include "DragonBonesData.h" +#include "SkinData.h" +#include "UserData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void ArmatureData::_onClear() { + for (const auto action : defaultActions) { + action->returnToPool(); + } + + for (const auto action : actions) { + action->returnToPool(); + } + + for (const auto& pair : bones) { + pair.second->returnToPool(); + } + + for (const auto& pair : slots) { + pair.second->returnToPool(); + } + + for (const auto& pair : constraints) { + pair.second->returnToPool(); + } + + for (const auto& pair : skins) { + pair.second->returnToPool(); + } + + for (const auto& pair : animations) { + pair.second->returnToPool(); + } + + if (canvas != nullptr) { + canvas->returnToPool(); + } + + if (userData != nullptr) { + userData->returnToPool(); + } + + type = ArmatureType::Armature; + frameRate = 0; + cacheFrameRate = 0; + scale = 1.0f; + name = ""; + aabb.clear(); + animationNames.clear(); + sortedBones.clear(); + sortedSlots.clear(); + defaultActions.clear(); + actions.clear(); + bones.clear(); + slots.clear(); + constraints.clear(); + skins.clear(); + animations.clear(); + parent = nullptr; + defaultSkin = nullptr; + defaultAnimation = nullptr; + canvas = nullptr; + userData = nullptr; +} + +void ArmatureData::sortBones() { + const auto total = sortedBones.size(); + if (total <= 0) { + return; + } + + const auto sortHelper = sortedBones; // Copy. + unsigned index = 0; + unsigned count = 0; + sortedBones.clear(); + while (count < total) { + const auto bone = sortHelper[index++]; + if (index >= total) { + index = 0; + } + + if (std::find(sortedBones.cbegin(), sortedBones.cend(), bone) != sortedBones.cend()) { + continue; + } + + auto flag = false; + for (const auto& pair : constraints) { + const auto constrait = pair.second; + if (constrait->root == bone && std::find(sortedBones.cbegin(), sortedBones.cend(), constrait->target) == sortedBones.cend()) { + flag = true; + break; + } + } + + if (flag) { + continue; + } + + if (bone->parent != nullptr && std::find(sortedBones.cbegin(), sortedBones.cend(), bone->parent) == sortedBones.cend()) { + continue; + } + + sortedBones.push_back(bone); + count++; + } +} + +void ArmatureData::cacheFrames(unsigned value) { + if (cacheFrameRate > value) // TODO clear cache. + { + return; + } + + cacheFrameRate = value; + for (const auto& pair : animations) { + pair.second->cacheFrames(cacheFrameRate); + } +} + +int ArmatureData::setCacheFrame(const Matrix& globalTransformMatrix, const Transform& transform) { + auto& dataArray = *&parent->cachedFrames; + auto arrayOffset = dataArray.size(); + + dataArray.resize(arrayOffset + 10); + dataArray[arrayOffset] = globalTransformMatrix.a; + dataArray[arrayOffset + 1] = globalTransformMatrix.b; + dataArray[arrayOffset + 2] = globalTransformMatrix.c; + dataArray[arrayOffset + 3] = globalTransformMatrix.d; + dataArray[arrayOffset + 4] = globalTransformMatrix.tx; + dataArray[arrayOffset + 5] = globalTransformMatrix.ty; + dataArray[arrayOffset + 6] = transform.rotation; + dataArray[arrayOffset + 7] = transform.skew; + dataArray[arrayOffset + 8] = transform.scaleX; + dataArray[arrayOffset + 9] = transform.scaleY; + + return arrayOffset; +} + +void ArmatureData::getCacheFrame(Matrix& globalTransformMatrix, Transform& transform, unsigned arrayOffset) const { + auto& dataArray = *&parent->cachedFrames; + globalTransformMatrix.a = dataArray[arrayOffset]; + globalTransformMatrix.b = dataArray[arrayOffset + 1]; + globalTransformMatrix.c = dataArray[arrayOffset + 2]; + globalTransformMatrix.d = dataArray[arrayOffset + 3]; + globalTransformMatrix.tx = dataArray[arrayOffset + 4]; + globalTransformMatrix.ty = dataArray[arrayOffset + 5]; + transform.rotation = dataArray[arrayOffset + 6]; + transform.skew = dataArray[arrayOffset + 7]; + transform.scaleX = dataArray[arrayOffset + 8]; + transform.scaleY = dataArray[arrayOffset + 9]; + transform.x = globalTransformMatrix.tx; + transform.y = globalTransformMatrix.ty; +} + +void ArmatureData::addBone(BoneData* value) { + if (bones.find(value->name) != bones.cend()) { + DRAGONBONES_ASSERT(false, "Same bone: " + value->name); + return; + } + + bones[value->name] = value; + sortedBones.push_back(value); +} + +void ArmatureData::addSlot(SlotData* value) { + if (slots.find(value->name) != slots.cend()) { + DRAGONBONES_ASSERT(false, "Same slot: " + value->name); + return; + } + + slots[value->name] = value; + sortedSlots.push_back(value); +} + +void ArmatureData::addConstraint(ConstraintData* value) { + if (constraints.find(value->name) != constraints.cend()) { + DRAGONBONES_ASSERT(false, "Same constaint: " + value->name); + return; + } + + constraints[value->name] = value; +} + +void ArmatureData::addSkin(SkinData* value) { + if (skins.find(value->name) != skins.cend()) { + DRAGONBONES_ASSERT(false, "Same skin: " + value->name); + return; + } + + value->parent = this; + skins[value->name] = value; + + if (defaultSkin == nullptr) { + defaultSkin = value; + } +} + +void ArmatureData::addAnimation(AnimationData* value) { + if (animations.find(value->name) != animations.cend()) { + DRAGONBONES_ASSERT(false, "Same animation: " + value->name); + return; + } + + value->parent = this; + animations[value->name] = value; + animationNames.push_back(value->name); + if (defaultAnimation == nullptr) { + defaultAnimation = value; + } +} + +void ArmatureData::addAction(ActionData* value, bool isDefault) { + if (isDefault) { + defaultActions.push_back(value); + } else { + actions.push_back(value); + } +} + +MeshDisplayData* ArmatureData::getMesh(const std::string& skinName, const std::string& slotName, const std::string& meshName) const { + const auto skin = getSkin(skinName); + if (skin == nullptr) { + return nullptr; + } + + return static_cast(skin->getDisplay(slotName, meshName)); +} + +void BoneData::_onClear() { + if (userData != nullptr) { + userData->returnToPool(); + } + + inheritTranslation = false; + inheritRotation = false; + inheritScale = false; + inheritReflection = false; + length = 0.0f; + name = ""; + transform.identity(); + parent = nullptr; + userData = nullptr; +} + +ColorTransform SlotData::DEFAULT_COLOR; +ColorTransform* SlotData::createColor() { + return new ColorTransform(); +} + +void SlotData::_onClear() { + if (userData != nullptr) { + userData->returnToPool(); + } + + if (color != nullptr && color != &DEFAULT_COLOR) { + delete color; + } + + blendMode = BlendMode::Normal; + displayIndex = 0; + zOrder = 0; + name = ""; + parent = nullptr; + color = nullptr; + userData = nullptr; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/ArmatureData.h b/cocos/editor-support/dragonbones/model/ArmatureData.h new file mode 100644 index 0000000..659448a --- /dev/null +++ b/cocos/editor-support/dragonbones/model/ArmatureData.h @@ -0,0 +1,496 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_ARMATURE_DATA_H +#define DRAGONBONES_ARMATURE_DATA_H + +#include "../core/BaseObject.h" +#include "../geom/ColorTransform.h" +#include "../geom/Matrix.h" +#include "../geom/Rectangle.h" +#include "../geom/Transform.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The armature data. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 骨架数据。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class ArmatureData : public BaseObject { + BIND_CLASS_TYPE_B(ArmatureData); + +public: + /** + * @private + */ + ArmatureType type; + /** + * - The animation frame rate. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 动画帧率。 + * @version DragonBones 3.0 + * @language zh_CN + */ + unsigned frameRate; + /** + * @private + */ + unsigned cacheFrameRate; + /** + * @private + */ + float scale; + /** + * - The armature name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 骨架名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::string name; + /** + * @private + */ + Rectangle aabb; + /** + * - The names of all the animation data. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 所有的动画数据名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::vector animationNames; + /** + * @private + */ + std::vector sortedBones; + /** + * @private + */ + std::vector sortedSlots; + /** + * @private + */ + std::vector defaultActions; + /** + * @private + */ + std::vector actions; + /** + * @private + */ + std::map bones; + /** + * @private + */ + std::map slots; + /** + * @private + */ + std::map constraints; + /** + * @private + */ + std::map skins; + /** + * @private + */ + std::map animations; + /** + * - The default skin data. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 默认插槽数据。 + * @version DragonBones 4.5 + * @language zh_CN + */ + SkinData* defaultSkin; + /** + * - The default animation data. + * @version DragonBones 4.5 + * @language en_US + */ + /** + * - 默认动画数据。 + * @version DragonBones 4.5 + * @language zh_CN + */ + AnimationData* defaultAnimation; + /** + * @private + */ + CanvasData* canvas; + /** + * @private + */ + UserData* userData; + /** + * @private + */ + DragonBonesData* parent; + ArmatureData() : canvas(nullptr), + userData(nullptr) { + _onClear(); + } + ~ArmatureData() { + _onClear(); + } + +protected: + virtual void _onClear() override; + +public: + /** + * @internal + */ + void sortBones(); + /** + * @internal + */ + void cacheFrames(unsigned frameRate); + /** + * @internal + */ + int setCacheFrame(const Matrix& globalTransformMatrix, const Transform& transform); + /** + * @internal + */ + void getCacheFrame(Matrix& globalTransformMatrix, Transform& transform, unsigned arrayOffset) const; + /** + * @internal + */ + void addBone(BoneData* value); + /** + * @internal + */ + void addSlot(SlotData* value); + /** + * @internal + */ + void addConstraint(ConstraintData* value); + /** + * @internal + */ + void addSkin(SkinData* value); + /** + * @internal + */ + void addAnimation(AnimationData* value); + /** + * @internal + */ + void addAction(ActionData* value, bool isDefault); + /** + * - Get a specific done data. + * @param boneName - The bone name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取特定的骨骼数据。 + * @param boneName - 骨骼名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline BoneData* getBone(const std::string& boneName) const { + return mapFind(bones, boneName); + } + /** + * - Get a specific slot data. + * @param slotName - The slot name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取特定的插槽数据。 + * @param slotName - 插槽名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline SlotData* getSlot(const std::string& slotName) const { + return mapFind(slots, slotName); + } + /** + * @private + */ + inline ConstraintData* getConstraint(const std::string& constraintName) const { + return mapFind(constraints, constraintName); + } + /** + * - Get a specific skin data. + * @param skinName - The skin name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取特定皮肤数据。 + * @param skinName - 皮肤名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline SkinData* getSkin(const std::string& skinName) const { + return mapFind(skins, skinName); + } + /** + * @private + */ + MeshDisplayData* getMesh(const std::string& skinName, const std::string& slotName, const std::string& meshName) const; + /** + * - Get a specific animation data. + * @param animationName - The animation animationName. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取特定的动画数据。 + * @param animationName - 动画名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline AnimationData* getAnimation(const std::string& animationName) const { + return mapFind(animations, animationName); + } + +public: // For WebAssembly. + int getType() const { return (int)type; } + void setType(int value) { type = (ArmatureType)value; } + + Rectangle* getAABB() { return &aabb; } + const std::vector& getAnimationNames() const { return animationNames; } + const std::vector& getSortedBones() const { return sortedBones; } + const std::vector& getSortedSlots() const { return sortedSlots; } + const std::vector& getDefaultActions() const { return defaultActions; } + const std::vector& getActions() const { return actions; } + + SkinData* getDefaultSkin() const { return defaultSkin; } + void setDefaultSkin(SkinData* value) { defaultSkin = value; } + + AnimationData* getDefaultAnimation() const { return defaultAnimation; } + void setDefaultAnimation(AnimationData* value) { defaultAnimation = value; } + + const UserData* getUserData() const { return userData; } + void setUserData(UserData* value) { userData = value; } + + const DragonBonesData* getParent() const { return parent; } + void setParent(DragonBonesData* value) { parent = value; } +}; +/** + * - The bone data. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 骨骼数据。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class BoneData : public BaseObject { + BIND_CLASS_TYPE_B(BoneData); + +public: + /** + * @private + */ + bool inheritTranslation; + /** + * @private + */ + bool inheritRotation; + /** + * @private + */ + bool inheritScale; + /** + * @private + */ + bool inheritReflection; + /** + * - The bone length. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 骨骼长度。 + * @version DragonBones 3.0 + * @language zh_CN + */ + float length; + /** + * - The bone name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 骨骼名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::string name; + /** + * @private + */ + Transform transform; + /** + * @private + */ + UserData* userData; + /** + * - The parent bone data. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 父骨骼数据。 + * @version DragonBones 3.0 + * @language zh_CN + */ + BoneData* parent; + BoneData() : userData(nullptr) { + _onClear(); + } + ~BoneData() { + _onClear(); + } + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + Transform* getTransfrom() { return &transform; } + + const UserData* getUserData() const { return userData; } + void setUserData(UserData* value) { userData = value; } + + const BoneData* getParent() const { return parent; } + void setParent(BoneData* value) { parent = value; } +}; +/** + * - The slot data. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 插槽数据。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class SlotData : public BaseObject { + BIND_CLASS_TYPE_B(SlotData); + +public: + /** + * @internal + */ + static ColorTransform DEFAULT_COLOR; + /** + * @internal + */ + static ColorTransform* createColor(); + +public: + /** + * @private + */ + BlendMode blendMode; + /** + * @private + */ + int displayIndex; + /** + * @private + */ + int zOrder; + /** + * - The slot name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 插槽名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::string name; + /** + * @private + */ + ColorTransform* color; + /** + * @private + */ + UserData* userData; + /** + * - The parent bone data. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 父骨骼数据。 + * @version DragonBones 3.0 + * @language zh_CN + */ + BoneData* parent; + SlotData() : color(nullptr), + userData(nullptr) { + _onClear(); + } + ~SlotData() { + _onClear(); + } + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + static ColorTransform* getDefaultColor() { return &DEFAULT_COLOR; } + + int getBlendMode() const { return (int)blendMode; } + void setBlendMode(int value) { blendMode = (BlendMode)value; } + + ColorTransform* getColor() const { return color; } + void setColor(ColorTransform* value) { color = value; } + + const BoneData* getParent() const { return parent; } + void setParent(BoneData* value) { parent = value; } + + const UserData* getUserData() const { return userData; } + void setUserData(UserData* value) { userData = value; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_ARMATURE_DATA_H diff --git a/cocos/editor-support/dragonbones/model/BoundingBoxData.cpp b/cocos/editor-support/dragonbones/model/BoundingBoxData.cpp new file mode 100644 index 0000000..d026c84 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/BoundingBoxData.cpp @@ -0,0 +1,536 @@ + +#include "BoundingBoxData.h" +#include "DisplayData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void BoundingBoxData::_onClear() { + color = 0x000000; + width = 0.0f; + height = 0.0f; +} + +int RectangleBoundingBoxData::_computeOutCode(float x, float y, float xMin, float yMin, float xMax, float yMax) { + int code = OutCode::InSide; // initialised as being inside of [[clip window]] + + if (x < xMin) // to the left of clip window + { + code |= OutCode::Left; + } else if (x > xMax) // to the right of clip window + { + code |= OutCode::Right; + } + + if (y < yMin) // below the clip window + { + code |= OutCode::Top; + } else if (y > yMax) // above the clip window + { + code |= OutCode::Bottom; + } + + return code; +} + +int RectangleBoundingBoxData::rectangleIntersectsSegment( + float xA, float yA, float xB, float yB, + float xMin, float yMin, float xMax, float yMax, + Point* intersectionPointA, + Point* intersectionPointB, + Point* normalRadians) { + const auto inSideA = xA > xMin && xA < xMax && yA > yMin && yA < yMax; + const auto inSideB = xB > xMin && xB < xMax && yB > yMin && yB < yMax; + + if (inSideA && inSideB) { + return -1; + } + + auto intersectionCount = 0; + auto outcode0 = RectangleBoundingBoxData::_computeOutCode(xA, yA, xMin, yMin, xMax, yMax); + auto outcode1 = RectangleBoundingBoxData::_computeOutCode(xB, yB, xMin, yMin, xMax, yMax); + + while (true) { + if ((outcode0 | outcode1) == 0) // Bitwise OR is 0. Trivially accept and get out of loop + { + intersectionCount = 2; + break; + } else if ((outcode0 & outcode1) != 0) // Bitwise AND is not 0. Trivially reject and get out of loop + { + break; + } + + // failed both tests, so calculate the line segment to clip + // from an outside point to an intersection with clip edge + auto x = 0.0f; + auto y = 0.0f; + auto normalRadian = 0.0f; + + // At least one endpoint is outside the clip rectangle; pick it. + const auto outcodeOut = outcode0 != 0 ? outcode0 : outcode1; + + // Now find the intersection point; + if ((outcodeOut & OutCode::Top) != 0) // point is above the clip rectangle + { + x = xA + (xB - xA) * (yMin - yA) / (yB - yA); + y = yMin; + + if (normalRadians != nullptr) { + normalRadian = -Transform::PI * 0.5f; + } + } else if ((outcodeOut & OutCode::Bottom) != 0) // point is below the clip rectangle + { + x = xA + (xB - xA) * (yMax - yA) / (yB - yA); + y = yMax; + + if (normalRadians != nullptr) { + normalRadian = Transform::PI * 0.5; + } + } else if ((outcodeOut & OutCode::Right) != 0) // point is to the right of clip rectangle + { + y = yA + (yB - yA) * (xMax - xA) / (xB - xA); + x = xMax; + + if (normalRadians != nullptr) { + normalRadian = 0; + } + } else if ((outcodeOut & OutCode::Left) != 0) // point is to the left of clip rectangle + { + y = yA + (yB - yA) * (xMin - xA) / (xB - xA); + x = xMin; + + if (normalRadians != nullptr) { + normalRadian = Transform::PI; + } + } + + // Now we move outside point to intersection point to clip + // and get ready for next pass. + if (outcodeOut == outcode0) { + xA = x; + yA = y; + outcode0 = RectangleBoundingBoxData::_computeOutCode(xA, yA, xMin, yMin, xMax, yMax); + + if (normalRadians != nullptr) { + normalRadians->x = normalRadian; + } + } else { + xB = x; + yB = y; + outcode1 = RectangleBoundingBoxData::_computeOutCode(xB, yB, xMin, yMin, xMax, yMax); + + if (normalRadians != nullptr) { + normalRadians->y = normalRadian; + } + } + } + + if (intersectionCount) { + if (inSideA) { + intersectionCount = 2; // 10 + + if (intersectionPointA != nullptr) { + intersectionPointA->x = xB; + intersectionPointA->y = yB; + } + + if (intersectionPointB != nullptr) { + intersectionPointB->x = xB; + intersectionPointB->y = xB; + } + + if (normalRadians != nullptr) { + normalRadians->x = normalRadians->y + Transform::PI; + } + } else if (inSideB) { + intersectionCount = 1; // 01 + + if (intersectionPointA != nullptr) { + intersectionPointA->x = xA; + intersectionPointA->y = yA; + } + + if (intersectionPointB != nullptr) { + intersectionPointB->x = xA; + intersectionPointB->y = yA; + } + + if (normalRadians != nullptr) { + normalRadians->y = normalRadians->x + Transform::PI; + } + } else { + intersectionCount = 3; // 11 + if (intersectionPointA != nullptr) { + intersectionPointA->x = xA; + intersectionPointA->y = yA; + } + + if (intersectionPointB != nullptr) { + intersectionPointB->x = xB; + intersectionPointB->y = yB; + } + } + } + + return intersectionCount; +} + +void RectangleBoundingBoxData::_onClear() { + BoundingBoxData::_onClear(); + + type = BoundingBoxType::Rectangle; +} + +bool RectangleBoundingBoxData::containsPoint(float pX, float pY) { + const auto widthH = width * 0.5f; + if (pX >= -widthH && pX <= widthH) { + const auto heightH = height * 0.5f; + if (pY >= -heightH && pY <= heightH) { + return true; + } + } + + return false; +} + +int RectangleBoundingBoxData::intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA, + Point* intersectionPointB, + Point* normalRadians) { + const auto widthH = width * 0.5f; + const auto heightH = height * 0.5f; + const auto intersectionCount = RectangleBoundingBoxData::rectangleIntersectsSegment( + xA, yA, xB, yB, + -widthH, -heightH, widthH, heightH, + intersectionPointA, intersectionPointB, normalRadians); + + return intersectionCount; +} + +int EllipseBoundingBoxData::ellipseIntersectsSegment( + float xA, float yA, float xB, float yB, + float xC, float yC, float widthH, float heightH, + Point* intersectionPointA, + Point* intersectionPointB, + Point* normalRadians) { + const auto d = widthH / heightH; + const auto dd = d * d; + + yA *= d; + yB *= d; + + const auto dX = xB - xA; + const auto dY = yB - yA; + const auto lAB = sqrt(dX * dX + dY * dY); + const auto xD = dX / lAB; + const auto yD = dY / lAB; + const auto a = (xC - xA) * xD + (yC - yA) * yD; + const auto aa = a * a; + const auto ee = xA * xA + yA * yA; + const auto rr = widthH * widthH; + const auto dR = rr - ee + aa; + auto intersectionCount = 0; + + if (dR >= 0.0f) { + const auto dT = sqrt(dR); + const auto sA = a - dT; + const auto sB = a + dT; + const auto inSideA = sA < 0.0f ? -1 : (sA <= lAB ? 0 : 1); + const auto inSideB = sB < 0.0f ? -1 : (sB <= lAB ? 0 : 1); + const auto sideAB = inSideA * inSideB; + + if (sideAB < 0) { + return -1; + } else if (sideAB == 0) { + if (inSideA == -1) { + intersectionCount = 2; // 10 + xB = xA + sB * xD; + yB = (yA + sB * yD) / d; + + if (intersectionPointA != nullptr) { + intersectionPointA->x = xB; + intersectionPointA->y = yB; + } + + if (intersectionPointB != nullptr) { + intersectionPointB->x = xB; + intersectionPointB->y = yB; + } + + if (normalRadians != nullptr) { + normalRadians->x = atan2(yB / rr * dd, xB / rr); + normalRadians->y = normalRadians->x + Transform::PI; + } + } else if (inSideB == 1) { + intersectionCount = 1; // 01 + xA = xA + sA * xD; + yA = (yA + sA * yD) / d; + + if (intersectionPointA != nullptr) { + intersectionPointA->x = xA; + intersectionPointA->y = yA; + } + + if (intersectionPointB != nullptr) { + intersectionPointB->x = xA; + intersectionPointB->y = yA; + } + + if (normalRadians != nullptr) { + normalRadians->x = atan2(yA / rr * dd, xA / rr); + normalRadians->y = normalRadians->x + Transform::PI; + } + } else { + intersectionCount = 3; // 11 + + if (intersectionPointA != nullptr) { + intersectionPointA->x = xA + sA * xD; + intersectionPointA->y = (yA + sA * yD) / d; + + if (normalRadians != nullptr) { + normalRadians->x = atan2(intersectionPointA->y / rr * dd, intersectionPointA->x / rr); + } + } + + if (intersectionPointB != nullptr) { + intersectionPointB->x = xA + sB * xD; + intersectionPointB->y = (yA + sB * yD) / d; + + if (normalRadians != nullptr) { + normalRadians->y = atan2(intersectionPointB->y / rr * dd, intersectionPointB->x / rr); + } + } + } + } + } + + return intersectionCount; +} + +void EllipseBoundingBoxData::_onClear() + +{ + BoundingBoxData::_onClear(); + + type = BoundingBoxType::Ellipse; +} + +bool EllipseBoundingBoxData::containsPoint(float pX, float pY) { + const auto widthH = width * 0.5f; + if (pX >= -widthH && pX <= widthH) { + const auto heightH = height * 0.5f; + if (pY >= -heightH && pY <= heightH) { + pY *= widthH / heightH; + return sqrt(pX * pX + pY * pY) <= widthH; + } + } + + return false; +} + +int EllipseBoundingBoxData::intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA, + Point* intersectionPointB, + Point* normalRadians) { + const auto intersectionCount = EllipseBoundingBoxData::ellipseIntersectsSegment( + xA, yA, xB, yB, + 0.0f, 0.0f, width * 0.5f, height * 0.5f, + intersectionPointA, intersectionPointB, normalRadians); + + return intersectionCount; +} + +int PolygonBoundingBoxData::polygonIntersectsSegment( + float xA, float yA, float xB, float yB, + const std::vector& vertices, + Point* intersectionPointA, + Point* intersectionPointB, + Point* normalRadians) { + if (xA == xB) { + xA = xB + 0.000001f; + } + + if (yA == yB) { + yA = yB + 0.000001f; + } + + const auto count = vertices.size(); + const auto dXAB = xA - xB; + const auto dYAB = yA - yB; + const auto llAB = xA * yB - yA * xB; + auto intersectionCount = 0; + auto xC = vertices[count - 2]; + auto yC = vertices[count - 1]; + auto dMin = 0.0f; + auto dMax = 0.0f; + auto xMin = 0.0f; + auto yMin = 0.0f; + auto xMax = 0.0f; + auto yMax = 0.0f; + + for (std::size_t i = 0; i < count; i += 2) { + const auto xD = vertices[i]; + const auto yD = vertices[i + 1]; + + if (xC == xD) { + xC = xD + 0.000001f; + } + + if (yC == yD) { + yC = yD + 0.000001f; + } + + const auto dXCD = xC - xD; + const auto dYCD = yC - yD; + const auto llCD = xC * yD - yC * xD; + const auto ll = dXAB * dYCD - dYAB * dXCD; + const auto x = (llAB * dXCD - dXAB * llCD) / ll; + + if (((x >= xC && x <= xD) || (x >= xD && x <= xC)) && (dXAB == 0.0f || (x >= xA && x <= xB) || (x >= xB && x <= xA))) { + const auto y = (llAB * dYCD - dYAB * llCD) / ll; + if (((y >= yC && y <= yD) || (y >= yD && y <= yC)) && (dYAB == 0.0f || (y >= yA && y <= yB) || (y >= yB && y <= yA))) { + if (intersectionPointB != nullptr) { + float d = x - xA; + if (d < 0.0f) { + d = -d; + } + + if (intersectionCount == 0) { + dMin = d; + dMax = d; + xMin = x; + yMin = y; + xMax = x; + yMax = y; + + if (normalRadians != nullptr) { + normalRadians->x = atan2(yD - yC, xD - xC) - Transform::PI * 0.5f; + normalRadians->y = normalRadians->x; + } + } else { + if (d < dMin) { + dMin = d; + xMin = x; + yMin = y; + + if (normalRadians != nullptr) { + normalRadians->x = atan2(yD - yC, xD - xC) - Transform::PI * 0.5f; + } + } + + if (d > dMax) { + dMax = d; + xMax = x; + yMax = y; + + if (normalRadians != nullptr) { + normalRadians->y = atan2(yD - yC, xD - xC) - Transform::PI * 0.5f; + } + } + } + + intersectionCount++; + } else { + xMin = x; + yMin = y; + xMax = x; + yMax = y; + intersectionCount++; + + if (normalRadians != nullptr) { + normalRadians->x = atan2(yD - yC, xD - xC) - Transform::PI * 0.5f; + normalRadians->y = normalRadians->x; + } + break; + } + } + } + + xC = xD; + yC = yD; + } + + if (intersectionCount == 1) { + if (intersectionPointA != nullptr) { + intersectionPointA->x = xMin; + intersectionPointA->y = yMin; + } + + if (intersectionPointB != nullptr) { + intersectionPointB->x = xMin; + intersectionPointB->y = yMin; + } + + if (normalRadians != nullptr) { + normalRadians->y = normalRadians->x + Transform::PI; + } + } else if (intersectionCount > 1) { + intersectionCount++; + + if (intersectionPointA != nullptr) { + intersectionPointA->x = xMin; + intersectionPointA->y = yMin; + } + + if (intersectionPointB != nullptr) { + intersectionPointB->x = xMax; + intersectionPointB->y = yMax; + } + } + + return intersectionCount; +} + +void PolygonBoundingBoxData::_onClear() { + BoundingBoxData::_onClear(); + + if (weight != nullptr) { + weight->returnToPool(); + } + + type = BoundingBoxType::Polygon; + x = 0.0f; + y = 0.0f; + vertices.clear(); + weight = nullptr; +} + +bool PolygonBoundingBoxData::containsPoint(float pX, float pY) { + auto isInSide = false; + if (pX >= x && pX <= width && pY >= y && pY <= height) { + for (std::size_t i = 0, l = vertices.size(), iP = l - 2; i < l; i += 2) { + const auto yA = vertices[iP + 1]; + const auto yB = vertices[i + 1]; + if ((yB < pY && yA >= pY) || (yA < pY && yB >= pY)) { + const auto xA = vertices[iP]; + const auto xB = vertices[i]; + if ((pY - yB) * (xA - xB) / (yA - yB) + xB < pX) { + isInSide = !isInSide; + } + } + + iP = i; + } + } + + return isInSide; +} + +int PolygonBoundingBoxData::intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA, + Point* intersectionPointB, + Point* normalRadians) { + auto intersectionCount = 0; + if (RectangleBoundingBoxData::rectangleIntersectsSegment(xA, yA, xB, yB, x, y, x + width, y + height, nullptr, nullptr, nullptr) != 0) { + intersectionCount = PolygonBoundingBoxData::polygonIntersectsSegment( + xA, yA, xB, yB, + vertices, + intersectionPointA, intersectionPointB, normalRadians); + } + + return intersectionCount; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/BoundingBoxData.h b/cocos/editor-support/dragonbones/model/BoundingBoxData.h new file mode 100644 index 0000000..5a49816 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/BoundingBoxData.h @@ -0,0 +1,275 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONESCPP_BOUNDINGBOXDATA_H +#define DRAGONBONESCPP_BOUNDINGBOXDATA_H + +#include "../core/BaseObject.h" +#include "../geom/Point.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The base class of bounding box data. + * @see dragonBones.RectangleData + * @see dragonBones.EllipseData + * @see dragonBones.PolygonData + * @version DragonBones 5.0 + * @language en_US + */ +/** + * - 边界框数据基类。 + * @see dragonBones.RectangleData + * @see dragonBones.EllipseData + * @see dragonBones.PolygonData + * @version DragonBones 5.0 + * @language zh_CN + */ +class BoundingBoxData : public BaseObject { + ABSTRACT_CLASS(BoundingBoxData); + +public: + /** + * - The bounding box type. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 边界框类型。 + * @version DragonBones 5.0 + * @language zh_CN + */ + BoundingBoxType type; + /** + * @private + */ + unsigned color; + /** + * @private + */ + float width; + /** + * @private + */ + float height; + +protected: + virtual void _onClear() override; + +public: + /** + * - Check whether the bounding box contains a specific point. (Local coordinate system) + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 检查边界框是否包含特定点。(本地坐标系) + * @version DragonBones 5.0 + * @language zh_CN + */ + virtual bool containsPoint(float pX, float pY) = 0; + /** + * - Check whether the bounding box intersects a specific segment. (Local coordinate system) + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 检查边界框是否与特定线段相交。(本地坐标系) + * @version DragonBones 5.0 + * @language zh_CN + */ + virtual int intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA = nullptr, + Point* intersectionPointB = nullptr, + Point* normalRadians = nullptr) = 0; + +public: // For WebAssembly. + int getType() const { return (int)type; } + void setType(int value) { type = (BoundingBoxType)value; } +}; +/** + * - The rectangle bounding box data. + * @version DragonBones 5.1 + * @language en_US + */ +/** + * - 矩形边界框数据。 + * @version DragonBones 5.1 + * @language zh_CN + */ +class RectangleBoundingBoxData : public BoundingBoxData { + BIND_CLASS_TYPE_A(RectangleBoundingBoxData); + +private: + enum OutCode { + InSide = 0, // 0000 + Left = 1, // 0001 + Right = 2, // 0010 + Top = 4, // 0100 + Bottom = 8 // 1000 + }; + /** + * - Compute the bit code for a point (x, y) using the clip rectangle + */ + static int _computeOutCode(float x, float y, float xMin, float yMin, float xMax, float yMax); + +public: + /** + * @private + */ + static int rectangleIntersectsSegment( + float xA, float yA, float xB, float yB, + float xMin, float yMin, float xMax, float yMax, + Point* intersectionPointA = nullptr, + Point* intersectionPointB = nullptr, + Point* normalRadians = nullptr); + /** + * @inheritDoc + */ + virtual bool containsPoint(float pX, float pY) override; + /** + * @inheritDoc + */ + virtual int intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA = nullptr, + Point* intersectionPointB = nullptr, + Point* normalRadians = nullptr) override; + +protected: + virtual void _onClear() override; +}; +/** + * - The ellipse bounding box data. + * @version DragonBones 5.1 + * @language en_US + */ +/** + * - 椭圆边界框数据。 + * @version DragonBones 5.1 + * @language zh_CN + */ +class EllipseBoundingBoxData : public BoundingBoxData { + BIND_CLASS_TYPE_A(EllipseBoundingBoxData); + +public: + /** + * @private + */ + static int ellipseIntersectsSegment( + float xA, float yA, float xB, float yB, + float xC, float yC, float widthH, float heightH, + Point* intersectionPointA = nullptr, + Point* intersectionPointB = nullptr, + Point* normalRadians = nullptr); + /** + * @inheritDoc + */ + virtual bool containsPoint(float pX, float pY) override; + /** + * @inheritDoc + */ + virtual int intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA = nullptr, + Point* intersectionPointB = nullptr, + Point* normalRadians = nullptr) override; + +protected: + virtual void _onClear() override; +}; +/** + * - The polygon bounding box data. + * @version DragonBones 5.1 + * @language en_US + */ +/** + * - 多边形边界框数据。 + * @version DragonBones 5.1 + * @language zh_CN + */ +class PolygonBoundingBoxData : public BoundingBoxData { + BIND_CLASS_TYPE_B(PolygonBoundingBoxData); + +public: + /** + * @private + */ + static int polygonIntersectsSegment( + float xA, float yA, float xB, float yB, + const std::vector& vertices, + Point* intersectionPointA = nullptr, + Point* intersectionPointB = nullptr, + Point* normalRadians = nullptr); + /** + * @private + */ + float x; + /** + * @private + */ + float y; + /** + * - The polygon vertices. + * @version DragonBones 5.1 + * @language en_US + */ + /** + * - 多边形顶点。 + * @version DragonBones 5.1 + * @language zh_CN + */ + std::vector vertices; + WeightData* weight; + /** + * @inheritDoc + */ + virtual bool containsPoint(float pX, float pY) override; + /** + * @inheritDoc + */ + virtual int intersectsSegment( + float xA, float yA, float xB, float yB, + Point* intersectionPointA = nullptr, + Point* intersectionPointB = nullptr, + Point* normalRadians = nullptr) override; + + PolygonBoundingBoxData() : weight(nullptr) { + _onClear(); + } + ~PolygonBoundingBoxData() { + _onClear(); + } + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + std::vector* getVertices() { return &vertices; } + + /*WeightData* getWeight() const { return weight; } + void setWeight(WeightData* value) { weight = value; } + */ +}; + +DRAGONBONES_NAMESPACE_END +#endif //DRAGONBONESCPP_BOUNDINGBOXDATA_H diff --git a/cocos/editor-support/dragonbones/model/CanvasData.cpp b/cocos/editor-support/dragonbones/model/CanvasData.cpp new file mode 100644 index 0000000..1a7565c --- /dev/null +++ b/cocos/editor-support/dragonbones/model/CanvasData.cpp @@ -0,0 +1,11 @@ +#include "CanvasData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void CanvasData::_onClear() { + hasBackground = false; + color = 0x000000; + aabb.clear(); +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/CanvasData.h b/cocos/editor-support/dragonbones/model/CanvasData.h new file mode 100644 index 0000000..2a7df32 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/CanvasData.h @@ -0,0 +1,45 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_CANVAS_DATA_H +#define DRAGONBONES_CANVAS_DATA_H + +#include "../core/BaseObject.h" +#include "../geom/Rectangle.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * @internal + */ +class CanvasData : public BaseObject { + BIND_CLASS_TYPE_A(CanvasData); + +public: + bool hasBackground; + unsigned color; + Rectangle aabb; + +protected: + virtual void _onClear() override; +}; +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_CANVAS_DATA_H diff --git a/cocos/editor-support/dragonbones/model/ConstraintData.cpp b/cocos/editor-support/dragonbones/model/ConstraintData.cpp new file mode 100644 index 0000000..79298ac --- /dev/null +++ b/cocos/editor-support/dragonbones/model/ConstraintData.cpp @@ -0,0 +1,25 @@ +// +// Created by liangshuochen on 09/06/2017. +// + +#include "ConstraintData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void ConstraintData::_onClear() { + order = 0; + name = ""; + target = nullptr; + root = nullptr; + bone = nullptr; +} + +void IKConstraintData::_onClear() { + ConstraintData::_onClear(); + + scaleEnabled = false; + bendPositive = false; + weight = 1.0f; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/ConstraintData.h b/cocos/editor-support/dragonbones/model/ConstraintData.h new file mode 100644 index 0000000..3dbc1f4 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/ConstraintData.h @@ -0,0 +1,75 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +// +// Created by liangshuochen on 09/06/2017. +// + +#ifndef DRAGONBONESCPP_CONSTRAINTDATA_H +#define DRAGONBONESCPP_CONSTRAINTDATA_H + +#include "../core/BaseObject.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * @internal + */ +class ConstraintData : public BaseObject { + ABSTRACT_CLASS(ConstraintData) + +public: + int order; + std::string name; + const BoneData* target; + const BoneData* root; + const BoneData* bone; + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + const BoneData* getTarget() const { return target; } + void setTarget(const BoneData* value) { target = value; } + + const BoneData* getBone() const { return bone; } + void setBone(const BoneData* value) { bone = value; } + + const BoneData* getRoot() const { return root; } + void setRoot(const BoneData* value) { root = value; } +}; +/** + * @internal + */ +class IKConstraintData : public ConstraintData { + BIND_CLASS_TYPE_A(IKConstraintData); + +public: + bool scaleEnabled; + bool bendPositive; + float weight; + +protected: + virtual void _onClear() override; +}; + +DRAGONBONES_NAMESPACE_END +#endif //DRAGONBONESCPP_CONSTRAINTDATA_H diff --git a/cocos/editor-support/dragonbones/model/DisplayData.cpp b/cocos/editor-support/dragonbones/model/DisplayData.cpp new file mode 100644 index 0000000..01412ec --- /dev/null +++ b/cocos/editor-support/dragonbones/model/DisplayData.cpp @@ -0,0 +1,90 @@ +// +// Created by liangshuochen on 08/06/2017. +// + +#include "DisplayData.h" +#include "BoundingBoxData.h" +#include "UserData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void VerticesData::clear() { + if (!isShared && weight != nullptr) { + weight->returnToPool(); + } + + isShared = false; + inheritDeform = false; + offset = 0; + data = nullptr; + weight = nullptr; +} + +void VerticesData::shareFrom(const VerticesData& value) { + isShared = true; + offset = value.offset; + weight = value.weight; +} + +void DisplayData::_onClear() { + name = ""; + path = ""; + transform.identity(); + parent = nullptr; +} + +void ImageDisplayData::_onClear() { + DisplayData::_onClear(); + + type = DisplayType::Image; + pivot.clear(); + texture = nullptr; +} + +void ArmatureDisplayData::_onClear() { + DisplayData::_onClear(); + + for (const auto action : actions) { + action->returnToPool(); + } + + type = DisplayType::Armature; + inheritAnimation = false; + actions.clear(); + armature = nullptr; +} + +void ArmatureDisplayData::addAction(ActionData* value) { + actions.push_back(value); +} + +void MeshDisplayData::_onClear() { + DisplayData::_onClear(); + + type = DisplayType::Mesh; + vertices.clear(); + texture = nullptr; +} + +void BoundingBoxDisplayData::_onClear() { + DisplayData::_onClear(); + + if (boundingBox != nullptr) { + boundingBox->returnToPool(); + } + + type = DisplayType::BoundingBox; + boundingBox = nullptr; +} + +void WeightData::_onClear() { + count = 0; + offset = 0; + bones.clear(); +} + +void WeightData::addBone(BoneData* value) { + bones.push_back(value); +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/DisplayData.h b/cocos/editor-support/dragonbones/model/DisplayData.h new file mode 100644 index 0000000..5f7479b --- /dev/null +++ b/cocos/editor-support/dragonbones/model/DisplayData.h @@ -0,0 +1,179 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONESCPP_DISPLAYDATA_H +#define DRAGONBONESCPP_DISPLAYDATA_H + +#include "../core/BaseObject.h" +#include "../geom/Transform.h" +#include "BoundingBoxData.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * @internal + */ +class VerticesData { +public: + bool isShared; + bool inheritDeform; + unsigned offset; + DragonBonesData* data; + WeightData* weight; + + VerticesData() : weight(nullptr) { + } + ~VerticesData() { + } + + void clear(); + void shareFrom(const VerticesData& value); +}; +/** + * @internal + */ +class DisplayData : public BaseObject { + ABSTRACT_CLASS(DisplayData) + +public: + DisplayType type; + std::string name; + std::string path; + Transform transform; + SkinData* parent; + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + int getType() const { return (int)type; } + void setType(int value) { type = (DisplayType)value; } + + Transform* getTransform() { return &transform; } + + SkinData* getParent() const { return parent; } + void setParent(SkinData* value) { parent = value; } +}; +/** + * @internal + */ +class ImageDisplayData : public DisplayData { + BIND_CLASS_TYPE_A(ImageDisplayData); + +public: + Point pivot; + TextureData* texture; + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + Point* getPivot() { return &pivot; } + + TextureData* getTexture() const { return texture; } + void setTexture(TextureData* value) { texture = value; } +}; +/** + * @internal + */ +class ArmatureDisplayData : public DisplayData { + BIND_CLASS_TYPE_A(ArmatureDisplayData); + +public: + bool inheritAnimation; + std::vector actions; + ArmatureData* armature; + +protected: + virtual void _onClear() override; + +public: + /** + * @private + */ + void addAction(ActionData* value); + +public: // For WebAssembly. + const std::vector& getActions() const { return actions; } + + ArmatureData* getArmature() const { return armature; } + void setArmature(ArmatureData* value) { armature = value; } +}; +/** + * @internal + */ +class MeshDisplayData : public DisplayData { + BIND_CLASS_TYPE_A(MeshDisplayData); + +public: + VerticesData vertices; + TextureData* texture; + +protected: + virtual void _onClear() override; +}; +/** + * @internal + */ +class BoundingBoxDisplayData : public DisplayData { + BIND_CLASS_TYPE_B(BoundingBoxDisplayData); + +public: + BoundingBoxData* boundingBox; + + BoundingBoxDisplayData() : boundingBox(nullptr) { + _onClear(); + } + ~BoundingBoxDisplayData() { + _onClear(); + } + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + const BoundingBoxData* getBoundingBox() const { return boundingBox; } + void setBoundingBox(BoundingBoxData* value) { boundingBox = value; } +}; +/** + * @internal + */ +class WeightData : public BaseObject { + BIND_CLASS_TYPE_A(WeightData); + +public: + unsigned count; + unsigned offset; + std::vector bones; + +protected: + virtual void _onClear() override; + +public: + void addBone(BoneData* value); + +public: // For WebAssembly. + const std::vector& getBones() const { return bones; } +}; + +DRAGONBONES_NAMESPACE_END + +#endif //DRAGONBONESCPP_DISPLAYDATA_H diff --git a/cocos/editor-support/dragonbones/model/DragonBonesData.cpp b/cocos/editor-support/dragonbones/model/DragonBonesData.cpp new file mode 100644 index 0000000..56e4b62 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/DragonBonesData.cpp @@ -0,0 +1,50 @@ +#include "DragonBonesData.h" +#include "ArmatureData.h" +#include "UserData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void DragonBonesData::_onClear() { + for (const auto& pair : armatures) { + pair.second->returnToPool(); + } + + if (binary != nullptr) { + free(const_cast(binary)); + binary = nullptr; + } + + if (userData != nullptr) { + userData->returnToPool(); + userData = nullptr; + } + + autoSearch = false; + frameRate = 0; + version = ""; + name = ""; + frameIndices.clear(); + cachedFrames.clear(); + armatureNames.clear(); + armatures.clear(); + + intArray = nullptr; + floatArray = nullptr; + frameIntArray = nullptr; + frameFloatArray = nullptr; + frameArray = nullptr; + timelineArray = nullptr; +} + +void DragonBonesData::addArmature(ArmatureData* value) { + if (armatures.find(value->name) != armatures.end()) { + DRAGONBONES_ASSERT(false, "Same armature: " + value->name); + return; + } + + value->parent = this; + armatures[value->name] = value; + armatureNames.push_back(value->name); +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/DragonBonesData.h b/cocos/editor-support/dragonbones/model/DragonBonesData.h new file mode 100644 index 0000000..25bbbb1 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/DragonBonesData.h @@ -0,0 +1,187 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_DRAGONBONES_DATA_H +#define DRAGONBONES_DRAGONBONES_DATA_H + +#include "../core/BaseObject.h" +#include "ArmatureData.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The DragonBones data. + * A DragonBones data contains multiple armature data. + * @see dragonBones.ArmatureData + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 龙骨数据。 + * 一个龙骨数据包含多个骨架数据。 + * @see dragonBones.ArmatureData + * @version DragonBones 3.0 + * @language zh_CN + */ +class DragonBonesData : public BaseObject { + BIND_CLASS_TYPE_B(DragonBonesData); + +public: + /** + * @private + */ + bool autoSearch; + /** + * - The animation frame rate. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 动画帧频。 + * @version DragonBones 3.0 + * @language zh_CN + */ + unsigned frameRate; + /** + * - The data version. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 数据版本。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::string version; + /** + * - The DragonBones data name. + * The name is consistent with the DragonBones project name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 龙骨数据名称。 + * 该名称与龙骨项目名保持一致。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::string name; + /** + * @internal + */ + std::vector frameIndices; + /** + * @internal + */ + std::vector cachedFrames; + /** + * - All armature data names. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 所有的骨架数据名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::vector armatureNames; + /** + * @private + */ + std::map armatures; + /** + * @internal + */ + const char* binary; + /** + * @internal + */ + const int16_t* intArray; + /** + * @internal + */ + const float* floatArray; + /** + * @internal + */ + const int16_t* frameIntArray; + /** + * @internal + */ + const float* frameFloatArray; + /** + * @internal + */ + const int16_t* frameArray; + /** + * @internal + */ + const uint16_t* timelineArray; + /** + * @private + */ + UserData* userData; + DragonBonesData() : binary(nullptr), + userData(nullptr) { + _onClear(); + } + ~DragonBonesData() { + _onClear(); + } + /** + * @internal + */ + void addArmature(ArmatureData* value); + /** + * - Get a specific armature data. + * @param armatureName - The armature data name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 获取特定的骨架数据。 + * @param armatureName - 骨架数据名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + inline ArmatureData* getArmature(const std::string& armatureName) const { + return mapFind(armatures, armatureName); + } + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + std::vector* getFrameIndices() { return &frameIndices; } + const std::vector& getArmatureNames() const { return armatureNames; } + +#if EGRET_WASM + unsigned getBinary() const { + return (unsigned)binary; + } +#endif // EGRET_WASM + + const UserData* getUserData() const { return userData; } + void setUserData(UserData* value) { userData = value; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_DRAGONBONES_DATA_H diff --git a/cocos/editor-support/dragonbones/model/SkinData.cpp b/cocos/editor-support/dragonbones/model/SkinData.cpp new file mode 100644 index 0000000..bf440b5 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/SkinData.cpp @@ -0,0 +1,41 @@ +#include "SkinData.h" +#include "DisplayData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void SkinData::_onClear() { + for (const auto& pair : displays) { + for (const auto display : pair.second) { + if (display != nullptr) { + display->returnToPool(); + } + } + } + + name = ""; + displays.clear(); + parent = nullptr; +} + +void SkinData::addDisplay(const std::string& slotName, DisplayData* value) { + if (value != nullptr) { + value->parent = this; + } + + displays[slotName].push_back(value); // TODO clear prev +} + +DisplayData* SkinData::getDisplay(const std::string& slotName, const std::string& displayName) { + const auto slotDisplays = getDisplays(slotName); + if (slotDisplays != nullptr) { + for (const auto display : *slotDisplays) { + if (display != nullptr && display->name == displayName) { + return display; + } + } + } + + return nullptr; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/SkinData.h b/cocos/editor-support/dragonbones/model/SkinData.h new file mode 100644 index 0000000..3786ebe --- /dev/null +++ b/cocos/editor-support/dragonbones/model/SkinData.h @@ -0,0 +1,87 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_SKIN_DATA_H +#define DRAGONBONES_SKIN_DATA_H + +#include "../core/BaseObject.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The skin data, typically a armature data instance contains at least one skinData. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 皮肤数据,通常一个骨架数据至少包含一个皮肤数据。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class SkinData : public BaseObject { + BIND_CLASS_TYPE_A(SkinData); + +public: + /** + * - The skin name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 皮肤名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::string name; + /** + * @private + */ + std::map> displays; + /** + * @private + */ + ArmatureData* parent; + +protected: + virtual void _onClear() override; + +public: + /** + * @internal + */ + void addDisplay(const std::string& slotName, DisplayData* value); + /** + * @private + */ + DisplayData* getDisplay(const std::string& slotName, const std::string& displayName); + /** + * @private + */ + std::vector* getDisplays(const std::string& slotName) { + return mapFindB(displays, slotName); + } + +public: // For WebAssembly. TODO parent + const std::map>& getSlotDisplays() const { return displays; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_SKIN_DATA_H diff --git a/cocos/editor-support/dragonbones/model/TextureAtlasData.cpp b/cocos/editor-support/dragonbones/model/TextureAtlasData.cpp new file mode 100644 index 0000000..8199179 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/TextureAtlasData.cpp @@ -0,0 +1,89 @@ +#include "TextureAtlasData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void TextureAtlasData::_onClear() { + for (const auto& pair : textures) { + pair.second->returnToPool(); + } + + autoSearch = false; + format = TextureFormat::DEFAULT; + width = 0; + height = 0; + scale = 1.0f; + name = ""; + imagePath.clear(); + textures.clear(); +} + +void TextureAtlasData::copyFrom(const TextureAtlasData& value) { + autoSearch = value.autoSearch; + format = value.format; + width = value.width; + height = value.height; + scale = value.scale; + name = value.name; + imagePath = value.imagePath; + + for (const auto& pair : textures) { + pair.second->returnToPool(); + } + + textures.clear(); + + for (const auto& pair : value.textures) { + const auto texture = createTexture(); + texture->copyFrom(*(pair.second)); + textures[pair.first] = texture; + } +} + +void TextureAtlasData::addTexture(TextureData* value) { + if (textures.find(value->name) != textures.cend()) { + DRAGONBONES_ASSERT(false, "Same texture: " + value->name); + return; + } + + textures[value->name] = value; + value->parent = this; +} + +Rectangle* TextureData::createRectangle() { + return new Rectangle(); +} + +TextureData::~TextureData() { +} + +void TextureData::_onClear() { + if (frame != nullptr) { + delete frame; + } + + rotated = false; + name = ""; + region.clear(); + parent = nullptr; + frame = nullptr; +} + +void TextureData::copyFrom(const TextureData& value) { + rotated = value.rotated; + name = value.name; + region = value.region; // Copy. + parent = value.parent; + + if (frame == nullptr && value.frame != nullptr) { + frame = TextureData::createRectangle(); + } else if (frame != nullptr && value.frame == nullptr) { + delete frame; + frame = nullptr; + } + + if (frame != nullptr && value.frame != nullptr) { + *frame = *(value.frame); // Copy. + } +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/TextureAtlasData.h b/cocos/editor-support/dragonbones/model/TextureAtlasData.h new file mode 100644 index 0000000..4b87cc8 --- /dev/null +++ b/cocos/editor-support/dragonbones/model/TextureAtlasData.h @@ -0,0 +1,143 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_TEXTUREATLAS_DATA_H +#define DRAGONBONES_TEXTUREATLAS_DATA_H + +#include "../core/BaseObject.h" +#include "../geom/Rectangle.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The texture atlas data. + * @version DragonBones 3.0 + * @language en_US + */ +/** + * - 贴图集数据。 + * @version DragonBones 3.0 + * @language zh_CN + */ +class TextureAtlasData : public BaseObject { + ABSTRACT_CLASS(TextureAtlasData); + +public: + /** + * @private + */ + bool autoSearch; + TextureFormat format; + /** + * @private + */ + unsigned width; + /** + * @private + */ + unsigned height; + /** + * @private + */ + float scale; + /** + * - The texture atlas name. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 贴图集名称。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::string name; + /** + * - The image path of the texture atlas. + * @version DragonBones 3.0 + * @language en_US + */ + /** + * - 贴图集图片路径。 + * @version DragonBones 3.0 + * @language zh_CN + */ + std::string imagePath; + /** + * @private + */ + std::map textures; + /** + * @private + */ + void copyFrom(const TextureAtlasData& value); + /** + * @internal + */ + virtual TextureData* createTexture() const = 0; + /** + * @internal + */ + virtual void addTexture(TextureData* value); + /** + * @private + */ + inline TextureData* getTexture(const std::string& textureName) const { + return mapFind(textures, textureName); + } + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + const std::map& getTextures() const { return textures; } +}; +/** + * @internal + */ +class TextureData : public BaseObject { +public: + static Rectangle* createRectangle(); + +public: + bool rotated; + std::string name; + Rectangle region; + Rectangle* frame; + TextureAtlasData* parent; + + TextureData() : frame(nullptr) {} + virtual ~TextureData() = 0; + + void copyFrom(const TextureData& value); + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + Rectangle* getRegion() { return ®ion; } + const Rectangle* getFrame() const { return frame; } + void setFrame(Rectangle* value) { frame = value; } + const TextureAtlasData* getParent() const { return parent; } + void setParent(TextureAtlasData* value) { parent = value; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_TEXTUREATLAS_DATA_H diff --git a/cocos/editor-support/dragonbones/model/UserData.cpp b/cocos/editor-support/dragonbones/model/UserData.cpp new file mode 100644 index 0000000..39ab1da --- /dev/null +++ b/cocos/editor-support/dragonbones/model/UserData.cpp @@ -0,0 +1,47 @@ +#include "UserData.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void UserData::_onClear() { + ints.clear(); + floats.clear(); + strings.clear(); +} + +void UserData::addInt(int value) { + ints.push_back(value); +} + +void UserData::addFloat(float value) { + floats.push_back(value); +} + +void UserData::addString(std::string value) { + strings.push_back(value); +} + +int UserData::getInt(unsigned index) const { + return index < ints.size() ? ints[index] : 0; +} + +float UserData::getFloat(unsigned index) const { + return index < floats.size() ? floats[index] : 0; +} + +std::string UserData::getString(unsigned index) const { + return index < strings.size() ? strings[index] : 0; +} + +void ActionData::_onClear() { + if (data != nullptr) { + data->returnToPool(); + } + + type = ActionType::Play; + name = ""; + bone = nullptr; + slot = nullptr; + data = nullptr; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/model/UserData.h b/cocos/editor-support/dragonbones/model/UserData.h new file mode 100644 index 0000000..afd230f --- /dev/null +++ b/cocos/editor-support/dragonbones/model/UserData.h @@ -0,0 +1,170 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_USER_DATA_H +#define DRAGONBONES_USER_DATA_H + +#include "../core/BaseObject.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * - The user custom data. + * @version DragonBones 5.0 + * @language en_US + */ +/** + * - 用户自定义数据。 + * @version DragonBones 5.0 + * @language zh_CN + */ +class UserData : public BaseObject { + BIND_CLASS_TYPE_A(UserData); + +public: + /** + * - The custom int numbers. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 自定义整数。 + * @version DragonBones 5.0 + * @language zh_CN + */ + std::vector ints; + /** + * - The custom float numbers. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 自定义浮点数。 + * @version DragonBones 5.0 + * @language zh_CN + */ + std::vector floats; + /** + * - The custom strings. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 自定义字符串。 + * @version DragonBones 5.0 + * @language zh_CN + */ + std::vector strings; + +protected: + virtual void _onClear() override; + +public: + /** + * @internal + */ + void addInt(int value); + /** + * @internal + */ + void addFloat(float value); + /** + * @internal + */ + void addString(std::string value); + /** + * - Get the custom int number. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 获取自定义整数。 + * @version DragonBones 5.0 + * @language zh_CN + */ + int getInt(unsigned index) const; + /** + * - Get the custom float number. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 获取自定义浮点数。 + * @version DragonBones 5.0 + * @language zh_CN + */ + float getFloat(unsigned index) const; + /** + * - Get the custom string. + * @version DragonBones 5.0 + * @language en_US + */ + /** + * - 获取自定义字符串。 + * @version DragonBones 5.0 + * @language zh_CN + */ + std::string getString(unsigned index) const; + +public: // For WebAssembly. + const std::vector& getInts() const { return ints; } + const std::vector& getFloats() const { return floats; } + const std::vector& getStrings() const { return strings; } +}; +/** + * @internal + */ +class ActionData : public BaseObject { + BIND_CLASS_TYPE_B(ActionData); + +public: + ActionType type; + std::string name; + const BoneData* bone; + const SlotData* slot; + UserData* data; + + ActionData() : data(nullptr) { + _onClear(); + } + virtual ~ActionData() { + _onClear(); + } + +protected: + virtual void _onClear() override; + +public: // For WebAssembly. + int getType() const { return (int)type; } + void setType(int value) { type = (ActionType)value; } + + const BoneData* getBone() const { return bone; } + void setBone(const BoneData* value) { bone = value; } + + const SlotData* getSlot() const { return slot; } + void setSlot(const SlotData* value) { slot = value; } + + const UserData* getData() const { return data; } + void setData(UserData* value) { data = value; } +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_USER_DATA_H diff --git a/cocos/editor-support/dragonbones/parser/BinaryDataParser.cpp b/cocos/editor-support/dragonbones/parser/BinaryDataParser.cpp new file mode 100644 index 0000000..9c7c0ec --- /dev/null +++ b/cocos/editor-support/dragonbones/parser/BinaryDataParser.cpp @@ -0,0 +1,202 @@ +#include "BinaryDataParser.h" + +DRAGONBONES_NAMESPACE_BEGIN + +TimelineData* BinaryDataParser::_parseBinaryTimeline(TimelineType type, unsigned offset, TimelineData* timelineData) { + const auto timeline = timelineData != nullptr ? timelineData : BaseObject::borrowObject(); + timeline->type = type; + timeline->offset = offset; + + _timeline = timeline; + + const auto keyFrameCount = (unsigned)_timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineKeyFrameCount]; + if (keyFrameCount == 1) { + timeline->frameIndicesOffset = -1; + } else { + unsigned frameIndicesOffset = 0; + const auto totalFrameCount = _animation->frameCount + 1; // One more frame than animation. + auto& frameIndices = _data->frameIndices; + + frameIndicesOffset = frameIndices.size(); + timeline->frameIndicesOffset = frameIndicesOffset; + frameIndices.resize(frameIndicesOffset + totalFrameCount); + + for ( + std::size_t i = 0, iK = 0, frameStart = 0, frameCount = 0; + i < totalFrameCount; + ++i) { + if (frameStart + frameCount <= i && iK < keyFrameCount) { + frameStart = _frameArray[_animation->frameOffset + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameOffset + iK]]; + if (iK == keyFrameCount - 1) { + frameCount = _animation->frameCount - frameStart; + } else { + frameCount = _frameArray[_animation->frameOffset + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameOffset + iK + 1]] - frameStart; + } + + iK++; + } + + frameIndices[frameIndicesOffset + i] = iK - 1; + } + } + + _timeline = nullptr; + + return timeline; +} + +void BinaryDataParser::_parseVertices(const rapidjson::Value& rawData, VerticesData& vertices) { + vertices.offset = rawData[OFFSET].GetUint(); + + const auto weightOffset = _intArray[vertices.offset + (unsigned)BinaryOffset::MeshWeightOffset]; + if (weightOffset >= 0) { + const auto weight = BaseObject::borrowObject(); + const auto vertexCount = _intArray[vertices.offset + (unsigned)BinaryOffset::MeshVertexCount]; + const auto boneCount = (unsigned)_intArray[weightOffset + (unsigned)BinaryOffset::WeigthBoneCount]; + weight->offset = weightOffset; + + for (std::size_t i = 0; i < boneCount; ++i) { + const auto boneIndex = _intArray[weightOffset + (unsigned)BinaryOffset::WeigthBoneIndices + i]; + weight->addBone(_rawBones[boneIndex]); + } + + auto boneIndicesOffset = weightOffset + (unsigned)BinaryOffset::WeigthBoneIndices + boneCount; + unsigned weightCount = 0; + for (std::size_t i = 0, l = vertexCount; i < l; ++i) { + const auto vertexBoneCount = (unsigned)_intArray[boneIndicesOffset++]; + weightCount += vertexBoneCount; + boneIndicesOffset += vertexBoneCount; + } + + weight->count = weightCount; + vertices.weight = weight; + } +} + +void BinaryDataParser::_parseMesh(const rapidjson::Value& rawData, MeshDisplayData& mesh) { + _parseVertices(rawData, mesh.vertices); +} + +AnimationData* BinaryDataParser::_parseAnimation(const rapidjson::Value& rawData) { + const auto animation = BaseObject::borrowObject(); + animation->frameCount = std::max(_getNumber(rawData, DURATION, 1), 1); + animation->playTimes = _getNumber(rawData, PLAY_TIMES, 1); + animation->duration = (float)(animation->frameCount) / _armature->frameRate; // float + animation->fadeInTime = _getNumber(rawData, FADE_IN_TIME, 0.0f); + animation->scale = _getNumber(rawData, SCALE, 1.0f); + animation->name = _getString(rawData, NAME, DEFAULT_NAME); + if (animation->name.empty()) { + animation->name = DEFAULT_NAME; + } + + // Offsets. + const auto& offsets = rawData[OFFSET]; + animation->frameIntOffset = offsets[0].GetUint(); + animation->frameFloatOffset = offsets[1].GetUint(); + animation->frameOffset = offsets[2].GetUint(); + + _animation = animation; + + if (rawData.HasMember(ACTION)) { + animation->actionTimeline = _parseBinaryTimeline(TimelineType::Action, rawData[ACTION].GetUint()); + } + + if (rawData.HasMember(Z_ORDER)) { + animation->zOrderTimeline = _parseBinaryTimeline(TimelineType::ZOrder, rawData[Z_ORDER].GetUint()); + } + + if (rawData.HasMember(BONE)) { + const auto& rawTimeliness = rawData[BONE]; + for (auto iterator = rawTimeliness.MemberBegin(); iterator != rawTimeliness.MemberEnd(); ++iterator) { + const auto bone = _armature->getBone(iterator->name.GetString()); + if (bone == nullptr) { + continue; + } + + const auto& rawTimelines = *&(iterator->value); + for (std::size_t i = 0, l = rawTimelines.Size(); i < l; i += 2) { + const auto timelineType = (TimelineType)rawTimelines[i].GetInt(); + const auto timelineOffset = rawTimelines[i + 1].GetUint(); + const auto timeline = _parseBinaryTimeline(timelineType, timelineOffset); + _animation->addBoneTimeline(bone, timeline); + } + } + } + + if (rawData.HasMember(SLOT)) { + const auto& rawTimeliness = rawData[SLOT]; + for (auto iterator = rawTimeliness.MemberBegin(); iterator != rawTimeliness.MemberEnd(); ++iterator) { + const auto slot = _armature->getSlot(iterator->name.GetString()); + if (slot == nullptr) { + continue; + } + + const auto& rawTimelines = *&(iterator->value); + for (std::size_t i = 0, l = rawTimelines.Size(); i < l; i += 2) { + const auto timelineType = (TimelineType)rawTimelines[i].GetInt(); + const auto timelineOffset = rawTimelines[i + 1].GetUint(); + const auto timeline = _parseBinaryTimeline(timelineType, timelineOffset); + _animation->addSlotTimeline(slot, timeline); + } + } + } + + if (rawData.HasMember(CONSTRAINT)) { + const auto& rawTimeliness = rawData[CONSTRAINT]; + for (auto iterator = rawTimeliness.MemberBegin(); iterator != rawTimeliness.MemberEnd(); ++iterator) { + const auto constraint = _armature->getConstraint(iterator->name.GetString()); + if (constraint == nullptr) { + continue; + } + + const auto& rawTimelines = *&(iterator->value); + for (std::size_t i = 0, l = rawTimelines.Size(); i < l; i += 2) { + const auto timelineType = (TimelineType)rawTimelines[i].GetInt(); + const auto timelineOffset = rawTimelines[i + 1].GetUint(); + const auto timeline = _parseBinaryTimeline(timelineType, timelineOffset); + _animation->addConstraintTimeline(constraint, timeline); + } + } + } + + _animation = nullptr; + + return animation; +} + +void BinaryDataParser::_parseArray(const rapidjson::Value& rawData) { + const auto& offsets = rawData[OFFSET]; + + _data->binary = _binary; + _data->intArray = _intArray = (int16_t*)(_binary + _binaryOffset + offsets[0].GetUint()); + _data->floatArray = _floatArray = (float*)(_binary + _binaryOffset + offsets[2].GetUint()); + _data->frameIntArray = _frameIntArray = (int16_t*)(_binary + _binaryOffset + offsets[4].GetUint()); + _data->frameFloatArray = _frameFloatArray = (float*)(_binary + _binaryOffset + offsets[6].GetUint()); + _data->frameArray = _frameArray = (int16_t*)(_binary + _binaryOffset + offsets[8].GetUint()); + _data->timelineArray = _timelineArray = (uint16_t*)(_binary + _binaryOffset + offsets[10].GetUint()); +} + +DragonBonesData* BinaryDataParser::parseDragonBonesData(const char* rawData, float scale) { + DRAGONBONES_ASSERT(rawData != nullptr, ""); + + if ( + rawData[0] != 'D' || + rawData[1] != 'B' || + rawData[2] != 'D' || + rawData[3] != 'T') { + DRAGONBONES_ASSERT(false, "Nonsupport data."); + return nullptr; + } + + const auto headerLength = (std::size_t)(((uint32_t*)(rawData + 8))[0]); + const auto headerBytes = rawData + 8 + 4; + rapidjson::Document document; + document.Parse(headerBytes, headerLength); + + _binaryOffset = 8 + 4 + headerLength; + _binary = rawData; + + return JSONDataParser::_parseDragonBonesData(document, scale); +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/parser/BinaryDataParser.h b/cocos/editor-support/dragonbones/parser/BinaryDataParser.h new file mode 100644 index 0000000..28615e0 --- /dev/null +++ b/cocos/editor-support/dragonbones/parser/BinaryDataParser.h @@ -0,0 +1,69 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_BINARY_DATA_PARSER_H +#define DRAGONBONES_BINARY_DATA_PARSER_H + +#include "JSONDataParser.h" + +DRAGONBONES_NAMESPACE_BEGIN + +/** + * @internal + */ +class BinaryDataParser : public JSONDataParser { + DRAGONBONES_DISALLOW_COPY_AND_ASSIGN(BinaryDataParser) + +private: + unsigned _binaryOffset; + const char* _binary; + const int16_t* _intArray; + const float* _floatArray; + const int16_t* _frameIntArray; + const float* _frameFloatArray; + const int16_t* _frameArray; + const uint16_t* _timelineArray; + + TimelineData* _parseBinaryTimeline(TimelineType type, unsigned offset, TimelineData* timelineData = nullptr); + void _parseVertices(const rapidjson::Value& rawData, VerticesData& vertices); + +protected: + virtual void _parseMesh(const rapidjson::Value& rawData, MeshDisplayData& mesh) override; + virtual AnimationData* _parseAnimation(const rapidjson::Value& rawData) override; + virtual void _parseArray(const rapidjson::Value& rawData) override; + +public: + BinaryDataParser() : _binaryOffset(0), + _binary(nullptr), + _intArray(nullptr), + _floatArray(nullptr), + _frameIntArray(nullptr), + _frameFloatArray(nullptr), + _frameArray(nullptr), + _timelineArray(nullptr) {} + virtual ~BinaryDataParser() {} + + virtual DragonBonesData* parseDragonBonesData(const char* rawData, float scale = 1.0f) override; +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_BINARY_DATA_PARSER_H diff --git a/cocos/editor-support/dragonbones/parser/DataParser.cpp b/cocos/editor-support/dragonbones/parser/DataParser.cpp new file mode 100644 index 0000000..c4b6663 --- /dev/null +++ b/cocos/editor-support/dragonbones/parser/DataParser.cpp @@ -0,0 +1,251 @@ +#include "DataParser.h" + +DRAGONBONES_NAMESPACE_BEGIN + +const char* DataParser::DATA_VERSION_2_3 = "2.3"; +const char* DataParser::DATA_VERSION_3_0 = "3.0"; +const char* DataParser::DATA_VERSION_4_0 = "4.0"; +const char* DataParser::DATA_VERSION_4_5 = "4.5"; +const char* DataParser::DATA_VERSION_5_0 = "5.0"; +const char* DataParser::DATA_VERSION_5_5 = "5.5"; +const char* DataParser::DATA_VERSION = DataParser::DATA_VERSION_5_5; + +const std::vector DataParser::DATA_VERSIONS{ + DataParser::DATA_VERSION_4_0, + DataParser::DATA_VERSION_4_5, + DataParser::DATA_VERSION_5_0, + DataParser::DATA_VERSION_5_5}; + +const char* DataParser::TEXTURE_ATLAS = "TextureAtlas"; +const char* DataParser::SUB_TEXTURE = "SubTexture"; +const char* DataParser::FORMAT = "format"; +const char* DataParser::IMAGE_PATH = "imagePath"; +const char* DataParser::WIDTH = "width"; +const char* DataParser::HEIGHT = "height"; +const char* DataParser::ROTATED = "rotated"; +const char* DataParser::FRAME_X = "frameX"; +const char* DataParser::FRAME_Y = "frameY"; +const char* DataParser::FRAME_WIDTH = "frameWidth"; +const char* DataParser::FRAME_HEIGHT = "frameHeight"; + +const char* DataParser::DRADON_BONES = "dragonBones"; +const char* DataParser::USER_DATA = "userData"; +const char* DataParser::ARMATURE = "armature"; +const char* DataParser::BONE = "bone"; +const char* DataParser::SLOT = "slot"; +const char* DataParser::CONSTRAINT = "constraint"; +const char* DataParser::IK = "ik"; +const char* DataParser::SKIN = "skin"; +const char* DataParser::DISPLAY = "display"; +const char* DataParser::ANIMATION = "animation"; +const char* DataParser::Z_ORDER = "zOrder"; +const char* DataParser::FFD = "ffd"; +const char* DataParser::FRAME = "frame"; +const char* DataParser::TRANSLATE_FRAME = "translateFrame"; +const char* DataParser::ROTATE_FRAME = "rotateFrame"; +const char* DataParser::SCALE_FRAME = "scaleFrame"; +const char* DataParser::DISPLAY_FRAME = "displayFrame"; +const char* DataParser::COLOR_FRAME = "colorFrame"; +const char* DataParser::DEFAULT_ACTIONS = "defaultActions"; +const char* DataParser::ACTIONS = "actions"; +const char* DataParser::EVENTS = "events"; +const char* DataParser::INTS = "ints"; +const char* DataParser::FLOATS = "floats"; +const char* DataParser::STRINGS = "strings"; +const char* DataParser::CANVAS = "canvas"; + +const char* DataParser::TRANSFORM = "transform"; +const char* DataParser::PIVOT = "pivot"; +const char* DataParser::AABB = "aabb"; +const char* DataParser::COLOR = "color"; + +const char* DataParser::VERSION = "version"; +const char* DataParser::COMPATIBLE_VERSION = "compatibleVersion"; +const char* DataParser::FRAME_RATE = "frameRate"; +const char* DataParser::TYPE = "type"; +const char* DataParser::SUB_TYPE = "subType"; +const char* DataParser::NAME = "name"; +const char* DataParser::PARENT = "parent"; +const char* DataParser::TARGET = "target"; +const char* DataParser::STAGE = "stage"; +const char* DataParser::SHARE = "share"; +const char* DataParser::PATH = "path"; +const char* DataParser::LENGTH = "length"; +const char* DataParser::DISPLAY_INDEX = "displayIndex"; +const char* DataParser::BLEND_MODE = "blendMode"; +const char* DataParser::INHERIT_TRANSLATION = "inheritTranslation"; +const char* DataParser::INHERIT_ROTATION = "inheritRotation"; +const char* DataParser::INHERIT_SCALE = "inheritScale"; +const char* DataParser::INHERIT_REFLECTION = "inheritReflection"; +const char* DataParser::INHERIT_ANIMATION = "inheritAnimation"; +const char* DataParser::INHERIT_DEFORM = "inheritDeform"; +const char* DataParser::BEND_POSITIVE = "bendPositive"; +const char* DataParser::CHAIN = "chain"; +const char* DataParser::WEIGHT = "weight"; + +const char* DataParser::FADE_IN_TIME = "fadeInTime"; +const char* DataParser::PLAY_TIMES = "playTimes"; +const char* DataParser::SCALE = "scale"; +const char* DataParser::OFFSET = "offset"; +const char* DataParser::POSITION = "position"; +const char* DataParser::DURATION = "duration"; +const char* DataParser::TWEEN_EASING = "tweenEasing"; +const char* DataParser::TWEEN_ROTATE = "tweenRotate"; +const char* DataParser::TWEEN_SCALE = "tweenScale"; +const char* DataParser::CLOCK_WISE = "clockwise"; +const char* DataParser::CURVE = "curve"; +const char* DataParser::EVENT = "event"; +const char* DataParser::SOUND = "sound"; +const char* DataParser::ACTION = "action"; + +const char* DataParser::X = "x"; +const char* DataParser::Y = "y"; +const char* DataParser::SKEW_X = "skX"; +const char* DataParser::SKEW_Y = "skY"; +const char* DataParser::SCALE_X = "scX"; +const char* DataParser::SCALE_Y = "scY"; +const char* DataParser::VALUE = "value"; +const char* DataParser::ROTATE = "rotate"; +const char* DataParser::SKEW = "skew"; + +const char* DataParser::ALPHA_OFFSET = "aO"; +const char* DataParser::RED_OFFSET = "rO"; +const char* DataParser::GREEN_OFFSET = "gO"; +const char* DataParser::BLUE_OFFSET = "bO"; +const char* DataParser::ALPHA_MULTIPLIER = "aM"; +const char* DataParser::RED_MULTIPLIER = "rM"; +const char* DataParser::GREEN_MULTIPLIER = "gM"; +const char* DataParser::BLUE_MULTIPLIER = "bM"; + +const char* DataParser::UVS = "uvs"; +const char* DataParser::VERTICES = "vertices"; +const char* DataParser::TRIANGLES = "triangles"; +const char* DataParser::WEIGHTS = "weights"; +const char* DataParser::SLOT_POSE = "slotPose"; +const char* DataParser::BONE_POSE = "bonePose"; + +const char* DataParser::GOTO_AND_PLAY = "gotoAndPlay"; + +const char* DataParser::DEFAULT_NAME = "default"; + +TextureFormat DataParser::_getTextureFormat(const std::string& value) { + auto lower = value; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + if (lower == "rgba8888") { + return TextureFormat::RGBA8888; + } else if (lower == "bgra8888") { + return TextureFormat::BGRA8888; + } else if (lower == "rgba4444") { + return TextureFormat::RGBA4444; + } else if (lower == "rgb888") { + return TextureFormat::RGB888; + } else if (lower == "rgb565") { + return TextureFormat::RGB565; + } else if (lower == "rgba5551") { + return TextureFormat::RGBA5551; + } + + return TextureFormat::DEFAULT; +} + +ArmatureType DataParser::_getArmatureType(const std::string& value) { + auto lower = value; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + if (lower == "armature") { + return ArmatureType::Armature; + } else if (lower == "movieClip") { + return ArmatureType::MovieClip; + } else if (lower == "stage") { + return ArmatureType::Stage; + } + + return ArmatureType::Armature; +} + +DisplayType DataParser::_getDisplayType(const std::string& value) { + auto lower = value; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + if (lower == "image") { + return DisplayType::Image; + } else if (lower == "armature") { + return DisplayType::Armature; + } else if (lower == "mesh") { + return DisplayType::Mesh; + } else if (lower == "boundingbox") { + return DisplayType::BoundingBox; + } + + return DisplayType::Image; +} + +BoundingBoxType DataParser::_getBoundingBoxType(const std::string& value) { + auto lower = value; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + if (lower == "rectangle") { + return BoundingBoxType::Rectangle; + } else if (lower == "ellipse") { + return BoundingBoxType::Ellipse; + } else if (lower == "polygon") { + return BoundingBoxType::Polygon; + } + + return BoundingBoxType::Rectangle; +} + +ActionType DataParser::_getActionType(const std::string& value) { + auto lower = value; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + if (lower == "play") { + return ActionType::Play; + } else if (lower == "frame") { + return ActionType::Frame; + } else if (lower == "sound") { + return ActionType::Sound; + } + + return ActionType::Play; +} + +BlendMode DataParser::_getBlendMode(const std::string& value) { + auto lower = value; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + if (lower == "normal") { + return BlendMode::Normal; + } else if (lower == "add") { + return BlendMode::Add; + } else if (lower == "alpha") { + return BlendMode::Alpha; + } else if (lower == "darken") { + return BlendMode::Darken; + } else if (lower == "difference") { + return BlendMode::Difference; + } else if (lower == "erase") { + return BlendMode::Erase; + } else if (lower == "hardlight") { + return BlendMode::HardLight; + } else if (lower == "invert") { + return BlendMode::Invert; + } else if (lower == "layer") { + return BlendMode::Layer; + } else if (lower == "lighten") { + return BlendMode::Lighten; + } else if (lower == "multiply") { + return BlendMode::Multiply; + } else if (lower == "overlay") { + return BlendMode::Overlay; + } else if (lower == "screen") { + return BlendMode::Screen; + } else if (lower == "subtract") { + return BlendMode::Subtract; + } + + return BlendMode::Normal; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/parser/DataParser.h b/cocos/editor-support/dragonbones/parser/DataParser.h new file mode 100644 index 0000000..4ee4bc6 --- /dev/null +++ b/cocos/editor-support/dragonbones/parser/DataParser.h @@ -0,0 +1,181 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2012-2018 DragonBones team and other contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DRAGONBONES_DATA_PARSER_H +#define DRAGONBONES_DATA_PARSER_H + +#include "../core/DragonBones.h" +#include "../model/AnimationData.h" +#include "../model/ArmatureData.h" +#include "../model/BoundingBoxData.h" +#include "../model/CanvasData.h" +#include "../model/ConstraintData.h" +#include "../model/DisplayData.h" +#include "../model/DragonBonesData.h" +#include "../model/SkinData.h" +#include "../model/TextureAtlasData.h" +#include "../model/UserData.h" + +DRAGONBONES_NAMESPACE_BEGIN +/** + * @internal + */ +class DataParser { + ABSTRACT_CLASS(DataParser) + +protected: + static const char* DATA_VERSION_2_3; + static const char* DATA_VERSION_3_0; + static const char* DATA_VERSION_4_0; + static const char* DATA_VERSION_4_5; + static const char* DATA_VERSION_5_0; + static const char* DATA_VERSION_5_5; + static const char* DATA_VERSION; + + static const std::vector DATA_VERSIONS; + + static const char* TEXTURE_ATLAS; + static const char* SUB_TEXTURE; + static const char* FORMAT; + static const char* IMAGE_PATH; + static const char* WIDTH; + static const char* HEIGHT; + static const char* ROTATED; + static const char* FRAME_X; + static const char* FRAME_Y; + static const char* FRAME_WIDTH; + static const char* FRAME_HEIGHT; + + static const char* DRADON_BONES; + static const char* USER_DATA; + static const char* ARMATURE; + static const char* BONE; + static const char* SLOT; + static const char* CONSTRAINT; + static const char* IK; + static const char* SKIN; + static const char* DISPLAY; + static const char* ANIMATION; + static const char* Z_ORDER; + static const char* FFD; + static const char* FRAME; + static const char* TRANSLATE_FRAME; + static const char* ROTATE_FRAME; + static const char* SCALE_FRAME; + static const char* DISPLAY_FRAME; + static const char* COLOR_FRAME; + static const char* DEFAULT_ACTIONS; + static const char* ACTIONS; + static const char* EVENTS; + static const char* INTS; + static const char* FLOATS; + static const char* STRINGS; + static const char* CANVAS; + + static const char* PIVOT; + static const char* TRANSFORM; + static const char* AABB; + static const char* COLOR; + + static const char* VERSION; + static const char* COMPATIBLE_VERSION; + static const char* FRAME_RATE; + static const char* TYPE; + static const char* SUB_TYPE; + static const char* NAME; + static const char* PARENT; + static const char* TARGET; + static const char* STAGE; + static const char* SHARE; + static const char* PATH; + static const char* LENGTH; + static const char* DISPLAY_INDEX; + static const char* BLEND_MODE; + static const char* INHERIT_TRANSLATION; + static const char* INHERIT_ROTATION; + static const char* INHERIT_SCALE; + static const char* INHERIT_REFLECTION; + static const char* INHERIT_ANIMATION; + static const char* INHERIT_DEFORM; + static const char* BEND_POSITIVE; + static const char* CHAIN; + static const char* WEIGHT; + + static const char* FADE_IN_TIME; + static const char* PLAY_TIMES; + static const char* SCALE; + static const char* OFFSET; + static const char* POSITION; + static const char* DURATION; + static const char* TWEEN_EASING; + static const char* TWEEN_ROTATE; + static const char* TWEEN_SCALE; + static const char* CLOCK_WISE; + static const char* CURVE; + static const char* EVENT; + static const char* SOUND; + static const char* ACTION; + + static const char* X; + static const char* Y; + static const char* SKEW_X; + static const char* SKEW_Y; + static const char* SCALE_X; + static const char* SCALE_Y; + static const char* VALUE; + static const char* ROTATE; + static const char* SKEW; + + static const char* ALPHA_OFFSET; + static const char* RED_OFFSET; + static const char* GREEN_OFFSET; + static const char* BLUE_OFFSET; + static const char* ALPHA_MULTIPLIER; + static const char* RED_MULTIPLIER; + static const char* GREEN_MULTIPLIER; + static const char* BLUE_MULTIPLIER; + + static const char* UVS; + static const char* VERTICES; + static const char* TRIANGLES; + static const char* WEIGHTS; + static const char* SLOT_POSE; + static const char* BONE_POSE; + + static const char* GOTO_AND_PLAY; + + static const char* DEFAULT_NAME; + + static TextureFormat _getTextureFormat(const std::string& value); + static ArmatureType _getArmatureType(const std::string& value); + static DisplayType _getDisplayType(const std::string& value); + static BoundingBoxType _getBoundingBoxType(const std::string& value); + static ActionType _getActionType(const std::string& value); + static BlendMode _getBlendMode(const std::string& value); + +public: + virtual DragonBonesData* parseDragonBonesData(const char* rawData, float scale = 1.0f) = 0; + virtual bool parseTextureAtlasData(const char* rawData, TextureAtlasData& textureAtlasData, float scale = 1.0f) = 0; +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_DATA_PARSER_H diff --git a/cocos/editor-support/dragonbones/parser/JSONDataParser.cpp b/cocos/editor-support/dragonbones/parser/JSONDataParser.cpp new file mode 100644 index 0000000..9855236 --- /dev/null +++ b/cocos/editor-support/dragonbones/parser/JSONDataParser.cpp @@ -0,0 +1,1730 @@ +#include "JSONDataParser.h" +#include "base/Macros.h" + +DRAGONBONES_NAMESPACE_BEGIN + +void JSONDataParser::_getCurvePoint( + float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + float t, + Point &result) { + const auto l_t = 1.0f - t; + const auto powA = l_t * l_t; + const auto powB = t * t; + const auto kA = l_t * powA; + const auto kB = 3.0f * t * powA; + const auto kC = 3.0f * l_t * powB; + const auto kD = t * powB; + + result.x = kA * x1 + kB * x2 + kC * x3 + kD * x4; + result.y = kA * y1 + kB * y2 + kC * y3 + kD * y4; +} + +void JSONDataParser::_samplingEasingCurve(const rapidjson::Value &curve, std::vector &samples) { + int curveCount = curve.Size(); + int stepIndex = -2; + for (std::size_t i = 0, l = samples.size(); i < l; ++i) { + float t = (float)(i + 1) / (l + 1); // float + while ((stepIndex + 6 < curveCount ? curve[stepIndex + 6].GetDouble() : 1) < t) // stepIndex + 3 * 2 + { + stepIndex += 6; + } + + const auto isInCurve = stepIndex >= 0 && stepIndex + 6 < curveCount; + const auto x1 = isInCurve ? curve[stepIndex].GetDouble() : 0.0f; + const auto y1 = isInCurve ? curve[stepIndex + 1].GetDouble() : 0.0f; + const auto x2 = curve[stepIndex + 2].GetDouble(); + const auto y2 = curve[stepIndex + 3].GetDouble(); + const auto x3 = curve[stepIndex + 4].GetDouble(); + const auto y3 = curve[stepIndex + 5].GetDouble(); + const auto x4 = isInCurve ? curve[stepIndex + 6].GetDouble() : 1.0f; + const auto y4 = isInCurve ? curve[stepIndex + 7].GetDouble() : 1.0f; + + float lower = 0.0f; + float higher = 1.0f; + while (higher - lower > 0.0001f) { + const auto percentage = (higher + lower) * 0.5f; + _getCurvePoint(x1, y1, x2, y2, x3, y3, x4, y4, percentage, _helpPoint); + if (t - _helpPoint.x > 0.0f) { + lower = percentage; + } else { + higher = percentage; + } + } + + samples[i] = _helpPoint.y; + } +} + +void JSONDataParser::_parseActionDataInFrame(const rapidjson::Value &rawData, unsigned frameStart, BoneData *bone, SlotData *slot) { + if (rawData.HasMember(EVENT)) { + _mergeActionFrame(rawData[EVENT], frameStart, ActionType::Frame, bone, slot); + } + + if (rawData.HasMember(SOUND)) { + _mergeActionFrame(rawData[SOUND], frameStart, ActionType::Sound, bone, slot); + } + + if (rawData.HasMember(ACTION)) { + _mergeActionFrame(rawData[ACTION], frameStart, ActionType::Play, bone, slot); + } + + if (rawData.HasMember(EVENTS)) { + _mergeActionFrame(rawData[EVENTS], frameStart, ActionType::Frame, bone, slot); + } + + if (rawData.HasMember(ACTIONS)) { + _mergeActionFrame(rawData[ACTIONS], frameStart, ActionType::Play, bone, slot); + } +} + +void JSONDataParser::_mergeActionFrame(const rapidjson::Value &rawData, unsigned frameStart, ActionType type, BoneData *bone, SlotData *slot) { + const auto actionOffset = _armature->actions.size(); + const auto &actions = _parseActionData(rawData, type, bone, slot); + ActionFrame *frame = nullptr; + + for (const auto action : actions) { + _armature->addAction(action, false); + } + + if (_actionFrames.empty()) // First frame. + { + _actionFrames.resize(1); + _actionFrames[0].frameStart = 0; + } + + for (auto &eachFrame : _actionFrames) // Get same frame. + { + if (eachFrame.frameStart == frameStart) { + frame = &eachFrame; + break; + } + } + + if (frame == nullptr) // Create and cache frame. + { + const auto frameCount = _actionFrames.size(); + _actionFrames.resize(frameCount + 1); + frame = &_actionFrames[frameCount]; + frame->frameStart = frameStart; + } + + for (std::size_t i = 0; i < actions.size(); ++i) // Cache action offsets. + { + frame->actions.push_back(actionOffset + i); + } +} + +unsigned JSONDataParser::_parseCacheActionFrame(ActionFrame &frame) { + const auto frameOffset = _frameArray.size(); + const auto actionCount = frame.actions.size(); + _frameArray.resize(_frameArray.size() + 1 + 1 + actionCount); + _frameArray[frameOffset + (unsigned)BinaryOffset::FramePosition] = frame.frameStart; + _frameArray[frameOffset + (unsigned)BinaryOffset::FramePosition + 1] = actionCount; // Action count. + + for (std::size_t i = 0; i < actionCount; ++i) // Action offsets. + { + _frameArray[frameOffset + (unsigned)BinaryOffset::FramePosition + 2 + i] = frame.actions[i]; + } + + return frameOffset; +} + +ArmatureData *JSONDataParser::_parseArmature(const rapidjson::Value &rawData, float scale) { + const auto armature = BaseObject::borrowObject(); + armature->name = _getString(rawData, NAME, ""); + armature->frameRate = _getNumber(rawData, FRAME_RATE, _data->frameRate); + armature->scale = scale; + + if (rawData.HasMember(TYPE) && rawData[TYPE].IsString()) { + armature->type = _getArmatureType(rawData[TYPE].GetString()); + } else { + armature->type = (ArmatureType)_getNumber(rawData, TYPE, (int)ArmatureType::Armature); + } + + if (armature->frameRate == 0) // Data error. + { + armature->frameRate = 24; + } + + _armature = armature; + + if (rawData.HasMember(CANVAS)) { + const auto &rawCanvas = rawData[CANVAS]; + const auto canvas = BaseObject::borrowObject(); + canvas->hasBackground = rawCanvas.HasMember(COLOR); + canvas->color = _getNumber(rawCanvas, COLOR, 0); + canvas->aabb.x = _getNumber(rawCanvas, X, 0.0f) * armature->scale; + canvas->aabb.y = _getNumber(rawCanvas, Y, 0.0f) * armature->scale; + canvas->aabb.width = _getNumber(rawCanvas, WIDTH, 0.0f) * armature->scale; + canvas->aabb.height = _getNumber(rawCanvas, HEIGHT, 0.0f) * armature->scale; + // + armature->canvas = canvas; + } + + if (rawData.HasMember(AABB)) { + const auto &rawAABB = rawData[AABB]; + armature->aabb.x = _getNumber(rawAABB, X, 0.0f) * armature->scale; + armature->aabb.y = _getNumber(rawAABB, Y, 0.0f) * armature->scale; + armature->aabb.width = _getNumber(rawAABB, WIDTH, 0.0f) * armature->scale; + armature->aabb.height = _getNumber(rawAABB, HEIGHT, 0.0f) * armature->scale; + } + + if (rawData.HasMember(BONE)) { + const auto &rawBones = rawData[BONE]; + for (std::size_t i = 0, l = rawBones.Size(); i < l; ++i) { + const auto &rawBone = rawBones[i]; + const auto &parentName = _getString(rawBone, PARENT, ""); + const auto bone = _parseBone(rawBone); + + if (!parentName.empty()) // Get bone parent. + { + const auto parent = armature->getBone(parentName); + if (parent != nullptr) { + bone->parent = parent; + } else // Cache. + { + auto &cacheBones = _cacheBones[parentName]; + cacheBones.push_back(bone); + } + } + + auto iterator = _cacheBones.find(bone->name); + if (iterator != _cacheBones.end()) { + for (const auto child : _cacheBones[bone->name]) { + child->parent = bone; + } + + _cacheBones.erase(iterator); + } + + armature->addBone(bone); + _rawBones.push_back(bone); // Cache raw bones sort. + } + } + + if (rawData.HasMember(IK)) { + const auto &rawIKS = rawData[IK]; + for (std::size_t i = 0, l = rawIKS.Size(); i < l; ++i) { + const auto constraint = _parseIKConstraint(rawIKS[i]); + if (constraint) { + armature->addConstraint(constraint); + } + } + } + + armature->sortBones(); + + if (rawData.HasMember(SLOT)) { + int zOrder = 0; + const auto &rawSlots = rawData[SLOT]; + for (std::size_t i = 0, l = rawSlots.Size(); i < l; ++i) { + armature->addSlot(_parseSlot(rawSlots[i], zOrder++)); + } + } + + if (rawData.HasMember(SKIN)) { + const auto &rawSkins = rawData[SKIN]; + for (std::size_t i = 0, l = rawSkins.Size(); i < l; ++i) { + armature->addSkin(_parseSkin(rawSkins[i])); + } + } + + for (std::size_t i = 0, l = _cacheRawMeshes.size(); i < l; ++i) // Link mesh. + { + const auto rawMeshData = _cacheRawMeshes[i]; + const auto &shareName = _getString(*rawMeshData, SHARE, ""); + if (shareName.empty()) { + continue; + } + + auto skinName = _getString(*rawMeshData, SKIN, DEFAULT_NAME); + if (skinName.empty()) // + { + skinName = DEFAULT_NAME; + } + + const auto shareMesh = armature->getMesh(skinName, "", shareName); // TODO slot; + if (shareMesh == nullptr) { + continue; // Error. + } + + const auto mesh = _cacheMeshes[i]; + mesh->vertices.shareFrom(shareMesh->vertices); + } + + if (rawData.HasMember(ANIMATION)) { + const auto &rawAnimations = rawData[ANIMATION]; + for (std::size_t i = 0, l = rawAnimations.Size(); i < l; ++i) { + armature->addAnimation(_parseAnimation(rawAnimations[i])); + } + } + + if (rawData.HasMember(DEFAULT_ACTIONS)) { + const auto &actions = _parseActionData(rawData[DEFAULT_ACTIONS], ActionType::Play, nullptr, nullptr); + for (const auto action : actions) { + armature->addAction(action, true); + + if (action->type == ActionType::Play) // Set default animation from default action. + { + const auto animation = armature->getAnimation(action->name); + if (animation != nullptr) { + armature->defaultAnimation = animation; + } + } + } + } + + if (rawData.HasMember(ACTIONS)) { + const auto &actions = _parseActionData(rawData[ACTIONS], ActionType::Play, nullptr, nullptr); + + for (const auto action : actions) { + armature->addAction(action, false); + } + } + + // Clear helper. + _rawBones.clear(); + _cacheRawMeshes.clear(); + _cacheMeshes.clear(); + _armature = nullptr; + + _weightSlotPose.clear(); + _weightBonePoses.clear(); + _cacheBones.clear(); + _slotChildActions.clear(); + + return armature; +} + +BoneData *JSONDataParser::_parseBone(const rapidjson::Value &rawData) { + const auto bone = BaseObject::borrowObject(); + bone->inheritTranslation = _getBoolean(rawData, INHERIT_TRANSLATION, true); + bone->inheritRotation = _getBoolean(rawData, INHERIT_ROTATION, true); + bone->inheritScale = _getBoolean(rawData, INHERIT_SCALE, true); + bone->inheritReflection = _getBoolean(rawData, INHERIT_REFLECTION, true); + bone->length = _getNumber(rawData, LENGTH, 0.0f) * _armature->scale; + bone->name = _getString(rawData, NAME, ""); + + if (rawData.HasMember(TRANSFORM)) { + _parseTransform(rawData[TRANSFORM], bone->transform, _armature->scale); + } + + return bone; +} + +ConstraintData *JSONDataParser::_parseIKConstraint(const rapidjson::Value &rawData) { + const auto bone = _armature->getBone(_getString(rawData, BONE, "")); + if (bone == nullptr) { + return nullptr; + } + + const auto target = _armature->getBone(_getString(rawData, TARGET, "")); + if (target == nullptr) { + return nullptr; + } + + const auto constraint = BaseObject::borrowObject(); + constraint->scaleEnabled = _getBoolean(rawData, SCALE, false); + constraint->bendPositive = _getBoolean(rawData, BEND_POSITIVE, true); + constraint->weight = _getNumber(rawData, WEIGHT, 1.0f); + constraint->name = _getString(rawData, NAME, ""); + constraint->bone = bone; + constraint->target = target; + + const auto chain = _getNumber(rawData, CHAIN, (unsigned)0); + if (chain > 0 && bone->parent != nullptr) { + constraint->root = bone->parent; + constraint->bone = bone; + } else { + constraint->root = bone; + constraint->bone = nullptr; + } + + return constraint; +} + +SlotData *JSONDataParser::_parseSlot(const rapidjson::Value &rawData, int zOrder) { + const auto slot = BaseObject::borrowObject(); + slot->displayIndex = _getNumber(rawData, DISPLAY_INDEX, (int)0); + slot->zOrder = zOrder; + slot->name = _getString(rawData, NAME, ""); + slot->parent = _armature->getBone(_getString(rawData, PARENT, "")); + + if (rawData.HasMember(BLEND_MODE) && rawData[BLEND_MODE].IsString()) { + slot->blendMode = _getBlendMode(rawData[BLEND_MODE].GetString()); + } else { + slot->blendMode = (BlendMode)_getNumber(rawData, BLEND_MODE, (int)BlendMode::Normal); + } + + if (rawData.HasMember(COLOR)) { + slot->color = SlotData::createColor(); + _parseColorTransform(rawData[COLOR], *slot->color); + } else { + slot->color = &SlotData::DEFAULT_COLOR; + } + + if (rawData.HasMember(ACTIONS)) { + _slotChildActions[slot->name] = _parseActionData(rawData[ACTIONS], ActionType::Play, nullptr, nullptr); + } + + return slot; +} + +SkinData *JSONDataParser::_parseSkin(const rapidjson::Value &rawData) { + const auto skin = BaseObject::borrowObject(); + skin->name = _getString(rawData, NAME, DEFAULT_NAME); + if (skin->name.empty()) { + skin->name = DEFAULT_NAME; + } + + if (rawData.HasMember(SLOT)) { + const auto &rawSlots = rawData[SLOT]; + _skin = skin; + + for (std::size_t i = 0, l = rawSlots.Size(); i < l; ++i) { + const auto &rawSlot = rawSlots[i]; + const auto &slotName = _getString(rawSlot, NAME, ""); + const auto slot = _armature->getSlot(slotName); + if (slot != nullptr) { + _slot = slot; + + if (rawSlot.HasMember(DISPLAY)) { + const auto &rawDisplays = rawSlot[DISPLAY]; + for (std::size_t j = 0, lJ = rawDisplays.Size(); j < lJ; ++j) { + const auto &rawDisplay = rawDisplays[j]; + if (!rawDisplay.IsNull()) { + skin->addDisplay(slotName, _parseDisplay(rawDisplay)); + } else { + skin->addDisplay(slotName, nullptr); + } + } + } + + _slot = nullptr; + } + } + + _skin = nullptr; + } + + return skin; +} + +DisplayData *JSONDataParser::_parseDisplay(const rapidjson::Value &rawData) { + const auto &name = _getString(rawData, NAME, ""); + const auto &path = _getString(rawData, PATH, ""); + auto type = DisplayType::Image; + DisplayData *display = nullptr; + + if (rawData.HasMember(TYPE) && rawData[TYPE].IsString()) { + type = _getDisplayType(rawData[TYPE].GetString()); + } else { + type = (DisplayType)_getNumber(rawData, TYPE, (int)DisplayType::Image); + } + + switch (type) { + case dragonBones::DisplayType::Image: { + const auto imageDisplay = BaseObject::borrowObject(); + imageDisplay->name = name; + imageDisplay->path = !path.empty() ? path : name; + _parsePivot(rawData, *imageDisplay); + + display = imageDisplay; + break; + } + + case dragonBones::DisplayType::Armature: { + const auto armatureDisplay = BaseObject::borrowObject(); + armatureDisplay->name = name; + armatureDisplay->path = !path.empty() ? path : name; + armatureDisplay->inheritAnimation = true; + + if (rawData.HasMember(ACTIONS)) { + const auto &actions = _parseActionData(rawData[ACTIONS], ActionType::Play, nullptr, nullptr); + + for (const auto action : actions) { + armatureDisplay->addAction(action); + } + } else if (_slotChildActions.find(_slot->name) != _slotChildActions.cend()) { + const auto displays = _skin->getDisplays(_slot->name); + if (displays == nullptr ? _slot->displayIndex == 0 : (std::size_t)_slot->displayIndex == displays->size()) { + for (const auto action : _slotChildActions[_slot->name]) { + armatureDisplay->addAction(action); + } + + _slotChildActions.erase(_slotChildActions.find(_slot->name)); + } + } + + display = armatureDisplay; + break; + } + + case dragonBones::DisplayType::Mesh: { + const auto meshDisplay = BaseObject::borrowObject(); + meshDisplay->vertices.inheritDeform = _getBoolean(rawData, INHERIT_DEFORM, true); + meshDisplay->name = name; + meshDisplay->path = !path.empty() ? path : name; + meshDisplay->vertices.data = _data; + + if (rawData.HasMember(SHARE)) { + _cacheRawMeshes.push_back(&rawData); + _cacheMeshes.push_back(meshDisplay); + } else { + _parseMesh(rawData, *meshDisplay); + } + + display = meshDisplay; + break; + } + + case dragonBones::DisplayType::BoundingBox: { + const auto boundingBox = _parseBoundingBox(rawData); + if (boundingBox != nullptr) { + const auto boundingBoxDisplay = BaseObject::borrowObject(); + boundingBoxDisplay->name = name; + boundingBoxDisplay->path = !path.empty() ? path : name; + boundingBoxDisplay->boundingBox = boundingBox; + + display = boundingBoxDisplay; + } + break; + } + case dragonBones::DisplayType::Path: + default: + // suppress warning + //TODO: + break; + } + + if (display != nullptr && rawData.HasMember(TRANSFORM)) { + _parseTransform(rawData[TRANSFORM], display->transform, _armature->scale); + } + + return display; +} + +void JSONDataParser::_parsePivot(const rapidjson::Value &rawData, ImageDisplayData &display) { + if (rawData.HasMember(PIVOT)) { + const auto &rawPivot = rawData[PIVOT]; + display.pivot.x = _getNumber(rawPivot, X, 0.0f); + display.pivot.y = _getNumber(rawPivot, Y, 0.0f); + } else { + display.pivot.x = 0.5f; + display.pivot.y = 0.5f; + } +} + +void JSONDataParser::_parseMesh(const rapidjson::Value &rawData, MeshDisplayData &mesh) { + const auto &rawVertices = rawData[VERTICES]; + const auto &rawUVs = rawData[UVS]; + const auto &rawTriangles = rawData[TRIANGLES]; + const auto vertexCount = rawVertices.Size() / 2; + const auto triangleCount = rawTriangles.Size() / 3; + const auto vertexOffset = _floatArray.size(); + const auto uvOffset = vertexOffset + vertexCount * 2; + const auto meshOffset = _intArray.size(); + const auto meshName = _skin->name + "_" + _slot->name + "_" + mesh.name; // Cache pose data. + + mesh.vertices.offset = meshOffset; + _intArray.resize(_intArray.size() + 1 + 1 + 1 + 1 + triangleCount * 3); + _intArray[meshOffset + (unsigned)BinaryOffset::MeshVertexCount] = vertexCount; + _intArray[meshOffset + (unsigned)BinaryOffset::MeshTriangleCount] = triangleCount; + _intArray[meshOffset + (unsigned)BinaryOffset::MeshFloatOffset] = vertexOffset; + for (std::size_t i = 0, l = triangleCount * 3; i < l; ++i) { + _intArray[meshOffset + (unsigned)BinaryOffset::MeshVertexIndices + i] = rawTriangles[i].GetUint(); + } + + _floatArray.resize(_floatArray.size() + vertexCount * 2 + vertexCount * 2); + for (std::size_t i = 0, l = vertexCount * 2; i < l; ++i) { + _floatArray[vertexOffset + i] = rawVertices[i].GetDouble(); + _floatArray[uvOffset + i] = rawUVs[i].GetDouble(); + } + + if (rawData.HasMember(WEIGHTS)) { + const auto &rawWeights = rawData[WEIGHTS]; + const auto &rawSlotPose = rawData[SLOT_POSE]; + const auto &rawBonePoses = rawData[BONE_POSE]; + const auto &sortedBones = _armature->sortedBones; + std::vector weightBoneIndices; + const unsigned weightBoneCount = rawBonePoses.Size() / 7; + const auto floatOffset = _floatArray.size(); + const auto weightCount = (rawWeights.Size() - vertexCount) / 2; // uint + const auto weightOffset = _intArray.size(); + const auto weight = BaseObject::borrowObject(); + + weight->count = weightCount; + weight->offset = weightOffset; + weightBoneIndices.resize(weightBoneCount); + _intArray.resize(_intArray.size() + 1 + 1 + weightBoneCount + vertexCount + weightCount); + _intArray[weightOffset + (unsigned)BinaryOffset::WeigthFloatOffset] = floatOffset; + + for (std::size_t i = 0; i < weightBoneCount; ++i) { + const auto rawBoneIndex = rawBonePoses[i * 7].GetUint(); + const auto bone = _rawBones[rawBoneIndex]; + weight->addBone(bone); + weightBoneIndices[i] = rawBoneIndex; + _intArray[weightOffset + (unsigned)BinaryOffset::WeigthBoneIndices + i] = indexOf(sortedBones, bone); + } + + _floatArray.resize(_floatArray.size() + weightCount * 3); + + _helpMatrixA.a = rawSlotPose[0].GetDouble(); + _helpMatrixA.b = rawSlotPose[1].GetDouble(); + _helpMatrixA.c = rawSlotPose[2].GetDouble(); + _helpMatrixA.d = rawSlotPose[3].GetDouble(); + _helpMatrixA.tx = rawSlotPose[4].GetDouble(); + _helpMatrixA.ty = rawSlotPose[5].GetDouble(); + + for ( + std::size_t i = 0, iW = 0, iB = weightOffset + (unsigned)BinaryOffset::WeigthBoneIndices + weightBoneCount, iV = floatOffset; + i < vertexCount; + ++i) { + const auto iD = i * 2; + const auto vertexBoneCount = rawWeights[iW++].GetUint(); + _intArray[iB++] = vertexBoneCount; + + auto x = _floatArray[vertexOffset + iD]; + auto y = _floatArray[vertexOffset + iD + 1]; + _helpMatrixA.transformPoint(x, y, _helpPoint); + + x = _helpPoint.x; + y = _helpPoint.y; + + for (std::size_t j = 0; j < vertexBoneCount; ++j) { + const auto rawBoneIndex = rawWeights[iW++].GetUint(); + const auto boneIndex = indexOf(weightBoneIndices, rawBoneIndex); + const auto matrixOffset = boneIndex * 7 + 1; + + _helpMatrixB.a = rawBonePoses[matrixOffset + 0].GetDouble(); + _helpMatrixB.b = rawBonePoses[matrixOffset + 1].GetDouble(); + _helpMatrixB.c = rawBonePoses[matrixOffset + 2].GetDouble(); + _helpMatrixB.d = rawBonePoses[matrixOffset + 3].GetDouble(); + _helpMatrixB.tx = rawBonePoses[matrixOffset + 4].GetDouble(); + _helpMatrixB.ty = rawBonePoses[matrixOffset + 5].GetDouble(); + _helpMatrixB.invert(); + _helpMatrixB.transformPoint(x, y, _helpPoint); + + _intArray[iB++] = boneIndex; + _floatArray[iV++] = rawWeights[iW++].GetDouble(); + _floatArray[iV++] = _helpPoint.x; + _floatArray[iV++] = _helpPoint.y; + } + } + + mesh.vertices.weight = weight; + _weightSlotPose[meshName] = &rawSlotPose; + _weightBonePoses[meshName] = &rawBonePoses; + } +} + +BoundingBoxData *JSONDataParser::_parseBoundingBox(const rapidjson::Value &rawData) { + BoundingBoxData *boundingBox = nullptr; + BoundingBoxType type = BoundingBoxType::Rectangle; + if (rawData.HasMember(SUB_TYPE) && rawData[SUB_TYPE].IsString()) { + type = _getBoundingBoxType(rawData[SUB_TYPE].GetString()); + } else { + type = (BoundingBoxType)_getNumber(rawData, SUB_TYPE, (int)type); + } + + switch (type) { + case BoundingBoxType::Rectangle: + boundingBox = BaseObject::borrowObject(); + break; + + case BoundingBoxType::Ellipse: + boundingBox = BaseObject::borrowObject(); + break; + + case BoundingBoxType::Polygon: + boundingBox = _parsePolygonBoundingBox(rawData); + break; + } + + if (boundingBox != nullptr) { + boundingBox->color = _getNumber(rawData, COLOR, 0x000000); + if (boundingBox->type == BoundingBoxType::Rectangle || boundingBox->type == BoundingBoxType::Ellipse) { + boundingBox->width = _getNumber(rawData, WIDTH, 0.0f); + boundingBox->height = _getNumber(rawData, HEIGHT, 0.0f); + } + } + + return boundingBox; +} + +PolygonBoundingBoxData *JSONDataParser::_parsePolygonBoundingBox(const rapidjson::Value &rawData) { + const auto polygonBoundingBox = BaseObject::borrowObject(); + + if (rawData.HasMember(VERTICES)) { + const auto &rawVertices = rawData[VERTICES]; + auto &vertices = polygonBoundingBox->vertices; + + polygonBoundingBox->vertices.resize(rawVertices.Size()); + + for (std::size_t i = 0, l = rawVertices.Size(); i < l; i += 2) { + const auto x = rawVertices[i].GetDouble(); + const auto y = rawVertices[i + 1].GetDouble(); + vertices[i] = x; + vertices[i + 1] = y; + + // AABB. + if (i == 0) { + polygonBoundingBox->x = x; + polygonBoundingBox->y = y; + polygonBoundingBox->width = x; + polygonBoundingBox->height = y; + } else { + if (x < polygonBoundingBox->x) { + polygonBoundingBox->x = x; + } else if (x > polygonBoundingBox->width) { + polygonBoundingBox->width = x; + } + + if (y < polygonBoundingBox->y) { + polygonBoundingBox->y = y; + } else if (y > polygonBoundingBox->height) { + polygonBoundingBox->height = y; + } + } + } + + polygonBoundingBox->width -= polygonBoundingBox->x; + polygonBoundingBox->height -= polygonBoundingBox->y; + } else { + DRAGONBONES_ASSERT(false, "Data error.\n Please reexport DragonBones Data to fixed the bug."); + } + + return polygonBoundingBox; +} + +AnimationData *JSONDataParser::_parseAnimation(const rapidjson::Value &rawData) { + const auto animation = BaseObject::borrowObject(); + animation->frameCount = std::max(_getNumber(rawData, DURATION, (unsigned)1), (unsigned)1); + animation->playTimes = _getNumber(rawData, PLAY_TIMES, (unsigned)1); + animation->duration = (float)animation->frameCount / _armature->frameRate; // float + animation->fadeInTime = _getNumber(rawData, FADE_IN_TIME, 0.0f); + animation->scale = _getNumber(rawData, SCALE, 1.0f); + animation->name = _getString(rawData, NAME, DEFAULT_NAME); + + if (animation->name.empty()) { + animation->name = DEFAULT_NAME; + } + + animation->frameIntOffset = _frameIntArray.size(); + animation->frameFloatOffset = _frameFloatArray.size(); + animation->frameOffset = _frameArray.size(); + + _animation = animation; + + if (rawData.HasMember(FRAME)) { + const auto &rawFrames = rawData[FRAME]; + const auto keyFrameCount = rawFrames.Size(); + if (keyFrameCount > 0) { + for (std::size_t i = 0, frameStart = 0; i < keyFrameCount; ++i) { + const auto &rawFrame = rawFrames[i]; + _parseActionDataInFrame(rawFrame, frameStart, nullptr, nullptr); + frameStart += _getNumber(rawFrame, DURATION, (unsigned)1); + } + } + } + + if (rawData.HasMember(Z_ORDER)) { + _animation->zOrderTimeline = _parseTimeline( + rawData[Z_ORDER], FRAME, TimelineType::ZOrder, + false, false, 0, + std::bind(&JSONDataParser::_parseZOrderFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + } + + if (rawData.HasMember(BONE)) { + const auto &rawTimelines = rawData[BONE]; + for (std::size_t i = 0, l = rawTimelines.Size(); i < l; ++i) { + _parseBoneTimeline(rawTimelines[i]); + } + } + + if (rawData.HasMember(SLOT)) { + const auto &rawTimelines = rawData[SLOT]; + for (std::size_t i = 0, l = rawTimelines.Size(); i < l; ++i) { + _parseSlotTimeline(rawTimelines[i]); + } + } + + if (rawData.HasMember(FFD)) { + const auto &rawTimelines = rawData[FFD]; + for (std::size_t i = 0, l = rawTimelines.Size(); i < l; ++i) { + const auto &rawTimeline = rawTimelines[i]; + auto skinName = _getString(rawTimeline, SKIN, DEFAULT_NAME); + const auto &slotName = _getString(rawTimeline, SLOT, ""); + const auto &displayName = _getString(rawTimeline, NAME, ""); + + if (skinName.empty()) // + { + skinName = DEFAULT_NAME; + } + + _slot = _armature->getSlot(slotName); + _mesh = _armature->getMesh(skinName, slotName, displayName); + if (_slot == nullptr || _mesh == nullptr) { + continue; + } + + const auto timeline = _parseTimeline( + rawTimeline, FRAME, TimelineType::SlotDeform, + false, true, 0, + std::bind(&JSONDataParser::_parseSlotFFDFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + if (timeline != nullptr) { + _animation->addSlotTimeline(_slot, timeline); + } + + _slot = nullptr; + _mesh = nullptr; + } + } + + if (rawData.HasMember(IK)) { + const auto &rawTimelines = rawData[IK]; + for (std::size_t i = 0, l = rawTimelines.Size(); i < l; ++i) { + const auto &rawTimeline = rawTimelines[i]; + const auto &constraintName = _getString(rawTimeline, NAME, ""); + const auto constraint = _armature->getConstraint(constraintName); + if (constraint == nullptr) { + continue; + } + + const auto timeline = _parseTimeline( + rawTimeline, FRAME, TimelineType::IKConstraint, + true, false, 2, + std::bind(&JSONDataParser::_parseIKConstraintFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + + if (timeline != nullptr) { + _animation->addConstraintTimeline(constraint, timeline); + } + } + } + + if (_actionFrames.size() > 0) { + std::sort(_actionFrames.begin(), _actionFrames.end()); + + const auto timeline = _animation->actionTimeline = BaseObject::borrowObject(); + const auto keyFrameCount = _actionFrames.size(); + timeline->type = TimelineType::Action; + timeline->offset = _timelineArray.size(); + _timelineArray.resize(_timelineArray.size() + 1 + 1 + 1 + 1 + 1 + keyFrameCount); + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineScale] = 100; + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineOffset] = 0; + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineKeyFrameCount] = keyFrameCount; + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameValueCount] = 0; + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameValueOffset] = 0; + + _timeline = timeline; + + if (keyFrameCount == 1) { + timeline->frameIndicesOffset = -1; + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameOffset + 0] = _parseCacheActionFrame(_actionFrames[0]) - _animation->frameOffset; + } else { + const auto totalFrameCount = _animation->frameCount + 1; // One more frame than animation. + auto &frameIndices = _data->frameIndices; + timeline->frameIndicesOffset = frameIndices.size(); + frameIndices.resize(frameIndices.size() + totalFrameCount); + + for ( + std::size_t i = 0, iK = 0, frameStart = 0, frameCount = 0; + i < totalFrameCount; + ++i) { + if (frameStart + frameCount <= i && iK < keyFrameCount) { + auto &frame = _actionFrames[iK]; + frameStart = frame.frameStart; + if (iK == keyFrameCount - 1) { + frameCount = _animation->frameCount - frameStart; + } else { + frameCount = _actionFrames[iK + 1].frameStart - frameStart; + } + + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameOffset + iK] = _parseActionFrame(frame, frameStart, frameCount) - _animation->frameOffset; + iK++; + } + + frameIndices[timeline->frameIndicesOffset + i] = iK - 1; + } + } + + _timeline = nullptr; + _actionFrames.clear(); + } + + _animation = nullptr; + + return animation; +} + +TimelineData *JSONDataParser::_parseTimeline( + const rapidjson::Value &rawData, const char *framesKey, TimelineType type, + bool addIntOffset, bool addFloatOffset, unsigned frameValueCount, + const std::function &frameParser) { + if (!rawData.HasMember(framesKey)) { + return nullptr; + } + + const auto &rawFrames = rawData[framesKey]; + const auto keyFrameCount = rawFrames.Size(); + if (keyFrameCount == 0) { + return nullptr; + } + + const auto timeline = BaseObject::borrowObject(); + timeline->type = type; + timeline->offset = _timelineArray.size(); + _timelineArray.resize(_timelineArray.size() + 1 + 1 + 1 + 1 + 1 + keyFrameCount); + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineScale] = _getNumber(rawData, SCALE, 1.0f) * 100.f; + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineOffset] = _getNumber(rawData, OFFSET, 0.0f) * 100.f; + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineKeyFrameCount] = keyFrameCount; + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameValueCount] = frameValueCount; + if (addIntOffset) { + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameValueOffset] = _frameIntArray.size() - _animation->frameIntOffset; + } else if (addFloatOffset) { + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameValueOffset] = _frameFloatArray.size() - _animation->frameFloatOffset; + } else { + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameValueOffset] = 0; + } + + _timeline = timeline; + + if (keyFrameCount == 1) // Only one frame. + { + timeline->frameIndicesOffset = -1; + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameOffset + 0] = frameParser(rawFrames[0], 0, 0) - _animation->frameOffset; + } else { + unsigned frameIndicesOffset = 0; + auto &frameIndices = _data->frameIndices; + const auto totalFrameCount = _animation->frameCount + 1; // One more frame than animation. + frameIndicesOffset = frameIndices.size(); + frameIndices.resize(frameIndicesOffset + totalFrameCount); + timeline->frameIndicesOffset = frameIndicesOffset; + + for ( + std::size_t i = 0, iK = 0, frameStart = 0, frameCount = 0; + i < totalFrameCount; + ++i) { + if (frameStart + frameCount <= i && iK < keyFrameCount) { + const auto &rawFrame = rawFrames[iK]; + frameStart = i; + frameCount = _getNumber(rawFrame, DURATION, (unsigned)1); + if (iK == keyFrameCount - 1) { + frameCount = _animation->frameCount - frameStart; + } + + _timelineArray[timeline->offset + (unsigned)BinaryOffset::TimelineFrameOffset + iK] = frameParser(rawFrame, frameStart, frameCount) - _animation->frameOffset; + iK++; + } + + frameIndices[frameIndicesOffset + i] = iK - 1; + } + } + + _timeline = nullptr; + + return timeline; +} + +void JSONDataParser::_parseBoneTimeline(const rapidjson::Value &rawData) { + const auto bone = _armature->getBone(_getString(rawData, NAME, "")); + if (bone == nullptr) { + return; + } + + _bone = bone; + _slot = _armature->getSlot(_bone->name); + + if (rawData.HasMember(TRANSLATE_FRAME)) { + const auto timeline = _parseTimeline( + rawData, TRANSLATE_FRAME, TimelineType::BoneTranslate, + false, true, 2, + std::bind(&JSONDataParser::_parseBoneTranslateFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + if (timeline != nullptr) { + _animation->addBoneTimeline(bone, timeline); + } + } + + if (rawData.HasMember(ROTATE_FRAME)) { + const auto timeline = _parseTimeline( + rawData, ROTATE_FRAME, TimelineType::BoneRotate, + false, true, 2, + std::bind(&JSONDataParser::_parseBoneRotateFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + if (timeline != nullptr) { + _animation->addBoneTimeline(bone, timeline); + } + } + + if (rawData.HasMember(SCALE_FRAME)) { + const auto timeline = _parseTimeline( + rawData, SCALE_FRAME, TimelineType::BoneScale, + false, true, 2, + std::bind(&JSONDataParser::_parseBoneScaleFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + if (timeline != nullptr) { + _animation->addBoneTimeline(bone, timeline); + } + } + + if (rawData.HasMember(FRAME)) { + const auto timeline = _parseTimeline( + rawData, FRAME, TimelineType::BoneAll, + false, true, 6, + std::bind(&JSONDataParser::_parseBoneAllFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + if (timeline != nullptr) { + _animation->addBoneTimeline(bone, timeline); + } + } + + _bone = nullptr; + _slot = nullptr; +} + +void JSONDataParser::_parseSlotTimeline(const rapidjson::Value &rawData) { + const auto slot = _armature->getSlot(_getString(rawData, NAME, "")); + if (slot == nullptr) { + return; + } + + TimelineData *displayTimeline = nullptr; + TimelineData *colorTimeline = nullptr; + _slot = slot; + + if (rawData.HasMember(DISPLAY_FRAME)) { + displayTimeline = _parseTimeline( + rawData, DISPLAY_FRAME, TimelineType::SlotDisplay, + false, false, 0, + std::bind(&JSONDataParser::_parseSlotDisplayFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + } else { + displayTimeline = _parseTimeline( + rawData, FRAME, TimelineType::SlotDisplay, + false, false, 0, + std::bind(&JSONDataParser::_parseSlotDisplayFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + } + + if (rawData.HasMember(COLOR_FRAME)) { + colorTimeline = _parseTimeline( + rawData, COLOR_FRAME, TimelineType::SlotColor, + true, false, 1, + std::bind(&JSONDataParser::_parseSlotColorFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + } else { + colorTimeline = _parseTimeline( + rawData, FRAME, TimelineType::SlotColor, + true, false, 1, + std::bind(&JSONDataParser::_parseSlotColorFrame, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + } + + if (displayTimeline != nullptr) { + _animation->addSlotTimeline(slot, displayTimeline); + } + + if (colorTimeline != nullptr) { + _animation->addSlotTimeline(slot, colorTimeline); + } + + _slot = nullptr; +} + +unsigned JSONDataParser::_parseFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + const auto frameOffset = _frameArray.size(); + _frameArray.resize(_frameArray.size() + 1); + _frameArray[frameOffset + (unsigned)BinaryOffset::FramePosition] = frameStart; + + return frameOffset; +} + +unsigned JSONDataParser::_parseTweenFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + const auto frameOffset = _parseFrame(rawData, frameStart, frameCount); + + if (frameCount > 0) { + if (rawData.HasMember(CURVE)) { + const auto sampleCount = frameCount + 1; + _helpArray.resize(sampleCount); + _samplingEasingCurve(rawData[CURVE], _helpArray); + _frameArray.resize(_frameArray.size() + 1 + 1 + _helpArray.size()); + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenType] = (int)TweenType::Curve; + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenEasingOrCurveSampleCount] = sampleCount; + for (std::size_t i = 0; i < sampleCount; ++i) { + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameCurveSamples + i] = _helpArray[i] * 10000.0f; + } + } else { + const auto noTween = -2.0f; + auto tweenEasing = noTween; + if (rawData.HasMember(TWEEN_EASING)) { + tweenEasing = _getNumber(rawData, TWEEN_EASING, noTween); + } + + if (tweenEasing == noTween) { + _frameArray.resize(_frameArray.size() + 1); + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenType] = (int16_t)TweenType::None; + } else if (tweenEasing == 0.0f) { + _frameArray.resize(_frameArray.size() + 1); + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenType] = (int16_t)TweenType::Line; + } else if (tweenEasing < 0.0f) { + _frameArray.resize(_frameArray.size() + 1 + 1); + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenType] = (int16_t)TweenType::QuadIn; + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenEasingOrCurveSampleCount] = -tweenEasing * 100.0f; + } else if (tweenEasing <= 1.0f) { + _frameArray.resize(_frameArray.size() + 1 + 1); + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenType] = (int16_t)TweenType::QuadOut; + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenEasingOrCurveSampleCount] = tweenEasing * 100.0f; + } else { + _frameArray.resize(_frameArray.size() + 1 + 1); + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenType] = (int16_t)TweenType::QuadInOut; + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenEasingOrCurveSampleCount] = tweenEasing * 100.0f - 100.0f; + } + } + } else { + _frameArray.resize(_frameArray.size() + 1); + _frameArray[frameOffset + (unsigned)BinaryOffset::FrameTweenType] = (int16_t)TweenType::None; + } + + return frameOffset; +} + +unsigned JSONDataParser::_parseActionFrame(const ActionFrame &frame, unsigned frameStart, unsigned frameCount) { + const auto frameOffset = _frameArray.size(); + const auto actionCount = frame.actions.size(); + _frameArray.resize(_frameArray.size() + 1 + 1 + actionCount); + _frameArray[frameOffset + (unsigned)BinaryOffset::FramePosition] = frameStart; + _frameArray[frameOffset + (unsigned)BinaryOffset::FramePosition + 1] = actionCount; // Action count. + + for (std::size_t i = 0; i < actionCount; ++i) // Action offsets. + { + _frameArray[frameOffset + (unsigned)BinaryOffset::FramePosition + 2 + i] = frame.actions[i]; + } + + return frameOffset; +} + +unsigned JSONDataParser::_parseZOrderFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + const auto frameOffset = _parseFrame(rawData, frameStart, frameCount); + + if (rawData.HasMember(Z_ORDER)) { + const auto &rawZOrder = rawData[Z_ORDER]; + if (!rawZOrder.Empty()) { + const auto slotCount = _armature->sortedSlots.size(); + std::vector unchanged; + std::vector zOrders; + unchanged.resize(slotCount - rawZOrder.Size() / 2); + zOrders.resize(slotCount); + + for (std::size_t i = 0; i < unchanged.size(); ++i) { + unchanged[i] = 0; + } + + for (std::size_t i = 0; i < slotCount; ++i) { + zOrders[i] = -1; + } + + unsigned originalIndex = 0; + unsigned unchangedIndex = 0; + for (std::size_t i = 0, l = rawZOrder.Size(); i < l; i += 2) { + const auto slotIndex = rawZOrder[i].GetInt(); + const auto zOrderOffset = rawZOrder[i + 1].GetInt(); + while (originalIndex != (unsigned)slotIndex) { + unchanged[unchangedIndex++] = originalIndex++; + } + + unsigned index = originalIndex + zOrderOffset; + // CC_ASSERT(index >= 0 && index < zOrders.size()); + + if (!(index >= 0 && index < zOrders.size())) { + originalIndex++; + continue; + } + zOrders[index] = originalIndex++; + } + + while (originalIndex < slotCount) { + unchanged[unchangedIndex++] = originalIndex++; + } + + _frameArray.resize(_frameArray.size() + 1 + slotCount); + _frameArray[frameOffset + 1] = slotCount; + + int i = slotCount; + while (i--) { + if (zOrders[i] == -1) { + // _frameArray[frameOffset + 2 + i] = unchanged[--unchangedIndex]; + + if (unchangedIndex > 0) { + _frameArray[frameOffset + 2 + i] = unchanged[--unchangedIndex]; + } else { + _frameArray[frameOffset + 2 + i] = 0; + } + } else { + _frameArray[frameOffset + 2 + i] = zOrders[i]; + } + } + + return frameOffset; + } + } + + _frameArray.resize(_frameArray.size() + 1); + _frameArray[frameOffset + 1] = 0; + + return frameOffset; +} + +unsigned JSONDataParser::_parseBoneAllFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + _helpTransform.identity(); + if (rawData.HasMember(TRANSFORM)) { + _parseTransform(rawData[TRANSFORM], _helpTransform, 1.0f); + } + + // Modify rotation. + auto rotation = _helpTransform.rotation; + if (frameStart != 0) { + if (_prevClockwise == 0) { + rotation = _prevRotation + Transform::normalizeRadian(rotation - _prevRotation); + } else { + if (_prevClockwise > 0 ? rotation >= _prevRotation : rotation <= _prevRotation) { + _prevClockwise = _prevClockwise > 0 ? _prevClockwise - 1 : _prevClockwise + 1; + } + + rotation = _prevRotation + rotation - _prevRotation + Transform::PI_D * _prevClockwise; + } + } + + _prevClockwise = _getNumber(rawData, TWEEN_ROTATE, 0.0f); + _prevRotation = rotation; + // + const auto frameOffset = _parseTweenFrame(rawData, frameStart, frameCount); + auto frameFloatOffset = _frameFloatArray.size(); + _frameFloatArray.resize(_frameFloatArray.size() + 6); + _frameFloatArray[frameFloatOffset++] = _helpTransform.x; + _frameFloatArray[frameFloatOffset++] = _helpTransform.y; + _frameFloatArray[frameFloatOffset++] = rotation; + _frameFloatArray[frameFloatOffset++] = _helpTransform.skew; + _frameFloatArray[frameFloatOffset++] = _helpTransform.scaleX; + _frameFloatArray[frameFloatOffset++] = _helpTransform.scaleY; + _parseActionDataInFrame(rawData, frameStart, _bone, _slot); + + return frameOffset; +} + +unsigned JSONDataParser::_parseBoneTranslateFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + const auto frameOffset = _parseTweenFrame(rawData, frameStart, frameCount); + auto frameFloatOffset = _frameFloatArray.size(); + _frameFloatArray.resize(_frameFloatArray.size() + 2); + _frameFloatArray[frameFloatOffset++] = _getNumber(rawData, X, 0.0f); + _frameFloatArray[frameFloatOffset++] = _getNumber(rawData, Y, 0.0f); + + return frameOffset; +} + +unsigned JSONDataParser::_parseBoneRotateFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + // Modify rotation. + auto rotation = _getNumber(rawData, ROTATE, 0.0f) * Transform::DEG_RAD; + if (frameStart != 0) { + if (_prevClockwise == 0) { + rotation = _prevRotation + Transform::normalizeRadian(rotation - _prevRotation); + } else { + if (_prevClockwise > 0 ? rotation >= _prevRotation : rotation <= _prevRotation) { + _prevClockwise = _prevClockwise > 0 ? _prevClockwise - 1 : _prevClockwise + 1; + } + + rotation = _prevRotation + rotation - _prevRotation + Transform::PI_D * _prevClockwise; + } + } + + _prevClockwise = _getNumber(rawData, CLOCK_WISE, 0.0f); + _prevRotation = rotation; + // + const auto frameOffset = _parseTweenFrame(rawData, frameStart, frameCount); + auto frameFloatOffset = _frameFloatArray.size(); + _frameFloatArray.resize(_frameFloatArray.size() + 2); + _frameFloatArray[frameFloatOffset++] = rotation; + _frameFloatArray[frameFloatOffset++] = _getNumber(rawData, SKEW, 0.0f) * Transform::DEG_RAD; + + return frameOffset; +} + +unsigned JSONDataParser::_parseBoneScaleFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + const auto frameOffset = _parseTweenFrame(rawData, frameStart, frameCount); + + auto frameFloatOffset = _frameFloatArray.size(); + _frameFloatArray.resize(_frameFloatArray.size() + 2); + _frameFloatArray[frameFloatOffset++] = _getNumber(rawData, X, 1.0f); + _frameFloatArray[frameFloatOffset++] = _getNumber(rawData, Y, 1.0f); + + return frameOffset; +} + +unsigned JSONDataParser::_parseSlotDisplayFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + const auto frameOffset = _parseFrame(rawData, frameStart, frameCount); + + _frameArray.resize(_frameArray.size() + 1); + + if (rawData.HasMember(VALUE)) { + _frameArray[frameOffset + 1] = _getNumber(rawData, VALUE, 0); + } else { + _frameArray[frameOffset + 1] = _getNumber(rawData, DISPLAY_INDEX, 0); + } + + _parseActionDataInFrame(rawData, frameStart, _slot->parent, _slot); + + return frameOffset; +} + +unsigned JSONDataParser::_parseSlotColorFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + const auto frameOffset = _parseTweenFrame(rawData, frameStart, frameCount); + auto colorOffset = -1; + + if (rawData.HasMember(VALUE) || rawData.HasMember(COLOR)) { + const auto &rawColor = rawData.HasMember(VALUE) ? rawData[VALUE] : rawData[COLOR]; + if ( + rawColor.HasMember(ALPHA_MULTIPLIER) || + rawColor.HasMember(RED_MULTIPLIER) || + rawColor.HasMember(GREEN_MULTIPLIER) || + rawColor.HasMember(BLUE_MULTIPLIER) || + rawColor.HasMember(ALPHA_OFFSET) || + rawColor.HasMember(RED_OFFSET) || + rawColor.HasMember(GREEN_OFFSET) || + rawColor.HasMember(BLUE_OFFSET)) { + _parseColorTransform(rawColor, _helpColorTransform); + colorOffset = _intArray.size(); + _intArray.resize(_intArray.size() + 8); + _intArray[colorOffset++] = _helpColorTransform.alphaMultiplier * 100; + _intArray[colorOffset++] = _helpColorTransform.redMultiplier * 100; + _intArray[colorOffset++] = _helpColorTransform.greenMultiplier * 100; + _intArray[colorOffset++] = _helpColorTransform.blueMultiplier * 100; + _intArray[colorOffset++] = _helpColorTransform.alphaOffset; + _intArray[colorOffset++] = _helpColorTransform.redOffset; + _intArray[colorOffset++] = _helpColorTransform.greenOffset; + _intArray[colorOffset++] = _helpColorTransform.blueOffset; + colorOffset -= 8; + } + } + + if (colorOffset < 0) { + if (_defaultColorOffset < 0) { + _defaultColorOffset = colorOffset = _intArray.size(); + _intArray.resize(_intArray.size() + 8); + _intArray[colorOffset++] = 100; + _intArray[colorOffset++] = 100; + _intArray[colorOffset++] = 100; + _intArray[colorOffset++] = 100; + _intArray[colorOffset++] = 0; + _intArray[colorOffset++] = 0; + _intArray[colorOffset++] = 0; + _intArray[colorOffset++] = 0; + } + + colorOffset = _defaultColorOffset; + } + + const auto frameIntOffset = _frameIntArray.size(); + _frameIntArray.resize(_frameIntArray.size() + 1); + _frameIntArray[frameIntOffset] = colorOffset; + + return frameOffset; +} + +unsigned JSONDataParser::_parseSlotFFDFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + const auto frameFloatOffset = _frameFloatArray.size(); + const auto frameOffset = _parseTweenFrame(rawData, frameStart, frameCount); + const auto offset = _getNumber(rawData, OFFSET, (unsigned)0); + const auto vertexCount = (unsigned)_intArray[_mesh->vertices.offset + (unsigned)BinaryOffset::MeshVertexCount]; + const auto meshName = _mesh->parent->name + "_" + _slot->name + "_" + _mesh->name; + const auto weight = _mesh->vertices.weight; + + auto x = 0.0f; + auto y = 0.0f; + unsigned iB = 0; + unsigned iV = 0; + if (weight != nullptr) { + const auto &rawSlotPose = *(_weightSlotPose[meshName]); + + _helpMatrixA.a = rawSlotPose[0].GetDouble(); + _helpMatrixA.b = rawSlotPose[1].GetDouble(); + _helpMatrixA.c = rawSlotPose[2].GetDouble(); + _helpMatrixA.d = rawSlotPose[3].GetDouble(); + _helpMatrixA.tx = rawSlotPose[4].GetDouble(); + _helpMatrixA.ty = rawSlotPose[5].GetDouble(); + + _frameFloatArray.resize(_frameFloatArray.size() + weight->count * 2); + iB = weight->offset + (unsigned)BinaryOffset::WeigthBoneIndices + weight->bones.size(); + } else { + _frameFloatArray.resize(_frameFloatArray.size() + vertexCount * 2); + } + + for ( + std::size_t i = 0; + i < vertexCount * 2; + i += 2) { + if (!rawData.HasMember(VERTICES)) // Fill 0. + { + x = 0.0f; + y = 0.0f; + } else { + if (i < offset || i - offset >= rawData[VERTICES].Size()) { + x = 0.0f; + } else { + x = rawData[VERTICES][i - offset].GetDouble(); + } + + if (i + 1 < offset || i + 1 - offset >= rawData[VERTICES].Size()) { + y = 0.0f; + } else { + y = rawData[VERTICES][i + 1 - offset].GetDouble(); + } + } + + if (weight != nullptr) // If mesh is skinned, transform point by bone bind pose. + { + const auto &rawBonePoses = *(_weightBonePoses[meshName]); + const unsigned vertexBoneCount = _intArray[iB++]; + + _helpMatrixA.transformPoint(x, y, _helpPoint, true); + x = _helpPoint.x; + y = _helpPoint.y; + + for (std::size_t j = 0; j < vertexBoneCount; ++j) { + const auto boneIndex = _intArray[iB++]; + const auto matrixOffset = boneIndex * 7 + 1; + + _helpMatrixB.a = rawBonePoses[matrixOffset + 0].GetDouble(); + _helpMatrixB.b = rawBonePoses[matrixOffset + 1].GetDouble(); + _helpMatrixB.c = rawBonePoses[matrixOffset + 2].GetDouble(); + _helpMatrixB.d = rawBonePoses[matrixOffset + 3].GetDouble(); + _helpMatrixB.tx = rawBonePoses[matrixOffset + 4].GetDouble(); + _helpMatrixB.ty = rawBonePoses[matrixOffset + 5].GetDouble(); + _helpMatrixB.invert(); + _helpMatrixB.transformPoint(x, y, _helpPoint, true); + + _frameFloatArray[frameFloatOffset + iV++] = _helpPoint.x; + _frameFloatArray[frameFloatOffset + iV++] = _helpPoint.y; + } + } else { + _frameFloatArray[frameFloatOffset + i] = x; + _frameFloatArray[frameFloatOffset + i + 1] = y; + } + } + + if (frameStart == 0) { + const auto frameIntOffset = _frameIntArray.size(); + _frameIntArray.resize(_frameIntArray.size() + 1 + 1 + 1 + 1 + 1); + _frameIntArray[frameIntOffset + (unsigned)BinaryOffset::DeformVertexOffset] = _mesh->vertices.offset; + _frameIntArray[frameIntOffset + (unsigned)BinaryOffset::DeformCount] = _frameFloatArray.size() - frameFloatOffset; + _frameIntArray[frameIntOffset + (unsigned)BinaryOffset::DeformValueCount] = _frameFloatArray.size() - frameFloatOffset; + _frameIntArray[frameIntOffset + (unsigned)BinaryOffset::DeformValueOffset] = 0; + _frameIntArray[frameIntOffset + (unsigned)BinaryOffset::DeformFloatOffset] = frameFloatOffset; + _timelineArray[_timeline->offset + (unsigned)BinaryOffset::TimelineFrameValueCount] = frameIntOffset - _animation->frameIntOffset; + } + + return frameOffset; +} + +unsigned JSONDataParser::_parseIKConstraintFrame(const rapidjson::Value &rawData, unsigned frameStart, unsigned frameCount) { + const auto frameOffset = _parseTweenFrame(rawData, frameStart, frameCount); + + auto frameIntOffset = _frameIntArray.size(); + _frameIntArray.resize(_frameIntArray.size() + 2); + _frameIntArray[frameIntOffset++] = _getBoolean(rawData, BEND_POSITIVE, true) ? 1 : 0; + _frameIntArray[frameIntOffset++] = std::round(_getNumber(rawData, WEIGHT, 1.0f) * 100.0f); + + return frameOffset; +} + +const std::vector &JSONDataParser::_parseActionData(const rapidjson::Value &rawData, ActionType type, BoneData *bone, SlotData *slot) { + static std::vector actions; + actions.clear(); + + if (rawData.IsString()) { + const auto action = BaseObject::borrowObject(); + action->type = type; + action->name = rawData.GetString(); + action->bone = bone; + action->slot = slot; + actions.push_back(action); + } else if (rawData.IsArray()) { + for (std::size_t iA = 0, lA = rawData.Size(); iA < lA; ++iA) { + const auto &rawAction = rawData[iA]; + const auto action = BaseObject::borrowObject(); + + if (rawAction.HasMember(GOTO_AND_PLAY)) { + action->type = ActionType::Play; + action->name = _getString(rawAction, GOTO_AND_PLAY, ""); + } else { + if (rawAction.HasMember(TYPE) && rawAction[TYPE].IsString()) { + action->type = _getActionType(rawAction[TYPE].GetString()); + } else { + action->type = (ActionType)_getNumber(rawAction, TYPE, (int)type); + } + + action->name = _getString(rawAction, NAME, ""); + } + + if (rawAction.HasMember(BONE)) { + const auto &boneName = _getString(rawAction, BONE, ""); + action->bone = _armature->getBone(boneName); + } else { + action->bone = bone; + } + + if (rawAction.HasMember(SLOT)) { + const auto &slotName = _getString(rawAction, SLOT, ""); + action->slot = _armature->getSlot(slotName); + } else { + action->slot = slot; + } + + if (rawAction.HasMember(INTS)) { + if (action->data == nullptr) { + action->data = BaseObject::borrowObject(); + } + + const auto &rawInts = rawAction[INTS]; + for (std::size_t i = 0, l = rawInts.Size(); i < l; ++i) { + action->data->addInt(rawInts[i].GetInt()); + } + } + + if (rawAction.HasMember(FLOATS)) { + if (action->data == nullptr) { + action->data = BaseObject::borrowObject(); + } + + const auto &rawFloats = rawAction[FLOATS]; + for (std::size_t i = 0, l = rawFloats.Size(); i < l; ++i) { + action->data->addFloat(rawFloats[i].GetDouble()); + } + } + + if (rawAction.HasMember(STRINGS)) { + if (action->data == nullptr) { + action->data = BaseObject::borrowObject(); + } + + const auto &rawStrings = rawAction[STRINGS]; + for (std::size_t i = 0, l = rawStrings.Size(); i < l; ++i) { + action->data->addString(rawStrings[i].GetString()); + } + } + + actions.push_back(action); + } + } + + return actions; +} + +void JSONDataParser::_parseTransform(const rapidjson::Value &rawData, Transform &transform, float scale) { + transform.x = _getNumber(rawData, X, 0.0f) * scale; + transform.y = _getNumber(rawData, Y, 0.0f) * scale; + + if (rawData.HasMember(ROTATE) || rawData.HasMember(SKEW)) { + transform.rotation = Transform::normalizeRadian(_getNumber(rawData, ROTATE, 0.0f) * Transform::DEG_RAD); + transform.skew = Transform::normalizeRadian(_getNumber(rawData, SKEW, 0.0f) * Transform::DEG_RAD); + } else if (rawData.HasMember(SKEW_X) || rawData.HasMember(SKEW_Y)) { + transform.rotation = Transform::normalizeRadian(_getNumber(rawData, SKEW_Y, 0.0f) * Transform::DEG_RAD); + transform.skew = Transform::normalizeRadian(_getNumber(rawData, SKEW_X, 0.0f) * Transform::DEG_RAD) - transform.rotation; + } + + transform.scaleX = _getNumber(rawData, SCALE_X, 1.0f); + transform.scaleY = _getNumber(rawData, SCALE_Y, 1.0f); +} + +void JSONDataParser::_parseColorTransform(const rapidjson::Value &rawData, ColorTransform &color) { + color.alphaMultiplier = _getNumber(rawData, ALPHA_MULTIPLIER, (int)100) * 0.01f; + color.redMultiplier = _getNumber(rawData, RED_MULTIPLIER, (int)100) * 0.01f; + color.greenMultiplier = _getNumber(rawData, GREEN_MULTIPLIER, (int)100) * 0.01f; + color.blueMultiplier = _getNumber(rawData, BLUE_MULTIPLIER, (int)100) * 0.01f; + color.alphaOffset = _getNumber(rawData, ALPHA_OFFSET, (int)0); + color.redOffset = _getNumber(rawData, RED_OFFSET, (int)0); + color.greenOffset = _getNumber(rawData, GREEN_OFFSET, (int)0); + color.blueOffset = _getNumber(rawData, BLUE_OFFSET, (int)0); +} + +void JSONDataParser::_parseArray(const rapidjson::Value &rawData) { + _intArray.clear(); + _floatArray.clear(); + _frameIntArray.clear(); + _frameFloatArray.clear(); + _frameArray.clear(); + _timelineArray.clear(); +} + +DragonBonesData *JSONDataParser::_parseDragonBonesData(const rapidjson::Value &rawData, float scale) { + const auto &version = _getString(rawData, VERSION, ""); + const auto &compatibleVersion = _getString(rawData, COMPATIBLE_VERSION, ""); + + if ( + indexOf(DATA_VERSIONS, version) >= 0 || + indexOf(DATA_VERSIONS, compatibleVersion) >= 0) { + const auto data = BaseObject::borrowObject(); + data->version = version; + data->name = _getString(rawData, NAME, ""); + data->frameRate = _getNumber(rawData, FRAME_RATE, 24); + + if (data->frameRate == 0) // Data error. + { + data->frameRate = 24; + } + + if (rawData.HasMember(ARMATURE)) { + _data = data; + _parseArray(rawData); + + const auto &rawArmatures = rawData[ARMATURE]; + for (std::size_t i = 0, l = rawArmatures.Size(); i < l; ++i) { + data->addArmature(_parseArmature(rawArmatures[i], scale)); + } + + if (data->binary == nullptr) { + // Align. + if (fmod(_intArray.size(), 2) != 0) { + _intArray.push_back(0); + } + + if (fmod(_frameIntArray.size(), 2) != 0) { + _frameIntArray.push_back(0); + } + + if (fmod(_frameArray.size(), 2) != 0) { + _frameArray.push_back(0); + } + + if (fmod(_timelineArray.size(), 2) != 0) { + _timelineArray.push_back(0); + } + + const auto l1 = _intArray.size() * 2; + const auto l2 = _floatArray.size() * 4; + const auto l3 = _frameIntArray.size() * 2; + const auto l4 = _frameFloatArray.size() * 4; + const auto l5 = _frameArray.size() * 2; + const auto l6 = _timelineArray.size() * 2; + + // NOTE: binary is freed in DragonBonesData::_onClear + char *binary = static_cast(malloc((l1 + l2 + l3 + l4 + l5 + l6) * sizeof(char))); + auto intArray = (int16_t *)binary; + auto floatArray = (float *)(binary + l1); + auto frameIntArray = (int16_t *)(binary + l1 + l2); + auto frameFloatArray = (float *)(binary + l1 + l2 + l3); + auto frameArray = (int16_t *)(binary + l1 + l2 + l3 + l4); + auto timelineArray = (uint16_t *)(binary + l1 + l2 + l3 + l4 + l5); + + for (std::size_t i = 0, l = _intArray.size(); i < l; ++i) { + intArray[i] = _intArray[i]; + } + + for (std::size_t i = 0, l = _floatArray.size(); i < l; ++i) { + floatArray[i] = _floatArray[i]; + } + + for (std::size_t i = 0, l = _frameIntArray.size(); i < l; ++i) { + frameIntArray[i] = _frameIntArray[i]; + } + + for (std::size_t i = 0, l = _frameFloatArray.size(); i < l; ++i) { + frameFloatArray[i] = _frameFloatArray[i]; + } + + for (std::size_t i = 0, l = _frameArray.size(); i < l; ++i) { + frameArray[i] = _frameArray[i]; + } + + for (std::size_t i = 0, l = _timelineArray.size(); i < l; ++i) { + timelineArray[i] = _timelineArray[i]; + } + + data->binary = binary; + data->intArray = intArray; + data->floatArray = floatArray; + data->frameIntArray = frameIntArray; + data->frameFloatArray = frameFloatArray; + data->frameArray = frameArray; + data->timelineArray = timelineArray; + } + + _defaultColorOffset = -1; + _data = nullptr; + } + + if (rawData.HasMember(TEXTURE_ATLAS)) { + _rawTextureAtlases = (rapidjson::Value *)&(rawData[TEXTURE_ATLAS]); + } + + return data; + } else { + DRAGONBONES_ASSERT( + false, + "Nonsupport data version: " + version + "\n" + + "Please convert DragonBones data to support version.\n" + + "Read more: https://github.com/DragonBones/Tools/"); + } + + return nullptr; +} + +void JSONDataParser::_parseTextureAtlasData(const rapidjson::Value &rawData, TextureAtlasData &textureAtlasData, float scale) { + textureAtlasData.format = _getTextureFormat(_getString(rawData, FORMAT, "")); + textureAtlasData.width = _getNumber(rawData, WIDTH, (unsigned)0); + textureAtlasData.height = _getNumber(rawData, HEIGHT, (unsigned)0); + textureAtlasData.scale = scale == 1.0f ? 1.0f / _getNumber(rawData, SCALE, 1.0f) : scale; + textureAtlasData.name = _getString(rawData, NAME, ""); + textureAtlasData.imagePath = _getString(rawData, IMAGE_PATH, ""); + + if (rawData.HasMember(SUB_TEXTURE)) { + const auto &rawTextures = rawData[SUB_TEXTURE]; + for (std::size_t i = 0, l = rawTextures.Size(); i < l; ++i) { + const auto &rawTexture = rawTextures[i]; + const auto textureData = textureAtlasData.createTexture(); + textureData->rotated = _getBoolean(rawTexture, ROTATED, false); + textureData->name = _getString(rawTexture, NAME, ""); + textureData->region.x = _getNumber(rawTexture, X, 0.0f); + textureData->region.y = _getNumber(rawTexture, Y, 0.0f); + textureData->region.width = _getNumber(rawTexture, WIDTH, 0.0f); + textureData->region.height = _getNumber(rawTexture, HEIGHT, 0.0f); + + const auto frameWidth = _getNumber(rawTexture, FRAME_WIDTH, -1.0f); + const auto frameHeight = _getNumber(rawTexture, FRAME_HEIGHT, -1.0f); + if (frameWidth > 0.0f && frameHeight > 0.0f) { + textureData->frame = TextureData::createRectangle(); + textureData->frame->x = _getNumber(rawTexture, FRAME_X, 0.0f); + textureData->frame->y = _getNumber(rawTexture, FRAME_Y, 0.0f); + textureData->frame->width = frameWidth; + textureData->frame->height = frameHeight; + } + + textureAtlasData.addTexture(textureData); + } + } +} + +DragonBonesData *JSONDataParser::parseDragonBonesData(const char *rawData, float scale) { + DRAGONBONES_ASSERT(rawData != nullptr, ""); + + rapidjson::Document document; + document.Parse(rawData); + + return _parseDragonBonesData(document, scale); +} + +bool JSONDataParser::parseTextureAtlasData(const char *rawData, TextureAtlasData &textureAtlasData, float scale) { + if (rawData == nullptr) { + if (_rawTextureAtlases == nullptr || _rawTextureAtlases->Empty()) { + return false; + } + + const auto &rawTextureAtlas = (*_rawTextureAtlases)[_rawTextureAtlasIndex++]; + _parseTextureAtlasData(rawTextureAtlas, textureAtlasData, scale); + if (_rawTextureAtlasIndex >= _rawTextureAtlases->Size()) { + _rawTextureAtlasIndex = 0; + _rawTextureAtlases = nullptr; + } + + return true; + } + + rapidjson::Document document; + document.Parse(rawData); + _parseTextureAtlasData(document, textureAtlasData, scale); + + return true; +} + +DRAGONBONES_NAMESPACE_END diff --git a/cocos/editor-support/dragonbones/parser/JSONDataParser.h b/cocos/editor-support/dragonbones/parser/JSONDataParser.h new file mode 100644 index 0000000..b1339c8 --- /dev/null +++ b/cocos/editor-support/dragonbones/parser/JSONDataParser.h @@ -0,0 +1,238 @@ +#ifndef DRAGONBONES_JSON_DATA_PARSER_H +#define DRAGONBONES_JSON_DATA_PARSER_H + +#include "DataParser.h" +#include "json/document.h" + +DRAGONBONES_NAMESPACE_BEGIN + +class ActionFrame { +public: + unsigned frameStart; + std::vector actions; + + bool operator<(const ActionFrame& b) const { + return frameStart < b.frameStart; + } +}; + +class JSONDataParser : public DataParser { + DRAGONBONES_DISALLOW_COPY_AND_ASSIGN(JSONDataParser) + +protected: + inline static bool _getBoolean(const rapidjson::Value& rawData, const char* key, bool defaultValue) { + if (rawData.HasMember(key)) { + const auto& value = rawData[key]; + if (value.IsBool()) { + return value.GetBool(); + } else if (value.IsString()) { + const std::string stringValue = value.GetString(); + if ( + stringValue == "0" || + stringValue == "NaN" || + stringValue == "" || + stringValue == "false" || + stringValue == "null" || + stringValue == "undefined") { + return false; + } + + return true; + } else if (value.IsNumber()) { + return value.GetInt() != 0; + } + } + + return defaultValue; + } + + inline static unsigned _getNumber(const rapidjson::Value& rawData, const char* key, unsigned defaultValue) { + if (rawData.HasMember(key)) { + return rawData[key].GetUint(); + } + + return defaultValue; + } + + inline static int _getNumber(const rapidjson::Value& rawData, const char* key, int defaultValue) { + if (rawData.HasMember(key)) { + return rawData[key].GetInt(); + } + + return defaultValue; + } + + inline static float _getNumber(const rapidjson::Value& rawData, const char* key, float defaultValue) { + if (rawData.HasMember(key) && rawData[key].IsNumber()) { + return rawData[key].GetFloat(); // cocos can not support GetFloat(); + } + + return defaultValue; + } + + inline static std::string _getString(const rapidjson::Value& rawData, const char* key, const std::string& defaultValue) { + if (rawData.HasMember(key)) { + if (rawData[key].IsString()) { + return rawData[key].GetString(); + } + + return dragonBones::to_string(rawData[key].GetDouble()); + } + + return defaultValue; + } + + inline static int _getParameter(const rapidjson::Value& rawData, std::size_t index, int defaultValue) { + if (rawData.Size() > index) { + return rawData[(int)index].GetInt(); + } + + return defaultValue; + } + + inline static float _getParameter(const rapidjson::Value& rawData, std::size_t index, float defaultValue) { + if (rawData.Size() > index) { + return rawData[(int)index].GetFloat(); + } + + return defaultValue; + } + + inline static std::string _getParameter(const rapidjson::Value& rawData, std::size_t index, const std::string& defaultValue) { + if (rawData.Size() > index) { + return rawData[(int)index].GetString(); + } + + return defaultValue; + } + +protected: + unsigned _rawTextureAtlasIndex; + std::vector _rawBones; + DragonBonesData* _data; + ArmatureData* _armature; + BoneData* _bone; + SlotData* _slot; + SkinData* _skin; + MeshDisplayData* _mesh; + AnimationData* _animation; + TimelineData* _timeline; + rapidjson::Value* _rawTextureAtlases; + +private: + int _defaultColorOffset; + int _prevClockwise; + float _prevRotation; + Matrix _helpMatrixA; + Matrix _helpMatrixB; + Transform _helpTransform; + ColorTransform _helpColorTransform; + Point _helpPoint; + std::vector _helpArray; + std::vector _intArray; + std::vector _floatArray; + std::vector _frameIntArray; + std::vector _frameFloatArray; + std::vector _frameArray; + std::vector _timelineArray; + std::vector _cacheRawMeshes; + std::vector _cacheMeshes; + std::vector _actionFrames; + std::map _weightSlotPose; + std::map _weightBonePoses; + std::map> _cacheBones; + std::map> _slotChildActions; + +public: + JSONDataParser() : _rawTextureAtlasIndex(0), + _rawBones(), + _data(nullptr), + _armature(nullptr), + _bone(nullptr), + _slot(nullptr), + _skin(nullptr), + _mesh(nullptr), + _animation(nullptr), + _timeline(nullptr), + _rawTextureAtlases(nullptr), + + _defaultColorOffset(-1), + _prevClockwise(0), + _prevRotation(0.0f), + _helpMatrixA(), + _helpMatrixB(), + _helpTransform(), + _helpColorTransform(), + _helpPoint(), + _helpArray(), + _intArray(), + _floatArray(), + _frameIntArray(), + _frameFloatArray(), + _frameArray(), + _timelineArray(), + _cacheMeshes(), + _cacheRawMeshes(), + _actionFrames(), + _weightSlotPose(), + _weightBonePoses(), + _cacheBones(), + _slotChildActions() { + } + virtual ~JSONDataParser() { + } + +private: + void _getCurvePoint( + float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + float t, + Point& result); + void _samplingEasingCurve(const rapidjson::Value& curve, std::vector& samples); + void _parseActionDataInFrame(const rapidjson::Value& rawData, unsigned frameStart, BoneData* bone, SlotData* slot); + void _mergeActionFrame(const rapidjson::Value& rawData, unsigned frameStart, ActionType type, BoneData* bone, SlotData* slot); + unsigned _parseCacheActionFrame(ActionFrame& frame); + +protected: + virtual ArmatureData* _parseArmature(const rapidjson::Value& rawData, float scale); + virtual BoneData* _parseBone(const rapidjson::Value& rawData); + virtual ConstraintData* _parseIKConstraint(const rapidjson::Value& rawData); + virtual SlotData* _parseSlot(const rapidjson::Value& rawData, int zOrder); + virtual SkinData* _parseSkin(const rapidjson::Value& rawData); + virtual DisplayData* _parseDisplay(const rapidjson::Value& rawData); + virtual void _parsePivot(const rapidjson::Value& rawData, ImageDisplayData& display); + virtual void _parseMesh(const rapidjson::Value& rawData, MeshDisplayData& mesh); + virtual BoundingBoxData* _parseBoundingBox(const rapidjson::Value& rawData); + virtual PolygonBoundingBoxData* _parsePolygonBoundingBox(const rapidjson::Value& rawData); + virtual AnimationData* _parseAnimation(const rapidjson::Value& rawData); + virtual TimelineData* _parseTimeline( + const rapidjson::Value& rawData, const char* framesKey, TimelineType type, + bool addIntOffset, bool addFloatOffset, unsigned frameValueCount, + const std::function& frameParser); + virtual void _parseBoneTimeline(const rapidjson::Value& rawData); + virtual void _parseSlotTimeline(const rapidjson::Value& rawData); + virtual unsigned _parseFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseTweenFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseActionFrame(const ActionFrame& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseZOrderFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseBoneAllFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseBoneTranslateFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseBoneRotateFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseBoneScaleFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseSlotDisplayFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseSlotColorFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseSlotFFDFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual unsigned _parseIKConstraintFrame(const rapidjson::Value& rawData, unsigned frameStart, unsigned frameCount); + virtual const std::vector& _parseActionData(const rapidjson::Value& rawData, ActionType type, BoneData* bone, SlotData* slot); + virtual void _parseTransform(const rapidjson::Value& rawData, Transform& transform, float scale); + virtual void _parseColorTransform(const rapidjson::Value& rawData, ColorTransform& color); + virtual void _parseArray(const rapidjson::Value& rawData); + virtual DragonBonesData* _parseDragonBonesData(const rapidjson::Value& rawData, float scale = 1.0f); + virtual void _parseTextureAtlasData(const rapidjson::Value& rawData, TextureAtlasData& textureAtlasData, float scale = 1.0f); + +public: + virtual DragonBonesData* parseDragonBonesData(const char* rawData, float scale = 1.0f) override; + virtual bool parseTextureAtlasData(const char* rawData, TextureAtlasData& textureAtlasData, float scale = 1.0f) override; +}; + +DRAGONBONES_NAMESPACE_END +#endif // DRAGONBONES_JSON_DATA_PARSER_H diff --git a/cocos/editor-support/middleware-adapter.cpp b/cocos/editor-support/middleware-adapter.cpp new file mode 100644 index 0000000..3665028 --- /dev/null +++ b/cocos/editor-support/middleware-adapter.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "middleware-adapter.h" +#include "base/DeferredReleasePool.h" +#include "base/memory/Memory.h" + +MIDDLEWARE_BEGIN + +const Color4F Color4F::WHITE(1.0F, 1.0F, 1.0F, 1.0F); +const Color4B Color4B::WHITE(255, 255, 255, 255); + +Color4F::Color4F(float r, float g, float b, float a) +: r(r), g(g), b(b), a(a) {} +Color4F::Color4F() = default; + +Color4F &Color4F::operator=(const Color4B &right) { + r = static_cast(right.r) / 255.0F; + g = static_cast(right.g) / 255.0F; + b = static_cast(right.b) / 255.0F; + a = static_cast(right.a) / 255.0F; + return *this; +} + +Color4B::Color4B() = default; +Color4B::Color4B(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +: r(r), g(g), b(b), a(a) {} + +Color4B &Color4B::operator=(const Color4B &right) = default; + +bool Color4B::operator==(const Color4B &right) const { + return (r == right.r && g == right.g && b == right.b && a == right.a); +} + +bool Color4B::operator!=(const Color4B &right) const { + return (r != right.r || g != right.g || b != right.b || a != right.a); +} + +bool Color4F::operator==(const Color4F &right) const { + return (r == right.r && g == right.g && b == right.b && a == right.a); +} + +bool Color4F::operator!=(const Color4F &right) const { + return (r != right.r || g != right.g || b != right.b || a != right.a); +} + +Texture2D::Texture2D() = default; + +Texture2D::~Texture2D() { + _texParamCallback = nullptr; +} + +int Texture2D::getPixelsWide() const { + return _pixelsWide; +} + +int Texture2D::getPixelsHigh() const { + return _pixelsHigh; +} + +void Texture2D::setPixelsWide(int wide) { + this->_pixelsWide = wide; +} + +void Texture2D::setPixelsHigh(int high) { + this->_pixelsHigh = high; +} + +int Texture2D::getRealTextureIndex() const { + return this->_realTextureIndex; +} + +void Texture2D::setRealTextureIndex(int textureIndex) { + this->_realTextureIndex = textureIndex; +} + +void Texture2D::setTexParamCallback(const texParamCallback &callback) { + this->_texParamCallback = callback; +} + +void Texture2D::setTexParameters(const TexParams &texParams) { + if (_texParamCallback) { + _texParamCallback(this->_realTextureIndex, texParams.minFilter, texParams.magFilter, texParams.wrapS, texParams.wrapT); + } +} + +void Texture2D::setRealTexture(void *texturePtr) { + _texturePtr = texturePtr; +} + +void *Texture2D::getRealTexture() const { + return _texturePtr; +} + +SpriteFrame *SpriteFrame::createWithTexture(Texture2D *texture, const cc::Rect &rect) { + auto *spriteFrame = new (std::nothrow) SpriteFrame(); + spriteFrame->initWithTexture(texture, rect); + + return spriteFrame; +} + +SpriteFrame *SpriteFrame::createWithTexture(Texture2D *texture, const cc::Rect &rect, bool rotated, const cc::Vec2 &offset, const cc::Size &originalSize) { + auto *spriteFrame = new (std::nothrow) SpriteFrame(); + spriteFrame->initWithTexture(texture, rect, rotated, offset, originalSize); + + return spriteFrame; +} + +bool SpriteFrame::initWithTexture(Texture2D *texture, const cc::Rect &rect) { + return initWithTexture(texture, rect, false, cc::Vec2::ZERO, cc::Size{rect.width, rect.height}); +} + +bool SpriteFrame::initWithTexture(Texture2D *texture, const cc::Rect &rect, bool rotated, const cc::Vec2 &offset, const cc::Size &originalSize) { + _texture = texture; + + if (texture) { + texture->addRef(); + } + + _rectInPixels = rect; + _offsetInPixels = offset; + _originalSizeInPixels = originalSize; + _rotated = rotated; + _anchorPoint = cc::Vec2(NAN, NAN); + + return true; +} + +SpriteFrame::SpriteFrame() = default; + +SpriteFrame::~SpriteFrame() { + CC_SAFE_RELEASE(_texture); +} + +void SpriteFrame::setTexture(Texture2D *texture) { + if (_texture != texture) { + CC_SAFE_RELEASE(_texture); + CC_SAFE_ADD_REF(texture); + _texture = texture; + } +} + +Texture2D *SpriteFrame::getTexture() { + return _texture; +} + +MIDDLEWARE_END diff --git a/cocos/editor-support/middleware-adapter.h b/cocos/editor-support/middleware-adapter.h new file mode 100644 index 0000000..c9ae620 --- /dev/null +++ b/cocos/editor-support/middleware-adapter.h @@ -0,0 +1,240 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "MiddlewareMacro.h" +#include "base/RefCounted.h" +#include "math/Geometry.h" +#include "math/Vec3.h" + +MIDDLEWARE_BEGIN + +struct Color4B { + Color4B(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + Color4B(); + bool operator==(const Color4B &right) const; + bool operator!=(const Color4B &right) const; + Color4B &operator=(const Color4B &right); + + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + uint8_t a = 0; + + static const Color4B WHITE; +}; + +struct Color4F { + Color4F(float r, float g, float b, float a); + Color4F(); + bool operator==(const Color4F &right) const; + bool operator!=(const Color4F &right) const; + Color4F &operator=(const Color4B &right); + + float r = 0.0F; + float g = 0.0F; + float b = 0.0F; + float a = 0.0F; + + static const Color4F WHITE; +}; + +/** + * Texture format with u v. + */ +struct Tex2F { + float u; + float v; +}; + +/** + * Vertex Format with x y z u v color. + */ +struct V3F_T2F_C4B { //NOLINT + // vertices (3F) + cc::Vec3 vertex; + + // tex coords (2F) + Tex2F texCoord; + + // colors (4F) + Color4B color; +}; + +/** + * Vertex Format with x y u v color1 color2. + */ +struct V3F_T2F_C4B_C4B { // NOLINT + // vertices (3F) + cc::Vec3 vertex; + + // tex coords (2F) + Tex2F texCoord; + + // colors (4F) + Color4B color; + + // colors (4F) + Color4B color2; +}; + +struct Triangles { + /**Vertex data pointer.*/ + V3F_T2F_C4B *verts = nullptr; + /**Index data pointer.*/ + unsigned short *indices = nullptr; // NOLINT + /**The number of vertices.*/ + int vertCount = 0; + /**The number of indices.*/ + int indexCount = 0; +}; + +struct TwoColorTriangles { + /**Vertex data pointer.*/ + V3F_T2F_C4B_C4B *verts = nullptr; + /**Index data pointer.*/ + unsigned short *indices = nullptr; //NOLINT + /**The number of vertices.*/ + int vertCount = 0; + /**The number of indices.*/ + int indexCount = 0; +}; + +/////////////////////////////////////////////////////////////////////// +// adapt to editor texture,this is a texture delegate,not real texture +/////////////////////////////////////////////////////////////////////// +class Texture2D : public cc::RefCounted { +public: + Texture2D(); + ~Texture2D() override; + /** + Extension to set the Min / Mag filter + */ + struct TexParams { // NOLINT + uint32_t minFilter; + uint32_t magFilter; + uint32_t wrapS; + uint32_t wrapT; + }; + + /** + * set texture param callback + */ + using texParamCallback = std::function; + + /** Sets the min filter, mag filter, wrap s and wrap t texture parameters. + If the texture size is NPOT (non power of 2), then in can only use GL_CLAMP_TO_EDGE in GL_TEXTURE_WRAP_{S,T}. + + @warning Calling this method could allocate additional texture memory. + + @since v0.8 + * @code + * When this function bound into js or lua,the input parameter will be changed + * In js: var setBlendFunc(var arg1, var arg2, var arg3, var arg4) + * In lua: local setBlendFunc(local arg1, local arg2, local arg3, local arg4) + * @endcode + */ + void setTexParameters(const TexParams &texParams); + + /** Gets the width of the texture in pixels. */ + int getPixelsWide() const; + + /** Gets the height of the texture in pixels. */ + int getPixelsHigh() const; + + /** Gets real texture index */ + int getRealTextureIndex() const; + + /** Sets the width of the texture in pixels. */ + void setPixelsWide(int wide); + + /** Sets the height of the texture in pixels. */ + void setPixelsHigh(int high); + + /** Sets real texture index.*/ + void setRealTextureIndex(int textureIndex); + + /** Sets texture param callback*/ + void setTexParamCallback(const texParamCallback &callback); + + void setRealTexture(void *texturePtr); + void *getRealTexture() const; + +private: + /** width in pixels */ + int _pixelsWide = 0; + + /** height in pixels */ + int _pixelsHigh = 0; + + /** js texture */ + int _realTextureIndex = 0; + + texParamCallback _texParamCallback = nullptr; + void *_texturePtr = nullptr; +}; + +/////////////////////////////////////////////////////////////////////// +// adapt to editor sprite frame +/////////////////////////////////////////////////////////////////////// +class SpriteFrame : public cc::RefCounted { +public: + static SpriteFrame *createWithTexture(Texture2D *pobTexture, const cc::Rect &rect); + static SpriteFrame *createWithTexture(Texture2D *pobTexture, const cc::Rect &rect, bool rotated, const cc::Vec2 &offset, const cc::Size &originalSize); + + SpriteFrame(); + ~SpriteFrame() override; + + /** Initializes a SpriteFrame with a texture, rect in points. + It is assumed that the frame was not trimmed. + */ + bool initWithTexture(Texture2D *pobTexture, const cc::Rect &rect); + + /** Initializes a SpriteFrame with a texture, rect, rotated, offset and originalSize in pixels. + The originalSize is the size in points of the frame before being trimmed. + */ + bool initWithTexture(Texture2D *pobTexture, const cc::Rect &rect, bool rotated, const cc::Vec2 &offset, const cc::Size &originalSize); + + /** Get texture of the frame. + * + * @return The texture of the sprite frame. + */ + Texture2D *getTexture(); + /** Set texture of the frame, the texture is retained. + * + * @param pobTexture The texture of the sprite frame. + */ + void setTexture(Texture2D *pobTexture); + +protected: + cc::Vec2 _anchorPoint; + cc::Rect _rectInPixels; + bool _rotated = false; + cc::Vec2 _offsetInPixels; + cc::Size _originalSizeInPixels; + Texture2D *_texture = nullptr; +}; +MIDDLEWARE_END diff --git a/cocos/editor-support/spine-creator-support/AttachmentVertices.cpp b/cocos/editor-support/spine-creator-support/AttachmentVertices.cpp new file mode 100644 index 0000000..e71ae64 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/AttachmentVertices.cpp @@ -0,0 +1,58 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "spine-creator-support/AttachmentVertices.h" + +using namespace cc; // NOLINT(google-build-using-namespace) + +namespace spine { + +AttachmentVertices::AttachmentVertices(middleware::Texture2D *texture, int verticesCount, uint16_t *triangles, int trianglesCount) { + _texture = texture; + if (_texture) _texture->addRef(); + + _triangles = new middleware::Triangles(); + _triangles->verts = new middleware::V3F_T2F_C4B[verticesCount]; + _triangles->vertCount = verticesCount; + _triangles->indices = triangles; + _triangles->indexCount = trianglesCount; +} + +AttachmentVertices::~AttachmentVertices() { + delete[] _triangles->verts; + delete _triangles; + if (_texture) _texture->release(); +} + +AttachmentVertices *AttachmentVertices::copy() { + AttachmentVertices *atv = new AttachmentVertices(nullptr, _triangles->vertCount, _triangles->indices, _triangles->indexCount); + return atv; +} + +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/AttachmentVertices.h b/cocos/editor-support/spine-creator-support/AttachmentVertices.h new file mode 100644 index 0000000..6d2c5a3 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/AttachmentVertices.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "middleware-adapter.h" + +namespace spine { +/** + * Store attachment vertex and indice list + */ +class AttachmentVertices { +public: + AttachmentVertices(cc::middleware::Texture2D *texture, int verticesCount, uint16_t *triangles, int trianglesCount); + virtual ~AttachmentVertices(); + AttachmentVertices *copy(); + + cc::middleware::Texture2D *_texture = nullptr; + cc::middleware::Triangles *_triangles = nullptr; +}; +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/SkeletonAnimation.cpp b/cocos/editor-support/spine-creator-support/SkeletonAnimation.cpp new file mode 100644 index 0000000..da92b83 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonAnimation.cpp @@ -0,0 +1,323 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "spine-creator-support/SkeletonAnimation.h" +#include +#include "base/DeferredReleasePool.h" +#include "base/Log.h" +#include "spine-creator-support/spine-cocos2dx.h" +#include "spine/Extension.h" + +namespace spine { + +struct TrackEntryListeners { + StartListener startListener; + InterruptListener interruptListener; + EndListener endListener; + DisposeListener disposeListener; + CompleteListener completeListener; + EventListener eventListener; +}; + +void animationCallback(AnimationState *state, EventType type, TrackEntry *entry, Event *event) { + (static_cast(state->getRendererObject()))->onAnimationStateEvent(entry, type, event); +} + +void trackEntryCallback(AnimationState *state, EventType type, TrackEntry *entry, Event *event) { + (static_cast(state->getRendererObject()))->onTrackEntryEvent(entry, type, event); + if (type == EventType_Dispose) { + if (entry->getRendererObject()) { + delete static_cast(entry->getRendererObject()); + entry->setRendererObject(nullptr); + } + } +} + +static TrackEntryListeners *getListeners(TrackEntry *entry) { + if (!entry->getRendererObject()) { + entry->setRendererObject(new spine::TrackEntryListeners()); + entry->setListener(trackEntryCallback); + } + return static_cast(entry->getRendererObject()); +} + +float SkeletonAnimation::GlobalTimeScale = 1.0F; +void SkeletonAnimation::setGlobalTimeScale(float timeScale) { + GlobalTimeScale = timeScale; +} + +SkeletonAnimation *SkeletonAnimation::create() { + auto *skeleton = new SkeletonAnimation(); + return skeleton; +} + +SkeletonAnimation *SkeletonAnimation::createWithData(SkeletonData *skeletonData, bool ownsSkeletonData) { + auto *node = new SkeletonAnimation(); + node->initWithData(skeletonData, ownsSkeletonData); + return node; +} + +SkeletonAnimation *SkeletonAnimation::createWithJsonFile(const std::string &skeletonJsonFile, const std::string &atlasFile, float scale) { + auto *node = new SkeletonAnimation(); + node->initWithJsonFile(skeletonJsonFile, atlasFile, scale); + return node; +} + +SkeletonAnimation *SkeletonAnimation::createWithBinaryFile(const std::string &skeletonBinaryFile, const std::string &atlasFile, float scale) { + auto *node = new SkeletonAnimation(); + node->initWithBinaryFile(skeletonBinaryFile, atlasFile, scale); + return node; +} + +void SkeletonAnimation::initialize() { + super::initialize(); + + _ownsAnimationStateData = true; + _state = new (__FILE__, __LINE__) AnimationState(new (__FILE__, __LINE__) AnimationStateData(_skeleton->getData())); + _state->setRendererObject(this); + _state->setListener(animationCallback); +} + +SkeletonAnimation::SkeletonAnimation() = default; + +SkeletonAnimation::~SkeletonAnimation() { + _startListener = nullptr; + _interruptListener = nullptr; + _endListener = nullptr; + _disposeListener = nullptr; + _completeListener = nullptr; + _eventListener = nullptr; + + if (_state) { + if (_ownsAnimationStateData) delete _state->getData(); + delete _state; + } +} + +void SkeletonAnimation::update(float deltaTime) { + if (!_skeleton) return; + if (!_paused) { + deltaTime *= _timeScale * GlobalTimeScale; + if (_ownsSkeleton) _skeleton->update(deltaTime); + _state->update(deltaTime); + _state->apply(*_skeleton); + _skeleton->updateWorldTransform(); + } +} + +void SkeletonAnimation::setAnimationStateData(AnimationStateData *stateData) { + CC_ASSERT(stateData); + + if (_state) { + if (_ownsAnimationStateData) delete _state->getData(); + delete _state; + } + + _ownsAnimationStateData = false; + _state = new (__FILE__, __LINE__) AnimationState(stateData); + _state->setRendererObject(this); + _state->setListener(animationCallback); +} + +void SkeletonAnimation::setMix(const std::string &fromAnimation, const std::string &toAnimation, float duration) { + if (_state) { + _state->getData()->setMix(fromAnimation.c_str(), toAnimation.c_str(), duration); + } +} + +TrackEntry *SkeletonAnimation::setAnimation(int trackIndex, const std::string &name, bool loop) { + if (!_skeleton) return nullptr; + Animation *animation = _skeleton->getData()->findAnimation(name.c_str()); + if (!animation) { + CC_LOG_WARNING("Spine: Animation not found: %s", name.c_str()); + return nullptr; + } + auto *trackEntry = _state->setAnimation(trackIndex, animation, loop); + _state->apply(*_skeleton); + return trackEntry; +} + +TrackEntry *SkeletonAnimation::addAnimation(int trackIndex, const std::string &name, bool loop, float delay) { + if (!_skeleton) return nullptr; + Animation *animation = _skeleton->getData()->findAnimation(name.c_str()); + if (!animation) { + CC_LOG_WARNING("Spine: Animation not found: %s", name.c_str()); + return nullptr; + } + return _state->addAnimation(trackIndex, animation, loop, delay); +} + +TrackEntry *SkeletonAnimation::setEmptyAnimation(int trackIndex, float mixDuration) { + if (_state) { + return _state->setEmptyAnimation(trackIndex, mixDuration); + } + return nullptr; +} + +void SkeletonAnimation::setEmptyAnimations(float mixDuration) { + if (_state) { + _state->setEmptyAnimations(mixDuration); + } +} + +TrackEntry *SkeletonAnimation::addEmptyAnimation(int trackIndex, float mixDuration, float delay) { + if (_state) { + return _state->addEmptyAnimation(trackIndex, mixDuration, delay); + } + return nullptr; +} + +Animation *SkeletonAnimation::findAnimation(const std::string &name) const { + if (_skeleton) { + return _skeleton->getData()->findAnimation(name.c_str()); + } + return nullptr; +} + +TrackEntry *SkeletonAnimation::getCurrent(int trackIndex) { + if (_state) { + return _state->getCurrent(trackIndex); + } + return nullptr; +} + +void SkeletonAnimation::clearTracks() { + if (_state) { + _state->clearTracks(); + super::setToSetupPose(); + } +} + +void SkeletonAnimation::clearTrack(int trackIndex) { + if (_state) { + _state->clearTrack(trackIndex); + } +} + +void SkeletonAnimation::onAnimationStateEvent(TrackEntry *entry, EventType type, Event *event) { + switch (type) { + case EventType_Start: + if (_startListener) _startListener(entry); + break; + case EventType_Interrupt: + if (_interruptListener) _interruptListener(entry); + break; + case EventType_End: + if (_endListener) _endListener(entry); + break; + case EventType_Dispose: + if (_disposeListener) _disposeListener(entry); + break; + case EventType_Complete: + if (_completeListener) _completeListener(entry); + break; + case EventType_Event: + if (_eventListener) _eventListener(entry, event); + break; + } +} + +void SkeletonAnimation::onTrackEntryEvent(TrackEntry *entry, EventType type, Event *event) { + if (!entry->getRendererObject()) return; + auto *listeners = static_cast(entry->getRendererObject()); + switch (type) { + case EventType_Start: + if (listeners->startListener) listeners->startListener(entry); + break; + case EventType_Interrupt: + if (listeners->interruptListener) listeners->interruptListener(entry); + break; + case EventType_End: + if (listeners->endListener) listeners->endListener(entry); + break; + case EventType_Dispose: + if (listeners->disposeListener) listeners->disposeListener(entry); + break; + case EventType_Complete: + if (listeners->completeListener) listeners->completeListener(entry); + break; + case EventType_Event: + if (listeners->eventListener) listeners->eventListener(entry, event); + break; + } +} + +void SkeletonAnimation::setStartListener(const StartListener &listener) { + _startListener = listener; +} + +void SkeletonAnimation::setInterruptListener(const InterruptListener &listener) { + _interruptListener = listener; +} + +void SkeletonAnimation::setEndListener(const EndListener &listener) { + _endListener = listener; +} + +void SkeletonAnimation::setDisposeListener(const DisposeListener &listener) { + _disposeListener = listener; +} + +void SkeletonAnimation::setCompleteListener(const CompleteListener &listener) { + _completeListener = listener; +} + +void SkeletonAnimation::setEventListener(const EventListener &listener) { + _eventListener = listener; +} + +void SkeletonAnimation::setTrackStartListener(TrackEntry *entry, const StartListener &listener) { // NOLINT(readability-convert-member-functions-to-static) + getListeners(entry)->startListener = listener; +} + +void SkeletonAnimation::setTrackInterruptListener(TrackEntry *entry, const InterruptListener &listener) { // NOLINT(readability-convert-member-functions-to-static) + getListeners(entry)->interruptListener = listener; +} + +void SkeletonAnimation::setTrackEndListener(TrackEntry *entry, const EndListener &listener) { // NOLINT(readability-convert-member-functions-to-static) + getListeners(entry)->endListener = listener; +} + +void SkeletonAnimation::setTrackDisposeListener(TrackEntry *entry, const DisposeListener &listener) { // NOLINT(readability-convert-member-functions-to-static) + getListeners(entry)->disposeListener = listener; +} + +void SkeletonAnimation::setTrackCompleteListener(TrackEntry *entry, const CompleteListener &listener) { // NOLINT(readability-convert-member-functions-to-static) + getListeners(entry)->completeListener = listener; +} + +void SkeletonAnimation::setTrackEventListener(TrackEntry *entry, const EventListener &listener) { // NOLINT(readability-convert-member-functions-to-static) + getListeners(entry)->eventListener = listener; +} + +AnimationState *SkeletonAnimation::getState() const { + return _state; +} + +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/SkeletonAnimation.h b/cocos/editor-support/spine-creator-support/SkeletonAnimation.h new file mode 100644 index 0000000..93eb7ab --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonAnimation.h @@ -0,0 +1,108 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated May 1, 2019. Replaces all prior versions. + * + * Copyright (c) 2013-2019, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS + * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once +#include "spine-creator-support/SkeletonRenderer.h" +#include "spine/spine.h" + +namespace spine { + +typedef std::function StartListener; +typedef std::function InterruptListener; +typedef std::function EndListener; +typedef std::function DisposeListener; +typedef std::function CompleteListener; +typedef std::function EventListener; + +/** Draws an animated skeleton, providing an AnimationState for applying one or more animations and queuing animations to be + * played later. */ +class SkeletonAnimation : public SkeletonRenderer { +public: + static SkeletonAnimation *create(); + static SkeletonAnimation *createWithData(SkeletonData *skeletonData, bool ownsSkeletonData = false); + static SkeletonAnimation *createWithJsonFile(const std::string &skeletonJsonFile, const std::string &atlasFile, float scale = 1); + static SkeletonAnimation *createWithBinaryFile(const std::string &skeletonBinaryFile, const std::string &atlasFile, float scale = 1); + static void setGlobalTimeScale(float timeScale); + + virtual void update(float deltaTime) override; + + void setAnimationStateData(AnimationStateData *stateData); + void setMix(const std::string &fromAnimation, const std::string &toAnimation, float duration); + + TrackEntry *setAnimation(int trackIndex, const std::string &name, bool loop); + TrackEntry *addAnimation(int trackIndex, const std::string &name, bool loop, float delay = 0); + TrackEntry *setEmptyAnimation(int trackIndex, float mixDuration); + void setEmptyAnimations(float mixDuration); + TrackEntry *addEmptyAnimation(int trackIndex, float mixDuration, float delay = 0); + Animation *findAnimation(const std::string &name) const; + TrackEntry *getCurrent(int trackIndex = 0); + void clearTracks(); + void clearTrack(int trackIndex = 0); + + void setStartListener(const StartListener &listener); + void setInterruptListener(const InterruptListener &listener); + void setEndListener(const EndListener &listener); + void setDisposeListener(const DisposeListener &listener); + void setCompleteListener(const CompleteListener &listener); + void setEventListener(const EventListener &listener); + + void setTrackStartListener(TrackEntry *entry, const StartListener &listener); + void setTrackInterruptListener(TrackEntry *entry, const InterruptListener &listener); + void setTrackEndListener(TrackEntry *entry, const EndListener &listener); + void setTrackDisposeListener(TrackEntry *entry, const DisposeListener &listener); + void setTrackCompleteListener(TrackEntry *entry, const CompleteListener &listener); + void setTrackEventListener(TrackEntry *entry, const EventListener &listener); + + virtual void onAnimationStateEvent(TrackEntry *entry, EventType type, Event *event); + virtual void onTrackEntryEvent(TrackEntry *entry, EventType type, Event *event); + + AnimationState *getState() const; + + SkeletonAnimation(); + virtual ~SkeletonAnimation(); + virtual void initialize() override; + +public: + static float GlobalTimeScale; + +protected: + AnimationState *_state = nullptr; + bool _ownsAnimationStateData = false; + StartListener _startListener = nullptr; + InterruptListener _interruptListener = nullptr; + EndListener _endListener = nullptr; + DisposeListener _disposeListener = nullptr; + CompleteListener _completeListener = nullptr; + EventListener _eventListener = nullptr; + +private: + typedef SkeletonRenderer super; +}; + +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/SkeletonCache.cpp b/cocos/editor-support/spine-creator-support/SkeletonCache.cpp new file mode 100644 index 0000000..703f430 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonCache.cpp @@ -0,0 +1,546 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SkeletonCache.h" +#include "base/memory/Memory.h" +#include "spine-creator-support/AttachmentVertices.h" + +USING_NS_MW; // NOLINT(google-build-using-namespace) +using namespace cc; // NOLINT(google-build-using-namespace) + +namespace spine { + +float SkeletonCache::FrameTime = 1.0F / 60.0F; +float SkeletonCache::MaxCacheTime = 120.0F; + +SkeletonCache::SegmentData::SegmentData() = default; + +SkeletonCache::SegmentData::~SegmentData() { + CC_SAFE_RELEASE_NULL(_texture); +} + +void SkeletonCache::SegmentData::setTexture(cc::middleware::Texture2D *value) { + CC_SAFE_ADD_REF(value); + CC_SAFE_RELEASE(_texture); + _texture = value; +} + +cc::middleware::Texture2D *SkeletonCache::SegmentData::getTexture() const { + return _texture; +} + +SkeletonCache::FrameData::FrameData() = default; + +SkeletonCache::FrameData::~FrameData() { + for (auto &bone : _bones) { + delete bone; + } + _bones.clear(); + + for (auto &color : _colors) { + delete color; + } + _colors.clear(); + + for (auto &segment : _segments) { + delete segment; + } + _segments.clear(); +} + +SkeletonCache::BoneData *SkeletonCache::FrameData::buildBoneData(std::size_t index) { + if (index > _bones.size()) return nullptr; + if (index == _bones.size()) { + auto *boneData = new BoneData; + _bones.push_back(boneData); + } + return _bones[index]; +} + +std::size_t SkeletonCache::FrameData::getBoneCount() const { + return _bones.size(); +} + +SkeletonCache::ColorData *SkeletonCache::FrameData::buildColorData(std::size_t index) { + if (index > _colors.size()) return nullptr; + if (index == _colors.size()) { + auto *colorData = new ColorData; + _colors.push_back(colorData); + } + return _colors[index]; +} + +std::size_t SkeletonCache::FrameData::getColorCount() const { + return _colors.size(); +} + +SkeletonCache::SegmentData *SkeletonCache::FrameData::buildSegmentData(std::size_t index) { + if (index > _segments.size()) return nullptr; + if (index == _segments.size()) { + auto *segmentData = new SegmentData; + _segments.push_back(segmentData); + } + return _segments[index]; +} + +std::size_t SkeletonCache::FrameData::getSegmentCount() const { + return _segments.size(); +} + +SkeletonCache::AnimationData::AnimationData() = default; + +SkeletonCache::AnimationData::~AnimationData() { + reset(); +} + +void SkeletonCache::AnimationData::reset() { + for (auto &frame : _frames) { + delete frame; + } + _frames.clear(); + _isComplete = false; + _totalTime = 0.0F; +} + +bool SkeletonCache::AnimationData::needUpdate(int toFrameIdx) const { + return !_isComplete && _totalTime <= MaxCacheTime && (toFrameIdx == -1 || _frames.size() < toFrameIdx + 1); +} + +SkeletonCache::FrameData *SkeletonCache::AnimationData::buildFrameData(std::size_t frameIdx) { + if (frameIdx > _frames.size()) { + return nullptr; + } + if (frameIdx == _frames.size()) { + auto *frameData = new FrameData(); + _frames.push_back(frameData); + } + return _frames[frameIdx]; +} + +SkeletonCache::FrameData *SkeletonCache::AnimationData::getFrameData(std::size_t frameIdx) const { + if (frameIdx >= _frames.size()) { + return nullptr; + } + return _frames[frameIdx]; +} + +std::size_t SkeletonCache::AnimationData::getFrameCount() const { + return _frames.size(); +} + +SkeletonCache::SkeletonCache() = default; + +SkeletonCache::~SkeletonCache() { + for (auto &animationCache : _animationCaches) { + delete animationCache.second; + } + _animationCaches.clear(); +} + +SkeletonCache::AnimationData *SkeletonCache::buildAnimationData(const std::string &animationName) { + AnimationData *aniData = nullptr; + auto it = _animationCaches.find(animationName); + if (it == _animationCaches.end()) { + auto *animation = findAnimation(animationName); + if (animation == nullptr) return nullptr; + + aniData = new AnimationData(); + aniData->_animationName = animationName; + _animationCaches[animationName] = aniData; + } else { + aniData = it->second; + } + return aniData; +} + +SkeletonCache::AnimationData *SkeletonCache::getAnimationData(const std::string &animationName) { + auto it = _animationCaches.find(animationName); + if (it == _animationCaches.end()) { + return nullptr; + } + return it->second; +} + +void SkeletonCache::update(float deltaTime) { + if (!_skeleton) return; + if (_ownsSkeleton) _skeleton->update(deltaTime); + _state->update(deltaTime); + _state->apply(*_skeleton); + _skeleton->updateWorldTransform(); +} + +void SkeletonCache::updateToFrame(const std::string &animationName, int toFrameIdx /*= -1*/) { + auto it = _animationCaches.find(animationName); + if (it == _animationCaches.end()) { + return; + } + + AnimationData *animationData = it->second; + if (!animationData || !animationData->needUpdate(toFrameIdx)) { + return; + } + + if (_curAnimationName != animationName) { + updateToFrame(_curAnimationName); + _curAnimationName = animationName; + } + + // init animation + if (animationData->getFrameCount() == 0) { + setAnimation(0, animationName, false); + } + + do { + update(FrameTime); + renderAnimationFrame(animationData); + animationData->_totalTime += FrameTime; + } while (animationData->needUpdate(toFrameIdx)); +} + +void SkeletonCache::renderAnimationFrame(AnimationData *animationData) { + std::size_t frameIndex = animationData->getFrameCount(); + FrameData *frameData = animationData->buildFrameData(frameIndex); + + if (!_skeleton) return; + + // If opacity is 0,then return. + if (_skeleton->getColor().a == 0) { + return; + } + + Color4F preColor(-1.0F, -1.0F, -1.0F, -1.0F); + Color4F preDarkColor(-1.0F, -1.0F, -1.0F, -1.0F); + // range [0.0, 1.0] + Color4F color; + Color4F darkColor; + Color4B finalColor; + Color4B finalDardk; + + AttachmentVertices *attachmentVertices = nullptr; + middleware::IOBuffer &vb = frameData->vb; + middleware::IOBuffer &ib = frameData->ib; + + // vertex size int bytes with two color + int vbs2 = sizeof(V3F_T2F_C4B_C4B); + // vertex size in floats with two color + int vs2 = static_cast(vbs2 / sizeof(float)); + + int vbSize = 0; + int ibSize = 0; + + int preBlendMode = -1; + int preTextureIndex = -1; + int curTextureIndex = -1; + + int preISegWritePos = -1; + int curISegLen = 0; + int curVSegLen = 0; + + int materialLen = 0; + Slot *slot = nullptr; + + middleware::Texture2D *texture = nullptr; + + auto flush = [&]() { + // fill pre segment count field + if (preISegWritePos != -1) { + SegmentData *preSegmentData = frameData->buildSegmentData(materialLen - 1); + preSegmentData->indexCount = curISegLen; + preSegmentData->vertexFloatCount = curVSegLen; + } + + SegmentData *segmentData = frameData->buildSegmentData(materialLen); + segmentData->setTexture(texture); + segmentData->blendMode = slot->getData().getBlendMode(); + + // save new segment count pos field + preISegWritePos = static_cast(ib.getCurPos() / sizeof(uint16_t)); + // reset pre blend mode to current + preBlendMode = static_cast(slot->getData().getBlendMode()); + // reset pre texture index to current + preTextureIndex = curTextureIndex; + // reset index segmentation count + curISegLen = 0; + // reset vertex segmentation count + curVSegLen = 0; + // material length increased + materialLen++; + }; + + auto &bones = _skeleton->getBones(); + for (std::size_t i = 0, n = bones.size(); i < n; i++) { + auto &bone = bones[i]; + auto boneCount = frameData->getBoneCount(); + BoneData *boneData = frameData->buildBoneData(boneCount); + auto &matm = boneData->globalTransformMatrix.m; + matm[0] = bone->getA(); + matm[1] = bone->getC(); + matm[4] = bone->getB(); + matm[5] = bone->getD(); + matm[12] = bone->getWorldX(); + matm[13] = bone->getWorldY(); + } + + auto &drawOrder = _skeleton->getDrawOrder(); + for (size_t i = 0, n = drawOrder.size(); i < n; ++i) { + slot = drawOrder[i]; + + if (slot->getBone().isActive() == false) { + continue; + } + + if (!slot->getAttachment()) { + _clipper->clipEnd(*slot); + continue; + } + const spine::Color &slotColor = slot->getColor(); + + TwoColorTriangles trianglesTwoColor; + spine::Color attachmentColor; + if (slot->getAttachment()->getRTTI().isExactly(RegionAttachment::rtti)) { + auto *attachment = dynamic_cast(slot->getAttachment()); + attachmentVertices = static_cast(attachment->getRendererObject()); + + // Early exit if attachment is invisible + if (attachment->getColor().a == 0) { + _clipper->clipEnd(*slot); + continue; + } + + trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount; + vbSize = static_cast(trianglesTwoColor.vertCount * sizeof(V3F_T2F_C4B_C4B)); + vb.checkSpace(vbSize, true); + trianglesTwoColor.verts = reinterpret_cast(vb.getCurBuffer()); + for (int ii = 0; ii < trianglesTwoColor.vertCount; ii++) { + trianglesTwoColor.verts[ii].texCoord = attachmentVertices->_triangles->verts[ii].texCoord; + } + attachment->computeWorldVertices(slot->getBone(), reinterpret_cast(trianglesTwoColor.verts), 0, vs2); + + trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount; + ibSize = static_cast(trianglesTwoColor.indexCount * sizeof(uint16_t)); + ib.checkSpace(ibSize, true); + trianglesTwoColor.indices = reinterpret_cast(ib.getCurBuffer()); + memcpy(trianglesTwoColor.indices, attachmentVertices->_triangles->indices, ibSize); + + attachmentColor = attachment->getColor(); + + } else if (slot->getAttachment()->getRTTI().isExactly(MeshAttachment::rtti)) { + auto *attachment = dynamic_cast(slot->getAttachment()); + attachmentVertices = static_cast(attachment->getRendererObject()); + + // Early exit if attachment is invisible + if (attachment->getColor().a == 0) { + _clipper->clipEnd(*slot); + continue; + } + + trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount; + vbSize = static_cast(trianglesTwoColor.vertCount * sizeof(V3F_T2F_C4B_C4B)); + vb.checkSpace(vbSize, true); + trianglesTwoColor.verts = reinterpret_cast(vb.getCurBuffer()); + for (int ii = 0; ii < trianglesTwoColor.vertCount; ii++) { + trianglesTwoColor.verts[ii].texCoord = attachmentVertices->_triangles->verts[ii].texCoord; + } + attachment->computeWorldVertices(*slot, 0, attachment->getWorldVerticesLength(), reinterpret_cast(trianglesTwoColor.verts), 0, vs2); + + trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount; + ibSize = static_cast(trianglesTwoColor.indexCount * sizeof(uint16_t)); + ib.checkSpace(ibSize, true); + trianglesTwoColor.indices = reinterpret_cast(ib.getCurBuffer()); + memcpy(trianglesTwoColor.indices, attachmentVertices->_triangles->indices, ibSize); + attachmentColor = attachment->getColor(); + } else if (slot->getAttachment()->getRTTI().isExactly(ClippingAttachment::rtti)) { + auto *clip = dynamic_cast(slot->getAttachment()); + _clipper->clipStart(*slot, clip); + continue; + } else { + _clipper->clipEnd(*slot); + continue; + } + color.a = _skeleton->getColor().a * slotColor.a * attachmentColor.a * 255; + // skip rendering if the color of this attachment is 0 + if (color.a == 0) { + _clipper->clipEnd(*slot); + continue; + } + + float red = _skeleton->getColor().r * attachmentColor.r * 255; + float green = _skeleton->getColor().g * attachmentColor.g * 255; + float blue = _skeleton->getColor().b * attachmentColor.b * 255; + + color.r = red * slotColor.r; + color.g = green * slotColor.g; + color.b = blue * slotColor.b; + + if (slot->hasDarkColor()) { + darkColor.r = red * slot->getDarkColor().r; + darkColor.g = green * slot->getDarkColor().g; + darkColor.b = blue * slot->getDarkColor().b; + darkColor.a = 0; + } else { + darkColor.r = 0; + darkColor.g = 0; + darkColor.b = 0; + darkColor.a = 0; + } + + finalColor.r = static_cast(std::round(color.r)); + finalColor.g = static_cast(std::round(color.g)); + finalColor.b = static_cast(std::round(color.b)); + finalColor.a = static_cast(std::round(color.a)); + + finalDardk.r = static_cast(std::round(darkColor.r)); + finalDardk.g = static_cast(std::round(darkColor.g)); + finalDardk.b = static_cast(std::round(darkColor.b)); + finalDardk.a = static_cast(std::round(darkColor.a)); + + if (preColor != color || preDarkColor != darkColor) { + preColor = color; + preDarkColor = darkColor; + auto colorCount = frameData->getColorCount(); + if (colorCount > 0) { + ColorData *preColorData = frameData->buildColorData(colorCount - 1); + preColorData->vertexFloatOffset = static_cast(vb.getCurPos() / sizeof(float)); + } + ColorData *colorData = frameData->buildColorData(colorCount); + colorData->finalColor = finalColor; + colorData->darkColor = finalDardk; + } + + // Two color tint logic + if (_clipper->isClipping()) { + _clipper->clipTriangles(reinterpret_cast(&trianglesTwoColor.verts[0].vertex), trianglesTwoColor.indices, trianglesTwoColor.indexCount, reinterpret_cast(&trianglesTwoColor.verts[0].texCoord), vs2); + + if (_clipper->getClippedTriangles().size() == 0) { + _clipper->clipEnd(*slot); + continue; + } + + trianglesTwoColor.vertCount = static_cast(_clipper->getClippedVertices().size()) >> 1; + vbSize = static_cast(trianglesTwoColor.vertCount * sizeof(V3F_T2F_C4B_C4B)); + vb.checkSpace(vbSize, true); + trianglesTwoColor.verts = reinterpret_cast(vb.getCurBuffer()); + + trianglesTwoColor.indexCount = static_cast(_clipper->getClippedTriangles().size()); + ibSize = static_cast(trianglesTwoColor.indexCount * sizeof(uint16_t)); + ib.checkSpace(ibSize, true); + trianglesTwoColor.indices = reinterpret_cast(ib.getCurBuffer()); + memcpy(trianglesTwoColor.indices, _clipper->getClippedTriangles().buffer(), sizeof(uint16_t) * _clipper->getClippedTriangles().size()); + + float *verts = _clipper->getClippedVertices().buffer(); + float *uvs = _clipper->getClippedUVs().buffer(); + + for (int v = 0, vn = trianglesTwoColor.vertCount, vv = 0; v < vn; ++v, vv += 2) { + V3F_T2F_C4B_C4B *vertex = trianglesTwoColor.verts + v; + vertex->vertex.x = verts[vv]; + vertex->vertex.y = verts[vv + 1]; + vertex->texCoord.u = uvs[vv]; + vertex->texCoord.v = uvs[vv + 1]; + vertex->color = finalColor; + vertex->color2 = finalDardk; + } + } else { + for (int v = 0, vn = trianglesTwoColor.vertCount; v < vn; ++v) { + V3F_T2F_C4B_C4B *vertex = trianglesTwoColor.verts + v; + vertex->color = finalColor; + vertex->color2 = finalDardk; + } + } + + texture = attachmentVertices->_texture; + curTextureIndex = attachmentVertices->_texture->getRealTextureIndex(); + // If texture or blendMode change,will change material. + if (preTextureIndex != curTextureIndex || preBlendMode != slot->getData().getBlendMode()) { + flush(); + } + + if (vbSize > 0 && ibSize > 0) { + auto vertexOffset = curVSegLen / vs2; + + if (vertexOffset > 0) { + auto *ibBuffer = reinterpret_cast(ib.getCurBuffer()); + for (uint32_t ii = 0, nn = ibSize / sizeof(uint16_t); ii < nn; ii++) { + ibBuffer[ii] += vertexOffset; + } + } + vb.move(vbSize); + ib.move(ibSize); + + // Record this turn index segmentation count,it will store in material buffer in the end. + curISegLen += static_cast(ibSize / sizeof(uint16_t)); + curVSegLen += static_cast(vbSize / sizeof(float)); + } + + _clipper->clipEnd(*slot); + } // End slot traverse + + _clipper->clipEnd(); + + if (preISegWritePos != -1) { + SegmentData *preSegmentData = frameData->buildSegmentData(materialLen - 1); + preSegmentData->indexCount = curISegLen; + preSegmentData->vertexFloatCount = curVSegLen; + } + + auto colorCount = frameData->getColorCount(); + if (colorCount > 0) { + ColorData *preColorData = frameData->buildColorData(colorCount - 1); + preColorData->vertexFloatOffset = static_cast(vb.getCurPos() / sizeof(float)); + } +} + +void SkeletonCache::onAnimationStateEvent(TrackEntry *entry, EventType type, Event *event) { + SkeletonAnimation::onAnimationStateEvent(entry, type, event); + if (type == EventType_Complete && entry) { + Animation *ani = entry->getAnimation(); + if (!ani) return; + std::string aniName = ani->getName().buffer(); + if (aniName == _curAnimationName) { + AnimationData *aniData = getAnimationData(_curAnimationName); + if (!aniData) return; + aniData->_isComplete = true; + } + } +} + +void SkeletonCache::resetAllAnimationData() { + for (auto &animationCache : _animationCaches) { + animationCache.second->reset(); + } +} + +void SkeletonCache::resetAnimationData(const std::string &animationName) { + for (auto &animationCache : _animationCaches) { + if (animationCache.second->_animationName == animationName) { + animationCache.second->reset(); + break; + } + } +} +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/SkeletonCache.h b/cocos/editor-support/spine-creator-support/SkeletonCache.h new file mode 100644 index 0000000..424e8cf --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonCache.h @@ -0,0 +1,157 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include +#include "IOBuffer.h" +#include "SkeletonAnimation.h" +#include "middleware-adapter.h" + +namespace spine { +class SkeletonCache : public SkeletonAnimation { +public: + struct SegmentData { + friend class SkeletonCache; + + SegmentData(); + ~SegmentData(); + + void setTexture(cc::middleware::Texture2D *value); + cc::middleware::Texture2D *getTexture() const; + + public: + int indexCount = 0; + int vertexFloatCount = 0; + int blendMode = 0; + + private: + cc::middleware::Texture2D *_texture = nullptr; + }; + + struct BoneData { + cc::Mat4 globalTransformMatrix; + }; + + struct ColorData { + cc::middleware::Color4B finalColor; + cc::middleware::Color4B darkColor; + int vertexFloatOffset = 0; + }; + + struct FrameData { + friend class SkeletonCache; + + FrameData(); + ~FrameData(); + + const std::vector &getBones() const { + return _bones; + } + std::size_t getBoneCount() const; + + const std::vector &getColors() const { + return _colors; + } + std::size_t getColorCount() const; + + const std::vector &getSegments() const { + return _segments; + } + std::size_t getSegmentCount() const; + + private: + // if segment data is empty, it will build new one. + SegmentData *buildSegmentData(std::size_t index); + // if color data is empty, it will build new one. + ColorData *buildColorData(std::size_t index); + // if bone data is empty, it will build new one. + BoneData *buildBoneData(std::size_t index); + + std::vector _bones; + std::vector _colors; + std::vector _segments; + + public: + cc::middleware::IOBuffer ib; + cc::middleware::IOBuffer vb; + }; + + struct AnimationData { + friend class SkeletonCache; + + AnimationData(); + ~AnimationData(); + void reset(); + + FrameData *getFrameData(std::size_t frameIdx) const; + std::size_t getFrameCount() const; + + bool isComplete() const { return _isComplete; } + bool needUpdate(int toFrameIdx) const; + + private: + // if frame is empty, it will build new one. + FrameData *buildFrameData(std::size_t frameIdx); + + private: + std::string _animationName = ""; + bool _isComplete = false; + float _totalTime = 0.0f; + std::vector _frames; + }; + + SkeletonCache(); + virtual ~SkeletonCache(); + + virtual void beginSchedule() override {} + virtual void stopSchedule() override {} + virtual void update(float deltaTime) override; + virtual void render(float deltaTime) override {} + virtual void onAnimationStateEvent(TrackEntry *entry, EventType type, Event *event) override; + + void updateToFrame(const std::string &animationName, int toFrameIdx = -1); + // if animation data is empty, it will build new one. + AnimationData *buildAnimationData(const std::string &animationName); + AnimationData *getAnimationData(const std::string &animationName); + void resetAllAnimationData(); + void resetAnimationData(const std::string &animationName); + +private: + void renderAnimationFrame(AnimationData *animationData); + +public: + static float FrameTime; + static float MaxCacheTime; + +private: + std::string _curAnimationName = ""; + std::map _animationCaches; +}; +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/SkeletonCacheAnimation.cpp b/cocos/editor-support/spine-creator-support/SkeletonCacheAnimation.cpp new file mode 100644 index 0000000..6a80f47 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonCacheAnimation.cpp @@ -0,0 +1,604 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SkeletonCacheAnimation.h" +#include "2d/renderer/RenderDrawInfo.h" +#include "2d/renderer/RenderEntity.h" +#include "MiddlewareMacro.h" +#include "SharedBufferManager.h" +#include "SkeletonCacheMgr.h" +#include "base/TypeDef.h" +#include "base/memory/Memory.h" +#include "gfx-base/GFXDef.h" +#include "math/Math.h" +#include "renderer/core/MaterialInstance.h" + +USING_NS_MW; // NOLINT(google-build-using-namespace) + +using namespace cc; // NOLINT(google-build-using-namespace) +using namespace cc::gfx; // NOLINT(google-build-using-namespace) +static const std::string TECH_STAGE = "opaque"; +static const std::string TEXTURE_KEY = "texture"; + +namespace spine { + +SkeletonCacheAnimation::SkeletonCacheAnimation(const std::string &uuid, bool isShare) { + if (isShare) { + _skeletonCache = SkeletonCacheMgr::getInstance()->buildSkeletonCache(uuid); + _skeletonCache->addRef(); + } else { + _skeletonCache = new SkeletonCache(); + _skeletonCache->addRef(); + _skeletonCache->initWithUUID(uuid); + } + + // store global TypedArray begin and end offset + _sharedBufferOffset = new IOTypedArray(se::Object::TypedArrayType::UINT32, sizeof(uint32_t) * 2); +} + +SkeletonCacheAnimation::~SkeletonCacheAnimation() { + if (_sharedBufferOffset) { + delete _sharedBufferOffset; + _sharedBufferOffset = nullptr; + } + + if (_skeletonCache) { + _skeletonCache->release(); + _skeletonCache = nullptr; + } + while (!_animationQueue.empty()) { + auto *ani = _animationQueue.front(); + _animationQueue.pop(); + delete ani; + } + for (auto *draw : _drawInfoArray) { + CC_SAFE_DELETE(draw); + } + + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } + stopSchedule(); +} + +void SkeletonCacheAnimation::update(float dt) { + if (_paused) return; + + auto gTimeScale = SkeletonAnimation::GlobalTimeScale; + dt *= _timeScale * gTimeScale; + + if (_isAniComplete) { + if (_animationQueue.empty() && !_headAnimation) { + if (_animationData && !_animationData->isComplete()) { + _skeletonCache->updateToFrame(_animationName); + } + return; + } + if (!_headAnimation) { + _headAnimation = _animationQueue.front(); + _animationQueue.pop(); + } + if (!_headAnimation) { + return; + } + _accTime += dt; + if (_accTime > _headAnimation->delay) { + std::string name = _headAnimation->animationName; + bool loop = _headAnimation->loop; + delete _headAnimation; + _headAnimation = nullptr; + setAnimation(name, loop); + return; + } + } + + if (!_animationData) return; + + if (_accTime <= 0.00001 && _playCount == 0) { + if (_startListener) { + _startListener(_animationName); + } + } + + _accTime += dt; + int frameIdx = floor(_accTime / SkeletonCache::FrameTime); + if (!_animationData->isComplete()) { + _skeletonCache->updateToFrame(_animationName, frameIdx); + } + + int finalFrameIndex = static_cast(_animationData->getFrameCount()) - 1; + if (_animationData->isComplete() && frameIdx >= finalFrameIndex) { + _playCount++; + _accTime = 0.0F; + if (_playTimes > 0 && _playCount >= _playTimes) { + frameIdx = finalFrameIndex; + _playCount = 0; + _isAniComplete = true; + } else { + frameIdx = 0; + } + if (_endListener) { + _endListener(_animationName); + } + if (_completeListener) { + _completeListener(_animationName); + } + } + _curFrameIndex = frameIdx; +} + +void SkeletonCacheAnimation::render(float /*dt*/) { + if (!_animationData) return; + SkeletonCache::FrameData *frameData = _animationData->getFrameData(_curFrameIndex); + if (!frameData) return; + auto *entity = _entity; + entity->clearDynamicRenderDrawInfos(); + + const auto &segments = frameData->getSegments(); + const auto &colors = frameData->getColors(); + if (segments.empty() || colors.empty()) return; + + auto *mgr = MiddlewareManager::getInstance(); + if (!mgr->isRendering) return; + + _sharedBufferOffset->reset(); + _sharedBufferOffset->clear(); + + auto *attachMgr = mgr->getAttachInfoMgr(); + auto *attachInfo = attachMgr->getBuffer(); + if (!attachInfo) return; + + // store attach info offset + _sharedBufferOffset->writeUint32(static_cast(attachInfo->getCurPos()) / sizeof(uint32_t)); + + auto vertexFormat = _useTint ? VF_XYZUVCC : VF_XYZUVC; + middleware::MeshBuffer *mb = mgr->getMeshBuffer(vertexFormat); + middleware::IOBuffer &vb = mb->getVB(); + middleware::IOBuffer &ib = mb->getIB(); + const auto &srcVB = frameData->vb; + const auto &srcIB = frameData->ib; + + // vertex size int bytes with one color + int vbs1 = sizeof(V3F_T2F_C4B); + // vertex size in floats with one color + int vs1 = static_cast(vbs1 / sizeof(float)); + // vertex size int bytes with two color + int vbs2 = sizeof(V3F_T2F_C4B_C4B); + // vertex size in floats with two color + int vs2 = static_cast(vbs2 / sizeof(float)); + + int vs = _useTint ? vs2 : vs1; + int vbs = _useTint ? vbs2 : vbs1; + + auto &nodeWorldMat = entity->getNode()->getWorldMatrix(); + + int colorOffset = 0; + SkeletonCache::ColorData *nowColor = colors[colorOffset++]; + auto maxVFOffset = nowColor->vertexFloatOffset; + + Color4B finalColor; + Color4B darkColor; + float tempR = 0.0F; + float tempG = 0.0F; + float tempB = 0.0F; + float tempA = 0.0F; + float multiplier = 1.0F; + int srcVertexBytesOffset = 0; + int srcVertexBytes = 0; + int vertexBytes = 0; + int vertexFloats = 0; + int tintBytes = 0; + int srcIndexBytesOffset = 0; + int indexBytes = 0; + double effectHash = 0; + int blendMode = 0; + int dstVertexOffset = 0; + int dstIndexOffset = 0; + float *dstVertexBuffer = nullptr; + unsigned int *dstColorBuffer = nullptr; + uint16_t *dstIndexBuffer = nullptr; + bool needColor = false; + int curBlendSrc = -1; + int curBlendDst = -1; + cc::Texture2D *curTexture = nullptr; + RenderDrawInfo *curDrawInfo = nullptr; + + if (abs(_nodeColor.r - 1.0F) > 0.0001F || + abs(_nodeColor.g - 1.0F) > 0.0001F || + abs(_nodeColor.b - 1.0F) > 0.0001F || + abs(_nodeColor.a - 1.0F) > 0.0001F || + _premultipliedAlpha) { + needColor = true; + } + + auto handleColor = [&](SkeletonCache::ColorData *colorData) { + tempA = colorData->finalColor.a * _nodeColor.a; + multiplier = _premultipliedAlpha ? tempA / 255 : 1; + tempR = _nodeColor.r * multiplier; + tempG = _nodeColor.g * multiplier; + tempB = _nodeColor.b * multiplier; + + finalColor.r = static_cast(std::round(colorData->finalColor.r * tempR)); + finalColor.g = static_cast(std::round(colorData->finalColor.g * tempG)); + finalColor.b = static_cast(std::round(colorData->finalColor.b * tempB)); + finalColor.a = static_cast(std::round(tempA)); + + darkColor.r = static_cast(std::round(colorData->darkColor.r * tempR)); + darkColor.g = static_cast(std::round(colorData->darkColor.g * tempG)); + darkColor.b = static_cast(std::round(colorData->darkColor.b * tempB)); + darkColor.a = _premultipliedAlpha ? 255 : 0; + }; + + handleColor(nowColor); + int segmentCount = 0; + for (auto *segment : segments) { + srcVertexBytes = static_cast(segment->vertexFloatCount * sizeof(float)); + if (!_useTint) { + tintBytes = static_cast(segment->vertexFloatCount / vs2 * sizeof(float)); + vertexBytes = srcVertexBytes - tintBytes; + vertexFloats = static_cast(vertexBytes / sizeof(float)); + } else { + vertexBytes = srcVertexBytes; + vertexFloats = segment->vertexFloatCount; + } + curDrawInfo = requestDrawInfo(segmentCount++); + entity->addDynamicRenderDrawInfo(curDrawInfo); + // fill new texture index + curTexture = static_cast(segment->getTexture()->getRealTexture()); + gfx::Texture *texture = curTexture->getGFXTexture(); + gfx::Sampler *sampler = curTexture->getGFXSampler(); + curDrawInfo->setTexture(texture); + curDrawInfo->setSampler(sampler); + + blendMode = segment->blendMode; + switch (blendMode) { + case BlendMode_Additive: + curBlendSrc = static_cast(_premultipliedAlpha ? BlendFactor::ONE : BlendFactor::SRC_ALPHA); + curBlendDst = static_cast(BlendFactor::ONE); + break; + case BlendMode_Multiply: + curBlendSrc = static_cast(BlendFactor::DST_COLOR); + curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_ALPHA); + break; + case BlendMode_Screen: + curBlendSrc = static_cast(BlendFactor::ONE); + curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_COLOR); + break; + default: + curBlendSrc = static_cast(_premultipliedAlpha ? BlendFactor::ONE : BlendFactor::SRC_ALPHA); + curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_ALPHA); + } + // fill new blend src and dst + auto *material = requestMaterial(curBlendSrc, curBlendDst); + curDrawInfo->setMaterial(material); + + // fill vertex buffer + vb.checkSpace(vertexBytes, true); + dstVertexOffset = static_cast(vb.getCurPos()) / vbs; + dstVertexBuffer = reinterpret_cast(vb.getCurBuffer()); + dstColorBuffer = reinterpret_cast(vb.getCurBuffer()); + if (!_useTint) { + char *srcBuffer = reinterpret_cast(srcVB.getBuffer()) + srcVertexBytesOffset; + for (std::size_t srcBufferIdx = 0; srcBufferIdx < srcVertexBytes; srcBufferIdx += vbs2) { + vb.writeBytes(srcBuffer + srcBufferIdx, vbs); + } + } else { + vb.writeBytes(reinterpret_cast(srcVB.getBuffer()) + srcVertexBytesOffset, vertexBytes); + } + // batch handle + if (_enableBatch) { + cc::Vec3 *point = nullptr; + for (auto posIndex = 0; posIndex < vertexFloats; posIndex += vs) { + point = reinterpret_cast(dstVertexBuffer + posIndex); + point->z = 0; + point->transformMat4(*point, nodeWorldMat); + } + } + // handle vertex color + if (needColor) { + int srcVertexFloatOffset = static_cast(srcVertexBytesOffset / sizeof(float)); + if (_useTint) { + for (auto colorIndex = 0; colorIndex < vertexFloats; colorIndex += vs, srcVertexFloatOffset += vs2) { + if (srcVertexFloatOffset >= maxVFOffset) { + nowColor = colors[colorOffset++]; + handleColor(nowColor); + maxVFOffset = nowColor->vertexFloatOffset; + } + memcpy(dstColorBuffer + colorIndex + 5, &finalColor, sizeof(finalColor)); + memcpy(dstColorBuffer + colorIndex + 6, &darkColor, sizeof(darkColor)); + } + } else { + for (auto colorIndex = 0; colorIndex < vertexFloats; colorIndex += vs, srcVertexFloatOffset += vs2) { + if (srcVertexFloatOffset >= maxVFOffset) { + nowColor = colors[colorOffset++]; + handleColor(nowColor); + maxVFOffset = nowColor->vertexFloatOffset; + } + memcpy(dstColorBuffer + colorIndex + 5, &finalColor, sizeof(finalColor)); + } + } + } + + // move src vertex buffer offset + srcVertexBytesOffset += srcVertexBytes; + + // fill index buffer + indexBytes = static_cast(segment->indexCount * sizeof(uint16_t)); + ib.checkSpace(indexBytes, true); + dstIndexOffset = static_cast(ib.getCurPos() / sizeof(uint16_t)); + dstIndexBuffer = reinterpret_cast(ib.getCurBuffer()); + ib.writeBytes(reinterpret_cast(srcIB.getBuffer()) + srcIndexBytesOffset, indexBytes); + for (auto indexPos = 0; indexPos < segment->indexCount; indexPos++) { + dstIndexBuffer[indexPos] += dstVertexOffset; + } + srcIndexBytesOffset += indexBytes; + + // fill new index and vertex buffer id + UIMeshBuffer *uiMeshBuffer = mb->getUIMeshBuffer(); + curDrawInfo->setMeshBuffer(uiMeshBuffer); + + // fill new index offset + curDrawInfo->setIndexOffset(dstIndexOffset); + // fill new indice segamentation count + curDrawInfo->setIbCount(segment->indexCount); + } + + if (_useAttach) { + const auto &bonesData = frameData->getBones(); + auto boneCount = frameData->getBoneCount(); + + for (std::size_t i = 0, n = boneCount; i < n; i++) { + auto *bone = bonesData[i]; + attachInfo->checkSpace(sizeof(cc::Mat4), true); + attachInfo->writeBytes(reinterpret_cast(&bone->globalTransformMatrix), sizeof(cc::Mat4)); + } + } +} + +Skeleton *SkeletonCacheAnimation::getSkeleton() const { + return _skeletonCache->getSkeleton(); +} + +void SkeletonCacheAnimation::setTimeScale(float scale) { + _timeScale = scale; +} + +float SkeletonCacheAnimation::getTimeScale() const { + return _timeScale; +} + +void SkeletonCacheAnimation::paused(bool value) { + _paused = value; +} + +Bone *SkeletonCacheAnimation::findBone(const std::string &boneName) const { + return _skeletonCache->findBone(boneName); +} + +Slot *SkeletonCacheAnimation::findSlot(const std::string &slotName) const { + return _skeletonCache->findSlot(slotName); +} + +void SkeletonCacheAnimation::setSkin(const std::string &skinName) { + _skeletonCache->setSkin(skinName); + _skeletonCache->resetAllAnimationData(); +} + +void SkeletonCacheAnimation::setSkin(const char *skinName) { + _skeletonCache->setSkin(skinName); + _skeletonCache->resetAllAnimationData(); +} + +Attachment *SkeletonCacheAnimation::getAttachment(const std::string &slotName, const std::string &attachmentName) const { + return _skeletonCache->getAttachment(slotName, attachmentName); +} + +bool SkeletonCacheAnimation::setAttachment(const std::string &slotName, const std::string &attachmentName) { + auto ret = _skeletonCache->setAttachment(slotName, attachmentName); + _skeletonCache->resetAllAnimationData(); + return ret; +} + +bool SkeletonCacheAnimation::setAttachment(const std::string &slotName, const char *attachmentName) { + auto ret = _skeletonCache->setAttachment(slotName, attachmentName); + _skeletonCache->resetAllAnimationData(); + return ret; +} + +void SkeletonCacheAnimation::setColor(float r, float g, float b, float a) { + _nodeColor.r = r / 255.0F; + _nodeColor.g = g / 255.0F; + _nodeColor.b = b / 255.0F; + _nodeColor.a = a / 255.0F; +} + +void SkeletonCacheAnimation::setBatchEnabled(bool enabled) { + if (_enableBatch != enabled) { + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } + _materialCaches.clear(); + _enableBatch = enabled; + } +} + +void SkeletonCacheAnimation::setOpacityModifyRGB(bool value) { + _premultipliedAlpha = value; +} + +bool SkeletonCacheAnimation::isOpacityModifyRGB() const { + return _premultipliedAlpha; +} + +void SkeletonCacheAnimation::beginSchedule() { + MiddlewareManager::getInstance()->addTimer(this); +} + +void SkeletonCacheAnimation::stopSchedule() { + MiddlewareManager::getInstance()->removeTimer(this); + + if (_sharedBufferOffset) { + _sharedBufferOffset->reset(); + _sharedBufferOffset->clear(); + } +} + +void SkeletonCacheAnimation::onEnable() { + beginSchedule(); +} + +void SkeletonCacheAnimation::onDisable() { + stopSchedule(); +} + +void SkeletonCacheAnimation::setUseTint(bool enabled) { + // cache mode default enable use tint + // _useTint = enabled; +} + +void SkeletonCacheAnimation::setAttachEnabled(bool enabled) { + _useAttach = enabled; +} + +void SkeletonCacheAnimation::setAnimation(const std::string &name, bool loop) { + _playTimes = loop ? 0 : 1; + _animationName = name; + _animationData = _skeletonCache->buildAnimationData(_animationName); + _isAniComplete = false; + _accTime = 0.0F; + _playCount = 0; + _curFrameIndex = 0; +} + +void SkeletonCacheAnimation::addAnimation(const std::string &name, bool loop, float delay) { + auto *aniInfo = new AniQueueData(); + aniInfo->animationName = name; + aniInfo->loop = loop; + aniInfo->delay = delay; + _animationQueue.push(aniInfo); +} + +Animation *SkeletonCacheAnimation::findAnimation(const std::string &name) const { + return _skeletonCache->findAnimation(name); +} + +void SkeletonCacheAnimation::setStartListener(const CacheFrameEvent &listener) { + _startListener = listener; +} + +void SkeletonCacheAnimation::setEndListener(const CacheFrameEvent &listener) { + _endListener = listener; +} + +void SkeletonCacheAnimation::setCompleteListener(const CacheFrameEvent &listener) { + _completeListener = listener; +} + +void SkeletonCacheAnimation::updateAnimationCache(const std::string &animationName) { + _skeletonCache->resetAnimationData(animationName); +} + +void SkeletonCacheAnimation::updateAllAnimationCache() { + _skeletonCache->resetAllAnimationData(); +} + +se_object_ptr SkeletonCacheAnimation::getSharedBufferOffset() const { + if (_sharedBufferOffset) { + return _sharedBufferOffset->getTypeArray(); + } + return nullptr; +} + +void SkeletonCacheAnimation::setToSetupPose() { + if (_skeletonCache) { + _skeletonCache->setToSetupPose(); + } +} + +void SkeletonCacheAnimation::setBonesToSetupPose() { + if (_skeletonCache) { + _skeletonCache->setBonesToSetupPose(); + } +} + +void SkeletonCacheAnimation::setSlotsToSetupPose() { + if (_skeletonCache) { + _skeletonCache->setSlotsToSetupPose(); + } +} +void SkeletonCacheAnimation::setRenderEntity(cc::RenderEntity *entity) { + _entity = entity; +} + +void SkeletonCacheAnimation::setMaterial(cc::Material *material) { + _material = material; + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } + _materialCaches.clear(); +} + +cc::RenderDrawInfo *SkeletonCacheAnimation::requestDrawInfo(int idx) { + if (_drawInfoArray.size() < idx + 1) { + cc::RenderDrawInfo *draw = new cc::RenderDrawInfo(); + draw->setDrawInfoType(static_cast(RenderDrawInfoType::MIDDLEWARE)); + _drawInfoArray.push_back(draw); + } + return _drawInfoArray[idx]; +} + +cc::Material *SkeletonCacheAnimation::requestMaterial(uint16_t blendSrc, uint16_t blendDst) { + uint32_t key = static_cast(blendSrc) << 16 | static_cast(blendDst); + if (_materialCaches.find(key) == _materialCaches.end()) { + const IMaterialInstanceInfo info{ + (Material *)_material, + 0}; + MaterialInstance *materialInstance = new MaterialInstance(info); + PassOverrides overrides; + BlendStateInfo stateInfo; + stateInfo.blendColor = gfx::Color{1.0F, 1.0F, 1.0F, 1.0F}; + BlendTargetInfo targetInfo; + targetInfo.blendEq = gfx::BlendOp::ADD; + targetInfo.blendAlphaEq = gfx::BlendOp::ADD; + targetInfo.blendSrc = (gfx::BlendFactor)blendSrc; + targetInfo.blendDst = (gfx::BlendFactor)blendDst; + targetInfo.blendSrcAlpha = (gfx::BlendFactor)blendSrc; + targetInfo.blendDstAlpha = (gfx::BlendFactor)blendDst; + BlendTargetInfoList targetList{targetInfo}; + stateInfo.targets = targetList; + overrides.blendState = stateInfo; + materialInstance->overridePipelineStates(overrides); + const MacroRecord macros{{"TWO_COLORED", _useTint}, {"USE_LOCAL", !_enableBatch}}; + materialInstance->recompileShaders(macros); + _materialCaches[key] = materialInstance; + } + return _materialCaches[key]; +} + +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/SkeletonCacheAnimation.h b/cocos/editor-support/spine-creator-support/SkeletonCacheAnimation.h new file mode 100644 index 0000000..cbeaa2a --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonCacheAnimation.h @@ -0,0 +1,145 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once +#include +#include "MiddlewareManager.h" +#include "SkeletonCache.h" +#include "base/RefCounted.h" +#include "middleware-adapter.h" +#include "spine/spine.h" + +namespace cc { +class RenderEntity; +class RenderDrawInfo; +class Material; +}; + +namespace spine { + +class SkeletonCacheAnimation : public cc::RefCounted, public cc::middleware::IMiddleware { +public: + SkeletonCacheAnimation(const std::string &uuid, bool isShare); + ~SkeletonCacheAnimation() override; + + void update(float dt) override; + void render(float dt) override; + + Skeleton *getSkeleton() const; + + void setTimeScale(float scale); + float getTimeScale() const; + + void paused(bool value); + + Bone *findBone(const std::string &boneName) const; + Slot *findSlot(const std::string &slotName) const; + + void setSkin(const std::string &skinName); + void setSkin(const char *skinName); + + Attachment *getAttachment(const std::string &slotName, const std::string &attachmentName) const; + bool setAttachment(const std::string &slotName, const std::string &attachmentName); + bool setAttachment(const std::string &slotName, const char *attachmentName); + void setColor(float r, float g, float b, float a); + void setBatchEnabled(bool enabled); + void setAttachEnabled(bool enabled); + + void setOpacityModifyRGB(bool value); + bool isOpacityModifyRGB() const; + + void beginSchedule(); + void stopSchedule(); + void onEnable(); + void onDisable(); + void setUseTint(bool enabled); + + void setAnimation(const std::string &name, bool loop); + void addAnimation(const std::string &name, bool loop, float delay = 0); + Animation *findAnimation(const std::string &name) const; + + using CacheFrameEvent = std::function; + void setStartListener(const CacheFrameEvent &listener); + void setEndListener(const CacheFrameEvent &listener); + void setCompleteListener(const CacheFrameEvent &listener); + void updateAnimationCache(const std::string &animationName); + void updateAllAnimationCache(); + + void setToSetupPose(); + void setBonesToSetupPose(); + void setSlotsToSetupPose(); + + /** + * @return shared buffer offset, it's a Uint32Array + * format |render info offset|attach info offset| + */ + se_object_ptr getSharedBufferOffset() const; + + cc::RenderDrawInfo *requestDrawInfo(int idx); + cc::Material *requestMaterial(uint16_t blendSrc, uint16_t blendDst); + void setMaterial(cc::Material *material); + void setRenderEntity(cc::RenderEntity* entity); +private: + float _timeScale = 1; + bool _paused = false; + bool _useAttach = false; + cc::middleware::Color4F _nodeColor = cc::middleware::Color4F::WHITE; + bool _premultipliedAlpha = false; + + CacheFrameEvent _startListener = nullptr; + CacheFrameEvent _endListener = nullptr; + CacheFrameEvent _completeListener = nullptr; + + SkeletonCache *_skeletonCache = nullptr; + SkeletonCache::AnimationData *_animationData = nullptr; + int _curFrameIndex = -1; + + float _accTime = 0.0F; + int _playCount = 0; + int _playTimes = 0; + bool _isAniComplete = true; + std::string _animationName; + bool _useTint = true; + bool _enableBatch = false; + + struct AniQueueData { + std::string animationName; + bool loop = false; + float delay = 0.0F; + }; + std::queue _animationQueue; + AniQueueData *_headAnimation = nullptr; + + cc::middleware::IOTypedArray *_sharedBufferOffset = nullptr; + cc::RenderEntity *_entity = nullptr; + cc::Material *_material = nullptr; + ccstd::vector _drawInfoArray; + ccstd::unordered_map _materialCaches; +}; +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/SkeletonCacheMgr.cpp b/cocos/editor-support/spine-creator-support/SkeletonCacheMgr.cpp new file mode 100644 index 0000000..85bfeca --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonCacheMgr.cpp @@ -0,0 +1,53 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SkeletonCacheMgr.h" +#include "base/DeferredReleasePool.h" + +namespace spine { +SkeletonCacheMgr *SkeletonCacheMgr::instance = nullptr; +SkeletonCache *SkeletonCacheMgr::buildSkeletonCache(const std::string &uuid) { + SkeletonCache *animation = _caches.at(uuid); + if (!animation) { + animation = new SkeletonCache(); + animation->addRef(); + animation->initWithUUID(uuid); + _caches.insert(uuid, animation); + cc::DeferredReleasePool::add(animation); + } + return animation; +} + +void SkeletonCacheMgr::removeSkeletonCache(const std::string &uuid) { + auto it = _caches.find(uuid); + if (it != _caches.end()) { + _caches.erase(it); + } +} +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/SkeletonCacheMgr.h b/cocos/editor-support/spine-creator-support/SkeletonCacheMgr.h new file mode 100644 index 0000000..12421c1 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonCacheMgr.h @@ -0,0 +1,60 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once +#include "SkeletonCache.h" +#include "base/RefMap.h" + +namespace spine { + +class SkeletonCacheMgr { +public: + static SkeletonCacheMgr *getInstance() { + if (instance == nullptr) { + instance = new SkeletonCacheMgr(); + } + return instance; + } + + static void destroyInstance() { + if (instance) { + delete instance; + instance = nullptr; + } + } + + void removeSkeletonCache(const std::string &uuid); + SkeletonCache *buildSkeletonCache(const std::string &uuid); + +private: + static SkeletonCacheMgr *instance; + cc::RefMap _caches; +}; + +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/SkeletonDataMgr.cpp b/cocos/editor-support/spine-creator-support/SkeletonDataMgr.cpp new file mode 100644 index 0000000..1f02e7d --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonDataMgr.cpp @@ -0,0 +1,116 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SkeletonDataMgr.h" +#include +#include + +using namespace spine; //NOLINT + +namespace spine { + +class SkeletonDataInfo { +public: + SkeletonDataInfo() = default; + + ~SkeletonDataInfo() { + if (data) { + delete data; + data = nullptr; + } + + if (atlas) { + delete atlas; + atlas = nullptr; + } + + if (attachmentLoader) { + delete attachmentLoader; + attachmentLoader = nullptr; + } + } + + SkeletonData *data = nullptr; + Atlas *atlas = nullptr; + AttachmentLoader *attachmentLoader = nullptr; + std::vector texturesIndex; +}; + +} // namespace spine + +SkeletonDataMgr *SkeletonDataMgr::instance = nullptr; + +SkeletonDataMgr::~SkeletonDataMgr() { + _destroyCallback = nullptr; + for (auto &e : _dataMap) { + delete e.second; + } + _dataMap.clear(); +} + +bool SkeletonDataMgr::hasSkeletonData(const std::string &uuid) { + auto it = _dataMap.find(uuid); + return it != _dataMap.end(); +} + +void SkeletonDataMgr::setSkeletonData(const std::string &uuid, SkeletonData *data, Atlas *atlas, AttachmentLoader *attachmentLoader, const std::vector &texturesIndex) { + auto it = _dataMap.find(uuid); + if (it != _dataMap.end()) { + releaseByUUID(uuid); + } + auto *info = new SkeletonDataInfo(); + info->data = data; + info->atlas = atlas; + info->attachmentLoader = attachmentLoader; + info->texturesIndex = texturesIndex; + _dataMap[uuid] = info; +} + +SkeletonData *SkeletonDataMgr::retainByUUID(const std::string &uuid) { + auto dataIt = _dataMap.find(uuid); + if (dataIt == _dataMap.end()) { + return nullptr; + } + return dataIt->second->data; +} + +void SkeletonDataMgr::releaseByUUID(const std::string &uuid) { + auto dataIt = _dataMap.find(uuid); + if (dataIt == _dataMap.end()) { + return; + } + SkeletonDataInfo *info = dataIt->second; + _dataMap.erase(dataIt); + if (_destroyCallback) { + for (auto &item : info->texturesIndex) { + _destroyCallback(item); + } + } + delete info; +} diff --git a/cocos/editor-support/spine-creator-support/SkeletonDataMgr.h b/cocos/editor-support/spine-creator-support/SkeletonDataMgr.h new file mode 100644 index 0000000..197a881 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonDataMgr.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include "base/RefCounted.h" +#include "spine/SkeletonData.h" +#include "spine/spine.h" + +namespace spine { + +class SkeletonDataInfo; + +/** + * Cache skeleton data. + */ +class SkeletonDataMgr final { +public: + static SkeletonDataMgr *getInstance() { + if (instance == nullptr) { + instance = new SkeletonDataMgr(); + } + return instance; + } + + static void destroyInstance() { + if (instance) { + delete instance; + instance = nullptr; + } + } + + SkeletonDataMgr() = default; + ~SkeletonDataMgr(); + + bool hasSkeletonData(const std::string &uuid); + void setSkeletonData(const std::string &uuid, SkeletonData *data, Atlas *atlas, AttachmentLoader *attachmentLoader, const std::vector &texturesIndex); + // equal to 'findByUUID' + SkeletonData *retainByUUID(const std::string &uuid); + // equal to 'deleteByUUID' + void releaseByUUID(const std::string &uuid); + + using destroyCallback = std::function; + void setDestroyCallback(destroyCallback callback) { + _destroyCallback = std::move(callback); + } + +private: + static SkeletonDataMgr *instance; + destroyCallback _destroyCallback = nullptr; + std::map _dataMap; +}; + +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/SkeletonRenderer.cpp b/cocos/editor-support/spine-creator-support/SkeletonRenderer.cpp new file mode 100644 index 0000000..bc21586 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonRenderer.cpp @@ -0,0 +1,1125 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "spine-creator-support/SkeletonRenderer.h" +#include +#include "2d/renderer/RenderDrawInfo.h" +#include "2d/renderer/RenderEntity.h" +#include "MiddlewareMacro.h" +#include "SharedBufferManager.h" +#include "SkeletonDataMgr.h" +#include "base/DeferredReleasePool.h" +#include "base/TypeDef.h" +#include "base/memory/Memory.h" +#include "gfx-base/GFXDef.h" +#include "math/Math.h" +#include "math/Vec3.h" +#include "renderer/core/MaterialInstance.h" +#include "spine-creator-support/AttachmentVertices.h" +#include "spine-creator-support/spine-cocos2dx.h" + +USING_NS_MW; // NOLINT(google-build-using-namespace) +using namespace spine; // NOLINT(google-build-using-namespace) +using namespace cc; // NOLINT(google-build-using-namespace) +using namespace cc::gfx; // NOLINT(google-build-using-namespace) + +using std::max; +using std::min; + +static const std::string TECH_STAGE = "opaque"; +static const std::string TEXTURE_KEY = "texture"; + +static spine::Cocos2dTextureLoader textureLoader; +static std::vector _slotTextureSet{}; + +enum DebugType { + NONE = 0, + SLOTS, + MESH, + BONES +}; +SkeletonRenderer *SkeletonRenderer::create() { + return new SkeletonRenderer(); +} + +SkeletonRenderer *SkeletonRenderer::createWithSkeleton(Skeleton *skeleton, bool ownsSkeleton, bool ownsSkeletonData) { + return new SkeletonRenderer(skeleton, ownsSkeleton, ownsSkeletonData); +} + +SkeletonRenderer *SkeletonRenderer::createWithData(SkeletonData *skeletonData, bool ownsSkeletonData) { + return new SkeletonRenderer(skeletonData, ownsSkeletonData); +} + +SkeletonRenderer *SkeletonRenderer::createWithFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) { + return new SkeletonRenderer(skeletonDataFile, atlasFile, scale); +} + +void SkeletonRenderer::initialize() { + if (_clipper == nullptr) { + _clipper = new (__FILE__, __LINE__) SkeletonClipping(); + } + + if (_sharedBufferOffset == nullptr) { + // store global TypedArray begin and end offset + _sharedBufferOffset = new cc::middleware::IOTypedArray(se::Object::TypedArrayType::UINT32, sizeof(uint32_t) * 2); + } + + _skeleton->setToSetupPose(); + _skeleton->updateWorldTransform(); +} + +void SkeletonRenderer::beginSchedule() { + MiddlewareManager::getInstance()->addTimer(this); +} + +void SkeletonRenderer::onEnable() { + beginSchedule(); +} + +void SkeletonRenderer::onDisable() { + stopSchedule(); +} + +void SkeletonRenderer::stopSchedule() { + MiddlewareManager::getInstance()->removeTimer(this); + if (_sharedBufferOffset) { + _sharedBufferOffset->reset(); + _sharedBufferOffset->clear(); + } + if (_debugBuffer) { + _debugBuffer->reset(); + _debugBuffer->clear(); + } +} + +void SkeletonRenderer::setSkeletonData(SkeletonData *skeletonData, bool ownsSkeletonData) { + _skeleton = new (__FILE__, __LINE__) Skeleton(skeletonData); + _ownsSkeletonData = ownsSkeletonData; +} + +SkeletonRenderer::SkeletonRenderer() = default; + +SkeletonRenderer::SkeletonRenderer(Skeleton *skeleton, bool ownsSkeleton, bool ownsSkeletonData, bool ownsAtlas) { + initWithSkeleton(skeleton, ownsSkeleton, ownsSkeletonData, ownsAtlas); +} + +SkeletonRenderer::SkeletonRenderer(SkeletonData *skeletonData, bool ownsSkeletonData) { + initWithData(skeletonData, ownsSkeletonData); +} + +SkeletonRenderer::SkeletonRenderer(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) { + initWithJsonFile(skeletonDataFile, atlasFile, scale); +} + +SkeletonRenderer::~SkeletonRenderer() { + CC_SAFE_RELEASE(_effectDelegate); + if (_ownsSkeletonData) delete _skeleton->getData(); + if (_ownsSkeleton) delete _skeleton; + if (_ownsAtlas && _atlas) delete _atlas; + delete _attachmentLoader; + delete _clipper; + + if (_debugBuffer) { + delete _debugBuffer; + _debugBuffer = nullptr; + } + + if (_sharedBufferOffset) { + delete _sharedBufferOffset; + _sharedBufferOffset = nullptr; + } + + for (auto *draw : _drawInfoArray) { + CC_SAFE_DELETE(draw); + } + + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } + + stopSchedule(); +} + +void SkeletonRenderer::initWithUUID(const std::string &uuid) { + _ownsSkeleton = true; + _uuid = uuid; + SkeletonData *skeletonData = SkeletonDataMgr::getInstance()->retainByUUID(uuid); + CC_ASSERT(skeletonData); + + setSkeletonData(skeletonData, false); + initialize(); +} + +void SkeletonRenderer::initWithSkeleton(Skeleton *skeleton, bool ownsSkeleton, bool ownsSkeletonData, bool ownsAtlas) { + _skeleton = skeleton; + _ownsSkeleton = ownsSkeleton; + _ownsSkeletonData = ownsSkeletonData; + _ownsAtlas = ownsAtlas; + + initialize(); +} + +void SkeletonRenderer::initWithData(SkeletonData *skeletonData, bool ownsSkeletonData) { + _ownsSkeleton = true; + setSkeletonData(skeletonData, ownsSkeletonData); + initialize(); +} + +void SkeletonRenderer::initWithJsonFile(const std::string &skeletonDataFile, Atlas *atlas, float scale) { + _atlas = atlas; + _attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas); + + SkeletonJson json(_attachmentLoader); + json.setScale(scale); + SkeletonData *skeletonData = json.readSkeletonDataFile(skeletonDataFile.c_str()); + CC_ASSERT(skeletonData); // Can use json.getError() to get error message. + + _ownsSkeleton = true; + setSkeletonData(skeletonData, true); + + initialize(); +} + +void SkeletonRenderer::initWithJsonFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) { + _atlas = new (__FILE__, __LINE__) Atlas(atlasFile.c_str(), &textureLoader); + CC_ASSERT(_atlas); + + _attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas); + + SkeletonJson json(_attachmentLoader); + json.setScale(scale); + SkeletonData *skeletonData = json.readSkeletonDataFile(skeletonDataFile.c_str()); + CC_ASSERT(skeletonData); // Can use json.getError() to get error message. + + _ownsSkeleton = true; + _ownsAtlas = true; + setSkeletonData(skeletonData, true); + + initialize(); +} + +void SkeletonRenderer::initWithBinaryFile(const std::string &skeletonDataFile, Atlas *atlas, float scale) { + _atlas = atlas; + _attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas); + + SkeletonBinary binary(_attachmentLoader); + binary.setScale(scale); + SkeletonData *skeletonData = binary.readSkeletonDataFile(skeletonDataFile.c_str()); + CC_ASSERT(skeletonData); // Can use binary.getError() to get error message. + + _ownsSkeleton = true; + setSkeletonData(skeletonData, true); + + initialize(); +} + +void SkeletonRenderer::initWithBinaryFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale) { + _atlas = new (__FILE__, __LINE__) Atlas(atlasFile.c_str(), &textureLoader); + CC_ASSERT(_atlas); + + _attachmentLoader = new (__FILE__, __LINE__) Cocos2dAtlasAttachmentLoader(_atlas); + + SkeletonBinary binary(_attachmentLoader); + binary.setScale(scale); + SkeletonData *skeletonData = binary.readSkeletonDataFile(skeletonDataFile.c_str()); + CC_ASSERT(skeletonData); // Can use binary.getError() to get error message. + + _ownsSkeleton = true; + _ownsAtlas = true; + setSkeletonData(skeletonData, true); + + initialize(); +} + +void SkeletonRenderer::render(float /*deltaTime*/) { + if (!_skeleton) return; + auto *entity = _entity; + entity->clearDynamicRenderDrawInfos(); + _sharedBufferOffset->reset(); + _sharedBufferOffset->clear(); + + // avoid other place call update. + auto *mgr = MiddlewareManager::getInstance(); + if (!mgr->isRendering) return; + + auto *attachMgr = mgr->getAttachInfoMgr(); + auto *attachInfo = attachMgr->getBuffer(); + if (!attachInfo) return; + // store attach info offset + _sharedBufferOffset->writeUint32(static_cast(attachInfo->getCurPos()) / sizeof(uint32_t)); + + // If opacity is 0,then return. + if (_skeleton->getColor().a == 0) { + return; + } + auto &nodeWorldMat = entity->getNode()->getWorldMatrix(); + // color range is [0.0, 1.0] + cc::middleware::Color4F color; + cc::middleware::Color4F darkColor; + AttachmentVertices *attachmentVertices = nullptr; + bool inRange = !(_startSlotIndex != -1 || _endSlotIndex != -1); + auto vertexFormat = _useTint ? VF_XYZUVCC : VF_XYZUVC; + cc::middleware::MeshBuffer *mb = mgr->getMeshBuffer(vertexFormat); + cc::middleware::IOBuffer &vb = mb->getVB(); + cc::middleware::IOBuffer &ib = mb->getIB(); + + // vertex size int bytes with one color + unsigned int vbs1 = sizeof(V3F_T2F_C4B); + // vertex size in floats with one color + unsigned int vs1 = vbs1 / sizeof(float); + // vertex size int bytes with two color + unsigned int vbs2 = sizeof(V3F_T2F_C4B_C4B); + // verex size in floats with two color + unsigned int vs2 = vbs2 / sizeof(float); + + auto vbs = vbs1; + if (_useTint) { + vbs = vbs2; + } + + unsigned int vbSize = 0; + unsigned int ibSize = 0; + + int curBlendSrc = -1; + int curBlendDst = -1; + int curBlendMode = -1; + int preBlendMode = -1; + uint32_t curISegLen = 0; + cc::Texture2D *preTexture = nullptr; + cc::Texture2D *curTexture = nullptr; + RenderDrawInfo *curDrawInfo = nullptr; + + int materialLen = 0; + Slot *slot = nullptr; + int isFull = 0; + + if (_debugSlots || _debugBones || _debugMesh) { + // If enable debug draw,then init debug buffer. + if (_debugBuffer == nullptr) { + _debugBuffer = new cc::middleware::IOTypedArray(se::Object::TypedArrayType::FLOAT32, MAX_DEBUG_BUFFER_SIZE); + } + _debugBuffer->reset(); + } + + auto flush = [&]() { + // fill pre segment indices count field + if (curDrawInfo) { + curDrawInfo->setIbCount(curISegLen); + } + curDrawInfo = requestDrawInfo(materialLen); + entity->addDynamicRenderDrawInfo(curDrawInfo); + // prepare to fill new segment field + curBlendMode = slot->getData().getBlendMode(); + switch (curBlendMode) { + case BlendMode_Additive: + curBlendSrc = static_cast(_premultipliedAlpha ? BlendFactor::ONE : BlendFactor::SRC_ALPHA); + curBlendDst = static_cast(BlendFactor::ONE); + break; + case BlendMode_Multiply: + curBlendSrc = static_cast(BlendFactor::DST_COLOR); + curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_ALPHA); + break; + case BlendMode_Screen: + curBlendSrc = static_cast(BlendFactor::ONE); + curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_COLOR); + break; + default: + curBlendSrc = static_cast(_premultipliedAlpha ? BlendFactor::ONE : BlendFactor::SRC_ALPHA); + curBlendDst = static_cast(BlendFactor::ONE_MINUS_SRC_ALPHA); + } + auto *material = requestMaterial(curBlendSrc, curBlendDst); + curDrawInfo->setMaterial(material); + gfx::Texture *texture = curTexture->getGFXTexture(); + gfx::Sampler *sampler = curTexture->getGFXSampler(); + curDrawInfo->setTexture(texture); + curDrawInfo->setSampler(sampler); + auto *uiMeshBuffer = mb->getUIMeshBuffer(); + curDrawInfo->setMeshBuffer(uiMeshBuffer); + curDrawInfo->setIndexOffset(static_cast(ib.getCurPos()) / sizeof(uint16_t)); + // reset pre blend mode to current + preBlendMode = static_cast(slot->getData().getBlendMode()); + // reset pre texture index to current + preTexture = curTexture; + // reset index segmentation count + curISegLen = 0; + // material length increased + materialLen++; + }; + + VertexEffect *effect = nullptr; + if (_effectDelegate) { + effect = _effectDelegate->getVertexEffect(); + } + + if (effect) { + effect->begin(*_skeleton); + } + + auto &drawOrder = _skeleton->getDrawOrder(); + for (size_t i = 0, n = drawOrder.size(); i < n; ++i) { + isFull = 0; + slot = drawOrder[i]; + + if (slot->getBone().isActive() == false) { + continue; + } + + if (_startSlotIndex >= 0 && _startSlotIndex == slot->getData().getIndex()) { + inRange = true; + } + + if (!inRange) { + _clipper->clipEnd(*slot); + continue; + } + + if (_endSlotIndex >= 0 && _endSlotIndex == slot->getData().getIndex()) { + inRange = false; + } + + if (!slot->getAttachment()) { + _clipper->clipEnd(*slot); + continue; + } + + cc::middleware::Triangles triangles; + cc::middleware::TwoColorTriangles trianglesTwoColor; + + if (slot->getAttachment()->getRTTI().isExactly(RegionAttachment::rtti)) { + auto *attachment = dynamic_cast(slot->getAttachment()); + attachmentVertices = reinterpret_cast(attachment->getRendererObject()); + + // Early exit if attachment is invisible + if (attachment->getColor().a == 0) { + _clipper->clipEnd(*slot); + continue; + } + + if (!_useTint) { + triangles.vertCount = attachmentVertices->_triangles->vertCount; + vbSize = triangles.vertCount * sizeof(V3F_T2F_C4B); + isFull |= vb.checkSpace(vbSize, true); + triangles.verts = reinterpret_cast(vb.getCurBuffer()); + memcpy(static_cast(triangles.verts), static_cast(attachmentVertices->_triangles->verts), vbSize); + attachment->computeWorldVertices(slot->getBone(), reinterpret_cast(triangles.verts), 0, vs1); + + triangles.indexCount = attachmentVertices->_triangles->indexCount; + ibSize = triangles.indexCount * sizeof(uint16_t); + ib.checkSpace(ibSize, true); + triangles.indices = reinterpret_cast(ib.getCurBuffer()); + memcpy(triangles.indices, attachmentVertices->_triangles->indices, ibSize); + } else { + trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount; + vbSize = trianglesTwoColor.vertCount * sizeof(V3F_T2F_C4B_C4B); + isFull |= vb.checkSpace(vbSize, true); + trianglesTwoColor.verts = reinterpret_cast(vb.getCurBuffer()); + for (int ii = 0; ii < trianglesTwoColor.vertCount; ii++) { + trianglesTwoColor.verts[ii].texCoord = attachmentVertices->_triangles->verts[ii].texCoord; + } + attachment->computeWorldVertices(slot->getBone(), reinterpret_cast(trianglesTwoColor.verts), 0, vs2); + + trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount; + ibSize = trianglesTwoColor.indexCount * sizeof(uint16_t); + ib.checkSpace(ibSize, true); + trianglesTwoColor.indices = reinterpret_cast(ib.getCurBuffer()); + memcpy(trianglesTwoColor.indices, attachmentVertices->_triangles->indices, ibSize); + } + + color.r = attachment->getColor().r; + color.g = attachment->getColor().g; + color.b = attachment->getColor().b; + color.a = attachment->getColor().a; + + if (_debugSlots) { + _debugBuffer->writeFloat32(DebugType::SLOTS); + _debugBuffer->writeFloat32(8); + float *vertices = _useTint ? reinterpret_cast(trianglesTwoColor.verts) : reinterpret_cast(triangles.verts); + unsigned int stride = _useTint ? vs2 : vs1; + // Quadrangle has 4 vertex. + for (int ii = 0; ii < 4; ii++) { + _debugBuffer->writeFloat32(vertices[0]); + _debugBuffer->writeFloat32(vertices[1]); + vertices += stride; + } + } + } else if (slot->getAttachment()->getRTTI().isExactly(MeshAttachment::rtti)) { + auto *attachment = dynamic_cast(slot->getAttachment()); + attachmentVertices = static_cast(attachment->getRendererObject()); + + // Early exit if attachment is invisible + if (attachment->getColor().a == 0) { + _clipper->clipEnd(*slot); + continue; + } + + if (!_useTint) { + triangles.vertCount = attachmentVertices->_triangles->vertCount; + vbSize = triangles.vertCount * sizeof(V3F_T2F_C4B); + isFull |= vb.checkSpace(vbSize, true); + triangles.verts = reinterpret_cast(vb.getCurBuffer()); + memcpy(static_cast(triangles.verts), static_cast(attachmentVertices->_triangles->verts), vbSize); + attachment->computeWorldVertices(*slot, 0, attachment->getWorldVerticesLength(), reinterpret_cast(triangles.verts), 0, vs1); + + triangles.indexCount = attachmentVertices->_triangles->indexCount; + ibSize = triangles.indexCount * sizeof(uint16_t); + ib.checkSpace(ibSize, true); + triangles.indices = reinterpret_cast(ib.getCurBuffer()); + memcpy(triangles.indices, attachmentVertices->_triangles->indices, ibSize); + } else { + trianglesTwoColor.vertCount = attachmentVertices->_triangles->vertCount; + vbSize = trianglesTwoColor.vertCount * sizeof(V3F_T2F_C4B_C4B); + isFull |= vb.checkSpace(vbSize, true); + trianglesTwoColor.verts = reinterpret_cast(vb.getCurBuffer()); + for (int ii = 0; ii < trianglesTwoColor.vertCount; ii++) { + trianglesTwoColor.verts[ii].texCoord = attachmentVertices->_triangles->verts[ii].texCoord; + } + attachment->computeWorldVertices(*slot, 0, attachment->getWorldVerticesLength(), reinterpret_cast(trianglesTwoColor.verts), 0, vs2); + + trianglesTwoColor.indexCount = attachmentVertices->_triangles->indexCount; + ibSize = trianglesTwoColor.indexCount * sizeof(uint16_t); + ib.checkSpace(ibSize, true); + trianglesTwoColor.indices = reinterpret_cast(ib.getCurBuffer()); + memcpy(trianglesTwoColor.indices, attachmentVertices->_triangles->indices, ibSize); + } + + color.r = attachment->getColor().r; + color.g = attachment->getColor().g; + color.b = attachment->getColor().b; + color.a = attachment->getColor().a; + + if (_debugMesh) { + int indexCount = _useTint ? trianglesTwoColor.indexCount : triangles.indexCount; + uint16_t *indices = _useTint ? trianglesTwoColor.indices : triangles.indices; + float *vertices = _useTint ? reinterpret_cast(trianglesTwoColor.verts) : reinterpret_cast(triangles.verts); + + unsigned int stride = _useTint ? vs2 : vs1; + _debugBuffer->writeFloat32(DebugType::MESH); + _debugBuffer->writeFloat32(static_cast(indexCount * 2)); + for (int ii = 0; ii < indexCount; ii += 3) { + unsigned int v1 = indices[ii] * stride; + unsigned int v2 = indices[ii + 1] * stride; + unsigned int v3 = indices[ii + 2] * stride; + _debugBuffer->writeFloat32(vertices[v1]); + _debugBuffer->writeFloat32(vertices[v1 + 1]); + _debugBuffer->writeFloat32(vertices[v2]); + _debugBuffer->writeFloat32(vertices[v2 + 1]); + _debugBuffer->writeFloat32(vertices[v3]); + _debugBuffer->writeFloat32(vertices[v3 + 1]); + } + } + + } else if (slot->getAttachment()->getRTTI().isExactly(ClippingAttachment::rtti)) { + auto *clip = dynamic_cast(slot->getAttachment()); + _clipper->clipStart(*slot, clip); + continue; + } else { + _clipper->clipEnd(*slot); + continue; + } + + color.a = _skeleton->getColor().a * slot->getColor().a * color.a * _nodeColor.a * 255; + // skip rendering if the color of this attachment is 0 + if (color.a == 0) { + _clipper->clipEnd(*slot); + continue; + } + + float multiplier = _premultipliedAlpha ? color.a : 255; + float red = _nodeColor.r * _skeleton->getColor().r * color.r * multiplier; + float green = _nodeColor.g * _skeleton->getColor().g * color.g * multiplier; + float blue = _nodeColor.b * _skeleton->getColor().b * color.b * multiplier; + + color.r = red * slot->getColor().r; + color.g = green * slot->getColor().g; + color.b = blue * slot->getColor().b; + + if (slot->hasDarkColor()) { + darkColor.r = red * slot->getDarkColor().r; + darkColor.g = green * slot->getDarkColor().g; + darkColor.b = blue * slot->getDarkColor().b; + } else { + darkColor.r = 0; + darkColor.g = 0; + darkColor.b = 0; + } + darkColor.a = _premultipliedAlpha ? 255 : 0; + + // One color tint logic + if (!_useTint) { + // Cliping logic + Color4B light; + light.r = (uint8_t)color.r; + light.g = (uint8_t)color.g; + light.b = (uint8_t)color.b; + light.a = (uint8_t)color.a; + if (_clipper->isClipping()) { + _clipper->clipTriangles(reinterpret_cast(&triangles.verts[0].vertex), triangles.indices, triangles.indexCount, reinterpret_cast(&triangles.verts[0].texCoord), vs1); + + if (_clipper->getClippedTriangles().size() == 0) { + _clipper->clipEnd(*slot); + continue; + } + + triangles.vertCount = static_cast(_clipper->getClippedVertices().size()) >> 1; + vbSize = triangles.vertCount * sizeof(V3F_T2F_C4B); + isFull |= vb.checkSpace(vbSize, true); + triangles.verts = reinterpret_cast(vb.getCurBuffer()); + + triangles.indexCount = static_cast(_clipper->getClippedTriangles().size()); + ibSize = triangles.indexCount * sizeof(uint16_t); + ib.checkSpace(ibSize, true); + triangles.indices = reinterpret_cast(ib.getCurBuffer()); + memcpy(triangles.indices, _clipper->getClippedTriangles().buffer(), sizeof(uint16_t) * _clipper->getClippedTriangles().size()); + + float *verts = _clipper->getClippedVertices().buffer(); + float *uvs = _clipper->getClippedUVs().buffer(); + + if (effect) { + for (int v = 0, vn = triangles.vertCount, vv = 0; v < vn; ++v, vv += 2) { + V3F_T2F_C4B *vertex = triangles.verts + v; + vertex->vertex.x = verts[vv]; + vertex->vertex.y = verts[vv + 1]; + vertex->texCoord.u = uvs[vv]; + vertex->texCoord.v = uvs[vv + 1]; + effect->transform(vertex->vertex.x, vertex->vertex.y); + vertex->color = light; + } + } else { + for (int v = 0, vn = triangles.vertCount, vv = 0; v < vn; ++v, vv += 2) { + V3F_T2F_C4B *vertex = triangles.verts + v; + vertex->vertex.x = verts[vv]; + vertex->vertex.y = verts[vv + 1]; + vertex->texCoord.u = uvs[vv]; + vertex->texCoord.v = uvs[vv + 1]; + vertex->color = light; + } + } + // No cliping logic + } else { + if (effect) { + for (int v = 0, vn = triangles.vertCount; v < vn; ++v) { + V3F_T2F_C4B *vertex = triangles.verts + v; + effect->transform(vertex->vertex.x, vertex->vertex.y); + vertex->color = light; + } + } else { + for (int v = 0, vn = triangles.vertCount; v < vn; ++v) { + V3F_T2F_C4B *vertex = triangles.verts + v; + vertex->color = light; + } + } + } + } + // Two color tint logic + else { + Color4B light; + Color4B dark; + light.r = (uint8_t)color.r; + light.g = (uint8_t)color.g; + light.b = (uint8_t)color.b; + light.a = (uint8_t)color.a; + dark.r = (uint8_t)darkColor.r; + dark.g = (uint8_t)darkColor.g; + dark.b = (uint8_t)darkColor.b; + dark.a = (uint8_t)darkColor.a; + if (_clipper->isClipping()) { + _clipper->clipTriangles(reinterpret_cast(&trianglesTwoColor.verts[0].vertex), trianglesTwoColor.indices, trianglesTwoColor.indexCount, reinterpret_cast(&trianglesTwoColor.verts[0].texCoord), vs2); + + if (_clipper->getClippedTriangles().size() == 0) { + _clipper->clipEnd(*slot); + continue; + } + + trianglesTwoColor.vertCount = static_cast(_clipper->getClippedVertices().size()) >> 1; + vbSize = trianglesTwoColor.vertCount * sizeof(V3F_T2F_C4B_C4B); + isFull |= vb.checkSpace(vbSize, true); + trianglesTwoColor.verts = reinterpret_cast(vb.getCurBuffer()); + + trianglesTwoColor.indexCount = static_cast(_clipper->getClippedTriangles().size()); + ibSize = trianglesTwoColor.indexCount * sizeof(uint16_t); + trianglesTwoColor.indices = reinterpret_cast(ib.getCurBuffer()); + memcpy(trianglesTwoColor.indices, _clipper->getClippedTriangles().buffer(), sizeof(uint16_t) * _clipper->getClippedTriangles().size()); + + float *verts = _clipper->getClippedVertices().buffer(); + float *uvs = _clipper->getClippedUVs().buffer(); + + if (effect) { + for (int v = 0, vn = trianglesTwoColor.vertCount, vv = 0; v < vn; ++v, vv += 2) { + V3F_T2F_C4B_C4B *vertex = trianglesTwoColor.verts + v; + vertex->vertex.x = verts[vv]; + vertex->vertex.y = verts[vv + 1]; + vertex->texCoord.u = uvs[vv]; + vertex->texCoord.v = uvs[vv + 1]; + effect->transform(vertex->vertex.x, vertex->vertex.y); + vertex->color = light; + vertex->color2 = dark; + } + } else { + for (int v = 0, vn = trianglesTwoColor.vertCount, vv = 0; v < vn; ++v, vv += 2) { + V3F_T2F_C4B_C4B *vertex = trianglesTwoColor.verts + v; + vertex->vertex.x = verts[vv]; + vertex->vertex.y = verts[vv + 1]; + vertex->texCoord.u = uvs[vv]; + vertex->texCoord.v = uvs[vv + 1]; + vertex->color = light; + vertex->color2 = dark; + } + } + } else { + if (effect) { + for (int v = 0, vn = trianglesTwoColor.vertCount; v < vn; ++v) { + V3F_T2F_C4B_C4B *vertex = trianglesTwoColor.verts + v; + effect->transform(vertex->vertex.x, vertex->vertex.y); + vertex->color = light; + vertex->color2 = dark; + } + } else { + for (int v = 0, vn = trianglesTwoColor.vertCount; v < vn; ++v) { + V3F_T2F_C4B_C4B *vertex = trianglesTwoColor.verts + v; + vertex->color = light; + vertex->color2 = dark; + } + } + } + } + + curTexture = (cc::Texture2D *)attachmentVertices->_texture->getRealTexture(); + // If texture or blendMode change,will change material. + if (preTexture != curTexture || preBlendMode != slot->getData().getBlendMode() || isFull) { + flush(); + } + if (_enableBatch) { + uint8_t *vbBuffer = vb.getCurBuffer(); + cc::Vec3 *point = nullptr; + for (unsigned int ii = 0, nn = vbSize; ii < nn; ii += vbs) { + point = reinterpret_cast(vbBuffer + ii); + point->z = 0; + point->transformMat4(*point, nodeWorldMat); + } + } + auto vertexOffset = vb.getCurPos() / vbs; + if (vbSize > 0 && ibSize > 0) { + if (vertexOffset > 0) { + auto *ibBuffer = reinterpret_cast(ib.getCurBuffer()); + for (unsigned int ii = 0, nn = ibSize / sizeof(uint16_t); ii < nn; ii++) { + ibBuffer[ii] += vertexOffset; + } + } + vb.move(static_cast(vbSize)); + ib.move(static_cast(ibSize)); + + // Record this turn index segmentation count,it will store in material buffer in the end. + curISegLen += ibSize / sizeof(uint16_t); + } + + _clipper->clipEnd(*slot); + } // End slot traverse + + _clipper->clipEnd(); + + if (effect) effect->end(); + + if (curDrawInfo) curDrawInfo->setIbCount(curISegLen); + + if (_useAttach || _debugBones) { + auto &bones = _skeleton->getBones(); + size_t bonesCount = bones.size(); + + cc::Mat4 boneMat = cc::Mat4::IDENTITY; + + if (_debugBones) { + _debugBuffer->writeFloat32(DebugType::BONES); + _debugBuffer->writeFloat32(static_cast(bonesCount * 4)); + } + + for (size_t i = 0, n = bonesCount; i < n; i++) { + Bone *bone = bones[i]; + + boneMat.m[0] = bone->getA(); + boneMat.m[1] = bone->getC(); + boneMat.m[4] = bone->getB(); + boneMat.m[5] = bone->getD(); + boneMat.m[12] = bone->getWorldX(); + boneMat.m[13] = bone->getWorldY(); + attachInfo->checkSpace(sizeof(boneMat), true); + attachInfo->writeBytes(reinterpret_cast(&boneMat), sizeof(boneMat)); + + if (_debugBones) { + float boneLength = bone->getData().getLength(); + float x = boneLength * bone->getA() + bone->getWorldX(); + float y = boneLength * bone->getC() + bone->getWorldY(); + _debugBuffer->writeFloat32(bone->getWorldX()); + _debugBuffer->writeFloat32(bone->getWorldY()); + _debugBuffer->writeFloat32(x); + _debugBuffer->writeFloat32(y); + } + } + } + + // debug end + if (_debugBuffer) { + if (_debugBuffer->isOutRange()) { + _debugBuffer->reset(); + CC_LOG_INFO("Spine debug data is too large, debug buffer has no space to put in it!!!!!!!!!!"); + CC_LOG_INFO("You can adjust MAX_DEBUG_BUFFER_SIZE macro"); + } + _debugBuffer->writeFloat32(DebugType::NONE); + } +} + +cc::Rect SkeletonRenderer::getBoundingBox() const { + static cc::middleware::IOBuffer buffer(1024); + float *worldVertices = nullptr; + float minX = 999999.0F; + float minY = 999999.0F; + float maxX = -999999.0F; + float maxY = -999999.0F; + for (int i = 0; i < _skeleton->getSlots().size(); ++i) { + Slot *slot = _skeleton->getSlots()[i]; + if (!slot->getAttachment()) continue; + int verticesCount; + if (slot->getAttachment()->getRTTI().isExactly(RegionAttachment::rtti)) { + auto *attachment = dynamic_cast(slot->getAttachment()); + buffer.checkSpace(8 * sizeof(float)); + worldVertices = reinterpret_cast(buffer.getCurBuffer()); + attachment->computeWorldVertices(slot->getBone(), worldVertices, 0, 2); + verticesCount = 8; + } else if (slot->getAttachment()->getRTTI().isExactly(MeshAttachment::rtti)) { + auto *mesh = dynamic_cast(slot->getAttachment()); + buffer.checkSpace(mesh->getWorldVerticesLength() * sizeof(float)); + worldVertices = reinterpret_cast(buffer.getCurBuffer()); + mesh->computeWorldVertices(*slot, 0, mesh->getWorldVerticesLength(), worldVertices, 0, 2); + verticesCount = static_cast(mesh->getWorldVerticesLength()); + } else { + continue; + } + for (int ii = 0; ii < verticesCount; ii += 2) { + float x = worldVertices[ii]; + float y = worldVertices[ii + 1]; + minX = min(minX, x); + minY = min(minY, y); + maxX = max(maxX, x); + maxY = max(maxY, y); + } + } + if (minX == 999999.0F) minX = minY = maxX = maxY = 0; + return cc::Rect(minX, minY, maxX - minX, maxY - minY); +} + +void SkeletonRenderer::updateWorldTransform() { + if (_skeleton) { + _skeleton->updateWorldTransform(); + } +} + +void SkeletonRenderer::setAttachEnabled(bool enabled) { + _useAttach = enabled; +} + +void SkeletonRenderer::setToSetupPose() { + if (_skeleton) { + _skeleton->setToSetupPose(); + } +} + +void SkeletonRenderer::setBonesToSetupPose() { + if (_skeleton) { + _skeleton->setBonesToSetupPose(); + } +} + +void SkeletonRenderer::setSlotsToSetupPose() { + if (_skeleton) { + _skeleton->setSlotsToSetupPose(); + } +} + +spine::Bone *SkeletonRenderer::findBone(const std::string &boneName) const { + if (_skeleton) { + return _skeleton->findBone(boneName.c_str()); + } + return nullptr; +} + +spine::Slot *SkeletonRenderer::findSlot(const std::string &slotName) const { + if (_skeleton) { + return _skeleton->findSlot(slotName.c_str()); + } + return nullptr; +} + +void SkeletonRenderer::setSkin(const std::string &skinName) { + if (_skeleton) { + _skeleton->setSkin(skinName.empty() ? nullptr : skinName.c_str()); + _skeleton->setSlotsToSetupPose(); + } +} + +void SkeletonRenderer::setSkin(const char *skinName) { + if (_skeleton) { + _skeleton->setSkin(skinName); + _skeleton->setSlotsToSetupPose(); + } +} + +spine::Attachment *SkeletonRenderer::getAttachment(const std::string &slotName, const std::string &attachmentName) const { + if (_skeleton) { + return _skeleton->getAttachment(slotName.c_str(), attachmentName.c_str()); + } + return nullptr; +} + +bool SkeletonRenderer::setAttachment(const std::string &slotName, const std::string &attachmentName) { + if (_skeleton) { + _skeleton->setAttachment(slotName.c_str(), attachmentName.empty() ? nullptr : attachmentName.c_str()); + } + return true; +} + +bool SkeletonRenderer::setAttachment(const std::string &slotName, const char *attachmentName) { + if (_skeleton) { + _skeleton->setAttachment(slotName.c_str(), attachmentName); + } + return true; +} + +void SkeletonRenderer::setUseTint(bool enabled) { + _useTint = enabled; +} + +void SkeletonRenderer::setVertexEffectDelegate(VertexEffectDelegate *effectDelegate) { + if (_effectDelegate == effectDelegate) { + return; + } + CC_SAFE_RELEASE(_effectDelegate); + _effectDelegate = effectDelegate; + CC_SAFE_ADD_REF(_effectDelegate); +} + +void SkeletonRenderer::setSlotsRange(int startSlotIndex, int endSlotIndex) { + this->_startSlotIndex = startSlotIndex; + this->_endSlotIndex = endSlotIndex; +} + +spine::Skeleton *SkeletonRenderer::getSkeleton() const { + return _skeleton; +} + +void SkeletonRenderer::setTimeScale(float scale) { + _timeScale = scale; +} + +float SkeletonRenderer::getTimeScale() const { + return _timeScale; +} + +void SkeletonRenderer::paused(bool value) { + _paused = value; +} + +void SkeletonRenderer::setColor(float r, float g, float b, float a) { + _nodeColor.r = r / 255.0F; + _nodeColor.g = g / 255.0F; + _nodeColor.b = b / 255.0F; + _nodeColor.a = a / 255.0F; +} + +void SkeletonRenderer::setBatchEnabled(bool enabled) { + if (enabled != _enableBatch) { + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } + _materialCaches.clear(); + _enableBatch = enabled; + } +} + +void SkeletonRenderer::setDebugBonesEnabled(bool enabled) { + _debugBones = enabled; +} + +void SkeletonRenderer::setDebugSlotsEnabled(bool enabled) { + _debugSlots = enabled; +} + +void SkeletonRenderer::setDebugMeshEnabled(bool enabled) { + _debugMesh = enabled; +} + +void SkeletonRenderer::setOpacityModifyRGB(bool value) { + _premultipliedAlpha = value; +} + +bool SkeletonRenderer::isOpacityModifyRGB() const { + return _premultipliedAlpha; +} + +se_object_ptr SkeletonRenderer::getDebugData() const { + if (_debugBuffer) { + return _debugBuffer->getTypeArray(); + } + return nullptr; +} + +se_object_ptr SkeletonRenderer::getSharedBufferOffset() const { + if (_sharedBufferOffset) { + return _sharedBufferOffset->getTypeArray(); + } + return nullptr; +} + +void SkeletonRenderer::setRenderEntity(cc::RenderEntity *entity) { + _entity = entity; +} + +void SkeletonRenderer::setMaterial(cc::Material *material) { + _material = material; + for (auto &item : _materialCaches) { + CC_SAFE_DELETE(item.second); + } + _materialCaches.clear(); +} + +cc::RenderDrawInfo *SkeletonRenderer::requestDrawInfo(int idx) { + if (_drawInfoArray.size() < idx + 1) { + cc::RenderDrawInfo *draw = new cc::RenderDrawInfo(); + draw->setDrawInfoType(static_cast(RenderDrawInfoType::MIDDLEWARE)); + _drawInfoArray.push_back(draw); + } + return _drawInfoArray[idx]; +} + +cc::Material *SkeletonRenderer::requestMaterial(uint16_t blendSrc, uint16_t blendDst) { + uint32_t key = static_cast(blendSrc) << 16 | static_cast(blendDst); + if (_materialCaches.find(key) == _materialCaches.end()) { + const IMaterialInstanceInfo info{ + (Material *)_material, + 0}; + MaterialInstance *materialInstance = new MaterialInstance(info); + PassOverrides overrides; + BlendStateInfo stateInfo; + stateInfo.blendColor = gfx::Color{1.0F, 1.0F, 1.0F, 1.0F}; + BlendTargetInfo targetInfo; + targetInfo.blendEq = gfx::BlendOp::ADD; + targetInfo.blendAlphaEq = gfx::BlendOp::ADD; + targetInfo.blendSrc = (gfx::BlendFactor)blendSrc; + targetInfo.blendDst = (gfx::BlendFactor)blendDst; + targetInfo.blendSrcAlpha = (gfx::BlendFactor)blendSrc; + targetInfo.blendDstAlpha = (gfx::BlendFactor)blendDst; + BlendTargetInfoList targetList{targetInfo}; + stateInfo.targets = targetList; + overrides.blendState = stateInfo; + materialInstance->overridePipelineStates(overrides); + const MacroRecord macros{{"TWO_COLORED", _useTint}, {"USE_LOCAL", !_enableBatch}}; + materialInstance->recompileShaders(macros); + _materialCaches[key] = materialInstance; + } + return _materialCaches[key]; +} + +void SkeletonRenderer::setSlotTexture(const std::string &slotName, cc::Texture2D *tex2d, bool createAttachment) { + if (!_skeleton) return; + auto slot = _skeleton->findSlot(slotName.c_str()); + if (!slot) return; + auto attachment = slot->getAttachment(); + if (!attachment) return; + auto width = tex2d->getWidth(); + auto height = tex2d->getHeight(); + + if (createAttachment) { + attachment = attachment->copy(); + slot->setAttachment(attachment); + } + AttachmentVertices *attachmentVertices = nullptr; + if (attachment->getRTTI().isExactly(spine::RegionAttachment::rtti)) { + auto region = static_cast(attachment); + region->setRegionWidth(width); + region->setRegionHeight(height); + region->setRegionOriginalWidth(width); + region->setRegionOriginalHeight(height); + region->setWidth(width); + region->setHeight(height); + region->setUVs(0, 0, 1.0f, 1.0f, false); + region->updateOffset(); + attachmentVertices = static_cast(region->getRendererObject()); + if (createAttachment) { + attachmentVertices = attachmentVertices->copy(); + region->setRendererObject(attachmentVertices); + } + V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts; + auto UVs = region->getUVs(); + for (int i = 0, ii = 0; i < 4; ++i, ii += 2) { + vertices[i].texCoord.u = UVs[ii]; + vertices[i].texCoord.v = UVs[ii + 1]; + } + } else if (attachment->getRTTI().isExactly(spine::MeshAttachment::rtti)) { + auto mesh = static_cast(attachment); + mesh->setRegionWidth(width); + mesh->setRegionHeight(height); + mesh->setRegionOriginalWidth(width); + mesh->setRegionOriginalHeight(height); + mesh->setWidth(width); + mesh->setHeight(height); + mesh->setRegionU(0); + mesh->setRegionV(0); + mesh->setRegionU2(1.0f); + mesh->setRegionV2(1.0f); + mesh->setRegionRotate(true); + mesh->setRegionDegrees(0); + mesh->updateUVs(); + attachmentVertices = static_cast(mesh->getRendererObject()); + if (createAttachment) { + attachmentVertices = attachmentVertices->copy(); + mesh->setRendererObject(attachmentVertices); + } + V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts; + auto UVs = mesh->getUVs(); + for (size_t i = 0, ii = 0, nn = mesh->getWorldVerticesLength(); ii < nn; ++i, ii += 2) { + vertices[i].texCoord.u = UVs[ii]; + vertices[i].texCoord.v = UVs[ii + 1]; + } + } + if (!attachmentVertices) return; + middleware::Texture2D *middlewareTexture = nullptr; + for (auto &it : _slotTextureSet) { + if (it->getRealTexture() == tex2d) { + middlewareTexture = it; + break; + } + } + if (!middlewareTexture) { + middlewareTexture = new middleware::Texture2D(); + middlewareTexture->addRef(); + middlewareTexture->setRealTexture(tex2d); + } + if (attachmentVertices->_texture) { + attachmentVertices->_texture->release(); + } + attachmentVertices->_texture = middlewareTexture; +} diff --git a/cocos/editor-support/spine-creator-support/SkeletonRenderer.h b/cocos/editor-support/spine-creator-support/SkeletonRenderer.h new file mode 100644 index 0000000..0587f6a --- /dev/null +++ b/cocos/editor-support/spine-creator-support/SkeletonRenderer.h @@ -0,0 +1,192 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include +#include "IOTypedArray.h" +#include "MiddlewareManager.h" +#include "Object.h" +#include "base/Macros.h" +#include "base/RefCounted.h" +#include "base/RefMap.h" +#include "core/assets/Texture2D.h" +#include "middleware-adapter.h" +#include "spine-creator-support/VertexEffectDelegate.h" +#include "spine/spine.h" + +namespace cc { +class RenderEntity; +class RenderDrawInfo; +class Material; +}; // namespace cc + +namespace spine { + +class AttachmentVertices; + +/** Draws a skeleton. + */ +class SkeletonRenderer : public cc::RefCounted, public cc::middleware::IMiddleware { +public: + static SkeletonRenderer *create(); + static SkeletonRenderer *createWithSkeleton(Skeleton *skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false); + static SkeletonRenderer *createWithData(SkeletonData *skeletonData, bool ownsSkeletonData = false); + static SkeletonRenderer *createWithFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale = 1); + + void update(float deltaTime) override {} + void render(float deltaTime) override; + virtual cc::Rect getBoundingBox() const; + + Skeleton *getSkeleton() const; + + void setTimeScale(float scale); + float getTimeScale() const; + + void updateWorldTransform(); + + void setToSetupPose(); + void setBonesToSetupPose(); + void setSlotsToSetupPose(); + void paused(bool value); + + /* Returns 0 if the bone was not found. */ + Bone *findBone(const std::string &boneName) const; + /* Returns 0 if the slot was not found. */ + Slot *findSlot(const std::string &slotName) const; + + /* Sets the skin used to look up attachments not found in the SkeletonData defaultSkin. Attachments from the new skin are + * attached if the corresponding attachment from the old skin was attached. + * @param skin May be empty string ("") for no skin.*/ + void setSkin(const std::string &skinName); + /** @param skin May be 0 for no skin.*/ + void setSkin(const char *skinName); + + /* Returns 0 if the slot or attachment was not found. */ + Attachment *getAttachment(const std::string &slotName, const std::string &attachmentName) const; + /* Returns false if the slot or attachment was not found. + * @param attachmentName May be empty string ("") for no attachment. */ + bool setAttachment(const std::string &slotName, const std::string &attachmentName); + /* @param attachmentName May be 0 for no attachment. */ + bool setAttachment(const std::string &slotName, const char *attachmentName); + + /* Enables/disables two color tinting for this instance. May break batching */ + void setUseTint(bool enabled); + + /* Sets the vertex effect to be used, set to 0 to disable vertex effects */ + void setVertexEffectDelegate(VertexEffectDelegate *effectDelegate); + /* Sets the range of slots that should be rendered. Use -1, -1 to clear the range */ + void setSlotsRange(int startSlotIndex, int endSlotIndex); + + /** + * @return debug data, it's a Float32Array, + * format |debug bones length|[beginX|beginY|toX|toY|...loop...] + */ + se_object_ptr getDebugData() const; + /** + * @return shared buffer offset, it's a Uint32Array + * format |render info offset|attach info offset| + */ + se_object_ptr getSharedBufferOffset() const; + + void setColor(float r, float g, float b, float a); + void setBatchEnabled(bool enabled); + void setDebugBonesEnabled(bool enabled); + void setDebugSlotsEnabled(bool enabled); + void setDebugMeshEnabled(bool enabled); + void setAttachEnabled(bool enabled); + + void setOpacityModifyRGB(bool value); + bool isOpacityModifyRGB() const; + + virtual void beginSchedule(); + virtual void stopSchedule(); + void onEnable(); + void onDisable(); + + SkeletonRenderer(); + explicit SkeletonRenderer(Skeleton *skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false, bool ownsAtlas = false); + explicit SkeletonRenderer(SkeletonData *skeletonData, bool ownsSkeletonData = false); + SkeletonRenderer(const std::string &skeletonDataFile, const std::string &atlasFile, float scale = 1); + + ~SkeletonRenderer() override; + + void initWithUUID(const std::string &uuid); + void initWithSkeleton(Skeleton *skeleton, bool ownsSkeleton = false, bool ownsSkeletonData = false, bool ownsAtlas = false); + void initWithData(SkeletonData *skeletonData, bool ownsSkeletonData = false); + void initWithJsonFile(const std::string &skeletonDataFile, Atlas *atlas, float scale = 1); + void initWithJsonFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale = 1); + void initWithBinaryFile(const std::string &skeletonDataFile, Atlas *atlas, float scale = 1); + void initWithBinaryFile(const std::string &skeletonDataFile, const std::string &atlasFile, float scale = 1); + + virtual void initialize(); + + cc::RenderDrawInfo *requestDrawInfo(int idx); + cc::Material *requestMaterial(uint16_t blendSrc, uint16_t blendDst); + void setMaterial(cc::Material *material); + void setRenderEntity(cc::RenderEntity *entity); + void setSlotTexture(const std::string &slotName, cc::Texture2D *tex2d, bool createAttachment); + +protected: + void setSkeletonData(SkeletonData *skeletonData, bool ownsSkeletonData); + + bool _ownsSkeletonData = false; + bool _ownsSkeleton = false; + bool _ownsAtlas = false; + Atlas *_atlas = nullptr; + AttachmentLoader *_attachmentLoader = nullptr; + Skeleton *_skeleton = nullptr; + VertexEffectDelegate *_effectDelegate = nullptr; + float _timeScale = 1; + bool _paused = false; + + bool _useAttach = false; + bool _debugMesh = false; + bool _debugSlots = false; + bool _debugBones = false; + cc::middleware::Color4F _nodeColor = cc::middleware::Color4F::WHITE; + bool _premultipliedAlpha = false; + SkeletonClipping *_clipper = nullptr; + bool _useTint = false; + bool _enableBatch = false; + std::string _uuid; + + int _startSlotIndex = -1; + int _endSlotIndex = -1; + + cc::middleware::IOTypedArray *_sharedBufferOffset = nullptr; + cc::middleware::IOTypedArray *_debugBuffer = nullptr; + + cc::RenderEntity *_entity = nullptr; + cc::Material *_material = nullptr; + ccstd::vector _drawInfoArray; + ccstd::unordered_map _materialCaches; +}; + +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/Vector2.cpp b/cocos/editor-support/spine-creator-support/Vector2.cpp new file mode 100644 index 0000000..3b18308 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/Vector2.cpp @@ -0,0 +1,48 @@ +#include "Vector2.h" +#include +namespace spine { + +Vector2::Vector2(): x(0), y(0) { + +} + +Vector2::Vector2(float x, float y) { + this->x = x; + this->y = y; +} + +Vector2::~Vector2() {} + +void Vector2::setX(float x) { + this->x = x; +} + +float Vector2::getX() { + return x; +} + +void Vector2::setY(float y) { + this->y = y; +} + +float Vector2::getY() { + return y; +} + +Vector2& Vector2::set(float x, float y) { + this->setX(x); + this->setY(y); + return *this; +} + +float Vector2::length() { + return sqrt(x * x + y * y); +} + +Vector2& Vector2::normalize() { + float invLen = 1.F / length(); + this->setX(x * invLen); + this->setY(y * invLen); + return *this; +} +} \ No newline at end of file diff --git a/cocos/editor-support/spine-creator-support/Vector2.h b/cocos/editor-support/spine-creator-support/Vector2.h new file mode 100644 index 0000000..0f62883 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/Vector2.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include "spine/spine.h" + +namespace spine { + class Vector2 + { + public: + float x, y; + public: + Vector2(); + Vector2(float x, float y); + ~Vector2(); + + void setX(float x); + float getX(); + + void setY(float y); + float getY(); + + Vector2 &set(float x, float y); + float length(); + Vector2 &normalize(); + }; +} \ No newline at end of file diff --git a/cocos/editor-support/spine-creator-support/VertexEffectDelegate.cpp b/cocos/editor-support/spine-creator-support/VertexEffectDelegate.cpp new file mode 100644 index 0000000..cf360b8 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/VertexEffectDelegate.cpp @@ -0,0 +1,84 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "VertexEffectDelegate.h" +namespace spine { + +VertexEffectDelegate::VertexEffectDelegate() { +} + +VertexEffectDelegate::~VertexEffectDelegate() { + clear(); +} + +void VertexEffectDelegate::clear() { + if (_interpolation) { + delete _interpolation; + _interpolation = nullptr; + } + if (_vertexEffect) { + delete _vertexEffect; + _vertexEffect = nullptr; + } + _effectType = "none"; +} + +JitterVertexEffect *VertexEffectDelegate::initJitter(float jitterX, float jitterY) { + clear(); + _vertexEffect = new JitterVertexEffect(jitterX, jitterY); + _effectType = "jitter"; + return (JitterVertexEffect *)_vertexEffect; +} + +SwirlVertexEffect *VertexEffectDelegate::initSwirlWithPow(float radius, int power) { + clear(); + _interpolation = new PowInterpolation(power); + _vertexEffect = new SwirlVertexEffect(radius, *_interpolation); + _effectType = "swirl"; + return (SwirlVertexEffect *)_vertexEffect; +} + +SwirlVertexEffect *VertexEffectDelegate::initSwirlWithPowOut(float radius, int power) { + clear(); + _interpolation = new PowOutInterpolation(power); + _vertexEffect = new SwirlVertexEffect(radius, *_interpolation); + _effectType = "swirl"; + return (SwirlVertexEffect *)_vertexEffect; +} + +JitterVertexEffect *VertexEffectDelegate::getJitterVertexEffect() { + JitterVertexEffect *jitter = dynamic_cast(_vertexEffect); + return jitter; +} + +SwirlVertexEffect *VertexEffectDelegate::getSwirlVertexEffect() { + SwirlVertexEffect *swirl = dynamic_cast(_vertexEffect); + return swirl; +} +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/VertexEffectDelegate.h b/cocos/editor-support/spine-creator-support/VertexEffectDelegate.h new file mode 100644 index 0000000..a4a077c --- /dev/null +++ b/cocos/editor-support/spine-creator-support/VertexEffectDelegate.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include +#include "base/RefCounted.h" +#include "spine/spine.h" + +namespace spine { +class VertexEffectDelegate : public cc::RefCounted { +public: + VertexEffectDelegate(); + ~VertexEffectDelegate() override; + JitterVertexEffect *initJitter(float jitterX, float jitterY); + SwirlVertexEffect *initSwirlWithPow(float radius, int power); + SwirlVertexEffect *initSwirlWithPowOut(float radius, int power); + VertexEffect *getVertexEffect() { + return _vertexEffect; + } + JitterVertexEffect *getJitterVertexEffect(); + SwirlVertexEffect *getSwirlVertexEffect(); + const std::string &getEffectType() const { + return _effectType; + } + void clear(); + +private: + VertexEffect *_vertexEffect = nullptr; + Interpolation *_interpolation = nullptr; + std::string _effectType = "none"; +}; +} // namespace spine diff --git a/cocos/editor-support/spine-creator-support/spine-cocos2dx.cpp b/cocos/editor-support/spine-creator-support/spine-cocos2dx.cpp new file mode 100644 index 0000000..85cd0f5 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/spine-cocos2dx.cpp @@ -0,0 +1,152 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated May 1, 2019. Replaces all prior versions. + * + * Copyright (c) 2013-2019, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS + * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "spine-creator-support/spine-cocos2dx.h" +#include "base/Data.h" +#include "middleware-adapter.h" +#include "platform/FileUtils.h" +#include "spine-creator-support/AttachmentVertices.h" + +namespace spine { +static CustomTextureLoader customTextureLoader = nullptr; +void spAtlasPage_setCustomTextureLoader(CustomTextureLoader texLoader) { + customTextureLoader = texLoader; +} + +static SpineObjectDisposeCallback spineObjectDisposeCallback = nullptr; +void setSpineObjectDisposeCallback(SpineObjectDisposeCallback callback) { + spineObjectDisposeCallback = callback; +} +} // namespace spine + +USING_NS_MW; // NOLINT(google-build-using-namespace) +using namespace cc; // NOLINT(google-build-using-namespace) +using namespace spine; // NOLINT(google-build-using-namespace) + +static void deleteAttachmentVertices(void *vertices) { + delete static_cast(vertices); +} + +static uint16_t quadTriangles[6] = {0, 1, 2, 2, 3, 0}; + +static void setAttachmentVertices(RegionAttachment *attachment) { + auto *region = static_cast(attachment->getRendererObject()); + auto *attachmentVertices = new AttachmentVertices(static_cast(region->page->getRendererObject()), 4, quadTriangles, 6); + V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts; + for (int i = 0, ii = 0; i < 4; ++i, ii += 2) { + vertices[i].texCoord.u = attachment->getUVs()[ii]; + vertices[i].texCoord.v = attachment->getUVs()[ii + 1]; + } + attachment->setRendererObject(attachmentVertices, deleteAttachmentVertices); +} + +static void setAttachmentVertices(MeshAttachment *attachment) { + auto *region = static_cast(attachment->getRendererObject()); + auto *attachmentVertices = new AttachmentVertices(static_cast(region->page->getRendererObject()), + static_cast(attachment->getWorldVerticesLength() >> 1), attachment->getTriangles().buffer(), static_cast(attachment->getTriangles().size())); + V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts; + for (size_t i = 0, ii = 0, nn = attachment->getWorldVerticesLength(); ii < nn; ++i, ii += 2) { + vertices[i].texCoord.u = attachment->getUVs()[ii]; + vertices[i].texCoord.v = attachment->getUVs()[ii + 1]; + } + attachment->setRendererObject(attachmentVertices, deleteAttachmentVertices); +} + +Cocos2dAtlasAttachmentLoader::Cocos2dAtlasAttachmentLoader(Atlas *atlas) : AtlasAttachmentLoader(atlas) { +} + +Cocos2dAtlasAttachmentLoader::~Cocos2dAtlasAttachmentLoader() = default; + +void Cocos2dAtlasAttachmentLoader::configureAttachment(Attachment *attachment) { + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + setAttachmentVertices(dynamic_cast(attachment)); + } else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + setAttachmentVertices(dynamic_cast(attachment)); + } +} + +uint32_t wrap(TextureWrap wrap) { + return static_cast(wrap); +} + +uint32_t filter(TextureFilter filter) { + return static_cast(filter); +} + +Cocos2dTextureLoader::Cocos2dTextureLoader() = default; +Cocos2dTextureLoader::~Cocos2dTextureLoader() = default; + +void Cocos2dTextureLoader::load(AtlasPage &page, const spine::String &path) { + middleware::Texture2D *texture = nullptr; + if (spine::customTextureLoader) { + texture = spine::customTextureLoader(path.buffer()); + } + CC_ASSERT_NOT_NULL(texture); + + if (texture) { + texture->addRef(); + + middleware::Texture2D::TexParams textureParams = {filter(page.minFilter), filter(page.magFilter), wrap(page.uWrap), wrap(page.vWrap)}; + texture->setTexParameters(textureParams); + + page.setRendererObject(texture); + page.width = texture->getPixelsWide(); + page.height = texture->getPixelsHigh(); + } +} + +void Cocos2dTextureLoader::unload(void *texture) { + if (texture) { + (static_cast(texture))->release(); + } +} + +Cocos2dExtension::Cocos2dExtension() = default; + +Cocos2dExtension::~Cocos2dExtension() = default; + +char *Cocos2dExtension::_readFile(const spine::String &path, int *length) { + *length = 0; + Data data = FileUtils::getInstance()->getDataFromFile(FileUtils::getInstance()->fullPathForFilename(path.buffer())); + if (data.isNull()) return nullptr; + + char *ret = static_cast(malloc(sizeof(unsigned char) * data.getSize())); + memcpy(ret, reinterpret_cast(data.getBytes()), data.getSize()); + *length = static_cast(data.getSize()); + return ret; +} + +SpineExtension *spine::getDefaultExtension() { + return new Cocos2dExtension(); +} + +void Cocos2dExtension::_free(void *mem, const char *file, int line) { + spineObjectDisposeCallback(mem); + DefaultSpineExtension::_free(mem, file, line); +} diff --git a/cocos/editor-support/spine-creator-support/spine-cocos2dx.h b/cocos/editor-support/spine-creator-support/spine-cocos2dx.h new file mode 100644 index 0000000..56ddce2 --- /dev/null +++ b/cocos/editor-support/spine-creator-support/spine-cocos2dx.h @@ -0,0 +1,77 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include "middleware-adapter.h" +#include "spine-creator-support/SkeletonAnimation.h" +#include "spine-creator-support/SkeletonCacheAnimation.h" +#include "spine-creator-support/SkeletonCacheMgr.h" +#include "spine-creator-support/SkeletonDataMgr.h" +#include "spine-creator-support/SkeletonRenderer.h" +#include "spine/spine.h" + +namespace spine { +typedef cc::middleware::Texture2D *(*CustomTextureLoader)(const char *path); +// set custom texture loader for _spAtlasPage_createTexture +void spAtlasPage_setCustomTextureLoader(CustomTextureLoader texLoader); + +class Cocos2dAtlasAttachmentLoader : public AtlasAttachmentLoader { +public: + Cocos2dAtlasAttachmentLoader(Atlas *atlas); + virtual ~Cocos2dAtlasAttachmentLoader(); + virtual void configureAttachment(Attachment *attachment); +}; + +class Cocos2dTextureLoader : public TextureLoader { +public: + Cocos2dTextureLoader(); + + virtual ~Cocos2dTextureLoader(); + + virtual void load(AtlasPage &page, const String &path); + + virtual void unload(void *texture); +}; + +class Cocos2dExtension : public DefaultSpineExtension { +public: + Cocos2dExtension(); + + virtual ~Cocos2dExtension(); + + virtual void _free(void *mem, const char *file, int line); + +protected: + virtual char *_readFile(const String &path, int *length); +}; + +typedef void (*SpineObjectDisposeCallback)(void *); +void setSpineObjectDisposeCallback(SpineObjectDisposeCallback callback); +} // namespace spine diff --git a/cocos/editor-support/spine-wasm/AtlasAttachmentLoaderExtension.cpp b/cocos/editor-support/spine-wasm/AtlasAttachmentLoaderExtension.cpp new file mode 100644 index 0000000..e8429ac --- /dev/null +++ b/cocos/editor-support/spine-wasm/AtlasAttachmentLoaderExtension.cpp @@ -0,0 +1,61 @@ +#include "AtlasAttachmentLoaderExtension.h" +#include "mesh-type-define.h" +//#include "LogUtil.h" +using namespace spine; + +static uint16_t quadTriangles[6] = {0, 1, 2, 2, 3, 0}; + +AttachmentVertices::AttachmentVertices(int verticesCount, uint16_t *triangles, int trianglesCount, uint32_t textureId) { + _triangles = new Triangles(); + _triangles->verts = new V3F_T2F_C4B[verticesCount]; + _triangles->vertCount = verticesCount; + _triangles->indices = triangles; + _triangles->indexCount = trianglesCount; + _textureId = textureId; +} + +AttachmentVertices::~AttachmentVertices() { + delete[] _triangles->verts; + delete _triangles; +} + +AttachmentVertices *AttachmentVertices::copy() { + AttachmentVertices *atv = new AttachmentVertices(_triangles->vertCount, _triangles->indices, _triangles->indexCount, _textureId); + return atv; +} + +static void deleteAttachmentVertices(void *vertices) { + delete static_cast(vertices); +} + +AtlasAttachmentLoaderExtension::AtlasAttachmentLoaderExtension(Atlas *atlas) : AtlasAttachmentLoader(atlas), _atlasCache(atlas) { +} + +AtlasAttachmentLoaderExtension::~AtlasAttachmentLoaderExtension() = default; + +void AtlasAttachmentLoaderExtension::configureAttachment(Attachment *attachment) { + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + auto *regionAttachment = static_cast(attachment); + auto &pages = _atlasCache->getPages(); + auto *region = static_cast(regionAttachment->getRendererObject()); + auto *attachmentVertices = new AttachmentVertices(4, quadTriangles, 6, pages.indexOf(region->page)); + V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts; + for (int i = 0, ii = 0; i < 4; ++i, ii += 2) { + vertices[i].texCoord.u = regionAttachment->getUVs()[ii]; + vertices[i].texCoord.v = regionAttachment->getUVs()[ii + 1]; + } + regionAttachment->setRendererObject(attachmentVertices, deleteAttachmentVertices); + } else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + auto *meshAttachment = static_cast(attachment); + auto &pages = _atlasCache->getPages(); + auto *region = static_cast(meshAttachment->getRendererObject()); + auto *attachmentVertices = new AttachmentVertices( + static_cast(meshAttachment->getWorldVerticesLength() >> 1), meshAttachment->getTriangles().buffer(), static_cast(meshAttachment->getTriangles().size()), pages.indexOf(region->page)); + V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts; + for (size_t i = 0, ii = 0, nn = meshAttachment->getWorldVerticesLength(); ii < nn; ++i, ii += 2) { + vertices[i].texCoord.u = meshAttachment->getUVs()[ii]; + vertices[i].texCoord.v = meshAttachment->getUVs()[ii + 1]; + } + meshAttachment->setRendererObject(attachmentVertices, deleteAttachmentVertices); + } +} \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/AtlasAttachmentLoaderExtension.h b/cocos/editor-support/spine-wasm/AtlasAttachmentLoaderExtension.h new file mode 100644 index 0000000..77230f8 --- /dev/null +++ b/cocos/editor-support/spine-wasm/AtlasAttachmentLoaderExtension.h @@ -0,0 +1,26 @@ +#ifndef __SPINE_ATLAS_ATTACHMENT_LOADER_EXT_H +#define __SPINE_ATLAS_ATTACHMENT_LOADER_EXT_H + +#include "mesh-type-define.h" +#include "spine/spine.h" + +class AttachmentVertices { +public: + AttachmentVertices(int verticesCount, uint16_t *triangles, int trianglesCount, uint32_t textureId); + virtual ~AttachmentVertices(); + AttachmentVertices *copy(); + Triangles *_triangles = nullptr; + uint32_t _textureId = 0; +}; + +class AtlasAttachmentLoaderExtension : public spine::AtlasAttachmentLoader { +public: + AtlasAttachmentLoaderExtension(spine::Atlas *atlas); + virtual ~AtlasAttachmentLoaderExtension(); + virtual void configureAttachment(spine::Attachment *attachment); + +private: + spine::Atlas *_atlasCache; +}; + +#endif \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/CMakeLists.txt b/cocos/editor-support/spine-wasm/CMakeLists.txt new file mode 100644 index 0000000..079c340 --- /dev/null +++ b/cocos/editor-support/spine-wasm/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.0) + +set(CMAKE_VERBOSE_MAKEFILE ON) +set(CMAKE_BUILD_TYPE "Release") + +set(APP_NAME "spine" CACHE STRING "Project Name") +project(${APP_NAME}_wasm) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -frtti -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=1") + +message("Current directory: ${CMAKE_CURRENT_SOURCE_DIR}") + +#include_directories(../ ../../) +include_directories(../ ../../ ../spine-creator-support/) +file(GLOB SPINE_CORE_SRC "../spine/*.cpp" ../spine-creator-support/Vector2.cpp) +file(GLOB COCOS_ADAPTER_SRC "./*.cpp") + +#file(GLOB SPINE_CORE_SRC ../spine-creator-support/Vector2.cpp) + +add_executable(${APP_NAME} ${SPINE_CORE_SRC} ${COCOS_ADAPTER_SRC} ) +#add_executable(${APP_NAME} ${COCOS_ADAPTER_SRC}) + +set(EMS_LINK_FLAGS "-O3 -s WASM=1 -s INITIAL_MEMORY=33554432 -s ALLOW_MEMORY_GROWTH=1 -s DYNAMIC_EXECUTION=0 -s ERROR_ON_UNDEFINED_SYMBOLS=0 \ + -flto --no-entry --bind -s USE_ES6_IMPORT_META=0 -s EXPORT_ES6=1 -s MODULARIZE=1 -s EXPORT_NAME='spineWasm' \ + -s ENVIRONMENT=web -s FILESYSTEM=0 -s NO_EXIT_RUNTIME=1 -s LLD_REPORT_UNDEFINED \ + -s MIN_SAFARI_VERSION=110000 \ + --js-library ../library_spine.js") + + +set_target_properties(${APP_NAME} PROPERTIES CXX_STANDARD 11 LINK_FLAGS ${EMS_LINK_FLAGS}) \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/library_spine.js b/cocos/editor-support/spine-wasm/library_spine.js new file mode 100644 index 0000000..ee6dc3b --- /dev/null +++ b/cocos/editor-support/spine-wasm/library_spine.js @@ -0,0 +1,18 @@ +mergeInto(LibraryManager.library, { + spineListenerCallBackFromJS: function () { + var wasmUtil = Module['SpineWasmUtil']; + var listenerID = wasmUtil.getCurrentListenerID(); + var trackEntry = wasmUtil.getCurrentTrackEntry(); + var event = wasmUtil.getCurrentEvent(); + globalThis.TrackEntryListeners.emitListener(listenerID, trackEntry, event); + }, + + spineTrackListenerCallback: function() { + var wasmUtil = Module['SpineWasmUtil']; + var listenerID = wasmUtil.getCurrentListenerID(); + var eventType = wasmUtil.getCurrentEventType(); + var trackEntry = wasmUtil.getCurrentTrackEntry(); + var event = wasmUtil.getCurrentEvent(); + globalThis.TrackEntryListeners.emitTrackEntryListener(listenerID, trackEntry, event, eventType.value); + } +}); diff --git a/cocos/editor-support/spine-wasm/mesh-type-define.h b/cocos/editor-support/spine-wasm/mesh-type-define.h new file mode 100644 index 0000000..de4e693 --- /dev/null +++ b/cocos/editor-support/spine-wasm/mesh-type-define.h @@ -0,0 +1,73 @@ +#ifndef __MESH_TYPE_DEF_H__ +#define __MESH_TYPE_DEF_H__ +#include + +struct Vec3 { + float x; + float y; + float z; +}; + +struct Tex2F { + float u; + float v; +}; + +struct Color4B { + Color4B(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : r(r), g(g), b(b), a(a) {} + Color4B() {} + Color4B &operator=(const Color4B &right) = default; + + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + uint8_t a = 0; + + static const Color4B WHITE; +}; + +struct Color4F { + Color4F(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) {} + Color4F() {} + Color4F &operator=(const Color4F &right) = default; + + float r = 0; + float g = 0; + float b = 0; + float a = 0; +}; + +struct V3F_T2F_C4B { + Vec3 vertex; + // tex coords (2F) + Tex2F texCoord; + + Color4B color; +}; + +struct V3F_T2F_C4B_C4B { // NOLINT + // vertices (3F) + Vec3 vertex; + + // tex coords (2F) + Tex2F texCoord; + + // colors (4F) + Color4B color; + + // colors (4F) + Color4B color2; +}; + +struct Triangles { + /**Vertex data pointer.*/ + V3F_T2F_C4B *verts = nullptr; + /**Index data pointer.*/ + unsigned short *indices = nullptr; // NOLINT + /**The number of vertices.*/ + int vertCount = 0; + /**The number of indices.*/ + int indexCount = 0; +}; + +#endif \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/spine-mesh-data.cpp b/cocos/editor-support/spine-wasm/spine-mesh-data.cpp new file mode 100644 index 0000000..91cb713 --- /dev/null +++ b/cocos/editor-support/spine-wasm/spine-mesh-data.cpp @@ -0,0 +1,60 @@ +#include "spine-mesh-data.h" +#include + +uint8_t* SpineMeshData::vBuf = nullptr; +uint8_t* SpineMeshData::vPtr = nullptr; +uint8_t* SpineMeshData::vEnd = nullptr; +uint16_t* SpineMeshData::iBuf = nullptr; +uint16_t* SpineMeshData::iPtr = nullptr; +uint16_t* SpineMeshData::iEnd = nullptr; + +void SpineMeshData::initMeshMemory() { + if (vBuf) return; + const auto vCount = 65535; + const auto byteStride = 7 * sizeof(float); + vBuf = new uint8_t[2 * vCount * byteStride]; + iBuf = new uint16_t[8 * 65535]; + + vPtr = vBuf; + iPtr = iBuf; +} + +void SpineMeshData::releaseMeshMemory() { + if (vBuf) { + delete[] vBuf; + vBuf = nullptr; + } + if (iBuf) { + delete[] iBuf; + iBuf = nullptr; + } +} + +void SpineMeshData::reset() { + vPtr = vBuf; + iPtr = iBuf; +} + +void SpineMeshData::moveVB(uint32_t count) { + vPtr += count; +} + +void SpineMeshData::moveIB(uint32_t count) { + iPtr += count; +} + +uint8_t* SpineMeshData::queryVBuffer() { + return vPtr; +} + +uint16_t* SpineMeshData::queryIBuffer() { + return iPtr; +} + +uint8_t* SpineMeshData::vb() { + return vBuf; +} + +uint16_t* SpineMeshData::ib() { + return iBuf; +} \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/spine-mesh-data.h b/cocos/editor-support/spine-wasm/spine-mesh-data.h new file mode 100644 index 0000000..66ad046 --- /dev/null +++ b/cocos/editor-support/spine-wasm/spine-mesh-data.h @@ -0,0 +1,26 @@ +#ifndef __SPINE_MESH_DATA_H__ +#define __SPINE_MESH_DATA_H__ +#include + +class SpineMeshData { +public: + static void initMeshMemory(); + static void releaseMeshMemory(); + static void reset(); + static void moveVB(uint32_t count); + static void moveIB(uint32_t count); + static uint8_t *queryVBuffer(); + static uint16_t *queryIBuffer(); + static uint8_t *vb(); + static uint16_t *ib(); + +private: + static uint8_t *vBuf; + static uint16_t *iBuf; + static uint8_t *vPtr; + static uint16_t *iPtr; + static uint8_t *vEnd; + static uint16_t *iEnd; +}; + +#endif \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/spine-model.cpp b/cocos/editor-support/spine-wasm/spine-model.cpp new file mode 100644 index 0000000..b191eca --- /dev/null +++ b/cocos/editor-support/spine-wasm/spine-model.cpp @@ -0,0 +1,61 @@ +#include "spine-model.h" + +SpineModel::SpineModel() { + SpineModel::data = new std::vector(6, 0); +} + +SpineModel::~SpineModel() { + delete SpineModel::data; + SpineModel::data = nullptr; +} + +void SpineModel::addSlotMesh(SlotMesh& mesh, bool needMerge) { + bool canMerge = false; + auto count = data->size(); + if (needMerge && count > 0) { + if (data->at(count - 2) == mesh.blendMode && data->at(count - 1) == mesh.textureID) { + canMerge = true; + data->at(count-4) += mesh.vCount; + data->at(count-3) += mesh.iCount; + } + } + if (!canMerge) { + data->resize(count + 6); + data->at(count) = (uint32_t)mesh.vBuf; + data->at(count + 1) = (uint32_t)mesh.iBuf; + data->at(count + 2) = mesh.vCount; + data->at(count + 3) = mesh.iCount; + data->at(count + 4) = mesh.blendMode; + data->at(count + 5) = mesh.textureID; + } + + auto indexCount = mesh.iCount; + uint16_t* iiPtr = mesh.iBuf; + for (uint16_t i = 0; i < indexCount; i++) { + iiPtr[i] += vCount; + } + + auto vertexCount = mesh.vCount; + float* floatPtr = (float*)mesh.vBuf; + int floatStride = this->byteStride / 4; + for (int i = 0; i < vertexCount; i++) { + floatPtr[floatStride * i + 2] = 0; + } + vCount += vertexCount; + iCount += indexCount; +} + +void SpineModel::clearMeshes() { + data->resize(0); + vCount = 0; + iCount = 0; +} + +std::vector* SpineModel::getData() { + return data; +} + +void SpineModel::setBufferPtr(uint8_t* vp, uint16_t* ip) { + vPtr = (uint32_t)vp; + iPtr = (uint32_t)ip; +} diff --git a/cocos/editor-support/spine-wasm/spine-model.h b/cocos/editor-support/spine-wasm/spine-model.h new file mode 100644 index 0000000..bb278bc --- /dev/null +++ b/cocos/editor-support/spine-wasm/spine-model.h @@ -0,0 +1,47 @@ +#ifndef __SPINE_MODEL_H__ +#define __SPINE_MODEL_H__ +#include +#include +#include "mesh-type-define.h" + +using namespace spine; +class SlotMesh { +public: + SlotMesh() {} + SlotMesh(uint8_t* vb, uint16_t* ib, uint32_t vc, uint32_t ic) + : vBuf(vb), iBuf(ib), vCount(vc), iCount(ic) {} + ~SlotMesh() {} + void set(uint8_t* vb, uint16_t* ib, uint32_t vc, uint32_t ic) + { + this->vBuf = vb; + this->iBuf = ib; + this->vCount = vc; + this->iCount = ic; + } + uint8_t* vBuf; + uint16_t* iBuf; + uint32_t vCount; + uint32_t iCount; + uint32_t blendMode; + uint32_t textureID; +}; + +class SpineModel { +public: + SpineModel(); + ~SpineModel(); + void addSlotMesh(SlotMesh& mesh, bool needMerge = true); + void clearMeshes(); + void setBufferPtr(uint8_t* vp, uint16_t* ip); + std::vector* data; + std::vector* getData(); + +public: + uint32_t vCount; + uint32_t iCount; + uint32_t vPtr; + uint32_t iPtr; + uint32_t byteStride; +}; + +#endif \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/spine-skeleton-instance.cpp b/cocos/editor-support/spine-wasm/spine-skeleton-instance.cpp new file mode 100644 index 0000000..8b171a4 --- /dev/null +++ b/cocos/editor-support/spine-wasm/spine-skeleton-instance.cpp @@ -0,0 +1,582 @@ +#include "spine-skeleton-instance.h" +#include +#include +#include "AtlasAttachmentLoaderExtension.h" +#include "spine-mesh-data.h" +#include "spine-wasm.h" +#include "util-function.h" + +SlotMesh globalMesh(nullptr, nullptr, 0, 0); + +extern "C" { +extern void spineListenerCallBackFromJS(); +extern void spineTrackListenerCallback(); +} +using namespace spine; + +static void animationCallback(AnimationState *state, EventType type, TrackEntry *entry, Event *event) { + SpineSkeletonInstance *instance = (static_cast(state->getRendererObject())); + instance->onAnimationStateEvent(entry, type, event); +} + +static void trackEntryCallback(AnimationState *state, EventType type, TrackEntry *entry, Event *event) { + void* renderObj = state->getRendererObject(); + if (renderObj) { + (static_cast(renderObj))->onTrackEntryEvent(entry, type, event); + if (type == EventType_Dispose) { + if (entry->getRendererObject()) { + entry->setRendererObject(nullptr); + } + } + } +} + +SpineSkeletonInstance::SpineSkeletonInstance() { + _model = new SpineModel(); +} + +SpineSkeletonInstance::~SpineSkeletonInstance() { + _skeletonData = nullptr; + if (_clipper) delete _clipper; + if (_animState) delete _animState; + if (_animStateData) delete _animStateData; + if (_skeleton) delete _skeleton; + if (_model) delete _model; +} + +void SpineSkeletonInstance::destroy() { + delete this; +} + +Skeleton *SpineSkeletonInstance::initSkeleton(SkeletonData *data) { + if (_clipper) delete _clipper; + if (_animState) delete _animState; + if (_animStateData) delete _animStateData; + if (_skeleton) delete _skeleton; + + _skeletonData = data; + _skeleton = new Skeleton(_skeletonData); + _animStateData = new AnimationStateData(_skeletonData); + _animState = new AnimationState(_animStateData); + _clipper = new SkeletonClipping(); + _skeleton->setToSetupPose(); + _skeleton->updateWorldTransform(); + _animState->setRendererObject(this); + _animState->setListener(animationCallback); + return _skeleton; +} + +TrackEntry *SpineSkeletonInstance::setAnimation(float trackIndex, const std::string &name, bool loop) { + if (!_skeleton) return nullptr; + spine::Animation *animation = _skeleton->getData()->findAnimation(name.c_str()); + if (!animation) { + _animState->clearTracks(); + _skeleton->setToSetupPose(); + return nullptr; + } + auto *trackEntry = _animState->setAnimation(trackIndex, animation, loop); + _animState->apply(*_skeleton); + _skeleton->updateWorldTransform(); + return trackEntry; +} + +void SpineSkeletonInstance::setSkin(const std::string &name) { + if (!_skeleton) return; + _skeleton->setSkin(name.c_str()); + _skeleton->setSlotsToSetupPose(); + _animState->apply(*_skeleton); + _skeleton->updateWorldTransform(); +} + +void SpineSkeletonInstance::updateAnimation(float dltTime) { + if (!_skeleton) return; + dltTime *= dtRate; + _skeleton->update(dltTime); + _animState->update(dltTime); + _animState->apply(*_skeleton); +} + +SpineModel *SpineSkeletonInstance::updateRenderData() { + if (_userData.debugMode) { + _debugShapes.clear(); + } + _skeleton->updateWorldTransform(); + SpineMeshData::reset(); + _model->clearMeshes(); + if (_userData.useTint) { + _model->byteStride = sizeof(V3F_T2F_C4B_C4B); + } else { + _model->byteStride = sizeof(V3F_T2F_C4B); + } + collectMeshData(); + _model->setBufferPtr(SpineMeshData::vb(), SpineMeshData::ib()); + return _model; +} + +void SpineSkeletonInstance::collectMeshData() { + uint32_t byteStrideOneColor = sizeof(V3F_T2F_C4B); + uint32_t byteStrideTwoColor = sizeof(V3F_T2F_C4B_C4B); + uint32_t sizeof_float = sizeof(float); + uint32_t strideOneColor = byteStrideOneColor / sizeof_float; + uint32_t strideTwoColor = byteStrideTwoColor / sizeof_float; + uint16_t sizeof_uint16 = sizeof(uint16_t); + + uint32_t byteStrideColor = !_userData.useTint ? byteStrideOneColor : byteStrideTwoColor; + uint32_t strideColor = byteStrideColor / sizeof_float; + + Color4F color; + auto &slotArray = _skeleton->getDrawOrder(); + uint32_t slotCount = slotArray.size(); + DEBUG_SHAPE_TYPE debugShapeType = DEBUG_SHAPE_TYPE::DEBUG_REGION; + + SlotMesh currMesh = globalMesh; + if (_effect) { + _effect->begin(*_skeleton); + } + const Color& skeletonColor = _skeleton->getColor(); + for (uint32_t drawIdx = 0; drawIdx < slotCount; ++drawIdx) { + auto slot = slotArray[drawIdx]; + auto& bone = slot->getBone(); + if (bone.isActive() == false) { + continue; + } + + if (!slot->getAttachment()) { + _clipper->clipEnd(*slot); + continue; + } + color.r = _userData.color.r; + color.g = _userData.color.g; + color.b = _userData.color.b; + color.a = _userData.color.a; + spine::Attachment* attachmentSlot = slot->getAttachment(); + const spine::RTTI& attachmentRTTI = attachmentSlot->getRTTI(); + if (attachmentRTTI.isExactly(spine::RegionAttachment::rtti)) { + debugShapeType = DEBUG_SHAPE_TYPE::DEBUG_REGION; + auto *attachment = static_cast(attachmentSlot); + auto *attachmentVertices = reinterpret_cast(attachment->getRendererObject()); + + auto& triangles = attachmentVertices->_triangles; + auto vertCount = triangles->vertCount; + auto indexCount = triangles->indexCount; + auto ibSize = indexCount * sizeof_uint16; + + auto vbSize = vertCount * byteStrideColor; + auto *vertices = SpineMeshData::queryVBuffer(); + auto *indices = SpineMeshData::queryIBuffer(); + + if (!_userData.useTint) { + memcpy(static_cast(vertices), static_cast(triangles->verts), vbSize); + } else { + V3F_T2F_C4B_C4B *verts = (V3F_T2F_C4B_C4B *)vertices; + for (int ii = 0; ii < vertCount; ii++) { + verts[ii].texCoord = triangles->verts[ii].texCoord; + } + } + memcpy(indices, triangles->indices, ibSize); + attachment->computeWorldVertices(bone, (float *)vertices, 0, strideColor); + currMesh.set((uint8_t *)vertices, indices, vertCount, indexCount); + const Color& attachmentColor = attachment->getColor(); + color.r *= attachmentColor.r; + color.g *= attachmentColor.g; + color.b *= attachmentColor.b; + color.a *= attachmentColor.a; + currMesh.textureID = attachmentVertices->_textureId; + } else if (attachmentRTTI.isExactly(spine::MeshAttachment::rtti)) { + debugShapeType = DEBUG_SHAPE_TYPE::DEBUG_MESH; + auto *attachment = static_cast(attachmentSlot); + auto *attachmentVertices = static_cast(attachment->getRendererObject()); + + auto& triangles = attachmentVertices->_triangles; + auto vertCount = triangles->vertCount; + auto indexCount = triangles->indexCount; + auto ibSize = indexCount * sizeof_uint16; + + auto vbSize = vertCount * byteStrideColor; + auto *vertices = SpineMeshData::queryVBuffer(); + auto *indices = SpineMeshData::queryIBuffer(); + if (!_userData.useTint) { + memcpy(static_cast(vertices), static_cast(triangles->verts), vbSize); + } else { + V3F_T2F_C4B_C4B *verts = (V3F_T2F_C4B_C4B *)vertices; + for (int ii = 0; ii < vertCount; ii++) { + verts[ii].texCoord = triangles->verts[ii].texCoord; + } + } + memcpy(indices, triangles->indices, ibSize); + attachment->computeWorldVertices(*slot, 0, attachment->getWorldVerticesLength(), (float *)vertices, 0, strideColor); + currMesh.set((uint8_t *)vertices, indices, vertCount, indexCount); + const Color& attachmentColor = attachment->getColor(); + color.r *= attachmentColor.r; + color.g *= attachmentColor.g; + color.b *= attachmentColor.b; + color.a *= attachmentColor.a; + currMesh.textureID = attachmentVertices->_textureId; + } else if (attachmentRTTI.isExactly(spine::ClippingAttachment::rtti)) { + auto *clip = static_cast(attachmentSlot); + _clipper->clipStart(*slot, clip); + continue; + } else { + _clipper->clipEnd(*slot); + continue; + } + const Color& slotColor = slot->getColor(); + uint32_t uintA = (uint32_t)(255 * skeletonColor.a * slotColor.a * color.a); + uint32_t multiplier = _userData.premultipliedAlpha ? uintA : 255; + uint32_t uintR = (uint32_t)(skeletonColor.r * slotColor.r * color.r * multiplier); + uint32_t uintG = (uint32_t)(skeletonColor.g * slotColor.g * color.g * multiplier); + uint32_t uintB = (uint32_t)(skeletonColor.b * slotColor.b * color.b * multiplier); + uint32_t light = (uintA << 24) + (uintB << 16) + (uintG << 8) + uintR; + + if (slot->hasDarkColor()) { + const Color& slotDarkColor = slot->getDarkColor(); + uintR = (uint32_t)(skeletonColor.r * slotDarkColor.r * color.r * multiplier); + uintG = (uint32_t)(skeletonColor.g * slotDarkColor.g * color.g * multiplier); + uintB = (uint32_t)(skeletonColor.b * slotDarkColor.b * color.b * multiplier); + } else { + uintR = 0; + uintG = 0; + uintB = 0; + } + uintA = _userData.premultipliedAlpha ? 255 : 0; + uint32_t dark = (uintA << 24) + (uintB << 16) + (uintG << 8) + uintR; + + if (!_userData.useTint) { + if (_clipper->isClipping()) { + _clipper->clipTriangles(reinterpret_cast(currMesh.vBuf), currMesh.iBuf, currMesh.iCount, (float *)(&currMesh.vBuf[3 * 4]), strideColor); + auto& clippedTriangles = _clipper->getClippedTriangles(); + if (clippedTriangles.size() == 0) { + _clipper->clipEnd(*slot); + continue; + } + auto& clippedVertices = _clipper->getClippedVertices(); + auto& clippedUVs = _clipper->getClippedUVs(); + const auto vertCount = static_cast(clippedVertices.size()) >> 1; + const auto indexCount = static_cast(clippedTriangles.size()); + const auto vbSize = vertCount * byteStrideColor; + uint8_t *vPtr = SpineMeshData::queryVBuffer(); + uint16_t *iPtr = SpineMeshData::queryIBuffer(); + currMesh.set(vPtr, iPtr, vertCount, indexCount); + memcpy(iPtr, clippedTriangles.buffer(), sizeof_uint16 * indexCount); + float *verts = clippedVertices.buffer(); + float *uvs = clippedUVs.buffer(); + + V3F_T2F_C4B *vertices = (V3F_T2F_C4B *)currMesh.vBuf; + if (_effect) { + for (int v = 0, vn = vertCount, vv = 0; v < vn; ++v, vv += 2) { + vertices[v].vertex.x = verts[vv]; + vertices[v].vertex.y = verts[vv + 1]; + vertices[v].texCoord.u = uvs[vv]; + vertices[v].texCoord.v = uvs[vv + 1]; + _effect->transform(vertices[v].vertex.x, vertices[v].vertex.y); + *((uint32_t *)&vertices[v].color) = light; + } + } else { + for (int v = 0, vn = vertCount, vv = 0; v < vn; ++v, vv += 2) { + vertices[v].vertex.x = verts[vv]; + vertices[v].vertex.y = verts[vv + 1]; + vertices[v].texCoord.u = uvs[vv]; + vertices[v].texCoord.v = uvs[vv + 1]; + *((uint32_t *)&vertices[v].color) = light; + } + } + } else { + auto vertCount = currMesh.vCount; + V3F_T2F_C4B *vertex = (V3F_T2F_C4B *)currMesh.vBuf; + if (_effect) { + for (int v = 0; v < vertCount; ++v) { + _effect->transform(vertex[v].vertex.x, vertex[v].vertex.y); + *((uint32_t *)&vertex[v].color) = light; + } + } else { + for (int v = 0; v < vertCount; ++v) { + *((uint32_t *)&vertex[v].color) = light; + } + } + } + } else { + if (_clipper->isClipping()) { + _clipper->clipTriangles(reinterpret_cast(currMesh.vBuf), currMesh.iBuf, currMesh.iCount, (float *)(&currMesh.vBuf[3 * 4]), strideColor); + auto& clippedTriangles = _clipper->getClippedTriangles(); + if (clippedTriangles.size() == 0) { + _clipper->clipEnd(*slot); + continue; + } + auto& clippedVertices = _clipper->getClippedVertices(); + auto& clippedUVs = _clipper->getClippedUVs(); + const auto vertCount = static_cast(clippedVertices.size()) >> 1; + const auto indexCount = static_cast(clippedTriangles.size()); + const auto vbSize = vertCount * byteStrideColor; + uint8_t *vPtr = SpineMeshData::queryVBuffer(); + uint16_t *iPtr = SpineMeshData::queryIBuffer(); + currMesh.set(vPtr, iPtr, vertCount, indexCount); + memcpy(iPtr, clippedTriangles.buffer(), sizeof_uint16 * indexCount); + float *verts = clippedVertices.buffer(); + float *uvs = clippedUVs.buffer(); + + V3F_T2F_C4B_C4B *vertices = (V3F_T2F_C4B_C4B *)currMesh.vBuf; + if (_effect) { + for (int v = 0, vn = vertCount, vv = 0; v < vn; ++v, vv += 2) { + vertices[v].vertex.x = verts[vv]; + vertices[v].vertex.y = verts[vv + 1]; + vertices[v].texCoord.u = uvs[vv]; + vertices[v].texCoord.v = uvs[vv + 1]; + _effect->transform(vertices[v].vertex.x, vertices[v].vertex.y); + *((uint32_t *)&vertices[v].color) = light; + *((uint32_t *)&vertices[v].color2) = dark; + } + } else { + for (int v = 0, vn = vertCount, vv = 0; v < vn; ++v, vv += 2) { + vertices[v].vertex.x = verts[vv]; + vertices[v].vertex.y = verts[vv + 1]; + vertices[v].texCoord.u = uvs[vv]; + vertices[v].texCoord.v = uvs[vv + 1]; + *((uint32_t *)&vertices[v].color) = light; + *((uint32_t *)&vertices[v].color2) = dark; + } + } + } else { + auto vertCount = currMesh.vCount; + V3F_T2F_C4B_C4B *vertex = (V3F_T2F_C4B_C4B *)currMesh.vBuf; + if (_effect) { + for (int v = 0; v < vertCount; ++v) { + _effect->transform(vertex[v].vertex.x, vertex[v].vertex.y); + *((uint32_t *)&vertex[v].color) = light; + *((uint32_t *)&vertex[v].color2) = dark; + } + } else { + for (int v = 0; v < vertCount; ++v) { + *((uint32_t *)&vertex[v].color) = light; + *((uint32_t *)&vertex[v].color2) = dark; + } + } + } + } + + SpineMeshData::moveVB(currMesh.vCount * byteStrideColor); + SpineMeshData::moveIB(currMesh.iCount); + // record debug shape info + if (_userData.debugMode) { + SpineDebugShape debugShape; + debugShape.type = static_cast(debugShapeType); + debugShape.vOffset = _model->vCount; + debugShape.vCount = currMesh.vCount; + debugShape.iOffset = _model->iCount; + debugShape.iCount = currMesh.iCount; + _debugShapes.push_back(debugShape); + } + + currMesh.blendMode = static_cast(slot->getData().getBlendMode()); + if (_userData.useSlotTexture) { + auto iter = slotTextureSet.find(slot); + if (iter != slotTextureSet.end()) { + currMesh.textureID = iter->second; + } + } + _model->addSlotMesh(currMesh); + _clipper->clipEnd(*slot); + } + + _clipper->clipEnd(); + if (_effect) _effect->end(); +} + +void SpineSkeletonInstance::setPremultipliedAlpha(bool val) { + _userData.premultipliedAlpha = val; +} + +void SpineSkeletonInstance::setColor(float r, float g, float b, float a) { + _userData.color.r = r; + _userData.color.g = g; + _userData.color.b = b; + _userData.color.a = a; +} + +void SpineSkeletonInstance::setJitterEffect(JitterVertexEffect *effect) { + _effect = effect; +} + +void SpineSkeletonInstance::setSwirlEffect(SwirlVertexEffect *effect) { + _effect = effect; +} + +void SpineSkeletonInstance::clearEffect() { + _effect = nullptr; +} + +AnimationState *SpineSkeletonInstance::getAnimationState() { + return _animState; +} + +void SpineSkeletonInstance::setMix(const std::string &from, const std::string &to, float duration) { + _animStateData->setMix(from.c_str(), to.c_str(), duration); +} + +void SpineSkeletonInstance::setListener(uint32_t listenerID, uint32_t type) { + switch (type) { + case EventType_Start: + _startListenerID = listenerID; + break; + case EventType_Interrupt: + _interruptListenerID = listenerID; + break; + case EventType_End: + _endListenerID = listenerID; + break; + case EventType_Dispose: + _disposeListenerID = listenerID; + break; + case EventType_Complete: + _completeListenerID = listenerID; + break; + case EventType_Event: + _eventListenerID = listenerID; + break; + } +} + +void SpineSkeletonInstance::setTrackEntryListener(uint32_t trackId, TrackEntry *entry) { + if (!entry->getRendererObject()) { + _trackEntryListenerID = trackId; + entry->setRendererObject(this); + entry->setListener(trackEntryCallback); + } +} + +void SpineSkeletonInstance::setUseTint(bool useTint) { + _userData.useTint = useTint; +} + +void SpineSkeletonInstance::setDebugMode(bool debug) { + _userData.debugMode = debug; +} + +void SpineSkeletonInstance::onTrackEntryEvent(TrackEntry *entry, EventType type, Event *event) { + if (!entry->getRendererObject()) return; + SpineWasmUtil::s_listenerID = _trackEntryListenerID; + SpineWasmUtil::s_currentType = type; + SpineWasmUtil::s_currentEntry = entry; + SpineWasmUtil::s_currentEvent = event; + spineTrackListenerCallback(); +} + +void SpineSkeletonInstance::onAnimationStateEvent(TrackEntry *entry, EventType type, Event *event) { + SpineWasmUtil::s_currentType = type; + SpineWasmUtil::s_currentEntry = entry; + SpineWasmUtil::s_currentEvent = event; + switch (type) { + case EventType_Start: + if (_startListenerID != 0) { + SpineWasmUtil::s_listenerID = _startListenerID; + spineListenerCallBackFromJS(); + } + break; + case EventType_Interrupt: + if (_interruptListenerID != 0) { + SpineWasmUtil::s_listenerID = _interruptListenerID; + spineListenerCallBackFromJS(); + } + break; + case EventType_End: + if (_endListenerID != 0) { + SpineWasmUtil::s_listenerID = _endListenerID; + spineListenerCallBackFromJS(); + } + break; + case EventType_Dispose: + if (_disposeListenerID != 0) { + SpineWasmUtil::s_listenerID = _disposeListenerID; + spineListenerCallBackFromJS(); + } + break; + case EventType_Complete: + if (_completeListenerID != 0) { + SpineWasmUtil::s_listenerID = _completeListenerID; + spineListenerCallBackFromJS(); + } + break; + case EventType_Event: + if (_eventListenerID != 0) { + SpineWasmUtil::s_listenerID = _eventListenerID; + spineListenerCallBackFromJS(); + } + break; + } +} + +std::vector &SpineSkeletonInstance::getDebugShapes() { + return this->_debugShapes; +} + +void SpineSkeletonInstance::resizeSlotRegion(const std::string &slotName, uint32_t width, uint32_t height, bool createNew) { + if (!_skeleton) return; + auto slot = _skeleton->findSlot(slotName.c_str()); + if (!slot) return; + auto attachment = slot->getAttachment(); + if (!attachment) return; + if (createNew) { + attachment = attachment->copy(); + slot->setAttachment(attachment); + } + if (attachment->getRTTI().isExactly(spine::RegionAttachment::rtti)) { + auto region = static_cast(attachment); + region->setRegionWidth(width); + region->setRegionHeight(height); + region->setRegionOriginalWidth(width); + region->setRegionOriginalHeight(height); + region->setWidth(width); + region->setHeight(height); + region->setUVs(0, 0, 1.0f, 1.0f, false); + region->updateOffset(); + auto attachmentVertices = static_cast(region->getRendererObject()); + if (createNew) { + attachmentVertices = attachmentVertices->copy(); + region->setRendererObject(attachmentVertices); + } + V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts; + auto UVs = region->getUVs(); + for (int i = 0, ii = 0; i < 4; ++i, ii += 2) { + vertices[i].texCoord.u = UVs[ii]; + vertices[i].texCoord.v = UVs[ii + 1]; + } + } else if (attachment->getRTTI().isExactly(spine::MeshAttachment::rtti)) { + auto mesh = static_cast(attachment); + mesh->setRegionWidth(width); + mesh->setRegionHeight(height); + mesh->setRegionOriginalWidth(width); + mesh->setRegionOriginalHeight(height); + mesh->setWidth(width); + mesh->setHeight(height); + mesh->setRegionU(0); + mesh->setRegionV(0); + mesh->setRegionU2(1.0f); + mesh->setRegionV2(1.0f); + mesh->setRegionRotate(true); + mesh->setRegionDegrees(0); + mesh->updateUVs(); + auto attachmentVertices = static_cast(mesh->getRendererObject()); + if (createNew) { + attachmentVertices = attachmentVertices->copy(); + mesh->setRendererObject(attachmentVertices); + } + V3F_T2F_C4B *vertices = attachmentVertices->_triangles->verts; + auto UVs = mesh->getUVs(); + for (size_t i = 0, ii = 0, nn = mesh->getWorldVerticesLength(); ii < nn; ++i, ii += 2) { + vertices[i].texCoord.u = UVs[ii]; + vertices[i].texCoord.v = UVs[ii + 1]; + } + } +} + +void SpineSkeletonInstance::setSlotTexture(const std::string &slotName, uint32_t textureID) { + if (!_skeleton) return; + auto slot = _skeleton->findSlot(slotName.c_str()); + if (!slot) return; + _userData.useSlotTexture = true; + auto iter = slotTextureSet.find(slot); + if (iter != slotTextureSet.end()) { + iter->second = textureID; + } else { + slotTextureSet[slot] = textureID; + } +} \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/spine-skeleton-instance.h b/cocos/editor-support/spine-wasm/spine-skeleton-instance.h new file mode 100644 index 0000000..c1b5314 --- /dev/null +++ b/cocos/editor-support/spine-wasm/spine-skeleton-instance.h @@ -0,0 +1,88 @@ +#ifndef _SPINE_SKELETON_INSTANCE_H_ +#define _SPINE_SKELETON_INSTANCE_H_ +#include +#include +#include +#include +#include +#include "mesh-type-define.h" +#include "spine-model.h" +using namespace spine; + + +enum DEBUG_SHAPE_TYPE { + DEBUG_REGION = 0, + DEBUG_MESH = 1 +}; +class SpineDebugShape { +public: + SpineDebugShape() {} + ~SpineDebugShape() {} + uint32_t type = 0; + uint32_t vOffset = 0; + uint32_t vCount = 0; + uint32_t iOffset = 0; + uint32_t iCount = 0; +}; + +class SpineSkeletonInstance { + struct UserData { + bool useTint = false; + bool premultipliedAlpha = false; + bool debugMode = false; + bool useSlotTexture = false; + Color4F color = Color4F(1.0F, 1.0F, 1.0F, 1.0F); + }; + +public: + SpineSkeletonInstance(); + ~SpineSkeletonInstance(); + Skeleton *initSkeleton(SkeletonData *data); + TrackEntry *setAnimation(float trackIndex, const std::string &name, bool loop); + void setSkin(const std::string &name); + void updateAnimation(float dltTime); + SpineModel *updateRenderData(); + void setPremultipliedAlpha(bool val); + void setUseTint(bool useTint); + void setDebugMode(bool debug); + void setColor(float r, float g, float b, float a); + void setJitterEffect(JitterVertexEffect *effect); + void setSwirlEffect(SwirlVertexEffect *effect); + void clearEffect(); + AnimationState *getAnimationState(); + void setMix(const std::string &from, const std::string &to, float duration); + void setListener(uint32_t listenerID, uint32_t type); + void setTrackEntryListener(uint32_t trackId, TrackEntry *entry); + void onAnimationStateEvent(TrackEntry *entry, EventType type, Event *event); + void onTrackEntryEvent(TrackEntry *entry, EventType type, Event *event); + std::vector &getDebugShapes(); + void resizeSlotRegion(const std::string &slotName, uint32_t width, uint32_t height, bool createNew = false); + void setSlotTexture(const std::string &slotName, uint32_t index); + void destroy(); + bool isCache{false}; + bool enable{true}; + float dtRate{1.0F}; +private: + void collectMeshData(); + +private: + Skeleton *_skeleton = nullptr; + SkeletonData *_skeletonData = nullptr; + AnimationStateData *_animStateData = nullptr; + AnimationState *_animState = nullptr; + SkeletonClipping *_clipper = nullptr; + VertexEffect *_effect = nullptr; + SpineModel *_model = nullptr; + uint32_t _startListenerID = 0; + uint32_t _interruptListenerID = 0; + uint32_t _endListenerID = 0; + uint32_t _disposeListenerID = 0; + uint32_t _completeListenerID = 0; + uint32_t _eventListenerID = 0; + uint32_t _trackEntryListenerID = 0; + UserData _userData; + std::vector _debugShapes{}; + std::map slotTextureSet{}; +}; + +#endif \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/spine-type-export.cpp b/cocos/editor-support/spine-wasm/spine-type-export.cpp new file mode 100644 index 0000000..4fc0eac --- /dev/null +++ b/cocos/editor-support/spine-wasm/spine-type-export.cpp @@ -0,0 +1,1628 @@ +#include +#include +#include +#include +#include +#include "spine-skeleton-instance.h" +#include "spine-wasm.h" +#include "Vector2.h" + +using namespace emscripten; +using namespace spine; + +namespace { +std::string STRING_SP2STD(const spine::String &str) { + std::string stdStr(str.buffer(), str.length()); + return stdStr; +} + +const spine::String STRING_STD2SP(const std::string &str) { + const spine::String spString(str.c_str()); + return spString; +} + +const std::vector VECTOR_SP2STD_STRING(Vector &container) { + int count = container.size(); + std::vector stdVector(count); + for (int i = 0; i < count; i++) { + stdVector[i] = STRING_SP2STD(container[i]); + } + return stdVector; +} + +template +Vector VECTOR_STD2SP(std::vector &container) { + int count = container.size(); + Vector vecSP = Vector(); + vecSP.setSize(count, 0); + for (int i = 0; i < count; i++) { + vecSP[i] = container[i]; + } + return vecSP; +} + +template +Vector VECTOR_STD2SP_POINTER(std::vector &container) { + int count = container.size(); + Vector vecSP = Vector(); + vecSP.setSize(count, nullptr); + for (int i = 0; i < count; i++) { + vecSP[i] = container[i]; + } + return vecSP; +} + +template +void VECTOR_STD_COPY_SP(std::vector &stdVector, Vector &spVector) { + int count = stdVector.size(); + for (int i = 0; i < count; i++) { + stdVector[i] = spVector[i]; + } +} + +using SPVectorFloat = Vector; +using SPVectorVectorFloat = Vector>; +using SPVectorInt = Vector; +using SPVectorVectorInt = Vector>; +using SPVectorSize_t = Vector; +using SPVectorBonePtr = Vector; +using SPVectorBoneDataPtr = Vector; +using SPVectorUnsignedShort = Vector; +using SPVectorConstraintDataPtr = Vector; +using SPVectorSlotPtr = Vector; +using SPVectorSkinPtr = Vector; +using SPVectorEventDataPtr = Vector; +using SPVectorAnimationPtr = Vector; +using SPVectorIkConstraintPtr = Vector; +using SPVectorIkConstraintDataPtr = Vector; +using SPVectorTransformConstraintPtr = Vector; +using SPVectorPathConstraintPtr = Vector; +using SPVectorTimelinePtr = Vector; +using SPVectorTrackEntryPtr = Vector; +using SPVectorUpdatablePtr = Vector; + +} // namespace + +EMSCRIPTEN_BINDINGS(spine) { + + register_vector("VectorFloat"); + register_vector>("VectorVectorFloat"); + register_vector("VectorUnsignedShort"); + register_vector("VectorOfUInt"); + register_vector("VectorString"); + register_vector("VectorBoneData"); + register_vector("VectorBone"); + register_vector("VectorSkinEntry"); + register_vector("VectorSlotData"); + register_vector("VectorSlot"); + register_vector("VectorAnimation"); + register_vector("VectorTimeline"); + register_vector("VectorSkin"); + register_vector("VectorEventData"); + register_vector("VectorEvent"); + register_vector("VectorConstraintData"); + register_vector("VectorIkConstraint"); + register_vector("VectorPathConstraint"); + register_vector("VectorTransformConstraint"); + register_vector("VectorIkConstraintData"); + register_vector("VectorTransformConstraintData"); + register_vector("VectorPathConstraintData"); + register_vector("VectorTrackEntry"); + + class_("SPVectorFloat") + .constructor<>() + .function("resize", &SPVectorFloat::setSize) + .function("size", &SPVectorFloat::size) + .function("get", &SPVectorFloat::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorFloat &obj, int index, float value) { + obj[index] = value; + })); + + class_("SPVectorVectorFloat") + .constructor<>() + .function("resize", &SPVectorVectorFloat::setSize) + .function("size", &SPVectorVectorFloat::size) + .function("get", &SPVectorVectorFloat::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorVectorFloat &obj, int index, SPVectorFloat &value) { + obj[index] = value; + })); + + class_("SPVectorInt") + .constructor<>() + .function("resize", &SPVectorInt::setSize) + .function("size", &SPVectorInt::size) + .function("get", &SPVectorInt::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorInt &obj, int index, int value) { + obj[index] = value; + })); + + class_("SPVectorVectorInt") + .constructor<>() + .function("resize", &SPVectorVectorInt::setSize) + .function("size", &SPVectorVectorInt::size) + .function("get", &SPVectorVectorInt::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorVectorInt &obj, int index, SPVectorInt &value) { + obj[index] = value; + })); + + class_("SPVectorSize_t") + .constructor<>() + .function("resize", &SPVectorSize_t::setSize) + .function("size", &SPVectorSize_t::size) + .function("get", &SPVectorSize_t::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorSize_t &obj, size_t index, size_t value) { + obj[index] = value; + })); + + class_("SPVectorUnsignedShort") + .constructor<>() + .function("resize", &SPVectorUnsignedShort::setSize) + .function("size", &SPVectorUnsignedShort::size) + .function("get", &SPVectorUnsignedShort::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorUnsignedShort &obj, int index, int value) { + obj[index] = value; + })); + + class_("SPVectorBonePtr") + .constructor<>() + .function("resize", &SPVectorBonePtr::setSize) + .function("size", &SPVectorBonePtr::size) + .function("get", &SPVectorBonePtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorBonePtr &obj, int index, Bone *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorBoneDataPtr") + .constructor<>() + .function("resize", &SPVectorBoneDataPtr::setSize) + .function("size", &SPVectorBoneDataPtr::size) + .function("get", &SPVectorBoneDataPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorBoneDataPtr &obj, int index, BoneData *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorConstraintDataPtr") + .constructor<>() + .function("resize", &SPVectorConstraintDataPtr::setSize) + .function("size", &SPVectorConstraintDataPtr::size) + .function("get", &SPVectorConstraintDataPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorConstraintDataPtr &obj, int index, ConstraintData *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorSlotPtr") + .constructor<>() + .function("resize", &SPVectorSlotPtr::setSize) + .function("size", &SPVectorSlotPtr::size) + .function("get", &SPVectorSlotPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorSlotPtr &obj, int index, Slot *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorSkinPtr") + .constructor<>() + .function("resize", &SPVectorSkinPtr::setSize) + .function("size", &SPVectorSkinPtr::size) + .function("get", &SPVectorSkinPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorSkinPtr &obj, int index, Skin *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorEventDataPtr") + .constructor<>() + .function("resize", &SPVectorEventDataPtr::setSize) + .function("size", &SPVectorEventDataPtr::size) + .function("get", &SPVectorEventDataPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorEventDataPtr &obj, int index, EventData *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorAnimationPtr") + .constructor<>() + .function("resize", &SPVectorAnimationPtr::setSize) + .function("size", &SPVectorAnimationPtr::size) + .function("get", &SPVectorAnimationPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorAnimationPtr &obj, int index, Animation *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorIkConstraintPtr") + .constructor<>() + .function("resize", &SPVectorIkConstraintPtr::setSize) + .function("size", &SPVectorIkConstraintPtr::size) + .function("get", &SPVectorIkConstraintPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorIkConstraintPtr &obj, int index, IkConstraint *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorIkConstraintDataPtr") + .constructor<>() + .function("resize", &SPVectorIkConstraintDataPtr::setSize) + .function("size", &SPVectorIkConstraintDataPtr::size) + .function("get", &SPVectorIkConstraintDataPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorIkConstraintDataPtr &obj, int index, IkConstraintData *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorTransformConstraintPtr") + .constructor<>() + .function("resize", &SPVectorTransformConstraintPtr::setSize) + .function("size", &SPVectorTransformConstraintPtr::size) + .function("get", &SPVectorTransformConstraintPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorTransformConstraintPtr &obj, int index, TransformConstraint *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorPathConstraintPtr") + .constructor<>() + .function("resize", &SPVectorPathConstraintPtr::setSize) + .function("size", &SPVectorPathConstraintPtr::size) + .function("get", &SPVectorPathConstraintPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorPathConstraintPtr &obj, int index, PathConstraint *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorTimelinePtr") + .constructor<>() + .function("resize", &SPVectorTimelinePtr::setSize) + .function("size", &SPVectorTimelinePtr::size) + .function("get", &SPVectorTimelinePtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorTimelinePtr &obj, int index, Timeline *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorTrackEntryPtr") + .constructor<>() + .function("resize", &SPVectorTrackEntryPtr::setSize) + .function("size", &SPVectorTrackEntryPtr::size) + .function("get", &SPVectorTrackEntryPtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorTrackEntryPtr &obj, int index, TrackEntry *value) { + obj[index] = value; + }), allow_raw_pointer()); + + class_("SPVectorUpdatablePtr") + .constructor<>() + .function("resize", &SPVectorUpdatablePtr::setSize) + .function("size", &SPVectorUpdatablePtr::size) + .function("get", &SPVectorUpdatablePtr::operator[], allow_raw_pointers()) + .function("set",optional_override([](SPVectorUpdatablePtr &obj, int index, Updatable *value) { + obj[index] = value; + }), allow_raw_pointer()); + + + class_("Vector2") + .constructor<>() + .constructor() + .function("setX", &Vector2::setX) + .function("getX", &Vector2::getX) + .function("setY", &Vector2::setY) + .function("getY", &Vector2::getY) + .function("set", &Vector2::set) + .function("length", &Vector2::length) + .function("normalize", &Vector2::normalize); + + class_("String") + .function("length", &String::length) + .function("isEmpty", &String::isEmpty) + .function("append", select_overload(&String::append)) + .function("equals", select_overload(&String::operator=)) + .function("buffer", &String::buffer, allow_raw_pointer()) + //.function("estr", optional_override([](String &obj) { + // auto str = emscripten::val(obj.buffer()); + // return str; }), allow_raw_pointers()) + .function("strPtr", optional_override([](String &obj) { + return reinterpret_cast(obj.buffer());}), allow_raw_pointers()) + .function("str", optional_override([](String &obj) { + std::string stdStr(obj.buffer(), obj.length()); + return stdStr; }), allow_raw_pointers()); + + enum_("TimelineType") + .value("rotate", TimelineType_Rotate) + .value("translate", TimelineType_Translate) + .value("scale", TimelineType_Scale) + .value("shear", TimelineType_Shear) + .value("attachment", TimelineType_Attachment) + .value("color", TimelineType_Color) + .value("deform", TimelineType_Deform) + .value("event", TimelineType_Event) + .value("drawOrder", TimelineType_DrawOrder) + .value("ikConstraint", TimelineType_IkConstraint) + .value("transformConstraint", TimelineType_TransformConstraint) + .value("pathConstraintPosition", TimelineType_PathConstraintPosition) + .value("pathConstraintSpacing", TimelineType_PathConstraintSpacing) + .value("pathConstraintMix", TimelineType_PathConstraintMix) + .value("twoColor", TimelineType_TwoColor); + + enum_("MixDirection") + .value("mixIn", MixDirection_In) + .value("mixOut", MixDirection_Out); + + enum_("MixBlend") + .value("setup", MixBlend_Setup) + .value("first", MixBlend_First) + .value("replace", MixBlend_Replace) + .value("add", MixBlend_Add); + + enum_("BlendMode") + .value("Normal", BlendMode_Normal) + .value("Additive", BlendMode_Additive) + .value("Multiply", BlendMode_Multiply) + .value("Screen", BlendMode_Screen); + + enum_("EventType") + .value("start", EventType_Start) + .value("interrupt", EventType_Interrupt) + .value("end", EventType_End) + .value("dispose", EventType_Dispose) + .value("complete", EventType_Complete) + .value("event", EventType_Event); + + enum_("TransformMode") + .value("Normal", TransformMode_Normal) + .value("OnlyTranslation", TransformMode_OnlyTranslation) + .value("NoRotationOrReflection", TransformMode_NoRotationOrReflection) + .value("NoScale", TransformMode_NoScale) + .value("NoScaleOrReflection", TransformMode_NoScaleOrReflection); + + enum_("PositionMode") + .value("Fixed", PositionMode_Fixed) + .value("Percent", PositionMode_Percent); + + enum_("SpacingMode") + .value("Length", SpacingMode_Length) + .value("Fixed", SpacingMode_Fixed) + .value("Percent", SpacingMode_Percent); + + enum_("RotateMode") + .value("Tangent", RotateMode_Tangent) + .value("Chain", RotateMode_Chain) + .value("ChainScale", RotateMode_ChainScale); + + enum_("TextureFilter") + .value("Unknown", TextureFilter_Unknown) + .value("Nearest", TextureFilter_Nearest) + .value("Linear", TextureFilter_Linear) + .value("MipMap", TextureFilter_MipMap) + .value("MipMapNearestNearest", TextureFilter_MipMapNearestNearest) + .value("MipMapLinearNearest", TextureFilter_MipMapLinearNearest) + .value("MipMapNearestLinear", TextureFilter_MipMapNearestLinear) + .value("MipMapLinearLinear", TextureFilter_MipMapLinearLinear); + + enum_("TextureWrap") + .value("MirroredRepeat", TextureWrap_MirroredRepeat) + .value("ClampToEdge", TextureWrap_ClampToEdge) + .value("Repeat", TextureWrap_Repeat); + + enum_("AttachmentType") + .value("Region", AttachmentType_Region) + .value("BoundingBox", AttachmentType_Boundingbox) + .value("Mesh", AttachmentType_Mesh) + .value("LinkedMesh", AttachmentType_Linkedmesh) + .value("Path", AttachmentType_Path) + .value("Point", AttachmentType_Point) + .value("Clipping", AttachmentType_Clipping); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + class_("MathUtils") + .class_property("PI", &MathUtil::Pi) + .class_property("PI2", &MathUtil::Pi_2) + .class_property("radDeg", &MathUtil::Rad_Deg) + .class_property("degreesToRadians", &MathUtil::Rad_Deg) + .class_property("degRad", &MathUtil::Deg_Rad) + .class_property("degreesToRadians", &MathUtil::Deg_Rad) + .class_function("abs", &MathUtil::abs) + .class_function("signum", &MathUtil::sign) + .class_function("clamp", &MathUtil::clamp) + .class_function("fmod", &MathUtil::fmod) + .class_function("atan2", &MathUtil::atan2) + .class_function("cos", &MathUtil::cos) + .class_function("sin", &MathUtil::sin) + .class_function("sqrt", &MathUtil::sqrt) + .class_function("acos", &MathUtil::acos) + .class_function("sinDeg", &MathUtil::sinDeg) + .class_function("cosDeg", &MathUtil::cosDeg) + .class_function("isNan", &MathUtil::isNan) + .class_function("random", &MathUtil::random) + .class_function("randomTriangular", select_overload(&MathUtil::randomTriangular)) + .class_function("randomTriangularWith", select_overload(&MathUtil::randomTriangular)) + .class_function("pow", &MathUtil::pow); + + class_("Color") + .constructor<>() + .constructor() + .function("set", static_cast(&Color::set)) + .function("add", static_cast(&Color::add)) + .function("clamp", &Color::clamp) + .property("r", &Color::r) + .property("g", &Color::g) + .property("b", &Color::b) + .property("a", &Color::a); + + class_("Interpolation") + .function("apply", &Interpolation::apply, pure_virtual()); + + class_("Triangulator") + .constructor<>() + .function("triangulate", &Triangulator::triangulate) + .function("decompose", &Triangulator::decompose, allow_raw_pointers()); + + class_("ConstraintData") + .constructor() + .function("getName", optional_override([](ConstraintData &obj) { return STRING_SP2STD(obj.getName()); })) + .function("getOrder", &ConstraintData::getOrder) + .function("setOrder", &ConstraintData::setOrder) + .function("getSkinRequired", &ConstraintData::isSkinRequired) + .function("setSkinRequired", &ConstraintData::setSkinRequired); + + class_>("IkConstraintData") + .constructor() + .function("getBones", optional_override([](IkConstraintData &obj) { + return &obj.getBones(); }), allow_raw_pointer()) + .function("getTarget", &IkConstraintData::getTarget, allow_raw_pointer()) + .function("setTarget", &IkConstraintData::setTarget, allow_raw_pointer()) + .function("getBendDirection", &IkConstraintData::getBendDirection) + .function("setBendDirection", &IkConstraintData::setBendDirection) + .function("getCompress", &IkConstraintData::getCompress) + .function("setCompress", &IkConstraintData::setCompress) + .function("getStretch", &IkConstraintData::getStretch) + .function("setStretch", &IkConstraintData::setStretch) + .function("getUniform", &IkConstraintData::getUniform) + .function("setUniform", &IkConstraintData::setUniform) + .function("getMix", &IkConstraintData::getMix) + .function("setMix", &IkConstraintData::setMix) + .function("getSoftness", &IkConstraintData::getSoftness) + .function("setSoftness", &IkConstraintData::setSoftness); + + class_>("PathConstraintData") + .constructor() + .function("getBones",optional_override([](PathConstraintData &obj) { + return &obj.getBones(); }), allow_raw_pointer()) + .function("getTarget", &PathConstraintData::getTarget, allow_raw_pointer()) + .function("setTarget", &PathConstraintData::setTarget, allow_raw_pointer()) + .function("getPositionMode", &PathConstraintData::getPositionMode) + .function("setPositionMode", &PathConstraintData::setPositionMode) + .function("getSpacingMode", &PathConstraintData::getSpacingMode) + .function("setSpacingMode", &PathConstraintData::setSpacingMode) + .function("getRotateMode", &PathConstraintData::getRotateMode) + .function("setRotateMode", &PathConstraintData::setRotateMode) + .function("getOffsetRotation", &PathConstraintData::getOffsetRotation) + .function("setOffsetRotation", &PathConstraintData::setOffsetRotation) + .function("getPosition", &PathConstraintData::getPosition) + .function("setPosition", &PathConstraintData::setPosition) + .function("getSpacing", &PathConstraintData::getSpacing) + .function("setSpacing", &PathConstraintData::setSpacing) + .function("getRotateMix", &PathConstraintData::getRotateMix) + .function("setRotateMix", &PathConstraintData::setRotateMix) + .function("getTranslateMix", &PathConstraintData::getTranslateMix) + .function("setTranslateMix", &PathConstraintData::setTranslateMix); + + class_("SkeletonBounds") + .function("update", &SkeletonBounds::update) + .function("aabbContainsPoint", &SkeletonBounds::aabbcontainsPoint) + .function("aabbIntersectsSegment", &SkeletonBounds::aabbintersectsSegment) + .function("aabbIntersectsSkeleton", &SkeletonBounds::aabbIntersectsSkeleton) + .function("containsPoint", optional_override([](SkeletonBounds &obj, float x, float y) { + return obj.containsPoint(x, y); }),allow_raw_pointers()) + .function("containsPointPolygon", optional_override([](SkeletonBounds &obj,Polygon* polygon, float x, float y) { + return obj.containsPoint(polygon, x, y); }),allow_raw_pointers()) + .function("intersectsSegment", optional_override([](SkeletonBounds &obj, float x1, float y1, float x2, float y2){ + return obj.intersectsSegment(x1, y1, x2, y2); }),allow_raw_pointers()) + .function("intersectsSegmentPolygon", optional_override([](SkeletonBounds &obj,Polygon* polygon, + float x1, float y1, float x2, float y2){ + return obj.intersectsSegment(polygon, x1, y1, x2, y2); }),allow_raw_pointers()) + .function("getPolygon", &SkeletonBounds::getPolygon, allow_raw_pointers()) + .function("getWidth", &SkeletonBounds::getWidth) + .function("getHeight", &SkeletonBounds::getHeight); + + class_("Event") + .constructor() + .function("getData", optional_override([](Event &obj) { + return const_cast(&obj.getData()); }), allow_raw_pointers()) + .function("getIntValue", &Event::getIntValue) + .function("setIntValue", &Event::setIntValue) + .function("getFloatValue", &Event::getFloatValue) + .function("setFloatValue", &Event::setFloatValue) + .function("getStringValue", optional_override([](Event &obj) { + return STRING_SP2STD(obj.getStringValue()); })) + .function("setStringValue", optional_override([](Event &obj, const std::string &name) { + return obj.setStringValue(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("getTime", &Event::getTime) + .function("getVolume", &Event::getVolume) + .function("setVolume", &Event::setVolume) + .function("getBalance", &Event::getBalance) + .function("setBalance", &Event::setBalance); + + class_("EventData") + .constructor() + .function("getName", optional_override([](EventData &obj) { + return STRING_SP2STD(obj.getName()); })) + .function("getIntValue", &EventData::getIntValue) + .function("setIntValue", &EventData::setIntValue) + .function("getFloatValue", &EventData::getFloatValue) + .function("setFloatValue", &EventData::setFloatValue) + .function("getStringValue", optional_override([](EventData &obj) { + return STRING_SP2STD(obj.getStringValue()); })) + .function("setStringValue", optional_override([](EventData &obj, const std::string &name) { + return obj.setStringValue(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("getAudioPath", optional_override([](EventData &obj) { + return STRING_SP2STD(obj.getAudioPath()); })) + .function("setAudioPath", optional_override([](EventData &obj, const std::string &name) { + return obj.setAudioPath(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("getVolume", &EventData::getVolume) + .function("setVolume", &EventData::setVolume) + .function("getBalance", &EventData::getBalance) + .function("setBalance", &EventData::setBalance); + + class_("Attachment") + //.function("getName", optional_override([](Attachment &obj) { + // return emscripten::val(obj.getName().buffer()); })); + .function("getName", &Attachment::getName, allow_raw_pointers()); + + // pure_virtual and raw pointer + class_>("VertexAttachment") + .function("getId", &VertexAttachment::getId) + .function("getBones", optional_override([](VertexAttachment &obj){ + return &obj.getBones(); }), allow_raw_pointer()) + .function("getVertices", optional_override([](VertexAttachment &obj){ + return &obj.getVertices(); }), allow_raw_pointer()) + .function("getWorldVerticesLength", &VertexAttachment::getWorldVerticesLength) + .function("setWorldVerticesLength", &VertexAttachment::setWorldVerticesLength) + .function("getDeformAttachment", &VertexAttachment::getDeformAttachment, allow_raw_pointer()) + .function("setDeformAttachment", &VertexAttachment::setDeformAttachment, allow_raw_pointer()) + .function("computeWorldVertices", select_overload&, size_t, size_t)> + (&VertexAttachment::computeWorldVertices), allow_raw_pointer()) + .function("copyTo", &VertexAttachment::copyTo, allow_raw_pointers()); + + class_>("BoundingBoxAttachment") + .constructor() + .function("getName", optional_override([](BoundingBoxAttachment &obj) { + return STRING_SP2STD(obj.getName()); })) + .function("copy", &BoundingBoxAttachment::copy, allow_raw_pointers()); + + class_>("ClippingAttachment") + .constructor() + .function("getEndSlot", &ClippingAttachment::getEndSlot, allow_raw_pointers()) + .function("setEndSlot", &ClippingAttachment::setEndSlot, allow_raw_pointers()) + .function("copy", &ClippingAttachment::copy, allow_raw_pointers()); + + class_>("MeshAttachment") + .constructor() + .function("getPath", optional_override([](MeshAttachment &obj) { + return STRING_SP2STD(obj.getPath()); })) + .function("setPath", optional_override([](MeshAttachment &obj, const std::string &path) { + const String &pathSP = STRING_STD2SP(path); + obj.setPath(pathSP); })) + .function("getRegionUVs", optional_override([](MeshAttachment &obj) { + return &obj.getRegionUVs(); }), allow_raw_pointer()) + .function("getUVs", optional_override([](MeshAttachment &obj) { + return &obj.getUVs(); }), allow_raw_pointer()) + .function("getTriangles", optional_override([](MeshAttachment &obj) { + return &obj.getTriangles(); }), allow_raw_pointer()) + .function("getColor", optional_override([](MeshAttachment &obj) { + return &obj.getColor(); }), allow_raw_pointers()) + .function("getWidth", &MeshAttachment::getWidth) + .function("setWidth", &MeshAttachment::setWidth) + .function("getHeight", &MeshAttachment::getHeight) + .function("setHeight", &MeshAttachment::setHeight) + .function("getHullLength", &MeshAttachment::getHullLength) + .function("setHullLength", &MeshAttachment::setHullLength) + .function("getEdges", optional_override([](MeshAttachment &obj) { + return &obj.getEdges(); }), allow_raw_pointer()) + .function("updateUVs", &MeshAttachment::updateUVs) + .function("getParentMesh", &MeshAttachment::getParentMesh, allow_raw_pointers()) + .function("setParentMesh", &MeshAttachment::setParentMesh, allow_raw_pointers()) + .function("copy", &MeshAttachment::copy, allow_raw_pointers()) + .function("newLinkedMesh", &MeshAttachment::newLinkedMesh, allow_raw_pointers()); + + class_>("PathAttachment") + .constructor() + .function("getLengths", optional_override([](PathAttachment &obj) { + return &obj.getLengths(); }), allow_raw_pointer()) + .function("getClosed", &PathAttachment::isClosed) + .function("setClosed", &PathAttachment::setClosed) + .function("getConstantSpeed", &PathAttachment::isConstantSpeed) + .function("setConstantSpeed", &PathAttachment::setConstantSpeed) + .function("copy", &PathAttachment::copy, allow_raw_pointers()); + + class_>("PointAttachment") + .constructor() + .function("getX", &PointAttachment::getX) + .function("setX", &PointAttachment::setX) + .function("getY", &PointAttachment::getY) + .function("setY", &PointAttachment::setY) + .function("getRotation", &PointAttachment::getRotation) + .function("setRotation", &PointAttachment::setRotation) + .function("computeWorldPosition", optional_override([](PointAttachment &obj, Bone &bone, float ox, float oy) { + obj.computeWorldPosition(bone, ox, oy);})) + .function("computeWorldRotation", &PointAttachment::computeWorldRotation) + .function("copy", &PointAttachment::copy, allow_raw_pointers()); + + class_>("RegionAttachment") + .constructor() + .function("getX", &RegionAttachment::getX) + .function("setX", &RegionAttachment::setX) + .function("getY", &RegionAttachment::getY) + .function("setY", &RegionAttachment::setY) + .function("getScaleX", &RegionAttachment::getScaleX) + .function("setScaleX", &RegionAttachment::setScaleX) + .function("getScaleY", &RegionAttachment::getScaleY) + .function("setScaleY", &RegionAttachment::setScaleY) + .function("getRotation", &RegionAttachment::getRotation) + .function("setRotation", &RegionAttachment::setRotation) + .function("getWidth", &RegionAttachment::getWidth) + .function("setWidth", &RegionAttachment::setWidth) + .function("getHeight", &RegionAttachment::getHeight) + .function("setHeight", &RegionAttachment::setHeight) + .function("getColor", optional_override([](RegionAttachment &obj) { + return &obj.getColor(); }), allow_raw_pointers()) + .function("getPath", optional_override([](RegionAttachment &obj) { + return STRING_SP2STD(obj.getPath()); })) + .function("setPath", optional_override([](RegionAttachment &obj, const std::string &path) { + const String &pathSP = STRING_STD2SP(path); + obj.setPath(pathSP); })) + .function("getRendererObject", &RegionAttachment::getRendererObject, allow_raw_pointers()) + .function("getOffset", optional_override([](RegionAttachment &obj) { + return &obj.getOffset(); }), allow_raw_pointer()) + .function("setUVs", &RegionAttachment::setUVs) + .function("getUVs", optional_override([](RegionAttachment &obj) { + return &obj.getUVs(); }), allow_raw_pointer()) + .function("updateOffset", &RegionAttachment::updateOffset) + .function("computeWorldVertices", select_overload&, size_t, size_t)> + (&RegionAttachment::computeWorldVertices), allow_raw_pointer()) + .function("copy", &RegionAttachment::copy, allow_raw_pointer()); + + class_("AttachmentLoader") + //.constructor<>() + .function("newClippingAttachment", &AttachmentLoader::newClippingAttachment, pure_virtual(), allow_raw_pointers()) + .function("newPointAttachment", &AttachmentLoader::newPointAttachment, pure_virtual(), allow_raw_pointers()) + .function("newPathAttachment", &AttachmentLoader::newPathAttachment, pure_virtual(), allow_raw_pointers()) + .function("newBoundingBoxAttachment", &AttachmentLoader::newBoundingBoxAttachment, pure_virtual(), allow_raw_pointers()) + .function("newMeshAttachment", &AttachmentLoader::newMeshAttachment, pure_virtual(), allow_raw_pointers()) + .function("newRegionAttachment", &AttachmentLoader::newRegionAttachment, pure_virtual(), allow_raw_pointers()); + + class_>("AtlasAttachmentLoader") + .constructor() + .function("newRegionAttachment", &AtlasAttachmentLoader::newRegionAttachment, allow_raw_pointer()) + .function("newMeshAttachment", &AtlasAttachmentLoader::newMeshAttachment, allow_raw_pointer()) + .function("newBoundingBoxAttachment", &AtlasAttachmentLoader::newBoundingBoxAttachment, allow_raw_pointer()) + .function("newPathAttachment", &AtlasAttachmentLoader::newPathAttachment, allow_raw_pointer()) + .function("newPointAttachment", &AtlasAttachmentLoader::newPointAttachment, allow_raw_pointer()) + .function("newClippingAttachment", &AtlasAttachmentLoader::newClippingAttachment, allow_raw_pointer()); + + class_("TextureAtlasPage") + .constructor() + .function("getName", optional_override([](AtlasPage &obj) { + return STRING_SP2STD((const String)obj.name); })) + .property("minFilter", &AtlasPage::minFilter) + .property("magFilter", &AtlasPage::magFilter) + .property("uWrap", &AtlasPage::uWrap) + .property("vWrap", &AtlasPage::vWrap) + //.property("texture", &AtlasPage::texture) // no texture, use renderer object + .property("width", &AtlasPage::width) + .property("height", &AtlasPage::height); + + class_("TextureAtlasRegion") + //.property("page", &AtlasRegion::page) + .function("getName", optional_override([](AtlasRegion &obj) { + return STRING_SP2STD((const String)obj.name); })) + .property("x", &AtlasRegion::x) + .property("y", &AtlasRegion::y) + .property("index", &AtlasRegion::index) + .property("rotate", &AtlasRegion::rotate) + .property("degrees", &AtlasRegion::degrees); + //.property("texture", &AtlasRegion::height) + + class_("TextureAtlas") + .constructor() + .function("findRegion", optional_override([](Atlas &obj, const std::string &name) { + return obj.findRegion(STRING_STD2SP(name)); }), allow_raw_pointers()); + + class_>("Pow") + .constructor() + .function("apply", &PowInterpolation::apply); + + class_>("PowOut") + .constructor() + .function("apply", &PowInterpolation::apply); + + class_("SlotData") + .constructor() + .function("getIndex", &SlotData::getIndex) + .function("getName", optional_override([](SlotData &obj) { + return STRING_SP2STD(obj.getName()); })) + .function("getBoneData", optional_override([](SlotData &obj) { + return &obj.getBoneData(); }), allow_raw_pointers()) + .function("getColor", optional_override([](SlotData &obj) { + return &obj.getColor();}), allow_raw_pointers()) + .function("getDarkColor", optional_override([](SlotData &obj) { + return &obj.getDarkColor();}), allow_raw_pointers()) + .function("getBlendMode", &SlotData::getBlendMode) + .function("setBlendMode", &SlotData::setBlendMode); + + class_("Updatable") + .function("update", &Updatable::update, pure_virtual()) + .function("isActive", &Updatable::isActive, pure_virtual()); + + class_>("IkConstraint") + .constructor() + .function("getData", &IkConstraint::getData, allow_raw_pointers()) + .function("getBones", optional_override([](IkConstraint &obj) { + return &obj.getBones(); }), allow_raw_pointer()) + .function("getTarget", &IkConstraint::getTarget, allow_raw_pointer()) + .function("setTarget", &IkConstraint::setTarget, allow_raw_pointer()) + .function("getBendDirection", &IkConstraint::getBendDirection) + .function("setBendDirection", &IkConstraint::setBendDirection) + .function("getCompress", &IkConstraint::getCompress) + .function("setCompress", &IkConstraint::setCompress) + .function("getStretch", &IkConstraint::getStretch) + .function("setStretch", &IkConstraint::setStretch) + .function("getMix", &IkConstraint::getMix) + .function("setMix", &IkConstraint::setMix) + .function("getSoftness", &IkConstraint::getSoftness) + .function("setSoftness", &IkConstraint::setSoftness) + .function("getActive", &IkConstraint::isActive) + .function("setActive", &IkConstraint::setActive) + .function("isActive", &IkConstraint::isActive) + .function("apply", static_cast(&IkConstraint::apply)) + .function("update", &IkConstraint::update) + .class_function("apply1", optional_override([]( + IkConstraint &obj, Bone &bone, float targetX, float targetY, + bool compress, bool stretch, bool uniform, float alpha){ + obj.apply(bone, targetX, targetY, compress, stretch, uniform, alpha); + })) + .class_function("apply2", optional_override([]( + IkConstraint &obj, Bone &parent, Bone &child, float targetX, float targetY, + int bendDir, bool stretch, float softness, float alpha){ + obj.apply(parent, child, targetX, targetY, bendDir, stretch, softness, alpha); + })); + + class_>("PathConstraint") + .constructor() + .function("getData", &PathConstraint::getData, allow_raw_pointers()) + .function("getBones", optional_override([](PathConstraint &obj) { + return &obj.getBones(); }), allow_raw_pointer()) + .function("getTarget", &PathConstraint::getTarget, allow_raw_pointer()) + .function("setTarget", &PathConstraint::setTarget, allow_raw_pointer()) + .function("getPosition", &PathConstraint::getPosition) + .function("setPosition", &PathConstraint::setPosition) + .function("getSpacing", &PathConstraint::getSpacing) + .function("setSpacing", &PathConstraint::setSpacing) + .function("getRotateMix", &PathConstraint::getRotateMix) + .function("setRotateMix", &PathConstraint::setRotateMix) + .function("getTranslateMix", &PathConstraint::getTranslateMix) + .function("getTranslateMix", &PathConstraint::setTranslateMix) + .function("getActive", &PathConstraint::isActive) + .function("isActive", &PathConstraint::isActive) + .function("setActive", &PathConstraint::setActive) + .function("apply", &PathConstraint::apply) + .function("update", &PathConstraint::update); + + class_>("TransformConstraintData") + .constructor() + .function("getBones", optional_override([](TransformConstraintData &obj) { + return &obj.getBones(); }), allow_raw_pointer()) + .function("getTarget", &TransformConstraintData::getTarget, allow_raw_pointers()) + .function("getRotateMix", &TransformConstraintData::getRotateMix) + .function("getTranslateMix", &TransformConstraintData::getTranslateMix) + .function("getScaleMix", &TransformConstraintData::getScaleMix) + .function("getShearMix", &TransformConstraintData::getShearMix) + .function("getOffsetRotation", &TransformConstraintData::getOffsetRotation) + .function("getOffsetX", &TransformConstraintData::getOffsetX) + .function("getOffsetY", &TransformConstraintData::getOffsetY) + .function("getOffsetScaleX", &TransformConstraintData::getOffsetScaleX) + .function("getOffsetScaleY", &TransformConstraintData::getOffsetScaleY) + .function("getOffsetShearY", &TransformConstraintData::getOffsetShearY) + .function("getRelative", &TransformConstraintData::isRelative) + .function("getLocal", &TransformConstraintData::isLocal); + + class_>("TransformConstraint") + .constructor() + .function("getData", &TransformConstraint::getData, allow_raw_pointers()) + .function("getBones", optional_override([](TransformConstraint &obj) { + return &obj.getBones(); }), allow_raw_pointer()) + .function("getTarget", &TransformConstraint::getTarget, allow_raw_pointers()) + .function("getRotateMix", &TransformConstraint::getRotateMix) + .function("setRotateMix", &TransformConstraint::setRotateMix) + .function("getTranslateMix", &TransformConstraint::getTranslateMix) + .function("setTranslateMix", &TransformConstraint::setTranslateMix) + .function("getScaleMix", &TransformConstraint::getScaleMix) + .function("setScaleMix", &TransformConstraint::setScaleMix) + .function("getShearMix", &TransformConstraint::getShearMix) + .function("setShearMix", &TransformConstraint::setShearMix) + .function("getActive", &TransformConstraint::isActive) + .function("setActive", &TransformConstraint::setActive) + .function("isActive", &TransformConstraint::isActive) + .function("apply", &TransformConstraint::apply) + .function("update", &TransformConstraint::update); + + class_>("Bone") + .constructor() + .function("getData", optional_override([](Bone &obj) { + return &obj.getData(); }), allow_raw_pointers()) + .function("getSkeleton", optional_override([](Bone &obj) { + return &obj.getSkeleton(); }), allow_raw_pointers()) + .function("getParent", optional_override([](Bone &obj) { + return obj.getParent(); }), allow_raw_pointers()) + .function("getChildren", optional_override([](Bone &obj) { + return &obj.getChildren(); }), allow_raw_pointer()) + .function("getX", &Bone::getX) + .function("setX", &Bone::setX) + .function("getY", &Bone::getY) + .function("setY", &Bone::setY) + .function("getRotation", &Bone::getRotation) + .function("setRotation", &Bone::setRotation) + .function("getScaleX", &Bone::getScaleX) + .function("setScaleX", &Bone::setScaleX) + .function("getScaleY", &Bone::getScaleY) + .function("setScaleY", &Bone::setScaleY) + .function("getShearX", &Bone::getShearX) + .function("setShearX", &Bone::setShearX) + .function("getShearY", &Bone::getShearY) + .function("setShearY", &Bone::setShearY) + .function("getAX", &Bone::getAX) + .function("setAX", &Bone::setAX) + .function("getAY", &Bone::getAY) + .function("setAY", &Bone::setAY) + .function("getARotation", &Bone::getAppliedRotation) + .function("setARotation", &Bone::setAppliedRotation) + .function("getAScaleX", &Bone::getAScaleX) + .function("setAScaleX", &Bone::setAScaleX) + .function("getAScaleY", &Bone::getAScaleY) + .function("setAScaleY", &Bone::setAScaleY) + .function("getAShearX", &Bone::getAShearX) + .function("setAShearX", &Bone::setAShearX) + .function("getAShearY", &Bone::getAShearY) + .function("setAShearY", &Bone::setAShearY) + .function("getAppliedValid", &Bone::isAppliedValid) + .function("setAppliedValid", &Bone::setAppliedValid) + .function("getA", &Bone::getA) + .function("setA", &Bone::setA) + .function("getB", &Bone::getB) + .function("setB", &Bone::setB) + .function("getC", &Bone::getC) + .function("setC", &Bone::setC) + .function("getD", &Bone::getD) + .function("setD", &Bone::setD) + .function("getWorldX", &Bone::getWorldX) + .function("setWorldX", &Bone::setWorldX) + .function("getWorldY", &Bone::getWorldY) + .function("setWorldY", &Bone::setWorldY) + .function("getActive", &Bone::isActive) + .function("setActive", &Bone::setActive) + .function("isActive", &Bone::isActive) + .function("update", &Bone::update) + .function("updateWorldTransform", select_overload(&Bone::updateWorldTransform)) + .function("updateWorldTransformWith", select_overload(&Bone::updateWorldTransform)) + .function("setToSetupPose", &Bone::setToSetupPose) + .function("getWorldRotationX", &Bone::getWorldRotationX) + .function("getWorldRotationY", &Bone::getWorldRotationY) + .function("getWorldScaleX", &Bone::getWorldScaleX) + .function("getWorldScaleY", &Bone::getWorldScaleY) + .function("worldToLocal", optional_override([](Bone &obj, Vector2 &vec2) { + float outLocalX, outLocalY; + obj.worldToLocal(vec2.x, vec2.y, outLocalX, outLocalY); + vec2.x = outLocalX; + vec2.y = outLocalY; + }), + allow_raw_pointers() + ) + .function("localToWorld", optional_override([](Bone &obj, Vector2 &vec2) { + float outWorldX, outWorldY; + obj.localToWorld(vec2.x, vec2.y, outWorldX, outWorldY); + vec2.x = outWorldX; + vec2.y = outWorldY; + }), + allow_raw_pointers() + ) + .function("worldToLocalRotation", &Bone::worldToLocalRotation) + .function("localToWorldRotation", &Bone::localToWorldRotation) + .function("rotateWorld", &Bone::rotateWorld); + + class_("BoneData") + .constructor() + .function("getIndex", &BoneData::getIndex) + .function("getName", optional_override([](BoneData &obj) { return STRING_SP2STD(obj.getName()); })) + .function("getParent", &BoneData::getParent, allow_raw_pointer()) + .function("getLength", &BoneData::getLength) + .function("setLength", &BoneData::setLength) + .function("getX", &BoneData::getX) + .function("setX", &BoneData::setX) + .function("getY", &BoneData::getY) + .function("setY", &BoneData::setY) + .function("getRotation", &BoneData::getRotation) + .function("setRotation", &BoneData::setRotation) + .function("getScaleX", &BoneData::getScaleX) + .function("setScaleX", &BoneData::setScaleX) + .function("getScaleY", &BoneData::getScaleY) + .function("setScaleY", &BoneData::setScaleY) + .function("getShearX", &BoneData::getShearX) + .function("setShearX", &BoneData::setShearX) + .function("getShearY", &BoneData::getShearY) + .function("setShearY", &BoneData::setShearY) + .function("getTransformMode", &BoneData::getTransformMode) + .function("setTransformMode", &BoneData::setTransformMode) + .function("getSkinRequired", &BoneData::isSkinRequired) + .function("setShinRequired", &BoneData::setSkinRequired); + + class_("Slot") + .constructor() + .function("getData", optional_override([](Slot &obj) { + return &obj.getData(); }), allow_raw_pointers()) + .function("getBone", optional_override([](Slot &obj) { + return &obj.getBone(); }), allow_raw_pointers()) + .function("getColor", optional_override([](Slot &obj) { + return &obj.getColor(); }), allow_raw_pointers()) + .function("getDarkColor", optional_override([](Slot &obj) { + return &obj.getDarkColor(); }), allow_raw_pointers()) + .function("getDeform", &Slot::getDeform, allow_raw_pointers()) + .function("getSkeleton", optional_override([](Slot &obj) { + return &obj.getSkeleton(); }), allow_raw_pointers()) + .function("getAttachment", &Slot::getAttachment, allow_raw_pointers()) + .function("setAttachment", &Slot::setAttachment, allow_raw_pointers()) + .function("setAttachmentTime", &Slot::setAttachmentTime) + .function("getAttachmentTime", &Slot::getAttachmentTime) + .function("setToSetupPose", &Slot::setToSetupPose); + + class_("Skin") + .constructor() + .function("getName", optional_override([](Skin &obj) { + return STRING_SP2STD(obj.getName()); })) + .function("getBones", optional_override([](Skin &obj) { + return &obj.getBones(); }), allow_raw_pointer()) + .function("getConstraints", optional_override([](Skin &obj) { + return &obj.getConstraints(); }), allow_raw_pointer()) + .function("setAttachment", optional_override([](Skin &obj, size_t index, + const std::string &name, Attachment *attachment) { + return obj.setAttachment(index, STRING_STD2SP(name), attachment); + }), allow_raw_pointers()) + .function("addSkin", select_overload(&Skin::addSkin), allow_raw_pointers()) + .function("copySkin", select_overload(&Skin::copySkin), allow_raw_pointers()) + .function("findNamesForSlot", optional_override([](Skin &obj, size_t slotIndex) { + std::vector vetNames; + std::vector entriesVector; + auto entries = obj.getAttachments(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + if (entry._slotIndex == slotIndex) vetNames.push_back(STRING_SP2STD(entry._name)); + } + return vetNames; + }), allow_raw_pointers()) + .function("getAttachment", optional_override([](Skin &obj, size_t slotIndex, + const std::string &name) { + return obj.getAttachment(slotIndex, STRING_STD2SP(name)); + }), allow_raw_pointers()) + .function("getAttachments", optional_override([](Skin &obj) { + std::vector entriesVector; + auto entries = obj.getAttachments(); + while (entries.hasNext()) { + entriesVector.push_back(&entries.next()); + } + return entriesVector; + }),allow_raw_pointers()) + .function("removeAttachment", optional_override([](Skin &obj, size_t index, + const std::string &name) { + obj.removeAttachment(index, STRING_STD2SP(name)); })) + .function("getAttachmentsForSlot", optional_override([](Skin &obj, size_t index) { + std::vector entriesVector; + auto entries = obj.getAttachments(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + if (entry._slotIndex == index) entriesVector.push_back(&entry); + } + return entriesVector; + }),allow_raw_pointers()); + + class_("SkinEntry") + .constructor() + .property("slotIndex", &Skin::AttachmentMap::Entry::_slotIndex) + .function("getName", optional_override([](Skin::AttachmentMap::Entry &obj) { return STRING_SP2STD((const String)obj._name); })) + .function("getAttachment", optional_override([](Skin::AttachmentMap::Entry &obj) { return obj._attachment; }), allow_raw_pointers()); + + class_("SkeletonClipping") + .constructor<>() + .function("getClippedVertices", &SkeletonClipping::getClippedVertices) + .function("getClippedTriangles", &SkeletonClipping::getClippedTriangles) + .function("getClippedUVs", &SkeletonClipping::getClippedUVs) + .function("clipStart", &SkeletonClipping::clipStart, allow_raw_pointers()) + .function("clipEndWithSlot", select_overload(&SkeletonClipping::clipEnd)) + .function("clipEnd", select_overload(&SkeletonClipping::clipEnd)) + .function("isClipping", &SkeletonClipping::isClipping); + + class_("SkeletonData") + .constructor<>() + .function("getName", optional_override([](SkeletonData &obj) { + return STRING_SP2STD(obj.getName()); })) + .function("setName", &SkeletonData::setName) + .function("getBones", optional_override([](SkeletonData &obj) { + return &obj.getBones(); }), allow_raw_pointer()) + .function("getSlots", optional_override([](SkeletonData &obj) { + return &obj.getSlots(); }), allow_raw_pointer()) + .function("getSkins", optional_override([](SkeletonData &obj) { + return &obj.getSkins(); }), allow_raw_pointer()) + .function("getDefaultSkin", &SkeletonData::getDefaultSkin, allow_raw_pointers()) + .function("setDefaultSkin", &SkeletonData::setDefaultSkin, allow_raw_pointers()) + .function("getEvents", optional_override([](SkeletonData &obj) { + return &obj.getEvents(); }), allow_raw_pointer()) + .function("getAnimations", optional_override([](SkeletonData &obj) { + return &obj.getAnimations(); }), allow_raw_pointer()) + .function("getIkConstraints", optional_override([](SkeletonData &obj) { + return &obj.getIkConstraints(); }), allow_raw_pointer()) + .function("getTransformConstraints", optional_override([](SkeletonData &obj) { + return &obj.getTransformConstraints(); }), allow_raw_pointer()) + .function("getPathConstraints", optional_override([](SkeletonData &obj) { + return &obj.getPathConstraints(); }), allow_raw_pointer()) + .function("getX", &SkeletonData::getX) + .function("setX", &SkeletonData::setX) + .function("getY", &SkeletonData::getY) + .function("setY", &SkeletonData::setY) + .function("getWidth", &SkeletonData::getWidth) + .function("setWidth", &SkeletonData::setWidth) + .function("getHeight", &SkeletonData::getHeight) + .function("setHeight", &SkeletonData::setHeight) + .function("getVersion", optional_override([](SkeletonData &obj) { + return STRING_SP2STD(obj.getVersion()); })) + .function("setVersion", &SkeletonData::setVersion) + .function("getHash", optional_override([](SkeletonData &obj) { + return STRING_SP2STD(obj.getHash()); })) + .function("setHash", &SkeletonData::setHash) + .function("getFps", &SkeletonData::getFps) + .function("setFps", &SkeletonData::setFps) + .function("getImagesPath", optional_override([](SkeletonData &obj) { + return STRING_SP2STD(obj.getImagesPath()); })) + .function("setImagesPath", &SkeletonData::setImagesPath) + .function("getAudioPath", optional_override([](SkeletonData &obj) { + return STRING_SP2STD(obj.getAudioPath()); })) + .function("setAudioPath", &SkeletonData::setAudioPath) + .function("findBone", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findBone(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("findBoneIndex", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findBoneIndex(STRING_STD2SP(name)); })) + .function("findSlot", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findSlot(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("findSlotIndex", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findSlotIndex(STRING_STD2SP(name)); })) + .function("findSkin", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findSkin(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("findEvent", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findEvent(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("findAnimation", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findAnimation(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("findIkConstraint", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findIkConstraint(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("findTransformConstraint", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findTransformConstraint(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("findPathConstraint", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findPathConstraint(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("findPathConstraintIndex", optional_override([](SkeletonData &obj, const std::string &name) { + return obj.findPathConstraintIndex(STRING_STD2SP(name)); })); + + class_("Animation") + .constructor &, float>() + .function("apply", optional_override([](Animation &obj, Skeleton &skeleton, + float lastTime, float time, bool loop, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, loop, &pEvents, alpha, blend, direction); + })) + .function("getName", optional_override([](Animation &obj) { return STRING_SP2STD(obj.getName()); })) + .function("getTimelines", optional_override([](Animation &obj) { + return &obj.getTimelines(); }), allow_raw_pointer()) + .function("hasTimeline", &Animation::hasTimeline) + .function("getDuration", &Animation::getDuration) + .function("setDuration", &Animation::setDuration); + + class_("Timeline") + .function("apply", optional_override([](Timeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), pure_virtual()) + .function("getPropertyId", &Timeline::getPropertyId, pure_virtual()); + + class_>("CurveTimeline") + .function("apply", optional_override([](CurveTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), pure_virtual()) + .function("getPropertyId", &CurveTimeline::getPropertyId, pure_virtual()) + .function("getFrameCount", &CurveTimeline::getFrameCount) + .function("setLinear", &CurveTimeline::setLinear) + .function("setStepped", &CurveTimeline::setStepped) + .function("setCurve", &CurveTimeline::setCurve) + .function("getCurvePercent", &CurveTimeline::getCurvePercent) + .function("getCurveType", &CurveTimeline::getCurveType); + + class_>("TranslateTimeline") + .constructor() + .class_property("ENTRIES", &TranslateTimeline::ENTRIES) + .function("getPropertyId", &TranslateTimeline::getPropertyId) + .function("setFrame", &TranslateTimeline::setFrame) + .function("apply", optional_override([](TranslateTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("ScaleTimeline") + .constructor() + .function("getPropertyId", &ScaleTimeline::getPropertyId) + .function("apply", optional_override([](ScaleTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("ShearTimeline") + .constructor() + .function("getPropertyId", &ShearTimeline::getPropertyId) + .function("apply", optional_override([](ShearTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("RotateTimeline") + .constructor() + //.class_property("ENTRIES", &RotateTimeline::ENTRIES) not bind + .function("getBoneIndex", &RotateTimeline::getBoneIndex) + .function("setBoneIndex", &RotateTimeline::setBoneIndex) + .function("getFrames", optional_override([](RotateTimeline &obj) { + return &obj.getFrames(); }), allow_raw_pointer()) + .function("getPropertyId", &RotateTimeline::getPropertyId) + .function("setFrame", &RotateTimeline::setFrame) + .function("apply", optional_override([](RotateTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("ColorTimeline") + .constructor() + .class_property("ENTRIES", &ColorTimeline::ENTRIES) + .function("getSlotIndex", &ColorTimeline::getSlotIndex) + .function("setSlotIndex", &ColorTimeline::setSlotIndex) + .function("getFrames", optional_override([](ColorTimeline &obj) { + return &obj.getFrames(); }), allow_raw_pointer()) + .function("getPropertyId", &ColorTimeline::getPropertyId) + .function("setFrame", &ColorTimeline::setFrame) + .function("apply", optional_override([](ColorTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("TwoColorTimeline") + .constructor() + .class_property("ENTRIES", &ColorTimeline::ENTRIES) + .function("getSlotIndex", &TwoColorTimeline::getSlotIndex) + .function("setSlotIndex", &TwoColorTimeline::setSlotIndex) + .function("getPropertyId", &TwoColorTimeline::getPropertyId) + .function("setFrame", &TwoColorTimeline::setFrame) + .function("apply", optional_override([](TwoColorTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("AttachmentTimeline") + .constructor() + .function("getSlotIndex", &AttachmentTimeline::getSlotIndex) + .function("setSlotIndex", &AttachmentTimeline::setSlotIndex) + .function("getFrames", optional_override([](AttachmentTimeline &obj) { + return &obj.getFrames(); }), allow_raw_pointer()) + .function("getAttachmentNames",optional_override([](AttachmentTimeline &obj) { + Vector attachmentNames = obj.getAttachmentNames(); + return VECTOR_SP2STD_STRING(attachmentNames); }), allow_raw_pointers()) + .function("getPropertyId", &AttachmentTimeline::getPropertyId) + .function("getFrameCount", &AttachmentTimeline::getFrameCount) + .function("setFrame", optional_override([](AttachmentTimeline &obj, int frameIndex, float time, const std::string &attachmentName){ + const String attachmentNameSP = STRING_STD2SP(attachmentName); + obj.setFrame(frameIndex, time, attachmentNameSP); + }), allow_raw_pointers()) + .function("apply", optional_override([](AttachmentTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("DeformTimeline") + .constructor() + .function("getSlotIndex", &DeformTimeline::getSlotIndex) + .function("setSlotIndex", &DeformTimeline::setSlotIndex) + .function("getAttachment", &DeformTimeline::getAttachment, allow_raw_pointers()) + .function("setAttachment", &DeformTimeline::setAttachment, allow_raw_pointers()) + .function("getFrames", optional_override([](DeformTimeline &obj) { + return &obj.getFrames(); }), allow_raw_pointer()) + .function("getFrameVertices", optional_override([](DeformTimeline &obj) { + return &obj.getVertices(); }), allow_raw_pointer()) + .function("getPropertyId", &DeformTimeline::getPropertyId) + .function("setFrame", optional_override([](DeformTimeline &obj, int frameIndex, float time, std::vector &vertices){ + Vector sp_vertices = VECTOR_STD2SP(vertices); + obj.setFrame(frameIndex, time, sp_vertices); + }), allow_raw_pointers()) + .function("apply", optional_override([](DeformTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("EventTimeline") + .constructor() + .function("getFrames", optional_override([](EventTimeline &obj) { + return &obj.getFrames(); }), allow_raw_pointer()) + .function("getEvents", optional_override([](EventTimeline &obj) { + return &obj.getEvents(); }), allow_raw_pointer()) + .function("getPropertyId", &EventTimeline::getPropertyId) + .function("getFrameCount", &EventTimeline::getFrameCount) + .function("setFrame", &EventTimeline::setFrame, allow_raw_pointers()) + .function("apply", optional_override([](EventTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("DrawOrderTimeline") + .constructor() + .function("getFrames", optional_override([](DrawOrderTimeline &obj) { + return &obj.getFrames(); }), allow_raw_pointer()) + .function("getPropertyId", &DrawOrderTimeline::getPropertyId) + .function("getFrameCount", &DrawOrderTimeline::getFrameCount) + .function("getDrawOrders", optional_override([](DrawOrderTimeline &obj) { + return &obj.getDrawOrders(); }), allow_raw_pointer()) + .function("setFrame", &DrawOrderTimeline::setFrame, allow_raw_pointers()) + .function("apply", optional_override([](DrawOrderTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("IkConstraintTimeline") + .constructor() + .class_property("ENTRIES", &IkConstraintTimeline::ENTRIES) + .function("getPropertyId", &IkConstraintTimeline::getPropertyId) + .function("setFrame", &IkConstraintTimeline::setFrame) + .function("apply", optional_override([](IkConstraintTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("TransformConstraintTimeline") + .constructor() + .class_property("ENTRIES", &TransformConstraintTimeline::ENTRIES) + .function("getPropertyId", &TransformConstraintTimeline::getPropertyId) + .function("setFrame", &TransformConstraintTimeline::setFrame) + .function("apply", optional_override([](TransformConstraintTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("PathConstraintPositionTimeline") + .constructor() + .class_property("ENTRIES", &TransformConstraintTimeline::ENTRIES) + .function("getPropertyId", &PathConstraintPositionTimeline::getPropertyId) + .function("setFrame", &PathConstraintPositionTimeline::setFrame) + .function("apply", optional_override([](PathConstraintPositionTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_>("PathConstraintMixTimeline") + .constructor() + .class_property("ENTRIES", &PathConstraintMixTimeline::ENTRIES) + .function("getPropertyId", &PathConstraintMixTimeline::getPropertyId) + .function("apply", optional_override([](PathConstraintMixTimeline &obj, Skeleton &skeleton, + float lastTime, float time, std::vector &stdPEvents, float alpha, + MixBlend blend, MixDirection direction) { + auto pEvents = VECTOR_STD2SP_POINTER(stdPEvents); + obj.apply(skeleton, lastTime, time, &pEvents, alpha, blend, direction); + }), allow_raw_pointers()); + + class_("TrackEntry") + .constructor<>() + .function("getAnimation", &TrackEntry::getAnimation, allow_raw_pointer()) + .function("getNext", &TrackEntry::getNext, allow_raw_pointer()) + .function("getMixingFrom", &TrackEntry::getMixingFrom, allow_raw_pointer()) + .function("getMixingTo", &TrackEntry::getMixingTo, allow_raw_pointer()) + //.function("getProp_listener", &TrackEntry::listener) + .function("getTrackIndex", &TrackEntry::getTrackIndex) + .function("getLoop", &TrackEntry::getLoop) + .function("setLoop", &TrackEntry::setLoop) + .function("getHoldPrevious", &TrackEntry::getHoldPrevious) + .function("setHoldPrevious", &TrackEntry::setHoldPrevious) + .function("getEventThreshold", &TrackEntry::getEventThreshold) + .function("setEventThreshold", &TrackEntry::setEventThreshold) + .function("getAttachmentThreshold", &TrackEntry::getAttachmentThreshold) + .function("setAttachmentThreshold", &TrackEntry::setAttachmentThreshold) + .function("getDrawOrderThreshold", &TrackEntry::getDrawOrderThreshold) + .function("setDrawOrderThreshold", &TrackEntry::setDrawOrderThreshold) + .function("getAnimationStart", &TrackEntry::getAnimationStart) + .function("setAnimationStart", &TrackEntry::setAnimationStart) + .function("getAnimationEnd", &TrackEntry::getAnimationEnd) + .function("setAnimationEnd", &TrackEntry::setAnimationEnd) + .function("getAnimationLast", &TrackEntry::getAnimationLast) + .function("setAnimationLast", &TrackEntry::setAnimationLast) + //.function("getProp_nextAnimationLast", &TrackEntry::nextAnimationLast) + .function("getDelay", &TrackEntry::getDelay) + .function("setDelay", &TrackEntry::setDelay) + .function("getTrackTime", &TrackEntry::getTrackTime) + .function("setTrackTime", &TrackEntry::setTrackTime) + //.function("getProp_trackLast", &TrackEntry::trackLast) + //.function("getProp_nextTrackLast", &TrackEntry::nextTrackLast) + .function("getTrackEnd", &TrackEntry::getTrackEnd) + .function("setTrackEnd", &TrackEntry::setTrackEnd) + .function("getTimeScale", &TrackEntry::getTimeScale) + .function("setTimeScale", &TrackEntry::setTimeScale) + .function("getAlpha", &TrackEntry::getAlpha) + .function("setAlpha", &TrackEntry::setAlpha) + .function("getMixTime", &TrackEntry::getMixTime) + .function("setMixTime", &TrackEntry::setMixTime) + .function("getMixDuration", &TrackEntry::getMixDuration) + .function("setMixDuration", &TrackEntry::setMixDuration) + //.function("getProp_interruptAlpha", &TrackEntry::_interruptAlpha) + //.function("getProp_totalAlpha", &TrackEntry::getAlpha) + .function("getMixBlend", &TrackEntry::getMixBlend) + .function("setMixBlend", &TrackEntry::setMixBlend) + //.function("getProp_timelineMode", &TrackEntry::timelineMode) + //.function("getProp_timelineHoldMix", &TrackEntry::timelineHoldMix) + //.function("getProp_timelinesRotation", &TrackEntry::timelinesRotation) + //.function("reset", &TrackEntry::reset) //private + .function("getAnimationTime", &TrackEntry::getAnimationTime) + .function("isComplete", &TrackEntry::isComplete) + .function("resetRotationDirections", &TrackEntry::resetRotationDirections); + + class_("AnimationStateData") + .constructor() + .function("getDefaultMix", &AnimationStateData::getDefaultMix) + .function("setDefaultMix", &AnimationStateData::setDefaultMix) + .function("getSkeletonData", &AnimationStateData::getSkeletonData, allow_raw_pointers()) + .function("setMix", optional_override([](AnimationStateData &obj, const std::string& fromName, const std::string& toName, float duration) { + return obj.setMix(STRING_STD2SP(fromName), STRING_STD2SP(toName), duration);})) + .function("setMixWith", optional_override([](AnimationStateData &obj, Animation* from, Animation* to, float duration) { + return obj.setMix(from, to, duration);}), allow_raw_pointers()) + .function("getMix", &AnimationStateData::getMix, allow_raw_pointers()); + + class_("AnimationState") + .constructor() + .function("getData", &AnimationState::getData, allow_raw_pointers()) + .function("getTracks", optional_override([](AnimationState &obj) { + return &obj.getTracks(); }), allow_raw_pointer()) + .function("getTimeScale", &AnimationState::getTimeScale) + .function("setTimeScale", &AnimationState::setTimeScale) + .function("update", &AnimationState::update) + .function("apply", &AnimationState::apply) + .function("clearTracks", &AnimationState::clearTracks) + .function("clearTrack", &AnimationState::clearTrack) + .function("setAnimation", optional_override([](AnimationState &obj, uint32_t trackIndex, const std::string &animName, bool loop) { return obj.setAnimation(trackIndex, STRING_STD2SP(animName), loop); }), allow_raw_pointers()) + .function("setAnimationWith", optional_override([](AnimationState &obj, uint32_t trackIndex, Animation *animation, bool loop) { return obj.setAnimation(trackIndex, animation, loop); }), allow_raw_pointers()) + .function("addAnimation", optional_override([](AnimationState &obj, uint32_t trackIndex, const std::string &animName, bool loop, float delay) { return obj.addAnimation(trackIndex, STRING_STD2SP(animName), loop, delay); }), allow_raw_pointers()) + .function("addAnimationWith", optional_override([](AnimationState &obj, uint32_t trackIndex, Animation *animation, bool loop, float delay) { return obj.addAnimation(trackIndex, animation, loop, delay); }), allow_raw_pointers()) + .function("setEmptyAnimation", &AnimationState::setEmptyAnimation, allow_raw_pointers()) + .function("addEmptyAnimation", &AnimationState::addEmptyAnimation, allow_raw_pointers()) + .function("setEmptyAnimations", &AnimationState::setEmptyAnimations) + .function("getCurrent", &AnimationState::getCurrent, allow_raw_pointer()) + .function("setListener", optional_override([](AnimationState &obj, AnimationStateListener inValue) { + obj.setListener(inValue); }),allow_raw_pointers()) + .function("setListenerObject", optional_override([](AnimationState &obj, AnimationStateListenerObject *inValue) { + obj.setListener(inValue); }),allow_raw_pointers()) + .function("disableQueue", &AnimationState::disableQueue) + .function("enableQueue", &AnimationState::enableQueue); + //.function("addListener", &AnimationState::addListener) + //.function("removeListener", &AnimationState::removeListener) + //.function("clearListeners", &AnimationState::clearListeners) // no have clearListeners + + //private + // class_("EventQueue") + // .constructor& >() + // .function("start", &EventQueue::start, allow_raw_pointers()) + // .function("interrupt", &EventQueue::interrupt, allow_raw_pointers()) + // .function("end", &EventQueue::end, allow_raw_pointers()) + // .function("dispose", &EventQueue::dispose, allow_raw_pointers()) + // .function("complete", &EventQueue::complete, allow_raw_pointers()) + // .function("event", &EventQueue::event, allow_raw_pointers()) + // .function("drain", &EventQueue::drain) + // .function("clear"); + + //class_("AnimationStateListener") + + //class_("AnimationStateListenerObject") + // .constructor<>() + // .function("callback", &AnimationStateListenerObject::callback, pure_virtual()); + + //class_("AnimationStateAdapter") + + class_("Skeleton") + .constructor() + .function("getData", &Skeleton::getData, allow_raw_pointer()) + .function("getBones", optional_override([](Skeleton &obj){ + return &obj.getBones(); }), allow_raw_pointer()) + .function("getSlots", optional_override([](Skeleton &obj){ + return &obj.getSlots(); }), allow_raw_pointer()) + .function("getDrawOrder", optional_override([](Skeleton &obj){ + return &obj.getDrawOrder(); }), allow_raw_pointer()) + .function("getIkConstraints", optional_override([](Skeleton &obj){ + return &obj.getIkConstraints(); }), allow_raw_pointer()) + .function("getTransformConstraints", optional_override([](Skeleton &obj){ + return &obj.getTransformConstraints(); }), allow_raw_pointer()) + .function("getPathConstraints", optional_override([](Skeleton &obj){ + return &obj.getPathConstraints(); }), allow_raw_pointer()) + .function("getUpdateCacheList", optional_override([](Skeleton &obj){ + return &obj.getUpdateCacheList(); }), allow_raw_pointer()) + .function("getSkin", &Skeleton::getSkin, allow_raw_pointer()) + .function("getColor", optional_override([](Skeleton &obj){ + return &obj.getColor(); }), allow_raw_pointers()) + .function("getTime", &Skeleton::getTime) + .function("setTime", &Skeleton::setTime) + .function("getScaleX", &Skeleton::getScaleX) + .function("setScaleX", &Skeleton::setScaleX) + .function("getScaleY", &Skeleton::getScaleY) + .function("setScaleY", &Skeleton::setScaleY) + .function("getX", &Skeleton::getX) + .function("setX", &Skeleton::setX) + .function("getY", &Skeleton::getY) + .function("setY", &Skeleton::setY) + .function("updateCache", &Skeleton::updateCache) + .function("updateWorldTransform", &Skeleton::updateWorldTransform) + .function("setToSetupPose", &Skeleton::setToSetupPose) + .function("setBonesToSetupPose", &Skeleton::setBonesToSetupPose) + .function("setSlotsToSetupPose", &Skeleton::setSlotsToSetupPose) + .function("getRootBone", &Skeleton::getRootBone, allow_raw_pointer()) + .function("findBone", optional_override([](Skeleton &obj, const std::string& name) { + return obj.findBone(STRING_STD2SP(name));}), allow_raw_pointers()) + .function("findBoneIndex", optional_override([](Skeleton &obj, const std::string& name) { + return obj.findBoneIndex(STRING_STD2SP(name));})) + .function("findSlot", optional_override([](Skeleton &obj, const std::string& name) { + return obj.findSlot(STRING_STD2SP(name));}), allow_raw_pointers()) + .function("findSlotIndex", optional_override([](Skeleton &obj, const std::string& name) { + return obj.findSlotIndex(STRING_STD2SP(name));})) + .function("setSkinByName", optional_override([](Skeleton &obj, const std::string& name) { + return obj.setSkin(STRING_STD2SP(name));})) + .function("setSkin", static_cast(&Skeleton::setSkin), allow_raw_pointer()) + .function("getAttachmentByName", optional_override([](Skeleton &obj, const std::string& slotName, const std::string& attachmentName) { + return obj.getAttachment(STRING_STD2SP(slotName), STRING_STD2SP(attachmentName));}), allow_raw_pointers()) + .function("getAttachment", optional_override([](Skeleton &obj, int slotIndex, const std::string& attachmentName) { + return obj.getAttachment(slotIndex, STRING_STD2SP(attachmentName));}),allow_raw_pointers()) + .function("setAttachment", optional_override([](Skeleton &obj, const std::string& slotName, const std::string& attachmentName) { + return obj.setAttachment(STRING_STD2SP(slotName), STRING_STD2SP(attachmentName));})) + .function("findIkConstraint", optional_override([](Skeleton &obj, const std::string &name) { return obj.findIkConstraint(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("findTransformConstraint", optional_override([](Skeleton &obj, const std::string &name) { return obj.findTransformConstraint(STRING_STD2SP(name)); }), allow_raw_pointers()) + .function("findPathConstraint", optional_override([](Skeleton &obj, const std::string &name) { return obj.findPathConstraint(STRING_STD2SP(name)); }), allow_raw_pointers()) + //.function("getBounds", optional_override([](Skeleton &obj, &outX, ) {}), allow_raw_pointers()) + .function("update", &Skeleton::update); + + //incomplete + // class_("SkeletonBinary") + // .constructor() + // .constructor() + // .function("setScale", &SkeletonBinary::setScale) + // .function("getError", &SkeletonBinary::getError); + //.function("readSkeletonDataFile", optional_override([](SkeletonBinary &obj, const spine::String& path) { return obj.readSkeletonDataFile(path); })); + + // incomplete + //class_("SkeletonJson") + //.constructor() + //.constructor() + //.function("setScale", &SkeletonJson::setScale); + //.function("getError", &SkeletonJson::getError); + + class_("VertexEffect") + .function("begin", &VertexEffect::begin, pure_virtual()) + .function("transform", optional_override([](VertexEffect &obj, float x, float y) { + obj.transform(x, y); }), pure_virtual()) + .function("end", &VertexEffect::end, pure_virtual()); + + class_>("JitterEffect") + .constructor() + .function("getJitterX", &JitterVertexEffect::getJitterX) + .function("setJitterX", &JitterVertexEffect::setJitterX) + .function("getJitterY", &JitterVertexEffect::getJitterY) + .function("setJitterY", &JitterVertexEffect::setJitterY) + .function("begin", &JitterVertexEffect::begin) + .function("transform", optional_override([](VertexEffect &obj, float x, float y) { + obj.transform(x, y); }), pure_virtual()) + .function("end", &JitterVertexEffect::end); + + class_>("SwirlEffect") + .constructor() + .function("begin", &SwirlVertexEffect::begin) + .function("transform", optional_override([](VertexEffect &obj, float x, float y) { + obj.transform(x, y); }), pure_virtual()) + .function("end", &SwirlVertexEffect::end) + .function("getCenterX", &SwirlVertexEffect::getCenterX) + .function("setCenterX", &SwirlVertexEffect::setCenterX) + .function("getCenterY", &SwirlVertexEffect::getCenterY) + .function("setCenterY", &SwirlVertexEffect::setCenterY) + .function("getRadius", &SwirlVertexEffect::getRadius) + .function("setRadius", &SwirlVertexEffect::setRadius) + .function("getAngle", &SwirlVertexEffect::getAngle) + .function("setAngle", &SwirlVertexEffect::setAngle) + .function("getWorldX", &SwirlVertexEffect::getWorldX) + .function("setWorldX", &SwirlVertexEffect::setWorldX) + .function("getWorldY", &SwirlVertexEffect::getWorldY) + .function("setWorldY", &SwirlVertexEffect::setWorldY); + + class_("SlotMesh") + .property("vCount", &SlotMesh::vCount) + .property("iCount", &SlotMesh::iCount) + .property("blendMode", &SlotMesh::blendMode) + .property("textureID", &SlotMesh::textureID); + + register_vector("VectorSlotMesh"); + class_("SpineModel") + .property("vCount", &SpineModel::vCount) + .property("iCount", &SpineModel::iCount) + .property("vPtr", &SpineModel::vPtr) + .property("iPtr", &SpineModel::iPtr) + .function("getData", &SpineModel::getData, allow_raw_pointer>()); + + class_("SpineDebugShape") + .property("type", &SpineDebugShape::type) + .property("vOffset", &SpineDebugShape::vOffset) + .property("vCount", &SpineDebugShape::vCount) + .property("iOffset", &SpineDebugShape::iOffset) + .property("iCount", &SpineDebugShape::iCount); + + register_vector("VectorDebugShape"); + class_("SkeletonInstance") + .constructor<>() + .property("isCache", &SpineSkeletonInstance::isCache) + .property("dtRate", &SpineSkeletonInstance::dtRate) + .property("enable", &SpineSkeletonInstance::enable) + .function("initSkeleton", &SpineSkeletonInstance::initSkeleton, allow_raw_pointers()) + .function("setAnimation", &SpineSkeletonInstance::setAnimation, allow_raw_pointers()) + .function("setSkin", &SpineSkeletonInstance::setSkin) + .function("updateAnimation", &SpineSkeletonInstance::updateAnimation) + .function("updateRenderData", &SpineSkeletonInstance::updateRenderData, allow_raw_pointer()) + .function("setPremultipliedAlpha", &SpineSkeletonInstance::setPremultipliedAlpha) + .function("setUseTint", &SpineSkeletonInstance::setUseTint) + .function("setColor", &SpineSkeletonInstance::setColor) + .function("setJitterEffect", &SpineSkeletonInstance::setJitterEffect, allow_raw_pointer()) + .function("setSwirlEffect", &SpineSkeletonInstance::setSwirlEffect, allow_raw_pointer()) + .function("clearEffect", &SpineSkeletonInstance::clearEffect) + .function("getAnimationState", &SpineSkeletonInstance::getAnimationState, allow_raw_pointer()) + .function("setMix", &SpineSkeletonInstance::setMix) + .function("setListener", &SpineSkeletonInstance::setListener) + .function("setTrackEntryListener", &SpineSkeletonInstance::setTrackEntryListener, allow_raw_pointer()) + .function("setDebugMode", &SpineSkeletonInstance::setDebugMode) + .function("getDebugShapes", &SpineSkeletonInstance::getDebugShapes) + .function("resizeSlotRegion", &SpineSkeletonInstance::resizeSlotRegion) + .function("destroy", &SpineSkeletonInstance::destroy) + .function("setSlotTexture", &SpineSkeletonInstance::setSlotTexture); +} + +EMSCRIPTEN_BINDINGS(cocos_spine) { + class_("SpineWasmUtil") + .class_function("spineWasmInit", &SpineWasmUtil::spineWasmInit) + .class_function("spineWasmDestroy", &SpineWasmUtil::spineWasmDestroy) + .class_function("queryStoreMemory", &SpineWasmUtil::queryStoreMemory) + .class_function("querySpineSkeletonDataByUUID", &SpineWasmUtil::querySpineSkeletonDataByUUID, allow_raw_pointers()) + .class_function("createSpineSkeletonDataWithJson", &SpineWasmUtil::createSpineSkeletonDataWithJson, allow_raw_pointers()) + .class_function("createSpineSkeletonDataWithBinary", &SpineWasmUtil::createSpineSkeletonDataWithBinary, allow_raw_pointers()) + .class_function("registerSpineSkeletonDataWithUUID", &SpineWasmUtil::registerSpineSkeletonDataWithUUID, allow_raw_pointers()) + .class_function("destroySpineSkeletonDataWithUUID", &SpineWasmUtil::destroySpineSkeletonDataWithUUID) + .class_function("destroySpineSkeleton", &SpineWasmUtil::destroySpineSkeleton, allow_raw_pointers()) + .class_function("getCurrentListenerID", &SpineWasmUtil::getCurrentListenerID) + .class_function("getCurrentEventType", &SpineWasmUtil::getCurrentEventType) + .class_function("getCurrentTrackEntry", &SpineWasmUtil::getCurrentTrackEntry, allow_raw_pointers()) + .class_function("getCurrentEvent", &SpineWasmUtil::getCurrentEvent, allow_raw_pointers()); +} diff --git a/cocos/editor-support/spine-wasm/spine-wasm.cpp b/cocos/editor-support/spine-wasm/spine-wasm.cpp new file mode 100644 index 0000000..4fa26f2 --- /dev/null +++ b/cocos/editor-support/spine-wasm/spine-wasm.cpp @@ -0,0 +1,127 @@ +#include "spine-wasm.h" +#include +#include "AtlasAttachmentLoaderExtension.h" +#include "spine-mesh-data.h" +#include "util-function.h" +#include "wasmSpineExtension.h" + +std::map skeletonDataMap{}; + +uint32_t SpineWasmUtil::s_listenerID = 0; +EventType SpineWasmUtil::s_currentType = EventType_Event; +TrackEntry* SpineWasmUtil::s_currentEntry = nullptr; +Event* SpineWasmUtil::s_currentEvent = nullptr; +uint8_t* SpineWasmUtil::s_mem = nullptr; +uint32_t SpineWasmUtil::s_memSize = 0; + +void SpineWasmUtil::spineWasmInit() { + LogUtil::Initialize(); + spine::SpineExtension* tension = new WasmSpineExtension(); + spine::SpineExtension::setInstance(tension); + + SpineMeshData::initMeshMemory(); + + //LogUtil::PrintToJs("spineWasmInit"); +} + +void SpineWasmUtil::spineWasmDestroy() { + auto* extension = spine::SpineExtension::getInstance(); + delete extension; + freeStoreMemory(); + SpineMeshData::releaseMeshMemory(); + LogUtil::ReleaseBuffer(); +} + +SkeletonData* SpineWasmUtil::querySpineSkeletonDataByUUID(const std::string& uuid) { + auto iter = skeletonDataMap.find(uuid); + if (iter == skeletonDataMap.end()) { + return nullptr; + } + SkeletonData* ptrVal = iter->second; + return ptrVal; +} + +SkeletonData* SpineWasmUtil::createSpineSkeletonDataWithJson(const std::string& jsonStr, const std::string& altasStr) { + auto* atlas = new Atlas(altasStr.c_str(), altasStr.size(), "", nullptr, false); + if (!atlas) { + return nullptr; + } + AttachmentLoader* attachmentLoader = new AtlasAttachmentLoaderExtension(atlas); + spine::SkeletonJson json(attachmentLoader); + json.setScale(1.0F); + SkeletonData* skeletonData = json.readSkeletonData(jsonStr.c_str()); + + return skeletonData; +} + +SkeletonData* SpineWasmUtil::createSpineSkeletonDataWithBinary(uint32_t byteSize, const std::string& altasStr) { + auto* atlas = new Atlas(altasStr.c_str(), altasStr.size(), "", nullptr, false); + if (!atlas) { + return nullptr; + } + AttachmentLoader* attachmentLoader = new AtlasAttachmentLoaderExtension(atlas); + spine::SkeletonBinary binary(attachmentLoader); + binary.setScale(1.0F); + SkeletonData* skeletonData = binary.readSkeletonData(s_mem, byteSize); + return skeletonData; +} + +void SpineWasmUtil::registerSpineSkeletonDataWithUUID(SkeletonData* data, const std::string& uuid) { + auto iter = skeletonDataMap.find(uuid); + if (iter == skeletonDataMap.end()) { + skeletonDataMap[uuid] = data; + } +} + +void SpineWasmUtil::destroySpineSkeletonDataWithUUID(const std::string& uuid) { + auto iter = skeletonDataMap.find(uuid); + if (iter != skeletonDataMap.end()) { + auto* data = skeletonDataMap[uuid]; + delete data; + skeletonDataMap.erase(iter); + } +} + +void SpineWasmUtil::destroySpineSkeleton(Skeleton* skeleton) { + if (skeleton) { + delete skeleton; + } +} + +uint32_t SpineWasmUtil::queryStoreMemory(uint32_t size) { + if (s_mem) { + if (s_memSize < size) { + delete[] s_mem; + s_mem = new uint8_t[size]; + s_memSize = size; + } + } else { + s_mem = new uint8_t[size]; + s_memSize = size; + } + return (uint32_t)s_mem; +} + +void SpineWasmUtil::freeStoreMemory() { + if (s_mem) { + delete[] s_mem; + s_mem = nullptr; + } + s_memSize = 0; +} + +uint32_t SpineWasmUtil::getCurrentListenerID() { + return s_listenerID; +} + +EventType SpineWasmUtil::getCurrentEventType() { + return s_currentType; +} + +TrackEntry* SpineWasmUtil::getCurrentTrackEntry() { + return s_currentEntry; +} + +Event* SpineWasmUtil::getCurrentEvent() { + return s_currentEvent; +} \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/spine-wasm.h b/cocos/editor-support/spine-wasm/spine-wasm.h new file mode 100644 index 0000000..993e2c9 --- /dev/null +++ b/cocos/editor-support/spine-wasm/spine-wasm.h @@ -0,0 +1,37 @@ + +#ifndef _SPINE_WASM_H_ +#define _SPINE_WASM_H_ +#include +#include +#include "spine-skeleton-instance.h" +using namespace spine; + +class SpineWasmUtil { +public: + static void spineWasmInit(); + static void spineWasmDestroy(); + static uint32_t queryStoreMemory(uint32_t size); + static void freeStoreMemory(); + + static SkeletonData* querySpineSkeletonDataByUUID(const std::string& uuid); + static SkeletonData* createSpineSkeletonDataWithJson(const std::string& jsonStr, const std::string& altasStr); + static SkeletonData* createSpineSkeletonDataWithBinary(uint32_t byteSize, const std::string& altasStr); + static void registerSpineSkeletonDataWithUUID(SkeletonData* data, const std::string& uuid); + static void destroySpineSkeletonDataWithUUID(const std::string& uuid); + static void destroySpineSkeleton(Skeleton* skeleton); + + static uint32_t getCurrentListenerID(); + static EventType getCurrentEventType(); + static TrackEntry* getCurrentTrackEntry(); + static Event* getCurrentEvent(); + + static uint32_t s_listenerID; + static EventType s_currentType; + static TrackEntry* s_currentEntry; + static Event* s_currentEvent; + + static uint8_t* s_mem; + static uint32_t s_memSize; +}; + +#endif diff --git a/cocos/editor-support/spine-wasm/util-function.cpp b/cocos/editor-support/spine-wasm/util-function.cpp new file mode 100644 index 0000000..b3a4bed --- /dev/null +++ b/cocos/editor-support/spine-wasm/util-function.cpp @@ -0,0 +1,67 @@ +#include "util-function.h" +#include +// extern "C" { +// extern void consoleInfo(char* ptr, uint32_t length); +// } +static char* logBuffer = nullptr; +const int LOG_LENGTH = 1024; + +void LogUtil::Initialize() { + //logBuffer = new char[LOG_LENGTH]; +} + +void LogUtil::PrintToJs(std::string& message) { + // int length = message.length(); + // if (length >= LOG_LENGTH) length = LOG_LENGTH -1; + // memcpy(logBuffer, message.c_str(), length); + // logBuffer[length] = 0; + // consoleInfo(logBuffer, length); +} + +void LogUtil::PrintToJs(const char* message) { + // std::string strMessage(message); + // int length = strMessage.length(); + // if (length >= LOG_LENGTH) length = LOG_LENGTH - 1; + // memcpy(logBuffer, strMessage.c_str(), length); + // logBuffer[length] = 0; + // consoleInfo(logBuffer, length); +} + +void LogUtil::PrintToJs(char* str, int length) { + // if (length >= LOG_LENGTH) length = LOG_LENGTH - 1; + // memcpy(logBuffer, str, length); + // logBuffer[length] = 0; + // consoleInfo(logBuffer, length); +} + +void LogUtil::PrintIntValue(int value, const char* message) { + // std::string strInt = std::to_string(value); + // std::string finalStr = std::string(message) + strInt; + // LogUtil::PrintToJs(finalStr); +} + +void LogUtil::ReleaseBuffer() { + //delete[] logBuffer; +} + +// const uint32_t MEMORY_SIZE = 8 * 1024 * 1024; +// static uint8_t* uint8Ptr = nullptr; + +// uint8_t* StoreMemory::getStoreMemory() { +// if (uint8Ptr) return uint8Ptr; + +// uint32_t* uint32Ptr = new uint32_t[MEMORY_SIZE / 4]; +// uint8Ptr = (uint8_t*)uint32Ptr; +// return uint8Ptr; +// } + +// void StoreMemory::freeStoreMemory() { +// if (uint8Ptr) { +// delete[] uint8Ptr; +// uint8Ptr = nullptr; +// } +// } + +// uint32_t StoreMemory::storeMemorySize() { +// return MEMORY_SIZE; +// } diff --git a/cocos/editor-support/spine-wasm/util-function.h b/cocos/editor-support/spine-wasm/util-function.h new file mode 100644 index 0000000..657033e --- /dev/null +++ b/cocos/editor-support/spine-wasm/util-function.h @@ -0,0 +1,22 @@ +#ifndef __UTIL_FUNCTION_H__ +#define __UTIL_FUNCTION_H__ +#include +#include +class LogUtil { +public: + static void Initialize(); + static void PrintToJs(std::string& message); + static void PrintToJs(const char* message); + static void PrintToJs(char* str, int length); + static void PrintIntValue(int value, const char* message); + static void ReleaseBuffer(); +}; + +// class StoreMemory { +// public: +// static uint8_t* getStoreMemory(); +// static void freeStoreMemory(); +// static uint32_t storeMemorySize(); +// }; + +#endif \ No newline at end of file diff --git a/cocos/editor-support/spine-wasm/wasmSpineExtension.cpp b/cocos/editor-support/spine-wasm/wasmSpineExtension.cpp new file mode 100644 index 0000000..2a9858e --- /dev/null +++ b/cocos/editor-support/spine-wasm/wasmSpineExtension.cpp @@ -0,0 +1,72 @@ +#include "wasmSpineExtension.h" +#include "util-function.h" + +using namespace spine; +// extern "C" { +// extern uint32_t jsReadFile(char* fileName, uint32_t length); +// } + +WasmSpineExtension::WasmSpineExtension() : DefaultSpineExtension() { +} + +WasmSpineExtension::~WasmSpineExtension() { +} + +char *WasmSpineExtension::_readFile(const String &path, int *length) { + // size_t pathSize = path.length(); + // uint8_t* uint8Ptr = StoreMemory::getStoreMemory(); + // char* shareBuffer = (char*)uint8Ptr; + // memcpy(shareBuffer, path.buffer(), pathSize); + // uint32_t resultSize = jsReadFile(shareBuffer, pathSize); + // *length = (int)resultSize; + // uint8_t *data = new uint8_t[resultSize]; + // memcpy(data, shareBuffer, resultSize); + // return (char*)data; + //LogUtil::PrintToJs("Error WasmSpineExtension::_readFile"); + return nullptr; +} + +void *WasmSpineExtension::_alloc(size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + if (size == 0) + return 0; + void *ptr = new uint8_t[size]; + return (void *)ptr; +} + +void *WasmSpineExtension::_calloc(size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + if (size == 0) + return 0; + uint8_t *ptr = new uint8_t[size]; + if (ptr) memset(ptr, 0, size); + return (void *)ptr; +} + +void *WasmSpineExtension::_realloc(void *ptr, size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + if (size == 0) + return 0; + uint8_t *mem = new uint8_t[size]; + memcpy(mem, ptr, size); + delete[](char *) ptr; + ptr = mem; + return mem; +} + +void WasmSpineExtension::_free(void *mem, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + delete[](char *) mem; +} + +SpineExtension *spine::getDefaultExtension() { + return new WasmSpineExtension(); +} diff --git a/cocos/editor-support/spine-wasm/wasmSpineExtension.h b/cocos/editor-support/spine-wasm/wasmSpineExtension.h new file mode 100644 index 0000000..9ef30fc --- /dev/null +++ b/cocos/editor-support/spine-wasm/wasmSpineExtension.h @@ -0,0 +1,23 @@ +#ifndef __WASM_SPINE_EXTENSION_H__ +#define __WASM_SPINE_EXTENSION_H__ +#include "spine/spine.h" + +class WasmSpineExtension : public spine::DefaultSpineExtension { +public: + WasmSpineExtension(); + + virtual ~WasmSpineExtension(); + +protected: + virtual void *_alloc(size_t size, const char *file, int line); + + virtual void *_calloc(size_t size, const char *file, int line); + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line); + + virtual void _free(void *mem, const char *file, int line); + + virtual char *_readFile(const spine::String &path, int *length); +}; + +#endif \ No newline at end of file diff --git a/cocos/editor-support/spine/Animation.cpp b/cocos/editor-support/spine/Animation.cpp new file mode 100644 index 0000000..e500cb1 --- /dev/null +++ b/cocos/editor-support/spine/Animation.cpp @@ -0,0 +1,140 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include +#include +#include + +#include + +#include + +using namespace spine; + +Animation::Animation(const String &name, Vector &timelines, float duration) : _timelines(timelines), + _timelineIds(), + _duration(duration), + _name(name) { + assert(_name.length() > 0); + for (int i = 0; i < (int)timelines.size(); i++) + _timelineIds.put(timelines[i]->getPropertyId(), true); +} + +bool Animation::hasTimeline(int id) { + return _timelineIds.containsKey(id); +} + +Animation::~Animation() { + ContainerUtil::cleanUpVectorOfPointers(_timelines); +} + +void Animation::apply(Skeleton &skeleton, float lastTime, float time, bool loop, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + if (loop && _duration != 0) { + time = MathUtil::fmod(time, _duration); + if (lastTime > 0) { + lastTime = MathUtil::fmod(lastTime, _duration); + } + } + + for (size_t i = 0, n = _timelines.size(); i < n; ++i) { + _timelines[i]->apply(skeleton, lastTime, time, pEvents, alpha, blend, direction); + } +} + +const String &Animation::getName() { + return _name; +} + +Vector &Animation::getTimelines() { + return _timelines; +} + +float Animation::getDuration() { + return _duration; +} + +void Animation::setDuration(float inValue) { + _duration = inValue; +} + +int Animation::binarySearch(Vector &values, float target, int step) { + int low = 0; + int size = (int)values.size(); + int high = size / step - 2; + if (high == 0) { + return step; + } + + int current = (int)(static_cast(high) >> 1); + while (true) { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + + if (low == high) return (low + 1) * step; + + current = (int)(static_cast(low + high) >> 1); + } +} + +int Animation::binarySearch(Vector &values, float target) { + int low = 0; + int size = (int)values.size(); + int high = size - 2; + if (high == 0) return 1; + + int current = (int)(static_cast(high) >> 1); + while (true) { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + + if (low == high) return (low + 1); + + current = (int)(static_cast(low + high) >> 1); + } +} + +int Animation::linearSearch(Vector &values, float target, int step) { + for (int i = 0, last = (int)values.size() - step; i <= last; i += step) { + if (values[i] > target) { + return i; + } + } + + return -1; +} diff --git a/cocos/editor-support/spine/Animation.h b/cocos/editor-support/spine/Animation.h new file mode 100644 index 0000000..8d2e42b --- /dev/null +++ b/cocos/editor-support/spine/Animation.h @@ -0,0 +1,120 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Animation_h +#define Spine_Animation_h + +#include +#include +#include +#include +#include +#include + +namespace spine { +class Timeline; + +class Skeleton; + +class Event; + +class SP_API Animation : public SpineObject { + friend class AnimationState; + + friend class TrackEntry; + + friend class AnimationStateData; + + friend class AttachmentTimeline; + + friend class ColorTimeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class RotateTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TwoColorTimeline; + +public: + Animation(const String &name, Vector &timelines, float duration); + + ~Animation(); + + /// Applies all the animation's timelines to the specified skeleton. + /// See also Timeline::apply(Skeleton&, float, float, Vector, float, MixPose, MixDirection) + void apply(Skeleton &skeleton, float lastTime, float time, bool loop, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction); + + const String &getName(); + + Vector &getTimelines(); + + bool hasTimeline(int id); + + float getDuration(); + + void setDuration(float inValue); + +private: + Vector _timelines; + HashMap _timelineIds; + float _duration; + String _name; + + /// @param target After the first and before the last entry. + static int binarySearch(Vector &values, float target, int step); + + /// @param target After the first and before the last entry. + static int binarySearch(Vector &values, float target); + + static int linearSearch(Vector &values, float target, int step); +}; +} // namespace spine + +#endif /* Spine_Animation_h */ diff --git a/cocos/editor-support/spine/AnimationState.cpp b/cocos/editor-support/spine/AnimationState.cpp new file mode 100644 index 0000000..b089442 --- /dev/null +++ b/cocos/editor-support/spine/AnimationState.cpp @@ -0,0 +1,1040 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +void dummyOnAnimationEventFunc(AnimationState *state, spine::EventType type, TrackEntry *entry, Event *event = NULL) { + SP_UNUSED(state); + SP_UNUSED(type); + SP_UNUSED(entry); + SP_UNUSED(event); +} + +TrackEntry::TrackEntry() : _animation(NULL), _next(NULL), _mixingFrom(NULL), _mixingTo(0), _trackIndex(0), _loop(false), _holdPrevious(false), _eventThreshold(0), _attachmentThreshold(0), _drawOrderThreshold(0), _animationStart(0), _animationEnd(0), _animationLast(0), _nextAnimationLast(0), _delay(0), _trackTime(0), _trackLast(0), _nextTrackLast(0), _trackEnd(0), _timeScale(1.0f), _alpha(0), _mixTime(0), _mixDuration(0), _interruptAlpha(0), _totalAlpha(0), _mixBlend(MixBlend_Replace), _listener(dummyOnAnimationEventFunc), _listenerObject(NULL) { +} + +TrackEntry::~TrackEntry() {} + +int TrackEntry::getTrackIndex() { return _trackIndex; } + +Animation *TrackEntry::getAnimation() { return _animation; } + +bool TrackEntry::getLoop() { return _loop; } + +void TrackEntry::setLoop(bool inValue) { _loop = inValue; } + +bool TrackEntry::getHoldPrevious() { return _holdPrevious; } + +void TrackEntry::setHoldPrevious(bool inValue) { _holdPrevious = inValue; } + +float TrackEntry::getDelay() { return _delay; } + +void TrackEntry::setDelay(float inValue) { _delay = inValue; } + +float TrackEntry::getTrackTime() { return _trackTime; } + +void TrackEntry::setTrackTime(float inValue) { _trackTime = inValue; } + +float TrackEntry::getTrackEnd() { return _trackEnd; } + +void TrackEntry::setTrackEnd(float inValue) { _trackEnd = inValue; } + +float TrackEntry::getAnimationStart() { return _animationStart; } + +void TrackEntry::setAnimationStart(float inValue) { _animationStart = inValue; } + +float TrackEntry::getAnimationEnd() { return _animationEnd; } + +void TrackEntry::setAnimationEnd(float inValue) { _animationEnd = inValue; } + +float TrackEntry::getAnimationLast() { return _animationLast; } + +void TrackEntry::setAnimationLast(float inValue) { + _animationLast = inValue; + _nextAnimationLast = inValue; +} + +float TrackEntry::getAnimationTime() { + if (_loop) { + float duration = _animationEnd - _animationStart; + if (duration == 0) return _animationStart; + return MathUtil::fmod(_trackTime, duration) + _animationStart; + } + + return MathUtil::min(_trackTime + _animationStart, _animationEnd); +} + +float TrackEntry::getTimeScale() { return _timeScale; } + +void TrackEntry::setTimeScale(float inValue) { _timeScale = inValue; } + +float TrackEntry::getAlpha() { return _alpha; } + +void TrackEntry::setAlpha(float inValue) { _alpha = inValue; } + +float TrackEntry::getEventThreshold() { return _eventThreshold; } + +void TrackEntry::setEventThreshold(float inValue) { _eventThreshold = inValue; } + +float TrackEntry::getAttachmentThreshold() { return _attachmentThreshold; } + +void TrackEntry::setAttachmentThreshold(float inValue) { _attachmentThreshold = inValue; } + +float TrackEntry::getDrawOrderThreshold() { return _drawOrderThreshold; } + +void TrackEntry::setDrawOrderThreshold(float inValue) { _drawOrderThreshold = inValue; } + +TrackEntry *TrackEntry::getNext() { return _next; } + +bool TrackEntry::isComplete() { + return _trackTime >= _animationEnd - _animationStart; +} + +float TrackEntry::getMixTime() { return _mixTime; } + +void TrackEntry::setMixTime(float inValue) { _mixTime = inValue; } + +float TrackEntry::getMixDuration() { return _mixDuration; } + +void TrackEntry::setMixDuration(float inValue) { _mixDuration = inValue; } + +TrackEntry *TrackEntry::getMixingFrom() { return _mixingFrom; } + +TrackEntry *TrackEntry::getMixingTo() { return _mixingTo; } + +void TrackEntry::setMixBlend(MixBlend blend) { _mixBlend = blend; } + +MixBlend TrackEntry::getMixBlend() { return _mixBlend; } + +void TrackEntry::resetRotationDirections() { + _timelinesRotation.clear(); +} + +void TrackEntry::setListener(AnimationStateListener inValue) { + _listener = inValue; + _listenerObject = NULL; +} + +void TrackEntry::setListener(AnimationStateListenerObject *inValue) { + _listener = dummyOnAnimationEventFunc; + _listenerObject = inValue; +} + +void TrackEntry::reset() { + _animation = NULL; + _next = NULL; + _mixingFrom = NULL; + _mixingTo = NULL; + + setRendererObject(NULL); + + _timelineMode.clear(); + _timelineHoldMix.clear(); + _timelinesRotation.clear(); + + _listener = dummyOnAnimationEventFunc; + _listenerObject = NULL; +} + +EventQueueEntry::EventQueueEntry(EventType eventType, TrackEntry *trackEntry, Event *event) : _type(eventType), + _entry(trackEntry), + _event(event) { +} + +EventQueue *EventQueue::newEventQueue(AnimationState &state, Pool &trackEntryPool) { + return new (__FILE__, __LINE__) EventQueue(state, trackEntryPool); +} + +EventQueueEntry EventQueue::newEventQueueEntry(EventType eventType, TrackEntry *entry, Event *event) { + return EventQueueEntry(eventType, entry, event); +} + +EventQueue::EventQueue(AnimationState &state, Pool &trackEntryPool) : _state(state), + _trackEntryPool(trackEntryPool), + _drainDisabled(false) { +} + +EventQueue::~EventQueue() { +} + +void EventQueue::start(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Start, entry)); + _state._animationsChanged = true; +} + +void EventQueue::interrupt(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Interrupt, entry)); +} + +void EventQueue::end(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_End, entry)); + _state._animationsChanged = true; +} + +void EventQueue::dispose(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Dispose, entry)); +} + +void EventQueue::complete(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Complete, entry)); +} + +void EventQueue::event(TrackEntry *entry, Event *event) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Event, entry, event)); +} + +/// Raises all events in the queue and drains the queue. +void EventQueue::drain() { + if (_drainDisabled) { + return; + } + + _drainDisabled = true; + + AnimationState &state = _state; + + // Don't cache _eventQueueEntries.size() so callbacks can queue their own events (eg, call setAnimation in AnimationState_Complete). + for (size_t i = 0; i < _eventQueueEntries.size(); ++i) { + EventQueueEntry *queueEntry = &_eventQueueEntries[i]; + TrackEntry *trackEntry = queueEntry->_entry; + + switch (queueEntry->_type) { + case EventType_Start: + case EventType_Interrupt: + case EventType_Complete: + if (!trackEntry->_listenerObject) + trackEntry->_listener(&state, queueEntry->_type, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, queueEntry->_type, trackEntry, NULL); + if (!state._listenerObject) + state._listener(&state, queueEntry->_type, trackEntry, NULL); + else + state._listenerObject->callback(&state, queueEntry->_type, trackEntry, NULL); + break; + case EventType_End: + if (!trackEntry->_listenerObject) + trackEntry->_listener(&state, queueEntry->_type, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, queueEntry->_type, trackEntry, NULL); + if (!state._listenerObject) + state._listener(&state, queueEntry->_type, trackEntry, NULL); + else + state._listenerObject->callback(&state, queueEntry->_type, trackEntry, NULL); + /* Fall through. */ + case EventType_Dispose: + if (!trackEntry->_listenerObject) + trackEntry->_listener(&state, EventType_Dispose, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL); + if (!state._listenerObject) + state._listener(&state, EventType_Dispose, trackEntry, NULL); + else + state._listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL); + + trackEntry->reset(); + _trackEntryPool.free(trackEntry); + break; + case EventType_Event: + if (!trackEntry->_listenerObject) + trackEntry->_listener(&state, queueEntry->_type, trackEntry, queueEntry->_event); + else + trackEntry->_listenerObject->callback(&state, queueEntry->_type, trackEntry, queueEntry->_event); + if (!state._listenerObject) + state._listener(&state, queueEntry->_type, trackEntry, queueEntry->_event); + else + state._listenerObject->callback(&state, queueEntry->_type, trackEntry, queueEntry->_event); + break; + } + } + _eventQueueEntries.clear(); + + _drainDisabled = false; +} + +const int Subsequent = 0; +const int First = 1; +const int Hold = 2; +const int HoldMix = 3; +const int NotLast = 4; + +AnimationState::AnimationState(AnimationStateData *data) : _data(data), + _queue(EventQueue::newEventQueue(*this, _trackEntryPool)), + _animationsChanged(false), + _listener(dummyOnAnimationEventFunc), + _listenerObject(NULL), + _timeScale(1) { +} + +AnimationState::~AnimationState() { + for (size_t i = 0; i < _tracks.size(); i++) { + TrackEntry *entry = _tracks[i]; + if (entry) { + TrackEntry *from = entry->_mixingFrom; + while (from) { + TrackEntry *curr = from; + from = curr->_mixingFrom; + delete curr; + } + TrackEntry *next = entry->_next; + while (next) { + TrackEntry *curr = next; + next = curr->_next; + delete curr; + } + delete entry; + } + } + delete _queue; +} + +void AnimationState::update(float delta) { + delta *= _timeScale; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *currentP = _tracks[i]; + if (currentP == NULL) { + continue; + } + + TrackEntry ¤t = *currentP; + + current._animationLast = current._nextAnimationLast; + current._trackLast = current._nextTrackLast; + + float currentDelta = delta * current._timeScale; + + if (current._delay > 0) { + current._delay -= currentDelta; + if (current._delay > 0) { + continue; + } + currentDelta = -current._delay; + current._delay = 0; + } + + TrackEntry *next = current._next; + if (next != NULL) { + // When the next entry's delay is passed, change to the next entry, preserving leftover time. + float nextTime = current._trackLast - next->_delay; + if (nextTime >= 0) { + next->_delay = 0; + next->_trackTime += current._timeScale == 0 ? 0 : (nextTime / current._timeScale + delta) * next->_timeScale; + current._trackTime += currentDelta; + setCurrent(i, next, true); + while (next->_mixingFrom != NULL) { + next->_mixTime += delta; + next = next->_mixingFrom; + } + continue; + } + } else if (current._trackLast >= current._trackEnd && current._mixingFrom == NULL) { + // clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom. + _tracks[i] = NULL; + + _queue->end(currentP); + disposeNext(currentP); + continue; + } + + if (current._mixingFrom != NULL && updateMixingFrom(currentP, delta)) { + // End mixing from entries once all have completed. + TrackEntry *from = current._mixingFrom; + current._mixingFrom = NULL; + if (from != NULL) from->_mixingTo = NULL; + while (from != NULL) { + _queue->end(from); + from = from->_mixingFrom; + } + } + + current._trackTime += currentDelta; + } + + _queue->drain(); +} + +bool AnimationState::apply(Skeleton &skeleton) { + if (_animationsChanged) { + animationsChanged(); + } + + bool applied = false; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *currentP = _tracks[i]; + if (currentP == NULL || currentP->_delay > 0) { + continue; + } + + TrackEntry ¤t = *currentP; + + applied = true; + MixBlend blend = i == 0 ? MixBlend_First : current._mixBlend; + + // apply mixing from entries first. + float mix = current._alpha; + if (current._mixingFrom != NULL) { + mix *= applyMixingFrom(currentP, skeleton, blend); + } else if (current._trackTime >= current._trackEnd && current._next == NULL) { + mix = 0; // Set to setup pose the last time the entry will be applied. + } + + // apply current entry. + float animationLast = current._animationLast, animationTime = current.getAnimationTime(); + size_t timelineCount = current._animation->_timelines.size(); + Vector &timelines = current._animation->_timelines; + if ((i == 0 && mix == 1) || blend == MixBlend_Add) { + for (size_t ii = 0; ii < timelineCount; ++ii) + timelines[ii]->apply(skeleton, animationLast, animationTime, &_events, mix, blend, MixDirection_In); + } else { + Vector &timelineMode = current._timelineMode; + + bool firstFrame = current._timelinesRotation.size() == 0; + if (firstFrame) current._timelinesRotation.setSize(timelines.size() << 1, 0); + Vector &timelinesRotation = current._timelinesRotation; + + for (size_t ii = 0; ii < timelineCount; ++ii) { + Timeline *timeline = timelines[ii]; + assert(timeline); + + MixBlend timelineBlend = (timelineMode[ii] & (NotLast - 1)) == Subsequent ? blend : MixBlend_Setup; + + RotateTimeline *rotateTimeline = NULL; + if (timeline->getRTTI().isExactly(RotateTimeline::rtti)) rotateTimeline = static_cast(timeline); + + if (rotateTimeline != NULL) + applyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame); + else + timeline->apply(skeleton, animationLast, animationTime, &_events, mix, timelineBlend, MixDirection_In); + } + } + + queueEvents(currentP, animationTime); + _events.clear(); + current._nextAnimationLast = animationTime; + current._nextTrackLast = current._trackTime; + } + + _queue->drain(); + return applied; +} + +void AnimationState::clearTracks() { + bool oldDrainDisabled = _queue->_drainDisabled; + _queue->_drainDisabled = true; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) + clearTrack(i); + _tracks.clear(); + _queue->_drainDisabled = oldDrainDisabled; + _queue->drain(); +} + +void AnimationState::clearTrack(size_t trackIndex) { + if (trackIndex >= _tracks.size()) return; + + TrackEntry *current = _tracks[trackIndex]; + if (current == NULL) return; + + _queue->end(current); + + disposeNext(current); + + TrackEntry *entry = current; + while (true) { + TrackEntry *from = entry->_mixingFrom; + if (from == NULL) break; + + _queue->end(from); + entry->_mixingFrom = NULL; + entry->_mixingTo = NULL; + entry = from; + } + + _tracks[current->_trackIndex] = NULL; + + _queue->drain(); +} + +TrackEntry *AnimationState::setAnimation(size_t trackIndex, const String &animationName, bool loop) { + Animation *animation = _data->_skeletonData->findAnimation(animationName); + assert(animation != NULL); + return setAnimation(trackIndex, animation, loop); +} + +TrackEntry *AnimationState::setAnimation(size_t trackIndex, Animation *animation, bool loop) { + assert(animation != NULL); + + bool interrupt = true; + TrackEntry *current = expandToIndex(trackIndex); + if (current != NULL) { + if (current->_nextTrackLast == -1) { + // Don't mix from an entry that was never applied. + _tracks[trackIndex] = current->_mixingFrom; + _queue->interrupt(current); + _queue->end(current); + disposeNext(current); + current = current->_mixingFrom; + interrupt = false; + } else { + disposeNext(current); + } + } + + TrackEntry *entry = newTrackEntry(trackIndex, animation, loop, current); + setCurrent(trackIndex, entry, interrupt); + _queue->drain(); + + return entry; +} + +TrackEntry *AnimationState::addAnimation(size_t trackIndex, const String &animationName, bool loop, float delay) { + Animation *animation = _data->_skeletonData->findAnimation(animationName); + assert(animation != NULL); + return addAnimation(trackIndex, animation, loop, delay); +} + +TrackEntry *AnimationState::addAnimation(size_t trackIndex, Animation *animation, bool loop, float delay) { + assert(animation != NULL); + + TrackEntry *last = expandToIndex(trackIndex); + if (last != NULL) { + while (last->_next != NULL) + last = last->_next; + } + + TrackEntry *entry = newTrackEntry(trackIndex, animation, loop, last); + + if (last == NULL) { + setCurrent(trackIndex, entry, true); + _queue->drain(); + } else { + last->_next = entry; + if (delay <= 0) { + float duration = last->_animationEnd - last->_animationStart; + if (duration != 0) { + if (last->_loop) { + delay += duration * (1 + (int)(last->_trackTime / duration)); + } else { + delay += MathUtil::max(duration, last->_trackTime); + } + delay -= _data->getMix(last->_animation, animation); + } else { + delay = last->_trackTime; + } + } + } + + entry->_delay = delay; + return entry; +} + +TrackEntry *AnimationState::setEmptyAnimation(size_t trackIndex, float mixDuration) { + TrackEntry *entry = setAnimation(trackIndex, AnimationState::getEmptyAnimation(), false); + entry->_mixDuration = mixDuration; + entry->_trackEnd = mixDuration; + return entry; +} + +TrackEntry *AnimationState::addEmptyAnimation(size_t trackIndex, float mixDuration, float delay) { + if (delay <= 0) { + delay -= mixDuration; + } + + TrackEntry *entry = addAnimation(trackIndex, AnimationState::getEmptyAnimation(), false, delay); + entry->_mixDuration = mixDuration; + entry->_trackEnd = mixDuration; + return entry; +} + +void AnimationState::setEmptyAnimations(float mixDuration) { + bool oldDrainDisabled = _queue->_drainDisabled; + _queue->_drainDisabled = true; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *current = _tracks[i]; + if (current != NULL) { + setEmptyAnimation(i, mixDuration); + } + } + _queue->_drainDisabled = oldDrainDisabled; + _queue->drain(); +} + +TrackEntry *AnimationState::getCurrent(size_t trackIndex) { + return trackIndex >= _tracks.size() ? NULL : _tracks[trackIndex]; +} + +AnimationStateData *AnimationState::getData() { + return _data; +} + +Vector &AnimationState::getTracks() { + return _tracks; +} + +float AnimationState::getTimeScale() { + return _timeScale; +} + +void AnimationState::setTimeScale(float inValue) { + _timeScale = inValue; +} + +void AnimationState::setListener(AnimationStateListener inValue) { + _listener = inValue; + _listenerObject = NULL; +} + +void AnimationState::setListener(AnimationStateListenerObject *inValue) { + _listener = dummyOnAnimationEventFunc; + _listenerObject = inValue; +} + +void AnimationState::disableQueue() { + _queue->_drainDisabled = true; +} +void AnimationState::enableQueue() { + _queue->_drainDisabled = false; +} + +Animation *AnimationState::getEmptyAnimation() { + static Vector timelines; + static Animation ret(String(""), timelines, 0); + return &ret; +} + +void AnimationState::applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleton &skeleton, float time, float alpha, + MixBlend blend, Vector &timelinesRotation, size_t i, bool firstFrame) { + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) { + rotateTimeline->apply(skeleton, 0, time, NULL, 1, blend, MixDirection_In); + return; + } + + Bone *bone = skeleton._bones[rotateTimeline->_boneIndex]; + if (!bone->isActive()) return; + Vector &frames = rotateTimeline->_frames; + float r1, r2; + if (time < frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_rotation = bone->_data._rotation; + default: + return; + case MixBlend_First: + r1 = bone->_rotation; + r2 = bone->_data._rotation; + } + } else { + r1 = blend == MixBlend_Setup ? bone->_data._rotation : bone->_rotation; + if (time >= frames[frames.size() - RotateTimeline::ENTRIES]) { + // Time is after last frame. + r2 = bone->_data._rotation + frames[frames.size() + RotateTimeline::PREV_ROTATION]; + } else { + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(frames, time, RotateTimeline::ENTRIES); + float prevRotation = frames[frame + RotateTimeline::PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = rotateTimeline->getCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + + RotateTimeline::PREV_TIME] - + frameTime)); + r2 = frames[frame + RotateTimeline::ROTATION] - prevRotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + r2 = prevRotation + r2 * percent + bone->_data._rotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + } + } + + // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. + float total, diff = r2 - r1; + diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + if (diff == 0) { + total = timelinesRotation[i]; + } else { + float lastTotal, lastDiff; + if (firstFrame) { + lastTotal = 0; + lastDiff = diff; + } else { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + + bool current = diff > 0, dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (MathUtil::sign(lastDiff) != MathUtil::sign(diff) && MathUtil::abs(lastDiff) <= 90) { + // A cross after a 360 rotation is a loop. + if (MathUtil::abs(lastTotal) > 180) lastTotal += 360 * MathUtil::sign(lastTotal); + dir = current; + } + + total = diff + lastTotal - MathUtil::fmod(lastTotal, 360); // Store loops as part of lastTotal. + if (dir != current) { + total += 360 * MathUtil::sign(lastTotal); + } + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + r1 += total * alpha; + bone->_rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; +} + +bool AnimationState::updateMixingFrom(TrackEntry *to, float delta) { + TrackEntry *from = to->_mixingFrom; + if (from == NULL) { + return true; + } + + bool finished = updateMixingFrom(from, delta); + + from->_animationLast = from->_nextAnimationLast; + from->_trackLast = from->_nextTrackLast; + + // Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (to->_mixTime > 0 && to->_mixTime >= to->_mixDuration) { + // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). + if (from->_totalAlpha == 0 || to->_mixDuration == 0) { + to->_mixingFrom = from->_mixingFrom; + if (from->_mixingFrom != NULL) from->_mixingFrom->_mixingTo = to; + to->_interruptAlpha = from->_interruptAlpha; + _queue->end(from); + } + return finished; + } + + from->_trackTime += delta * from->_timeScale; + to->_mixTime += delta; + + return false; +} + +float AnimationState::applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBlend blend) { + TrackEntry *from = to->_mixingFrom; + if (from->_mixingFrom != NULL) applyMixingFrom(from, skeleton, blend); + + float mix; + if (to->_mixDuration == 0) { + // Single frame mix to undo mixingFrom changes. + mix = 1; + if (blend == MixBlend_First) blend = MixBlend_Setup; + } else { + mix = to->_mixTime / to->_mixDuration; + if (mix > 1) { + mix = 1; + } + if (blend != MixBlend_First) blend = from->_mixBlend; + } + + Vector *eventBuffer = mix < from->_eventThreshold ? &_events : NULL; + bool attachments = mix < from->_attachmentThreshold, drawOrder = mix < from->_drawOrderThreshold; + float animationLast = from->_animationLast, animationTime = from->getAnimationTime(); + Vector &timelines = from->_animation->_timelines; + size_t timelineCount = timelines.size(); + float alphaHold = from->_alpha * to->_interruptAlpha, alphaMix = alphaHold * (1 - mix); + + if (blend == MixBlend_Add) { + for (size_t i = 0; i < timelineCount; i++) + timelines[i]->apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection_Out); + } else { + Vector &timelineMode = from->_timelineMode; + Vector &timelineHoldMix = from->_timelineHoldMix; + + bool firstFrame = from->_timelinesRotation.size() == 0; + if (firstFrame) from->_timelinesRotation.setSize(timelines.size() << 1, 0); + + Vector &timelinesRotation = from->_timelinesRotation; + + from->_totalAlpha = 0; + for (size_t i = 0; i < timelineCount; i++) { + Timeline *timeline = timelines[i]; + MixDirection direction = MixDirection_Out; + MixBlend timelineBlend; + float alpha; + switch (timelineMode[i] & (NotLast - 1)) { + case Subsequent: + timelineBlend = blend; + if (!attachments && (timeline->getRTTI().isExactly(AttachmentTimeline::rtti))) { + if ((timelineMode[i] & NotLast) == NotLast) continue; + timelineBlend = MixBlend_Setup; + } + if (!drawOrder && (timeline->getRTTI().isExactly(DrawOrderTimeline::rtti))) continue; + alpha = alphaMix; + break; + case First: + timelineBlend = MixBlend_Setup; + alpha = alphaMix; + break; + case Hold: + timelineBlend = MixBlend_Setup; + alpha = alphaHold; + break; + default: + timelineBlend = MixBlend_Setup; + TrackEntry *holdMix = timelineHoldMix[i]; + alpha = alphaHold * MathUtil::max(0.0f, 1.0f - holdMix->_mixTime / holdMix->_mixDuration); + break; + } + from->_totalAlpha += alpha; + if ((timeline->getRTTI().isExactly(RotateTimeline::rtti))) { + applyRotateTimeline((RotateTimeline *)timeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i << 1, firstFrame); + } else { + if (timelineBlend == MixBlend_Setup) { + if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) { + if (attachments || (timelineMode[i] & NotLast) == NotLast) direction = MixDirection_In; + } else if (timeline->getRTTI().isExactly(DrawOrderTimeline::rtti)) { + if (drawOrder) direction = MixDirection_In; + } + } + timeline->apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction); + } + } + } + + if (to->_mixDuration > 0) { + queueEvents(from, animationTime); + } + + _events.clear(); + from->_nextAnimationLast = animationTime; + from->_nextTrackLast = from->_trackTime; + + return mix; +} + +void AnimationState::queueEvents(TrackEntry *entry, float animationTime) { + float animationStart = entry->_animationStart, animationEnd = entry->_animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = MathUtil::fmod(entry->_trackLast, duration); + + // Queue events before complete. + size_t i = 0, n = _events.size(); + for (; i < n; ++i) { + Event *e = _events[i]; + if (e->_time < trackLastWrapped) break; + if (e->_time > animationEnd) continue; // Discard events outside animation start/end. + _queue->event(entry, e); + } + + // Queue complete if completed a loop iteration or the animation. + bool complete = false; + if (entry->_loop) + complete = duration == 0 || (trackLastWrapped > MathUtil::fmod(entry->_trackTime, duration)); + else + complete = animationTime >= animationEnd && entry->_animationLast < animationEnd; + if (complete) _queue->complete(entry); + + // Queue events after complete. + for (; i < n; ++i) { + Event *e = _events[i]; + if (e->_time < animationStart) continue; // Discard events outside animation start/end. + _queue->event(entry, _events[i]); + } +} + +void AnimationState::setCurrent(size_t index, TrackEntry *current, bool interrupt) { + TrackEntry *from = expandToIndex(index); + _tracks[index] = current; + + if (from != NULL) { + if (interrupt) _queue->interrupt(from); + + current->_mixingFrom = from; + from->_mixingTo = current; + current->_mixTime = 0; + + // Store interrupted mix percentage. + if (from->_mixingFrom != NULL && from->_mixDuration > 0) { + current->_interruptAlpha *= MathUtil::min(1.0f, from->_mixTime / from->_mixDuration); + } + + from->_timelinesRotation.clear(); // Reset rotation for mixing out, in case entry was mixed in. + } + + _queue->start(current); // triggers animationsChanged +} + +TrackEntry *AnimationState::expandToIndex(size_t index) { + if (index < _tracks.size()) return _tracks[index]; + while (index >= _tracks.size()) + _tracks.add(NULL); + return NULL; +} + +TrackEntry *AnimationState::newTrackEntry(size_t trackIndex, Animation *animation, bool loop, TrackEntry *last) { + TrackEntry *entryP = _trackEntryPool.obtain(); // Pooling + TrackEntry &entry = *entryP; + + entry._trackIndex = trackIndex; + entry._animation = animation; + entry._loop = loop; + entry._holdPrevious = 0; + + entry._eventThreshold = 0; + entry._attachmentThreshold = 0; + entry._drawOrderThreshold = 0; + + entry._animationStart = 0; + entry._animationEnd = animation->getDuration(); + entry._animationLast = -1; + entry._nextAnimationLast = -1; + + entry._delay = 0; + entry._trackTime = 0; + entry._trackLast = -1; + entry._nextTrackLast = -1; // nextTrackLast == -1 signifies a TrackEntry that wasn't applied yet. + entry._trackEnd = FLT_MAX; // loop ? float.MaxValue : animation.Duration; + entry._timeScale = 1; + + entry._alpha = 1; + entry._interruptAlpha = 1; + entry._mixTime = 0; + entry._mixDuration = (last == NULL) ? 0 : _data->getMix(last->_animation, animation); + + return entryP; +} + +void AnimationState::disposeNext(TrackEntry *entry) { + TrackEntry *next = entry->_next; + while (next != NULL) { + _queue->dispose(next); + next = next->_next; + } + entry->_next = NULL; +} + +void AnimationState::animationsChanged() { + _animationsChanged = false; + + _propertyIDs.clear(); + + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *entry = _tracks[i]; + if (!entry) continue; + + while (entry->_mixingFrom != NULL) + entry = entry->_mixingFrom; + + do { + if (entry->_mixingTo == NULL || entry->_mixBlend != MixBlend_Add) computeHold(entry); + entry = entry->_mixingTo; + } while (entry != NULL); + } + + _propertyIDs.clear(); + for (int i = (int)_tracks.size() - 1; i >= 0; i--) { + TrackEntry *entry = _tracks[i]; + while (entry) { + computeNotLast(entry); + entry = entry->_mixingFrom; + } + } +} + +void AnimationState::computeHold(TrackEntry *entry) { + TrackEntry *to = entry->_mixingTo; + Vector &timelines = entry->_animation->_timelines; + size_t timelinesCount = timelines.size(); + Vector &timelineMode = entry->_timelineMode; + timelineMode.setSize(timelinesCount, 0); + Vector &timelineHoldMix = entry->_timelineHoldMix; + timelineHoldMix.setSize(timelinesCount, 0); + + if (to != NULL && to->_holdPrevious) { + for (size_t i = 0; i < timelinesCount; i++) { + int id = timelines[i]->getPropertyId(); + if (!_propertyIDs.containsKey(id)) _propertyIDs.put(id, true); + timelineMode[i] = Hold; + } + return; + } + + // outer: + size_t i = 0; +continue_outer: + for (; i < timelinesCount; ++i) { + Timeline *timeline = timelines[i]; + int id = timeline->getPropertyId(); + if (_propertyIDs.containsKey(id)) { + timelineMode[i] = Subsequent; + } else { + _propertyIDs.put(id, true); + + if (to == NULL || timeline->getRTTI().isExactly(AttachmentTimeline::rtti) || + timeline->getRTTI().isExactly(DrawOrderTimeline::rtti) || + timeline->getRTTI().isExactly(EventTimeline::rtti) || !to->_animation->hasTimeline(id)) { + timelineMode[i] = First; + } else { + for (TrackEntry *next = to->_mixingTo; next != NULL; next = next->_mixingTo) { + if (next->_animation->hasTimeline(id)) continue; + if (entry->_mixDuration > 0) { + timelineMode[i] = HoldMix; + timelineHoldMix[i] = entry; + i++; + goto continue_outer; // continue outer; + } + break; + } + timelineMode[i] = Hold; + } + } + } +} + +void AnimationState::computeNotLast(TrackEntry *entry) { + Vector &timelines = entry->_animation->_timelines; + size_t timelinesCount = timelines.size(); + Vector &timelineMode = entry->_timelineMode; + + for (size_t i = 0; i < timelinesCount; i++) { + if (timelines[i]->getRTTI().isExactly(AttachmentTimeline::rtti)) { + AttachmentTimeline *timeline = static_cast(timelines[i]); + if (!_propertyIDs.containsKey(timeline->getSlotIndex())) + _propertyIDs.put(timeline->getSlotIndex(), true); + else + timelineMode[i] |= NotLast; + } + } +} diff --git a/cocos/editor-support/spine/AnimationState.h b/cocos/editor-support/spine/AnimationState.h new file mode 100644 index 0000000..eddae70 --- /dev/null +++ b/cocos/editor-support/spine/AnimationState.h @@ -0,0 +1,435 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AnimationState_h +#define Spine_AnimationState_h + +#include +#include +#include +#include +#include +#include + +#ifdef SPINE_USE_STD_FUNCTION + #include +#endif + +namespace spine { +enum EventType { + EventType_Start, + EventType_Interrupt, + EventType_End, + EventType_Dispose, + EventType_Complete, + EventType_Event +}; + +class AnimationState; +class TrackEntry; + +class Animation; +class Event; +class AnimationStateData; +class Skeleton; +class RotateTimeline; + +#ifdef SPINE_USE_STD_FUNCTION +typedef std::function AnimationStateListener; +#else +typedef void (*AnimationStateListener)(AnimationState* state, EventType type, TrackEntry* entry, Event* event); +#endif + +/// Abstract class to inherit from to create a callback object +class SP_API AnimationStateListenerObject { +public: + AnimationStateListenerObject(){}; + virtual ~AnimationStateListenerObject(){}; + +public: + /// The callback function to be called + virtual void callback(AnimationState* state, EventType type, TrackEntry* entry, Event* event) = 0; +}; + +/// State for the playback of an animation +class SP_API TrackEntry : public SpineObject, public HasRendererObject { + friend class EventQueue; + friend class AnimationState; + +public: + TrackEntry(); + + virtual ~TrackEntry(); + + /// The index of the track where this entry is either current or queued. + int getTrackIndex(); + + /// The animation to apply for this track entry. + Animation* getAnimation(); + + /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. + bool getLoop(); + void setLoop(bool inValue); + + /// If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead + /// of being mixed out. + /// + /// When mixing between animations that key the same property, if a lower track also keys that property then the value will + /// briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% + /// while the second animation mixes from 0% to 100%. Setting holdPrevious to true applies the first animation + /// at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which + /// keys the property, only when a higher track also keys the property. + /// + /// Snapping will occur if holdPrevious is true and this animation does not key all the same properties as the + /// previous animation. + bool getHoldPrevious(); + void setHoldPrevious(bool inValue); + + /// Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones incrementing + /// the track time. When a track entry is queued, delay is the time from the start of the previous animation to when the + /// track entry will become the current track entry. + float getDelay(); + void setDelay(float inValue); + + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// TrackEntry.AnimationTime. The track time can be set to start the animation at a time other than 0, without affecting looping. + float getTrackTime(); + void setTrackTime(float inValue); + + /// The track time in seconds when this animation will be removed from the track. Defaults to the animation duration for + /// non-looping animations and to int.MaxValue for looping animations. If the track end time is reached and no + /// other animations are queued for playback, and mixing from any previous animations is complete, properties keyed by the animation, + /// are set to the setup pose and the track is cleared. + /// + /// It may be desired to use AnimationState.addEmptyAnimation(int, float, float) to mix the properties back to the + /// setup pose over time, rather than have it happen instantly. + float getTrackEnd(); + void setTrackEnd(float inValue); + + /// Seconds when this animation starts, both initially and after looping. Defaults to 0. + /// + /// When changing the animation start time, it often makes sense to set TrackEntry.AnimationLast to the same value to + /// prevent timeline keys before the start time from triggering. + float getAnimationStart(); + void setAnimationStart(float inValue); + + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to TrackEntry.AnimationStart at this time. Defaults to the animation duration. + float getAnimationEnd(); + void setAnimationEnd(float inValue); + + /// The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this + /// animation is applied, event timelines will fire all events between the animation last time (exclusive) and animation time + /// (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation is applied. + float getAnimationLast(); + void setAnimationLast(float inValue); + + /// Uses TrackEntry.TrackTime to compute the animation time between TrackEntry.AnimationStart. and + /// TrackEntry.AnimationEnd. When the track time is 0, the animation time is equal to the animation start time. + float getAnimationTime(); + + /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or + /// faster. Defaults to 1. + float getTimeScale(); + void setTimeScale(float inValue); + + /// Values less than 1 mix this animation with the last skeleton pose. Defaults to 1, which overwrites the last skeleton pose with + /// this animation. + /// + /// Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense + /// to use alpha on track 0 if the skeleton pose is from the last frame render. + float getAlpha(); + void setAlpha(float inValue); + + /// + /// When the mix percentage (mix time / mix duration) is less than the event threshold, event timelines for the animation + /// being mixed out will be applied. Defaults to 0, so event timelines are not applied for an animation being mixed out. + float getEventThreshold(); + void setEventThreshold(float inValue); + + /// When the mix percentage (mix time / mix duration) is less than the attachment threshold, attachment timelines for the + /// animation being mixed out will be applied. Defaults to 0, so attachment timelines are not applied for an animation being + /// mixed out. + float getAttachmentThreshold(); + void setAttachmentThreshold(float inValue); + + /// When the mix percentage (mix time / mix duration) is less than the draw order threshold, draw order timelines for the + /// animation being mixed out will be applied. Defaults to 0, so draw order timelines are not applied for an animation being + /// mixed out. + float getDrawOrderThreshold(); + void setDrawOrderThreshold(float inValue); + + /// The animation queued to start after this animation, or NULL. + TrackEntry* getNext(); + + /// Returns true if at least one loop has been completed. + bool isComplete(); + + /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than + /// TrackEntry.MixDuration when the mix is complete. + float getMixTime(); + void setMixTime(float inValue); + + /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by + /// AnimationStateData based on the animation before this animation (if any). + /// + /// The mix duration can be set manually rather than use the value from AnimationStateData.GetMix. + /// In that case, the mixDuration must be set before AnimationState.update(float) is next called. + /// + /// When using AnimationState::addAnimation(int, Animation, bool, float) with a delay + /// less than or equal to 0, note the Delay is set using the mix duration from the AnimationStateData + float getMixDuration(); + void setMixDuration(float inValue); + + MixBlend getMixBlend(); + void setMixBlend(MixBlend blend); + + /// The track entry for the previous animation when mixing from the previous animation to this animation, or NULL if no + /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a double linked list with MixingTo. + TrackEntry* getMixingFrom(); + + /// The track entry for the next animation when mixing from this animation, or NULL if no mixing is currently occuring. + /// When mixing from multiple animations, MixingTo makes up a double linked list with MixingFrom. + TrackEntry* getMixingTo(); + + /// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the + /// long way around when using alpha and starting animations on other tracks. + /// + /// Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way around. + /// The two rotations likely change over time, so which direction is the short or long way also changes. + /// If the short way was always chosen, bones would flip to the other side when that direction became the long way. + /// TrackEntry chooses the short way the first time it is applied and remembers that direction. + void resetRotationDirections(); + + void setListener(AnimationStateListener listener); + + void setListener(AnimationStateListenerObject* listener); + +private: + Animation* _animation; + + TrackEntry* _next; + TrackEntry* _mixingFrom; + TrackEntry* _mixingTo; + int _trackIndex; + + bool _loop, _holdPrevious; + float _eventThreshold, _attachmentThreshold, _drawOrderThreshold; + float _animationStart, _animationEnd, _animationLast, _nextAnimationLast; + float _delay, _trackTime, _trackLast, _nextTrackLast, _trackEnd, _timeScale; + float _alpha, _mixTime, _mixDuration, _interruptAlpha, _totalAlpha; + MixBlend _mixBlend; + Vector _timelineMode; + Vector _timelineHoldMix; + Vector _timelinesRotation; + AnimationStateListener _listener; + AnimationStateListenerObject* _listenerObject; + + void reset(); +}; + +class SP_API EventQueueEntry : public SpineObject { + friend class EventQueue; + +public: + EventType _type; + TrackEntry* _entry; + Event* _event; + + EventQueueEntry(EventType eventType, TrackEntry* trackEntry, Event* event = NULL); +}; + +class SP_API EventQueue : public SpineObject { + friend class AnimationState; + +private: + Vector _eventQueueEntries; + AnimationState& _state; + Pool& _trackEntryPool; + bool _drainDisabled; + + static EventQueue* newEventQueue(AnimationState& state, Pool& trackEntryPool); + + static EventQueueEntry newEventQueueEntry(EventType eventType, TrackEntry* entry, Event* event = NULL); + + EventQueue(AnimationState& state, Pool& trackEntryPool); + + ~EventQueue(); + + void start(TrackEntry* entry); + + void interrupt(TrackEntry* entry); + + void end(TrackEntry* entry); + + void dispose(TrackEntry* entry); + + void complete(TrackEntry* entry); + + void event(TrackEntry* entry, Event* event); + + /// Raises all events in the queue and drains the queue. + void drain(); +}; + +class SP_API AnimationState : public SpineObject, public HasRendererObject { + friend class TrackEntry; + friend class EventQueue; + +public: + explicit AnimationState(AnimationStateData* data); + + ~AnimationState(); + + /// Increments the track entry times, setting queued animations as current if needed + /// @param delta delta time + void update(float delta); + + /// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the + /// animation state can be applied to multiple skeletons to pose them identically. + bool apply(Skeleton& skeleton); + + /// Removes all animations from all tracks, leaving skeletons in their previous pose. + /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + void clearTracks(); + + /// Removes all animations from the tracks, leaving skeletons in their previous pose. + /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + void clearTrack(size_t trackIndex); + + /// Sets an animation by name. setAnimation(int, Animation, bool) + TrackEntry* setAnimation(size_t trackIndex, const String& animationName, bool loop); + + /// Sets the current animation for a track, discarding any queued animations. + /// @param loop If true, the animation will repeat. + /// If false, it will not, instead its last frame is applied if played beyond its duration. + /// In either case TrackEntry.TrackEnd determines when the track is cleared. + /// @return + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after AnimationState.Dispose. + TrackEntry* setAnimation(size_t trackIndex, Animation* animation, bool loop); + + /// Queues an animation by name. + /// addAnimation(int, Animation, bool, float) + TrackEntry* addAnimation(size_t trackIndex, const String& animationName, bool loop, float delay); + + /// Adds an animation to be played delay seconds after the current or last queued animation + /// for a track. If the track is empty, it is equivalent to calling setAnimation. + /// @param delay + /// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation + /// duration of the previous track minus any mix duration plus the negative delay. + /// + /// @return A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after AnimationState.Dispose + TrackEntry* addAnimation(size_t trackIndex, Animation* animation, bool loop, float delay); + + /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. + TrackEntry* setEmptyAnimation(size_t trackIndex, float mixDuration); + + /// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the + /// specified mix duration. + /// @return + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after AnimationState.Dispose. + /// + /// @param trackIndex Track number. + /// @param mixDuration Mix duration. + /// @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation + /// duration of the previous track minus any mix duration plus the negative delay. + TrackEntry* addEmptyAnimation(size_t trackIndex, float mixDuration, float delay); + + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. + void setEmptyAnimations(float mixDuration); + + /// @return The track entry for the animation currently playing on the track, or NULL if no animation is currently playing. + TrackEntry* getCurrent(size_t trackIndex); + + AnimationStateData* getData(); + + /// A list of tracks that have animations, which may contain NULLs. + Vector& getTracks(); + + float getTimeScale(); + void setTimeScale(float inValue); + + void setListener(AnimationStateListener listener); + void setListener(AnimationStateListenerObject* listener); + + void disableQueue(); + void enableQueue(); + +private: + AnimationStateData* _data; + + Pool _trackEntryPool; + Vector _tracks; + Vector _events; + EventQueue* _queue; + + HashMap _propertyIDs; + bool _animationsChanged; + + AnimationStateListener _listener; + AnimationStateListenerObject* _listenerObject; + + float _timeScale; + + static Animation* getEmptyAnimation(); + + static void applyRotateTimeline(RotateTimeline* rotateTimeline, Skeleton& skeleton, float time, float alpha, MixBlend pose, Vector& timelinesRotation, size_t i, bool firstFrame); + + /// Returns true when all mixing from entries are complete. + bool updateMixingFrom(TrackEntry* to, float delta); + + float applyMixingFrom(TrackEntry* to, Skeleton& skeleton, MixBlend currentPose); + + void queueEvents(TrackEntry* entry, float animationTime); + + /// Sets the active TrackEntry for a given track number. + void setCurrent(size_t index, TrackEntry* current, bool interrupt); + + TrackEntry* expandToIndex(size_t index); + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// @param last May be NULL. + TrackEntry* newTrackEntry(size_t trackIndex, Animation* animation, bool loop, TrackEntry* last); + + /// Dispose all track entries queued after the given TrackEntry. + void disposeNext(TrackEntry* entry); + + void animationsChanged(); + + void computeHold(TrackEntry* entry); + + void computeNotLast(TrackEntry* entry); +}; +} // namespace spine + +#endif /* Spine_AnimationState_h */ diff --git a/cocos/editor-support/spine/AnimationStateData.cpp b/cocos/editor-support/spine/AnimationStateData.cpp new file mode 100644 index 0000000..f5cf08a --- /dev/null +++ b/cocos/editor-support/spine/AnimationStateData.cpp @@ -0,0 +1,85 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include +#include + +using namespace spine; + +AnimationStateData::AnimationStateData(SkeletonData *skeletonData) : _skeletonData(skeletonData), _defaultMix(0) { +} + +void AnimationStateData::setMix(const String &fromName, const String &toName, float duration) { + Animation *from = _skeletonData->findAnimation(fromName); + Animation *to = _skeletonData->findAnimation(toName); + + setMix(from, to, duration); +} + +void AnimationStateData::setMix(Animation *from, Animation *to, float duration) { + assert(from != NULL); + assert(to != NULL); + + AnimationPair key(from, to); + _animationToMixTime.put(key, duration); +} + +float AnimationStateData::getMix(Animation *from, Animation *to) { + assert(from != NULL); + assert(to != NULL); + + AnimationPair key(from, to); + + if (_animationToMixTime.containsKey(key)) return _animationToMixTime[key]; + return _defaultMix; +} + +SkeletonData *AnimationStateData::getSkeletonData() { + return _skeletonData; +} + +float AnimationStateData::getDefaultMix() { + return _defaultMix; +} + +void AnimationStateData::setDefaultMix(float inValue) { + _defaultMix = inValue; +} + +AnimationStateData::AnimationPair::AnimationPair(Animation *a1, Animation *a2) : _a1(a1), _a2(a2) { +} + +bool AnimationStateData::AnimationPair::operator==(const AnimationPair &other) const { + return _a1->_name == other._a1->_name && _a2->_name == other._a2->_name; +} diff --git a/cocos/editor-support/spine/AnimationStateData.h b/cocos/editor-support/spine/AnimationStateData.h new file mode 100644 index 0000000..95a47e2 --- /dev/null +++ b/cocos/editor-support/spine/AnimationStateData.h @@ -0,0 +1,85 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AnimationStateData_h +#define Spine_AnimationStateData_h + +#include +#include +#include + +#include + +namespace spine { +class SkeletonData; +class Animation; + +/// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. +class SP_API AnimationStateData : public SpineObject { + friend class AnimationState; + +public: + explicit AnimationStateData(SkeletonData* skeletonData); + + /// The SkeletonData to look up animations when they are specified by name. + SkeletonData* getSkeletonData(); + + /// The mix duration to use when no mix duration has been specifically defined between two animations. + float getDefaultMix(); + void setDefaultMix(float inValue); + + /// Sets a mix duration by animation names. + void setMix(const String& fromName, const String& toName, float duration); + + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + void setMix(Animation* from, Animation* to, float duration); + + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + float getMix(Animation* from, Animation* to); + +private: + class AnimationPair : public SpineObject { + public: + Animation* _a1; + Animation* _a2; + + explicit AnimationPair(Animation* a1 = NULL, Animation* a2 = NULL); + + bool operator==(const AnimationPair& other) const; + }; + + SkeletonData* _skeletonData; + float _defaultMix; + HashMap _animationToMixTime; +}; +} // namespace spine + +#endif /* Spine_AnimationStateData_h */ diff --git a/cocos/editor-support/spine/Atlas.cpp b/cocos/editor-support/spine/Atlas.cpp new file mode 100644 index 0000000..f26fc1d --- /dev/null +++ b/cocos/editor-support/spine/Atlas.cpp @@ -0,0 +1,332 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include +#include + +#include + +using namespace spine; + +Atlas::Atlas(const String &path, TextureLoader *textureLoader, bool createTexture) : _textureLoader(textureLoader) { + int dirLength; + char *dir; + int length; + const char *data; + + /* Get directory from atlas path. */ + const char *lastForwardSlash = strrchr(path.buffer(), '/'); + const char *lastBackwardSlash = strrchr(path.buffer(), '\\'); + const char *lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash; + if (lastSlash == path) lastSlash++; /* Never drop starting slash. */ + dirLength = (int)(lastSlash ? lastSlash - path.buffer() : 0); + dir = SpineExtension::calloc(dirLength + 1, __FILE__, __LINE__); + memcpy(dir, path.buffer(), dirLength); + dir[dirLength] = '\0'; + + data = SpineExtension::readFile(path, &length); + if (data) { + load(data, length, dir, createTexture); + } + + SpineExtension::free(data, __FILE__, __LINE__); + SpineExtension::free(dir, __FILE__, __LINE__); +} + +Atlas::Atlas(const char *data, int length, const char *dir, TextureLoader *textureLoader, bool createTexture) : _textureLoader( + textureLoader) { + load(data, length, dir, createTexture); +} + +Atlas::~Atlas() { + if (_textureLoader) { + for (size_t i = 0, n = _pages.size(); i < n; ++i) { + _textureLoader->unload(_pages[i]->getRendererObject()); + } + } + ContainerUtil::cleanUpVectorOfPointers(_pages); + ContainerUtil::cleanUpVectorOfPointers(_regions); +} + +void Atlas::flipV() { + for (size_t i = 0, n = _regions.size(); i < n; ++i) { + AtlasRegion *regionP = _regions[i]; + AtlasRegion ®ion = *regionP; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } +} + +AtlasRegion *Atlas::findRegion(const String &name) { + for (size_t i = 0, n = _regions.size(); i < n; ++i) + if (_regions[i]->name == name) return _regions[i]; + return NULL; +} + +Vector &Atlas::getPages() { + return _pages; +} + +void Atlas::load(const char *begin, int length, const char *dir, bool createTexture) { + static const char *formatNames[] = {"", "Alpha", "Intensity", "LuminanceAlpha", "RGB565", "RGBA4444", "RGB888", "RGBA8888"}; + static const char *textureFilterNames[] = {"", "Nearest", "Linear", "MipMap", "MipMapNearestNearest", "MipMapLinearNearest", + "MipMapNearestLinear", "MipMapLinearLinear"}; + + int count; + const char *end = begin + length; + int dirLength = (int)strlen(dir); + int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/' && dir[dirLength - 1] != '\\'; + + AtlasPage *page = NULL; + Str str; + Str tuple[4]; + + while (readLine(&begin, end, &str)) { + if (str.end - str.begin == 0) { + page = 0; + } else if (!page) { + char *name = mallocString(&str); + char *path = SpineExtension::calloc(dirLength + needsSlash + strlen(name) + 1, __FILE__, __LINE__); + memcpy(path, dir, dirLength); + if (needsSlash) path[dirLength] = '/'; + strcpy(path + dirLength + needsSlash, name); + + page = new (__FILE__, __LINE__) AtlasPage(String(name, true)); + + int tupleVal = readTuple(&begin, end, tuple); + assert(tupleVal == 2); + + /* size is only optional for an atlas packed with an old TexturePacker. */ + page->width = toInt(tuple); + page->height = toInt(tuple + 1); + readTuple(&begin, end, tuple); + + page->format = (Format)indexOf(formatNames, 8, tuple); + + readTuple(&begin, end, tuple); + page->minFilter = (TextureFilter)indexOf(textureFilterNames, 8, tuple); + page->magFilter = (TextureFilter)indexOf(textureFilterNames, 8, tuple + 1); + + readValue(&begin, end, &str); + + page->uWrap = TextureWrap_ClampToEdge; + page->vWrap = TextureWrap_ClampToEdge; + if (!equals(&str, "none")) { + if (str.end - str.begin == 1) { + if (*str.begin == 'x') { + page->uWrap = TextureWrap_Repeat; + } else if (*str.begin == 'y') { + page->vWrap = TextureWrap_Repeat; + } + } else if (equals(&str, "xy")) { + page->uWrap = TextureWrap_Repeat; + page->vWrap = TextureWrap_Repeat; + } + } + + if (createTexture) { + if (_textureLoader) _textureLoader->load(*page, String(path)); + SpineExtension::free(path, __FILE__, __LINE__); + } else + page->texturePath = String(path, true); + + _pages.add(page); + } else { + AtlasRegion *region = new (__FILE__, __LINE__) AtlasRegion(); + + region->page = page; + region->name = String(mallocString(&str), true); + + readValue(&begin, end, &str); + if (equals(&str, "true")) + region->degrees = 90; + else if (equals(&str, "false")) + region->degrees = 0; + else + region->degrees = toInt(&str); + region->rotate = region->degrees == 90; + + readTuple(&begin, end, tuple); + region->x = toInt(tuple); + region->y = toInt(tuple + 1); + + readTuple(&begin, end, tuple); + region->width = toInt(tuple); + region->height = toInt(tuple + 1); + + region->u = region->x / (float)page->width; + region->v = region->y / (float)page->height; + if (region->rotate) { + region->u2 = (region->x + region->height) / (float)page->width; + region->v2 = (region->y + region->width) / (float)page->height; + } else { + region->u2 = (region->x + region->width) / (float)page->width; + region->v2 = (region->y + region->height) / (float)page->height; + } + + count = readTuple(&begin, end, tuple); + assert(count); + + if (count == 4) { + /* split is optional */ + region->splits.setSize(4, 0); + region->splits[0] = toInt(tuple); + region->splits[1] = toInt(tuple + 1); + region->splits[2] = toInt(tuple + 2); + region->splits[3] = toInt(tuple + 3); + + count = readTuple(&begin, end, tuple); + assert(count); + + if (count == 4) { + /* pad is optional, but only present with splits */ + region->pads.setSize(4, 0); + region->pads[0] = toInt(tuple); + region->pads[1] = toInt(tuple + 1); + region->pads[2] = toInt(tuple + 2); + region->pads[3] = toInt(tuple + 3); + + readTuple(&begin, end, tuple); + } + } + + region->originalWidth = toInt(tuple); + region->originalHeight = toInt(tuple + 1); + + readTuple(&begin, end, tuple); + region->offsetX = (float)toInt(tuple); + region->offsetY = (float)toInt(tuple + 1); + + readValue(&begin, end, &str); + + region->index = toInt(&str); + + _regions.add(region); + } + } +} + +void Atlas::trim(Str *str) { + while (isspace((unsigned char)*str->begin) && str->begin < str->end) + (str->begin)++; + + if (str->begin == str->end) return; + + str->end--; + + while (((unsigned char)*str->end == '\r') && str->end >= str->begin) + str->end--; + + str->end++; +} + +int Atlas::readLine(const char **begin, const char *end, Str *str) { + if (*begin == end) return 0; + + str->begin = *begin; + + /* Find next delimiter. */ + while (*begin != end && **begin != '\n') + (*begin)++; + + str->end = *begin; + trim(str); + + if (*begin != end) (*begin)++; + + return 1; +} + +int Atlas::beginPast(Str *str, char c) { + const char *begin = str->begin; + while (true) { + char lastSkippedChar = *begin; + if (begin == str->end) return 0; + begin++; + if (lastSkippedChar == c) break; + } + str->begin = begin; + return 1; +} + +int Atlas::readValue(const char **begin, const char *end, Str *str) { + readLine(begin, end, str); + if (!beginPast(str, ':')) return 0; + trim(str); + return 1; +} + +int Atlas::readTuple(const char **begin, const char *end, Str tuple[]) { + int i; + Str str = {NULL, NULL}; + readLine(begin, end, &str); + if (!beginPast(&str, ':')) return 0; + + for (i = 0; i < 3; ++i) { + tuple[i].begin = str.begin; + if (!beginPast(&str, ',')) break; + tuple[i].end = str.begin - 2; + trim(&tuple[i]); + } + + tuple[i].begin = str.begin; + tuple[i].end = str.end; + trim(&tuple[i]); + + return i + 1; +} + +char *Atlas::mallocString(Str *str) { + int length = (int)(str->end - str->begin); + char *string = SpineExtension::calloc(length + 1, __FILE__, __LINE__); + memcpy(string, str->begin, length); + string[length] = '\0'; + return string; +} + +int Atlas::indexOf(const char **array, int count, Str *str) { + int length = (int)(str->end - str->begin); + int i; + for (i = count - 1; i >= 0; i--) + if (strncmp(array[i], str->begin, length) == 0) return i; + return 0; +} + +int Atlas::equals(Str *str, const char *other) { + return strncmp(other, str->begin, str->end - str->begin) == 0; +} + +int Atlas::toInt(Str *str) { + return (int)strtol(str->begin, (char **)&str->end, 10); +} diff --git a/cocos/editor-support/spine/Atlas.h b/cocos/editor-support/spine/Atlas.h new file mode 100644 index 0000000..83cd413 --- /dev/null +++ b/cocos/editor-support/spine/Atlas.h @@ -0,0 +1,155 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Atlas_h +#define Spine_Atlas_h + +#include +#include +#include +#include +#include + +namespace spine { +enum Format { + Format_Alpha, + Format_Intensity, + Format_LuminanceAlpha, + Format_RGB565, + Format_RGBA4444, + Format_RGB888, + Format_RGBA8888 +}; + +enum TextureFilter { + TextureFilter_Unknown, + TextureFilter_Nearest, + TextureFilter_Linear, + TextureFilter_MipMap, + TextureFilter_MipMapNearestNearest, + TextureFilter_MipMapLinearNearest, + TextureFilter_MipMapNearestLinear, + TextureFilter_MipMapLinearLinear +}; + +enum TextureWrap { + TextureWrap_MirroredRepeat, + TextureWrap_ClampToEdge, + TextureWrap_Repeat +}; + +class SP_API AtlasPage : public SpineObject, public HasRendererObject { +public: + String name; + String texturePath; + Format format; + TextureFilter minFilter; + TextureFilter magFilter; + TextureWrap uWrap; + TextureWrap vWrap; + int width, height; + + explicit AtlasPage(const String &inName) : name(inName), format(Format_RGBA8888), minFilter(TextureFilter_Nearest), magFilter(TextureFilter_Nearest), uWrap(TextureWrap_ClampToEdge), vWrap(TextureWrap_ClampToEdge), width(0), height(0) { + } +}; + +class SP_API AtlasRegion : public SpineObject { +public: + AtlasPage *page; + String name; + int x, y, width, height; + float u, v, u2, v2; + float offsetX, offsetY; + int originalWidth, originalHeight; + int index; + bool rotate; + int degrees; + Vector splits; + Vector pads; +}; + +class TextureLoader; + +class SP_API Atlas : public SpineObject { +public: + Atlas(const String &path, TextureLoader *textureLoader, bool createTexture = true); + + Atlas(const char *data, int length, const char *dir, TextureLoader *textureLoader, bool createTexture = true); + + ~Atlas(); + + void flipV(); + + /// Returns the first region found with the specified name. This method uses String comparison to find the region, so the result + /// should be cached rather than calling this method multiple times. + /// @return The region, or NULL. + AtlasRegion *findRegion(const String &name); + + Vector &getPages(); + +private: + Vector _pages; + Vector _regions; + TextureLoader *_textureLoader; + + void load(const char *begin, int length, const char *dir, bool createTexture); + + class Str { + public: + const char *begin; + const char *end; + }; + + static void trim(Str *str); + + /// Tokenize string without modification. Returns 0 on failure + static int readLine(const char **begin, const char *end, Str *str); + + /// Moves str->begin past the first occurence of c. Returns 0 on failure + static int beginPast(Str *str, char c); + + /// Returns 0 on failure + static int readValue(const char **begin, const char *end, Str *str); + + /// Returns the number of tuple values read (1, 2, 4, or 0 for failure) + static int readTuple(const char **begin, const char *end, Str tuple[]); + + static char *mallocString(Str *str); + + static int indexOf(const char **array, int count, Str *str); + + static int equals(Str *str, const char *other); + + static int toInt(Str *str); + + static Atlas *abortAtlas(Atlas *atlas); +}; +} // namespace spine + +#endif /* Spine_Atlas_h */ diff --git a/cocos/editor-support/spine/AtlasAttachmentLoader.cpp b/cocos/editor-support/spine/AtlasAttachmentLoader.cpp new file mode 100644 index 0000000..68ee7d3 --- /dev/null +++ b/cocos/editor-support/spine/AtlasAttachmentLoader.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace spine { +RTTI_IMPL(AtlasAttachmentLoader, AttachmentLoader) + +AtlasAttachmentLoader::AtlasAttachmentLoader(Atlas *atlas) : AttachmentLoader(), _atlas(atlas) { +} + +RegionAttachment *AtlasAttachmentLoader::newRegionAttachment(Skin &skin, const String &name, const String &path) { + SP_UNUSED(skin); + + AtlasRegion *regionP = findRegion(path); + if (!regionP) return NULL; + + AtlasRegion ®ion = *regionP; + + RegionAttachment *attachmentP = new (__FILE__, __LINE__) RegionAttachment(name); + + RegionAttachment &attachment = *attachmentP; + attachment.setRendererObject(regionP); + attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment._regionOffsetX = region.offsetX; + attachment._regionOffsetY = region.offsetY; + attachment._regionWidth = (float)region.width; + attachment._regionHeight = (float)region.height; + attachment._regionOriginalWidth = (float)region.originalWidth; + attachment._regionOriginalHeight = (float)region.originalHeight; + return attachmentP; +} + +MeshAttachment *AtlasAttachmentLoader::newMeshAttachment(Skin &skin, const String &name, const String &path) { + SP_UNUSED(skin); + + AtlasRegion *regionP = findRegion(path); + if (!regionP) return NULL; + + AtlasRegion ®ion = *regionP; + + MeshAttachment *attachmentP = new (__FILE__, __LINE__) MeshAttachment(name); + + MeshAttachment &attachment = *attachmentP; + attachment.setRendererObject(regionP); + attachment._regionU = region.u; + attachment._regionV = region.v; + attachment._regionU2 = region.u2; + attachment._regionV2 = region.v2; + attachment._regionRotate = region.rotate; + attachment._regionDegrees = region.degrees; + attachment._regionOffsetX = region.offsetX; + attachment._regionOffsetY = region.offsetY; + attachment._regionWidth = (float)region.width; + attachment._regionHeight = (float)region.height; + attachment._regionOriginalWidth = (float)region.originalWidth; + attachment._regionOriginalHeight = (float)region.originalHeight; + + return attachmentP; +} + +BoundingBoxAttachment *AtlasAttachmentLoader::newBoundingBoxAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) BoundingBoxAttachment(name); +} + +PathAttachment *AtlasAttachmentLoader::newPathAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) PathAttachment(name); +} + +PointAttachment *AtlasAttachmentLoader::newPointAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) PointAttachment(name); +} + +ClippingAttachment *AtlasAttachmentLoader::newClippingAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) ClippingAttachment(name); +} + +void AtlasAttachmentLoader::configureAttachment(Attachment *attachment) { + SP_UNUSED(attachment); +} + +AtlasRegion *AtlasAttachmentLoader::findRegion(const String &name) { + return _atlas->findRegion(name); +} + +} // namespace spine diff --git a/cocos/editor-support/spine/AtlasAttachmentLoader.h b/cocos/editor-support/spine/AtlasAttachmentLoader.h new file mode 100644 index 0000000..6dbb061 --- /dev/null +++ b/cocos/editor-support/spine/AtlasAttachmentLoader.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AtlasAttachmentLoader_h +#define Spine_AtlasAttachmentLoader_h + +#include +#include +#include + +namespace spine { +class Atlas; +class AtlasRegion; + +/// An AttachmentLoader that configures attachments using texture regions from an Atlas. +/// See http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data about Loading Skeleton Data in the Spine Runtimes Guide. +class SP_API AtlasAttachmentLoader : public AttachmentLoader { +public: + RTTI_DECL + + explicit AtlasAttachmentLoader(Atlas* atlas); + + virtual RegionAttachment* newRegionAttachment(Skin& skin, const String& name, const String& path); + + virtual MeshAttachment* newMeshAttachment(Skin& skin, const String& name, const String& path); + + virtual BoundingBoxAttachment* newBoundingBoxAttachment(Skin& skin, const String& name); + + virtual PathAttachment* newPathAttachment(Skin& skin, const String& name); + + virtual PointAttachment* newPointAttachment(Skin& skin, const String& name); + + virtual ClippingAttachment* newClippingAttachment(Skin& skin, const String& name); + + virtual void configureAttachment(Attachment* attachment); + + AtlasRegion* findRegion(const String& name); + +private: + Atlas* _atlas; +}; +} // namespace spine + +#endif /* Spine_AtlasAttachmentLoader_h */ diff --git a/cocos/editor-support/spine/Attachment.cpp b/cocos/editor-support/spine/Attachment.cpp new file mode 100644 index 0000000..7172b4c --- /dev/null +++ b/cocos/editor-support/spine/Attachment.cpp @@ -0,0 +1,63 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(Attachment) + +Attachment::Attachment(const String &name) : _name(name), _refCount(0) { + assert(_name.length() > 0); +} + +Attachment::~Attachment() { +} + +const String &Attachment::getName() const { + return _name; +} + +int Attachment::getRefCount() { + return _refCount; +} + +void Attachment::reference() { + _refCount++; +} + +void Attachment::dereference() { + _refCount--; +} diff --git a/cocos/editor-support/spine/Attachment.h b/cocos/editor-support/spine/Attachment.h new file mode 100644 index 0000000..a88516d --- /dev/null +++ b/cocos/editor-support/spine/Attachment.h @@ -0,0 +1,60 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Attachment_h +#define Spine_Attachment_h + +#include +#include +#include + +namespace spine { +class SP_API Attachment : public SpineObject { + RTTI_DECL + +public: + explicit Attachment(const String &name); + + virtual ~Attachment(); + + const String &getName() const; + + virtual Attachment *copy() = 0; + + int getRefCount(); + void reference(); + void dereference(); + +private: + const String _name; + int _refCount; +}; +} // namespace spine + +#endif /* Spine_Attachment_h */ diff --git a/cocos/editor-support/spine/AttachmentLoader.cpp b/cocos/editor-support/spine/AttachmentLoader.cpp new file mode 100644 index 0000000..87fbb27 --- /dev/null +++ b/cocos/editor-support/spine/AttachmentLoader.cpp @@ -0,0 +1,52 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(AttachmentLoader) + +AttachmentLoader::AttachmentLoader() { +} + +AttachmentLoader::~AttachmentLoader() { +} diff --git a/cocos/editor-support/spine/AttachmentLoader.h b/cocos/editor-support/spine/AttachmentLoader.h new file mode 100644 index 0000000..d3d4f43 --- /dev/null +++ b/cocos/editor-support/spine/AttachmentLoader.h @@ -0,0 +1,75 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AttachmentLoader_h +#define Spine_AttachmentLoader_h + +#include +#include +#include + +namespace spine { +class Skin; +class Attachment; +class RegionAttachment; +class MeshAttachment; +class BoundingBoxAttachment; +class PathAttachment; +class PointAttachment; +class ClippingAttachment; + +class SP_API AttachmentLoader : public SpineObject { +public: + RTTI_DECL + + AttachmentLoader(); + + virtual ~AttachmentLoader(); + + /// @return May be NULL to not load any attachment. + virtual RegionAttachment* newRegionAttachment(Skin& skin, const String& name, const String& path) = 0; + + /// @return May be NULL to not load any attachment. + virtual MeshAttachment* newMeshAttachment(Skin& skin, const String& name, const String& path) = 0; + + /// @return May be NULL to not load any attachment. + virtual BoundingBoxAttachment* newBoundingBoxAttachment(Skin& skin, const String& name) = 0; + + /// @return May be NULL to not load any attachment + virtual PathAttachment* newPathAttachment(Skin& skin, const String& name) = 0; + + virtual PointAttachment* newPointAttachment(Skin& skin, const String& name) = 0; + + virtual ClippingAttachment* newClippingAttachment(Skin& skin, const String& name) = 0; + + virtual void configureAttachment(Attachment* attachment) = 0; +}; +} // namespace spine + +#endif /* Spine_AttachmentLoader_h */ diff --git a/cocos/editor-support/spine/AttachmentTimeline.cpp b/cocos/editor-support/spine/AttachmentTimeline.cpp new file mode 100644 index 0000000..eab3779 --- /dev/null +++ b/cocos/editor-support/spine/AttachmentTimeline.cpp @@ -0,0 +1,127 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(AttachmentTimeline, Timeline) + +AttachmentTimeline::AttachmentTimeline(int frameCount) : Timeline(), _slotIndex(0) { + _frames.ensureCapacity(frameCount); + _attachmentNames.ensureCapacity(frameCount); + + _frames.setSize(frameCount, 0); + + for (int i = 0; i < frameCount; ++i) { + _attachmentNames.add(String()); + } +} + +void AttachmentTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(alpha); + + assert(_slotIndex < skeleton._slots.size()); + + String *attachmentName; + Slot *slotP = skeleton._slots[_slotIndex]; + Slot &slot = *slotP; + if (!slot._bone.isActive()) return; + + if (direction == MixDirection_Out && blend == MixBlend_Setup) { + attachmentName = &slot._data._attachmentName; + slot.setAttachment(attachmentName->length() == 0 ? NULL : skeleton.getAttachment(_slotIndex, *attachmentName)); + return; + } + + if (time < _frames[0]) { + // Time is before first frame. + if (blend == MixBlend_Setup || blend == MixBlend_First) { + attachmentName = &slot._data._attachmentName; + slot.setAttachment(attachmentName->length() == 0 ? NULL : skeleton.getAttachment(_slotIndex, *attachmentName)); + } + return; + } + + size_t frameIndex; + if (time >= _frames[_frames.size() - 1]) { + // Time is after last frame. + frameIndex = _frames.size() - 1; + } else { + frameIndex = Animation::binarySearch(_frames, time, 1) - 1; + } + + attachmentName = &_attachmentNames[frameIndex]; + slot.setAttachment(attachmentName->length() == 0 ? NULL : skeleton.getAttachment(_slotIndex, *attachmentName)); +} + +int AttachmentTimeline::getPropertyId() { + return ((int)TimelineType_Attachment << 24) + _slotIndex; +} + +void AttachmentTimeline::setFrame(int frameIndex, float time, const String &attachmentName) { + _frames[frameIndex] = time; + _attachmentNames[frameIndex] = attachmentName; +} + +size_t AttachmentTimeline::getSlotIndex() { + return _slotIndex; +} + +void AttachmentTimeline::setSlotIndex(size_t inValue) { + _slotIndex = inValue; +} + +const Vector &AttachmentTimeline::getFrames() { + return _frames; +} + +const Vector &AttachmentTimeline::getAttachmentNames() { + return _attachmentNames; +} + +size_t AttachmentTimeline::getFrameCount() { + return _frames.size(); +} diff --git a/cocos/editor-support/spine/AttachmentTimeline.h b/cocos/editor-support/spine/AttachmentTimeline.h new file mode 100644 index 0000000..951add5 --- /dev/null +++ b/cocos/editor-support/spine/AttachmentTimeline.h @@ -0,0 +1,74 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AttachmentTimeline_h +#define Spine_AttachmentTimeline_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + +class Skeleton; +class Event; + +class SP_API AttachmentTimeline : public Timeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + explicit AttachmentTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frameIndex, float time, const String& attachmentName); + + size_t getSlotIndex(); + void setSlotIndex(size_t inValue); + const Vector& getFrames(); + const Vector& getAttachmentNames(); + size_t getFrameCount(); + +private: + size_t _slotIndex; + Vector _frames; + Vector _attachmentNames; +}; +} // namespace spine + +#endif /* Spine_AttachmentTimeline_h */ diff --git a/cocos/editor-support/spine/AttachmentType.h b/cocos/editor-support/spine/AttachmentType.h new file mode 100644 index 0000000..b4d12c1 --- /dev/null +++ b/cocos/editor-support/spine/AttachmentType.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AttachmentType_h +#define Spine_AttachmentType_h + +namespace spine { +enum AttachmentType { + AttachmentType_Region, + AttachmentType_Boundingbox, + AttachmentType_Mesh, + AttachmentType_Linkedmesh, + AttachmentType_Path, + AttachmentType_Point, + AttachmentType_Clipping +}; +} + +#endif /* Spine_AttachmentType_h */ diff --git a/cocos/editor-support/spine/BlendMode.h b/cocos/editor-support/spine/BlendMode.h new file mode 100644 index 0000000..5c6cbda --- /dev/null +++ b/cocos/editor-support/spine/BlendMode.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BlendMode_h +#define Spine_BlendMode_h + +namespace spine { +enum BlendMode { + BlendMode_Normal = 0, + BlendMode_Additive, + BlendMode_Multiply, + BlendMode_Screen +}; +} + +#endif /* Spine_BlendMode_h */ diff --git a/cocos/editor-support/spine/Bone.cpp b/cocos/editor-support/spine/Bone.cpp new file mode 100644 index 0000000..0e174a3 --- /dev/null +++ b/cocos/editor-support/spine/Bone.cpp @@ -0,0 +1,547 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(Bone, Updatable) + +bool Bone::yDown = false; + +void Bone::setYDown(bool inValue) { + yDown = inValue; +} + +bool Bone::isYDown() { + return yDown; +} + +Bone::Bone(BoneData &data, Skeleton &skeleton, Bone *parent) : Updatable(), + _data(data), + _skeleton(skeleton), + _parent(parent), + _x(0), + _y(0), + _rotation(0), + _scaleX(0), + _scaleY(0), + _shearX(0), + _shearY(0), + _ax(0), + _ay(0), + _arotation(0), + _ascaleX(0), + _ascaleY(0), + _ashearX(0), + _ashearY(0), + _appliedValid(false), + _a(1), + _b(0), + _worldX(0), + _c(0), + _d(1), + _worldY(0), + _sorted(false), + _active(false) { + setToSetupPose(); +} + +void Bone::update() { + updateWorldTransform(_x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY); +} + +void Bone::updateWorldTransform() { + updateWorldTransform(_x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY); +} + +void Bone::updateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { + float cosine, sine; + float pa, pb, pc, pd; + Bone *parent = _parent; + + _ax = x; + _ay = y; + _arotation = rotation; + _ascaleX = scaleX; + _ascaleY = scaleY; + _ashearX = shearX; + _ashearY = shearY; + _appliedValid = true; + + if (!parent) { /* Root bone. */ + float rotationY = rotation + 90 + shearY; + float sx = _skeleton.getScaleX(); + float sy = _skeleton.getScaleY(); + _a = MathUtil::cosDeg(rotation + shearX) * scaleX * sx; + _b = MathUtil::cosDeg(rotationY) * scaleY * sx; + _c = MathUtil::sinDeg(rotation + shearX) * scaleX * sy; + _d = MathUtil::sinDeg(rotationY) * scaleY * sy; + _worldX = x * sx + _skeleton.getX(); + _worldY = y * sy + _skeleton.getY(); + return; + } + + pa = parent->_a; + pb = parent->_b; + pc = parent->_c; + pd = parent->_d; + + _worldX = pa * x + pb * y + parent->_worldX; + _worldY = pc * x + pd * y + parent->_worldY; + + switch (_data.getTransformMode()) { + case TransformMode_Normal: { + float rotationY = rotation + 90 + shearY; + float la = MathUtil::cosDeg(rotation + shearX) * scaleX; + float lb = MathUtil::cosDeg(rotationY) * scaleY; + float lc = MathUtil::sinDeg(rotation + shearX) * scaleX; + float ld = MathUtil::sinDeg(rotationY) * scaleY; + _a = pa * la + pb * lc; + _b = pa * lb + pb * ld; + _c = pc * la + pd * lc; + _d = pc * lb + pd * ld; + return; + } + case TransformMode_OnlyTranslation: { + float rotationY = rotation + 90 + shearY; + _a = MathUtil::cosDeg(rotation + shearX) * scaleX; + _b = MathUtil::cosDeg(rotationY) * scaleY; + _c = MathUtil::sinDeg(rotation + shearX) * scaleX; + _d = MathUtil::sinDeg(rotationY) * scaleY; + break; + } + case TransformMode_NoRotationOrReflection: { + float s = pa * pa + pc * pc; + float prx, rx, ry, la, lb, lc, ld; + if (s > 0.0001f) { + s = MathUtil::abs(pa * pd - pb * pc) / s; + pb = pc * s; + pd = pa * s; + prx = MathUtil::atan2(pc, pa) * MathUtil::Rad_Deg; + } else { + pa = 0; + pc = 0; + prx = 90 - MathUtil::atan2(pd, pb) * MathUtil::Rad_Deg; + } + rx = rotation + shearX - prx; + ry = rotation + shearY - prx + 90; + la = MathUtil::cosDeg(rx) * scaleX; + lb = MathUtil::cosDeg(ry) * scaleY; + lc = MathUtil::sinDeg(rx) * scaleX; + ld = MathUtil::sinDeg(ry) * scaleY; + _a = pa * la - pb * lc; + _b = pa * lb - pb * ld; + _c = pc * la + pd * lc; + _d = pc * lb + pd * ld; + break; + } + case TransformMode_NoScale: + case TransformMode_NoScaleOrReflection: { + float za, zc, s; + float r, zb, zd, la, lb, lc, ld; + cosine = MathUtil::cosDeg(rotation); + sine = MathUtil::sinDeg(rotation); + za = (pa * cosine + pb * sine) / _skeleton.getScaleX(); + zc = (pc * cosine + pd * sine) / _skeleton.getScaleY(); + s = MathUtil::sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = MathUtil::sqrt(za * za + zc * zc); + if (_data.getTransformMode() == TransformMode_NoScale && (pa * pd - pb * pc < 0) != (_skeleton.getScaleX() < 0 != _skeleton.getScaleY() < 0)) + s = -s; + r = MathUtil::Pi / 2 + MathUtil::atan2(zc, za); + zb = MathUtil::cos(r) * s; + zd = MathUtil::sin(r) * s; + la = MathUtil::cosDeg(shearX) * scaleX; + lb = MathUtil::cosDeg(90 + shearY) * scaleY; + lc = MathUtil::sinDeg(shearX) * scaleX; + ld = MathUtil::sinDeg(90 + shearY) * scaleY; + _a = za * la + zb * lc; + _b = za * lb + zb * ld; + _c = zc * la + zd * lc; + _d = zc * lb + zd * ld; + break; + } + } + _a *= _skeleton.getScaleX(); + _b *= _skeleton.getScaleX(); + _c *= _skeleton.getScaleY(); + _d *= _skeleton.getScaleY(); +} + +void Bone::setToSetupPose() { + BoneData &data = _data; + _x = data.getX(); + _y = data.getY(); + _rotation = data.getRotation(); + _scaleX = data.getScaleX(); + _scaleY = data.getScaleY(); + _shearX = data.getShearX(); + _shearY = data.getShearY(); +} + +void Bone::worldToLocal(float worldX, float worldY, float &outLocalX, float &outLocalY) { + float a = _a; + float b = _b; + float c = _c; + float d = _d; + + float invDet = 1 / (a * d - b * c); + float x = worldX - _worldX; + float y = worldY - _worldY; + + outLocalX = (x * d * invDet - y * b * invDet); + outLocalY = (y * a * invDet - x * c * invDet); +} + +void Bone::localToWorld(float localX, float localY, float &outWorldX, float &outWorldY) { + outWorldX = localX * _a + localY * _b + _worldX; + outWorldY = localX * _c + localY * _d + _worldY; +} + +float Bone::worldToLocalRotation(float worldRotation) { + float sin = MathUtil::sinDeg(worldRotation); + float cos = MathUtil::cosDeg(worldRotation); + + return MathUtil::atan2(_a * sin - _c * cos, _d * cos - _b * sin) * MathUtil::Rad_Deg + this->_rotation - this->_shearX; +} + +float Bone::localToWorldRotation(float localRotation) { + localRotation -= this->_rotation - this->_shearX; + float sin = MathUtil::sinDeg(localRotation); + float cos = MathUtil::cosDeg(localRotation); + + return MathUtil::atan2(cos * _c + sin * _d, cos * _a + sin * _b) * MathUtil::Rad_Deg; +} + +void Bone::rotateWorld(float degrees) { + float a = _a; + float b = _b; + float c = _c; + float d = _d; + + float cos = MathUtil::cosDeg(degrees); + float sin = MathUtil::sinDeg(degrees); + + _a = cos * a - sin * c; + _b = cos * b - sin * d; + _c = sin * a + cos * c; + _d = sin * b + cos * d; + + _appliedValid = false; +} + +float Bone::getWorldToLocalRotationX() { + Bone *parent = _parent; + if (!parent) { + return _arotation; + } + + float pa = parent->_a; + float pb = parent->_b; + float pc = parent->_c; + float pd = parent->_d; + float a = _a; + float c = _c; + + return MathUtil::atan2(pa * c - pc * a, pd * a - pb * c) * MathUtil::Rad_Deg; +} + +float Bone::getWorldToLocalRotationY() { + Bone *parent = _parent; + if (!parent) { + return _arotation; + } + + float pa = parent->_a; + float pb = parent->_b; + float pc = parent->_c; + float pd = parent->_d; + float b = _b; + float d = _d; + + return MathUtil::atan2(pa * d - pc * b, pd * b - pb * d) * MathUtil::Rad_Deg; +} + +BoneData &Bone::getData() { + return _data; +} + +Skeleton &Bone::getSkeleton() { + return _skeleton; +} + +Bone *Bone::getParent() { + return _parent; +} + +Vector &Bone::getChildren() { + return _children; +} + +float Bone::getX() { + return _x; +} + +void Bone::setX(float inValue) { + _x = inValue; +} + +float Bone::getY() { + return _y; +} + +void Bone::setY(float inValue) { + _y = inValue; +} + +float Bone::getRotation() { + return _rotation; +} + +void Bone::setRotation(float inValue) { + _rotation = inValue; +} + +float Bone::getScaleX() { + return _scaleX; +} + +void Bone::setScaleX(float inValue) { + _scaleX = inValue; +} + +float Bone::getScaleY() { + return _scaleY; +} + +void Bone::setScaleY(float inValue) { + _scaleY = inValue; +} + +float Bone::getShearX() { + return _shearX; +} + +void Bone::setShearX(float inValue) { + _shearX = inValue; +} + +float Bone::getShearY() { + return _shearY; +} + +void Bone::setShearY(float inValue) { + _shearY = inValue; +} + +float Bone::getAppliedRotation() { + return _arotation; +} + +void Bone::setAppliedRotation(float inValue) { + _arotation = inValue; +} + +float Bone::getAX() { + return _ax; +} + +void Bone::setAX(float inValue) { + _ax = inValue; +} + +float Bone::getAY() { + return _ay; +} + +void Bone::setAY(float inValue) { + _ay = inValue; +} + +float Bone::getAScaleX() { + return _ascaleX; +} + +void Bone::setAScaleX(float inValue) { + _ascaleX = inValue; +} + +float Bone::getAScaleY() { + return _ascaleY; +} + +void Bone::setAScaleY(float inValue) { + _ascaleY = inValue; +} + +float Bone::getAShearX() { + return _ashearX; +} + +void Bone::setAShearX(float inValue) { + _ashearX = inValue; +} + +float Bone::getAShearY() { + return _ashearY; +} + +void Bone::setAShearY(float inValue) { + _ashearY = inValue; +} + +float Bone::getA() { + return _a; +} + +void Bone::setA(float inValue) { + _a = inValue; +} + +float Bone::getB() { + return _b; +} + +void Bone::setB(float inValue) { + _b = inValue; +} + +float Bone::getC() { + return _c; +} + +void Bone::setC(float inValue) { + _c = inValue; +} + +float Bone::getD() { + return _d; +} + +void Bone::setD(float inValue) { + _d = inValue; +} + +float Bone::getWorldX() { + return _worldX; +} + +void Bone::setWorldX(float inValue) { + _worldX = inValue; +} + +float Bone::getWorldY() { + return _worldY; +} + +void Bone::setWorldY(float inValue) { + _worldY = inValue; +} + +float Bone::getWorldRotationX() { + return MathUtil::atan2(_c, _a) * MathUtil::MathUtil::Rad_Deg; +} + +float Bone::getWorldRotationY() { + return MathUtil::atan2(_d, _b) * MathUtil::Rad_Deg; +} + +float Bone::getWorldScaleX() { + return MathUtil::sqrt(_a * _a + _c * _c); +} + +float Bone::getWorldScaleY() { + return MathUtil::sqrt(_b * _b + _d * _d); +} + +bool Bone::isAppliedValid() { + return _appliedValid; +} +void Bone::setAppliedValid(bool valid) { + _appliedValid = valid; +} + +void Bone::updateAppliedTransform() { + Bone *parent = _parent; + _appliedValid = 1; + if (!parent) { + _ax = _worldX; + _ay = _worldY; + _arotation = MathUtil::atan2(_c, _a) * MathUtil::Rad_Deg; + _ascaleX = MathUtil::sqrt(_a * _a + _c * _c); + _ascaleY = MathUtil::sqrt(_b * _b + _d * _d); + _ashearX = 0; + _ashearY = MathUtil::atan2(_a * _b + _c * _d, _a * _d - _b * _c) * MathUtil::Rad_Deg; + } else { + float pa = parent->_a, pb = parent->_b, pc = parent->_c, pd = parent->_d; + float pid = 1 / (pa * pd - pb * pc); + float dx = _worldX - parent->_worldX, dy = _worldY - parent->_worldY; + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * _a - ib * _c; + float rb = ia * _b - ib * _d; + float rc = id * _c - ic * _a; + float rd = id * _d - ic * _b; + _ax = (dx * pd * pid - dy * pb * pid); + _ay = (dy * pa * pid - dx * pc * pid); + _ashearX = 0; + _ascaleX = MathUtil::sqrt(ra * ra + rc * rc); + if (_ascaleX > 0.0001f) { + float det = ra * rd - rb * rc; + _ascaleY = det / _ascaleX; + _ashearY = MathUtil::atan2(ra * rb + rc * rd, det) * MathUtil::Rad_Deg; + _arotation = MathUtil::atan2(rc, ra) * MathUtil::Rad_Deg; + } else { + _ascaleX = 0; + _ascaleY = MathUtil::sqrt(rb * rb + rd * rd); + _ashearY = 0; + _arotation = 90 - MathUtil::atan2(rd, rb) * MathUtil::Rad_Deg; + } + } +} + +bool Bone::isActive() { + return _active; +} + +void Bone::setActive(bool inValue) { + _active = inValue; +} diff --git a/cocos/editor-support/spine/Bone.h b/cocos/editor-support/spine/Bone.h new file mode 100644 index 0000000..5e3b81f --- /dev/null +++ b/cocos/editor-support/spine/Bone.h @@ -0,0 +1,251 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Bone_h +#define Spine_Bone_h + +#include +#include +#include + +namespace spine { +class BoneData; + +class Skeleton; + +/// Stores a bone's current pose. +/// +/// A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a +/// local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a +/// constraint or application code modifies the world transform after it was computed from the local transform. +class SP_API Bone : public Updatable { + friend class AnimationState; + + friend class RotateTimeline; + + friend class IkConstraint; + + friend class TransformConstraint; + + friend class VertexAttachment; + + friend class PathConstraint; + + friend class Skeleton; + + friend class RegionAttachment; + + friend class PointAttachment; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TranslateTimeline; + + RTTI_DECL + +public: + static void setYDown(bool inValue); + + static bool isYDown(); + + /// @param parent May be NULL. + Bone(BoneData &data, Skeleton &skeleton, Bone *parent = NULL); + + /// Same as updateWorldTransform. This method exists for Bone to implement Spine::Updatable. + virtual void update(); + + /// Computes the world transform using the parent bone and this bone's local transform. + void updateWorldTransform(); + + /// Computes the world transform using the parent bone and the specified local transform. + void updateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY); + + void setToSetupPose(); + + void worldToLocal(float worldX, float worldY, float &outLocalX, float &outLocalY); + + void localToWorld(float localX, float localY, float &outWorldX, float &outWorldY); + + float worldToLocalRotation(float worldRotation); + + float localToWorldRotation(float localRotation); + + /// Rotates the world transform the specified amount and sets isAppliedValid to false. + /// @param degrees Degrees. + void rotateWorld(float degrees); + + float getWorldToLocalRotationX(); + + float getWorldToLocalRotationY(); + + BoneData &getData(); + + Skeleton &getSkeleton(); + + Bone *getParent(); + + Vector &getChildren(); + + /// The local X translation. + float getX(); + + void setX(float inValue); + + /// The local Y translation. + float getY(); + + void setY(float inValue); + + /// The local rotation. + float getRotation(); + + void setRotation(float inValue); + + /// The local scaleX. + float getScaleX(); + + void setScaleX(float inValue); + + /// The local scaleY. + float getScaleY(); + + void setScaleY(float inValue); + + /// The local shearX. + float getShearX(); + + void setShearX(float inValue); + + /// The local shearY. + float getShearY(); + + void setShearY(float inValue); + + /// The rotation, as calculated by any constraints. + float getAppliedRotation(); + + void setAppliedRotation(float inValue); + + /// The applied local x translation. + float getAX(); + + void setAX(float inValue); + + /// The applied local y translation. + float getAY(); + + void setAY(float inValue); + + /// The applied local scaleX. + float getAScaleX(); + + void setAScaleX(float inValue); + + /// The applied local scaleY. + float getAScaleY(); + + void setAScaleY(float inValue); + + /// The applied local shearX. + float getAShearX(); + + void setAShearX(float inValue); + + /// The applied local shearY. + float getAShearY(); + + void setAShearY(float inValue); + + float getA(); + + void setA(float inValue); + + float getB(); + + void setB(float inValue); + + float getC(); + + void setC(float inValue); + + float getD(); + + void setD(float inValue); + + float getWorldX(); + + void setWorldX(float inValue); + + float getWorldY(); + + void setWorldY(float inValue); + + float getWorldRotationX(); + + float getWorldRotationY(); + + /// Returns the magnitide (always positive) of the world scale X. + float getWorldScaleX(); + + /// Returns the magnitide (always positive) of the world scale Y. + float getWorldScaleY(); + + bool isAppliedValid(); + void setAppliedValid(bool valid); + + bool isActive(); + + void setActive(bool inValue); + +private: + static bool yDown; + + BoneData &_data; + Skeleton &_skeleton; + Bone *_parent; + Vector _children; + float _x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY; + float _ax, _ay, _arotation, _ascaleX, _ascaleY, _ashearX, _ashearY; + bool _appliedValid; + float _a, _b, _worldX; + float _c, _d, _worldY; + bool _sorted; + bool _active; + + /// Computes the individual applied transform values from the world transform. This can be useful to perform processing using + /// the applied transform after the world transform has been modified directly (eg, by a constraint).. + /// + /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. + void updateAppliedTransform(); +}; +} // namespace spine + +#endif /* Spine_Bone_h */ diff --git a/cocos/editor-support/spine/BoneData.cpp b/cocos/editor-support/spine/BoneData.cpp new file mode 100644 index 0000000..c6720b7 --- /dev/null +++ b/cocos/editor-support/spine/BoneData.cpp @@ -0,0 +1,147 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +using namespace spine; + +BoneData::BoneData(int index, const String &name, BoneData *parent) : _index(index), + _name(name), + _parent(parent), + _length(0), + _x(0), + _y(0), + _rotation(0), + _scaleX(1), + _scaleY(1), + _shearX(0), + _shearY(0), + _transformMode(TransformMode_Normal), + _skinRequired(false) { + assert(index >= 0); + assert(_name.length() > 0); +} + +int BoneData::getIndex() { + return _index; +} + +const String &BoneData::getName() { + return _name; +} + +BoneData *BoneData::getParent() { + return _parent; +} + +float BoneData::getLength() { + return _length; +} + +void BoneData::setLength(float inValue) { + _length = inValue; +} + +float BoneData::getX() { + return _x; +} + +void BoneData::setX(float inValue) { + _x = inValue; +} + +float BoneData::getY() { + return _y; +} + +void BoneData::setY(float inValue) { + _y = inValue; +} + +float BoneData::getRotation() { + return _rotation; +} + +void BoneData::setRotation(float inValue) { + _rotation = inValue; +} + +float BoneData::getScaleX() { + return _scaleX; +} + +void BoneData::setScaleX(float inValue) { + _scaleX = inValue; +} + +float BoneData::getScaleY() { + return _scaleY; +} + +void BoneData::setScaleY(float inValue) { + _scaleY = inValue; +} + +float BoneData::getShearX() { + return _shearX; +} + +void BoneData::setShearX(float inValue) { + _shearX = inValue; +} + +float BoneData::getShearY() { + return _shearY; +} + +void BoneData::setShearY(float inValue) { + _shearY = inValue; +} + +TransformMode BoneData::getTransformMode() { + return _transformMode; +} + +void BoneData::setTransformMode(TransformMode inValue) { + _transformMode = inValue; +} + +bool BoneData::isSkinRequired() { + return _skinRequired; +} + +void BoneData::setSkinRequired(bool inValue) { + _skinRequired = inValue; +} diff --git a/cocos/editor-support/spine/BoneData.h b/cocos/editor-support/spine/BoneData.h new file mode 100644 index 0000000..1ae439b --- /dev/null +++ b/cocos/editor-support/spine/BoneData.h @@ -0,0 +1,123 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BoneData_h +#define Spine_BoneData_h + +#include +#include +#include + +namespace spine { +class SP_API BoneData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + + friend class RotateTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TranslateTimeline; + +public: + BoneData(int index, const String &name, BoneData *parent = NULL); + + /// The index of the bone in Skeleton.Bones + int getIndex(); + + /// The name of the bone, which is unique within the skeleton. + const String &getName(); + + /// May be NULL. + BoneData *getParent(); + + float getLength(); + + void setLength(float inValue); + + /// Local X translation. + float getX(); + + void setX(float inValue); + + /// Local Y translation. + float getY(); + + void setY(float inValue); + + /// Local rotation. + float getRotation(); + + void setRotation(float inValue); + + /// Local scaleX. + float getScaleX(); + + void setScaleX(float inValue); + + /// Local scaleY. + float getScaleY(); + + void setScaleY(float inValue); + + /// Local shearX. + float getShearX(); + + void setShearX(float inValue); + + /// Local shearY. + float getShearY(); + + void setShearY(float inValue); + + /// The transform mode for how parent world transforms affect this bone. + TransformMode getTransformMode(); + + void setTransformMode(TransformMode inValue); + + bool isSkinRequired(); + void setSkinRequired(bool inValue); + +private: + const int _index; + const String _name; + BoneData *_parent; + float _length; + float _x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY; + TransformMode _transformMode; + bool _skinRequired; +}; +} // namespace spine + +#endif /* Spine_BoneData_h */ diff --git a/cocos/editor-support/spine/BoundingBoxAttachment.cpp b/cocos/editor-support/spine/BoundingBoxAttachment.cpp new file mode 100644 index 0000000..1ff2e5f --- /dev/null +++ b/cocos/editor-support/spine/BoundingBoxAttachment.cpp @@ -0,0 +1,47 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +using namespace spine; + +RTTI_IMPL(BoundingBoxAttachment, VertexAttachment) + +BoundingBoxAttachment::BoundingBoxAttachment(const String& name) : VertexAttachment(name) { +} + +Attachment* BoundingBoxAttachment::copy() { + BoundingBoxAttachment* copy = new (__FILE__, __LINE__) BoundingBoxAttachment(getName()); + copyTo(copy); + return copy; +} diff --git a/cocos/editor-support/spine/BoundingBoxAttachment.h b/cocos/editor-support/spine/BoundingBoxAttachment.h new file mode 100644 index 0000000..d198557 --- /dev/null +++ b/cocos/editor-support/spine/BoundingBoxAttachment.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BoundingBoxAttachment_h +#define Spine_BoundingBoxAttachment_h + +#include +#include + +namespace spine { +/// Attachment that has a polygon for bounds checking. +class SP_API BoundingBoxAttachment : public VertexAttachment { + RTTI_DECL + + explicit BoundingBoxAttachment(const String& name); + + virtual Attachment* copy(); +}; +} // namespace spine + +#endif /* Spine_BoundingBoxAttachment_h */ diff --git a/cocos/editor-support/spine/ClippingAttachment.cpp b/cocos/editor-support/spine/ClippingAttachment.cpp new file mode 100644 index 0000000..e8ff042 --- /dev/null +++ b/cocos/editor-support/spine/ClippingAttachment.cpp @@ -0,0 +1,58 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +using namespace spine; + +RTTI_IMPL(ClippingAttachment, VertexAttachment) + +ClippingAttachment::ClippingAttachment(const String &name) : VertexAttachment(name), _endSlot(NULL) { +} + +SlotData *ClippingAttachment::getEndSlot() { + return _endSlot; +} + +void ClippingAttachment::setEndSlot(SlotData *inValue) { + _endSlot = inValue; +} + +Attachment *ClippingAttachment::copy() { + ClippingAttachment *copy = new (__FILE__, __LINE__) ClippingAttachment(getName()); + copyTo(copy); + copy->_endSlot = _endSlot; + return copy; +} diff --git a/cocos/editor-support/spine/ClippingAttachment.h b/cocos/editor-support/spine/ClippingAttachment.h new file mode 100644 index 0000000..bd21e22 --- /dev/null +++ b/cocos/editor-support/spine/ClippingAttachment.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ClippingAttachment_h +#define Spine_ClippingAttachment_h + +#include + +namespace spine { +class SlotData; + +class SP_API ClippingAttachment : public VertexAttachment { + friend class SkeletonBinary; + friend class SkeletonJson; + + friend class SkeletonClipping; + + RTTI_DECL + +public: + explicit ClippingAttachment(const String& name); + + SlotData* getEndSlot(); + void setEndSlot(SlotData* inValue); + + virtual Attachment* copy(); + +private: + SlotData* _endSlot; +}; +} // namespace spine + +#endif /* Spine_ClippingAttachment_h */ diff --git a/cocos/editor-support/spine/Color.h b/cocos/editor-support/spine/Color.h new file mode 100644 index 0000000..f56b5c8 --- /dev/null +++ b/cocos/editor-support/spine/Color.h @@ -0,0 +1,93 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_COLOR_H +#define SPINE_COLOR_H + +#include + +namespace spine { +class SP_API Color : public SpineObject { +public: + Color() : r(0), g(0), b(0), a(0) { + } + + Color(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) { + clamp(); + } + + inline Color &set(float _r, float _g, float _b, float _a) { + this->r = _r; + this->g = _g; + this->b = _b; + this->a = _a; + clamp(); + return *this; + } + + inline Color &set(const Color &other) { + r = other.r; + g = other.g; + b = other.b; + a = other.a; + clamp(); + return *this; + } + + inline Color &add(float _r, float _g, float _b, float _a) { + this->r += _r; + this->g += _g; + this->b += _b; + this->a += _a; + clamp(); + return *this; + } + + inline Color &add(const Color &other) { + r += other.r; + g += other.g; + b += other.b; + a += other.a; + clamp(); + return *this; + } + + inline Color &clamp() { + r = MathUtil::clamp(this->r, 0, 1); + g = MathUtil::clamp(this->g, 0, 1); + b = MathUtil::clamp(this->b, 0, 1); + a = MathUtil::clamp(this->a, 0, 1); + return *this; + } + + float r, g, b, a; +}; +} // namespace spine + +#endif //SPINE_COLOR_H diff --git a/cocos/editor-support/spine/ColorTimeline.cpp b/cocos/editor-support/spine/ColorTimeline.cpp new file mode 100644 index 0000000..988fed2 --- /dev/null +++ b/cocos/editor-support/spine/ColorTimeline.cpp @@ -0,0 +1,144 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(ColorTimeline, CurveTimeline) + +const int ColorTimeline::ENTRIES = 5; +const int ColorTimeline::PREV_TIME = -5; +const int ColorTimeline::PREV_R = -4; +const int ColorTimeline::PREV_G = -3; +const int ColorTimeline::PREV_B = -2; +const int ColorTimeline::PREV_A = -1; +const int ColorTimeline::R = 1; +const int ColorTimeline::G = 2; +const int ColorTimeline::B = 3; +const int ColorTimeline::A = 4; + +ColorTimeline::ColorTimeline(int frameCount) : CurveTimeline(frameCount), _slotIndex(0) { + _frames.setSize(frameCount * ENTRIES, 0); +} + +void ColorTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slotP = skeleton._slots[_slotIndex]; + Slot &slot = *slotP; + if (!slot._bone.isActive()) return; + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + slot._color.set(slot._data._color); + return; + case MixBlend_First: { + Color &color = slot._color, setup = slot._data._color; + color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha); + } + default:; + } + return; + } + + float r, g, b, a; + if (time >= _frames[_frames.size() - ENTRIES]) { + // Time is after last frame. + size_t i = _frames.size(); + r = _frames[i + PREV_R]; + g = _frames[i + PREV_G]; + b = _frames[i + PREV_B]; + a = _frames[i + PREV_A]; + } else { + // Interpolate between the previous frame and the current frame. + size_t frame = (size_t)Animation::binarySearch(_frames, time, ENTRIES); + r = _frames[frame + PREV_R]; + g = _frames[frame + PREV_G]; + b = _frames[frame + PREV_B]; + a = _frames[frame + PREV_A]; + float frameTime = _frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + + r += (_frames[frame + R] - r) * percent; + g += (_frames[frame + G] - g) * percent; + b += (_frames[frame + B] - b) * percent; + a += (_frames[frame + A] - a) * percent; + } + + if (alpha == 1) { + slot.getColor().set(r, g, b, a); + } else { + Color &color = slot.getColor(); + if (blend == MixBlend_Setup) color.set(slot.getData().getColor()); + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); + } +} + +int ColorTimeline::getPropertyId() { + return ((int)TimelineType_Color << 24) + _slotIndex; +} + +void ColorTimeline::setFrame(int frameIndex, float time, float r, float g, float b, float a) { + frameIndex *= ENTRIES; + _frames[frameIndex] = time; + _frames[frameIndex + R] = r; + _frames[frameIndex + G] = g; + _frames[frameIndex + B] = b; + _frames[frameIndex + A] = a; +} + +int ColorTimeline::getSlotIndex() { + return _slotIndex; +} + +void ColorTimeline::setSlotIndex(int inValue) { + _slotIndex = inValue; +} + +Vector &ColorTimeline::getFrames() { + return _frames; +} diff --git a/cocos/editor-support/spine/ColorTimeline.h b/cocos/editor-support/spine/ColorTimeline.h new file mode 100644 index 0000000..6c02f6d --- /dev/null +++ b/cocos/editor-support/spine/ColorTimeline.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ColorTimeline_h +#define Spine_ColorTimeline_h + +#include + +namespace spine { +class SP_API ColorTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + +public: + static const int ENTRIES; + + explicit ColorTimeline(int frameCount); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + virtual int getPropertyId(); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frameIndex, float time, float r, float g, float b, float a); + + int getSlotIndex(); + + void setSlotIndex(int inValue); + + Vector &getFrames(); + +protected: + static const int PREV_TIME; + static const int PREV_R; + static const int PREV_G; + static const int PREV_B; + static const int PREV_A; + static const int R; + static const int G; + static const int B; + static const int A; + +private: + int _slotIndex; + Vector _frames; +}; +} // namespace spine + +#endif /* Spine_ColorTimeline_h */ diff --git a/cocos/editor-support/spine/Constraint.cpp b/cocos/editor-support/spine/Constraint.cpp new file mode 100644 index 0000000..444c042 --- /dev/null +++ b/cocos/editor-support/spine/Constraint.cpp @@ -0,0 +1,45 @@ +/****************************************************************************** + * Spine Runtimes Software License v2.5 + * + * Copyright (c) 2013-2016, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable, and + * non-transferable license to use, install, execute, and perform the Spine + * Runtimes software and derivative works solely for personal or internal + * use. Without the written permission of Esoteric Software (see Section 2 of + * the Spine Software License Agreement), you may not (a) modify, translate, + * adapt, or develop new applications using the Spine Runtimes or otherwise + * create derivative works or improvements of the Spine Runtimes or (b) remove, + * delete, alter, or obscure any trademarks or any copyright, trademark, patent, + * or other intellectual property or proprietary rights notices on or in the + * Software, including any copy thereof. Redistributions in binary or source + * form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF + * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +using namespace spine; + +RTTI_IMPL(Constraint, Updatable) + +Constraint::Constraint() { +} + +Constraint::~Constraint() { +} diff --git a/cocos/editor-support/spine/Constraint.h b/cocos/editor-support/spine/Constraint.h new file mode 100644 index 0000000..b3f6875 --- /dev/null +++ b/cocos/editor-support/spine/Constraint.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * Spine Runtimes Software License v2.5 + * + * Copyright (c) 2013-2016, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable, and + * non-transferable license to use, install, execute, and perform the Spine + * Runtimes software and derivative works solely for personal or internal + * use. Without the written permission of Esoteric Software (see Section 2 of + * the Spine Software License Agreement), you may not (a) modify, translate, + * adapt, or develop new applications using the Spine Runtimes or otherwise + * create derivative works or improvements of the Spine Runtimes or (b) remove, + * delete, alter, or obscure any trademarks or any copyright, trademark, patent, + * or other intellectual property or proprietary rights notices on or in the + * Software, including any copy thereof. Redistributions in binary or source + * form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF + * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Constraint_h +#define Spine_Constraint_h + +#include + +namespace spine { +/// The interface for all constraints. +class SP_API Constraint : public Updatable { + RTTI_DECL + +public: + Constraint(); + + virtual ~Constraint(); + + virtual void update() = 0; + + /// The ordinal for the order a skeleton's constraints will be applied. + virtual int getOrder() = 0; +}; +} // namespace spine + +#endif /* Spine_Constraint_h */ diff --git a/cocos/editor-support/spine/ConstraintData.cpp b/cocos/editor-support/spine/ConstraintData.cpp new file mode 100644 index 0000000..59547a9 --- /dev/null +++ b/cocos/editor-support/spine/ConstraintData.cpp @@ -0,0 +1,62 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +using namespace spine; + +ConstraintData::ConstraintData(const String& name) : _name(name), _order(0), _skinRequired(false) { +} + +ConstraintData::~ConstraintData() { +} + +const String& ConstraintData::getName() { + return _name; +} + +size_t ConstraintData::getOrder() { + return _order; +} + +void ConstraintData::setOrder(size_t inValue) { + _order = inValue; +} + +bool ConstraintData::isSkinRequired() { + return _skinRequired; +} + +void ConstraintData::setSkinRequired(bool inValue) { + _skinRequired = inValue; +} diff --git a/cocos/editor-support/spine/ConstraintData.h b/cocos/editor-support/spine/ConstraintData.h new file mode 100644 index 0000000..bbdb9b8 --- /dev/null +++ b/cocos/editor-support/spine/ConstraintData.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Constraint_h +#define Spine_Constraint_h + +#include +#include + +namespace spine { +/// The interface for all constraints. +class SP_API ConstraintData : public SpineObject { +public: + ConstraintData(const String& name); + + virtual ~ConstraintData(); + + /// The IK constraint's name, which is unique within the skeleton. + const String& getName(); + + /// The ordinal for the order a skeleton's constraints will be applied. + size_t getOrder(); + void setOrder(size_t inValue); + + /// Whether the constraint is only active for a specific skin. + bool isSkinRequired(); + void setSkinRequired(bool inValue); + +private: + const String _name; + size_t _order; + bool _skinRequired; +}; +} // namespace spine + +#endif /* Spine_Constraint_h */ diff --git a/cocos/editor-support/spine/ContainerUtil.h b/cocos/editor-support/spine/ContainerUtil.h new file mode 100644 index 0000000..f5c8dd0 --- /dev/null +++ b/cocos/editor-support/spine/ContainerUtil.h @@ -0,0 +1,127 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ContainerUtil_h +#define Spine_ContainerUtil_h + +#include +#include +#include +#include +#include + +#include + +namespace spine { +class SP_API ContainerUtil : public SpineObject { +public: + /// Finds an item by comparing each item's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + template + static T* findWithName(Vector& items, const String& name) { + assert(name.length() > 0); + + for (size_t i = 0; i < items.size(); ++i) { + T* item = items[i]; + if (item->getName() == name) { + return item; + } + } + + return NULL; + } + + /// @return -1 if the item was not found. + template + static int findIndexWithName(Vector& items, const String& name) { + assert(name.length() > 0); + + for (size_t i = 0, len = items.size(); i < len; ++i) { + T* item = items[i]; + if (item->getName() == name) { + return static_cast(i); + } + } + + return -1; + } + + /// Finds an item by comparing each item's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + template + static T* findWithDataName(Vector& items, const String& name) { + assert(name.length() > 0); + + for (size_t i = 0; i < items.size(); ++i) { + T* item = items[i]; + if (item->getData().getName() == name) { + return item; + } + } + + return NULL; + } + + /// @return -1 if the item was not found. + template + static int findIndexWithDataName(Vector& items, const String& name) { + assert(name.length() > 0); + + for (size_t i = 0, len = items.size(); i < len; ++i) { + T* item = items[i]; + if (item->getData().getName() == name) { + return static_cast(i); + } + } + + return -1; + } + + template + static void cleanUpVectorOfPointers(Vector& items) { + for (int i = (int)items.size() - 1; i >= 0; i--) { + T* item = items[i]; + + delete item; + + items.removeAt(i); + } + } + +private: + // ctor, copy ctor, and assignment should be private in a Singleton + ContainerUtil(); + ContainerUtil(const ContainerUtil&); + ContainerUtil& operator=(const ContainerUtil&); +}; +} // namespace spine + +#endif /* Spine_ContainerUtil_h */ diff --git a/cocos/editor-support/spine/CurveTimeline.cpp b/cocos/editor-support/spine/CurveTimeline.cpp new file mode 100644 index 0000000..8149465 --- /dev/null +++ b/cocos/editor-support/spine/CurveTimeline.cpp @@ -0,0 +1,127 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +using namespace spine; + +RTTI_IMPL(CurveTimeline, Timeline) + +const float CurveTimeline::LINEAR = 0; +const float CurveTimeline::STEPPED = 1; +const float CurveTimeline::BEZIER = 2; +const int CurveTimeline::BEZIER_SIZE = 10 * 2 - 1; + +CurveTimeline::CurveTimeline(int frameCount) { + assert(frameCount > 0); + + _curves.setSize((frameCount - 1) * BEZIER_SIZE, 0); +} + +CurveTimeline::~CurveTimeline() { +} + +size_t CurveTimeline::getFrameCount() { + return _curves.size() / BEZIER_SIZE + 1; +} + +void CurveTimeline::setLinear(size_t frameIndex) { + _curves[frameIndex * BEZIER_SIZE] = LINEAR; +} + +void CurveTimeline::setStepped(size_t frameIndex) { + _curves[frameIndex * BEZIER_SIZE] = STEPPED; +} + +void CurveTimeline::setCurve(size_t frameIndex, float cx1, float cy1, float cx2, float cy2) { + float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; + float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; + + size_t i = frameIndex * BEZIER_SIZE; + _curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (size_t n = i + BEZIER_SIZE - 1; i < n; i += 2) { + _curves[i] = x; + _curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } +} + +float CurveTimeline::getCurvePercent(size_t frameIndex, float percent) { + percent = MathUtil::clamp(percent, 0, 1); + size_t i = frameIndex * BEZIER_SIZE; + float type = _curves[i]; + + if (type == LINEAR) { + return percent; + } + + if (type == STEPPED) { + return 0; + } + + i++; + float x = 0; + for (size_t start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { + x = _curves[i]; + if (x >= percent) { + float prevX, prevY; + if (i == start) { + prevX = 0; + prevY = 0; + } else { + prevX = _curves[i - 2]; + prevY = _curves[i - 1]; + } + return prevY + (_curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + + float y = _curves[i - 1]; + + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. +} + +float CurveTimeline::getCurveType(size_t frameIndex) { + return _curves[frameIndex * BEZIER_SIZE]; +} diff --git a/cocos/editor-support/spine/CurveTimeline.h b/cocos/editor-support/spine/CurveTimeline.h new file mode 100644 index 0000000..cacb286 --- /dev/null +++ b/cocos/editor-support/spine/CurveTimeline.h @@ -0,0 +1,76 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_CurveTimeline_h +#define Spine_CurveTimeline_h + +#include +#include + +namespace spine { +/// Base class for frames that use an interpolation bezier curve. +class SP_API CurveTimeline : public Timeline { + RTTI_DECL + +public: + explicit CurveTimeline(int frameCount); + + virtual ~CurveTimeline(); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction) = 0; + + virtual int getPropertyId() = 0; + + size_t getFrameCount(); + + void setLinear(size_t frameIndex); + + void setStepped(size_t frameIndex); + + /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + /// the difference between the keyframe's values. + void setCurve(size_t frameIndex, float cx1, float cy1, float cx2, float cy2); + + float getCurvePercent(size_t frameIndex, float percent); + + float getCurveType(size_t frameIndex); + +protected: + static const float LINEAR; + static const float STEPPED; + static const float BEZIER; + static const int BEZIER_SIZE; + +private: + Vector _curves; // type, x, y, ... +}; +} // namespace spine + +#endif /* Spine_CurveTimeline_h */ diff --git a/cocos/editor-support/spine/Debug.h b/cocos/editor-support/spine/Debug.h new file mode 100644 index 0000000..fcdd106 --- /dev/null +++ b/cocos/editor-support/spine/Debug.h @@ -0,0 +1,126 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_DEBUG_H +#define SPINE_DEBUG_H + +#include + +#include + +namespace spine { +class SP_API DebugExtension : public SpineExtension { + struct Allocation { + void *address; + size_t size; + const char *fileName; + int line; + + Allocation() : address(NULL), size(0), fileName(NULL), line(0) { + } + + Allocation(void *a, size_t s, const char *f, int l) : address(a), size(s), fileName(f), line(l) { + } + }; + +public: + DebugExtension(SpineExtension *extension) : _extension(extension), _allocations(0), _reallocations(0), _frees(0) { + } + + void reportLeaks() { + for (std::map::iterator it = _allocated.begin(); it != _allocated.end(); it++) { + printf("\"%s:%i (%zu bytes at %p)\n", it->second.fileName, it->second.line, it->second.size, it->second.address); + } + printf("allocations: %zu, reallocations: %zu, frees: %zu\n", _allocations, _reallocations, _frees); + if (_allocated.empty()) printf("No leaks detected"); + } + + void clearAllocations() { + _allocated.clear(); + _usedMemory = 0; + } + + virtual void *_alloc(size_t size, const char *file, int line) { + void *result = _extension->_alloc(size, file, line); + _allocated[result] = Allocation(result, size, file, line); + _allocations++; + _usedMemory += size; + return result; + } + + virtual void *_calloc(size_t size, const char *file, int line) { + void *result = _extension->_calloc(size, file, line); + _allocated[result] = Allocation(result, size, file, line); + _allocations++; + _usedMemory += size; + return result; + } + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) { + if (_allocated.count(ptr)) _usedMemory -= _allocated[ptr].size; + _allocated.erase(ptr); + void *result = _extension->_realloc(ptr, size, file, line); + _reallocations++; + _allocated[result] = Allocation(result, size, file, line); + _usedMemory += size; + return result; + } + + virtual void _free(void *mem, const char *file, int line) { + if (_allocated.count(mem)) { + _extension->_free(mem, file, line); + _frees++; + _usedMemory -= _allocated[mem].size; + _allocated.erase(mem); + return; + } + + printf("%s:%i (address %p): Double free or not allocated through SpineExtension\n", file, line, mem); + _extension->_free(mem, file, line); + } + + virtual char *_readFile(const String &path, int *length) { + return _extension->_readFile(path, length); + } + + size_t getUsedMemory() { + return _usedMemory; + } + +private: + SpineExtension *_extension; + std::map _allocated; + size_t _allocations; + size_t _reallocations; + size_t _frees; + size_t _usedMemory; +}; +} // namespace spine + +#endif //SPINE_DEBUG_H diff --git a/cocos/editor-support/spine/DeformTimeline.cpp b/cocos/editor-support/spine/DeformTimeline.cpp new file mode 100644 index 0000000..164f60c --- /dev/null +++ b/cocos/editor-support/spine/DeformTimeline.cpp @@ -0,0 +1,297 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(DeformTimeline, CurveTimeline) + +DeformTimeline::DeformTimeline(int frameCount) : CurveTimeline(frameCount), _slotIndex(0), _attachment(NULL) { + _frames.ensureCapacity(frameCount); + _frameVertices.ensureCapacity(frameCount); + + _frames.setSize(frameCount, 0); + + for (int i = 0; i < frameCount; ++i) { + Vector vec; + _frameVertices.add(vec); + } +} + +void DeformTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slotP = skeleton._slots[_slotIndex]; + Slot &slot = *slotP; + if (!slot._bone.isActive()) return; + + Attachment *slotAttachment = slot.getAttachment(); + if (slotAttachment == NULL || !slotAttachment->getRTTI().instanceOf(VertexAttachment::rtti)) { + return; + } + + VertexAttachment *attachment = static_cast(slotAttachment); + if (attachment->_deformAttachment != _attachment) { + return; + } + + Vector &deformArray = slot._deform; + if (deformArray.size() == 0) { + blend = MixBlend_Setup; + } + + Vector > &frameVertices = _frameVertices; + size_t vertexCount = frameVertices[0].size(); + + Vector &frames = _frames; + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + deformArray.clear(); + return; + case MixBlend_First: { + if (alpha == 1) { + deformArray.clear(); + return; + } + deformArray.setSize(vertexCount, 0); + Vector &deformInner = deformArray; + if (attachment->getBones().size() == 0) { + // Unweighted vertex positions. + Vector &setupVertices = attachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deformInner[i] += (setupVertices[i] - deformInner[i]) * alpha; + } else { + // Weighted deform offsets. + alpha = 1 - alpha; + for (size_t i = 0; i < vertexCount; i++) + deformInner[i] *= alpha; + } + } + case MixBlend_Replace: + case MixBlend_Add: + return; + } + } + + deformArray.setSize(vertexCount, 0); + Vector &deform = deformArray; + + if (time >= frames[frames.size() - 1]) { // Time is after last frame. + Vector &lastVertices = frameVertices[frames.size() - 1]; + if (alpha == 1) { + if (blend == MixBlend_Add) { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] - setupVertices[i]; + } else { + // Weighted deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i]; + } + } else { + // Vertex positions or deform offsets, no alpha. + memcpy(deform.buffer(), lastVertices.buffer(), vertexCount * sizeof(float)); + } + } else { + switch (blend) { + case MixBlend_Setup: { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] = lastVertices[i] * alpha; + } + break; + } + case MixBlend_First: + case MixBlend_Replace: + // Vertex positions or deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - deform[i]) * alpha; + break; + case MixBlend_Add: + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } else { + // Weighted deform offsets, alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] * alpha; + } + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(frames, time); + Vector &prevVertices = frameVertices[frame - 1]; + Vector &nextVertices = frameVertices[frame]; + float frameTime = frames[frame]; + float percent = getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); + + if (alpha == 1) { + if (blend == MixBlend_Add) { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i]; + } + } else { + // Weighted deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += prev + (nextVertices[i] - prev) * percent; + } + } + } else { + // Vertex positions or deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } else { + switch (blend) { + case MixBlend_Setup: { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i], setup = setupVertices[i]; + deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + case MixBlend_First: + case MixBlend_Replace: + // Vertex positions or deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + } + break; + case MixBlend_Add: + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + } + } +} + +int DeformTimeline::getPropertyId() { + assert(_attachment != NULL); + return ((int)TimelineType_Deform << 24) + _attachment->_id + _slotIndex; +} + +void DeformTimeline::setFrame(int frameIndex, float time, Vector &vertices) { + _frames[frameIndex] = time; + _frameVertices[frameIndex].clear(); + _frameVertices[frameIndex].addAll(vertices); +} + +int DeformTimeline::getSlotIndex() { + return _slotIndex; +} + +void DeformTimeline::setSlotIndex(int inValue) { + _slotIndex = inValue; +} + +Vector &DeformTimeline::getFrames() { + return _frames; +} + +Vector > &DeformTimeline::getVertices() { + return _frameVertices; +} + +VertexAttachment *DeformTimeline::getAttachment() { + return _attachment; +} + +void DeformTimeline::setAttachment(VertexAttachment *inValue) { + _attachment = inValue; +} diff --git a/cocos/editor-support/spine/DeformTimeline.h b/cocos/editor-support/spine/DeformTimeline.h new file mode 100644 index 0000000..c3c9110 --- /dev/null +++ b/cocos/editor-support/spine/DeformTimeline.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_DeformTimeline_h +#define Spine_DeformTimeline_h + +#include + +namespace spine { +class VertexAttachment; + +class SP_API DeformTimeline : public CurveTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + explicit DeformTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frameIndex, float time, Vector& vertices); + + int getSlotIndex(); + void setSlotIndex(int inValue); + Vector& getFrames(); + Vector >& getVertices(); + VertexAttachment* getAttachment(); + void setAttachment(VertexAttachment* inValue); + +private: + int _slotIndex; + Vector _frames; + Vector > _frameVertices; + VertexAttachment* _attachment; +}; +} // namespace spine + +#endif /* Spine_DeformTimeline_h */ diff --git a/cocos/editor-support/spine/DrawOrderTimeline.cpp b/cocos/editor-support/spine/DrawOrderTimeline.cpp new file mode 100644 index 0000000..0c1dce7 --- /dev/null +++ b/cocos/editor-support/spine/DrawOrderTimeline.cpp @@ -0,0 +1,124 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(DrawOrderTimeline, Timeline) + +DrawOrderTimeline::DrawOrderTimeline(int frameCount) : Timeline() { + _frames.ensureCapacity(frameCount); + _drawOrders.ensureCapacity(frameCount); + + _frames.setSize(frameCount, 0); + + for (int i = 0; i < frameCount; ++i) { + Vector vec; + _drawOrders.add(vec); + } +} + +void DrawOrderTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(alpha); + + Vector &drawOrder = skeleton._drawOrder; + Vector &slots = skeleton._slots; + if (direction == MixDirection_Out && blend == MixBlend_Setup) { + drawOrder.clear(); + drawOrder.ensureCapacity(slots.size()); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + return; + } + + if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) { + drawOrder.clear(); + drawOrder.ensureCapacity(slots.size()); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } + return; + } + + size_t frame; + if (time >= _frames[_frames.size() - 1]) { + // Time is after last frame. + frame = _frames.size() - 1; + } else + frame = (size_t)Animation::binarySearch(_frames, time) - 1; + + Vector &drawOrderToSetupIndex = _drawOrders[frame]; + if (drawOrderToSetupIndex.size() == 0) { + drawOrder.clear(); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } else { + for (size_t i = 0, n = drawOrderToSetupIndex.size(); i < n; ++i) + drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } +} + +int DrawOrderTimeline::getPropertyId() { + return ((int)TimelineType_DrawOrder << 24); +} + +void DrawOrderTimeline::setFrame(size_t frameIndex, float time, Vector &drawOrder) { + _frames[frameIndex] = time; + _drawOrders[frameIndex].clear(); + _drawOrders[frameIndex].addAll(drawOrder); +} + +Vector &DrawOrderTimeline::getFrames() { + return _frames; +} + +Vector > &DrawOrderTimeline::getDrawOrders() { + return _drawOrders; +} + +size_t DrawOrderTimeline::getFrameCount() { + return _frames.size(); +} diff --git a/cocos/editor-support/spine/DrawOrderTimeline.h b/cocos/editor-support/spine/DrawOrderTimeline.h new file mode 100644 index 0000000..4236ab2 --- /dev/null +++ b/cocos/editor-support/spine/DrawOrderTimeline.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_DrawOrderTimeline_h +#define Spine_DrawOrderTimeline_h + +#include + +namespace spine { +class SP_API DrawOrderTimeline : public Timeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + explicit DrawOrderTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + + /// Sets the time and value of the specified keyframe. + /// @param drawOrder May be NULL to use bind pose draw order + void setFrame(size_t frameIndex, float time, Vector& drawOrder); + + Vector& getFrames(); + Vector >& getDrawOrders(); + size_t getFrameCount(); + +private: + Vector _frames; + Vector > _drawOrders; +}; +} // namespace spine + +#endif /* Spine_DrawOrderTimeline_h */ diff --git a/cocos/editor-support/spine/Event.cpp b/cocos/editor-support/spine/Event.cpp new file mode 100644 index 0000000..3e3b4a6 --- /dev/null +++ b/cocos/editor-support/spine/Event.cpp @@ -0,0 +1,93 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +spine::Event::Event(float time, const spine::EventData &data) : _data(data), + _time(time), + _intValue(0), + _floatValue(0), + _stringValue(), + _volume(1), + _balance(0) { +} + +const spine::EventData &spine::Event::getData() { + return _data; +} + +float spine::Event::getTime() { + return _time; +} + +int spine::Event::getIntValue() { + return _intValue; +} + +void spine::Event::setIntValue(int inValue) { + _intValue = inValue; +} + +float spine::Event::getFloatValue() { + return _floatValue; +} + +void spine::Event::setFloatValue(float inValue) { + _floatValue = inValue; +} + +const spine::String &spine::Event::getStringValue() { + return _stringValue; +} + +void spine::Event::setStringValue(const spine::String &inValue) { + _stringValue = inValue; +} + +float spine::Event::getVolume() { + return _volume; +} + +void spine::Event::setVolume(float inValue) { + _volume = inValue; +} + +float spine::Event::getBalance() { + return _balance; +} + +void spine::Event::setBalance(float inValue) { + _balance = inValue; +} diff --git a/cocos/editor-support/spine/Event.h b/cocos/editor-support/spine/Event.h new file mode 100644 index 0000000..8eba17a --- /dev/null +++ b/cocos/editor-support/spine/Event.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Event_h +#define Spine_Event_h + +#include +#include + +namespace spine { +class EventData; + +/// Stores the current pose values for an Event. +class SP_API Event : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + +public: + Event(float time, const EventData &data); + + const EventData &getData(); + + /// The animation time this event was keyed. + float getTime(); + + int getIntValue(); + + void setIntValue(int inValue); + + float getFloatValue(); + + void setFloatValue(float inValue); + + const String &getStringValue(); + + void setStringValue(const String &inValue); + + float getVolume(); + + void setVolume(float inValue); + + float getBalance(); + + void setBalance(float inValue); + +private: + const EventData &_data; + const float _time; + int _intValue; + float _floatValue; + String _stringValue; + float _volume; + float _balance; +}; +} // namespace spine + +#endif /* Spine_Event_h */ diff --git a/cocos/editor-support/spine/EventData.cpp b/cocos/editor-support/spine/EventData.cpp new file mode 100644 index 0000000..ea0ebfb --- /dev/null +++ b/cocos/editor-support/spine/EventData.cpp @@ -0,0 +1,99 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +spine::EventData::EventData(const spine::String &name) : _name(name), + _intValue(0), + _floatValue(0), + _stringValue(), + _audioPath(), + _volume(1), + _balance(0) { + assert(_name.length() > 0); +} + +/// The name of the event, which is unique within the skeleton. +const spine::String &spine::EventData::getName() const { + return _name; +} + +int spine::EventData::getIntValue() { + return _intValue; +} + +void spine::EventData::setIntValue(int inValue) { + _intValue = inValue; +} + +float spine::EventData::getFloatValue() { + return _floatValue; +} + +void spine::EventData::setFloatValue(float inValue) { + _floatValue = inValue; +} + +const spine::String &spine::EventData::getStringValue() { + return _stringValue; +} + +void spine::EventData::setStringValue(const spine::String &inValue) { + this->_stringValue = inValue; +} + +const spine::String &spine::EventData::getAudioPath() { + return _audioPath; +} + +void spine::EventData::setAudioPath(const spine::String &inValue) { + _audioPath = inValue; +} + +float spine::EventData::getVolume() { + return _volume; +} + +void spine::EventData::setVolume(float inValue) { + _volume = inValue; +} + +float spine::EventData::getBalance() { + return _balance; +} + +void spine::EventData::setBalance(float inValue) { + _balance = inValue; +} diff --git a/cocos/editor-support/spine/EventData.h b/cocos/editor-support/spine/EventData.h new file mode 100644 index 0000000..28fdbe0 --- /dev/null +++ b/cocos/editor-support/spine/EventData.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_EventData_h +#define Spine_EventData_h + +#include +#include + +namespace spine { +/// Stores the setup pose values for an Event. +class SP_API EventData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Event; + +public: + explicit EventData(const String &name); + + /// The name of the event, which is unique within the skeleton. + const String &getName() const; + + int getIntValue(); + + void setIntValue(int inValue); + + float getFloatValue(); + + void setFloatValue(float inValue); + + const String &getStringValue(); + + void setStringValue(const String &inValue); + + const String &getAudioPath(); + + void setAudioPath(const String &inValue); + + float getVolume(); + + void setVolume(float inValue); + + float getBalance(); + + void setBalance(float inValue); + +private: + const String _name; + int _intValue; + float _floatValue; + String _stringValue; + String _audioPath; + float _volume; + float _balance; +}; +} // namespace spine + +#endif /* Spine_EventData_h */ diff --git a/cocos/editor-support/spine/EventTimeline.cpp b/cocos/editor-support/spine/EventTimeline.cpp new file mode 100644 index 0000000..c3f0127 --- /dev/null +++ b/cocos/editor-support/spine/EventTimeline.cpp @@ -0,0 +1,110 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(EventTimeline, Timeline) + +EventTimeline::EventTimeline(int frameCount) : Timeline() { + _frames.setSize(frameCount, 0); + _events.setSize(frameCount, NULL); +} + +EventTimeline::~EventTimeline() { + ContainerUtil::cleanUpVectorOfPointers(_events); +} + +void EventTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + if (pEvents == NULL) return; + + Vector &events = *pEvents; + + size_t frameCount = _frames.size(); + + if (lastTime > time) { + // Fire events after last time for looped animations. + apply(skeleton, lastTime, FLT_MAX, pEvents, alpha, blend, direction); + lastTime = -1.0f; + } else if (lastTime >= _frames[frameCount - 1]) { + // Last time is after last frame. + return; + } + + if (time < _frames[0]) return; // Time is before first frame. + + int frame; + if (lastTime < _frames[0]) { + frame = 0; + } else { + frame = Animation::binarySearch(_frames, lastTime); + float frameTime = _frames[frame]; + while (frame > 0) { + // Fire multiple events with the same frame. + if (_frames[frame - 1] != frameTime) break; + frame--; + } + } + + for (; (size_t)frame < frameCount && time >= _frames[frame]; ++frame) + events.add(_events[frame]); +} + +int EventTimeline::getPropertyId() { + return ((int)TimelineType_Event << 24); +} + +void EventTimeline::setFrame(size_t frameIndex, Event *event) { + _frames[frameIndex] = event->getTime(); + _events[frameIndex] = event; +} + +Vector &EventTimeline::getFrames() { return _frames; } + +Vector &EventTimeline::getEvents() { return _events; } + +size_t EventTimeline::getFrameCount() { return _frames.size(); } diff --git a/cocos/editor-support/spine/EventTimeline.h b/cocos/editor-support/spine/EventTimeline.h new file mode 100644 index 0000000..17fe195 --- /dev/null +++ b/cocos/editor-support/spine/EventTimeline.h @@ -0,0 +1,64 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_EventTimeline_h +#define Spine_EventTimeline_h + +#include + +namespace spine { +class SP_API EventTimeline : public Timeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + explicit EventTimeline(int frameCount); + + ~EventTimeline(); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + + /// Sets the time and value of the specified keyframe. + void setFrame(size_t frameIndex, Event* event); + + Vector& getFrames(); + Vector& getEvents(); + size_t getFrameCount(); + +private: + Vector _frames; + Vector _events; +}; +} // namespace spine + +#endif /* Spine_EventTimeline_h */ diff --git a/cocos/editor-support/spine/Extension.cpp b/cocos/editor-support/spine/Extension.cpp new file mode 100644 index 0000000..000e69e --- /dev/null +++ b/cocos/editor-support/spine/Extension.cpp @@ -0,0 +1,127 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include + +#include + +using namespace spine; + +SpineExtension *SpineExtension::_instance = NULL; + +void SpineExtension::setInstance(SpineExtension *inValue) { + assert(inValue); + + _instance = inValue; +} + +SpineExtension *SpineExtension::getInstance() { + if (!_instance) _instance = spine::getDefaultExtension(); + assert(_instance); + + return _instance; +} + +SpineExtension::~SpineExtension() { +} + +SpineExtension::SpineExtension() { +} + +DefaultSpineExtension::~DefaultSpineExtension() { +} + +void *DefaultSpineExtension::_alloc(size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + if (size == 0) + return 0; + void *ptr = ::malloc(size); + return ptr; +} + +void *DefaultSpineExtension::_calloc(size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + if (size == 0) + return 0; + + void *ptr = ::malloc(size); + if (ptr) { + memset(ptr, 0, size); + } + return ptr; +} + +void *DefaultSpineExtension::_realloc(void *ptr, size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + void *mem = NULL; + if (size == 0) + return 0; + if (ptr == NULL) + mem = ::malloc(size); + else + mem = ::realloc(ptr, size); + return mem; +} + +void DefaultSpineExtension::_free(void *mem, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + ::free(mem); +} + +char *DefaultSpineExtension::_readFile(const String &path, int *length) { + char *data; + FILE *file = fopen(path.buffer(), "rb"); + if (!file) return 0; + + fseek(file, 0, SEEK_END); + *length = (int)ftell(file); + fseek(file, 0, SEEK_SET); + + data = SpineExtension::alloc(*length, __FILE__, __LINE__); + fread(data, 1, *length, file); + fclose(file); + + return data; +} + +DefaultSpineExtension::DefaultSpineExtension() : SpineExtension() { +} diff --git a/cocos/editor-support/spine/Extension.h b/cocos/editor-support/spine/Extension.h new file mode 100644 index 0000000..12c5cf8 --- /dev/null +++ b/cocos/editor-support/spine/Extension.h @@ -0,0 +1,117 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Extension_h +#define Spine_Extension_h + +#include +#include + +#define SP_UNUSED(x) (void)(x) + +namespace spine { +class String; + +class SP_API SpineExtension { +public: + template + static T *alloc(size_t num, const char *file, int line) { + return (T *)getInstance()->_alloc(sizeof(T) * num, file, line); + } + + template + static T *calloc(size_t num, const char *file, int line) { + return (T *)getInstance()->_calloc(sizeof(T) * num, file, line); + } + + template + static T *realloc(T *ptr, size_t num, const char *file, int line) { + return (T *)getInstance()->_realloc(ptr, sizeof(T) * num, file, line); + } + + template + static void free(T *ptr, const char *file, int line) { + getInstance()->_free((void *)ptr, file, line); + } + + static char *readFile(const String &path, int *length) { + return getInstance()->_readFile(path, length); + } + + static void setInstance(SpineExtension *inSpineExtension); + + static SpineExtension *getInstance(); + + virtual ~SpineExtension(); + + /// Implement this function to use your own memory allocator + virtual void *_alloc(size_t size, const char *file, int line) = 0; + + virtual void *_calloc(size_t size, const char *file, int line) = 0; + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) = 0; + + /// If you provide a spineAllocFunc, you should also provide a spineFreeFunc + virtual void _free(void *mem, const char *file, int line) = 0; + + virtual char *_readFile(const String &path, int *length) = 0; + +protected: + SpineExtension(); + +private: + static SpineExtension *_instance; +}; + +class SP_API DefaultSpineExtension : public SpineExtension { +public: + DefaultSpineExtension(); + + virtual ~DefaultSpineExtension(); + +protected: + virtual void *_alloc(size_t size, const char *file, int line); + + virtual void *_calloc(size_t size, const char *file, int line); + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line); + + virtual void _free(void *mem, const char *file, int line); + + virtual char *_readFile(const String &path, int *length); +}; + +// This function is to be implemented by engine specific runtimes to provide +// the default extension for that engine. It is called the first time +// SpineExtension::getInstance() is called, when no instance has been set +// yet. +extern SpineExtension *getDefaultExtension(); +} // namespace spine + +#endif /* Spine_Extension_h */ diff --git a/cocos/editor-support/spine/HasRendererObject.h b/cocos/editor-support/spine/HasRendererObject.h new file mode 100644 index 0000000..79d9d58 --- /dev/null +++ b/cocos/editor-support/spine/HasRendererObject.h @@ -0,0 +1,65 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_HasRendererObject_h +#define Spine_HasRendererObject_h + +#include "editor-support/spine/dll.h" +#include + +namespace spine { + +typedef void (*DisposeRendererObject)(void* rendererObject); + +class SP_API HasRendererObject { +public: + explicit HasRendererObject() : _rendererObject(NULL), _dispose(NULL){}; + + virtual ~HasRendererObject() { + if (_dispose && _rendererObject) + _dispose(_rendererObject); + } + + void* getRendererObject() { return _rendererObject; } + void setRendererObject(void* rendererObject, DisposeRendererObject dispose = NULL) { + if (_dispose && _rendererObject && _rendererObject != rendererObject) + _dispose(_rendererObject); + + _rendererObject = rendererObject; + _dispose = dispose; + } + +private: + void* _rendererObject; + DisposeRendererObject _dispose; +}; + +} // namespace spine + +#endif diff --git a/cocos/editor-support/spine/HashMap.h b/cocos/editor-support/spine/HashMap.h new file mode 100644 index 0000000..d7e14cf --- /dev/null +++ b/cocos/editor-support/spine/HashMap.h @@ -0,0 +1,192 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_HashMap_h +#define Spine_HashMap_h + +#include +#include +#include + +// Required for new with line number and file name in MSVC +#ifdef _MSC_VER + #pragma warning(disable : 4291) +#endif + +namespace spine { +template +class SP_API HashMap : public SpineObject { +private: + class Entry; + +public: + class SP_API Pair { + public: + explicit Pair(K &k, V &v) : key(k), value(v) {} + + K &key; + V &value; + }; + + class SP_API Entries { + public: + friend class HashMap; + + explicit Entries(Entry *entry) : _entry(NULL), _hasChecked(false) { + _start.next = entry; + _entry = &_start; + } + + Pair next() { + assert(_entry); + assert(_hasChecked); + _entry = _entry->next; + Pair pair(_entry->_key, _entry->_value); + _hasChecked = false; + return pair; + } + + bool hasNext() { + _hasChecked = true; + return _entry->next; + } + + private: + bool _hasChecked; + Entry _start; + Entry *_entry; + }; + + HashMap() : _head(NULL), + _size(0) { + } + + ~HashMap() { + clear(); + } + + void clear() { + for (Entry *entry = _head; entry != NULL;) { + Entry *next = entry->next; + delete entry; + entry = next; + } + _head = NULL; + _size = 0; + } + + size_t size() { + return _size; + } + + void put(const K &key, const V &value) { + Entry *entry = find(key); + if (entry) { + entry->_key = key; + entry->_value = value; + } else { + entry = new (__FILE__, __LINE__) Entry(); + entry->_key = key; + entry->_value = value; + + Entry *oldHead = _head; + + if (oldHead) { + _head = entry; + oldHead->prev = entry; + entry->next = oldHead; + } else { + _head = entry; + } + _size++; + } + } + + bool containsKey(const K &key) { + return find(key) != NULL; + } + + bool remove(const K &key) { + Entry *entry = find(key); + if (!entry) return false; + + Entry *prev = entry->prev; + Entry *next = entry->next; + + if (prev) + prev->next = next; + else + _head = next; + if (next) next->prev = entry->prev; + + delete entry; + _size--; + + return true; + } + + V operator[](const K &key) { + Entry *entry = find(key); + if (entry) + return entry->_value; + else { + assert(false); + return 0; + } + } + + Entries getEntries() const { + return Entries(_head); + } + +private: + Entry *find(const K &key) { + for (Entry *entry = _head; entry != NULL; entry = entry->next) { + if (entry->_key == key) + return entry; + } + return NULL; + } + + class SP_API Entry : public SpineObject { + public: + K _key; + V _value; + Entry *next; + Entry *prev; + + Entry() : next(NULL), prev(NULL) {} + }; + + Entry *_head; + size_t _size; +}; +} // namespace spine + +#endif /* Spine_HashMap_h */ diff --git a/cocos/editor-support/spine/IkConstraint.cpp b/cocos/editor-support/spine/IkConstraint.cpp new file mode 100644 index 0000000..e7079af --- /dev/null +++ b/cocos/editor-support/spine/IkConstraint.cpp @@ -0,0 +1,359 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(IkConstraint, Updatable) + +void IkConstraint::apply(Bone &bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha) { + Bone *p = bone.getParent(); + float pa = p->_a, pb = p->_b, pc = p->_c, pd = p->_d; + float rotationIK = -bone._ashearX - bone._arotation; + float tx = 0, ty = 0; + if (!bone._appliedValid) bone.updateAppliedTransform(); + + switch (bone._data.getTransformMode()) { + case TransformMode_OnlyTranslation: + tx = targetX - bone._worldX; + ty = targetY - bone._worldY; + break; + case TransformMode_NoRotationOrReflection: { + rotationIK += MathUtil::atan2(pc, pa) * MathUtil::Rad_Deg; + float ps = MathUtil::abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + pb = -pc * ps; + pd = pa * ps; + } + default: + float x = targetX - p->_worldX, y = targetY - p->_worldY; + float d = pa * pd - pb * pc; + tx = (x * pd - y * pb) / d - bone._ax; + ty = (y * pa - x * pc) / d - bone._ay; + } + rotationIK += MathUtil::atan2(ty, tx) * MathUtil::Rad_Deg; + if (bone._ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) + rotationIK += 360; + float sx = bone._ascaleX; + float sy = bone._ascaleY; + if (compress || stretch) { + switch (bone._data.getTransformMode()) { + case TransformMode_NoScale: + case TransformMode_NoScaleOrReflection: + tx = targetX - bone._worldX; + ty = targetY - bone._worldY; + default:; + } + float b = bone._data.getLength() * sx, dd = MathUtil::sqrt(tx * tx + ty * ty); + if (((compress && dd < b) || (stretch && dd > b)) && (b > 0.0001f)) { + float s = (dd / b - 1) * alpha + 1; + sx *= s; + if (uniform) sy *= s; + } + } + bone.updateWorldTransform(bone._ax, bone._ay, bone._arotation + rotationIK * alpha, sx, sy, bone._ashearX, bone._ashearY); +} + +void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, float softness, float alpha) { + float a, b, c, d; + float px, py, psx, sx, psy; + float cx, cy, csx, cwx, cwy; + int o1, o2, s2, u; + Bone *pp = parent.getParent(); + float tx, ty, dx, dy, dd, l1, l2, a1, a2, r, td, sd, p; + float id, x, y; + if (alpha == 0) { + child.updateWorldTransform(); + return; + } + if (!parent._appliedValid) parent.updateAppliedTransform(); + if (!child._appliedValid) child.updateAppliedTransform(); + px = parent._ax; + py = parent._ay; + psx = parent._ascaleX; + sx = psx; + psy = parent._ascaleY; + csx = child._ascaleX; + if (psx < 0) { + psx = -psx; + o1 = 180; + s2 = -1; + } else { + o1 = 0; + s2 = 1; + } + if (psy < 0) { + psy = -psy; + s2 = -s2; + } + if (csx < 0) { + csx = -csx; + o2 = 180; + } else + o2 = 0; + r = psx - psy; + cx = child._ax; + u = (r < 0 ? -r : r) <= 0.0001f; + if (!u) { + cy = 0; + cwx = parent._a * cx + parent._worldX; + cwy = parent._c * cx + parent._worldY; + } else { + cy = child._ay; + cwx = parent._a * cx + parent._b * cy + parent._worldX; + cwy = parent._c * cx + parent._d * cy + parent._worldY; + } + a = pp->_a; + b = pp->_b; + c = pp->_c; + d = pp->_d; + id = 1 / (a * d - b * c); + x = cwx - pp->_worldX; + y = cwy - pp->_worldY; + dx = (x * d - y * b) * id - px; + dy = (y * a - x * c) * id - py; + l1 = MathUtil::sqrt(dx * dx + dy * dy); + l2 = child._data.getLength() * csx; + if (l1 < 0.0001) { + apply(parent, targetX, targetY, false, stretch, false, alpha); + child.updateWorldTransform(cx, cy, 0, child._ascaleX, child._ascaleY, child._ashearX, child._ashearY); + return; + } + x = targetX - pp->_worldX; + y = targetY - pp->_worldY; + tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + dd = tx * tx + ty * ty; + if (softness != 0) { + softness *= psx * (csx + 1) / 2; + td = MathUtil::sqrt(dd), sd = td - l1 - l2 * psx + softness; + if (sd > 0) { + p = MathUtil::min(1.0f, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } + if (u) { + float cosine; + l2 *= psx; + cosine = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cosine < -1) + cosine = -1; + else if (cosine > 1) { + cosine = 1; + if (stretch) sx *= (MathUtil::sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + } + a2 = MathUtil::acos(cosine) * bendDir; + a = l1 + l2 * cosine; + b = l2 * MathUtil::sin(a2); + a1 = MathUtil::atan2(ty * a - tx * b, tx * a + ty * b); + } else { + a = psx * l2, b = psy * l2; + float aa = a * a, bb = b * b, ll = l1 * l1, ta = MathUtil::atan2(ty, tx); + float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c0; + if (d >= 0) { + float q = MathUtil::sqrt(d), r0, r1; + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + r0 = q / c2; + r1 = c0 / q; + r = MathUtil::abs(r0) < MathUtil::abs(r1) ? r0 : r1; + if (r * r <= dd) { + y = MathUtil::sqrt(dd - r * r) * bendDir; + a1 = ta - MathUtil::atan2(y, r); + a2 = MathUtil::atan2(y / psy, (r - l1) / psx); + goto break_outer; + } + } + { + float minAngle = MathUtil::Pi, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c0 = -a * l1 / (aa - bb); + if (c0 >= -1 && c0 <= 1) { + c0 = MathUtil::acos(c0); + x = a * MathUtil::cos(c0) + l1; + y = b * MathUtil::sin(c0); + d = x * x + y * y; + if (d < minDist) { + minAngle = c0; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) { + maxAngle = c0; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) / 2) { + a1 = ta - MathUtil::atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } else { + a1 = ta - MathUtil::atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + } +break_outer : { + float os = MathUtil::atan2(cy, cx) * s2; + a1 = (a1 - os) * MathUtil::Rad_Deg + o1 - parent._arotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) + a1 += 360; + parent.updateWorldTransform(px, py, parent._rotation + a1 * alpha, sx, parent._ascaleY, 0, 0); + a2 = ((a2 + os) * MathUtil::Rad_Deg - child._ashearX) * s2 + o2 - child._arotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) + a2 += 360; + child.updateWorldTransform(cx, cy, child._arotation + a2 * alpha, child._ascaleX, child._ascaleY, child._ashearX, child._ashearY); +} +} + +IkConstraint::IkConstraint(IkConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _bendDirection(data.getBendDirection()), + _compress(data.getCompress()), + _stretch(data.getStretch()), + _mix(data.getMix()), + _softness(data.getSoftness()), + _target(skeleton.findBone( + data.getTarget()->getName())), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); i++) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } +} + +/// Applies the constraint to the constrained bones. +void IkConstraint::apply() { + update(); +} + +void IkConstraint::update() { + switch (_bones.size()) { + case 1: { + Bone *bone0 = _bones[0]; + apply(*bone0, _target->getWorldX(), _target->getWorldY(), _compress, _stretch, _data._uniform, _mix); + } break; + case 2: { + Bone *bone0 = _bones[0]; + Bone *bone1 = _bones[1]; + apply(*bone0, *bone1, _target->getWorldX(), _target->getWorldY(), _bendDirection, _stretch, _softness, _mix); + } break; + } +} + +int IkConstraint::getOrder() { + return _data.getOrder(); +} + +IkConstraintData &IkConstraint::getData() { + return _data; +} + +Vector &IkConstraint::getBones() { + return _bones; +} + +Bone *IkConstraint::getTarget() { + return _target; +} + +void IkConstraint::setTarget(Bone *inValue) { + _target = inValue; +} + +int IkConstraint::getBendDirection() { + return _bendDirection; +} + +void IkConstraint::setBendDirection(int inValue) { + _bendDirection = inValue; +} + +float IkConstraint::getMix() { + return _mix; +} + +void IkConstraint::setMix(float inValue) { + _mix = inValue; +} + +bool IkConstraint::getStretch() { + return _stretch; +} + +void IkConstraint::setStretch(bool inValue) { + _stretch = inValue; +} + +bool IkConstraint::getCompress() { + return _compress; +} + +void IkConstraint::setCompress(bool inValue) { + _compress = inValue; +} + +bool IkConstraint::isActive() { + return _active; +} + +void IkConstraint::setActive(bool inValue) { + _active = inValue; +} + +float IkConstraint::getSoftness() { + return _softness; +} + +void IkConstraint::setSoftness(float inValue) { + _softness = inValue; +} diff --git a/cocos/editor-support/spine/IkConstraint.h b/cocos/editor-support/spine/IkConstraint.h new file mode 100644 index 0000000..5fb217a --- /dev/null +++ b/cocos/editor-support/spine/IkConstraint.h @@ -0,0 +1,115 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_IkConstraint_h +#define Spine_IkConstraint_h + +#include + +#include + +namespace spine { +class IkConstraintData; + +class Skeleton; + +class Bone; + +class SP_API IkConstraint : public Updatable { + friend class Skeleton; + + friend class IkConstraintTimeline; + + RTTI_DECL + +public: + /// Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified + /// in the world coordinate system. + static void apply(Bone &bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha); + + /// Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as + /// possible. The target is specified in the world coordinate system. + /// @param child A direct descendant of the parent bone. + static void apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, float softness, float alpha); + + IkConstraint(IkConstraintData &data, Skeleton &skeleton); + + /// Applies the constraint to the constrained bones. + void apply(); + + virtual void update(); + + virtual int getOrder(); + + IkConstraintData &getData(); + + Vector &getBones(); + + Bone *getTarget(); + + void setTarget(Bone *inValue); + + int getBendDirection(); + + void setBendDirection(int inValue); + + bool getCompress(); + + void setCompress(bool inValue); + + bool getStretch(); + + void setStretch(bool inValue); + + float getMix(); + + void setMix(float inValue); + + float getSoftness(); + + void setSoftness(float inValue); + + bool isActive(); + + void setActive(bool inValue); + +private: + IkConstraintData &_data; + Vector _bones; + int _bendDirection; + bool _compress; + bool _stretch; + float _mix; + float _softness; + Bone *_target; + bool _active; +}; +} // namespace spine + +#endif /* Spine_IkConstraint_h */ diff --git a/cocos/editor-support/spine/IkConstraintData.cpp b/cocos/editor-support/spine/IkConstraintData.cpp new file mode 100644 index 0000000..263f9df --- /dev/null +++ b/cocos/editor-support/spine/IkConstraintData.cpp @@ -0,0 +1,108 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +using namespace spine; + +IkConstraintData::IkConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _bendDirection(1), + _compress(false), + _stretch(false), + _uniform(false), + _mix(1), + _softness(0) { +} + +Vector &IkConstraintData::getBones() { + return _bones; +} + +BoneData *IkConstraintData::getTarget() { + return _target; +} + +void IkConstraintData::setTarget(BoneData *inValue) { + _target = inValue; +} + +int IkConstraintData::getBendDirection() { + return _bendDirection; +} + +void IkConstraintData::setBendDirection(int inValue) { + _bendDirection = inValue; +} + +float IkConstraintData::getMix() { + return _mix; +} + +void IkConstraintData::setMix(float inValue) { + _mix = inValue; +} + +bool IkConstraintData::getStretch() { + return _stretch; +} + +void IkConstraintData::setStretch(bool inValue) { + _stretch = inValue; +} + +bool IkConstraintData::getCompress() { + return _compress; +} + +void IkConstraintData::setCompress(bool inValue) { + _compress = inValue; +} + +bool IkConstraintData::getUniform() { + return _uniform; +} + +void IkConstraintData::setUniform(bool inValue) { + _uniform = inValue; +} + +float IkConstraintData::getSoftness() { + return _softness; +} + +void IkConstraintData::setSoftness(float inValue) { + _softness = inValue; +} diff --git a/cocos/editor-support/spine/IkConstraintData.h b/cocos/editor-support/spine/IkConstraintData.h new file mode 100644 index 0000000..3461e96 --- /dev/null +++ b/cocos/editor-support/spine/IkConstraintData.h @@ -0,0 +1,89 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_IkConstraintData_h +#define Spine_IkConstraintData_h + +#include +#include +#include +#include + +namespace spine { +class BoneData; + +class SP_API IkConstraintData : public ConstraintData { + friend class SkeletonBinary; + friend class SkeletonJson; + friend class IkConstraint; + friend class Skeleton; + friend class IkConstraintTimeline; + +public: + explicit IkConstraintData(const String& name); + + /// The bones that are constrained by this IK Constraint. + Vector& getBones(); + + /// The bone that is the IK target. + BoneData* getTarget(); + void setTarget(BoneData* inValue); + + /// Controls the bend direction of the IK bones, either 1 or -1. + int getBendDirection(); + void setBendDirection(int inValue); + + bool getCompress(); + void setCompress(bool inValue); + + bool getStretch(); + void setStretch(bool inValue); + + bool getUniform(); + void setUniform(bool inValue); + + float getMix(); + void setMix(float inValue); + + float getSoftness(); + void setSoftness(float inValue); + +private: + Vector _bones; + BoneData* _target; + int _bendDirection; + bool _compress; + bool _stretch; + bool _uniform; + float _mix; + float _softness; +}; +} // namespace spine + +#endif /* Spine_IkConstraintData_h */ diff --git a/cocos/editor-support/spine/IkConstraintTimeline.cpp b/cocos/editor-support/spine/IkConstraintTimeline.cpp new file mode 100644 index 0000000..9f58d48 --- /dev/null +++ b/cocos/editor-support/spine/IkConstraintTimeline.cpp @@ -0,0 +1,168 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(IkConstraintTimeline, CurveTimeline) + +const int IkConstraintTimeline::ENTRIES = 6; +const int IkConstraintTimeline::PREV_TIME = -6; +const int IkConstraintTimeline::PREV_MIX = -5; +const int IkConstraintTimeline::PREV_SOFTNESS = -4; +const int IkConstraintTimeline::PREV_BEND_DIRECTION = -3; +const int IkConstraintTimeline::PREV_COMPRESS = -2; +const int IkConstraintTimeline::PREV_STRETCH = -1; +const int IkConstraintTimeline::MIX = 1; +const int IkConstraintTimeline::SOFTNESS = 2; +const int IkConstraintTimeline::BEND_DIRECTION = 3; +const int IkConstraintTimeline::COMPRESS = 4; +const int IkConstraintTimeline::STRETCH = 5; + +IkConstraintTimeline::IkConstraintTimeline(int frameCount) : CurveTimeline(frameCount), _ikConstraintIndex(0) { + _frames.setSize(frameCount * ENTRIES, 0); +} + +void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + IkConstraint *constraintP = skeleton._ikConstraints[_ikConstraintIndex]; + IkConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._mix = constraint._data._mix; + constraint._softness = constraint._data._softness; + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + return; + case MixBlend_First: + constraint._mix += (constraint._data._mix - constraint._mix) * alpha; + constraint._softness += (constraint._data._softness - constraint._softness) * alpha; + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + return; + default: + return; + } + } + + if (time >= _frames[_frames.size() - ENTRIES]) { + // Time is after last frame. + if (blend == MixBlend_Setup) { + constraint._mix = + constraint._data._mix + (_frames[_frames.size() + PREV_MIX] - constraint._data._mix) * alpha; + constraint._softness = constraint._data._softness + (_frames[_frames.size() + PREV_SOFTNESS] - constraint._data._softness) * alpha; + if (direction == MixDirection_Out) { + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + } else { + constraint._bendDirection = (int)_frames[_frames.size() + PREV_BEND_DIRECTION]; + constraint._compress = _frames[_frames.size() + PREV_COMPRESS] != 0; + constraint._stretch = _frames[_frames.size() + PREV_STRETCH] != 0; + } + } else { + constraint._mix += (_frames[_frames.size() + PREV_MIX] - constraint._mix) * alpha; + constraint._softness += (_frames[_frames.size() + PREV_SOFTNESS] - constraint._softness) * alpha; + if (direction == MixDirection_In) { + constraint._bendDirection = (int)_frames[_frames.size() + PREV_BEND_DIRECTION]; + constraint._compress = _frames[_frames.size() + PREV_COMPRESS] != 0; + constraint._stretch = _frames[_frames.size() + PREV_STRETCH] != 0; + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(_frames, time, ENTRIES); + float mix = _frames[frame + PREV_MIX]; + float softness = _frames[frame + PREV_SOFTNESS]; + float frameTime = _frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + + if (blend == MixBlend_Setup) { + constraint._mix = + constraint._data._mix + (mix + (_frames[frame + MIX] - mix) * percent - constraint._data._mix) * alpha; + constraint._softness = constraint._data._softness + (softness + (_frames[frame + SOFTNESS] - softness) * percent - constraint._data._softness) * alpha; + if (direction == MixDirection_Out) { + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + } else { + constraint._bendDirection = (int)_frames[_frames.size() + PREV_BEND_DIRECTION]; + constraint._compress = _frames[frame + PREV_COMPRESS] != 0; + constraint._stretch = _frames[frame + PREV_STRETCH] != 0; + } + } else { + constraint._mix += (mix + (_frames[frame + MIX] - mix) * percent - constraint._mix) * alpha; + constraint._softness += (softness + (_frames[frame + SOFTNESS] - softness) * percent - constraint._softness) * alpha; + if (direction == MixDirection_In) { + constraint._bendDirection = (int)_frames[frame + PREV_BEND_DIRECTION]; + constraint._compress = _frames[frame + PREV_COMPRESS] != 0; + constraint._stretch = _frames[frame + PREV_STRETCH] != 0; + } + } +} + +int IkConstraintTimeline::getPropertyId() { + return ((int)TimelineType_IkConstraint << 24) + _ikConstraintIndex; +} + +void IkConstraintTimeline::setFrame(int frameIndex, float time, float mix, float softness, int bendDirection, bool compress, bool stretch) { + frameIndex *= ENTRIES; + _frames[frameIndex] = time; + _frames[frameIndex + MIX] = mix; + _frames[frameIndex + SOFTNESS] = softness; + _frames[frameIndex + BEND_DIRECTION] = (float)bendDirection; + _frames[frameIndex + COMPRESS] = compress ? 1 : 0; + _frames[frameIndex + STRETCH] = stretch ? 1 : 0; +} diff --git a/cocos/editor-support/spine/IkConstraintTimeline.h b/cocos/editor-support/spine/IkConstraintTimeline.h new file mode 100644 index 0000000..c5ceffa --- /dev/null +++ b/cocos/editor-support/spine/IkConstraintTimeline.h @@ -0,0 +1,73 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_IkConstraintTimeline_h +#define Spine_IkConstraintTimeline_h + +#include + +namespace spine { + +class SP_API IkConstraintTimeline : public CurveTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + static const int ENTRIES; + + explicit IkConstraintTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + + /// Sets the time, mix and bend direction of the specified keyframe. + void setFrame(int frameIndex, float time, float mix, float softness, int bendDirection, bool compress, bool stretch); + +private: + static const int PREV_TIME; + static const int PREV_MIX; + static const int PREV_SOFTNESS; + static const int PREV_BEND_DIRECTION; + static const int PREV_COMPRESS; + static const int PREV_STRETCH; + static const int MIX; + static const int SOFTNESS; + static const int BEND_DIRECTION; + static const int COMPRESS; + static const int STRETCH; + + Vector _frames; + int _ikConstraintIndex; +}; +} // namespace spine + +#endif /* Spine_IkConstraintTimeline_h */ diff --git a/cocos/editor-support/spine/Json.cpp b/cocos/editor-support/spine/Json.cpp new file mode 100644 index 0000000..0c50d0f --- /dev/null +++ b/cocos/editor-support/spine/Json.cpp @@ -0,0 +1,556 @@ +/* +Copyright (c) 2009, Dave Gamble +Copyright (c) 2013, Esoteric Software + +Permission is hereby granted, dispose of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +/* Json */ +/* JSON parser in CPP, from json.c in the spine-c runtime */ + +#ifndef _DEFAULT_SOURCE + /* Bring strings.h definitions into string.h, where appropriate */ + #define _DEFAULT_SOURCE +#endif + +#ifndef _BSD_SOURCE + /* Bring strings.h definitions into string.h, where appropriate */ + #define _BSD_SOURCE +#endif + +#include +#include +#include + +#include +#include + +using namespace spine; + +const int Json::JSON_FALSE = 0; +const int Json::JSON_TRUE = 1; +const int Json::JSON_NULL = 2; +const int Json::JSON_NUMBER = 3; +const int Json::JSON_STRING = 4; +const int Json::JSON_ARRAY = 5; +const int Json::JSON_OBJECT = 6; + +const char *Json::_error = NULL; + +Json *Json::getItem(Json *object, const char *string) { + Json *c = object->_child; + while (c && json_strcasecmp(c->_name, string)) { + c = c->_next; + } + return c; +} + +const char *Json::getString(Json *object, const char *name, const char *defaultValue) { + object = getItem(object, name); + if (object) { + return object->_valueString; + } + + return defaultValue; +} + +float Json::getFloat(Json *value, const char *name, float defaultValue) { + value = getItem(value, name); + return value ? value->_valueFloat : defaultValue; +} + +int Json::getInt(Json *value, const char *name, int defaultValue) { + value = getItem(value, name); + return value ? value->_valueInt : defaultValue; +} + +bool Json::getBoolean(spine::Json *value, const char *name, bool defaultValue) { + value = getItem(value, name); + if (value) { + if (value->_valueString) return strcmp(value->_valueString, "true") == 0; + if (value->_type == JSON_TRUE) return true; + if (value->_type == JSON_FALSE) return false; + if (value->_type == JSON_NULL) return false; + if (value->_type == JSON_NUMBER) return value->_valueFloat != 0; + return defaultValue; + } else { + return defaultValue; + } +} + +const char *Json::getError() { + return _error; +} + +Json::Json(const char *value) : _next(NULL), +#if SPINE_JSON_HAVE_PREV + _prev(NULL), +#endif + _child(NULL), + _type(0), + _size(0), + _valueString(NULL), + _valueInt(0), + _valueFloat(0), + _name(NULL) { + if (value) { + value = parseValue(this, skip(value)); + + assert(value); + } +} + +Json::~Json() { + spine::Json *curr = nullptr; + spine::Json *next = _child; + do { + curr = next; + if (curr) { + next = curr->_next; + } + delete curr; + } while (next); + + if (_valueString) { + SpineExtension::free(_valueString, __FILE__, __LINE__); + } + + if (_name) { + SpineExtension::free(_name, __FILE__, __LINE__); + } +} + +const char *Json::skip(const char *inValue) { + if (!inValue) { + /* must propagate NULL since it's often called in skip(f(...)) form */ + return NULL; + } + + while (*inValue && (unsigned char)*inValue <= 32) { + inValue++; + } + + return inValue; +} + +const char *Json::parseValue(Json *item, const char *value) { + /* Referenced by constructor, parseArray(), and parseObject(). */ + /* Always called with the result of skip(). */ +#ifdef SPINE_JSON_DEBUG /* Checked at entry to graph, constructor, and after every parse call. */ + if (!value) { + /* Fail on null. */ + return NULL; + } +#endif + + switch (*value) { + case 'n': { + if (!strncmp(value + 1, "ull", 3)) { + item->_type = JSON_NULL; + return value + 4; + } + break; + } + case 'f': { + if (!strncmp(value + 1, "alse", 4)) { + item->_type = JSON_FALSE; + /* calloc prevents us needing item->_type = JSON_FALSE or valueInt = 0 here */ + return value + 5; + } + break; + } + case 't': { + if (!strncmp(value + 1, "rue", 3)) { + item->_type = JSON_TRUE; + item->_valueInt = 1; + return value + 4; + } + break; + } + case '\"': + return parseString(item, value); + case '[': + return parseArray(item, value); + case '{': + return parseObject(item, value); + case '-': /* fallthrough */ + case '0': /* fallthrough */ + case '1': /* fallthrough */ + case '2': /* fallthrough */ + case '3': /* fallthrough */ + case '4': /* fallthrough */ + case '5': /* fallthrough */ + case '6': /* fallthrough */ + case '7': /* fallthrough */ + case '8': /* fallthrough */ + case '9': + return parseNumber(item, value); + default: + break; + } + + _error = value; + return NULL; /* failure. */ +} + +static const unsigned char firstByteMark[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC}; + +const char *Json::parseString(Json *item, const char *str) { + const char *ptr = str + 1; + char *ptr2; + char *out; + int len = 0; + unsigned uc, uc2; + if (*str != '\"') { + /* TODO: don't need this check when called from parseValue, but do need from parseObject */ + _error = str; + return 0; + } /* not a string! */ + + while (*ptr != '\"' && *ptr && ++len) { + if (*ptr++ == '\\') { + ptr++; /* Skip escaped quotes. */ + } + } + + out = SpineExtension::alloc(len + 1, __FILE__, __LINE__); /* The length needed for the string, roughly. */ + if (!out) { + return 0; + } + + ptr = str + 1; + ptr2 = out; + while (*ptr != '\"' && *ptr) { + if (*ptr != '\\') { + *ptr2++ = *ptr++; + } else { + ptr++; + switch (*ptr) { + case 'b': + *ptr2++ = '\b'; + break; + case 'f': + *ptr2++ = '\f'; + break; + case 'n': + *ptr2++ = '\n'; + break; + case 'r': + *ptr2++ = '\r'; + break; + case 't': + *ptr2++ = '\t'; + break; + case 'u': { + /* transcode utf16 to utf8. */ + sscanf(ptr + 1, "%4x", &uc); + ptr += 4; /* get the unicode char. */ + + if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) { + break; /* check for invalid. */ + } + + /* TODO provide an option to ignore surrogates, use unicode replacement character? */ + if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */ { + if (ptr[1] != '\\' || ptr[2] != 'u') { + break; /* missing second-half of surrogate. */ + } + sscanf(ptr + 3, "%4x", &uc2); + ptr += 6; + if (uc2 < 0xDC00 || uc2 > 0xDFFF) { + break; /* invalid second-half of surrogate. */ + } + uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF)); + } + + len = 4; + if (uc < 0x80) { + len = 1; + } else if (uc < 0x800) { + len = 2; + } else if (uc < 0x10000) { + len = 3; + } + ptr2 += len; + + switch (len) { + case 4: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + /* fallthrough */ + case 3: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + /* fallthrough */ + case 2: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + /* fallthrough */ + case 1: + *--ptr2 = (uc | firstByteMark[len]); + } + ptr2 += len; + break; + } + default: + *ptr2++ = *ptr; + break; + } + ptr++; + } + } + + *ptr2 = 0; + + if (*ptr == '\"') { + ptr++; /* TODO error handling if not \" or \0 ? */ + } + + item->_valueString = out; + item->_type = JSON_STRING; + + return ptr; +} + +const char *Json::parseNumber(Json *item, const char *num) { + double result = 0.0; + int negative = 0; + char *ptr = (char *)num; + + if (*ptr == '-') { + negative = -1; + ++ptr; + } + + while (*ptr >= '0' && *ptr <= '9') { + result = result * 10.0 + (*ptr - '0'); + ++ptr; + } + + if (*ptr == '.') { + double fraction = 0.0; + int n = 0; + ++ptr; + + while (*ptr >= '0' && *ptr <= '9') { + fraction = (fraction * 10.0) + (*ptr - '0'); + ++ptr; + ++n; + } + result += fraction / pow(10.0, n); + } + + if (negative) { + result = -result; + } + + if (*ptr == 'e' || *ptr == 'E') { + double exponent = 0; + int expNegative = 0; + int n = 0; + ++ptr; + + if (*ptr == '-') { + expNegative = -1; + ++ptr; + } else if (*ptr == '+') { + ++ptr; + } + + while (*ptr >= '0' && *ptr <= '9') { + exponent = (exponent * 10.0) + (*ptr - '0'); + ++ptr; + ++n; + } + + if (expNegative) { + result = result / pow(10, exponent); + } else { + result = result * pow(10, exponent); + } + } + + if (ptr != num) { + /* Parse success, number found. */ + item->_valueFloat = (float)result; + item->_valueInt = (int)result; + item->_type = JSON_NUMBER; + return ptr; + } else { + /* Parse failure, _error is set. */ + _error = num; + return NULL; + } +} + +const char *Json::parseArray(Json *item, const char *value) { + Json *child; + +#ifdef SPINE_JSON_DEBUG /* unnecessary, only callsite (parse_value) verifies this */ + if (*value != '[') { + ep = value; + return 0; + } /* not an array! */ +#endif + + item->_type = JSON_ARRAY; + value = skip(value + 1); + if (*value == ']') { + return value + 1; /* empty array. */ + } + + item->_child = child = new Json(NULL); + if (!item->_child) { + return NULL; /* memory fail */ + } + + value = skip(parseValue(child, skip(value))); /* skip any spacing, get the value. */ + + if (!value) { + return NULL; + } + + item->_size = 1; + + while (*value == ',') { + Json *new_item = new Json(NULL); + if (!new_item) { + return NULL; /* memory fail */ + } + child->_next = new_item; +#if SPINE_JSON_HAVE_PREV + new_item->prev = child; +#endif + child = new_item; + value = skip(parseValue(child, skip(value + 1))); + if (!value) { + return NULL; /* parse fail */ + } + item->_size++; + } + + if (*value == ']') { + return value + 1; /* end of array */ + } + + _error = value; + + return NULL; /* malformed. */ +} + +/* Build an object from the text. */ +const char *Json::parseObject(Json *item, const char *value) { + Json *child; + +#ifdef SPINE_JSON_DEBUG /* unnecessary, only callsite (parse_value) verifies this */ + if (*value != '{') { + ep = value; + return 0; + } /* not an object! */ +#endif + + item->_type = JSON_OBJECT; + value = skip(value + 1); + if (*value == '}') { + return value + 1; /* empty array. */ + } + + item->_child = child = new Json(NULL); + if (!item->_child) { + return NULL; + } + value = skip(parseString(child, skip(value))); + if (!value) { + return NULL; + } + child->_name = child->_valueString; + child->_valueString = 0; + if (*value != ':') { + _error = value; + return NULL; + } /* fail! */ + + value = skip(parseValue(child, skip(value + 1))); /* skip any spacing, get the value. */ + if (!value) { + return NULL; + } + + item->_size = 1; + + while (*value == ',') { + Json *new_item = new Json(NULL); + if (!new_item) { + return NULL; /* memory fail */ + } + child->_next = new_item; +#if SPINE_JSON_HAVE_PREV + new_item->prev = child; +#endif + child = new_item; + value = skip(parseString(child, skip(value + 1))); + if (!value) { + return NULL; + } + child->_name = child->_valueString; + child->_valueString = 0; + if (*value != ':') { + _error = value; + return NULL; + } /* fail! */ + + value = skip(parseValue(child, skip(value + 1))); /* skip any spacing, get the value. */ + if (!value) { + return NULL; + } + item->_size++; + } + + if (*value == '}') { + return value + 1; /* end of array */ + } + + _error = value; + + return NULL; /* malformed. */ +} + +int Json::json_strcasecmp(const char *s1, const char *s2) { + /* TODO we may be able to elide these NULL checks if we can prove + * the graph and input (only callsite is Json_getItem) should not have NULLs + */ + if (s1 && s2) { +#if defined(_WIN32) + return _stricmp(s1, s2); +#else + return strcasecmp(s1, s2); +#endif + } else { + if (s1 < s2) { + return -1; /* s1 is null, s2 is not */ + } else if (s1 == s2) { + return 0; /* both are null */ + } else { + return 1; /* s2 is nul s1 is not */ + } + } +} diff --git a/cocos/editor-support/spine/Json.h b/cocos/editor-support/spine/Json.h new file mode 100644 index 0000000..478d84b --- /dev/null +++ b/cocos/editor-support/spine/Json.h @@ -0,0 +1,113 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Json_h +#define Spine_Json_h + +#include + +#ifndef SPINE_JSON_HAVE_PREV + /* spine doesn't use the "prev" link in the Json sibling lists. */ + #define SPINE_JSON_HAVE_PREV 0 +#endif + +namespace spine { +class SP_API Json { + friend class SkeletonJson; + +public: + /* Json Types: */ + static const int JSON_FALSE; + static const int JSON_TRUE; + static const int JSON_NULL; + static const int JSON_NUMBER; + static const int JSON_STRING; + static const int JSON_ARRAY; + static const int JSON_OBJECT; + + /* Get item "string" from object. Case insensitive. */ + static Json *getItem(Json *object, const char *string); + + static const char *getString(Json *object, const char *name, const char *defaultValue); + + static float getFloat(Json *object, const char *name, float defaultValue); + + static int getInt(Json *object, const char *name, int defaultValue); + + static bool getBoolean(Json *object, const char *name, bool defaultValue); + + /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when Json_create() returns 0. 0 when Json_create() succeeds. */ + static const char *getError(); + + /* Supply a block of JSON, and this returns a Json object you can interrogate. Call Json_dispose when finished. */ + explicit Json(const char *value); + + ~Json(); + +private: + static const char *_error; + + Json *_next; +#if SPINE_JSON_HAVE_PREV + Json *_prev; /* next/prev allow you to walk array/object chains. Alternatively, use getSize/getItem */ +#endif + Json *_child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + + int _type; /* The type of the item, as above. */ + int _size; /* The number of children. */ + + const char *_valueString; /* The item's string, if type==JSON_STRING */ + int _valueInt; /* The item's number, if type==JSON_NUMBER */ + float _valueFloat; /* The item's number, if type==JSON_NUMBER */ + + const char *_name; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + + /* Utility to jump whitespace and cr/lf */ + static const char *skip(const char *inValue); + + /* Parser core - when encountering text, process appropriately. */ + static const char *parseValue(Json *item, const char *value); + + /* Parse the input text into an unescaped cstring, and populate item. */ + static const char *parseString(Json *item, const char *str); + + /* Parse the input text to generate a number, and populate the result into item. */ + static const char *parseNumber(Json *item, const char *num); + + /* Build an array from input text. */ + static const char *parseArray(Json *item, const char *value); + + /* Build an object from the text. */ + static const char *parseObject(Json *item, const char *value); + + static int json_strcasecmp(const char *s1, const char *s2); +}; +} // namespace spine + +#endif /* Spine_Json_h */ diff --git a/cocos/editor-support/spine/LinkedMesh.cpp b/cocos/editor-support/spine/LinkedMesh.cpp new file mode 100644 index 0000000..861a102 --- /dev/null +++ b/cocos/editor-support/spine/LinkedMesh.cpp @@ -0,0 +1,45 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +using namespace spine; + +LinkedMesh::LinkedMesh(MeshAttachment *mesh, const String &skin, size_t slotIndex, const String &parent, bool inheritDeform) : _mesh(mesh), + _skin(skin), + _slotIndex(slotIndex), + _parent(parent), + _inheritDeform(inheritDeform) { +} diff --git a/cocos/editor-support/spine/LinkedMesh.h b/cocos/editor-support/spine/LinkedMesh.h new file mode 100644 index 0000000..accd83a --- /dev/null +++ b/cocos/editor-support/spine/LinkedMesh.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_LinkedMesh_h +#define Spine_LinkedMesh_h + +#include +#include + +namespace spine { +class MeshAttachment; + +class SP_API LinkedMesh : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + +public: + LinkedMesh(MeshAttachment *mesh, const String &skin, size_t slotIndex, const String &parent, bool inheritDeform); + +private: + MeshAttachment *_mesh; + String _skin; + size_t _slotIndex; + String _parent; + bool _inheritDeform; +}; +} // namespace spine + +#endif /* Spine_LinkedMesh_h */ diff --git a/cocos/editor-support/spine/MathUtil.cpp b/cocos/editor-support/spine/MathUtil.cpp new file mode 100644 index 0000000..30f8ae2 --- /dev/null +++ b/cocos/editor-support/spine/MathUtil.cpp @@ -0,0 +1,128 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include +#include + +// Required for division by 0 in _isNaN on MSVC +#ifdef _MSC_VER + #pragma warning(disable : 4723) +#endif + +using namespace spine; + +const float MathUtil::Pi = 3.1415926535897932385f; +const float MathUtil::Pi_2 = 3.1415926535897932385f * 2; +const float MathUtil::Deg_Rad = (3.1415926535897932385f / 180.0f); +const float MathUtil::Rad_Deg = (180.0f / 3.1415926535897932385f); + +float MathUtil::abs(float v) { + return ((v) < 0 ? -(v) : (v)); +} + +float MathUtil::sign(float v) { + return ((v) < 0 ? -1.0f : (v) > 0 ? 1.0f + : 0.0f); +} + +float MathUtil::clamp(float x, float min, float max) { + return ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))); +} + +float MathUtil::fmod(float a, float b) { + return (float)::fmod(a, b); +} + +/// Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323 +/// degrees), largest error of 0.00488 radians (0.2796 degrees). +float MathUtil::atan2(float y, float x) { + return (float)::atan2(y, x); +} + +/// Returns the cosine in radians from a lookup table. +float MathUtil::cos(float radians) { + return (float)::cos(radians); +} + +/// Returns the sine in radians from a lookup table. +float MathUtil::sin(float radians) { + return (float)::sin(radians); +} + +float MathUtil::sqrt(float v) { + return (float)::sqrt(v); +} + +float MathUtil::acos(float v) { + return (float)::acos(v); +} + +/// Returns the sine in radians from a lookup table. +float MathUtil::sinDeg(float degrees) { + return (float)::sin(degrees * MathUtil::Deg_Rad); +} + +/// Returns the cosine in radians from a lookup table. +float MathUtil::cosDeg(float degrees) { + return (float)::cos(degrees * MathUtil::Deg_Rad); +} + +/* Need to pass 0 as an argument, so VC++ doesn't error with C2124 */ +static bool _isNan(float value, float zero) { + float _nan = (float)0.0 / zero; + return 0 == memcmp((void *)&value, (void *)&_nan, sizeof(value)); +} + +bool MathUtil::isNan(float v) { + return _isNan(v, 0); +} + +float MathUtil::random() { + return ::rand() / (float)RAND_MAX; +} + +float MathUtil::randomTriangular(float min, float max) { + return randomTriangular(min, max, (min + max) * 0.5f); +} + +float MathUtil::randomTriangular(float min, float max, float mode) { + float u = random(); + float d = max - min; + if (u <= (mode - min) / d) return min + sqrt(u * d * (mode - min)); + return max - sqrt((1 - u) * d * (max - mode)); +} + +float MathUtil::pow(float a, float b) { + return (float)::pow(a, b); +} diff --git a/cocos/editor-support/spine/MathUtil.h b/cocos/editor-support/spine/MathUtil.h new file mode 100644 index 0000000..1041eb6 --- /dev/null +++ b/cocos/editor-support/spine/MathUtil.h @@ -0,0 +1,129 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_MathUtil_h +#define Spine_MathUtil_h + +#include + +#include + +namespace spine { + +class SP_API MathUtil : public SpineObject { +private: + MathUtil(); + +public: + static const float Pi; + static const float Pi_2; + static const float Deg_Rad; + static const float Rad_Deg; + + template + static inline T min(T a, T b) { return a < b ? a : b; } + + template + static inline T max(T a, T b) { return a > b ? a : b; } + + static float sign(float val); + + static float clamp(float x, float lower, float upper); + + static float abs(float v); + + /// Returns the sine in radians from a lookup table. + static float sin(float radians); + + /// Returns the cosine in radians from a lookup table. + static float cos(float radians); + + /// Returns the sine in radians from a lookup table. + static float sinDeg(float degrees); + + /// Returns the cosine in radians from a lookup table. + static float cosDeg(float degrees); + + /// Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323 + /// degrees), largest error of 0.00488 radians (0.2796 degrees). + static float atan2(float y, float x); + + static float acos(float v); + + static float sqrt(float v); + + static float fmod(float a, float b); + + static bool isNan(float v); + + static float random(); + + static float randomTriangular(float min, float max); + + static float randomTriangular(float min, float max, float mode); + + static float pow(float a, float b); +}; + +struct SP_API Interpolation { + virtual float apply(float a) = 0; + + virtual float interpolate(float start, float end, float a) { + return start + (end - start) * apply(a); + } + + virtual ~Interpolation(){}; +}; + +struct SP_API PowInterpolation : public Interpolation { + PowInterpolation(int power) : power(power) { + } + + float apply(float a) { + if (a <= 0.5f) return MathUtil::pow(a * 2.0f, (float)power) / 2.0f; + return MathUtil::pow((a - 1.0f) * 2.0f, (float)power) / (power % 2 == 0 ? -2.0f : 2.0f) + 1.0f; + } + + int power; +}; + +struct SP_API PowOutInterpolation : public Interpolation { + PowOutInterpolation(int power) : power(power) { + } + + float apply(float a) { + return MathUtil::pow(a - 1, (float)power) * (power % 2 == 0 ? -1.0f : 1.0f) + 1.0f; + } + + int power; +}; + +} // namespace spine + +#endif /* Spine_MathUtil_h */ diff --git a/cocos/editor-support/spine/MeshAttachment.cpp b/cocos/editor-support/spine/MeshAttachment.cpp new file mode 100644 index 0000000..9e73ede --- /dev/null +++ b/cocos/editor-support/spine/MeshAttachment.cpp @@ -0,0 +1,330 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include + +using namespace spine; + +RTTI_IMPL(MeshAttachment, VertexAttachment) + +MeshAttachment::MeshAttachment(const String &name) : VertexAttachment(name), HasRendererObject(), _regionOffsetX(0), _regionOffsetY(0), _regionWidth(0), _regionHeight(0), _regionOriginalWidth(0), _regionOriginalHeight(0), _parentMesh(NULL), _path(), _regionU(0), _regionV(0), _regionU2(0), _regionV2(0), _width(0), _height(0), _color(1, 1, 1, 1), _hullLength(0), _regionRotate(false), _regionDegrees(0) {} + +MeshAttachment::~MeshAttachment() {} + +void MeshAttachment::updateUVs() { + if (_uvs.size() != _regionUVs.size()) { + _uvs.setSize(_regionUVs.size(), 0); + } + + int i = 0, n = _regionUVs.size(); + float u = _regionU, v = _regionV; + float width = 0, height = 0; + + switch (_regionDegrees) { + case 90: { + float textureWidth = _regionHeight / (_regionU2 - _regionU); + float textureHeight = _regionWidth / (_regionV2 - _regionV); + u -= (_regionOriginalHeight - _regionOffsetY - _regionHeight) / textureWidth; + v -= (_regionOriginalWidth - _regionOffsetX - _regionWidth) / textureHeight; + width = _regionOriginalHeight / textureWidth; + height = _regionOriginalWidth / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + _regionUVs[i + 1] * width; + _uvs[i + 1] = v + (1 - _regionUVs[i]) * height; + } + return; + } + case 180: { + float textureWidth = _regionWidth / (_regionU2 - _regionU); + float textureHeight = _regionHeight / (_regionV2 - _regionV); + u -= (_regionOriginalWidth - _regionOffsetX - _regionWidth) / textureWidth; + v -= _regionOffsetY / textureHeight; + width = _regionOriginalWidth / textureWidth; + height = _regionOriginalHeight / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + (1 - _regionUVs[i]) * width; + _uvs[i + 1] = v + (1 - _regionUVs[i + 1]) * height; + } + return; + } + case 270: { + float textureHeight = _regionHeight / (_regionV2 - _regionV); + float textureWidth = _regionWidth / (_regionU2 - _regionU); + u -= _regionOffsetY / textureWidth; + v -= _regionOffsetX / textureHeight; + width = _regionOriginalHeight / textureWidth; + height = _regionOriginalWidth / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + (1 - _regionUVs[i + 1]) * width; + _uvs[i + 1] = v + _regionUVs[i] * height; + } + return; + } + default: { + float textureWidth = _regionWidth / (_regionU2 - _regionU); + float textureHeight = _regionHeight / (_regionV2 - _regionV); + u -= _regionOffsetX / textureWidth; + v -= (_regionOriginalHeight - _regionOffsetY - _regionHeight) / textureHeight; + width = _regionOriginalWidth / textureWidth; + height = _regionOriginalHeight / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + _regionUVs[i] * width; + _uvs[i + 1] = v + _regionUVs[i + 1] * height; + } + } + } +} + +int MeshAttachment::getHullLength() { + return _hullLength; +} + +void MeshAttachment::setHullLength(int inValue) { + _hullLength = inValue; +} + +Vector &MeshAttachment::getRegionUVs() { + return _regionUVs; +} + +Vector &MeshAttachment::getUVs() { + return _uvs; +} + +Vector &MeshAttachment::getTriangles() { + return _triangles; +} + +const String &MeshAttachment::getPath() { + return _path; +} + +void MeshAttachment::setPath(const String &inValue) { + _path = inValue; +} + +float MeshAttachment::getRegionU() { + return _regionU; +} + +void MeshAttachment::setRegionU(float inValue) { + _regionU = inValue; +} + +float MeshAttachment::getRegionV() { + return _regionV; +} + +void MeshAttachment::setRegionV(float inValue) { + _regionV = inValue; +} + +float MeshAttachment::getRegionU2() { + return _regionU2; +} + +void MeshAttachment::setRegionU2(float inValue) { + _regionU2 = inValue; +} + +float MeshAttachment::getRegionV2() { + return _regionV2; +} + +void MeshAttachment::setRegionV2(float inValue) { + _regionV2 = inValue; +} + +bool MeshAttachment::getRegionRotate() { + return _regionRotate; +} + +void MeshAttachment::setRegionRotate(bool inValue) { + _regionRotate = inValue; +} + +int MeshAttachment::getRegionDegrees() { + return _regionDegrees; +} + +void MeshAttachment::setRegionDegrees(int inValue) { + _regionDegrees = inValue; +} + +float MeshAttachment::getRegionOffsetX() { + return _regionOffsetX; +} + +void MeshAttachment::setRegionOffsetX(float inValue) { + _regionOffsetX = inValue; +} + +float MeshAttachment::getRegionOffsetY() { + return _regionOffsetY; +} + +void MeshAttachment::setRegionOffsetY(float inValue) { + _regionOffsetY = inValue; +} + +float MeshAttachment::getRegionWidth() { + return _regionWidth; +} + +void MeshAttachment::setRegionWidth(float inValue) { + _regionWidth = inValue; +} + +float MeshAttachment::getRegionHeight() { + return _regionHeight; +} + +void MeshAttachment::setRegionHeight(float inValue) { + _regionHeight = inValue; +} + +float MeshAttachment::getRegionOriginalWidth() { + return _regionOriginalWidth; +} + +void MeshAttachment::setRegionOriginalWidth(float inValue) { + _regionOriginalWidth = inValue; +} + +float MeshAttachment::getRegionOriginalHeight() { + return _regionOriginalHeight; +} + +void MeshAttachment::setRegionOriginalHeight(float inValue) { + _regionOriginalHeight = inValue; +} + +MeshAttachment *MeshAttachment::getParentMesh() { + return _parentMesh; +} + +void MeshAttachment::setParentMesh(MeshAttachment *inValue) { + _parentMesh = inValue; + if (inValue != NULL) { + _bones.clearAndAddAll(inValue->_bones); + _vertices.clearAndAddAll(inValue->_vertices); + _worldVerticesLength = inValue->_worldVerticesLength; + _regionUVs.clearAndAddAll(inValue->_regionUVs); + _triangles.clearAndAddAll(inValue->_triangles); + _hullLength = inValue->_hullLength; + _edges.clearAndAddAll(inValue->_edges); + _width = inValue->_width; + _height = inValue->_height; + } +} + +Vector &MeshAttachment::getEdges() { + return _edges; +} + +float MeshAttachment::getWidth() { + return _width; +} + +void MeshAttachment::setWidth(float inValue) { + _width = inValue; +} + +float MeshAttachment::getHeight() { + return _height; +} + +void MeshAttachment::setHeight(float inValue) { + _height = inValue; +} + +spine::Color &MeshAttachment::getColor() { + return _color; +} + +Attachment *MeshAttachment::copy() { + if (_parentMesh) return newLinkedMesh(); + + MeshAttachment *copy = new (__FILE__, __LINE__) MeshAttachment(getName()); + copy->setRendererObject(getRendererObject()); + copy->_regionU = _regionU; + copy->_regionV = _regionV; + copy->_regionU2 = _regionU2; + copy->_regionV2 = _regionV2; + copy->_regionRotate = _regionRotate; + copy->_regionDegrees = _regionDegrees; + copy->_regionOffsetX = _regionOffsetX; + copy->_regionOffsetY = _regionOffsetY; + copy->_regionWidth = _regionWidth; + copy->_regionHeight = _regionHeight; + copy->_regionOriginalWidth = _regionOriginalWidth; + copy->_regionOriginalHeight = _regionOriginalHeight; + copy->_path = _path; + copy->_color.set(_color); + + copyTo(copy); + copy->_regionUVs.clearAndAddAll(_regionUVs); + copy->_uvs.clearAndAddAll(_uvs); + copy->_triangles.clearAndAddAll(_triangles); + copy->_hullLength = _hullLength; + + // Nonessential. + copy->_edges.clearAndAddAll(copy->_edges); + copy->_width = _width; + copy->_height = _height; + return copy; +} + +MeshAttachment *MeshAttachment::newLinkedMesh() { + MeshAttachment *copy = new (__FILE__, __LINE__) MeshAttachment(getName()); + copy->setRendererObject(getRendererObject()); + copy->_regionU = _regionU; + copy->_regionV = _regionV; + copy->_regionU2 = _regionU2; + copy->_regionV2 = _regionV2; + copy->_regionRotate = _regionRotate; + copy->_regionDegrees = _regionDegrees; + copy->_regionOffsetX = _regionOffsetX; + copy->_regionOffsetY = _regionOffsetY; + copy->_regionWidth = _regionWidth; + copy->_regionHeight = _regionHeight; + copy->_regionOriginalWidth = _regionOriginalWidth; + copy->_regionOriginalHeight = _regionOriginalHeight; + copy->_path = _path; + copy->_color.set(_color); + copy->_deformAttachment = this->_deformAttachment; + copy->setParentMesh(_parentMesh ? _parentMesh : this); + copy->updateUVs(); + return copy; +} diff --git a/cocos/editor-support/spine/MeshAttachment.h b/cocos/editor-support/spine/MeshAttachment.h new file mode 100644 index 0000000..9220b76 --- /dev/null +++ b/cocos/editor-support/spine/MeshAttachment.h @@ -0,0 +1,143 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_MeshAttachment_h +#define Spine_MeshAttachment_h + +#include +#include +#include +#include + +namespace spine { +/// Attachment that displays a texture region using a mesh. +class SP_API MeshAttachment : public VertexAttachment, public HasRendererObject { + friend class SkeletonBinary; + friend class SkeletonJson; + friend class AtlasAttachmentLoader; + + RTTI_DECL + +public: + explicit MeshAttachment(const String& name); + + virtual ~MeshAttachment(); + + void updateUVs(); + + int getHullLength(); + void setHullLength(int inValue); + + Vector& getRegionUVs(); + + /// The UV pair for each vertex, normalized within the entire texture. See also MeshAttachment::updateUVs + Vector& getUVs(); + + Vector& getTriangles(); + + Color& getColor(); + + const String& getPath(); + void setPath(const String& inValue); + + float getRegionU(); + void setRegionU(float inValue); + + float getRegionV(); + void setRegionV(float inValue); + + float getRegionU2(); + void setRegionU2(float inValue); + + float getRegionV2(); + void setRegionV2(float inValue); + + bool getRegionRotate(); + void setRegionRotate(bool inValue); + + int getRegionDegrees(); + void setRegionDegrees(int inValue); + + float getRegionOffsetX(); + void setRegionOffsetX(float inValue); + + // Pixels stripped from the bottom left, unrotated. + float getRegionOffsetY(); + void setRegionOffsetY(float inValue); + + float getRegionWidth(); + void setRegionWidth(float inValue); + + // Unrotated, stripped size. + float getRegionHeight(); + void setRegionHeight(float inValue); + + float getRegionOriginalWidth(); + void setRegionOriginalWidth(float inValue); + + // Unrotated, unstripped size. + float getRegionOriginalHeight(); + void setRegionOriginalHeight(float inValue); + + MeshAttachment* getParentMesh(); + void setParentMesh(MeshAttachment* inValue); + + // Nonessential. + Vector& getEdges(); + float getWidth(); + void setWidth(float inValue); + float getHeight(); + void setHeight(float inValue); + + virtual Attachment* copy(); + + MeshAttachment* newLinkedMesh(); + +private: + float _regionOffsetX, _regionOffsetY, _regionWidth, _regionHeight, _regionOriginalWidth, _regionOriginalHeight; + MeshAttachment* _parentMesh; + Vector _uvs; + Vector _regionUVs; + Vector _triangles; + Vector _edges; + String _path; + float _regionU; + float _regionV; + float _regionU2; + float _regionV2; + float _width; + float _height; + Color _color; + int _hullLength; + bool _regionRotate; + int _regionDegrees; +}; +} // namespace spine + +#endif /* Spine_MeshAttachment_h */ diff --git a/cocos/editor-support/spine/MixBlend.h b/cocos/editor-support/spine/MixBlend.h new file mode 100644 index 0000000..a0824ea --- /dev/null +++ b/cocos/editor-support/spine/MixBlend.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_MixPose_h +#define Spine_MixPose_h + +namespace spine { + +/// Controls how a timeline is mixed with the setup or current pose. +/// See also Timeline::apply(Skeleton&, float, float, Vector&, float, Blend, MixDirection) +enum MixBlend { + MixBlend_Setup = 0, + MixBlend_First, + MixBlend_Replace, + MixBlend_Add +}; +} // namespace spine + +#endif /* Spine_MixPose_h */ diff --git a/cocos/editor-support/spine/MixDirection.h b/cocos/editor-support/spine/MixDirection.h new file mode 100644 index 0000000..1f9920b --- /dev/null +++ b/cocos/editor-support/spine/MixDirection.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_MixDirection_h +#define Spine_MixDirection_h + +namespace spine { + +/// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose) or mixing in toward 1 (the timeline's pose). +/// See also Timeline::apply(Skeleton&, float, float, Vector&, float, MixPose, MixDirection) +enum MixDirection { + MixDirection_In = 0, + MixDirection_Out +}; + +} // namespace spine + +#endif /* Spine_MixDirection_h */ diff --git a/cocos/editor-support/spine/PathAttachment.cpp b/cocos/editor-support/spine/PathAttachment.cpp new file mode 100644 index 0000000..3bdc719 --- /dev/null +++ b/cocos/editor-support/spine/PathAttachment.cpp @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +using namespace spine; + +RTTI_IMPL(PathAttachment, VertexAttachment) + +PathAttachment::PathAttachment(const String& name) : VertexAttachment(name), _closed(false), _constantSpeed(false) { +} + +Vector& PathAttachment::getLengths() { + return _lengths; +} + +bool PathAttachment::isClosed() { + return _closed; +} + +void PathAttachment::setClosed(bool inValue) { + _closed = inValue; +} + +bool PathAttachment::isConstantSpeed() { + return _constantSpeed; +} + +void PathAttachment::setConstantSpeed(bool inValue) { + _constantSpeed = inValue; +} + +Attachment* PathAttachment::copy() { + PathAttachment* copy = new (__FILE__, __LINE__) PathAttachment(getName()); + copyTo(copy); + copy->_lengths.clearAndAddAll(_lengths); + copy->_closed = _closed; + copy->_constantSpeed = _constantSpeed; + return copy; +} diff --git a/cocos/editor-support/spine/PathAttachment.h b/cocos/editor-support/spine/PathAttachment.h new file mode 100644 index 0000000..0c968af --- /dev/null +++ b/cocos/editor-support/spine/PathAttachment.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathAttachment_h +#define Spine_PathAttachment_h + +#include + +namespace spine { +class SP_API PathAttachment : public VertexAttachment { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + explicit PathAttachment(const String& name); + + /// The length in the setup pose from the start of the path to the end of each curve. + Vector& getLengths(); + bool isClosed(); + void setClosed(bool inValue); + bool isConstantSpeed(); + void setConstantSpeed(bool inValue); + + virtual Attachment* copy(); + +private: + Vector _lengths; + bool _closed; + bool _constantSpeed; +}; +} // namespace spine + +#endif /* Spine_PathAttachment_h */ diff --git a/cocos/editor-support/spine/PathConstraint.cpp b/cocos/editor-support/spine/PathConstraint.cpp new file mode 100644 index 0000000..b5aac9e --- /dev/null +++ b/cocos/editor-support/spine/PathConstraint.cpp @@ -0,0 +1,547 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraint, Updatable) + +const float PathConstraint::EPSILON = 0.00001f; +const int PathConstraint::NONE = -1; +const int PathConstraint::BEFORE = -2; +const int PathConstraint::AFTER = -3; + +PathConstraint::PathConstraint(PathConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _target(skeleton.findSlot( + data.getTarget()->getName())), + _position(data.getPosition()), + _spacing(data.getSpacing()), + _rotateMix(data.getRotateMix()), + _translateMix(data.getTranslateMix()), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); i++) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } + + _segments.setSize(10, 0); +} + +void PathConstraint::apply() { + update(); +} + +void PathConstraint::update() { + Attachment *baseAttachment = _target->getAttachment(); + if (baseAttachment == NULL || !baseAttachment->getRTTI().instanceOf(PathAttachment::rtti)) { + return; + } + + PathAttachment *attachment = static_cast(baseAttachment); + + float rotateMix = _rotateMix; + float translateMix = _translateMix; + bool translate = translateMix > 0; + bool rotate = rotateMix > 0; + if (!translate && !rotate) { + return; + } + + PathConstraintData &data = _data; + bool percentSpacing = data._spacingMode == SpacingMode_Percent; + RotateMode rotateMode = data._rotateMode; + bool tangents = rotateMode == RotateMode_Tangent, scale = rotateMode == RotateMode_ChainScale; + size_t boneCount = _bones.size(); + size_t spacesCount = tangents ? boneCount : boneCount + 1; + _spaces.setSize(spacesCount, 0); + float spacing = _spacing; + if (scale || !percentSpacing) { + if (scale) _lengths.setSize(boneCount, 0); + bool lengthSpacing = data._spacingMode == SpacingMode_Length; + + for (size_t i = 0, n = spacesCount - 1; i < n;) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + float setupLength = bone._data.getLength(); + if (setupLength < PathConstraint::EPSILON) { + if (scale) _lengths[i] = 0; + _spaces[++i] = 0; + } else if (percentSpacing) { + if (scale) { + float x = setupLength * bone._a, y = setupLength * bone._c; + float length = MathUtil::sqrt(x * x + y * y); + _lengths[i] = length; + } + _spaces[++i] = spacing; + } else { + float x = setupLength * bone._a; + float y = setupLength * bone._c; + float length = MathUtil::sqrt(x * x + y * y); + if (scale) { + _lengths[i] = length; + } + + _spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + } + } + } else { + for (size_t i = 1; i < spacesCount; ++i) { + _spaces[i] = spacing; + } + } + + Vector &positions = computeWorldPositions(*attachment, spacesCount, tangents, + data.getPositionMode() == PositionMode_Percent, percentSpacing); + float boneX = positions[0]; + float boneY = positions[1]; + float offsetRotation = data.getOffsetRotation(); + bool tip; + if (offsetRotation == 0) { + tip = rotateMode == RotateMode_Chain; + } else { + tip = false; + Bone &p = _target->getBone(); + offsetRotation *= p.getA() * p.getD() - p.getB() * p.getC() > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + } + + for (size_t i = 0, p = 3; i < boneCount; i++, p += 3) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + bone._worldX += (boneX - bone._worldX) * translateMix; + bone._worldY += (boneY - bone._worldY) * translateMix; + float x = positions[p]; + float y = positions[p + 1]; + float dx = x - boneX; + float dy = y - boneY; + if (scale) { + float length = _lengths[i]; + if (length >= PathConstraint::EPSILON) { + float s = (MathUtil::sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; + bone._a *= s; + bone._c *= s; + } + } + + boneX = x; + boneY = y; + + if (rotate) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d, r, cos, sin; + if (tangents) + r = positions[p - 1]; + else if (_spaces[i + 1] < PathConstraint::EPSILON) + r = positions[p + 2]; + else + r = MathUtil::atan2(dy, dx); + + r -= MathUtil::atan2(c, a); + + if (tip) { + cos = MathUtil::cos(r); + sin = MathUtil::sin(r); + float length = bone._data.getLength(); + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; + } else + r += offsetRotation; + + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= rotateMix; + cos = MathUtil::cos(r); + sin = MathUtil::sin(r); + bone._a = cos * a - sin * c; + bone._b = cos * b - sin * d; + bone._c = sin * a + cos * c; + bone._d = sin * b + cos * d; + } + + bone._appliedValid = false; + } +} + +int PathConstraint::getOrder() { + return _data.getOrder(); +} + +float PathConstraint::getPosition() { + return _position; +} + +void PathConstraint::setPosition(float inValue) { + _position = inValue; +} + +float PathConstraint::getSpacing() { + return _spacing; +} + +void PathConstraint::setSpacing(float inValue) { + _spacing = inValue; +} + +float PathConstraint::getRotateMix() { + return _rotateMix; +} + +void PathConstraint::setRotateMix(float inValue) { + _rotateMix = inValue; +} + +float PathConstraint::getTranslateMix() { + return _translateMix; +} + +void PathConstraint::setTranslateMix(float inValue) { + _translateMix = inValue; +} + +Vector &PathConstraint::getBones() { + return _bones; +} + +Slot *PathConstraint::getTarget() { + return _target; +} + +void PathConstraint::setTarget(Slot *inValue) { + _target = inValue; +} + +PathConstraintData &PathConstraint::getData() { + return _data; +} + +Vector & +PathConstraint::computeWorldPositions(PathAttachment &path, int spacesCount, bool tangents, bool percentPosition, bool percentSpacing) { + Slot &target = *_target; + float position = _position; + _positions.setSize(spacesCount * 3 + 2, 0); + Vector &out = _positions; + Vector &world = _world; + bool closed = path.isClosed(); + int verticesLength = path.getWorldVerticesLength(); + int curveCount = verticesLength / 6; + int prevCurve = NONE; + + float pathLength; + if (!path.isConstantSpeed()) { + Vector &lengths = path.getLengths(); + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + if (percentPosition) position *= pathLength; + + if (percentSpacing) { + for (int i = 1; i < spacesCount; ++i) + _spaces[i] *= pathLength; + } + + world.setSize(8, 0); + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { + float space = _spaces[i]; + position += space; + float p = position; + + if (closed) { + p = MathUtil::fmod(p, pathLength); + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + if (prevCurve != BEFORE) { + prevCurve = BEFORE; + path.computeWorldVertices(target, 2, 4, world, 0); + } + + addBeforePosition(p, world, 0, out, o); + + continue; + } else if (p > pathLength) { + if (prevCurve != AFTER) { + prevCurve = AFTER; + path.computeWorldVertices(target, verticesLength - 6, 4, world, 0); + } + + addAfterPosition(p - pathLength, world, 0, out, o); + + continue; + } + + // Determine curve containing position. + for (;; curve++) { + float length = lengths[curve]; + if (p > length) continue; + + if (curve == 0) + p /= length; + else { + float prev = lengths[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + + if (curve != prevCurve) { + prevCurve = curve; + if (closed && curve == curveCount) { + path.computeWorldVertices(target, verticesLength - 4, 4, world, 0); + path.computeWorldVertices(target, 0, 4, world, 4); + } else + path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0); + } + + addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], + out, o, tangents || (i > 0 && space < EPSILON)); + } + return out; + } + + // World vertices. + if (closed) { + verticesLength += 2; + world.setSize(verticesLength, 0); + path.computeWorldVertices(target, 2, verticesLength - 4, world, 0); + path.computeWorldVertices(target, 0, 2, world, verticesLength - 4); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } else { + curveCount--; + verticesLength -= 4; + world.setSize(verticesLength, 0); + path.computeWorldVertices(target, 2, verticesLength, world, 0); + } + + // Curve lengths. + _curves.setSize(curveCount, 0); + pathLength = 0; + float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0; + float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; + for (int i = 0, w = 2; i < curveCount; i++, w += 6) { + cx1 = world[w]; + cy1 = world[w + 1]; + cx2 = world[w + 2]; + cy2 = world[w + 3]; + x2 = world[w + 4]; + y2 = world[w + 5]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f; + tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f; + dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + + if (percentPosition) + position *= pathLength; + else + position *= pathLength / path.getLengths()[curveCount - 1]; + + if (percentSpacing) { + for (int i = 1; i < spacesCount; ++i) + _spaces[i] *= pathLength; + } + + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { + float space = _spaces[i]; + position += space; + float p = position; + + if (closed) { + p = MathUtil::fmod(p, pathLength); + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + addBeforePosition(p, world, 0, out, o); + continue; + } else if (p > pathLength) { + addAfterPosition(p - pathLength, world, verticesLength - 4, out, o); + continue; + } + + // Determine curve containing position. + for (;; curve++) { + float length = _curves[curve]; + if (p > length) continue; + if (curve == 0) + p /= length; + else { + float prev = _curves[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + + // Curve segment lengths. + if (curve != prevCurve) { + prevCurve = curve; + int ii = curve * 6; + x1 = world[ii]; + y1 = world[ii + 1]; + cx1 = world[ii + 2]; + cy1 = world[ii + 3]; + cx2 = world[ii + 4]; + cy2 = world[ii + 5]; + x2 = world[ii + 6]; + y2 = world[ii + 7]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.03f; + tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f; + dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; + curveLength = MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[9] = curveLength; + segment = 0; + } + + // Weight by segment length. + p *= curveLength; + for (;; segment++) { + float length = _segments[segment]; + if (p > length) continue; + if (segment == 0) + p /= length; + else { + float prev = _segments[segment - 1]; + p = segment + (p - prev) / (length - prev); + } + break; + } + addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, + tangents || (i > 0 && space < EPSILON)); + } + + return out; +} + +void PathConstraint::addBeforePosition(float p, Vector &temp, int i, Vector &output, int o) { + float x1 = temp[i]; + float y1 = temp[i + 1]; + float dx = temp[i + 2] - x1; + float dy = temp[i + 3] - y1; + float r = MathUtil::atan2(dy, dx); + output[o] = x1 + p * MathUtil::cos(r); + output[o + 1] = y1 + p * MathUtil::sin(r); + output[o + 2] = r; +} + +void PathConstraint::addAfterPosition(float p, Vector &temp, int i, Vector &output, int o) { + float x1 = temp[i + 2]; + float y1 = temp[i + 3]; + float dx = x1 - temp[i]; + float dy = y1 - temp[i + 1]; + float r = MathUtil::atan2(dy, dx); + output[o] = x1 + p * MathUtil::cos(r); + output[o + 1] = y1 + p * MathUtil::sin(r); + output[o + 2] = r; +} + +void PathConstraint::addCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, + float y2, Vector &output, int o, bool tangents) { + if (p < EPSILON || MathUtil::isNan(p)) { + output[o] = x1; + output[o + 1] = y1; + output[o + 2] = MathUtil::atan2(cy1 - y1, cx1 - x1); + return; + } + + float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u; + float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p; + float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + output[o] = x; + output[o + 1] = y; + if (tangents) { + if (p < 0.001) + output[o + 2] = MathUtil::atan2(cy1 - y1, cx1 - x1); + else + output[o + 2] = MathUtil::atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } +} + +bool PathConstraint::isActive() { + return _active; +} + +void PathConstraint::setActive(bool inValue) { + _active = inValue; +} diff --git a/cocos/editor-support/spine/PathConstraint.h b/cocos/editor-support/spine/PathConstraint.h new file mode 100644 index 0000000..f8fb569 --- /dev/null +++ b/cocos/editor-support/spine/PathConstraint.h @@ -0,0 +1,115 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraint_h +#define Spine_PathConstraint_h + +#include + +#include + +namespace spine { +class PathConstraintData; +class Skeleton; +class PathAttachment; +class Bone; +class Slot; + +class SP_API PathConstraint : public Updatable { + friend class Skeleton; + friend class PathConstraintMixTimeline; + friend class PathConstraintPositionTimeline; + friend class PathConstraintSpacingTimeline; + + RTTI_DECL + +public: + PathConstraint(PathConstraintData& data, Skeleton& skeleton); + + /// Applies the constraint to the constrained bones. + void apply(); + + virtual void update(); + + virtual int getOrder(); + + float getPosition(); + void setPosition(float inValue); + + float getSpacing(); + void setSpacing(float inValue); + + float getRotateMix(); + void setRotateMix(float inValue); + + float getTranslateMix(); + void setTranslateMix(float inValue); + + Vector& getBones(); + + Slot* getTarget(); + void setTarget(Slot* inValue); + + PathConstraintData& getData(); + + bool isActive(); + + void setActive(bool inValue); + +private: + static const float EPSILON; + static const int NONE; + static const int BEFORE; + static const int AFTER; + + PathConstraintData& _data; + Vector _bones; + Slot* _target; + float _position, _spacing, _rotateMix, _translateMix; + + Vector _spaces; + Vector _positions; + Vector _world; + Vector _curves; + Vector _lengths; + Vector _segments; + + bool _active; + + Vector& computeWorldPositions(PathAttachment& path, int spacesCount, bool tangents, bool percentPosition, bool percentSpacing); + + static void addBeforePosition(float p, Vector& temp, int i, Vector& output, int o); + + static void addAfterPosition(float p, Vector& temp, int i, Vector& output, int o); + + static void addCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, Vector& output, int o, bool tangents); +}; +} // namespace spine + +#endif /* Spine_PathConstraint_h */ diff --git a/cocos/editor-support/spine/PathConstraintData.cpp b/cocos/editor-support/spine/PathConstraintData.cpp new file mode 100644 index 0000000..607cd9a --- /dev/null +++ b/cocos/editor-support/spine/PathConstraintData.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include + +using namespace spine; + +PathConstraintData::PathConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _positionMode(PositionMode_Fixed), + _spacingMode(SpacingMode_Length), + _rotateMode(RotateMode_Tangent), + _offsetRotation(0), + _position(0), + _spacing(0), + _rotateMix(0), + _translateMix(0) { +} + +Vector &PathConstraintData::getBones() { + return _bones; +} + +SlotData *PathConstraintData::getTarget() { + return _target; +} + +void PathConstraintData::setTarget(SlotData *inValue) { + _target = inValue; +} + +PositionMode PathConstraintData::getPositionMode() { + return _positionMode; +} + +void PathConstraintData::setPositionMode(PositionMode inValue) { + _positionMode = inValue; +} + +SpacingMode PathConstraintData::getSpacingMode() { + return _spacingMode; +} + +void PathConstraintData::setSpacingMode(SpacingMode inValue) { + _spacingMode = inValue; +} + +RotateMode PathConstraintData::getRotateMode() { + return _rotateMode; +} + +void PathConstraintData::setRotateMode(RotateMode inValue) { + _rotateMode = inValue; +} + +float PathConstraintData::getOffsetRotation() { + return _offsetRotation; +} + +void PathConstraintData::setOffsetRotation(float inValue) { + _offsetRotation = inValue; +} + +float PathConstraintData::getPosition() { + return _position; +} + +void PathConstraintData::setPosition(float inValue) { + _position = inValue; +} + +float PathConstraintData::getSpacing() { + return _spacing; +} + +void PathConstraintData::setSpacing(float inValue) { + _spacing = inValue; +} + +float PathConstraintData::getRotateMix() { + return _rotateMix; +} + +void PathConstraintData::setRotateMix(float inValue) { + _rotateMix = inValue; +} + +float PathConstraintData::getTranslateMix() { + return _translateMix; +} + +void PathConstraintData::setTranslateMix(float inValue) { + _translateMix = inValue; +} diff --git a/cocos/editor-support/spine/PathConstraintData.h b/cocos/editor-support/spine/PathConstraintData.h new file mode 100644 index 0000000..915d65b --- /dev/null +++ b/cocos/editor-support/spine/PathConstraintData.h @@ -0,0 +1,98 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraintData_h +#define Spine_PathConstraintData_h + +#include +#include +#include +#include +#include +#include +#include + +namespace spine { +class BoneData; +class SlotData; + +class SP_API PathConstraintData : public ConstraintData { + friend class SkeletonBinary; + friend class SkeletonJson; + + friend class PathConstraint; + friend class Skeleton; + friend class PathConstraintMixTimeline; + friend class PathConstraintPositionTimeline; + friend class PathConstraintSpacingTimeline; + +public: + explicit PathConstraintData(const String& name); + + Vector& getBones(); + + SlotData* getTarget(); + void setTarget(SlotData* inValue); + + PositionMode getPositionMode(); + void setPositionMode(PositionMode inValue); + + SpacingMode getSpacingMode(); + void setSpacingMode(SpacingMode inValue); + + RotateMode getRotateMode(); + void setRotateMode(RotateMode inValue); + + float getOffsetRotation(); + void setOffsetRotation(float inValue); + + float getPosition(); + void setPosition(float inValue); + + float getSpacing(); + void setSpacing(float inValue); + + float getRotateMix(); + void setRotateMix(float inValue); + + float getTranslateMix(); + void setTranslateMix(float inValue); + +private: + Vector _bones; + SlotData* _target; + PositionMode _positionMode; + SpacingMode _spacingMode; + RotateMode _rotateMode; + float _offsetRotation; + float _position, _spacing, _rotateMix, _translateMix; +}; +} // namespace spine + +#endif /* Spine_PathConstraintData_h */ diff --git a/cocos/editor-support/spine/PathConstraintMixTimeline.cpp b/cocos/editor-support/spine/PathConstraintMixTimeline.cpp new file mode 100644 index 0000000..3c43fd4 --- /dev/null +++ b/cocos/editor-support/spine/PathConstraintMixTimeline.cpp @@ -0,0 +1,124 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintMixTimeline, CurveTimeline) + +const int PathConstraintMixTimeline::ENTRIES = 3; +const int PathConstraintMixTimeline::PREV_TIME = -3; +const int PathConstraintMixTimeline::PREV_ROTATE = -2; +const int PathConstraintMixTimeline::PREV_TRANSLATE = -1; +const int PathConstraintMixTimeline::ROTATE = 1; +const int PathConstraintMixTimeline::TRANSLATE = 2; + +PathConstraintMixTimeline::PathConstraintMixTimeline(int frameCount) : CurveTimeline(frameCount), + _pathConstraintIndex(0) { + _frames.setSize(frameCount * ENTRIES, 0); +} + +void PathConstraintMixTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraintP = skeleton._pathConstraints[_pathConstraintIndex]; + PathConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._rotateMix = constraint._data._rotateMix; + constraint._translateMix = constraint._data._translateMix; + return; + case MixBlend_First: + constraint._rotateMix += (constraint._data._rotateMix - constraint._rotateMix) * alpha; + constraint._translateMix += (constraint._data._translateMix - constraint._translateMix) * alpha; + return; + default: + return; + } + } + + float rotate, translate; + if (time >= _frames[_frames.size() - ENTRIES]) { + // Time is after last frame. + rotate = _frames[_frames.size() + PREV_ROTATE]; + translate = _frames[_frames.size() + PREV_TRANSLATE]; + } else { + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(_frames, time, ENTRIES); + rotate = _frames[frame + PREV_ROTATE]; + translate = _frames[frame + PREV_TRANSLATE]; + float frameTime = _frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + + rotate += (_frames[frame + ROTATE] - rotate) * percent; + translate += (_frames[frame + TRANSLATE] - translate) * percent; + } + + if (blend == MixBlend_Setup) { + constraint._rotateMix = constraint._data._rotateMix + (rotate - constraint._data._rotateMix) * alpha; + constraint._translateMix = + constraint._data._translateMix + (translate - constraint._data._translateMix) * alpha; + } else { + constraint._rotateMix += (rotate - constraint._rotateMix) * alpha; + constraint._translateMix += (translate - constraint._translateMix) * alpha; + } +} + +int PathConstraintMixTimeline::getPropertyId() { + return ((int)TimelineType_PathConstraintMix << 24) + _pathConstraintIndex; +} + +void PathConstraintMixTimeline::setFrame(int frameIndex, float time, float rotateMix, float translateMix) { + frameIndex *= ENTRIES; + _frames[frameIndex] = time; + _frames[frameIndex + ROTATE] = rotateMix; + _frames[frameIndex + TRANSLATE] = translateMix; +} diff --git a/cocos/editor-support/spine/PathConstraintMixTimeline.h b/cocos/editor-support/spine/PathConstraintMixTimeline.h new file mode 100644 index 0000000..4e5141a --- /dev/null +++ b/cocos/editor-support/spine/PathConstraintMixTimeline.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraintMixTimeline_h +#define Spine_PathConstraintMixTimeline_h + +#include + +namespace spine { +#define SP_PATHCONSTRAINTMIXTIMELINE_ENTRIES 5 + +class SP_API PathConstraintMixTimeline : public CurveTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + static const int ENTRIES; + + explicit PathConstraintMixTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + +private: + static const int PREV_TIME; + static const int PREV_ROTATE; + static const int PREV_TRANSLATE; + static const int ROTATE; + static const int TRANSLATE; + + Vector _frames; + int _pathConstraintIndex; + + /// Sets the time and mixes of the specified keyframe. + void setFrame(int frameIndex, float time, float rotateMix, float translateMix); +}; +} // namespace spine + +#endif /* Spine_PathConstraintMixTimeline_h */ diff --git a/cocos/editor-support/spine/PathConstraintPositionTimeline.cpp b/cocos/editor-support/spine/PathConstraintPositionTimeline.cpp new file mode 100644 index 0000000..47ef1dc --- /dev/null +++ b/cocos/editor-support/spine/PathConstraintPositionTimeline.cpp @@ -0,0 +1,114 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintPositionTimeline, CurveTimeline) + +const int PathConstraintPositionTimeline::ENTRIES = 2; +const int PathConstraintPositionTimeline::PREV_TIME = -2; +const int PathConstraintPositionTimeline::PREV_VALUE = -1; +const int PathConstraintPositionTimeline::VALUE = 1; + +PathConstraintPositionTimeline::PathConstraintPositionTimeline(int frameCount) : CurveTimeline(frameCount), + _pathConstraintIndex(0) { + _frames.setSize(frameCount * ENTRIES, 0); +} + +PathConstraintPositionTimeline::~PathConstraintPositionTimeline() { +} + +void PathConstraintPositionTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraintP = skeleton._pathConstraints[_pathConstraintIndex]; + PathConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._position = constraint._data._position; + return; + case MixBlend_First: + constraint._position += (constraint._data._position - constraint._position) * alpha; + return; + default: + return; + } + } + + float position; + if (time >= _frames[_frames.size() - ENTRIES]) { + // Time is after last frame. + position = _frames[_frames.size() + PREV_VALUE]; + } else { + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(_frames, time, ENTRIES); + position = _frames[frame + PREV_VALUE]; + float frameTime = _frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + + position += (_frames[frame + VALUE] - position) * percent; + } + if (blend == MixBlend_Setup) + constraint._position = constraint._data._position + (position - constraint._data._position) * alpha; + else + constraint._position += (position - constraint._position) * alpha; +} + +int PathConstraintPositionTimeline::getPropertyId() { + return ((int)TimelineType_PathConstraintPosition << 24) + _pathConstraintIndex; +} + +void PathConstraintPositionTimeline::setFrame(int frameIndex, float time, float value) { + frameIndex *= ENTRIES; + _frames[frameIndex] = time; + _frames[frameIndex + VALUE] = value; +} diff --git a/cocos/editor-support/spine/PathConstraintPositionTimeline.h b/cocos/editor-support/spine/PathConstraintPositionTimeline.h new file mode 100644 index 0000000..986cc21 --- /dev/null +++ b/cocos/editor-support/spine/PathConstraintPositionTimeline.h @@ -0,0 +1,67 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraintPositionTimeline_h +#define Spine_PathConstraintPositionTimeline_h + +#include + +namespace spine { + +class SP_API PathConstraintPositionTimeline : public CurveTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + static const int ENTRIES; + + explicit PathConstraintPositionTimeline(int frameCount); + + virtual ~PathConstraintPositionTimeline(); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frameIndex, float time, float value); + +protected: + static const int PREV_TIME; + static const int PREV_VALUE; + static const int VALUE; + + Vector _frames; + int _pathConstraintIndex; +}; +} // namespace spine + +#endif /* Spine_PathConstraintPositionTimeline_h */ diff --git a/cocos/editor-support/spine/PathConstraintSpacingTimeline.cpp b/cocos/editor-support/spine/PathConstraintSpacingTimeline.cpp new file mode 100644 index 0000000..bfe9856 --- /dev/null +++ b/cocos/editor-support/spine/PathConstraintSpacingTimeline.cpp @@ -0,0 +1,100 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintSpacingTimeline, PathConstraintPositionTimeline) + +PathConstraintSpacingTimeline::PathConstraintSpacingTimeline(int frameCount) : PathConstraintPositionTimeline( + frameCount) { +} + +void PathConstraintSpacingTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraintP = skeleton._pathConstraints[_pathConstraintIndex]; + PathConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._spacing = constraint._data._spacing; + return; + case MixBlend_First: + constraint._spacing += (constraint._data._spacing - constraint._spacing) * alpha; + return; + default: + return; + } + } + + float spacing; + if (time >= _frames[_frames.size() - ENTRIES]) { + // Time is after last frame. + spacing = _frames[_frames.size() + PREV_VALUE]; + } else { + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(_frames, time, ENTRIES); + spacing = _frames[frame + PREV_VALUE]; + float frameTime = _frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + + spacing += (_frames[frame + VALUE] - spacing) * percent; + } + + if (blend == MixBlend_Setup) + constraint._spacing = constraint._data._spacing + (spacing - constraint._data._spacing) * alpha; + else + constraint._spacing += (spacing - constraint._spacing) * alpha; +} + +int PathConstraintSpacingTimeline::getPropertyId() { + return ((int)TimelineType_PathConstraintSpacing << 24) + _pathConstraintIndex; +} diff --git a/cocos/editor-support/spine/PathConstraintSpacingTimeline.h b/cocos/editor-support/spine/PathConstraintSpacingTimeline.h new file mode 100644 index 0000000..ee88e72 --- /dev/null +++ b/cocos/editor-support/spine/PathConstraintSpacingTimeline.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraintSpacingTimeline_h +#define Spine_PathConstraintSpacingTimeline_h + +#include + +namespace spine { +class SP_API PathConstraintSpacingTimeline : public PathConstraintPositionTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + explicit PathConstraintSpacingTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); +}; +} // namespace spine + +#endif /* Spine_PathConstraintSpacingTimeline_h */ diff --git a/cocos/editor-support/spine/PointAttachment.cpp b/cocos/editor-support/spine/PointAttachment.cpp new file mode 100644 index 0000000..d71d89c --- /dev/null +++ b/cocos/editor-support/spine/PointAttachment.cpp @@ -0,0 +1,90 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +#include + +using namespace spine; + +RTTI_IMPL(PointAttachment, Attachment) + +PointAttachment::PointAttachment(const String &name) : Attachment(name), _x(0), _y(0), _rotation(0) { +} + +void PointAttachment::computeWorldPosition(Bone &bone, float &ox, float &oy) { + bone.localToWorld(_x, _y, ox, oy); +} + +float PointAttachment::computeWorldRotation(Bone &bone) { + float cos = MathUtil::cosDeg(_rotation); + float sin = MathUtil::sinDeg(_rotation); + float ix = cos * bone._a + sin * bone._b; + float iy = cos * bone._c + sin * bone._d; + + return MathUtil::atan2(iy, ix) * MathUtil::Rad_Deg; +} + +float PointAttachment::getX() { + return _x; +} + +void PointAttachment::setX(float inValue) { + _x = inValue; +} + +float PointAttachment::getY() { + return _y; +} + +void PointAttachment::setY(float inValue) { + _y = inValue; +} + +float PointAttachment::getRotation() { + return _rotation; +} + +void PointAttachment::setRotation(float inValue) { + _rotation = inValue; +} + +Attachment *PointAttachment::copy() { + PointAttachment *copy = new (__FILE__, __LINE__) PointAttachment(getName()); + copy->_x = _x; + copy->_y = _y; + copy->_rotation = _rotation; + return copy; +} diff --git a/cocos/editor-support/spine/PointAttachment.h b/cocos/editor-support/spine/PointAttachment.h new file mode 100644 index 0000000..b9caaff --- /dev/null +++ b/cocos/editor-support/spine/PointAttachment.h @@ -0,0 +1,73 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PointAttachment_h +#define Spine_PointAttachment_h + +#include + +namespace spine { +class Bone; + +/// An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be +/// used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a +/// skin. +/// +/// See http://esotericsoftware.com/spine-point-attachments for Point Attachments in the Spine User Guide. +/// +class SP_API PointAttachment : public Attachment { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + explicit PointAttachment(const String& name); + + void computeWorldPosition(Bone& bone, float& ox, float& oy); + + float computeWorldRotation(Bone& bone); + + float getX(); + void setX(float inValue); + + float getY(); + void setY(float inValue); + + float getRotation(); + void setRotation(float inValue); + + virtual Attachment* copy(); + +private: + float _x, _y, _rotation; +}; +} // namespace spine + +#endif /* Spine_PointAttachment_h */ diff --git a/cocos/editor-support/spine/Pool.h b/cocos/editor-support/spine/Pool.h new file mode 100644 index 0000000..6dd4cee --- /dev/null +++ b/cocos/editor-support/spine/Pool.h @@ -0,0 +1,74 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Pool_h +#define Spine_Pool_h + +#include +#include +#include +#include + +namespace spine { +template +class SP_API Pool : public SpineObject { +public: + Pool() { + } + + ~Pool() { + ContainerUtil::cleanUpVectorOfPointers(_objects); + } + + T *obtain() { + if (_objects.size() > 0) { + T **object = &_objects[_objects.size() - 1]; + T *ret = *object; + _objects.removeAt(_objects.size() - 1); + + return ret; + } else { + T *ret = new (__FILE__, __LINE__) T(); + + return ret; + } + } + + void free(T *object) { + if (!_objects.contains(object)) { + _objects.add(object); + } + } + +private: + Vector _objects; +}; +} // namespace spine + +#endif /* Spine_Pool_h */ diff --git a/cocos/editor-support/spine/PositionMode.h b/cocos/editor-support/spine/PositionMode.h new file mode 100644 index 0000000..fe828d3 --- /dev/null +++ b/cocos/editor-support/spine/PositionMode.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PositionMode_h +#define Spine_PositionMode_h + +namespace spine { +enum PositionMode { + PositionMode_Fixed = 0, + PositionMode_Percent +}; +} + +#endif /* Spine_PositionMode_h */ diff --git a/cocos/editor-support/spine/RTTI.cpp b/cocos/editor-support/spine/RTTI.cpp new file mode 100644 index 0000000..fa7e8fd --- /dev/null +++ b/cocos/editor-support/spine/RTTI.cpp @@ -0,0 +1,60 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include + +using namespace spine; + +RTTI::RTTI(const char *className) : _className(className), _pBaseRTTI(NULL) { +} + +RTTI::RTTI(const char *className, const RTTI &baseRTTI) : _className(className), _pBaseRTTI(&baseRTTI) { +} + +const char *RTTI::getClassName() const { + return _className; +} + +bool RTTI::isExactly(const RTTI &rtti) const { + return !strcmp(this->_className, rtti._className); +} + +bool RTTI::instanceOf(const RTTI &rtti) const { + const RTTI *pCompare = this; + while (pCompare) { + if (!strcmp(pCompare->_className, rtti._className)) return true; + pCompare = pCompare->_pBaseRTTI; + } + return false; +} diff --git a/cocos/editor-support/spine/RTTI.h b/cocos/editor-support/spine/RTTI.h new file mode 100644 index 0000000..d474443 --- /dev/null +++ b/cocos/editor-support/spine/RTTI.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RTTI_h +#define Spine_RTTI_h + +#include + +namespace spine { +class SP_API RTTI : public SpineObject { +public: + explicit RTTI(const char *className); + + RTTI(const char *className, const RTTI &baseRTTI); + + const char *getClassName() const; + + bool isExactly(const RTTI &rtti) const; + + bool instanceOf(const RTTI &rtti) const; + +private: + // Prevent copying + RTTI(const RTTI &obj); + + RTTI &operator=(const RTTI &obj); + + const char *_className; + const RTTI *_pBaseRTTI; +}; +} // namespace spine + +#define RTTI_DECL \ +public: \ + static const spine::RTTI rtti; \ + virtual const spine::RTTI &getRTTI() const; + +#define RTTI_IMPL_NOPARENT(name) \ + const spine::RTTI name::rtti(#name); \ + const spine::RTTI &name::getRTTI() const { return rtti; } + +#define RTTI_IMPL(name, parent) \ + const spine::RTTI name::rtti(#name, parent::rtti); \ + const spine::RTTI &name::getRTTI() const { return rtti; } + +#endif /* Spine_RTTI_h */ diff --git a/cocos/editor-support/spine/RegionAttachment.cpp b/cocos/editor-support/spine/RegionAttachment.cpp new file mode 100644 index 0000000..e83afc9 --- /dev/null +++ b/cocos/editor-support/spine/RegionAttachment.cpp @@ -0,0 +1,287 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +#include + +using namespace spine; + +RTTI_IMPL(RegionAttachment, Attachment) + +const int RegionAttachment::BLX = 0; +const int RegionAttachment::BLY = 1; +const int RegionAttachment::ULX = 2; +const int RegionAttachment::ULY = 3; +const int RegionAttachment::URX = 4; +const int RegionAttachment::URY = 5; +const int RegionAttachment::BRX = 6; +const int RegionAttachment::BRY = 7; + +RegionAttachment::RegionAttachment(const String &name) : Attachment(name), HasRendererObject(), _x(0), _y(0), _rotation(0), _scaleX(1), _scaleY(1), _width(0), _height(0), _regionOffsetX(0), _regionOffsetY(0), _regionWidth(0), _regionHeight(0), _regionOriginalWidth(0), _regionOriginalHeight(0), _path(), _regionU(0), _regionV(0), _regionU2(0), _regionV2(0), _color(1, 1, 1, 1) { + _vertexOffset.setSize(NUM_UVS, 0); + _uvs.setSize(NUM_UVS, 0); +} + +void RegionAttachment::updateOffset() { + float regionScaleX = _width / _regionOriginalWidth * _scaleX; + float regionScaleY = _height / _regionOriginalHeight * _scaleY; + float localX = -_width / 2 * _scaleX + _regionOffsetX * regionScaleX; + float localY = -_height / 2 * _scaleY + _regionOffsetY * regionScaleY; + float localX2 = localX + _regionWidth * regionScaleX; + float localY2 = localY + _regionHeight * regionScaleY; + float cos = MathUtil::cosDeg(_rotation); + float sin = MathUtil::sinDeg(_rotation); + float localXCos = localX * cos + _x; + float localXSin = localX * sin; + float localYCos = localY * cos + _y; + float localYSin = localY * sin; + float localX2Cos = localX2 * cos + _x; + float localX2Sin = localX2 * sin; + float localY2Cos = localY2 * cos + _y; + float localY2Sin = localY2 * sin; + + _vertexOffset[BLX] = localXCos - localYSin; + _vertexOffset[BLY] = localYCos + localXSin; + _vertexOffset[ULX] = localXCos - localY2Sin; + _vertexOffset[ULY] = localY2Cos + localXSin; + _vertexOffset[URX] = localX2Cos - localY2Sin; + _vertexOffset[URY] = localY2Cos + localX2Sin; + _vertexOffset[BRX] = localX2Cos - localYSin; + _vertexOffset[BRY] = localYCos + localX2Sin; +} + +void RegionAttachment::setUVs(float u, float v, float u2, float v2, bool rotate) { + if (rotate) { + _uvs[URX] = u; + _uvs[URY] = v2; + _uvs[BRX] = u; + _uvs[BRY] = v; + _uvs[BLX] = u2; + _uvs[BLY] = v; + _uvs[ULX] = u2; + _uvs[ULY] = v2; + } else { + _uvs[ULX] = u; + _uvs[ULY] = v2; + _uvs[URX] = u; + _uvs[URY] = v; + _uvs[BRX] = u2; + _uvs[BRY] = v; + _uvs[BLX] = u2; + _uvs[BLY] = v2; + } +} + +void RegionAttachment::computeWorldVertices(Bone &bone, Vector &worldVertices, size_t offset, size_t stride) { + assert(worldVertices.size() >= (offset + 8)); + computeWorldVertices(bone, worldVertices.buffer(), offset, stride); +} + +void RegionAttachment::computeWorldVertices(Bone &bone, float *worldVertices, size_t offset, size_t stride) { + float x = bone.getWorldX(), y = bone.getWorldY(); + float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); + float offsetX, offsetY; + + offsetX = _vertexOffset[BRX]; + offsetY = _vertexOffset[BRY]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[BLX]; + offsetY = _vertexOffset[BLY]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[ULX]; + offsetY = _vertexOffset[ULY]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[URX]; + offsetY = _vertexOffset[URY]; + worldVertices[offset] = offsetX * a + offsetY * b + x; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; +} + +float RegionAttachment::getX() { + return _x; +} + +void RegionAttachment::setX(float inValue) { + _x = inValue; +} + +float RegionAttachment::getY() { + return _y; +} + +void RegionAttachment::setY(float inValue) { + _y = inValue; +} + +float RegionAttachment::getRotation() { + return _rotation; +} + +void RegionAttachment::setRotation(float inValue) { + _rotation = inValue; +} + +float RegionAttachment::getScaleX() { + return _scaleX; +} + +void RegionAttachment::setScaleX(float inValue) { + _scaleX = inValue; +} + +float RegionAttachment::getScaleY() { + return _scaleY; +} + +void RegionAttachment::setScaleY(float inValue) { + _scaleY = inValue; +} + +float RegionAttachment::getWidth() { + return _width; +} + +void RegionAttachment::setWidth(float inValue) { + _width = inValue; +} + +float RegionAttachment::getHeight() { + return _height; +} + +void RegionAttachment::setHeight(float inValue) { + _height = inValue; +} + +const String &RegionAttachment::getPath() { + return _path; +} + +void RegionAttachment::setPath(const String &inValue) { + _path = inValue; +} + +float RegionAttachment::getRegionOffsetX() { + return _regionOffsetX; +} + +void RegionAttachment::setRegionOffsetX(float inValue) { + _regionOffsetX = inValue; +} + +float RegionAttachment::getRegionOffsetY() { + return _regionOffsetY; +} + +void RegionAttachment::setRegionOffsetY(float inValue) { + _regionOffsetY = inValue; +} + +float RegionAttachment::getRegionWidth() { + return _regionWidth; +} + +void RegionAttachment::setRegionWidth(float inValue) { + _regionWidth = inValue; +} + +float RegionAttachment::getRegionHeight() { + return _regionHeight; +} + +void RegionAttachment::setRegionHeight(float inValue) { + _regionHeight = inValue; +} + +float RegionAttachment::getRegionOriginalWidth() { + return _regionOriginalWidth; +} + +void RegionAttachment::setRegionOriginalWidth(float inValue) { + _regionOriginalWidth = inValue; +} + +float RegionAttachment::getRegionOriginalHeight() { + return _regionOriginalHeight; +} + +void RegionAttachment::setRegionOriginalHeight(float inValue) { + _regionOriginalHeight = inValue; +} + +Vector &RegionAttachment::getOffset() { + return _vertexOffset; +} + +Vector &RegionAttachment::getUVs() { + return _uvs; +} + +spine::Color &RegionAttachment::getColor() { + return _color; +} + +Attachment *RegionAttachment::copy() { + RegionAttachment *copy = new (__FILE__, __LINE__) RegionAttachment(getName()); + copy->_regionWidth = _regionWidth; + copy->_regionHeight = _regionHeight; + copy->_regionOffsetX = _regionOffsetX; + copy->_regionOffsetY = _regionOffsetY; + copy->_regionOriginalWidth = _regionOriginalWidth; + copy->_regionOriginalHeight = _regionOriginalHeight; + copy->setRendererObject(getRendererObject()); + copy->_path = _path; + copy->_x = _x; + copy->_y = _y; + copy->_scaleX = _scaleX; + copy->_scaleY = _scaleY; + copy->_rotation = _rotation; + copy->_width = _width; + copy->_height = _height; + copy->_uvs.clearAndAddAll(_uvs); + copy->_vertexOffset.clearAndAddAll(_vertexOffset); + copy->_color.set(_color); + return copy; +} diff --git a/cocos/editor-support/spine/RegionAttachment.h b/cocos/editor-support/spine/RegionAttachment.h new file mode 100644 index 0000000..ae2ee47 --- /dev/null +++ b/cocos/editor-support/spine/RegionAttachment.h @@ -0,0 +1,133 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RegionAttachment_h +#define Spine_RegionAttachment_h + +#include +#include +#include + +#include + +#define NUM_UVS 8 + +namespace spine { +class Bone; + +/// Attachment that displays a texture region. +class SP_API RegionAttachment : public Attachment, public HasRendererObject { + friend class SkeletonBinary; + friend class SkeletonJson; + friend class AtlasAttachmentLoader; + + RTTI_DECL + +public: + explicit RegionAttachment(const String& name); + + void updateOffset(); + + void setUVs(float u, float v, float u2, float v2, bool rotate); + + /// Transforms the attachment's four vertices to world coordinates. + /// @param bone The parent bone. + /// @param worldVertices The output world vertices. Must have a length greater than or equal to offset + 8. + /// @param offset The worldVertices index to begin writing values. + /// @param stride The number of worldVertices entries between the value pairs written. + void computeWorldVertices(Bone& bone, float* worldVertices, size_t offset, size_t stride = 2); + void computeWorldVertices(Bone& bone, Vector& worldVertices, size_t offset, size_t stride = 2); + + float getX(); + void setX(float inValue); + float getY(); + void setY(float inValue); + float getRotation(); + void setRotation(float inValue); + float getScaleX(); + void setScaleX(float inValue); + float getScaleY(); + void setScaleY(float inValue); + float getWidth(); + void setWidth(float inValue); + float getHeight(); + void setHeight(float inValue); + + Color& getColor(); + + const String& getPath(); + void setPath(const String& inValue); + + float getRegionOffsetX(); + void setRegionOffsetX(float inValue); + + float getRegionOffsetY(); + void setRegionOffsetY(float inValue); + + float getRegionWidth(); + void setRegionWidth(float inValue); + + float getRegionHeight(); + void setRegionHeight(float inValue); + + float getRegionOriginalWidth(); + void setRegionOriginalWidth(float inValue); + + float getRegionOriginalHeight(); + void setRegionOriginalHeight(float inValue); + + Vector& getOffset(); + Vector& getUVs(); + + virtual Attachment* copy(); + +private: + static const int BLX; + static const int BLY; + static const int ULX; + static const int ULY; + static const int URX; + static const int URY; + static const int BRX; + static const int BRY; + + float _x, _y, _rotation, _scaleX, _scaleY, _width, _height; + float _regionOffsetX, _regionOffsetY, _regionWidth, _regionHeight, _regionOriginalWidth, _regionOriginalHeight; + Vector _vertexOffset; + Vector _uvs; + String _path; + float _regionU; + float _regionV; + float _regionU2; + float _regionV2; + Color _color; +}; +} // namespace spine + +#endif /* Spine_RegionAttachment_h */ diff --git a/cocos/editor-support/spine/RotateMode.h b/cocos/editor-support/spine/RotateMode.h new file mode 100644 index 0000000..3a76863 --- /dev/null +++ b/cocos/editor-support/spine/RotateMode.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RotateMode_h +#define Spine_RotateMode_h + +namespace spine { +enum RotateMode { + RotateMode_Tangent = 0, + RotateMode_Chain, + RotateMode_ChainScale +}; +} + +#endif /* Spine_RotateMode_h */ diff --git a/cocos/editor-support/spine/RotateTimeline.cpp b/cocos/editor-support/spine/RotateTimeline.cpp new file mode 100644 index 0000000..c74d0b4 --- /dev/null +++ b/cocos/editor-support/spine/RotateTimeline.cpp @@ -0,0 +1,138 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(RotateTimeline, CurveTimeline) + +RotateTimeline::RotateTimeline(int frameCount) : CurveTimeline(frameCount), _boneIndex(0) { + _frames.setSize(frameCount << 1, 0); +} + +void RotateTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton.getBones()[_boneIndex]; + if (!bone->_active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: { + bone->_rotation = bone->_data._rotation; + break; + } + case MixBlend_First: { + float r = bone->_data._rotation - bone->_rotation; + bone->_rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; + break; + } + default: { + // TODO? + break; + } + } + return; + } + + if (time >= _frames[_frames.size() - ENTRIES]) { + float r = _frames[_frames.size() + PREV_ROTATION]; + switch (blend) { + case MixBlend_Setup: + bone->_rotation = bone->_data._rotation + r * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + r += bone->_data._rotation - bone->_rotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + // Fall through. + case MixBlend_Add: + bone->_rotation += r * alpha; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(_frames, time, ENTRIES); + float prevRotation = _frames[frame + PREV_ROTATION]; + float frameTime = _frames[frame]; + float percent = getCurvePercent((frame >> 1) - 1, + 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + float r = _frames[frame + ROTATION] - prevRotation; + r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent; + switch (blend) { + case MixBlend_Setup: + bone->_rotation = bone->_data._rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + r += bone->_data._rotation - bone->_rotation; + // Fall through. + case MixBlend_Add: + bone->_rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; + } +} + +int RotateTimeline::getPropertyId() { + return ((int)TimelineType_Rotate << 24) + _boneIndex; +} + +void RotateTimeline::setFrame(int frameIndex, float time, float degrees) { + frameIndex <<= 1; + _frames[frameIndex] = time; + _frames[frameIndex + ROTATION] = degrees; +} + +int RotateTimeline::getBoneIndex() { + return _boneIndex; +} + +void RotateTimeline::setBoneIndex(int inValue) { + _boneIndex = inValue; +} + +Vector &RotateTimeline::getFrames() { + return _frames; +} diff --git a/cocos/editor-support/spine/RotateTimeline.h b/cocos/editor-support/spine/RotateTimeline.h new file mode 100644 index 0000000..fc9e463 --- /dev/null +++ b/cocos/editor-support/spine/RotateTimeline.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RotateTimeline_h +#define Spine_RotateTimeline_h + +#include + +namespace spine { +class SP_API RotateTimeline : public CurveTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + friend class AnimationState; + + RTTI_DECL + +public: + static const int ENTRIES = 2; + + explicit RotateTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frameIndex, float time, float degrees); + + int getBoneIndex(); + void setBoneIndex(int inValue); + + Vector& getFrames(); + +private: + static const int PREV_TIME = -2; + static const int PREV_ROTATION = -1; + static const int ROTATION = 1; + + int _boneIndex; + Vector _frames; // time, angle, ... +}; +} // namespace spine + +#endif /* Spine_RotateTimeline_h */ diff --git a/cocos/editor-support/spine/ScaleTimeline.cpp b/cocos/editor-support/spine/ScaleTimeline.cpp new file mode 100644 index 0000000..f9c2ec8 --- /dev/null +++ b/cocos/editor-support/spine/ScaleTimeline.cpp @@ -0,0 +1,153 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(ScaleTimeline, TranslateTimeline) + +ScaleTimeline::ScaleTimeline(int frameCount) : TranslateTimeline(frameCount) { +} + +void ScaleTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + Bone *boneP = skeleton._bones[_boneIndex]; + Bone &bone = *boneP; + + if (!bone._active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone._scaleX = bone._data._scaleX; + bone._scaleY = bone._data._scaleY; + return; + case MixBlend_First: + bone._scaleX += (bone._data._scaleX - bone._scaleX) * alpha; + bone._scaleY += (bone._data._scaleY - bone._scaleY) * alpha; + default: { + } + } + return; + } + + float x, y; + if (time >= _frames[_frames.size() - ENTRIES]) { + // Time is after last frame. + x = _frames[_frames.size() + PREV_X] * bone._data._scaleX; + y = _frames[_frames.size() + PREV_Y] * bone._data._scaleY; + } else { + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(_frames, time, ENTRIES); + x = _frames[frame + PREV_X]; + y = _frames[frame + PREV_Y]; + float frameTime = _frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + + x = (x + (_frames[frame + X] - x) * percent) * bone._data._scaleX; + y = (y + (_frames[frame + Y] - y) * percent) * bone._data._scaleY; + } + + if (alpha == 1) { + if (blend == MixBlend_Add) { + bone._scaleX += x - bone._data._scaleX; + bone._scaleY += y - bone._data._scaleY; + } else { + bone._scaleX = x; + bone._scaleY = y; + } + } else { + // Mixing out uses sign of setup or current pose, else use sign of key. + float bx, by; + if (direction == MixDirection_Out) { + switch (blend) { + case MixBlend_Setup: + bx = bone._data._scaleX; + by = bone._data._scaleY; + bone._scaleX = bx + (MathUtil::abs(x) * MathUtil::sign(bx) - bx) * alpha; + bone._scaleY = by + (MathUtil::abs(y) * MathUtil::sign(by) - by) * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bx = bone._scaleX; + by = bone._scaleY; + bone._scaleX = bx + (MathUtil::abs(x) * MathUtil::sign(bx) - bx) * alpha; + bone._scaleY = by + (MathUtil::abs(y) * MathUtil::sign(by) - by) * alpha; + break; + case MixBlend_Add: + bx = bone._scaleX; + by = bone._scaleY; + bone._scaleX = bx + (MathUtil::abs(x) * MathUtil::sign(bx) - bone._data._scaleX) * alpha; + bone._scaleY = by + (MathUtil::abs(y) * MathUtil::sign(by) - bone._data._scaleY) * alpha; + } + } else { + switch (blend) { + case MixBlend_Setup: + bx = MathUtil::abs(bone._data._scaleX) * MathUtil::sign(x); + by = MathUtil::abs(bone._data._scaleY) * MathUtil::sign(y); + bone._scaleX = bx + (x - bx) * alpha; + bone._scaleY = by + (y - by) * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bx = MathUtil::abs(bone._scaleX) * MathUtil::sign(x); + by = MathUtil::abs(bone._scaleY) * MathUtil::sign(y); + bone._scaleX = bx + (x - bx) * alpha; + bone._scaleY = by + (y - by) * alpha; + break; + case MixBlend_Add: + bx = MathUtil::sign(x); + by = MathUtil::sign(y); + bone._scaleX = MathUtil::abs(bone._scaleX) * bx + (x - MathUtil::abs(bone._data._scaleX) * bx) * alpha; + bone._scaleY = MathUtil::abs(bone._scaleY) * by + (y - MathUtil::abs(bone._data._scaleY) * by) * alpha; + } + } + } +} + +int ScaleTimeline::getPropertyId() { + return ((int)TimelineType_Scale << 24) + _boneIndex; +} diff --git a/cocos/editor-support/spine/ScaleTimeline.h b/cocos/editor-support/spine/ScaleTimeline.h new file mode 100644 index 0000000..01a85c8 --- /dev/null +++ b/cocos/editor-support/spine/ScaleTimeline.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ScaleTimeline_h +#define Spine_ScaleTimeline_h + +#include + +namespace spine { +class SP_API ScaleTimeline : public TranslateTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + explicit ScaleTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); +}; +} // namespace spine + +#endif /* Spine_ScaleTimeline_h */ diff --git a/cocos/editor-support/spine/ShearTimeline.cpp b/cocos/editor-support/spine/ShearTimeline.cpp new file mode 100644 index 0000000..1587cc5 --- /dev/null +++ b/cocos/editor-support/spine/ShearTimeline.cpp @@ -0,0 +1,112 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(ShearTimeline, TranslateTimeline) + +ShearTimeline::ShearTimeline(int frameCount) : TranslateTimeline(frameCount) { +} + +void ShearTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *boneP = skeleton._bones[_boneIndex]; + Bone &bone = *boneP; + if (!bone._active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone._shearX = bone._data._shearX; + bone._shearY = bone._data._shearY; + return; + case MixBlend_First: + bone._shearX += (bone._data._shearX - bone._shearX) * alpha; + bone._shearY += (bone._data._shearY - bone._shearY) * alpha; + default: { + } + } + return; + } + + float x, y; + if (time >= _frames[_frames.size() - ENTRIES]) { + // Time is after last frame. + x = _frames[_frames.size() + PREV_X]; + y = _frames[_frames.size() + PREV_Y]; + } else { + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(_frames, time, ENTRIES); + x = _frames[frame + PREV_X]; + y = _frames[frame + PREV_Y]; + float frameTime = _frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + + x = x + (_frames[frame + X] - x) * percent; + y = y + (_frames[frame + Y] - y) * percent; + } + + switch (blend) { + case MixBlend_Setup: + bone._shearX = bone._data._shearX + x * alpha; + bone._shearY = bone._data._shearY + y * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bone._shearX += (bone._data._shearX + x - bone._shearX) * alpha; + bone._shearY += (bone._data._shearY + y - bone._shearY) * alpha; + break; + case MixBlend_Add: + bone._shearX += x * alpha; + bone._shearY += y * alpha; + } +} + +int ShearTimeline::getPropertyId() { + return ((int)TimelineType_Shear << 24) + _boneIndex; +} diff --git a/cocos/editor-support/spine/ShearTimeline.h b/cocos/editor-support/spine/ShearTimeline.h new file mode 100644 index 0000000..de00885 --- /dev/null +++ b/cocos/editor-support/spine/ShearTimeline.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ShearTimeline_h +#define Spine_ShearTimeline_h + +#include + +namespace spine { +class SP_API ShearTimeline : public TranslateTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + explicit ShearTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); +}; +} // namespace spine + +#endif /* Spine_ShearTimeline_h */ diff --git a/cocos/editor-support/spine/Skeleton.cpp b/cocos/editor-support/spine/Skeleton.cpp new file mode 100644 index 0000000..b2021b9 --- /dev/null +++ b/cocos/editor-support/spine/Skeleton.cpp @@ -0,0 +1,681 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace spine; + +Skeleton::Skeleton(SkeletonData *skeletonData) : _data(skeletonData), + _skin(NULL), + _color(1, 1, 1, 1), + _time(0), + _scaleX(1), + _scaleY(1), + _x(0), + _y(0) { + _bones.ensureCapacity(_data->getBones().size()); + for (size_t i = 0; i < _data->getBones().size(); ++i) { + BoneData *data = _data->getBones()[i]; + + Bone *bone; + if (data->getParent() == NULL) { + bone = new (__FILE__, __LINE__) Bone(*data, *this, NULL); + } else { + Bone *parent = _bones[data->getParent()->getIndex()]; + bone = new (__FILE__, __LINE__) Bone(*data, *this, parent); + parent->getChildren().add(bone); + } + + _bones.add(bone); + } + + _slots.ensureCapacity(_data->getSlots().size()); + _drawOrder.ensureCapacity(_data->getSlots().size()); + for (size_t i = 0; i < _data->getSlots().size(); ++i) { + SlotData *data = _data->getSlots()[i]; + + Bone *bone = _bones[data->getBoneData().getIndex()]; + Slot *slot = new (__FILE__, __LINE__) Slot(*data, *bone); + + _slots.add(slot); + _drawOrder.add(slot); + } + + _ikConstraints.ensureCapacity(_data->getIkConstraints().size()); + for (size_t i = 0; i < _data->getIkConstraints().size(); ++i) { + IkConstraintData *data = _data->getIkConstraints()[i]; + + IkConstraint *constraint = new (__FILE__, __LINE__) IkConstraint(*data, *this); + + _ikConstraints.add(constraint); + } + + _transformConstraints.ensureCapacity(_data->getTransformConstraints().size()); + for (size_t i = 0; i < _data->getTransformConstraints().size(); ++i) { + TransformConstraintData *data = _data->getTransformConstraints()[i]; + + TransformConstraint *constraint = new (__FILE__, __LINE__) TransformConstraint(*data, *this); + + _transformConstraints.add(constraint); + } + + _pathConstraints.ensureCapacity(_data->getPathConstraints().size()); + for (size_t i = 0; i < _data->getPathConstraints().size(); ++i) { + PathConstraintData *data = _data->getPathConstraints()[i]; + + PathConstraint *constraint = new (__FILE__, __LINE__) PathConstraint(*data, *this); + + _pathConstraints.add(constraint); + } + + updateCache(); +} + +Skeleton::~Skeleton() { + ContainerUtil::cleanUpVectorOfPointers(_bones); + ContainerUtil::cleanUpVectorOfPointers(_slots); + ContainerUtil::cleanUpVectorOfPointers(_ikConstraints); + ContainerUtil::cleanUpVectorOfPointers(_transformConstraints); + ContainerUtil::cleanUpVectorOfPointers(_pathConstraints); +} + +void Skeleton::updateCache() { + _updateCache.clear(); + _updateCacheReset.clear(); + + for (size_t i = 0, n = _bones.size(); i < n; ++i) { + Bone *bone = _bones[i]; + bone->_sorted = bone->_data.isSkinRequired(); + bone->_active = !bone->_sorted; + } + + if (_skin) { + Vector &skinBones = _skin->getBones(); + for (size_t i = 0, n = skinBones.size(); i < n; i++) { + Bone *bone = _bones[skinBones[i]->getIndex()]; + do { + bone->_sorted = false; + bone->_active = true; + bone = bone->_parent; + } while (bone); + } + } + + size_t ikCount = _ikConstraints.size(); + size_t transformCount = _transformConstraints.size(); + size_t pathCount = _pathConstraints.size(); + + size_t constraintCount = ikCount + transformCount + pathCount; + + size_t i = 0; +continue_outer: + for (; i < constraintCount; ++i) { + for (size_t ii = 0; ii < ikCount; ++ii) { + IkConstraint *constraint = _ikConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortIkConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < transformCount; ++ii) { + TransformConstraint *constraint = _transformConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortTransformConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < pathCount; ++ii) { + PathConstraint *constraint = _pathConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortPathConstraint(constraint); + i++; + goto continue_outer; + } + } + } + + size_t n = _bones.size(); + for (i = 0; i < n; ++i) { + sortBone(_bones[i]); + } +} + +void Skeleton::printUpdateCache() { + for (size_t i = 0; i < _updateCache.size(); i++) { + Updatable *updatable = _updateCache[i]; + if (updatable->getRTTI().isExactly(Bone::rtti)) { + printf("bone %s\n", ((Bone *)updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(TransformConstraint::rtti)) { + printf("transform constraint %s\n", ((TransformConstraint *)updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(IkConstraint::rtti)) { + printf("ik constraint %s\n", ((IkConstraint *)updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(PathConstraint::rtti)) { + printf("path constraint %s\n", ((PathConstraint *)updatable)->getData().getName().buffer()); + } + } +} + +void Skeleton::updateWorldTransform() { + for (size_t i = 0, n = _updateCacheReset.size(); i < n; ++i) { + Bone *boneP = _updateCacheReset[i]; + Bone &bone = *boneP; + bone._ax = bone._x; + bone._ay = bone._y; + bone._arotation = bone._rotation; + bone._ascaleX = bone._scaleX; + bone._ascaleY = bone._scaleY; + bone._ashearX = bone._shearX; + bone._ashearY = bone._shearY; + bone._appliedValid = true; + } + + for (size_t i = 0, n = _updateCache.size(); i < n; ++i) { + _updateCache[i]->update(); + } +} + +void Skeleton::setToSetupPose() { + setBonesToSetupPose(); + setSlotsToSetupPose(); +} + +void Skeleton::setBonesToSetupPose() { + for (size_t i = 0, n = _bones.size(); i < n; ++i) { + _bones[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _ikConstraints.size(); i < n; ++i) { + IkConstraint *constraintP = _ikConstraints[i]; + IkConstraint &constraint = *constraintP; + + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + constraint._mix = constraint._data._mix; + constraint._softness = constraint._data._softness; + } + + for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) { + TransformConstraint *constraintP = _transformConstraints[i]; + TransformConstraint &constraint = *constraintP; + TransformConstraintData &constraintData = constraint._data; + + constraint._rotateMix = constraintData._rotateMix; + constraint._translateMix = constraintData._translateMix; + constraint._scaleMix = constraintData._scaleMix; + constraint._shearMix = constraintData._shearMix; + } + + for (size_t i = 0, n = _pathConstraints.size(); i < n; ++i) { + PathConstraint *constraintP = _pathConstraints[i]; + PathConstraint &constraint = *constraintP; + PathConstraintData &constraintData = constraint._data; + + constraint._position = constraintData._position; + constraint._spacing = constraintData._spacing; + constraint._rotateMix = constraintData._rotateMix; + constraint._translateMix = constraintData._translateMix; + } +} + +void Skeleton::setSlotsToSetupPose() { + _drawOrder.clear(); + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + _drawOrder.add(_slots[i]); + } + + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + _slots[i]->setToSetupPose(); + } +} + +Bone *Skeleton::findBone(const String &boneName) { + return ContainerUtil::findWithDataName(_bones, boneName); +} + +int Skeleton::findBoneIndex(const String &boneName) { + return ContainerUtil::findIndexWithDataName(_bones, boneName); +} + +Slot *Skeleton::findSlot(const String &slotName) { + return ContainerUtil::findWithDataName(_slots, slotName); +} + +int Skeleton::findSlotIndex(const String &slotName) { + return ContainerUtil::findIndexWithDataName(_slots, slotName); +} + +void Skeleton::setSkin(const String &skinName) { + Skin *foundSkin = _data->findSkin(skinName); + + assert(foundSkin != NULL); + + setSkin(foundSkin); +} + +void Skeleton::setSkin(Skin *newSkin) { + if (_skin == newSkin) return; + if (newSkin != NULL) { + if (_skin != NULL) { + Skeleton &thisRef = *this; + newSkin->attachAll(thisRef, *_skin); + } else { + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + Slot *slotP = _slots[i]; + Slot &slot = *slotP; + const String &name = slot._data.getAttachmentName(); + if (name.length() > 0) { + Attachment *attachment = newSkin->getAttachment(i, name); + if (attachment != NULL) { + slot.setAttachment(attachment); + } + } + } + } + } + + _skin = newSkin; + updateCache(); +} + +Attachment *Skeleton::getAttachment(const String &slotName, const String &attachmentName) { + return getAttachment(_data->findSlotIndex(slotName), attachmentName); +} + +Attachment *Skeleton::getAttachment(int slotIndex, const String &attachmentName) { + assert(attachmentName.length() > 0); + + if (_skin != NULL) { + Attachment *attachment = _skin->getAttachment(slotIndex, attachmentName); + if (attachment != NULL) { + return attachment; + } + } + + return _data->getDefaultSkin() != NULL ? _data->getDefaultSkin()->getAttachment(slotIndex, attachmentName) : NULL; +} + +void Skeleton::setAttachment(const String &slotName, const String &attachmentName) { + assert(slotName.length() > 0); + + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + Slot *slot = _slots[i]; + if (slot->_data.getName() == slotName) { + Attachment *attachment = NULL; + if (attachmentName.length() > 0) { + attachment = getAttachment(i, attachmentName); + + assert(attachment != NULL); + } + + slot->setAttachment(attachment); + + return; + } + } + + printf("Slot not found: %s", slotName.buffer()); + + assert(false); +} + +IkConstraint *Skeleton::findIkConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _ikConstraints.size(); i < n; ++i) { + IkConstraint *ikConstraint = _ikConstraints[i]; + if (ikConstraint->_data.getName() == constraintName) { + return ikConstraint; + } + } + return NULL; +} + +TransformConstraint *Skeleton::findTransformConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) { + TransformConstraint *transformConstraint = _transformConstraints[i]; + if (transformConstraint->_data.getName() == constraintName) { + return transformConstraint; + } + } + + return NULL; +} + +PathConstraint *Skeleton::findPathConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _pathConstraints.size(); i < n; ++i) { + PathConstraint *constraint = _pathConstraints[i]; + if (constraint->_data.getName() == constraintName) { + return constraint; + } + } + + return NULL; +} + +void Skeleton::update(float delta) { + _time += delta; +} + +void Skeleton::getBounds(float &outX, float &outY, float &outWidth, float &outHeight, Vector &outVertexBuffer) { + float minX = FLT_MAX; + float minY = FLT_MAX; + float maxX = FLT_MIN; + float maxY = FLT_MIN; + + for (size_t i = 0; i < _drawOrder.size(); ++i) { + Slot *slot = _drawOrder[i]; + if (!slot->_bone._active) continue; + size_t verticesLength = 0; + Attachment *attachment = slot->getAttachment(); + + if (attachment != NULL && attachment->getRTTI().instanceOf(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = static_cast(attachment); + + verticesLength = 8; + if (outVertexBuffer.size() < 8) { + outVertexBuffer.setSize(8, 0); + } + regionAttachment->computeWorldVertices(slot->getBone(), outVertexBuffer, 0); + } else if (attachment != NULL && attachment->getRTTI().instanceOf(MeshAttachment::rtti)) { + MeshAttachment *mesh = static_cast(attachment); + + verticesLength = mesh->getWorldVerticesLength(); + if (outVertexBuffer.size() < verticesLength) { + outVertexBuffer.setSize(verticesLength, 0); + } + + mesh->computeWorldVertices(*slot, 0, verticesLength, outVertexBuffer, 0); + } + + for (size_t ii = 0; ii < verticesLength; ii += 2) { + float vx = outVertexBuffer[ii]; + float vy = outVertexBuffer[ii + 1]; + + minX = MathUtil::min(minX, vx); + minY = MathUtil::min(minY, vy); + maxX = MathUtil::max(maxX, vx); + maxY = MathUtil::max(maxY, vy); + } + } + + outX = minX; + outY = minY; + outWidth = maxX - minX; + outHeight = maxY - minY; +} + +Bone *Skeleton::getRootBone() { + return _bones.size() == 0 ? NULL : _bones[0]; +} + +SkeletonData *Skeleton::getData() { + return _data; +} + +Vector &Skeleton::getBones() { + return _bones; +} + +Vector &Skeleton::getUpdateCacheList() { + return _updateCache; +} + +Vector &Skeleton::getSlots() { + return _slots; +} + +Vector &Skeleton::getDrawOrder() { + return _drawOrder; +} + +Vector &Skeleton::getIkConstraints() { + return _ikConstraints; +} + +Vector &Skeleton::getPathConstraints() { + return _pathConstraints; +} + +Vector &Skeleton::getTransformConstraints() { + return _transformConstraints; +} + +Skin *Skeleton::getSkin() { + return _skin; +} + +Color &Skeleton::getColor() { + return _color; +} + +float Skeleton::getTime() { + return _time; +} + +void Skeleton::setTime(float inValue) { + _time = inValue; +} + +void Skeleton::setPosition(float x, float y) { + _x = x; + _y = y; +} + +float Skeleton::getX() { + return _x; +} + +void Skeleton::setX(float inValue) { + _x = inValue; +} + +float Skeleton::getY() { + return _y; +} + +void Skeleton::setY(float inValue) { + _y = inValue; +} + +float Skeleton::getScaleX() { + return _scaleX; +} + +void Skeleton::setScaleX(float inValue) { + _scaleX = inValue; +} + +float Skeleton::getScaleY() { + return _scaleY * (Bone::isYDown() ? -1 : 1); +} + +void Skeleton::setScaleY(float inValue) { + _scaleY = inValue; +} + +void Skeleton::sortIkConstraint(IkConstraint *constraint) { + constraint->_active = constraint->_target->_active && (!constraint->_data.isSkinRequired() || (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) return; + + Bone *target = constraint->getTarget(); + sortBone(target); + + Vector &constrained = constraint->getBones(); + Bone *parent = constrained[0]; + sortBone(parent); + + if (constrained.size() > 1) { + Bone *child = constrained[constrained.size() - 1]; + if (!_updateCache.contains(child)) _updateCacheReset.add(child); + } + + _updateCache.add(constraint); + + sortReset(parent->getChildren()); + constrained[constrained.size() - 1]->_sorted = true; +} + +void Skeleton::sortPathConstraint(PathConstraint *constraint) { + constraint->_active = constraint->_target->_bone._active && (!constraint->_data.isSkinRequired() || (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) return; + + Slot *slot = constraint->getTarget(); + int slotIndex = slot->getData().getIndex(); + Bone &slotBone = slot->getBone(); + if (_skin != NULL) sortPathConstraintAttachment(_skin, slotIndex, slotBone); + if (_data->_defaultSkin != NULL && _data->_defaultSkin != _skin) + sortPathConstraintAttachment(_data->_defaultSkin, slotIndex, slotBone); + for (size_t ii = 0, nn = _data->_skins.size(); ii < nn; ii++) + sortPathConstraintAttachment(_data->_skins[ii], slotIndex, slotBone); + + Attachment *attachment = slot->getAttachment(); + if (attachment != NULL && attachment->getRTTI().instanceOf(PathAttachment::rtti)) + sortPathConstraintAttachment(attachment, slotBone); + + Vector &constrained = constraint->getBones(); + size_t boneCount = constrained.size(); + for (size_t i = 0; i < boneCount; ++i) { + sortBone(constrained[i]); + } + + _updateCache.add(constraint); + + for (size_t i = 0; i < boneCount; i++) + sortReset(constrained[i]->getChildren()); + for (size_t i = 0; i < boneCount; i++) + constrained[i]->_sorted = true; +} + +void Skeleton::sortTransformConstraint(TransformConstraint *constraint) { + constraint->_active = constraint->_target->_active && (!constraint->_data.isSkinRequired() || (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) return; + + sortBone(constraint->getTarget()); + + Vector &constrained = constraint->getBones(); + size_t boneCount = constrained.size(); + if (constraint->_data.isLocal()) { + for (size_t i = 0; i < boneCount; i++) { + Bone *child = constrained[i]; + sortBone(child->getParent()); + if (!_updateCache.contains(child)) _updateCacheReset.add(child); + } + } else { + for (size_t i = 0; i < boneCount; ++i) { + sortBone(constrained[i]); + } + } + + _updateCache.add(constraint); + + for (size_t i = 0; i < boneCount; ++i) + sortReset(constrained[i]->getChildren()); + for (size_t i = 0; i < boneCount; ++i) + constrained[i]->_sorted = true; +} + +void Skeleton::sortPathConstraintAttachment(Skin *skin, size_t slotIndex, Bone &slotBone) { + Skin::AttachmentMap::Entries attachments = skin->getAttachments(); + + while (attachments.hasNext()) { + Skin::AttachmentMap::Entry entry = attachments.next(); + if (entry._slotIndex == slotIndex) { + Attachment *value = entry._attachment; + sortPathConstraintAttachment(value, slotBone); + } + } +} + +void Skeleton::sortPathConstraintAttachment(Attachment *attachment, Bone &slotBone) { + if (attachment == NULL || !attachment->getRTTI().instanceOf(PathAttachment::rtti)) return; + Vector &pathBones = static_cast(attachment)->getBones(); + if (pathBones.size() == 0) + sortBone(&slotBone); + else { + for (size_t i = 0, n = pathBones.size(); i < n;) { + size_t nn = pathBones[i++]; + nn += i; + while (i < nn) { + sortBone(_bones[pathBones[i++]]); + } + } + } +} + +void Skeleton::sortBone(Bone *bone) { + if (bone->_sorted) return; + Bone *parent = bone->_parent; + if (parent != NULL) sortBone(parent); + bone->_sorted = true; + _updateCache.add(bone); +} + +void Skeleton::sortReset(Vector &bones) { + for (size_t i = 0, n = bones.size(); i < n; ++i) { + Bone *bone = bones[i]; + if (!bone->_active) continue; + if (bone->_sorted) sortReset(bone->getChildren()); + bone->_sorted = false; + } +} diff --git a/cocos/editor-support/spine/Skeleton.h b/cocos/editor-support/spine/Skeleton.h new file mode 100644 index 0000000..b75756b --- /dev/null +++ b/cocos/editor-support/spine/Skeleton.h @@ -0,0 +1,243 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Skeleton_h +#define Spine_Skeleton_h + +#include +#include +#include +#include +#include + +namespace spine { +class SkeletonData; + +class Bone; + +class Updatable; + +class Slot; + +class IkConstraint; + +class PathConstraint; + +class TransformConstraint; + +class Skin; + +class Attachment; + +class SP_API Skeleton : public SpineObject { + friend class AnimationState; + + friend class SkeletonBounds; + + friend class SkeletonClipping; + + friend class AttachmentTimeline; + + friend class ColorTimeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TwoColorTimeline; + +public: + explicit Skeleton(SkeletonData *skeletonData); + + ~Skeleton(); + + /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added + /// or removed. + void updateCache(); + + void printUpdateCache(); + + /// Updates the world transform for each bone and applies constraints. + void updateWorldTransform(); + + /// Sets the bones, constraints, and slots to their setup pose values. + void setToSetupPose(); + + /// Sets the bones and constraints to their setup pose values. + void setBonesToSetupPose(); + + void setSlotsToSetupPose(); + + /// @return May be NULL. + Bone *findBone(const String &boneName); + + /// @return -1 if the bone was not found. + int findBoneIndex(const String &boneName); + + /// @return May be NULL. + Slot *findSlot(const String &slotName); + + /// @return -1 if the bone was not found. + int findSlotIndex(const String &slotName); + + /// Sets a skin by name (see setSkin). + void setSkin(const String &skinName); + + /// Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. + /// If there was no old skin, each slot's setup mode attachment is attached from the new skin. + /// After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling + /// See Skeleton::setSlotsToSetupPose() + /// Also, often AnimationState::apply(Skeleton&) is called before the next time the + /// skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin. + /// @param newSkin May be NULL. + void setSkin(Skin *newSkin); + + /// @return May be NULL. + Attachment *getAttachment(const String &slotName, const String &attachmentName); + + /// @return May be NULL. + Attachment *getAttachment(int slotIndex, const String &attachmentName); + + /// @param attachmentName May be empty. + void setAttachment(const String &slotName, const String &attachmentName); + + /// @return May be NULL. + IkConstraint *findIkConstraint(const String &constraintName); + + /// @return May be NULL. + TransformConstraint *findTransformConstraint(const String &constraintName); + + /// @return May be NULL. + PathConstraint *findPathConstraint(const String &constraintName); + + void update(float delta); + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// @param outX The horizontal distance between the skeleton origin and the left side of the AABB. + /// @param outY The vertical distance between the skeleton origin and the bottom side of the AABB. + /// @param outWidth The width of the AABB + /// @param outHeight The height of the AABB. + /// @param outVertexBuffer Reference to hold a Vector of floats. This method will assign it with new floats as needed. + void getBounds(float &outX, float &outY, float &outWidth, float &outHeight, Vector &outVertexBuffer); + + Bone *getRootBone(); + + SkeletonData *getData(); + + Vector &getBones(); + + Vector &getUpdateCacheList(); + + Vector &getSlots(); + + Vector &getDrawOrder(); + + Vector &getIkConstraints(); + + Vector &getPathConstraints(); + + Vector &getTransformConstraints(); + + Skin *getSkin(); + + Color &getColor(); + + float getTime(); + + void setTime(float inValue); + + void setPosition(float x, float y); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getScaleX(); + + void setScaleX(float inValue); + + float getScaleY(); + + void setScaleY(float inValue); + +private: + SkeletonData *_data; + Vector _bones; + Vector _slots; + Vector _drawOrder; + Vector _ikConstraints; + Vector _transformConstraints; + Vector _pathConstraints; + Vector _updateCache; + Vector _updateCacheReset; + Skin *_skin; + Color _color; + float _time; + float _scaleX, _scaleY; + float _x, _y; + + void sortIkConstraint(IkConstraint *constraint); + + void sortPathConstraint(PathConstraint *constraint); + + void sortTransformConstraint(TransformConstraint *constraint); + + void sortPathConstraintAttachment(Skin *skin, size_t slotIndex, Bone &slotBone); + + void sortPathConstraintAttachment(Attachment *attachment, Bone &slotBone); + + void sortBone(Bone *bone); + + static void sortReset(Vector &bones); +}; +} // namespace spine + +#endif /* Spine_Skeleton_h */ diff --git a/cocos/editor-support/spine/SkeletonBinary.cpp b/cocos/editor-support/spine/SkeletonBinary.cpp new file mode 100644 index 0000000..c3742e2 --- /dev/null +++ b/cocos/editor-support/spine/SkeletonBinary.cpp @@ -0,0 +1,1059 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +const int SkeletonBinary::BONE_ROTATE = 0; +const int SkeletonBinary::BONE_TRANSLATE = 1; +const int SkeletonBinary::BONE_SCALE = 2; +const int SkeletonBinary::BONE_SHEAR = 3; + +const int SkeletonBinary::SLOT_ATTACHMENT = 0; +const int SkeletonBinary::SLOT_COLOR = 1; +const int SkeletonBinary::SLOT_TWO_COLOR = 2; + +const int SkeletonBinary::PATH_POSITION = 0; +const int SkeletonBinary::PATH_SPACING = 1; +const int SkeletonBinary::PATH_MIX = 2; + +const int SkeletonBinary::CURVE_LINEAR = 0; +const int SkeletonBinary::CURVE_STEPPED = 1; +const int SkeletonBinary::CURVE_BEZIER = 2; + +SkeletonBinary::SkeletonBinary(Atlas *atlasArray) : _attachmentLoader( + new (__FILE__, __LINE__) AtlasAttachmentLoader(atlasArray)), + _error(), + _scale(1), + _ownsLoader(true) { +} + +SkeletonBinary::SkeletonBinary(AttachmentLoader *attachmentLoader) : _attachmentLoader(attachmentLoader), _error(), _scale(1), _ownsLoader(false) { + assert(_attachmentLoader != NULL); +} + +SkeletonBinary::~SkeletonBinary() { + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + if (_ownsLoader) delete _attachmentLoader; +} + +SkeletonData *SkeletonBinary::readSkeletonData(const unsigned char *binary, const int length) { + bool nonessential; + SkeletonData *skeletonData; + + DataInput *input = new (__FILE__, __LINE__) DataInput(); + input->cursor = binary; + input->end = binary + length; + + _linkedMeshes.clear(); + + skeletonData = new (__FILE__, __LINE__) SkeletonData(); + + char *skeletonData_hash = readString(input); + skeletonData->_hash.own(skeletonData_hash); + + char *skeletonData_version = readString(input); + skeletonData->_version.own(skeletonData_version); + if ("3.8.75" == skeletonData->_version) { + delete input; + delete skeletonData; + setError("Unsupported skeleton data, please export with a newer version of Spine.", ""); + return NULL; + } + + skeletonData->_x = readFloat(input); + skeletonData->_y = readFloat(input); + skeletonData->_width = readFloat(input); + skeletonData->_height = readFloat(input); + + nonessential = readBoolean(input); + + if (nonessential) { + /* Skip images path, audio path & fps */ + skeletonData->_fps = readFloat(input); + skeletonData->_imagesPath.own(readString(input)); + skeletonData->_audioPath.own(readString(input)); + } + + int numStrings = readVarint(input, true); + for (int i = 0; i < numStrings; i++) + skeletonData->_strings.add(readString(input)); + + /* Bones. */ + int numBones = readVarint(input, true); + skeletonData->_bones.setSize(numBones, 0); + for (int i = 0; i < numBones; ++i) { + const char *name = readString(input); + BoneData *parent = i == 0 ? 0 : skeletonData->_bones[readVarint(input, true)]; + BoneData *data = new (__FILE__, __LINE__) BoneData(i, String(name, true), parent); + data->_rotation = readFloat(input); + data->_x = readFloat(input) * _scale; + data->_y = readFloat(input) * _scale; + data->_scaleX = readFloat(input); + data->_scaleY = readFloat(input); + data->_shearX = readFloat(input); + data->_shearY = readFloat(input); + data->_length = readFloat(input) * _scale; + data->_transformMode = static_cast(readVarint(input, true)); + data->_skinRequired = readBoolean(input); + if (nonessential) readInt(input); /* Skip bone color. */ + skeletonData->_bones[i] = data; + } + + /* Slots. */ + int slotsCount = readVarint(input, true); + skeletonData->_slots.setSize(slotsCount, 0); + for (int i = 0; i < slotsCount; ++i) { + const char *slotName = readString(input); + BoneData *boneData = skeletonData->_bones[readVarint(input, true)]; + SlotData *slotData = new (__FILE__, __LINE__) SlotData(i, String(slotName, true), *boneData); + + readColor(input, slotData->getColor()); + unsigned char r = readByte(input); + unsigned char g = readByte(input); + unsigned char b = readByte(input); + unsigned char a = readByte(input); + if (!(r == 0xff && g == 0xff && b == 0xff && a == 0xff)) { + slotData->getDarkColor().set(r / 255.0f, g / 255.0f, b / 255.0f, 1); + slotData->setHasDarkColor(true); + } + slotData->_attachmentName = readStringRef(input, skeletonData); + slotData->_blendMode = static_cast(readVarint(input, true)); + skeletonData->_slots[i] = slotData; + } + + /* IK constraints. */ + int ikConstraintsCount = readVarint(input, true); + skeletonData->_ikConstraints.setSize(ikConstraintsCount, 0); + for (int i = 0; i < ikConstraintsCount; ++i) { + const char *name = readString(input); + IkConstraintData *data = new (__FILE__, __LINE__) IkConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + data->setSkinRequired(readBoolean(input)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_bones[readVarint(input, true)]; + data->_mix = readFloat(input); + data->_softness = readFloat(input) * _scale; + data->_bendDirection = readSByte(input); + data->_compress = readBoolean(input); + data->_stretch = readBoolean(input); + data->_uniform = readBoolean(input); + skeletonData->_ikConstraints[i] = data; + } + + /* Transform constraints. */ + int transformConstraintsCount = readVarint(input, true); + skeletonData->_transformConstraints.setSize(transformConstraintsCount, 0); + for (int i = 0; i < transformConstraintsCount; ++i) { + const char *name = readString(input); + TransformConstraintData *data = new (__FILE__, __LINE__) TransformConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + data->setSkinRequired(readBoolean(input)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_bones[readVarint(input, true)]; + data->_local = readBoolean(input); + data->_relative = readBoolean(input); + data->_offsetRotation = readFloat(input); + data->_offsetX = readFloat(input) * _scale; + data->_offsetY = readFloat(input) * _scale; + data->_offsetScaleX = readFloat(input); + data->_offsetScaleY = readFloat(input); + data->_offsetShearY = readFloat(input); + data->_rotateMix = readFloat(input); + data->_translateMix = readFloat(input); + data->_scaleMix = readFloat(input); + data->_shearMix = readFloat(input); + skeletonData->_transformConstraints[i] = data; + } + + /* Path constraints */ + int pathConstraintsCount = readVarint(input, true); + skeletonData->_pathConstraints.setSize(pathConstraintsCount, 0); + for (int i = 0; i < pathConstraintsCount; ++i) { + const char *name = readString(input); + PathConstraintData *data = new (__FILE__, __LINE__) PathConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + data->setSkinRequired(readBoolean(input)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_slots[readVarint(input, true)]; + data->_positionMode = static_cast(readVarint(input, true)); + data->_spacingMode = static_cast(readVarint(input, true)); + data->_rotateMode = static_cast(readVarint(input, true)); + data->_offsetRotation = readFloat(input); + data->_position = readFloat(input); + if (data->_positionMode == PositionMode_Fixed) data->_position *= _scale; + data->_spacing = readFloat(input); + if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) + data->_spacing *= _scale; + data->_rotateMix = readFloat(input); + data->_translateMix = readFloat(input); + skeletonData->_pathConstraints[i] = data; + } + + /* Default skin. */ + Skin *defaultSkin = readSkin(input, true, skeletonData, nonessential); + if (defaultSkin) { + skeletonData->_defaultSkin = defaultSkin; + skeletonData->_skins.add(defaultSkin); + } + + /* Skins. */ + for (size_t i = 0, n = (size_t)readVarint(input, true); i < n; ++i) + skeletonData->_skins.add(readSkin(input, false, skeletonData, nonessential)); + + /* Linked meshes. */ + for (int i = 0, n = _linkedMeshes.size(); i < n; ++i) { + LinkedMesh *linkedMesh = _linkedMeshes[i]; + Skin *skin = linkedMesh->_skin.length() == 0 ? skeletonData->getDefaultSkin() : skeletonData->findSkin(linkedMesh->_skin); + if (skin == NULL) { + delete input; + delete skeletonData; + setError("Skin not found: ", linkedMesh->_skin.buffer()); + return NULL; + } + Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent); + if (parent == NULL) { + delete input; + delete skeletonData; + setError("Parent mesh not found: ", linkedMesh->_parent.buffer()); + return NULL; + } + linkedMesh->_mesh->_deformAttachment = linkedMesh->_inheritDeform ? static_cast(parent) : linkedMesh->_mesh; + linkedMesh->_mesh->setParentMesh(static_cast(parent)); + linkedMesh->_mesh->updateUVs(); + _attachmentLoader->configureAttachment(linkedMesh->_mesh); + } + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + /* Events. */ + int eventsCount = readVarint(input, true); + skeletonData->_events.setSize(eventsCount, 0); + for (int i = 0; i < eventsCount; ++i) { + const char *name = readStringRef(input, skeletonData); + EventData *eventData = new (__FILE__, __LINE__) EventData(String(name)); + eventData->_intValue = readVarint(input, false); + eventData->_floatValue = readFloat(input); + eventData->_stringValue.own(readString(input)); + eventData->_audioPath.own(readString(input)); // skip audio path + if (!eventData->_audioPath.isEmpty()) { + eventData->_volume = readFloat(input); + eventData->_balance = readFloat(input); + } + skeletonData->_events[i] = eventData; + } + + /* Animations. */ + int animationsCount = readVarint(input, true); + skeletonData->_animations.setSize(animationsCount, 0); + for (int i = 0; i < animationsCount; ++i) { + String name(readString(input), true); + Animation *animation = readAnimation(name, input, skeletonData); + if (!animation) { + delete input; + delete skeletonData; + return NULL; + } + skeletonData->_animations[i] = animation; + } + + delete input; + return skeletonData; +} + +SkeletonData *SkeletonBinary::readSkeletonDataFile(const String &path) { + int length; + SkeletonData *skeletonData; + const char *binary = SpineExtension::readFile(path.buffer(), &length); + if (length == 0 || !binary) { + setError("Unable to read skeleton file: ", path.buffer()); + return NULL; + } + skeletonData = readSkeletonData((unsigned char *)binary, length); + SpineExtension::free(binary, __FILE__, __LINE__); + return skeletonData; +} + +void SkeletonBinary::setError(const char *value1, const char *value2) { + char message[256]; + int length; + strcpy(message, value1); + length = (int)strlen(value1); + if (value2) strncat(message + length, value2, 255 - length); + _error = String(message); +} + +char *SkeletonBinary::readString(DataInput *input) { + int length = readVarint(input, true); + char *string; + if (length == 0) return NULL; + string = SpineExtension::alloc(length, __FILE__, __LINE__); + memcpy(string, input->cursor, length - 1); + input->cursor += length - 1; + string[length - 1] = '\0'; + return string; +} + +char *SkeletonBinary::readStringRef(DataInput *input, SkeletonData *skeletonData) { + int index = readVarint(input, true); + return index == 0 ? nullptr : skeletonData->_strings[index - 1]; +} + +float SkeletonBinary::readFloat(DataInput *input) { + union { + int intValue; + float floatValue; + } intToFloat; + intToFloat.intValue = readInt(input); + return intToFloat.floatValue; +} + +unsigned char SkeletonBinary::readByte(DataInput *input) { + return *input->cursor++; +} + +signed char SkeletonBinary::readSByte(DataInput *input) { + return (signed char)readByte(input); +} + +bool SkeletonBinary::readBoolean(DataInput *input) { + return readByte(input) != 0; +} + +int SkeletonBinary::readInt(DataInput *input) { + int result = readByte(input); + result <<= 8; + result |= readByte(input); + result <<= 8; + result |= readByte(input); + result <<= 8; + result |= readByte(input); + return result; +} + +void SkeletonBinary::readColor(DataInput *input, Color &color) { + color.r = readByte(input) / 255.0f; + color.g = readByte(input) / 255.0f; + color.b = readByte(input) / 255.0f; + color.a = readByte(input) / 255.0f; +} + +int SkeletonBinary::readVarint(DataInput *input, bool optimizePositive) { + unsigned char b = readByte(input); + int value = b & 0x7F; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 7; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 14; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 21; + if (b & 0x80) value |= (readByte(input) & 0x7F) << 28; + } + } + } + if (!optimizePositive) value = (((unsigned int)value >> 1) ^ -(value & 1)); + return value; +} + +Skin *SkeletonBinary::readSkin(DataInput *input, bool defaultSkin, SkeletonData *skeletonData, bool nonessential) { + Skin *skin; + int slotCount = 0; + if (defaultSkin) { + slotCount = readVarint(input, true); + if (slotCount == 0) return NULL; + skin = new (__FILE__, __LINE__) Skin("default"); + } else { + skin = new (__FILE__, __LINE__) Skin(readStringRef(input, skeletonData)); + for (int i = 0, n = readVarint(input, true); i < n; i++) + skin->getBones().add(skeletonData->_bones[readVarint(input, true)]); + + for (int i = 0, n = readVarint(input, true); i < n; i++) + skin->getConstraints().add(skeletonData->_ikConstraints[readVarint(input, true)]); + + for (int i = 0, n = readVarint(input, true); i < n; i++) + skin->getConstraints().add(skeletonData->_transformConstraints[readVarint(input, true)]); + + for (int i = 0, n = readVarint(input, true); i < n; i++) + skin->getConstraints().add(skeletonData->_pathConstraints[readVarint(input, true)]); + slotCount = readVarint(input, true); + } + + for (int i = 0; i < slotCount; ++i) { + int slotIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + String name(readStringRef(input, skeletonData)); + Attachment *attachment = readAttachment(input, skin, slotIndex, name, skeletonData, nonessential); + if (attachment) skin->setAttachment(slotIndex, String(name), attachment); + } + } + return skin; +} + +Attachment *SkeletonBinary::readAttachment(DataInput *input, Skin *skin, int slotIndex, const String &attachmentName, + SkeletonData *skeletonData, bool nonessential) { + String name(readStringRef(input, skeletonData)); + if (name.isEmpty()) name = attachmentName; + + AttachmentType type = static_cast(readByte(input)); + switch (type) { + case AttachmentType_Region: { + String path(readStringRef(input, skeletonData)); + if (path.isEmpty()) path = name; + auto rotation = readFloat(input); + auto x = readFloat(input); + auto y = readFloat(input); + auto scaleX = readFloat(input); + auto scaleY = readFloat(input); + auto width = readFloat(input); + auto height = readFloat(input); + + static Color color; + readColor(input, color); + + RegionAttachment *region = _attachmentLoader->newRegionAttachment(*skin, String(name), String(path)); + if (region == NULL) { + return NULL; + } + region->_path = path; + region->_rotation = rotation; + region->_x = x * _scale; + region->_y = y * _scale; + region->_scaleX = scaleX; + region->_scaleY = scaleY; + region->_width = width * _scale; + region->_height = height * _scale; + region->_color = color; + region->updateOffset(); + _attachmentLoader->configureAttachment(region); + return region; + } + case AttachmentType_Boundingbox: { + int vertexCount = readVarint(input, true); + BoundingBoxAttachment *box = _attachmentLoader->newBoundingBoxAttachment(*skin, String(name)); + readVertices(input, static_cast(box), vertexCount); + if (nonessential) { + /* Skip color. */ + readInt(input); + } + _attachmentLoader->configureAttachment(box); + return box; + } + case AttachmentType_Mesh: { + bool needDelete = false; + int vertexCount; + MeshAttachment *mesh; + String path(readStringRef(input, skeletonData)); + if (path.isEmpty()) path = name; + + mesh = _attachmentLoader->newMeshAttachment(*skin, String(name), String(path)); + if (!mesh) { + mesh = new MeshAttachment(name); + needDelete = true; + } + mesh->_path = path; + readColor(input, mesh->getColor()); + vertexCount = readVarint(input, true); + readFloatArray(input, vertexCount << 1, 1, mesh->getRegionUVs()); + readShortArray(input, mesh->getTriangles()); + readVertices(input, static_cast(mesh), vertexCount); + mesh->updateUVs(); + mesh->_hullLength = readVarint(input, true) << 1; + if (nonessential) { + readShortArray(input, mesh->getEdges()); + mesh->_width = readFloat(input) * _scale; + mesh->_height = readFloat(input) * _scale; + } else { + mesh->_width = 0; + mesh->_height = 0; + } + // mesh should not be added to skin, just delete it. While it still needs to read + // all bytes to make sure next readAttachment read back right data. + if (needDelete) { + delete mesh; + mesh = nullptr; + return nullptr; + } + _attachmentLoader->configureAttachment(mesh); + return mesh; + } + case AttachmentType_Linkedmesh: { + String path(readStringRef(input, skeletonData)); + if (path.isEmpty()) path = name; + + MeshAttachment *mesh = _attachmentLoader->newMeshAttachment(*skin, String(name), String(path)); + mesh->_path = path; + readColor(input, mesh->getColor()); + String skinName(readStringRef(input, skeletonData)); + String parent(readStringRef(input, skeletonData)); + bool inheritDeform = readBoolean(input); + if (nonessential) { + mesh->_width = readFloat(input) * _scale; + mesh->_height = readFloat(input) * _scale; + } + + LinkedMesh *linkedMesh = new (__FILE__, __LINE__) LinkedMesh(mesh, String(skinName), slotIndex, + String(parent), inheritDeform); + _linkedMeshes.add(linkedMesh); + return mesh; + } + case AttachmentType_Path: { + PathAttachment *path = _attachmentLoader->newPathAttachment(*skin, String(name)); + path->_closed = readBoolean(input); + path->_constantSpeed = readBoolean(input); + int vertexCount = readVarint(input, true); + readVertices(input, static_cast(path), vertexCount); + int lengthsLength = vertexCount / 3; + path->_lengths.setSize(lengthsLength, 0); + for (int i = 0; i < lengthsLength; ++i) { + path->_lengths[i] = readFloat(input) * _scale; + } + if (nonessential) { + /* Skip color. */ + readInt(input); + } + _attachmentLoader->configureAttachment(path); + return path; + } + case AttachmentType_Point: { + PointAttachment *point = _attachmentLoader->newPointAttachment(*skin, String(name)); + point->_rotation = readFloat(input); + point->_x = readFloat(input) * _scale; + point->_y = readFloat(input) * _scale; + + if (nonessential) { + /* Skip color. */ + readInt(input); + } + _attachmentLoader->configureAttachment(point); + return point; + } + case AttachmentType_Clipping: { + int endSlotIndex = readVarint(input, true); + int vertexCount = readVarint(input, true); + ClippingAttachment *clip = _attachmentLoader->newClippingAttachment(*skin, name); + readVertices(input, static_cast(clip), vertexCount); + clip->_endSlot = skeletonData->_slots[endSlotIndex]; + if (nonessential) { + /* Skip color. */ + readInt(input); + } + _attachmentLoader->configureAttachment(clip); + return clip; + } + } + return NULL; +} + +void SkeletonBinary::readVertices(DataInput *input, VertexAttachment *attachment, int vertexCount) { + float scale = _scale; + int verticesLength = vertexCount << 1; + attachment->setWorldVerticesLength(vertexCount << 1); + + if (!readBoolean(input)) { + readFloatArray(input, verticesLength, scale, attachment->getVertices()); + return; + } + + Vector &vertices = attachment->getVertices(); + Vector &bones = attachment->getBones(); + vertices.ensureCapacity(verticesLength * 3 * 3); + bones.ensureCapacity(verticesLength * 3); + + for (int i = 0; i < vertexCount; ++i) { + int boneCount = readVarint(input, true); + bones.add(boneCount); + for (int ii = 0; ii < boneCount; ++ii) { + bones.add(readVarint(input, true)); + vertices.add(readFloat(input) * scale); + vertices.add(readFloat(input) * scale); + vertices.add(readFloat(input)); + } + } +} + +void SkeletonBinary::readFloatArray(DataInput *input, int n, float scale, Vector &array) { + array.setSize(n, 0); + + int i; + if (scale == 1) { + for (i = 0; i < n; ++i) { + array[i] = readFloat(input); + } + } else { + for (i = 0; i < n; ++i) { + array[i] = readFloat(input) * scale; + } + } +} + +void SkeletonBinary::readShortArray(DataInput *input, Vector &array) { + int n = readVarint(input, true); + array.setSize(n, 0); + + int i; + for (i = 0; i < n; ++i) { + array[i] = readByte(input) << 8; + array[i] |= readByte(input); + } +} + +Animation *SkeletonBinary::readAnimation(const String &name, DataInput *input, SkeletonData *skeletonData) { + Vector timelines; + float scale = _scale; + float duration = 0; + + // Slot timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int slotIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + unsigned char timelineType = readByte(input); + int frameCount = readVarint(input, true); + switch (timelineType) { + case SLOT_ATTACHMENT: { + AttachmentTimeline *timeline = new (__FILE__, __LINE__) AttachmentTimeline(frameCount); + timeline->_slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) { + float time = readFloat(input); + String attachmentName(readStringRef(input, skeletonData)); + timeline->setFrame(frameIndex, time, attachmentName); + } + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[frameCount - 1]); + break; + } + case SLOT_COLOR: { + ColorTimeline *timeline = new (__FILE__, __LINE__) ColorTimeline(frameCount); + timeline->_slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) { + float time = readFloat(input); + int color = readInt(input); + float r = ((color & 0xff000000) >> 24) / 255.0f; + float g = ((color & 0x00ff0000) >> 16) / 255.0f; + float b = ((color & 0x0000ff00) >> 8) / 255.0f; + float a = ((color & 0x000000ff)) / 255.0f; + timeline->setFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * ColorTimeline::ENTRIES]); + break; + } + case SLOT_TWO_COLOR: { + TwoColorTimeline *timeline = new (__FILE__, __LINE__) TwoColorTimeline(frameCount); + timeline->_slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) { + float time = readFloat(input); + int color = readInt(input); + float r = ((color & 0xff000000) >> 24) / 255.0f; + float g = ((color & 0x00ff0000) >> 16) / 255.0f; + float b = ((color & 0x0000ff00) >> 8) / 255.0f; + float a = ((color & 0x000000ff)) / 255.0f; + int color2 = readInt(input); // 0x00rrggbb + float r2 = ((color2 & 0x00ff0000) >> 16) / 255.0f; + float g2 = ((color2 & 0x0000ff00) >> 8) / 255.0f; + float b2 = ((color2 & 0x000000ff)) / 255.0f; + + timeline->setFrame(frameIndex, time, r, g, b, a, r2, g2, b2); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * TwoColorTimeline::ENTRIES]); + break; + } + default: { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Invalid timeline type for a slot: ", skeletonData->_slots[slotIndex]->_name.buffer()); + return NULL; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int boneIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + unsigned char timelineType = readByte(input); + int frameCount = readVarint(input, true); + switch (timelineType) { + case BONE_ROTATE: { + RotateTimeline *timeline = new (__FILE__, __LINE__) RotateTimeline(frameCount); + timeline->_boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) { + float time = readFloat(input); + float degrees = readFloat(input); + timeline->setFrame(frameIndex, time, degrees); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * RotateTimeline::ENTRIES]); + break; + } + case BONE_TRANSLATE: + case BONE_SCALE: + case BONE_SHEAR: { + TranslateTimeline *timeline; + float timelineScale = 1; + if (timelineType == BONE_SCALE) { + timeline = new (__FILE__, __LINE__) ScaleTimeline(frameCount); + } else if (timelineType == BONE_SHEAR) { + timeline = new (__FILE__, __LINE__) ShearTimeline(frameCount); + } else { + timeline = new (__FILE__, __LINE__) TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline->_boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) { + float time = readFloat(input); + float x = readFloat(input) * timelineScale; + float y = readFloat(input) * timelineScale; + timeline->setFrame(frameIndex, time, x, y); + if (frameIndex < frameCount - 1) { + readCurve(input, frameIndex, timeline); + } + } + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * TranslateTimeline::ENTRIES]); + break; + } + default: { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Invalid timeline type for a bone: ", skeletonData->_bones[boneIndex]->_name.buffer()); + return NULL; + } + } + } + } + + // IK timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + int frameCount = readVarint(input, true); + IkConstraintTimeline *timeline = new (__FILE__, __LINE__) IkConstraintTimeline(frameCount); + timeline->_ikConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) { + float time = readFloat(input); + float mix = readFloat(input); + float softness = readFloat(input) * _scale; + signed char bendDirection = readSByte(input); + bool compress = readBoolean(input); + bool stretch = readBoolean(input); + timeline->setFrame(frameIndex, time, mix, softness, bendDirection, compress, stretch); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * IkConstraintTimeline::ENTRIES]); + } + + // Transform constraint timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + int frameCount = readVarint(input, true); + TransformConstraintTimeline *timeline = new (__FILE__, __LINE__) TransformConstraintTimeline(frameCount); + timeline->_transformConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) { + float time = readFloat(input); + float rotateMix = readFloat(input); + float translateMix = readFloat(input); + float scaleMix = readFloat(input); + float shearMix = readFloat(input); + timeline->setFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * TransformConstraintTimeline::ENTRIES]); + } + + // Path constraint timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + PathConstraintData *data = skeletonData->_pathConstraints[index]; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + int timelineType = readSByte(input); + int frameCount = readVarint(input, true); + switch (timelineType) { + case PATH_POSITION: + case PATH_SPACING: { + PathConstraintPositionTimeline *timeline; + float timelineScale = 1; + if (timelineType == PATH_SPACING) { + timeline = new (__FILE__, __LINE__) PathConstraintSpacingTimeline(frameCount); + + if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) timelineScale = scale; + } else { + timeline = new (__FILE__, __LINE__) PathConstraintPositionTimeline(frameCount); + + if (data->_positionMode == PositionMode_Fixed) timelineScale = scale; + } + timeline->_pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) { + float time = readFloat(input); + float value = readFloat(input) * timelineScale; + timeline->setFrame(frameIndex, time, value); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * PathConstraintPositionTimeline::ENTRIES]); + break; + } + case PATH_MIX: { + PathConstraintMixTimeline *timeline = new (__FILE__, __LINE__) PathConstraintMixTimeline(frameCount); + + timeline->_pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) { + float time = readFloat(input); + float rotateMix = readFloat(input); + float translateMix = readFloat(input); + timeline->setFrame(frameIndex, time, rotateMix, translateMix); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[(frameCount - 1) * PathConstraintMixTimeline::ENTRIES]); + break; + } + } + } + } + + // Deform timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + Skin *skin = skeletonData->_skins[readVarint(input, true)]; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + int slotIndex = readVarint(input, true); + for (int iii = 0, nnn = readVarint(input, true); iii < nnn; iii++) { + const char *attachmentName = readStringRef(input, skeletonData); + Attachment *baseAttachment = skin->getAttachment(slotIndex, String(attachmentName)); + + if (!baseAttachment) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Attachment not found: ", attachmentName); + return NULL; + } + + VertexAttachment *attachment = static_cast(baseAttachment); + + bool weighted = attachment->_bones.size() > 0; + Vector &vertices = attachment->_vertices; + size_t deformLength = weighted ? vertices.size() / 3 * 2 : vertices.size(); + + size_t frameCount = (size_t)readVarint(input, true); + + DeformTimeline *timeline = new (__FILE__, __LINE__) DeformTimeline(frameCount); + timeline->_slotIndex = slotIndex; + timeline->_attachment = attachment; + + for (size_t frameIndex = 0; frameIndex < frameCount; ++frameIndex) { + float time = readFloat(input); + Vector deform; + size_t end = (size_t)readVarint(input, true); + if (end == 0) { + if (weighted) { + deform.setSize(deformLength, 0); + for (size_t iiii = 0; iiii < deformLength; ++iiii) + deform[iiii] = 0; + } else { + deform.clearAndAddAll(vertices); + } + } else { + deform.setSize(deformLength, 0); + size_t start = (size_t)readVarint(input, true); + end += start; + if (scale == 1) { + for (size_t v = start; v < end; ++v) + deform[v] = readFloat(input); + } else { + for (size_t v = start; v < end; ++v) + deform[v] = readFloat(input) * scale; + } + + if (!weighted) { + for (size_t v = 0, vn = deform.size(); v < vn; ++v) + deform[v] += vertices[v]; + } + } + + timeline->setFrame(frameIndex, time, deform); + if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline); + } + + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + size_t drawOrderCount = (size_t)readVarint(input, true); + if (drawOrderCount > 0) { + DrawOrderTimeline *timeline = new (__FILE__, __LINE__) DrawOrderTimeline(drawOrderCount); + + size_t slotCount = skeletonData->_slots.size(); + for (size_t i = 0; i < drawOrderCount; ++i) { + float time = readFloat(input); + size_t offsetCount = (size_t)readVarint(input, true); + + Vector drawOrder; + drawOrder.setSize(slotCount, 0); + for (int ii = (int)slotCount - 1; ii >= 0; --ii) + drawOrder[ii] = -1; + + Vector unchanged; + unchanged.setSize(slotCount - offsetCount, 0); + size_t originalIndex = 0, unchangedIndex = 0; + for (size_t ii = 0; ii < offsetCount; ++ii) { + size_t slotIndex = (size_t)readVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + size_t index = originalIndex; + drawOrder[index + (size_t)readVarint(input, true)] = originalIndex++; + } + + // Collect remaining unchanged items. + while (originalIndex < slotCount) { + unchanged[unchangedIndex++] = originalIndex++; + } + + // Fill in unchanged items. + for (int ii = (int)slotCount - 1; ii >= 0; --ii) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline->setFrame(i, time, drawOrder); + } + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = readVarint(input, true); + if (eventCount > 0) { + EventTimeline *timeline = new (__FILE__, __LINE__) EventTimeline(eventCount); + + for (int i = 0; i < eventCount; ++i) { + float time = readFloat(input); + EventData *eventData = skeletonData->_events[readVarint(input, true)]; + Event *event = new (__FILE__, __LINE__) Event(time, *eventData); + + event->_intValue = readVarint(input, false); + event->_floatValue = readFloat(input); + bool freeString = readBoolean(input); + const char *event_stringValue = freeString ? readString(input) : eventData->_stringValue.buffer(); + event->_stringValue = String(event_stringValue); + if (freeString) SpineExtension::free(event_stringValue, __FILE__, __LINE__); + + if (!eventData->_audioPath.isEmpty()) { + event->_volume = readFloat(input); + event->_balance = readFloat(input); + } + timeline->setFrame(i, event); + } + + timelines.add(timeline); + duration = MathUtil::max(duration, timeline->_frames[eventCount - 1]); + } + + return new (__FILE__, __LINE__) Animation(String(name), timelines, duration); +} + +void SkeletonBinary::readCurve(DataInput *input, int frameIndex, CurveTimeline *timeline) { + switch (readByte(input)) { + case CURVE_STEPPED: { + timeline->setStepped(frameIndex); + break; + } + case CURVE_BEZIER: { + float cx1 = readFloat(input); + float cy1 = readFloat(input); + float cx2 = readFloat(input); + float cy2 = readFloat(input); + timeline->setCurve(frameIndex, cx1, cy1, cx2, cy2); + break; + } + } +} diff --git a/cocos/editor-support/spine/SkeletonBinary.h b/cocos/editor-support/spine/SkeletonBinary.h new file mode 100644 index 0000000..bd96684 --- /dev/null +++ b/cocos/editor-support/spine/SkeletonBinary.h @@ -0,0 +1,131 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonBinary_h +#define Spine_SkeletonBinary_h + +#include +#include +#include +#include +#include + +namespace spine { +class SkeletonData; +class Atlas; +class AttachmentLoader; +class LinkedMesh; +class Skin; +class Attachment; +class VertexAttachment; +class Animation; +class CurveTimeline; + +class SP_API SkeletonBinary : public SpineObject { +public: + static const int BONE_ROTATE; + static const int BONE_TRANSLATE; + static const int BONE_SCALE; + static const int BONE_SHEAR; + + static const int SLOT_ATTACHMENT; + static const int SLOT_COLOR; + static const int SLOT_TWO_COLOR; + + static const int PATH_POSITION; + static const int PATH_SPACING; + static const int PATH_MIX; + + static const int CURVE_LINEAR; + static const int CURVE_STEPPED; + static const int CURVE_BEZIER; + + explicit SkeletonBinary(Atlas* atlasArray); + + explicit SkeletonBinary(AttachmentLoader* attachmentLoader); + + ~SkeletonBinary(); + + SkeletonData* readSkeletonData(const unsigned char* binary, int length); + + SkeletonData* readSkeletonDataFile(const String& path); + + void setScale(float scale) { _scale = scale; } + + String& getError() { return _error; } + +private: + struct DataInput : public SpineObject { + const unsigned char* cursor; + const unsigned char* end; + }; + + AttachmentLoader* _attachmentLoader; + Vector _linkedMeshes; + String _error; + float _scale; + const bool _ownsLoader; + + void setError(const char* value1, const char* value2); + + char* readString(DataInput* input); + + char* readStringRef(DataInput* input, SkeletonData* skeletonData); + + float readFloat(DataInput* input); + + unsigned char readByte(DataInput* input); + + signed char readSByte(DataInput* input); + + bool readBoolean(DataInput* input); + + int readInt(DataInput* input); + + void readColor(DataInput* input, Color& color); + + int readVarint(DataInput* input, bool optimizePositive); + + Skin* readSkin(DataInput* input, bool defaultSkin, SkeletonData* skeletonData, bool nonessential); + + Attachment* readAttachment(DataInput* input, Skin* skin, int slotIndex, const String& attachmentName, SkeletonData* skeletonData, bool nonessential); + + void readVertices(DataInput* input, VertexAttachment* attachment, int vertexCount); + + void readFloatArray(DataInput* input, int n, float scale, Vector& array); + + void readShortArray(DataInput* input, Vector& array); + + Animation* readAnimation(const String& name, DataInput* input, SkeletonData* skeletonData); + + void readCurve(DataInput* input, int frameIndex, CurveTimeline* timeline); +}; +} // namespace spine + +#endif /* Spine_SkeletonBinary_h */ diff --git a/cocos/editor-support/spine/SkeletonBounds.cpp b/cocos/editor-support/spine/SkeletonBounds.cpp new file mode 100644 index 0000000..005c788 --- /dev/null +++ b/cocos/editor-support/spine/SkeletonBounds.cpp @@ -0,0 +1,223 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include + +#include + +#include + +using namespace spine; + +SkeletonBounds::SkeletonBounds() : _minX(0), _minY(0), _maxX(0), _maxY(0) { +} + +void SkeletonBounds::update(Skeleton &skeleton, bool updateAabb) { + Vector &slots = skeleton._slots; + size_t slotCount = slots.size(); + + _boundingBoxes.clear(); + for (size_t i = 0, n = _polygons.size(); i < n; ++i) { + _polygonPool.add(_polygons[i]); + } + + _polygons.clear(); + + for (size_t i = 0; i < slotCount; i++) { + Slot *slot = slots[i]; + if (!slot->getBone().isActive()) continue; + + Attachment *attachment = slot->getAttachment(); + if (attachment == NULL || !attachment->getRTTI().instanceOf(BoundingBoxAttachment::rtti)) continue; + BoundingBoxAttachment *boundingBox = static_cast(attachment); + _boundingBoxes.add(boundingBox); + + spine::Polygon *polygonP = NULL; + size_t poolCount = _polygonPool.size(); + if (poolCount > 0) { + polygonP = _polygonPool[poolCount - 1]; + _polygonPool.removeAt(poolCount - 1); + } else + polygonP = new (__FILE__, __LINE__) Polygon(); + + _polygons.add(polygonP); + + Polygon &polygon = *polygonP; + + size_t count = boundingBox->getWorldVerticesLength(); + polygon._count = count; + if (polygon._vertices.size() < count) { + polygon._vertices.setSize(count, 0); + } + boundingBox->computeWorldVertices(*slot, polygon._vertices); + } + + if (updateAabb) + aabbCompute(); + else { + _minX = FLT_MIN; + _minY = FLT_MIN; + _maxX = FLT_MAX; + _maxY = FLT_MAX; + } +} + +bool SkeletonBounds::aabbcontainsPoint(float x, float y) { + return x >= _minX && x <= _maxX && y >= _minY && y <= _maxY; +} + +bool SkeletonBounds::aabbintersectsSegment(float x1, float y1, float x2, float y2) { + float minX = _minX; + float minY = _minY; + float maxX = _maxX; + float maxY = _maxY; + + if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || + (y1 >= maxY && y2 >= maxY)) { + return false; + } + + float m = (y2 - y1) / (x2 - x1); + float y = m * (minX - x1) + y1; + if (y > minY && y < maxY) return true; + y = m * (maxX - x1) + y1; + if (y > minY && y < maxY) return true; + float x = (minY - y1) / m + x1; + if (x > minX && x < maxX) return true; + x = (maxY - y1) / m + x1; + if (x > minX && x < maxX) return true; + return false; +} + +bool SkeletonBounds::aabbIntersectsSkeleton(SkeletonBounds bounds) { + return _minX < bounds._maxX && _maxX > bounds._minX && _minY < bounds._maxY && _maxY > bounds._minY; +} + +bool SkeletonBounds::containsPoint(spine::Polygon *polygon, float x, float y) { + Vector &vertices = polygon->_vertices; + int nn = polygon->_count; + + int prevIndex = nn - 2; + bool inside = false; + for (int ii = 0; ii < nn; ii += 2) { + float vertexY = vertices[ii + 1]; + float prevY = vertices[prevIndex + 1]; + if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) { + float vertexX = vertices[ii]; + if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) { + inside = !inside; + } + } + prevIndex = ii; + } + return inside; +} + +BoundingBoxAttachment *SkeletonBounds::containsPoint(float x, float y) { + for (size_t i = 0, n = _polygons.size(); i < n; ++i) + if (containsPoint(_polygons[i], x, y)) return _boundingBoxes[i]; + return NULL; +} + +BoundingBoxAttachment *SkeletonBounds::intersectsSegment(float x1, float y1, float x2, float y2) { + for (size_t i = 0, n = _polygons.size(); i < n; ++i) + if (intersectsSegment(_polygons[i], x1, y1, x2, y2)) return _boundingBoxes[i]; + return NULL; +} + +bool SkeletonBounds::intersectsSegment(spine::Polygon *polygon, float x1, float y1, float x2, float y2) { + Vector &vertices = polygon->_vertices; + size_t nn = polygon->_count; + + float width12 = x1 - x2, height12 = y1 - y2; + float det1 = x1 * y2 - y1 * x2; + float x3 = vertices[nn - 2], y3 = vertices[nn - 1]; + for (size_t ii = 0; ii < nn; ii += 2) { + float x4 = vertices[ii], y4 = vertices[ii + 1]; + float det2 = x3 * y4 - y3 * x4; + float width34 = x3 - x4, height34 = y3 - y4; + float det3 = width12 * height34 - height12 * width34; + float x = (det1 * width34 - width12 * det2) / det3; + if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) { + float y = (det1 * height34 - height12 * det2) / det3; + if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) { + return true; + } + } + x3 = x4; + y3 = y4; + } + + return false; +} + +spine::Polygon *SkeletonBounds::getPolygon(BoundingBoxAttachment *attachment) { + int index = _boundingBoxes.indexOf(attachment); + return index == -1 ? NULL : _polygons[index]; +} + +float SkeletonBounds::getWidth() { + return _maxX - _minX; +} + +float SkeletonBounds::getHeight() { + return _maxY - _minY; +} + +void SkeletonBounds::aabbCompute() { + float minX = FLT_MIN; + float minY = FLT_MIN; + float maxX = FLT_MAX; + float maxY = FLT_MAX; + + for (size_t i = 0, n = _polygons.size(); i < n; ++i) { + spine::Polygon *polygon = _polygons[i]; + Vector &vertices = polygon->_vertices; + for (int ii = 0, nn = polygon->_count; ii < nn; ii += 2) { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = MathUtil::min(minX, x); + minY = MathUtil::min(minY, y); + maxX = MathUtil::max(maxX, x); + maxY = MathUtil::max(maxY, y); + } + } + _minX = minX; + _minY = minY; + _maxX = maxX; + _maxY = maxY; +} diff --git a/cocos/editor-support/spine/SkeletonBounds.h b/cocos/editor-support/spine/SkeletonBounds.h new file mode 100644 index 0000000..fde3b02 --- /dev/null +++ b/cocos/editor-support/spine/SkeletonBounds.h @@ -0,0 +1,104 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonBounds_h +#define Spine_SkeletonBounds_h + +#include +#include + +namespace spine { +class Skeleton; +class BoundingBoxAttachment; +class Polygon; + +/// Collects each BoundingBoxAttachment that is visible and computes the world vertices for its polygon. +/// The polygon vertices are provided along with convenience methods for doing hit detection. +class SP_API SkeletonBounds : public SpineObject { +public: + SkeletonBounds(); + + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// @param skeleton The skeleton. + /// @param updateAabb + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + void update(Skeleton& skeleton, bool updateAabb); + + /// Returns true if the axis aligned bounding box contains the point. + bool aabbcontainsPoint(float x, float y); + + /// Returns true if the axis aligned bounding box intersects the line segment. + bool aabbintersectsSegment(float x1, float y1, float x2, float y2); + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + bool aabbIntersectsSkeleton(SkeletonBounds bounds); + + /// Returns true if the polygon contains the point. + bool containsPoint(Polygon* polygon, float x, float y); + + /// Returns the first bounding box attachment that contains the point, or NULL. When doing many checks, it is usually more + /// efficient to only call this method if {@link #aabbcontainsPoint(float, float)} returns true. + BoundingBoxAttachment* containsPoint(float x, float y); + + /// Returns the first bounding box attachment that contains the line segment, or NULL. When doing many checks, it is usually + /// more efficient to only call this method if {@link #aabbintersectsSegment(float, float, float, float)} returns true. + BoundingBoxAttachment* intersectsSegment(float x1, float y1, float x2, float y2); + + /// Returns true if the polygon contains the line segment. + bool intersectsSegment(Polygon* polygon, float x1, float y1, float x2, float y2); + + Polygon* getPolygon(BoundingBoxAttachment* attachment); + + float getWidth(); + float getHeight(); + +private: + Vector _polygonPool; + Vector _boundingBoxes; + Vector _polygons; + float _minX, _minY, _maxX, _maxY; + + void aabbCompute(); +}; + +class Polygon : public SpineObject { +public: + Vector _vertices; + int _count; + + Polygon() : _count(0) { + _vertices.ensureCapacity(16); + } +}; +} // namespace spine + +#endif /* Spine_SkeletonBounds_h */ diff --git a/cocos/editor-support/spine/SkeletonClipping.cpp b/cocos/editor-support/spine/SkeletonClipping.cpp new file mode 100644 index 0000000..370f4a0 --- /dev/null +++ b/cocos/editor-support/spine/SkeletonClipping.cpp @@ -0,0 +1,326 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +using namespace spine; + +SkeletonClipping::SkeletonClipping() : _clipAttachment(NULL) { + _clipOutput.ensureCapacity(128); + _clippedVertices.ensureCapacity(128); + _clippedTriangles.ensureCapacity(128); + _clippedUVs.ensureCapacity(128); +} + +size_t SkeletonClipping::clipStart(Slot &slot, ClippingAttachment *clip) { + if (_clipAttachment != NULL) { + return 0; + } + + _clipAttachment = clip; + + int n = clip->getWorldVerticesLength(); + _clippingPolygon.setSize(n, 0); + clip->computeWorldVertices(slot, 0, n, _clippingPolygon, 0, 2); + makeClockwise(_clippingPolygon); + _clippingPolygons = &_triangulator.decompose(_clippingPolygon, _triangulator.triangulate(_clippingPolygon)); + + for (size_t i = 0; i < _clippingPolygons->size(); ++i) { + Vector *polygonP = (*_clippingPolygons)[i]; + Vector &polygon = *polygonP; + makeClockwise(polygon); + polygon.add(polygon[0]); + polygon.add(polygon[1]); + } + + return (*_clippingPolygons).size(); +} + +void SkeletonClipping::clipEnd(Slot &slot) { + if (_clipAttachment != NULL && _clipAttachment->_endSlot == &slot._data) { + clipEnd(); + } +} + +void SkeletonClipping::clipEnd() { + if (_clipAttachment == NULL) return; + + _clipAttachment = NULL; + _clippingPolygons = NULL; + _clippedVertices.clear(); + _clippedUVs.clear(); + _clippedTriangles.clear(); + _clippingPolygon.clear(); +} + +void SkeletonClipping::clipTriangles(Vector &vertices, Vector &triangles, Vector &uvs, size_t stride) { + clipTriangles(vertices.buffer(), triangles.buffer(), triangles.size(), uvs.buffer(), stride); +} + +void SkeletonClipping::clipTriangles(float *vertices, unsigned short *triangles, + size_t trianglesLength, float *uvs, size_t stride) { + Vector &clipOutput = _clipOutput; + Vector &clippedVertices = _clippedVertices; + Vector &clippedTriangles = _clippedTriangles; + Vector *> &polygons = *_clippingPolygons; + size_t polygonsCount = (*_clippingPolygons).size(); + + size_t index = 0; + clippedVertices.clear(); + _clippedUVs.clear(); + clippedTriangles.clear(); + + size_t i = 0; +continue_outer: + for (; i < trianglesLength; i += 3) { + int vertexOffset = triangles[i] * stride; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] * stride; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] * stride; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + + for (size_t p = 0; p < polygonsCount; p++) { + size_t s = clippedVertices.size(); + if (clip(x1, y1, x2, y2, x3, y3, &(*polygons[p]), &clipOutput)) { + size_t clipOutputLength = clipOutput.size(); + if (clipOutputLength == 0) continue; + float d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1; + float d = 1 / (d0 * d2 + d1 * (y1 - y3)); + + size_t clipOutputCount = clipOutputLength >> 1; + clippedVertices.setSize(s + clipOutputCount * 2, 0); + _clippedUVs.setSize(s + clipOutputCount * 2, 0); + for (size_t ii = 0; ii < clipOutputLength; ii += 2) { + float x = clipOutput[ii], y = clipOutput[ii + 1]; + clippedVertices[s] = x; + clippedVertices[s + 1] = y; + float c0 = x - x3, c1 = y - y3; + float a = (d0 * c0 + d1 * c1) * d; + float b = (d4 * c0 + d2 * c1) * d; + float c = 1 - a - b; + _clippedUVs[s] = u1 * a + u2 * b + u3 * c; + _clippedUVs[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3 * (clipOutputCount - 2), 0); + clipOutputCount--; + for (size_t ii = 1; ii < clipOutputCount; ii++) { + clippedTriangles[s] = (unsigned short)(index); + clippedTriangles[s + 1] = (unsigned short)(index + ii); + clippedTriangles[s + 2] = (unsigned short)(index + ii + 1); + s += 3; + } + index += clipOutputCount + 1; + } else { + clippedVertices.setSize(s + 3 * 2, 0); + _clippedUVs.setSize(s + 3 * 2, 0); + clippedVertices[s] = x1; + clippedVertices[s + 1] = y1; + clippedVertices[s + 2] = x2; + clippedVertices[s + 3] = y2; + clippedVertices[s + 4] = x3; + clippedVertices[s + 5] = y3; + + _clippedUVs[s] = u1; + _clippedUVs[s + 1] = v1; + _clippedUVs[s + 2] = u2; + _clippedUVs[s + 3] = v2; + _clippedUVs[s + 4] = u3; + _clippedUVs[s + 5] = v3; + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3, 0); + clippedTriangles[s] = (unsigned short)index; + clippedTriangles[s + 1] = (unsigned short)(index + 1); + clippedTriangles[s + 2] = (unsigned short)(index + 2); + index += 3; + i += 3; + goto continue_outer; + } + } + } +} + +bool SkeletonClipping::isClipping() { + return _clipAttachment != NULL; +} + +Vector &SkeletonClipping::getClippedVertices() { + return _clippedVertices; +} + +Vector &SkeletonClipping::getClippedTriangles() { + return _clippedTriangles; +} + +Vector &SkeletonClipping::getClippedUVs() { + return _clippedUVs; +} + +bool SkeletonClipping::clip(float x1, float y1, float x2, float y2, float x3, float y3, Vector *clippingArea, + Vector *output) { + Vector *originalOutput = output; + bool clipped = false; + + // Avoid copy at the end. + Vector *input; + if (clippingArea->size() % 4 >= 2) { + input = output; + output = &_scratch; + } else + input = &_scratch; + + input->clear(); + input->add(x1); + input->add(y1); + input->add(x2); + input->add(y2); + input->add(x3); + input->add(y3); + input->add(x1); + input->add(y1); + output->clear(); + + Vector &clippingVertices = *clippingArea; + size_t clippingVerticesLast = clippingArea->size() - 4; + for (size_t i = 0;; i += 2) { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; + float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; + + Vector &inputVertices = *input; + size_t inputVerticesLength = input->size() - 2, outputStart = output->size(); + for (size_t ii = 0; ii < inputVerticesLength; ii += 2) { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; + bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; + if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { + if (side2) { + // v1 inside, v2 inside + output->add(inputX2); + output->add(inputY2); + continue; + } + // v1 inside, v2 outside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (MathUtil::abs(s) > 0.000001f) { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output->add(edgeX + (edgeX2 - edgeX) * ua); + output->add(edgeY + (edgeY2 - edgeY) * ua); + } else { + output->add(edgeX); + output->add(edgeY); + } + } else if (side2) { + // v1 outside, v2 inside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (MathUtil::abs(s) > 0.000001f) { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output->add(edgeX + (edgeX2 - edgeX) * ua); + output->add(edgeY + (edgeY2 - edgeY) * ua); + } else { + output->add(edgeX); + output->add(edgeY); + } + output->add(inputX2); + output->add(inputY2); + } + clipped = true; + } + + if (outputStart == output->size()) { + // All edges outside. + originalOutput->clear(); + return true; + } + + output->add((*output)[0]); + output->add((*output)[1]); + + if (i == clippingVerticesLast) { + break; + } + Vector *temp = output; + output = input; + output->clear(); + input = temp; + } + + if (originalOutput != output) { + originalOutput->clear(); + for (size_t i = 0, n = output->size() - 2; i < n; ++i) + originalOutput->add((*output)[i]); + } else + originalOutput->setSize(originalOutput->size() - 2, 0); + + return clipped; +} + +void SkeletonClipping::makeClockwise(Vector &polygon) { + size_t verticeslength = polygon.size(); + + float area = polygon[verticeslength - 2] * polygon[1] - polygon[0] * polygon[verticeslength - 1]; + float p1x, p1y, p2x, p2y; + + for (size_t i = 0, n = verticeslength - 3; i < n; i += 2) { + p1x = polygon[i]; + p1y = polygon[i + 1]; + p2x = polygon[i + 2]; + p2y = polygon[i + 3]; + area += p1x * p2y - p2x * p1y; + } + + if (area < 0) return; + + for (size_t i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { + float x = polygon[i], y = polygon[i + 1]; + int other = lastX - i; + polygon[i] = polygon[other]; + polygon[i + 1] = polygon[other + 1]; + polygon[other] = x; + polygon[other + 1] = y; + } +} diff --git a/cocos/editor-support/spine/SkeletonClipping.h b/cocos/editor-support/spine/SkeletonClipping.h new file mode 100644 index 0000000..2046f8c --- /dev/null +++ b/cocos/editor-support/spine/SkeletonClipping.h @@ -0,0 +1,79 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonClipping_h +#define Spine_SkeletonClipping_h + +#include +#include + +namespace spine { +class Slot; +class ClippingAttachment; + +class SP_API SkeletonClipping : public SpineObject { +public: + SkeletonClipping(); + + size_t clipStart(Slot& slot, ClippingAttachment* clip); + + void clipEnd(Slot& slot); + + void clipEnd(); + + void clipTriangles(float* vertices, unsigned short* triangles, size_t trianglesLength, float* uvs, size_t stride); + + void clipTriangles(Vector& vertices, Vector& triangles, Vector& uvs, size_t stride); + + bool isClipping(); + + Vector& getClippedVertices(); + Vector& getClippedTriangles(); + Vector& getClippedUVs(); + +private: + Triangulator _triangulator; + Vector _clippingPolygon; + Vector _clipOutput; + Vector _clippedVertices; + Vector _clippedTriangles; + Vector _clippedUVs; + Vector _scratch; + ClippingAttachment* _clipAttachment; + Vector*>* _clippingPolygons; + + /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping + * area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */ + bool clip(float x1, float y1, float x2, float y2, float x3, float y3, Vector* clippingArea, Vector* output); + + static void makeClockwise(Vector& polygon); +}; +} // namespace spine + +#endif /* Spine_SkeletonClipping_h */ diff --git a/cocos/editor-support/spine/SkeletonData.cpp b/cocos/editor-support/spine/SkeletonData.cpp new file mode 100644 index 0000000..b6c75b9 --- /dev/null +++ b/cocos/editor-support/spine/SkeletonData.cpp @@ -0,0 +1,240 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +SkeletonData::SkeletonData() : _name(), + _defaultSkin(NULL), + _x(0), + _y(0), + _width(0), + _height(0), + _version(), + _hash(), + _fps(0), + _imagesPath() { +} + +SkeletonData::~SkeletonData() { + ContainerUtil::cleanUpVectorOfPointers(_bones); + ContainerUtil::cleanUpVectorOfPointers(_slots); + ContainerUtil::cleanUpVectorOfPointers(_skins); + + _defaultSkin = NULL; + + ContainerUtil::cleanUpVectorOfPointers(_events); + ContainerUtil::cleanUpVectorOfPointers(_animations); + ContainerUtil::cleanUpVectorOfPointers(_ikConstraints); + ContainerUtil::cleanUpVectorOfPointers(_transformConstraints); + ContainerUtil::cleanUpVectorOfPointers(_pathConstraints); + for (size_t i = 0; i < _strings.size(); i++) { + SpineExtension::free(_strings[i], __FILE__, __LINE__); + } +} + +BoneData *SkeletonData::findBone(const String &boneName) { + return ContainerUtil::findWithName(_bones, boneName); +} + +int SkeletonData::findBoneIndex(const String &boneName) { + return ContainerUtil::findIndexWithName(_bones, boneName); +} + +SlotData *SkeletonData::findSlot(const String &slotName) { + return ContainerUtil::findWithName(_slots, slotName); +} + +int SkeletonData::findSlotIndex(const String &slotName) { + return ContainerUtil::findIndexWithName(_slots, slotName); +} + +Skin *SkeletonData::findSkin(const String &skinName) { + return ContainerUtil::findWithName(_skins, skinName); +} + +spine::EventData *SkeletonData::findEvent(const String &eventDataName) { + return ContainerUtil::findWithName(_events, eventDataName); +} + +Animation *SkeletonData::findAnimation(const String &animationName) { + return ContainerUtil::findWithName(_animations, animationName); +} + +IkConstraintData *SkeletonData::findIkConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_ikConstraints, constraintName); +} + +TransformConstraintData *SkeletonData::findTransformConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_transformConstraints, constraintName); +} + +PathConstraintData *SkeletonData::findPathConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_pathConstraints, constraintName); +} + +int SkeletonData::findPathConstraintIndex(const String &pathConstraintName) { + return ContainerUtil::findIndexWithName(_pathConstraints, pathConstraintName); +} + +const String &SkeletonData::getName() { + return _name; +} + +void SkeletonData::setName(const String &inValue) { + _name = inValue; +} + +Vector &SkeletonData::getBones() { + return _bones; +} + +Vector &SkeletonData::getSlots() { + return _slots; +} + +Vector &SkeletonData::getSkins() { + return _skins; +} + +Skin *SkeletonData::getDefaultSkin() { + return _defaultSkin; +} + +void SkeletonData::setDefaultSkin(Skin *inValue) { + _defaultSkin = inValue; +} + +Vector &SkeletonData::getEvents() { + return _events; +} + +Vector &SkeletonData::getAnimations() { + return _animations; +} + +Vector &SkeletonData::getIkConstraints() { + return _ikConstraints; +} + +Vector &SkeletonData::getTransformConstraints() { + return _transformConstraints; +} + +Vector &SkeletonData::getPathConstraints() { + return _pathConstraints; +} + +float SkeletonData::getX() { + return _x; +} + +void SkeletonData::setX(float inValue) { + _x = inValue; +} + +float SkeletonData::getY() { + return _y; +} + +void SkeletonData::setY(float inValue) { + _y = inValue; +} + +float SkeletonData::getWidth() { + return _width; +} + +void SkeletonData::setWidth(float inValue) { + _width = inValue; +} + +float SkeletonData::getHeight() { + return _height; +} + +void SkeletonData::setHeight(float inValue) { + _height = inValue; +} + +const String &SkeletonData::getVersion() { + return _version; +} + +void SkeletonData::setVersion(const String &inValue) { + _version = inValue; +} + +const String &SkeletonData::getHash() { + return _hash; +} + +void SkeletonData::setHash(const String &inValue) { + _hash = inValue; +} + +const String &SkeletonData::getImagesPath() { + return _imagesPath; +} + +void SkeletonData::setImagesPath(const String &inValue) { + _imagesPath = inValue; +} + +const String &SkeletonData::getAudioPath() { + return _audioPath; +} + +void SkeletonData::setAudioPath(const String &inValue) { + _audioPath = inValue; +} + +float SkeletonData::getFps() { + return _fps; +} + +void SkeletonData::setFps(float inValue) { + _fps = inValue; +} diff --git a/cocos/editor-support/spine/SkeletonData.h b/cocos/editor-support/spine/SkeletonData.h new file mode 100644 index 0000000..7de7d8d --- /dev/null +++ b/cocos/editor-support/spine/SkeletonData.h @@ -0,0 +1,191 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonData_h +#define Spine_SkeletonData_h + +#include +#include + +namespace spine { +class BoneData; + +class SlotData; + +class Skin; + +class EventData; + +class Animation; + +class IkConstraintData; + +class TransformConstraintData; + +class PathConstraintData; + +/// Stores the setup pose and all of the stateless data for a skeleton. +class SP_API SkeletonData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Skeleton; + +public: + SkeletonData(); + + ~SkeletonData(); + + /// Finds a bone by comparing each bone's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + BoneData *findBone(const String &boneName); + + /// @return -1 if the bone was not found. + int findBoneIndex(const String &boneName); + + /// @return May be NULL. + SlotData *findSlot(const String &slotName); + + /// @return -1 if the slot was not found. + int findSlotIndex(const String &slotName); + + /// @return May be NULL. + Skin *findSkin(const String &skinName); + + /// @return May be NULL. + spine::EventData *findEvent(const String &eventDataName); + + /// @return May be NULL. + Animation *findAnimation(const String &animationName); + + /// @return May be NULL. + IkConstraintData *findIkConstraint(const String &constraintName); + + /// @return May be NULL. + TransformConstraintData *findTransformConstraint(const String &constraintName); + + /// @return May be NULL. + PathConstraintData *findPathConstraint(const String &constraintName); + + /// @return -1 if the path constraint was not found. + int findPathConstraintIndex(const String &pathConstraintName); + + const String &getName(); + + void setName(const String &inValue); + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + Vector &getBones(); + + Vector &getSlots(); + + /// All skins, including the default skin. + Vector &getSkins(); + + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// @return May be NULL. + Skin *getDefaultSkin(); + + void setDefaultSkin(Skin *inValue); + + Vector &getEvents(); + + Vector &getAnimations(); + + Vector &getIkConstraints(); + + Vector &getTransformConstraints(); + + Vector &getPathConstraints(); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getWidth(); + + void setWidth(float inValue); + + float getHeight(); + + void setHeight(float inValue); + + /// The Spine version used to export this data, or NULL. + const String &getVersion(); + + void setVersion(const String &inValue); + + const String &getHash(); + + void setHash(const String &inValue); + + const String &getImagesPath(); + + void setImagesPath(const String &inValue); + + const String &getAudioPath(); + + void setAudioPath(const String &inValue); + + /// The dopesheet FPS in Spine. Available only when nonessential data was exported. + float getFps(); + + void setFps(float inValue); + +private: + String _name; + Vector _bones; // Ordered parents first + Vector _slots; // Setup pose draw order. + Vector _skins; + Skin *_defaultSkin; + Vector _events; + Vector _animations; + Vector _ikConstraints; + Vector _transformConstraints; + Vector _pathConstraints; + float _x, _y, _width, _height; + String _version; + String _hash; + Vector _strings; + + // Nonessential. + float _fps; + String _imagesPath; + String _audioPath; +}; +} // namespace spine + +#endif /* Spine_SkeletonData_h */ diff --git a/cocos/editor-support/spine/SkeletonJson.cpp b/cocos/editor-support/spine/SkeletonJson.cpp new file mode 100644 index 0000000..a6eb588 --- /dev/null +++ b/cocos/editor-support/spine/SkeletonJson.cpp @@ -0,0 +1,1260 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + #define strdup _strdup +#endif + +using namespace spine; + +SkeletonJson::SkeletonJson(Atlas *atlas) : _attachmentLoader(new (__FILE__, __LINE__) AtlasAttachmentLoader(atlas)), + _scale(1), + _ownsLoader(true) {} + +SkeletonJson::SkeletonJson(AttachmentLoader *attachmentLoader) : _attachmentLoader(attachmentLoader), _scale(1), _ownsLoader(false) { + assert(_attachmentLoader != NULL); +} + +SkeletonJson::~SkeletonJson() { + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + + if (_ownsLoader) delete _attachmentLoader; +} + +SkeletonData *SkeletonJson::readSkeletonDataFile(const String &path) { + int length; + SkeletonData *skeletonData; + const char *json = SpineExtension::readFile(path, &length); + if (length == 0 || !json) { + setError(NULL, "Unable to read skeleton file: ", path); + return NULL; + } + + skeletonData = readSkeletonData(json); + + SpineExtension::free(json, __FILE__, __LINE__); + + return skeletonData; +} + +SkeletonData *SkeletonJson::readSkeletonData(const char *json) { + int i, ii; + SkeletonData *skeletonData; + Json *root, *skeleton, *bones, *boneMap, *ik, *transform, *path, *slots, *skins, *animations, *events; + + _error = ""; + _linkedMeshes.clear(); + + root = new Json(json); + + if (!root) { + setError(NULL, "Invalid skeleton JSON: ", Json::getError()); + return NULL; + } + + skeletonData = new (__FILE__, __LINE__) SkeletonData(); + + skeleton = Json::getItem(root, "skeleton"); + if (skeleton) { + skeletonData->_hash = Json::getString(skeleton, "hash", 0); + skeletonData->_version = Json::getString(skeleton, "spine", 0); + skeletonData->_x = Json::getFloat(skeleton, "x", 0); + skeletonData->_y = Json::getFloat(skeleton, "y", 0); + skeletonData->_width = Json::getFloat(skeleton, "width", 0); + skeletonData->_height = Json::getFloat(skeleton, "height", 0); + skeletonData->_fps = Json::getFloat(skeleton, "fps", 30); + skeletonData->_audioPath = Json::getString(skeleton, "audio", 0); + skeletonData->_imagesPath = Json::getString(skeleton, "images", 0); + } + + /* Bones. */ + bones = Json::getItem(root, "bones"); + skeletonData->_bones.setSize(bones->_size, 0); + int bonesCount = 0; + for (boneMap = bones->_child, i = 0; boneMap; boneMap = boneMap->_next, ++i) { + BoneData *data; + const char *transformMode; + + BoneData *parent = 0; + const char *parentName = Json::getString(boneMap, "parent", 0); + if (parentName) { + parent = skeletonData->findBone(parentName); + if (!parent) { + delete skeletonData; + setError(root, "Parent bone not found: ", parentName); + return NULL; + } + } + + data = new (__FILE__, __LINE__) BoneData(bonesCount, Json::getString(boneMap, "name", 0), parent); + + data->_length = Json::getFloat(boneMap, "length", 0) * _scale; + data->_x = Json::getFloat(boneMap, "x", 0) * _scale; + data->_y = Json::getFloat(boneMap, "y", 0) * _scale; + data->_rotation = Json::getFloat(boneMap, "rotation", 0); + data->_scaleX = Json::getFloat(boneMap, "scaleX", 1); + data->_scaleY = Json::getFloat(boneMap, "scaleY", 1); + data->_shearX = Json::getFloat(boneMap, "shearX", 0); + data->_shearY = Json::getFloat(boneMap, "shearY", 0); + transformMode = Json::getString(boneMap, "transform", "normal"); + data->_transformMode = TransformMode_Normal; + if (strcmp(transformMode, "normal") == 0) + data->_transformMode = TransformMode_Normal; + else if (strcmp(transformMode, "onlyTranslation") == 0) + data->_transformMode = TransformMode_OnlyTranslation; + else if (strcmp(transformMode, "noRotationOrReflection") == 0) + data->_transformMode = TransformMode_NoRotationOrReflection; + else if (strcmp(transformMode, "noScale") == 0) + data->_transformMode = TransformMode_NoScale; + else if (strcmp(transformMode, "noScaleOrReflection") == 0) + data->_transformMode = TransformMode_NoScaleOrReflection; + data->_skinRequired = Json::getBoolean(boneMap, "skin", false); + + skeletonData->_bones[i] = data; + bonesCount++; + } + + /* Slots. */ + slots = Json::getItem(root, "slots"); + if (slots) { + Json *slotMap; + skeletonData->_slots.ensureCapacity(slots->_size); + skeletonData->_slots.setSize(slots->_size, 0); + for (slotMap = slots->_child, i = 0; slotMap; slotMap = slotMap->_next, ++i) { + SlotData *data; + const char *color; + const char *dark; + Json *item; + + const char *boneName = Json::getString(slotMap, "bone", 0); + BoneData *boneData = skeletonData->findBone(boneName); + if (!boneData) { + delete skeletonData; + setError(root, "Slot bone not found: ", boneName); + return NULL; + } + + data = new (__FILE__, __LINE__) SlotData(i, Json::getString(slotMap, "name", 0), *boneData); + + color = Json::getString(slotMap, "color", 0); + if (color) { + Color &c = data->getColor(); + c.r = toColor(color, 0); + c.g = toColor(color, 1); + c.b = toColor(color, 2); + c.a = toColor(color, 3); + } + + dark = Json::getString(slotMap, "dark", 0); + if (dark) { + Color &darkColor = data->getDarkColor(); + darkColor.r = toColor(dark, 0); + darkColor.g = toColor(dark, 1); + darkColor.b = toColor(dark, 2); + darkColor.a = 1; + data->setHasDarkColor(true); + } + + item = Json::getItem(slotMap, "attachment"); + if (item) data->setAttachmentName(item->_valueString); + + item = Json::getItem(slotMap, "blend"); + if (item) { + if (strcmp(item->_valueString, "additive") == 0) + data->_blendMode = BlendMode_Additive; + else if (strcmp(item->_valueString, "multiply") == 0) + data->_blendMode = BlendMode_Multiply; + else if (strcmp(item->_valueString, "screen") == 0) + data->_blendMode = BlendMode_Screen; + } + + skeletonData->_slots[i] = data; + } + } + + /* IK constraints. */ + ik = Json::getItem(root, "ik"); + if (ik) { + Json *constraintMap; + skeletonData->_ikConstraints.ensureCapacity(ik->_size); + skeletonData->_ikConstraints.setSize(ik->_size, 0); + for (constraintMap = ik->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *targetName; + + IkConstraintData *data = new (__FILE__, __LINE__) IkConstraintData(Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "IK bone not found: ", boneMap->_valueString); + return NULL; + } + } + + targetName = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findBone(targetName); + if (!data->_target) { + delete skeletonData; + setError(root, "Target bone not found: ", targetName); + return NULL; + } + + data->_mix = Json::getFloat(constraintMap, "mix", 1); + data->_softness = Json::getFloat(constraintMap, "softness", 0) * _scale; + data->_bendDirection = Json::getInt(constraintMap, "bendPositive", 1) ? 1 : -1; + data->_compress = Json::getInt(constraintMap, "compress", 0) ? true : false; + data->_stretch = Json::getInt(constraintMap, "stretch", 0) ? true : false; + data->_uniform = Json::getInt(constraintMap, "uniform", 0) ? true : false; + + skeletonData->_ikConstraints[i] = data; + } + } + + /* Transform constraints. */ + transform = Json::getItem(root, "transform"); + if (transform) { + Json *constraintMap; + skeletonData->_transformConstraints.ensureCapacity(transform->_size); + skeletonData->_transformConstraints.setSize(transform->_size, 0); + for (constraintMap = transform->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + + TransformConstraintData *data = new (__FILE__, __LINE__) TransformConstraintData(Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "Transform bone not found: ", boneMap->_valueString); + return NULL; + } + } + + name = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findBone(name); + if (!data->_target) { + delete skeletonData; + setError(root, "Target bone not found: ", name); + return NULL; + } + + data->_local = Json::getInt(constraintMap, "local", 0) ? true : false; + data->_relative = Json::getInt(constraintMap, "relative", 0) ? true : false; + data->_offsetRotation = Json::getFloat(constraintMap, "rotation", 0); + data->_offsetX = Json::getFloat(constraintMap, "x", 0) * _scale; + data->_offsetY = Json::getFloat(constraintMap, "y", 0) * _scale; + data->_offsetScaleX = Json::getFloat(constraintMap, "scaleX", 0); + data->_offsetScaleY = Json::getFloat(constraintMap, "scaleY", 0); + data->_offsetShearY = Json::getFloat(constraintMap, "shearY", 0); + + data->_rotateMix = Json::getFloat(constraintMap, "rotateMix", 1); + data->_translateMix = Json::getFloat(constraintMap, "translateMix", 1); + data->_scaleMix = Json::getFloat(constraintMap, "scaleMix", 1); + data->_shearMix = Json::getFloat(constraintMap, "shearMix", 1); + + skeletonData->_transformConstraints[i] = data; + } + } + + /* Path constraints */ + path = Json::getItem(root, "path"); + if (path) { + Json *constraintMap; + skeletonData->_pathConstraints.ensureCapacity(path->_size); + skeletonData->_pathConstraints.setSize(path->_size, 0); + for (constraintMap = path->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + const char *item; + + PathConstraintData *data = new (__FILE__, __LINE__) PathConstraintData(Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "Path bone not found: ", boneMap->_valueString); + return NULL; + } + } + + name = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findSlot(name); + if (!data->_target) { + delete skeletonData; + setError(root, "Target slot not found: ", name); + return NULL; + } + + item = Json::getString(constraintMap, "positionMode", "percent"); + if (strcmp(item, "fixed") == 0) { + data->_positionMode = PositionMode_Fixed; + } else if (strcmp(item, "percent") == 0) { + data->_positionMode = PositionMode_Percent; + } + + item = Json::getString(constraintMap, "spacingMode", "length"); + if (strcmp(item, "length") == 0) + data->_spacingMode = SpacingMode_Length; + else if (strcmp(item, "fixed") == 0) + data->_spacingMode = SpacingMode_Fixed; + else if (strcmp(item, "percent") == 0) + data->_spacingMode = SpacingMode_Percent; + + item = Json::getString(constraintMap, "rotateMode", "tangent"); + if (strcmp(item, "tangent") == 0) + data->_rotateMode = RotateMode_Tangent; + else if (strcmp(item, "chain") == 0) + data->_rotateMode = RotateMode_Chain; + else if (strcmp(item, "chainScale") == 0) + data->_rotateMode = RotateMode_ChainScale; + + data->_offsetRotation = Json::getFloat(constraintMap, "rotation", 0); + data->_position = Json::getFloat(constraintMap, "position", 0); + if (data->_positionMode == PositionMode_Fixed) data->_position *= _scale; + data->_spacing = Json::getFloat(constraintMap, "spacing", 0); + if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) data->_spacing *= _scale; + data->_rotateMix = Json::getFloat(constraintMap, "rotateMix", 1); + data->_translateMix = Json::getFloat(constraintMap, "translateMix", 1); + + skeletonData->_pathConstraints[i] = data; + } + } + + /* Skins. */ + skins = Json::getItem(root, "skins"); + if (skins) { + Json *skinMap; + skeletonData->_skins.ensureCapacity(skins->_size); + skeletonData->_skins.setSize(skins->_size, 0); + int skinsIndex = 0; + for (skinMap = skins->_child, i = 0; skinMap; skinMap = skinMap->_next, ++i) { + Json *attachmentsMap; + Json *curves; + + Skin *skin = nullptr; + auto skinName = Json::getString(skinMap, "name", ""); + if (strlen(skinName) == 0) { + skinName = skinMap->_name; + } + skin = new (__FILE__, __LINE__) Skin(skinName); + + Json *item = Json::getItem(skinMap, "bones"); + if (item) { + for (item = item->_child; item; item = item->_next) { + BoneData *data = skeletonData->findBone(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin bone not found: "), item->_valueString); + return NULL; + } + skin->getBones().add(data); + } + } + + item = Json::getItem(skinMap, "ik"); + if (item) { + for (item = item->_child; item; item = item->_next) { + IkConstraintData *data = skeletonData->findIkConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin IK constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "transform"); + if (item) { + for (item = item->_child; item; item = item->_next) { + TransformConstraintData *data = skeletonData->findTransformConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin transform constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "path"); + if (item) { + for (item = item->_child; item; item = item->_next) { + PathConstraintData *data = skeletonData->findPathConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin path constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + skeletonData->_skins[skinsIndex++] = skin; + if (strcmp(skinName, "default") == 0) { + skeletonData->_defaultSkin = skin; + } + + auto attachmentsData = Json::getItem(skinMap, "attachments"); + if (!attachmentsData) { + attachmentsData = skinMap; + } + for (attachmentsMap = attachmentsData->_child; attachmentsMap; attachmentsMap = attachmentsMap->_next) { + SlotData *slot = skeletonData->findSlot(attachmentsMap->_name); + Json *attachmentMap; + + for (attachmentMap = attachmentsMap->_child; attachmentMap; attachmentMap = attachmentMap->_next) { + Attachment *attachment = NULL; + const char *skinAttachmentName = attachmentMap->_name; + const char *attachmentName = Json::getString(attachmentMap, "name", skinAttachmentName); + const char *attachmentPath = Json::getString(attachmentMap, "path", attachmentName); + const char *color; + Json *entry; + + const char *typeString = Json::getString(attachmentMap, "type", "region"); + AttachmentType type; + if (strcmp(typeString, "region") == 0) + type = AttachmentType_Region; + else if (strcmp(typeString, "mesh") == 0) + type = AttachmentType_Mesh; + else if (strcmp(typeString, "linkedmesh") == 0) + type = AttachmentType_Linkedmesh; + else if (strcmp(typeString, "boundingbox") == 0) + type = AttachmentType_Boundingbox; + else if (strcmp(typeString, "path") == 0) + type = AttachmentType_Path; + else if (strcmp(typeString, "clipping") == 0) + type = AttachmentType_Clipping; + else if (strcmp(typeString, "point") == 0) + type = AttachmentType_Point; + else { + delete skeletonData; + setError(root, "Unknown attachment type: ", typeString); + return NULL; + } + + switch (type) { + case AttachmentType_Region: { + attachment = _attachmentLoader->newRegionAttachment(*skin, attachmentName, attachmentPath); + if (!attachment) { + // delete skeletonData; + // setError(root, "Error reading attachment: ", skinAttachmentName); + // return NULL; + continue; + } + + RegionAttachment *region = static_cast(attachment); + region->_path = attachmentPath; + + region->_x = Json::getFloat(attachmentMap, "x", 0) * _scale; + region->_y = Json::getFloat(attachmentMap, "y", 0) * _scale; + region->_scaleX = Json::getFloat(attachmentMap, "scaleX", 1); + region->_scaleY = Json::getFloat(attachmentMap, "scaleY", 1); + region->_rotation = Json::getFloat(attachmentMap, "rotation", 0); + region->_width = Json::getFloat(attachmentMap, "width", 32) * _scale; + region->_height = Json::getFloat(attachmentMap, "height", 32) * _scale; + + color = Json::getString(attachmentMap, "color", 0); + if (color) { + region->getColor().r = toColor(color, 0); + region->getColor().g = toColor(color, 1); + region->getColor().b = toColor(color, 2); + region->getColor().a = toColor(color, 3); + } + + region->updateOffset(); + _attachmentLoader->configureAttachment(region); + break; + } + case AttachmentType_Mesh: + case AttachmentType_Linkedmesh: { + attachment = _attachmentLoader->newMeshAttachment(*skin, attachmentName, attachmentPath); + + if (!attachment) { + // delete skeletonData; + // setError(root, "Error reading attachment: ", skinAttachmentName); + // return NULL; + continue; + } + + MeshAttachment *mesh = static_cast(attachment); + mesh->_path = attachmentPath; + + color = Json::getString(attachmentMap, "color", 0); + if (color) { + mesh->getColor().r = toColor(color, 0); + mesh->getColor().g = toColor(color, 1); + mesh->getColor().b = toColor(color, 2); + mesh->getColor().a = toColor(color, 3); + } + + mesh->_width = Json::getFloat(attachmentMap, "width", 32) * _scale; + mesh->_height = Json::getFloat(attachmentMap, "height", 32) * _scale; + + entry = Json::getItem(attachmentMap, "parent"); + if (!entry) { + int verticesLength; + entry = Json::getItem(attachmentMap, "triangles"); + mesh->_triangles.ensureCapacity(entry->_size); + mesh->_triangles.setSize(entry->_size, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_triangles[ii] = (unsigned short)entry->_valueInt; + + entry = Json::getItem(attachmentMap, "uvs"); + verticesLength = entry->_size; + mesh->_regionUVs.ensureCapacity(verticesLength); + mesh->_regionUVs.setSize(verticesLength, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_regionUVs[ii] = entry->_valueFloat; + + readVertices(attachmentMap, mesh, verticesLength); + + mesh->updateUVs(); + + mesh->_hullLength = Json::getInt(attachmentMap, "hull", 0); + + entry = Json::getItem(attachmentMap, "edges"); + if (entry) { + mesh->_edges.ensureCapacity(entry->_size); + mesh->_edges.setSize(entry->_size, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_edges[ii] = entry->_valueInt; + } + _attachmentLoader->configureAttachment(mesh); + } else { + bool inheritDeform = Json::getInt(attachmentMap, "deform", 1) ? true : false; + LinkedMesh *linkedMesh = new (__FILE__, __LINE__) LinkedMesh(mesh, + String(Json::getString(attachmentMap, "skin", 0)), slot->getIndex(), String(entry->_valueString), + inheritDeform); + _linkedMeshes.add(linkedMesh); + } + break; + } + case AttachmentType_Boundingbox: { + attachment = _attachmentLoader->newBoundingBoxAttachment(*skin, attachmentName); + + BoundingBoxAttachment *box = static_cast(attachment); + + int vertexCount = Json::getInt(attachmentMap, "vertexCount", 0) << 1; + readVertices(attachmentMap, box, vertexCount); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Path: { + attachment = _attachmentLoader->newPathAttachment(*skin, attachmentName); + + PathAttachment *pathAttatchment = static_cast(attachment); + + int vertexCount = 0; + pathAttatchment->_closed = Json::getInt(attachmentMap, "closed", 0) ? true : false; + pathAttatchment->_constantSpeed = Json::getInt(attachmentMap, "constantSpeed", 1) ? true : false; + vertexCount = Json::getInt(attachmentMap, "vertexCount", 0); + readVertices(attachmentMap, pathAttatchment, vertexCount << 1); + + pathAttatchment->_lengths.ensureCapacity(vertexCount / 3); + pathAttatchment->_lengths.setSize(vertexCount / 3, 0); + + curves = Json::getItem(attachmentMap, "lengths"); + for (curves = curves->_child, ii = 0; curves; curves = curves->_next, ++ii) + pathAttatchment->_lengths[ii] = curves->_valueFloat * _scale; + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Point: { + attachment = _attachmentLoader->newPointAttachment(*skin, attachmentName); + + PointAttachment *point = static_cast(attachment); + + point->_x = Json::getFloat(attachmentMap, "x", 0) * _scale; + point->_y = Json::getFloat(attachmentMap, "y", 0) * _scale; + point->_rotation = Json::getFloat(attachmentMap, "rotation", 0); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Clipping: { + attachment = _attachmentLoader->newClippingAttachment(*skin, attachmentName); + + ClippingAttachment *clip = static_cast(attachment); + + int vertexCount = 0; + const char *end = Json::getString(attachmentMap, "end", 0); + if (end) clip->_endSlot = skeletonData->findSlot(end); + vertexCount = Json::getInt(attachmentMap, "vertexCount", 0) << 1; + readVertices(attachmentMap, clip, vertexCount); + _attachmentLoader->configureAttachment(attachment); + break; + } + } + + skin->setAttachment(slot->getIndex(), skinAttachmentName, attachment); + } + } + } + } + + /* Linked meshes. */ + int n = _linkedMeshes.size(); + for (i = 0; i < n; ++i) { + LinkedMesh *linkedMesh = _linkedMeshes[i]; + Skin *skin = linkedMesh->_skin.length() == 0 ? skeletonData->getDefaultSkin() : skeletonData->findSkin(linkedMesh->_skin); + if (skin == NULL) { + // delete skeletonData; + // setError(root, "Skin not found: ", linkedMesh->_skin.buffer()); + // return NULL; + continue; + } + Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent); + if (parent == NULL) { + // delete skeletonData; + // setError(root, "Parent mesh not found: ", linkedMesh->_parent.buffer()); + // return NULL; + continue; + } + linkedMesh->_mesh->_deformAttachment = linkedMesh->_inheritDeform ? static_cast(parent) : linkedMesh->_mesh; + linkedMesh->_mesh->setParentMesh(static_cast(parent)); + linkedMesh->_mesh->updateUVs(); + _attachmentLoader->configureAttachment(linkedMesh->_mesh); + } + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + /* Events. */ + events = Json::getItem(root, "events"); + if (events) { + Json *eventMap; + skeletonData->_events.ensureCapacity(events->_size); + skeletonData->_events.setSize(events->_size, 0); + for (eventMap = events->_child, i = 0; eventMap; eventMap = eventMap->_next, ++i) { + EventData *eventData = new (__FILE__, __LINE__) EventData(String(eventMap->_name)); + + eventData->_intValue = Json::getInt(eventMap, "int", 0); + eventData->_floatValue = Json::getFloat(eventMap, "float", 0); + const char *stringValue = Json::getString(eventMap, "string", 0); + eventData->_stringValue = stringValue; + const char *audioPath = Json::getString(eventMap, "audio", 0); + eventData->_audioPath = audioPath; + if (audioPath) { + eventData->_volume = Json::getFloat(eventMap, "volume", 1); + eventData->_balance = Json::getFloat(eventMap, "balance", 0); + } + skeletonData->_events[i] = eventData; + } + } + + /* Animations. */ + animations = Json::getItem(root, "animations"); + if (animations) { + Json *animationMap; + skeletonData->_animations.ensureCapacity(animations->_size); + skeletonData->_animations.setSize(animations->_size, 0); + int animationsIndex = 0; + for (animationMap = animations->_child; animationMap; animationMap = animationMap->_next) { + Animation *animation = readAnimation(animationMap, skeletonData); + if (!animation) { + // delete skeletonData; + // delete root; + // return NULL; + continue; + } + skeletonData->_animations[animationsIndex++] = animation; + } + } + + delete root; + + return skeletonData; +} + +float SkeletonJson::toColor(const char *value, size_t index) { + char digits[3]; + char *error; + int color; + + if (index >= strlen(value) / 2) return -1; + + value += index * 2; + + digits[0] = *value; + digits[1] = *(value + 1); + digits[2] = '\0'; + color = (int)strtoul(digits, &error, 16); + if (*error != 0) return -1; + + return color / (float)255; +} + +void SkeletonJson::readCurve(Json *frame, CurveTimeline *timeline, size_t frameIndex) { + Json *curve = Json::getItem(frame, "curve"); + if (!curve) return; + if (curve->_type == Json::JSON_STRING && strcmp(curve->_valueString, "stepped") == 0) + timeline->setStepped(frameIndex); + else { + float c1 = Json::getFloat(frame, "curve", 0); + float c2 = Json::getFloat(frame, "c2", 0); + float c3 = Json::getFloat(frame, "c3", 1); + float c4 = Json::getFloat(frame, "c4", 1); + timeline->setCurve(frameIndex, c1, c2, c3, c4); + } +} + +Animation *SkeletonJson::readAnimation(Json *root, SkeletonData *skeletonData) { + Vector timelines; + float duration = 0; + + size_t frameIndex; + Json *valueMap; + int timelinesCount = 0; + + Json *bones = Json::getItem(root, "bones"); + Json *slots = Json::getItem(root, "slots"); + Json *ik = Json::getItem(root, "ik"); + Json *transform = Json::getItem(root, "transform"); + Json *paths = Json::getItem(root, "path"); + if (paths == NULL) { + paths = Json::getItem(root, "paths"); + } + Json *deform = Json::getItem(root, "deform"); + Json *drawOrder = Json::getItem(root, "drawOrder"); + Json *events = Json::getItem(root, "events"); + Json *boneMap, *slotMap, *constraintMap; + if (!drawOrder) drawOrder = Json::getItem(root, "draworder"); + + for (boneMap = bones ? bones->_child : NULL; boneMap; boneMap = boneMap->_next) + timelinesCount += boneMap->_size; + + for (slotMap = slots ? slots->_child : NULL; slotMap; slotMap = slotMap->_next) + timelinesCount += slotMap->_size; + + timelinesCount += ik ? ik->_size : 0; + timelinesCount += transform ? transform->_size : 0; + + for (constraintMap = paths ? paths->_child : NULL; constraintMap; constraintMap = constraintMap->_next) + timelinesCount += constraintMap->_size; + + for (constraintMap = deform ? deform->_child : NULL; constraintMap; constraintMap = constraintMap->_next) + for (slotMap = constraintMap->_child; slotMap; slotMap = slotMap->_next) + timelinesCount += slotMap->_size; + + if (drawOrder) ++timelinesCount; + + if (events) ++timelinesCount; + + /** Slot timelines. */ + for (slotMap = slots ? slots->_child : 0; slotMap; slotMap = slotMap->_next) { + Json *timelineMap; + + int slotIndex = skeletonData->findSlotIndex(slotMap->_name); + if (slotIndex == -1) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Slot not found: ", slotMap->_name); + return NULL; + } + + for (timelineMap = slotMap->_child; timelineMap; timelineMap = timelineMap->_next) { + if (strcmp(timelineMap->_name, "attachment") == 0) { + AttachmentTimeline *timeline = new (__FILE__, __LINE__) AttachmentTimeline(timelineMap->_size); + + timeline->_slotIndex = slotIndex; + + for (valueMap = timelineMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + Json *name = Json::getItem(valueMap, "name"); + String attachmentName = name->_type == Json::JSON_NULL ? "" : name->_valueString; + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), attachmentName); + } + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[timelineMap->_size - 1]); + + } else if (strcmp(timelineMap->_name, "color") == 0) { + ColorTimeline *timeline = new (__FILE__, __LINE__) ColorTimeline(timelineMap->_size); + + timeline->_slotIndex = slotIndex; + + for (valueMap = timelineMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + const char *s = Json::getString(valueMap, "color", 0); + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), toColor(s, 0), toColor(s, 1), + toColor(s, 2), toColor(s, 3)); + readCurve(valueMap, timeline, frameIndex); + } + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[(timelineMap->_size - 1) * ColorTimeline::ENTRIES]); + + } else if (strcmp(timelineMap->_name, "twoColor") == 0) { + TwoColorTimeline *timeline = new (__FILE__, __LINE__) TwoColorTimeline(timelineMap->_size); + + timeline->_slotIndex = slotIndex; + + for (valueMap = timelineMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + const char *s = Json::getString(valueMap, "light", 0); + const char *ds = Json::getString(valueMap, "dark", 0); + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), toColor(s, 0), toColor(s, 1), + toColor(s, 2), toColor(s, 3), toColor(ds, 0), toColor(ds, 1), toColor(ds, 2)); + readCurve(valueMap, timeline, frameIndex); + } + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, + timeline->_frames[(timelineMap->_size - 1) * TwoColorTimeline::ENTRIES]); + } else { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Invalid timeline type for a slot: ", timelineMap->_name); + return NULL; + } + } + } + + /** Bone timelines. */ + for (boneMap = bones ? bones->_child : 0; boneMap; boneMap = boneMap->_next) { + Json *timelineMap; + + int boneIndex = skeletonData->findBoneIndex(boneMap->_name); + if (boneIndex == -1) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Bone not found: ", boneMap->_name); + return NULL; + } + + for (timelineMap = boneMap->_child; timelineMap; timelineMap = timelineMap->_next) { + if (strcmp(timelineMap->_name, "rotate") == 0) { + RotateTimeline *timeline = new (__FILE__, __LINE__) RotateTimeline(timelineMap->_size); + + timeline->_boneIndex = boneIndex; + + for (valueMap = timelineMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), Json::getFloat(valueMap, "angle", 0)); + readCurve(valueMap, timeline, frameIndex); + } + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[(timelineMap->_size - 1) * RotateTimeline::ENTRIES]); + } else { + int isScale = strcmp(timelineMap->_name, "scale") == 0; + int isTranslate = strcmp(timelineMap->_name, "translate") == 0; + int isShear = strcmp(timelineMap->_name, "shear") == 0; + if (isScale || isTranslate || isShear) { + float timelineScale = isTranslate ? _scale : 1; + float defaultValue = 0; + TranslateTimeline *timeline = 0; + if (isScale) { + timeline = new (__FILE__, __LINE__) ScaleTimeline(timelineMap->_size); + defaultValue = 1; + } else if (isTranslate) { + timeline = new (__FILE__, __LINE__) TranslateTimeline(timelineMap->_size); + } else if (isShear) { + timeline = new (__FILE__, __LINE__) ShearTimeline(timelineMap->_size); + } + timeline->_boneIndex = boneIndex; + + for (valueMap = timelineMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), + Json::getFloat(valueMap, "x", defaultValue) * timelineScale, + Json::getFloat(valueMap, "y", defaultValue) * timelineScale); + readCurve(valueMap, timeline, frameIndex); + } + + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[(timelineMap->_size - 1) * TranslateTimeline::ENTRIES]); + } else { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Invalid timeline type for a bone: ", timelineMap->_name); + return NULL; + } + } + } + } + + /** IK constraint timelines. */ + for (constraintMap = ik ? ik->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + IkConstraintData *constraint = skeletonData->findIkConstraint(constraintMap->_name); + IkConstraintTimeline *timeline = new (__FILE__, __LINE__) IkConstraintTimeline(constraintMap->_size); + + for (frameIndex = 0; frameIndex < skeletonData->_ikConstraints.size(); ++frameIndex) { + if (constraint == skeletonData->_ikConstraints[frameIndex]) { + timeline->_ikConstraintIndex = frameIndex; + break; + } + } + for (valueMap = constraintMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), Json::getFloat(valueMap, "mix", 1), + Json::getFloat(valueMap, "softness", 0) * _scale, Json::getInt(valueMap, "bendPositive", 1) ? 1 : -1, + Json::getInt(valueMap, "compress", 0) ? true : false, Json::getInt(valueMap, "stretch", 0) ? true : false); + readCurve(valueMap, timeline, frameIndex); + } + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[(constraintMap->_size - 1) * IkConstraintTimeline::ENTRIES]); + } + + /** Transform constraint timelines. */ + for (constraintMap = transform ? transform->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + TransformConstraintData *constraint = skeletonData->findTransformConstraint(constraintMap->_name); + TransformConstraintTimeline *timeline = new (__FILE__, __LINE__) TransformConstraintTimeline(constraintMap->_size); + + for (frameIndex = 0; frameIndex < skeletonData->_transformConstraints.size(); ++frameIndex) { + if (constraint == skeletonData->_transformConstraints[frameIndex]) { + timeline->_transformConstraintIndex = frameIndex; + break; + } + } + for (valueMap = constraintMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), + Json::getFloat(valueMap, "rotateMix", 1), Json::getFloat(valueMap, "translateMix", 1), + Json::getFloat(valueMap, "scaleMix", 1), Json::getFloat(valueMap, "shearMix", 1)); + readCurve(valueMap, timeline, frameIndex); + } + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[(constraintMap->_size - 1) * TransformConstraintTimeline::ENTRIES]); + } + + /** Path constraint timelines. */ + for (constraintMap = paths ? paths->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + size_t constraintIndex = 0, i; + Json *timelineMap; + + PathConstraintData *data = skeletonData->findPathConstraint(constraintMap->_name); + if (!data) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Path constraint not found: ", constraintMap->_name); + return NULL; + } + + for (i = 0; i < skeletonData->_pathConstraints.size(); i++) { + if (skeletonData->_pathConstraints[i] == data) { + constraintIndex = i; + break; + } + } + + for (timelineMap = constraintMap->_child; timelineMap; timelineMap = timelineMap->_next) { + const char *timelineName = timelineMap->_name; + if (strcmp(timelineName, "position") == 0 || strcmp(timelineName, "spacing") == 0) { + PathConstraintPositionTimeline *timeline; + float timelineScale = 1; + if (strcmp(timelineName, "spacing") == 0) { + timeline = new (__FILE__, __LINE__) PathConstraintSpacingTimeline(timelineMap->_size); + + if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) { + timelineScale = _scale; + } + } else { + timeline = new (__FILE__, __LINE__) PathConstraintPositionTimeline(timelineMap->_size); + + if (data->_positionMode == PositionMode_Fixed) { + timelineScale = _scale; + } + } + + timeline->_pathConstraintIndex = constraintIndex; + for (valueMap = timelineMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), + Json::getFloat(valueMap, timelineName, 0) * timelineScale); + readCurve(valueMap, timeline, frameIndex); + } + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[(timelineMap->_size - 1) * + PathConstraintPositionTimeline::ENTRIES]); + } else if (strcmp(timelineName, "mix") == 0) { + PathConstraintMixTimeline *timeline = new (__FILE__, __LINE__) PathConstraintMixTimeline(timelineMap->_size); + timeline->_pathConstraintIndex = constraintIndex; + for (valueMap = timelineMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), + Json::getFloat(valueMap, "rotateMix", 1), + Json::getFloat(valueMap, "translateMix", 1)); + readCurve(valueMap, timeline, frameIndex); + } + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[(timelineMap->_size - 1) * PathConstraintMixTimeline::ENTRIES]); + } + } + } + + /** Deform timelines. */ + for (constraintMap = deform ? deform->_child : NULL; constraintMap; constraintMap = constraintMap->_next) { + Skin *skin = skeletonData->findSkin(constraintMap->_name); + for (slotMap = constraintMap->_child; slotMap; slotMap = slotMap->_next) { + int slotIndex = skeletonData->findSlotIndex(slotMap->_name); + Json *timelineMap; + for (timelineMap = slotMap->_child; timelineMap; timelineMap = timelineMap->_next) { + DeformTimeline *timeline; + int weighted, deformLength; + + Attachment *baseAttachment = skin->getAttachment(slotIndex, timelineMap->_name); + + if (!baseAttachment) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + // setError(NULL, "Attachment not found: ", timelineMap->_name); + // return NULL; + continue; + } + + VertexAttachment *attachment = static_cast(baseAttachment); + + weighted = attachment->_bones.size() != 0; + Vector &verts = attachment->_vertices; + deformLength = weighted ? verts.size() / 3 * 2 : verts.size(); + + timeline = new (__FILE__, __LINE__) DeformTimeline(timelineMap->_size); + + timeline->_slotIndex = slotIndex; + timeline->_attachment = attachment; + + for (valueMap = timelineMap->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + Json *vertices = Json::getItem(valueMap, "vertices"); + Vector deformed; + if (!vertices) { + if (weighted) { + deformed.setSize(deformLength, 0); + } else { + deformed.clearAndAddAll(attachment->_vertices); + } + } else { + int v, start = Json::getInt(valueMap, "offset", 0); + Json *vertex; + deformed.setSize(deformLength, 0); + if (_scale == 1) { + for (vertex = vertices->_child, v = start; vertex; vertex = vertex->_next, ++v) { + deformed[v] = vertex->_valueFloat; + } + } else { + for (vertex = vertices->_child, v = start; vertex; vertex = vertex->_next, ++v) { + deformed[v] = vertex->_valueFloat * _scale; + } + } + if (!weighted) { + Vector &verticesAttachment = attachment->_vertices; + for (v = 0; v < deformLength; ++v) { + deformed[v] += verticesAttachment[v]; + } + } + } + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), deformed); + readCurve(valueMap, timeline, frameIndex); + } + + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[timelineMap->_size - 1]); + } + } + } + + /** Draw order timeline. */ + if (drawOrder) { + DrawOrderTimeline *timeline = new (__FILE__, __LINE__) DrawOrderTimeline(drawOrder->_size); + + for (valueMap = drawOrder->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + int ii; + Vector drawOrder2; + Json *offsets = Json::getItem(valueMap, "offsets"); + if (offsets) { + Json *offsetMap; + Vector unchanged; + unchanged.ensureCapacity(skeletonData->_slots.size() - offsets->_size); + unchanged.setSize(skeletonData->_slots.size() - offsets->_size, 0); + size_t originalIndex = 0, unchangedIndex = 0; + + drawOrder2.ensureCapacity(skeletonData->_slots.size()); + drawOrder2.setSize(skeletonData->_slots.size(), 0); + for (ii = (int)skeletonData->_slots.size() - 1; ii >= 0; --ii) + drawOrder2[ii] = -1; + + for (offsetMap = offsets->_child; offsetMap; offsetMap = offsetMap->_next) { + int slotIndex = skeletonData->findSlotIndex(Json::getString(offsetMap, "slot", 0)); + if (slotIndex == -1) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Slot not found: ", Json::getString(offsetMap, "slot", 0)); + return NULL; + } + /* Collect unchanged items. */ + while (originalIndex != (size_t)slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + /* Set changed items. */ + drawOrder2[originalIndex + Json::getInt(offsetMap, "offset", 0)] = originalIndex; + originalIndex++; + } + /* Collect remaining unchanged items. */ + while (originalIndex < skeletonData->_slots.size()) + unchanged[unchangedIndex++] = originalIndex++; + /* Fill in unchanged items. */ + for (ii = (int)skeletonData->_slots.size() - 1; ii >= 0; ii--) + if (drawOrder2[ii] == -1) drawOrder2[ii] = unchanged[--unchangedIndex]; + } + timeline->setFrame(frameIndex, Json::getFloat(valueMap, "time", 0), drawOrder2); + } + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[drawOrder->_size - 1]); + } + + /** Event timeline. */ + if (events) { + EventTimeline *timeline = new (__FILE__, __LINE__) EventTimeline(events->_size); + + for (valueMap = events->_child, frameIndex = 0; valueMap; valueMap = valueMap->_next, ++frameIndex) { + Event *event; + EventData *eventData = skeletonData->findEvent(Json::getString(valueMap, "name", 0)); + if (!eventData) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Event not found: ", Json::getString(valueMap, "name", 0)); + return NULL; + } + + event = new (__FILE__, __LINE__) Event(Json::getFloat(valueMap, "time", 0), *eventData); + event->_intValue = Json::getInt(valueMap, "int", eventData->_intValue); + event->_floatValue = Json::getFloat(valueMap, "float", eventData->_floatValue); + event->_stringValue = Json::getString(valueMap, "string", eventData->_stringValue.buffer()); + if (!eventData->_audioPath.isEmpty()) { + event->_volume = Json::getFloat(valueMap, "volume", 1); + event->_balance = Json::getFloat(valueMap, "balance", 0); + } + timeline->setFrame(frameIndex, event); + } + timelines.add(timeline); + timelinesCount++; + duration = MathUtil::max(duration, timeline->_frames[events->_size - 1]); + } + + return new (__FILE__, __LINE__) Animation(String(root->_name), timelines, duration); +} + +void SkeletonJson::readVertices(Json *attachmentMap, VertexAttachment *attachment, size_t verticesLength) { + Json *entry; + size_t i, n, nn, entrySize; + Vector vertices; + + attachment->setWorldVerticesLength(verticesLength); + + entry = Json::getItem(attachmentMap, "vertices"); + entrySize = entry->_size; + vertices.ensureCapacity(entrySize); + vertices.setSize(entrySize, 0); + for (entry = entry->_child, i = 0; entry; entry = entry->_next, ++i) + vertices[i] = entry->_valueFloat; + + if (verticesLength == entrySize) { + if (_scale != 1) { + for (i = 0; i < entrySize; ++i) + vertices[i] *= _scale; + } + + attachment->getVertices().clearAndAddAll(vertices); + return; + } + + Vertices bonesAndWeights; + bonesAndWeights._bones.ensureCapacity(verticesLength * 3); + bonesAndWeights._vertices.ensureCapacity(verticesLength * 3 * 3); + + for (i = 0, n = entrySize; i < n;) { + int boneCount = (int)vertices[i++]; + bonesAndWeights._bones.add(boneCount); + for (nn = i + boneCount * 4; i < nn; i += 4) { + bonesAndWeights._bones.add((int)vertices[i]); + bonesAndWeights._vertices.add(vertices[i + 1] * _scale); + bonesAndWeights._vertices.add(vertices[i + 2] * _scale); + bonesAndWeights._vertices.add(vertices[i + 3]); + } + } + + attachment->getVertices().clearAndAddAll(bonesAndWeights._vertices); + attachment->getBones().clearAndAddAll(bonesAndWeights._bones); +} + +void SkeletonJson::setError(Json *root, const String &value1, const String &value2) { + _error = String(value1).append(value2); + delete root; +} diff --git a/cocos/editor-support/spine/SkeletonJson.h b/cocos/editor-support/spine/SkeletonJson.h new file mode 100644 index 0000000..3aa8707 --- /dev/null +++ b/cocos/editor-support/spine/SkeletonJson.h @@ -0,0 +1,91 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonJson_h +#define Spine_SkeletonJson_h + +#include +#include +#include + +namespace spine { +class CurveTimeline; + +class VertexAttachment; + +class Animation; + +class Json; + +class SkeletonData; + +class Atlas; + +class AttachmentLoader; + +class LinkedMesh; + +class String; + +class SP_API SkeletonJson : public SpineObject { +public: + explicit SkeletonJson(Atlas *atlas); + + explicit SkeletonJson(AttachmentLoader *attachmentLoader); + + ~SkeletonJson(); + + SkeletonData *readSkeletonDataFile(const String &path); + + SkeletonData *readSkeletonData(const char *json); + + void setScale(float scale) { _scale = scale; } + + String &getError() { return _error; } + +private: + AttachmentLoader *_attachmentLoader; + Vector _linkedMeshes; + float _scale; + const bool _ownsLoader; + String _error; + + static float toColor(const char *value, size_t index); + + static void readCurve(Json *frame, CurveTimeline *timeline, size_t frameIndex); + + Animation *readAnimation(Json *root, SkeletonData *skeletonData); + + void readVertices(Json *attachmentMap, VertexAttachment *attachment, size_t verticesLength); + + void setError(Json *root, const String &value1, const String &value2); +}; +} // namespace spine + +#endif /* Spine_SkeletonJson_h */ diff --git a/cocos/editor-support/spine/Skin.cpp b/cocos/editor-support/spine/Skin.cpp new file mode 100644 index 0000000..5a495fb --- /dev/null +++ b/cocos/editor-support/spine/Skin.cpp @@ -0,0 +1,198 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include + +#include +#include + +#include + +using namespace spine; + +Skin::AttachmentMap::AttachmentMap() { +} + +static void disposeAttachment(Attachment *attachment) { + if (!attachment) return; + attachment->dereference(); + if (attachment->getRefCount() == 0) delete attachment; +} + +void Skin::AttachmentMap::put(size_t slotIndex, const String &attachmentName, Attachment *attachment) { + if (slotIndex >= _buckets.size()) + _buckets.setSize(slotIndex + 1, Vector()); + Vector &bucket = _buckets[slotIndex]; + int existing = findInBucket(bucket, attachmentName); + attachment->reference(); + if (existing >= 0) { + disposeAttachment(bucket[existing]._attachment); + bucket[existing]._attachment = attachment; + } else { + bucket.add(Entry(slotIndex, attachmentName, attachment)); + } +} + +Attachment *Skin::AttachmentMap::get(size_t slotIndex, const String &attachmentName) { + if (slotIndex >= _buckets.size()) return NULL; + int existing = findInBucket(_buckets[slotIndex], attachmentName); + return existing >= 0 ? _buckets[slotIndex][existing]._attachment : NULL; +} + +void Skin::AttachmentMap::remove(size_t slotIndex, const String &attachmentName) { + if (slotIndex >= _buckets.size()) return; + int existing = findInBucket(_buckets[slotIndex], attachmentName); + if (existing >= 0) { + disposeAttachment(_buckets[slotIndex][existing]._attachment); + _buckets[slotIndex].removeAt(existing); + } +} + +int Skin::AttachmentMap::findInBucket(Vector &bucket, const String &attachmentName) { + for (size_t i = 0; i < bucket.size(); i++) + if (bucket[i]._name == attachmentName) return i; + return -1; +} + +Skin::AttachmentMap::Entries Skin::AttachmentMap::getEntries() { + return Skin::AttachmentMap::Entries(_buckets); +} + +Skin::Skin(const String &name) : _name(name), _attachments() { + assert(_name.length() > 0); +} + +Skin::~Skin() { + Skin::AttachmentMap::Entries entries = _attachments.getEntries(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry entry = entries.next(); + disposeAttachment(entry._attachment); + } +} + +void Skin::setAttachment(size_t slotIndex, const String &name, Attachment *attachment) { + assert(attachment); + _attachments.put(slotIndex, name, attachment); +} + +Attachment *Skin::getAttachment(size_t slotIndex, const String &name) { + return _attachments.get(slotIndex, name); +} + +void Skin::removeAttachment(size_t slotIndex, const String &name) { + _attachments.remove(slotIndex, name); +} + +void Skin::findNamesForSlot(size_t slotIndex, Vector &names) { + Skin::AttachmentMap::Entries entries = _attachments.getEntries(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + if (entry._slotIndex == slotIndex) { + names.add(entry._name); + } + } +} + +void Skin::findAttachmentsForSlot(size_t slotIndex, Vector &attachments) { + Skin::AttachmentMap::Entries entries = _attachments.getEntries(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + if (entry._slotIndex == slotIndex) attachments.add(entry._attachment); + } +} + +const String &Skin::getName() { + return _name; +} + +Skin::AttachmentMap::Entries Skin::getAttachments() { + return _attachments.getEntries(); +} + +void Skin::attachAll(Skeleton &skeleton, Skin &oldSkin) { + Vector &slots = skeleton.getSlots(); + Skin::AttachmentMap::Entries entries = oldSkin.getAttachments(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + int slotIndex = entry._slotIndex; + Slot *slot = slots[slotIndex]; + + if (slot->getAttachment() == entry._attachment) { + Attachment *attachment = getAttachment(slotIndex, entry._name); + if (attachment) slot->setAttachment(attachment); + } + } +} + +void Skin::addSkin(Skin *other) { + for (size_t i = 0; i < other->getBones().size(); i++) + if (!_bones.contains(other->getBones()[i])) _bones.add(other->getBones()[i]); + + for (size_t i = 0; i < other->getConstraints().size(); i++) + if (!_constraints.contains(other->getConstraints()[i])) _constraints.add(other->getConstraints()[i]); + + AttachmentMap::Entries entries = other->getAttachments(); + while (entries.hasNext()) { + AttachmentMap::Entry &entry = entries.next(); + setAttachment(entry._slotIndex, entry._name, entry._attachment); + } +} + +void Skin::copySkin(Skin *other) { + for (size_t i = 0; i < other->getBones().size(); i++) + if (!_bones.contains(other->getBones()[i])) _bones.add(other->getBones()[i]); + + for (size_t i = 0; i < other->getConstraints().size(); i++) + if (!_constraints.contains(other->getConstraints()[i])) _constraints.add(other->getConstraints()[i]); + + AttachmentMap::Entries entries = other->getAttachments(); + while (entries.hasNext()) { + AttachmentMap::Entry &entry = entries.next(); + if (entry._attachment->getRTTI().isExactly(MeshAttachment::rtti)) + setAttachment(entry._slotIndex, entry._name, static_cast(entry._attachment)->newLinkedMesh()); + else + setAttachment(entry._slotIndex, entry._name, entry._attachment->copy()); + } +} + +Vector &Skin::getConstraints() { + return _constraints; +} + +Vector &Skin::getBones() { + return _bones; +} diff --git a/cocos/editor-support/spine/Skin.h b/cocos/editor-support/spine/Skin.h new file mode 100644 index 0000000..7ce6574 --- /dev/null +++ b/cocos/editor-support/spine/Skin.h @@ -0,0 +1,163 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Skin_h +#define Spine_Skin_h + +#include +#include + +namespace spine { +class Attachment; + +class Skeleton; +class BoneData; +class ConstraintData; + +/// Stores attachments by slot index and attachment name. +/// See SkeletonData::getDefaultSkin, Skeleton::getSkin, and +/// http://esotericsoftware.com/spine-runtime-skins in the Spine Runtimes Guide. +class SP_API Skin : public SpineObject { + friend class Skeleton; + +public: + class SP_API AttachmentMap : public SpineObject { + friend class Skin; + + public: + struct SP_API Entry { + size_t _slotIndex; + String _name; + Attachment *_attachment; + //Entry(); + Entry(size_t slotIndex, const String &name, Attachment *attachment) : _slotIndex(slotIndex), + _name(name), + _attachment(attachment) { + } + }; + + class SP_API Entries { + friend class AttachmentMap; + + public: + bool hasNext() { + while (true) { + if (_slotIndex >= _buckets.size()) return false; + if (_bucketIndex >= _buckets[_slotIndex].size()) { + _bucketIndex = 0; + ++_slotIndex; + continue; + }; + return true; + } + } + + Entry &next() { + Entry &result = _buckets[_slotIndex][_bucketIndex]; + ++_bucketIndex; + return result; + } + + protected: + Entries(Vector > &buckets) : _buckets(buckets), _slotIndex(0), _bucketIndex(0) { + } + + private: + Vector > &_buckets; + size_t _slotIndex; + size_t _bucketIndex; + }; + + void put(size_t slotIndex, const String &attachmentName, Attachment *attachment); + + Attachment *get(size_t slotIndex, const String &attachmentName); + + void remove(size_t slotIndex, const String &attachmentName); + + Entries getEntries(); + + protected: + AttachmentMap(); + + private: + int findInBucket(Vector &, const String &attachmentName); + + Vector > _buckets; + }; + + explicit Skin(const String &name); + + ~Skin(); + + /// Adds an attachment to the skin for the specified slot index and name. + /// If the name already exists for the slot, the previous value is replaced. + void setAttachment(size_t slotIndex, const String &name, Attachment *attachment); + + /// Returns the attachment for the specified slot index and name, or NULL. + Attachment *getAttachment(size_t slotIndex, const String &name); + + // Removes the attachment from the skin. + void removeAttachment(size_t slotIndex, const String &name); + + /// Finds the skin keys for a given slot. The results are added to the passed array of names. + /// @param slotIndex The target slotIndex. To find the slot index, use Skeleton::findSlotIndex or SkeletonData::findSlotIndex + /// @param names Found skin key names will be added to this array. + void findNamesForSlot(size_t slotIndex, Vector &names); + + /// Finds the attachments for a given slot. The results are added to the passed array of Attachments. + /// @param slotIndex The target slotIndex. To find the slot index, use Skeleton::findSlotIndex or SkeletonData::findSlotIndex + /// @param attachments Found Attachments will be added to this array. + void findAttachmentsForSlot(size_t slotIndex, Vector &attachments); + + const String &getName(); + + /// Adds all attachments, bones, and constraints from the specified skin to this skin. + void addSkin(Skin *other); + + /// Adds all attachments, bones, and constraints from the specified skin to this skin. Attachments are deep copied. + void copySkin(Skin *other); + + AttachmentMap::Entries getAttachments(); + + Vector &getBones(); + + Vector &getConstraints(); + +private: + const String _name; + AttachmentMap _attachments; + Vector _bones; + Vector _constraints; + + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + void attachAll(Skeleton &skeleton, Skin &oldSkin); +}; +} // namespace spine + +#endif /* Spine_Skin_h */ diff --git a/cocos/editor-support/spine/Slot.cpp b/cocos/editor-support/spine/Slot.cpp new file mode 100644 index 0000000..49a6020 --- /dev/null +++ b/cocos/editor-support/spine/Slot.cpp @@ -0,0 +1,114 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include +#include + +using namespace spine; + +Slot::Slot(SlotData &data, Bone &bone) : _data(data), + _bone(bone), + _skeleton(bone.getSkeleton()), + _color(1, 1, 1, 1), + _darkColor(0, 0, 0, 0), + _hasDarkColor(data.hasDarkColor()), + _attachment(NULL), + _attachmentTime(0) { + setToSetupPose(); +} + +void Slot::setToSetupPose() { + _color.set(_data.getColor()); + + const String &attachmentName = _data.getAttachmentName(); + if (attachmentName.length() > 0) { + _attachment = NULL; + setAttachment(_skeleton.getAttachment(_data.getIndex(), attachmentName)); + } else { + setAttachment(NULL); + } +} + +SlotData &Slot::getData() { + return _data; +} + +Bone &Slot::getBone() { + return _bone; +} + +Skeleton &Slot::getSkeleton() { + return _skeleton; +} + +Color &Slot::getColor() { + return _color; +} + +Color &Slot::getDarkColor() { + return _darkColor; +} + +bool Slot::hasDarkColor() { + return _hasDarkColor; +} + +Attachment *Slot::getAttachment() { + return _attachment; +} + +void Slot::setAttachment(Attachment *inValue) { + if (_attachment == inValue) { + return; + } + + _attachment = inValue; + _attachmentTime = _skeleton.getTime(); + _deform.clear(); +} + +float Slot::getAttachmentTime() { + return _skeleton.getTime() - _attachmentTime; +} + +void Slot::setAttachmentTime(float inValue) { + _attachmentTime = _skeleton.getTime() - inValue; +} + +Vector &Slot::getDeform() { + return _deform; +} diff --git a/cocos/editor-support/spine/Slot.h b/cocos/editor-support/spine/Slot.h new file mode 100644 index 0000000..5668ae6 --- /dev/null +++ b/cocos/editor-support/spine/Slot.h @@ -0,0 +1,124 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Slot_h +#define Spine_Slot_h + +#include +#include +#include + +namespace spine { +class SlotData; + +class Bone; + +class Skeleton; + +class Attachment; + +class SP_API Slot : public SpineObject { + friend class VertexAttachment; + + friend class Skeleton; + + friend class SkeletonBounds; + + friend class SkeletonClipping; + + friend class AttachmentTimeline; + + friend class ColorTimeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TwoColorTimeline; + +public: + Slot(SlotData &data, Bone &bone); + + void setToSetupPose(); + + SlotData &getData(); + + Bone &getBone(); + + Skeleton &getSkeleton(); + + Color &getColor(); + + Color &getDarkColor(); + + bool hasDarkColor(); + + /// May be NULL. + Attachment *getAttachment(); + + void setAttachment(Attachment *inValue); + + float getAttachmentTime(); + + void setAttachmentTime(float inValue); + + Vector &getDeform(); + +private: + SlotData &_data; + Bone &_bone; + Skeleton &_skeleton; + Color _color; + Color _darkColor; + bool _hasDarkColor; + Attachment *_attachment; + float _attachmentTime; + Vector _deform; +}; +} // namespace spine + +#endif /* Spine_Slot_h */ diff --git a/cocos/editor-support/spine/SlotData.cpp b/cocos/editor-support/spine/SlotData.cpp new file mode 100644 index 0000000..281c9da --- /dev/null +++ b/cocos/editor-support/spine/SlotData.cpp @@ -0,0 +1,94 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +using namespace spine; + +SlotData::SlotData(int index, const String &name, BoneData &boneData) : _index(index), + _name(name), + _boneData(boneData), + _color(1, 1, 1, 1), + _darkColor(0, 0, 0, 0), + _hasDarkColor(false), + _attachmentName(), + _blendMode(BlendMode_Normal) { + assert(_index >= 0); + assert(_name.length() > 0); +} + +int SlotData::getIndex() { + return _index; +} + +const String &SlotData::getName() { + return _name; +} + +BoneData &SlotData::getBoneData() { + return _boneData; +} + +Color &SlotData::getColor() { + return _color; +} + +Color &SlotData::getDarkColor() { + return _darkColor; +} + +bool SlotData::hasDarkColor() { + return _hasDarkColor; +} + +void SlotData::setHasDarkColor(bool inValue) { + _hasDarkColor = inValue; +} + +const String &SlotData::getAttachmentName() { + return _attachmentName; +} + +void SlotData::setAttachmentName(const String &inValue) { + _attachmentName = inValue; +} + +BlendMode SlotData::getBlendMode() { + return _blendMode; +} + +void SlotData::setBlendMode(BlendMode inValue) { + _blendMode = inValue; +} diff --git a/cocos/editor-support/spine/SlotData.h b/cocos/editor-support/spine/SlotData.h new file mode 100644 index 0000000..f64e2dc --- /dev/null +++ b/cocos/editor-support/spine/SlotData.h @@ -0,0 +1,113 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SlotData_h +#define Spine_SlotData_h + +#include +#include +#include +#include + +namespace spine { +class BoneData; + +class SP_API SlotData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AttachmentTimeline; + + friend class ColorTimeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TwoColorTimeline; + +public: + SlotData(int index, const String &name, BoneData &boneData); + + int getIndex(); + + const String &getName(); + + BoneData &getBoneData(); + + Color &getColor(); + + Color &getDarkColor(); + + bool hasDarkColor(); + + void setHasDarkColor(bool inValue); + + /// May be empty. + const String &getAttachmentName(); + + void setAttachmentName(const String &inValue); + + BlendMode getBlendMode(); + + void setBlendMode(BlendMode inValue); + +private: + const int _index; + String _name; + BoneData &_boneData; + Color _color; + Color _darkColor; + + bool _hasDarkColor; + String _attachmentName; + BlendMode _blendMode; +}; +} // namespace spine + +#endif /* Spine_SlotData_h */ diff --git a/cocos/editor-support/spine/SpacingMode.h b/cocos/editor-support/spine/SpacingMode.h new file mode 100644 index 0000000..c84ea5e --- /dev/null +++ b/cocos/editor-support/spine/SpacingMode.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SpacingMode_h +#define Spine_SpacingMode_h + +namespace spine { +enum SpacingMode { + SpacingMode_Length = 0, + SpacingMode_Fixed, + SpacingMode_Percent +}; +} + +#endif /* Spine_SpacingMode_h */ diff --git a/cocos/editor-support/spine/SpineObject.cpp b/cocos/editor-support/spine/SpineObject.cpp new file mode 100644 index 0000000..f635b04 --- /dev/null +++ b/cocos/editor-support/spine/SpineObject.cpp @@ -0,0 +1,66 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include + +using namespace spine; + +void *SpineObject::operator new(size_t sz) { + return SpineExtension::getInstance()->_calloc(sz, __FILE__, __LINE__); +} + +void *SpineObject::operator new(size_t sz, const char *file, int line) { + return SpineExtension::getInstance()->_calloc(sz, file, line); +} + +void *SpineObject::operator new(size_t sz, void *ptr) { + SP_UNUSED(sz); + return ptr; +} + +void SpineObject::operator delete(void *p, const char *file, int line) { + SpineExtension::free(p, file, line); +} + +void SpineObject::operator delete(void *p, void *mem) { + SP_UNUSED(mem); + SpineExtension::free(p, __FILE__, __LINE__); +} + +void SpineObject::operator delete(void *p) { + SpineExtension::free(p, __FILE__, __LINE__); +} + +SpineObject::~SpineObject() { +} diff --git a/cocos/editor-support/spine/SpineObject.h b/cocos/editor-support/spine/SpineObject.h new file mode 100644 index 0000000..4b76811 --- /dev/null +++ b/cocos/editor-support/spine/SpineObject.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Object_h +#define Spine_Object_h + +#include +#include + +#include + +namespace spine { +class String; + +class SP_API SpineObject { +public: + void *operator new(size_t sz); + + void *operator new(size_t sz, const char *file, int line); + + void *operator new(size_t sz, void *ptr); + + void operator delete(void *p, const char *file, int line); + + void operator delete(void *p, void *mem); + + void operator delete(void *p); + + virtual ~SpineObject(); +}; +} // namespace spine + +#endif diff --git a/cocos/editor-support/spine/SpineString.h b/cocos/editor-support/spine/SpineString.h new file mode 100644 index 0000000..baa1a12 --- /dev/null +++ b/cocos/editor-support/spine/SpineString.h @@ -0,0 +1,211 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_STRING_H +#define SPINE_STRING_H + +#include +#include + +#include +#include + +// Required for sprintf on MSVC +#ifdef _MSC_VER + #pragma warning(disable : 4996) +#endif + +namespace spine { +class SP_API String : public SpineObject { +public: + String() : _length(0), _buffer(NULL) { + } + + String(const char *chars, bool own = false) { + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + if (!own) { + _buffer = SpineExtension::calloc(_length + 1, __FILE__, __LINE__); + memcpy((void *)_buffer, chars, _length + 1); + } else { + _buffer = (char *)chars; + } + } + } + + String(const String &other) { + if (!other._buffer) { + _length = 0; + _buffer = NULL; + } else { + _length = other._length; + _buffer = SpineExtension::calloc(other._length + 1, __FILE__, __LINE__); + memcpy((void *)_buffer, other._buffer, other._length + 1); + } + } + + size_t length() const { + return _length; + } + + bool isEmpty() const { + return _length == 0; + } + + const char *buffer() const { + return _buffer; + } + + void own(const String &other) { + if (this == &other) return; + if (_buffer) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + _length = other._length; + _buffer = other._buffer; + other._length = 0; + other._buffer = NULL; + } + + void own(const char *chars) { + if (_buffer == chars) return; + if (_buffer) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + _buffer = (char *)chars; + } + } + + void unown() { + _length = 0; + _buffer = NULL; + } + + String &operator=(const String &other) { + if (this == &other) return *this; + if (_buffer) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + if (!other._buffer) { + _length = 0; + _buffer = NULL; + } else { + _length = other._length; + _buffer = SpineExtension::calloc(other._length + 1, __FILE__, __LINE__); + memcpy((void *)_buffer, other._buffer, other._length + 1); + } + return *this; + } + + String &operator=(const char *chars) { + if (_buffer == chars) return *this; + if (_buffer) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + _buffer = SpineExtension::calloc(_length + 1, __FILE__, __LINE__); + memcpy((void *)_buffer, chars, _length + 1); + } + return *this; + } + + String &append(const char *chars) { + size_t len = strlen(chars); + size_t thisLen = _length; + _length = _length + len; + bool same = chars == _buffer; + _buffer = SpineExtension::realloc(_buffer, _length + 1, __FILE__, __LINE__); + memcpy((void *)(_buffer + thisLen), (void *)(same ? _buffer : chars), len + 1); + return *this; + } + + String &append(const String &other) { + size_t len = other.length(); + size_t thisLen = _length; + _length = _length + len; + bool same = other._buffer == _buffer; + _buffer = SpineExtension::realloc(_buffer, _length + 1, __FILE__, __LINE__); + memcpy((void *)(_buffer + thisLen), (void *)(same ? _buffer : other._buffer), len + 1); + return *this; + } + + String &append(int other) { + char str[100]; + sprintf(str, "%i", other); + append(str); + return *this; + } + + String &append(float other) { + char str[100]; + sprintf(str, "%f", other); + append(str); + return *this; + } + + friend bool operator==(const String &a, const String &b) { + if (a._buffer == b._buffer) return true; + if (a._length != b._length) return false; + if (a._buffer && b._buffer) { + return strcmp(a._buffer, b._buffer) == 0; + } else { + return false; + } + } + + friend bool operator!=(const String &a, const String &b) { + return !(a == b); + } + + ~String() { + if (_buffer) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + } + +private: + mutable size_t _length; + mutable char *_buffer; +}; +} // namespace spine + +#endif //SPINE_STRING_H diff --git a/cocos/editor-support/spine/TextureLoader.cpp b/cocos/editor-support/spine/TextureLoader.cpp new file mode 100644 index 0000000..9039834 --- /dev/null +++ b/cocos/editor-support/spine/TextureLoader.cpp @@ -0,0 +1,42 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +namespace spine { +TextureLoader::TextureLoader() { +} + +TextureLoader::~TextureLoader() { +} +} // namespace spine diff --git a/cocos/editor-support/spine/TextureLoader.h b/cocos/editor-support/spine/TextureLoader.h new file mode 100644 index 0000000..0b54c33 --- /dev/null +++ b/cocos/editor-support/spine/TextureLoader.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TextureLoader_h +#define Spine_TextureLoader_h + +#include +#include + +namespace spine { +class AtlasPage; + +class SP_API TextureLoader : public SpineObject { +public: + TextureLoader(); + + virtual ~TextureLoader(); + + virtual void load(AtlasPage& page, const String& path) = 0; + + virtual void unload(void* texture) = 0; +}; +} // namespace spine + +#endif /* Spine_TextureLoader_h */ diff --git a/cocos/editor-support/spine/Timeline.cpp b/cocos/editor-support/spine/Timeline.cpp new file mode 100644 index 0000000..fd56e35 --- /dev/null +++ b/cocos/editor-support/spine/Timeline.cpp @@ -0,0 +1,48 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +namespace spine { +RTTI_IMPL_NOPARENT(Timeline) + +Timeline::Timeline() { +} + +Timeline::~Timeline() { +} + +} // namespace spine diff --git a/cocos/editor-support/spine/Timeline.h b/cocos/editor-support/spine/Timeline.h new file mode 100644 index 0000000..28d71fd --- /dev/null +++ b/cocos/editor-support/spine/Timeline.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Timeline_h +#define Spine_Timeline_h + +#include +#include +#include +#include +#include + +namespace spine { +class Skeleton; + +class Event; + +class SP_API Timeline : public SpineObject { + RTTI_DECL + +public: + Timeline(); + + virtual ~Timeline(); + + /// Sets the value(s) for the specified time. + /// @param skeleton The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change. + /// @param lastTime lastTime The time this timeline was last applied. Timelines such as EventTimeline trigger only at specific times rather than every frame. In that case, the timeline triggers everything between lastTime (exclusive) and time (inclusive). + /// @param time The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys. + /// @param pEvents If any events are fired, they are added to this array. Can be NULL to ignore firing events or if the timeline does not fire events. May be NULL. + /// @param alpha alpha 0 applies the current or setup pose value (depending on pose parameter). 1 applies the timeline + /// value. Between 0 and 1 applies a value between the current or setup pose and the timeline value. By adjusting alpha over + /// time, an animation can be mixed in or out. alpha can also be useful to apply animations on top of each other (layered). + /// @param blend Controls how mixing is applied when alpha is than 1. + /// @param direction Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction) = 0; + + virtual int getPropertyId() = 0; +}; +} // namespace spine + +#endif /* Spine_Timeline_h */ diff --git a/cocos/editor-support/spine/TimelineType.h b/cocos/editor-support/spine/TimelineType.h new file mode 100644 index 0000000..a86c0fa --- /dev/null +++ b/cocos/editor-support/spine/TimelineType.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TimelineType_h +#define Spine_TimelineType_h + +namespace spine { +enum TimelineType { + TimelineType_Rotate = 0, + TimelineType_Translate, + TimelineType_Scale, + TimelineType_Shear, + TimelineType_Attachment, + TimelineType_Color, + TimelineType_Deform, + TimelineType_Event, + TimelineType_DrawOrder, + TimelineType_IkConstraint, + TimelineType_TransformConstraint, + TimelineType_PathConstraintPosition, + TimelineType_PathConstraintSpacing, + TimelineType_PathConstraintMix, + TimelineType_TwoColor +}; +} + +#endif /* Spine_TimelineType_h */ diff --git a/cocos/editor-support/spine/TransformConstraint.cpp b/cocos/editor-support/spine/TransformConstraint.cpp new file mode 100644 index 0000000..0252768 --- /dev/null +++ b/cocos/editor-support/spine/TransformConstraint.cpp @@ -0,0 +1,354 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraint, Updatable) + +TransformConstraint::TransformConstraint(TransformConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _target(skeleton.findBone( + data.getTarget()->getName())), + _rotateMix( + data.getRotateMix()), + _translateMix( + data.getTranslateMix()), + _scaleMix( + data.getScaleMix()), + _shearMix( + data.getShearMix()), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); ++i) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } +} + +void TransformConstraint::apply() { + update(); +} + +void TransformConstraint::update() { + if (_data.isLocal()) { + if (_data.isRelative()) + applyRelativeLocal(); + else + applyAbsoluteLocal(); + } else { + if (_data.isRelative()) + applyRelativeWorld(); + else + applyAbsoluteWorld(); + } +} + +int TransformConstraint::getOrder() { + return _data.getOrder(); +} + +TransformConstraintData &TransformConstraint::getData() { + return _data; +} + +Vector &TransformConstraint::getBones() { + return _bones; +} + +Bone *TransformConstraint::getTarget() { + return _target; +} + +void TransformConstraint::setTarget(Bone *inValue) { + _target = inValue; +} + +float TransformConstraint::getRotateMix() { + return _rotateMix; +} + +void TransformConstraint::setRotateMix(float inValue) { + _rotateMix = inValue; +} + +float TransformConstraint::getTranslateMix() { + return _translateMix; +} + +void TransformConstraint::setTranslateMix(float inValue) { + _translateMix = inValue; +} + +float TransformConstraint::getScaleMix() { + return _scaleMix; +} + +void TransformConstraint::setScaleMix(float inValue) { + _scaleMix = inValue; +} + +float TransformConstraint::getShearMix() { + return _shearMix; +} + +void TransformConstraint::setShearMix(float inValue) { + _shearMix = inValue; +} + +void TransformConstraint::applyAbsoluteWorld() { + float rotateMix = _rotateMix, translateMix = _translateMix, scaleMix = _scaleMix, shearMix = _shearMix; + Bone &target = *_target; + float ta = target._a, tb = target._b, tc = target._c, td = target._d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + float offsetRotation = _data._offsetRotation * degRadReflect, offsetShearY = _data._offsetShearY * degRadReflect; + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + bool modified = false; + + if (rotateMix != 0) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + float r = MathUtil::atan2(tc, ta) - MathUtil::atan2(c, a) + offsetRotation; + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= rotateMix; + float cos = MathUtil::cos(r), sin = MathUtil::sin(r); + bone._a = cos * a - sin * c; + bone._b = cos * b - sin * d; + bone._c = sin * a + cos * c; + bone._d = sin * b + cos * d; + modified = true; + } + + if (translateMix != 0) { + float tx, ty; + target.localToWorld(_data._offsetX, _data._offsetY, tx, ty); + bone._worldX += (tx - bone._worldX) * translateMix; + bone._worldY += (ty - bone._worldY) * translateMix; + modified = true; + } + + if (scaleMix > 0) { + float s = MathUtil::sqrt(bone._a * bone._a + bone._c * bone._c); + + if (s > 0.00001f) s = (s + (MathUtil::sqrt(ta * ta + tc * tc) - s + _data._offsetScaleX) * scaleMix) / s; + bone._a *= s; + bone._c *= s; + s = MathUtil::sqrt(bone._b * bone._b + bone._d * bone._d); + + if (s > 0.00001f) s = (s + (MathUtil::sqrt(tb * tb + td * td) - s + _data._offsetScaleY) * scaleMix) / s; + bone._b *= s; + bone._d *= s; + modified = true; + } + + if (shearMix > 0) { + float b = bone._b, d = bone._d; + float by = MathUtil::atan2(d, b); + float r = MathUtil::atan2(td, tb) - MathUtil::atan2(tc, ta) - (by - MathUtil::atan2(bone._c, bone._a)); + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r = by + (r + offsetShearY) * shearMix; + float s = MathUtil::sqrt(b * b + d * d); + bone._b = MathUtil::cos(r) * s; + bone._d = MathUtil::sin(r) * s; + modified = true; + } + + if (modified) bone._appliedValid = false; + } +} + +void TransformConstraint::applyRelativeWorld() { + float rotateMix = _rotateMix, translateMix = _translateMix, scaleMix = _scaleMix, shearMix = _shearMix; + Bone &target = *_target; + float ta = target._a, tb = target._b, tc = target._c, td = target._d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + float offsetRotation = _data._offsetRotation * degRadReflect, offsetShearY = _data._offsetShearY * degRadReflect; + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + bool modified = false; + + if (rotateMix != 0) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + float r = MathUtil::atan2(tc, ta) + offsetRotation; + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= rotateMix; + float cos = MathUtil::cos(r), sin = MathUtil::sin(r); + bone._a = cos * a - sin * c; + bone._b = cos * b - sin * d; + bone._c = sin * a + cos * c; + bone._d = sin * b + cos * d; + modified = true; + } + + if (translateMix != 0) { + float tx, ty; + target.localToWorld(_data._offsetX, _data._offsetY, tx, ty); + bone._worldX += tx * translateMix; + bone._worldY += ty * translateMix; + modified = true; + } + + if (scaleMix > 0) { + float s = (MathUtil::sqrt(ta * ta + tc * tc) - 1 + _data._offsetScaleX) * scaleMix + 1; + bone._a *= s; + bone._c *= s; + s = (MathUtil::sqrt(tb * tb + td * td) - 1 + _data._offsetScaleY) * scaleMix + 1; + bone._b *= s; + bone._d *= s; + modified = true; + } + + if (shearMix > 0) { + float r = MathUtil::atan2(td, tb) - MathUtil::atan2(tc, ta); + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + float b = bone._b, d = bone._d; + r = MathUtil::atan2(d, b) + (r - MathUtil::Pi / 2 + offsetShearY) * shearMix; + float s = MathUtil::sqrt(b * b + d * d); + bone._b = MathUtil::cos(r) * s; + bone._d = MathUtil::sin(r) * s; + modified = true; + } + + if (modified) bone._appliedValid = false; + } +} + +void TransformConstraint::applyAbsoluteLocal() { + float rotateMix = _rotateMix, translateMix = _translateMix, scaleMix = _scaleMix, shearMix = _shearMix; + Bone &target = *_target; + if (!target._appliedValid) target.updateAppliedTransform(); + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + if (!bone._appliedValid) bone.updateAppliedTransform(); + + float rotation = bone._arotation; + if (rotateMix != 0) { + float r = target._arotation - rotation + _data._offsetRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + rotation += r * rotateMix; + } + + float x = bone._ax, y = bone._ay; + if (translateMix != 0) { + x += (target._ax - x + _data._offsetX) * translateMix; + y += (target._ay - y + _data._offsetY) * translateMix; + } + + float scaleX = bone._ascaleX, scaleY = bone._ascaleY; + if (scaleMix != 0) { + if (scaleX > 0.00001f) scaleX = (scaleX + (target._ascaleX - scaleX + _data._offsetScaleX) * scaleMix) / scaleX; + if (scaleY > 0.00001f) scaleY = (scaleY + (target._ascaleY - scaleY + _data._offsetScaleY) * scaleMix) / scaleY; + } + + float shearY = bone._ashearY; + if (shearMix != 0) { + float r = target._ashearY - shearY + _data._offsetShearY; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone._shearY += r * shearMix; + } + + bone.updateWorldTransform(x, y, rotation, scaleX, scaleY, bone._ashearX, shearY); + } +} + +void TransformConstraint::applyRelativeLocal() { + float rotateMix = _rotateMix, translateMix = _translateMix, scaleMix = _scaleMix, shearMix = _shearMix; + Bone &target = *_target; + if (!target._appliedValid) target.updateAppliedTransform(); + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + if (!bone._appliedValid) bone.updateAppliedTransform(); + + float rotation = bone._arotation; + if (rotateMix != 0) rotation += (target._arotation + _data._offsetRotation) * rotateMix; + + float x = bone._ax, y = bone._ay; + if (translateMix != 0) { + x += (target._ax + _data._offsetX) * translateMix; + y += (target._ay + _data._offsetY) * translateMix; + } + + float scaleX = bone._ascaleX, scaleY = bone._ascaleY; + if (scaleMix != 0) { + if (scaleX > 0.00001f) scaleX *= ((target._ascaleX - 1 + _data._offsetScaleX) * scaleMix) + 1; + if (scaleY > 0.00001f) scaleY *= ((target._ascaleY - 1 + _data._offsetScaleY) * scaleMix) + 1; + } + + float shearY = bone._ashearY; + if (shearMix != 0) shearY += (target._ashearY + _data._offsetShearY) * shearMix; + + bone.updateWorldTransform(x, y, rotation, scaleX, scaleY, bone._ashearX, shearY); + } +} + +bool TransformConstraint::isActive() { + return _active; +} + +void TransformConstraint::setActive(bool inValue) { + _active = inValue; +} diff --git a/cocos/editor-support/spine/TransformConstraint.h b/cocos/editor-support/spine/TransformConstraint.h new file mode 100644 index 0000000..625dc8e --- /dev/null +++ b/cocos/editor-support/spine/TransformConstraint.h @@ -0,0 +1,97 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TransformConstraint_h +#define Spine_TransformConstraint_h + +#include + +#include + +namespace spine { +class TransformConstraintData; +class Skeleton; +class Bone; + +class SP_API TransformConstraint : public Updatable { + friend class Skeleton; + friend class TransformConstraintTimeline; + + RTTI_DECL + +public: + TransformConstraint(TransformConstraintData& data, Skeleton& skeleton); + + void apply(); + + virtual void update(); + + virtual int getOrder(); + + TransformConstraintData& getData(); + + Vector& getBones(); + + Bone* getTarget(); + void setTarget(Bone* inValue); + + float getRotateMix(); + void setRotateMix(float inValue); + + float getTranslateMix(); + void setTranslateMix(float inValue); + + float getScaleMix(); + void setScaleMix(float inValue); + + float getShearMix(); + void setShearMix(float inValue); + + bool isActive(); + + void setActive(bool inValue); + +private: + TransformConstraintData& _data; + Vector _bones; + Bone* _target; + float _rotateMix, _translateMix, _scaleMix, _shearMix; + bool _active; + + void applyAbsoluteWorld(); + + void applyRelativeWorld(); + + void applyAbsoluteLocal(); + + void applyRelativeLocal(); +}; +} // namespace spine + +#endif /* Spine_TransformConstraint_h */ diff --git a/cocos/editor-support/spine/TransformConstraintData.cpp b/cocos/editor-support/spine/TransformConstraintData.cpp new file mode 100644 index 0000000..5da407b --- /dev/null +++ b/cocos/editor-support/spine/TransformConstraintData.cpp @@ -0,0 +1,111 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +#include + +using namespace spine; +TransformConstraintData::TransformConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _rotateMix(0), + _translateMix(0), + _scaleMix(0), + _shearMix(0), + _offsetRotation(0), + _offsetX(0), + _offsetY(0), + _offsetScaleX(0), + _offsetScaleY(0), + _offsetShearY(0), + _relative(false), + _local(false) { +} + +Vector &TransformConstraintData::getBones() { + return _bones; +} + +BoneData *TransformConstraintData::getTarget() { + return _target; +} + +float TransformConstraintData::getRotateMix() { + return _rotateMix; +} + +float TransformConstraintData::getTranslateMix() { + return _translateMix; +} + +float TransformConstraintData::getScaleMix() { + return _scaleMix; +} + +float TransformConstraintData::getShearMix() { + return _shearMix; +} + +float TransformConstraintData::getOffsetRotation() { + return _offsetRotation; +} + +float TransformConstraintData::getOffsetX() { + return _offsetX; +} + +float TransformConstraintData::getOffsetY() { + return _offsetY; +} + +float TransformConstraintData::getOffsetScaleX() { + return _offsetScaleX; +} + +float TransformConstraintData::getOffsetScaleY() { + return _offsetScaleY; +} + +float TransformConstraintData::getOffsetShearY() { + return _offsetShearY; +} + +bool TransformConstraintData::isRelative() { + return _relative; +} + +bool TransformConstraintData::isLocal() { + return _local; +} diff --git a/cocos/editor-support/spine/TransformConstraintData.h b/cocos/editor-support/spine/TransformConstraintData.h new file mode 100644 index 0000000..8da5520 --- /dev/null +++ b/cocos/editor-support/spine/TransformConstraintData.h @@ -0,0 +1,78 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TransformConstraintData_h +#define Spine_TransformConstraintData_h + +#include +#include +#include +#include + +namespace spine { +class BoneData; + +class SP_API TransformConstraintData : public ConstraintData { + friend class SkeletonBinary; + friend class SkeletonJson; + + friend class TransformConstraint; + friend class Skeleton; + friend class TransformConstraintTimeline; + +public: + explicit TransformConstraintData(const String& name); + + Vector& getBones(); + BoneData* getTarget(); + float getRotateMix(); + float getTranslateMix(); + float getScaleMix(); + float getShearMix(); + + float getOffsetRotation(); + float getOffsetX(); + float getOffsetY(); + float getOffsetScaleX(); + float getOffsetScaleY(); + float getOffsetShearY(); + + bool isRelative(); + bool isLocal(); + +private: + Vector _bones; + BoneData* _target; + float _rotateMix, _translateMix, _scaleMix, _shearMix; + float _offsetRotation, _offsetX, _offsetY, _offsetScaleX, _offsetScaleY, _offsetShearY; + bool _relative, _local; +}; +} // namespace spine + +#endif /* Spine_TransformConstraintData_h */ diff --git a/cocos/editor-support/spine/TransformConstraintTimeline.cpp b/cocos/editor-support/spine/TransformConstraintTimeline.cpp new file mode 100644 index 0000000..621cf0a --- /dev/null +++ b/cocos/editor-support/spine/TransformConstraintTimeline.cpp @@ -0,0 +1,146 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraintTimeline, CurveTimeline) + +const int TransformConstraintTimeline::ENTRIES = 5; +const int TransformConstraintTimeline::PREV_TIME = -5; +const int TransformConstraintTimeline::PREV_ROTATE = -4; +const int TransformConstraintTimeline::PREV_TRANSLATE = -3; +const int TransformConstraintTimeline::PREV_SCALE = -2; +const int TransformConstraintTimeline::PREV_SHEAR = -1; +const int TransformConstraintTimeline::ROTATE = 1; +const int TransformConstraintTimeline::TRANSLATE = 2; +const int TransformConstraintTimeline::SCALE = 3; +const int TransformConstraintTimeline::SHEAR = 4; + +TransformConstraintTimeline::TransformConstraintTimeline(int frameCount) : CurveTimeline(frameCount), + _transformConstraintIndex(0) { + _frames.setSize(frameCount * ENTRIES, 0); +} + +void TransformConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + TransformConstraint *constraintP = skeleton._transformConstraints[_transformConstraintIndex]; + TransformConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._rotateMix = constraint._data._rotateMix; + constraint._translateMix = constraint._data._translateMix; + constraint._scaleMix = constraint._data._scaleMix; + constraint._shearMix = constraint._data._shearMix; + return; + case MixBlend_First: + constraint._rotateMix += (constraint._data._rotateMix - constraint._rotateMix) * alpha; + constraint._translateMix += (constraint._data._translateMix - constraint._translateMix) * alpha; + constraint._scaleMix += (constraint._data._scaleMix - constraint._scaleMix) * alpha; + constraint._shearMix += (constraint._data._shearMix - constraint._shearMix) * alpha; + return; + default: + return; + } + } + + float rotate, translate, scale, shear; + if (time >= _frames[_frames.size() - ENTRIES]) { + // Time is after last frame. + size_t i = _frames.size(); + rotate = _frames[i + PREV_ROTATE]; + translate = _frames[i + PREV_TRANSLATE]; + scale = _frames[i + PREV_SCALE]; + shear = _frames[i + PREV_SHEAR]; + } else { + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(_frames, time, ENTRIES); + rotate = _frames[frame + PREV_ROTATE]; + translate = _frames[frame + PREV_TRANSLATE]; + scale = _frames[frame + PREV_SCALE]; + shear = _frames[frame + PREV_SHEAR]; + float frameTime = _frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + + rotate += (_frames[frame + ROTATE] - rotate) * percent; + translate += (_frames[frame + TRANSLATE] - translate) * percent; + scale += (_frames[frame + SCALE] - scale) * percent; + shear += (_frames[frame + SHEAR] - shear) * percent; + } + + if (blend == MixBlend_Setup) { + TransformConstraintData &data = constraint._data; + constraint._rotateMix = data._rotateMix + (rotate - data._rotateMix) * alpha; + constraint._translateMix = data._translateMix + (translate - data._translateMix) * alpha; + constraint._scaleMix = data._scaleMix + (scale - data._scaleMix) * alpha; + constraint._shearMix = data._shearMix + (shear - data._shearMix) * alpha; + } else { + constraint._rotateMix += (rotate - constraint._rotateMix) * alpha; + constraint._translateMix += (translate - constraint._translateMix) * alpha; + constraint._scaleMix += (scale - constraint._scaleMix) * alpha; + constraint._shearMix += (shear - constraint._shearMix) * alpha; + } +} + +int TransformConstraintTimeline::getPropertyId() { + return ((int)TimelineType_TransformConstraint << 24) + _transformConstraintIndex; +} + +void TransformConstraintTimeline::setFrame(size_t frameIndex, float time, float rotateMix, float translateMix, float scaleMix, + float shearMix) { + frameIndex *= ENTRIES; + _frames[frameIndex] = time; + _frames[frameIndex + ROTATE] = rotateMix; + _frames[frameIndex + TRANSLATE] = translateMix; + _frames[frameIndex + SCALE] = scaleMix; + _frames[frameIndex + SHEAR] = shearMix; +} diff --git a/cocos/editor-support/spine/TransformConstraintTimeline.h b/cocos/editor-support/spine/TransformConstraintTimeline.h new file mode 100644 index 0000000..5e3047a --- /dev/null +++ b/cocos/editor-support/spine/TransformConstraintTimeline.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TransformConstraintTimeline_h +#define Spine_TransformConstraintTimeline_h + +#include + +namespace spine { + +class SP_API TransformConstraintTimeline : public CurveTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + static const int ENTRIES; + + explicit TransformConstraintTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + + void setFrame(size_t frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix); + +private: + static const int PREV_TIME; + static const int PREV_ROTATE; + static const int PREV_TRANSLATE; + static const int PREV_SCALE; + static const int PREV_SHEAR; + static const int ROTATE; + static const int TRANSLATE; + static const int SCALE; + static const int SHEAR; + + Vector _frames; + int _transformConstraintIndex; +}; +} // namespace spine + +#endif /* Spine_TransformConstraintTimeline_h */ diff --git a/cocos/editor-support/spine/TransformMode.h b/cocos/editor-support/spine/TransformMode.h new file mode 100644 index 0000000..c827b4e --- /dev/null +++ b/cocos/editor-support/spine/TransformMode.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TransformMode_h +#define Spine_TransformMode_h + +namespace spine { +enum TransformMode { + TransformMode_Normal = 0, + TransformMode_OnlyTranslation, + TransformMode_NoRotationOrReflection, + TransformMode_NoScale, + TransformMode_NoScaleOrReflection +}; +} + +#endif /* Spine_TransformMode_h */ diff --git a/cocos/editor-support/spine/TranslateTimeline.cpp b/cocos/editor-support/spine/TranslateTimeline.cpp new file mode 100644 index 0000000..cd1267b --- /dev/null +++ b/cocos/editor-support/spine/TranslateTimeline.cpp @@ -0,0 +1,131 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(TranslateTimeline, CurveTimeline) + +const int TranslateTimeline::ENTRIES = 3; +const int TranslateTimeline::PREV_TIME = -3; +const int TranslateTimeline::PREV_X = -2; +const int TranslateTimeline::PREV_Y = -1; +const int TranslateTimeline::X = 1; +const int TranslateTimeline::Y = 2; + +TranslateTimeline::TranslateTimeline(int frameCount) : CurveTimeline(frameCount), _boneIndex(0) { + _frames.ensureCapacity(frameCount * ENTRIES); + _frames.setSize(frameCount * ENTRIES, 0); +} + +TranslateTimeline::~TranslateTimeline() { +} + +void TranslateTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *boneP = skeleton._bones[_boneIndex]; + Bone &bone = *boneP; + if (!bone._active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone._x = bone._data._x; + bone._y = bone._data._y; + return; + case MixBlend_First: + bone._x += (bone._data._x - bone._x) * alpha; + bone._y += (bone._data._y - bone._y) * alpha; + default: { + } + } + return; + } + + float x, y; + if (time >= _frames[_frames.size() - ENTRIES]) { + // Time is after last frame. + x = _frames[_frames.size() + PREV_X]; + y = _frames[_frames.size() + PREV_Y]; + } else { + // Interpolate between the previous frame and the current frame. + int frame = Animation::binarySearch(_frames, time, ENTRIES); + x = _frames[frame + PREV_X]; + y = _frames[frame + PREV_Y]; + float frameTime = _frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + + x += (_frames[frame + X] - x) * percent; + y += (_frames[frame + Y] - y) * percent; + } + + switch (blend) { + case MixBlend_Setup: + bone._x = bone._data._x + x * alpha; + bone._y = bone._data._y + y * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bone._x += (bone._data._x + x - bone._x) * alpha; + bone._y += (bone._data._y + y - bone._y) * alpha; + break; + case MixBlend_Add: + bone._x += x * alpha; + bone._y += y * alpha; + } +} + +int TranslateTimeline::getPropertyId() { + return ((int)TimelineType_Translate << 24) + _boneIndex; +} + +void TranslateTimeline::setFrame(int frameIndex, float time, float x, float y) { + frameIndex *= ENTRIES; + _frames[frameIndex] = time; + _frames[frameIndex + X] = x; + _frames[frameIndex + Y] = y; +} diff --git a/cocos/editor-support/spine/TranslateTimeline.h b/cocos/editor-support/spine/TranslateTimeline.h new file mode 100644 index 0000000..68b8102 --- /dev/null +++ b/cocos/editor-support/spine/TranslateTimeline.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TranslateTimeline_h +#define Spine_TranslateTimeline_h + +#include + +#include +#include + +namespace spine { + +class SP_API TranslateTimeline : public CurveTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + static const int ENTRIES; + + explicit TranslateTimeline(int frameCount); + + virtual ~TranslateTimeline(); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frameIndex, float time, float x, float y); + +protected: + static const int PREV_TIME; + static const int PREV_X; + static const int PREV_Y; + static const int X; + static const int Y; + + Vector _frames; + int _boneIndex; +}; +} // namespace spine + +#endif /* Spine_TranslateTimeline_h */ diff --git a/cocos/editor-support/spine/Triangulator.cpp b/cocos/editor-support/spine/Triangulator.cpp new file mode 100644 index 0000000..291f731 --- /dev/null +++ b/cocos/editor-support/spine/Triangulator.cpp @@ -0,0 +1,289 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +using namespace spine; + +Triangulator::~Triangulator() { + ContainerUtil::cleanUpVectorOfPointers(_convexPolygons); + ContainerUtil::cleanUpVectorOfPointers(_convexPolygonsIndices); +} + +Vector &Triangulator::triangulate(Vector &vertices) { + size_t vertexCount = vertices.size() >> 1; + + Vector &indices = _indices; + indices.clear(); + indices.ensureCapacity(vertexCount); + indices.setSize(vertexCount, 0); + for (size_t i = 0; i < vertexCount; ++i) { + indices[i] = i; + } + + Vector &isConcaveArray = _isConcaveArray; + isConcaveArray.ensureCapacity(vertexCount); + isConcaveArray.setSize(vertexCount, 0); + for (size_t i = 0, n = vertexCount; i < n; ++i) { + isConcaveArray[i] = isConcave(i, vertexCount, vertices, indices); + } + + Vector &triangles = _triangles; + triangles.clear(); + triangles.ensureCapacity(MathUtil::max((int)0, (int)vertexCount - 2) << 2); + + while (vertexCount > 3) { + // Find ear tip. + size_t previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) { + if (!isConcaveArray[i]) { + int p1 = indices[previous] << 1, p2 = indices[i] << 1, p3 = indices[next] << 1; + float p1x = vertices[p1], p1y = vertices[p1 + 1]; + float p2x = vertices[p2], p2y = vertices[p2 + 1]; + float p3x = vertices[p3], p3y = vertices[p3 + 1]; + for (size_t ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { + if (!isConcaveArray[ii]) continue; + + int v = indices[ii] << 1; + float &vx = vertices[v], vy = vertices[v + 1]; + if (positiveArea(p3x, p3y, p1x, p1y, vx, vy)) { + if (positiveArea(p1x, p1y, p2x, p2y, vx, vy)) { + if (positiveArea(p2x, p2y, p3x, p3y, vx, vy)) { + goto break_outer; // break outer; + } + } + } + } + break; + } + break_outer: + + if (next == 0) { + do { + if (!isConcaveArray[i]) break; + i--; + } while (i > 0); + break; + } + + previous = i; + i = next; + next = (next + 1) % vertexCount; + } + + // Cut ear tip. + triangles.add(indices[(vertexCount + i - 1) % vertexCount]); + triangles.add(indices[i]); + triangles.add(indices[(i + 1) % vertexCount]); + indices.removeAt(i); + isConcaveArray.removeAt(i); + vertexCount--; + + int previousIndex = (vertexCount + i - 1) % vertexCount; + int nextIndex = i == vertexCount ? 0 : i; + isConcaveArray[previousIndex] = isConcave(previousIndex, vertexCount, vertices, indices); + isConcaveArray[nextIndex] = isConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) { + triangles.add(indices[2]); + triangles.add(indices[0]); + triangles.add(indices[1]); + } + + return triangles; +} + +Vector *> &Triangulator::decompose(Vector &vertices, Vector &triangles) { + Vector *> &convexPolygons = _convexPolygons; + for (size_t i = 0, n = convexPolygons.size(); i < n; ++i) + _polygonPool.free(convexPolygons[i]); + convexPolygons.clear(); + + Vector *> &convexPolygonsIndices = _convexPolygonsIndices; + for (size_t i = 0, n = convexPolygonsIndices.size(); i < n; ++i) + _polygonIndicesPool.free(convexPolygonsIndices[i]); + convexPolygonsIndices.clear(); + + Vector *polygonIndices = _polygonIndicesPool.obtain(); + polygonIndices->clear(); + + Vector *polygon = _polygonPool.obtain(); + polygon->clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastwinding = 0; + for (size_t i = 0, n = triangles.size(); i < n; i += 3) { + int t1 = triangles[i] << 1, t2 = triangles[i + 1] << 1, t3 = triangles[i + 2] << 1; + float x1 = vertices[t1], y1 = vertices[t1 + 1]; + float x2 = vertices[t2], y2 = vertices[t2 + 1]; + float x3 = vertices[t3], y3 = vertices[t3 + 1]; + + // If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan). + bool merged = false; + if (fanBaseIndex == t1) { + size_t o = polygon->size() - 4; + Vector &p = *polygon; + int winding1 = winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3); + int winding2 = winding(x3, y3, p[0], p[1], p[2], p[3]); + if (winding1 == lastwinding && winding2 == lastwinding) { + polygon->add(x3); + polygon->add(y3); + polygonIndices->add(t3); + merged = true; + } + } + + // Otherwise make this triangle the new base. + if (!merged) { + if (polygon->size() > 0) { + convexPolygons.add(polygon); + convexPolygonsIndices.add(polygonIndices); + } else { + _polygonPool.free(polygon); + _polygonIndicesPool.free(polygonIndices); + } + + polygon = _polygonPool.obtain(); + polygon->clear(); + polygon->add(x1); + polygon->add(y1); + polygon->add(x2); + polygon->add(y2); + polygon->add(x3); + polygon->add(y3); + polygonIndices = _polygonIndicesPool.obtain(); + polygonIndices->clear(); + polygonIndices->add(t1); + polygonIndices->add(t2); + polygonIndices->add(t3); + lastwinding = winding(x1, y1, x2, y2, x3, y3); + fanBaseIndex = t1; + } + } + + if (polygon->size() > 0) { + convexPolygons.add(polygon); + convexPolygonsIndices.add(polygonIndices); + } + + // Go through the list of polygons and try to merge the remaining triangles with the found triangle fans. + for (size_t i = 0, n = convexPolygons.size(); i < n; ++i) { + polygonIndices = convexPolygonsIndices[i]; + + if (polygonIndices->size() == 0) continue; + int firstIndex = (*polygonIndices)[0]; + int lastIndex = (*polygonIndices)[polygonIndices->size() - 1]; + + polygon = convexPolygons[i]; + size_t o = polygon->size() - 4; + Vector &p = *polygon; + float prevPrevX = p[o], prevPrevY = p[o + 1]; + float prevX = p[o + 2], prevY = p[o + 3]; + float firstX = p[0], firstY = p[1]; + float secondX = p[2], secondY = p[3]; + int winding0 = winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (size_t ii = 0; ii < n; ++ii) { + if (ii == i) continue; + + Vector *otherIndicesP = convexPolygonsIndices[ii]; + Vector &otherIndices = *otherIndicesP; + + if (otherIndices.size() != 3) continue; + + int otherFirstIndex = otherIndices[0]; + int otherSecondIndex = otherIndices[1]; + int otherLastIndex = otherIndices[2]; + + Vector *otherPolyP = convexPolygons[ii]; + Vector &otherPoly = *otherPolyP; + + float x3 = otherPoly[otherPoly.size() - 2], y3 = otherPoly[otherPoly.size() - 1]; + + if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue; + + int winding1 = winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3); + int winding2 = winding(x3, y3, firstX, firstY, secondX, secondY); + if (winding1 == winding0 && winding2 == winding0) { + otherPoly.clear(); + otherIndices.clear(); + polygon->add(x3); + polygon->add(y3); + polygonIndices->add(otherLastIndex); + prevPrevX = prevX; + prevPrevY = prevY; + prevX = x3; + prevY = y3; + ii = 0; + } + } + } + + // Remove empty polygons that resulted from the merge step above. + for (int i = (int)convexPolygons.size() - 1; i >= 0; --i) { + polygon = convexPolygons[i]; + if (polygon->size() == 0) { + convexPolygons.removeAt(i); + _polygonPool.free(polygon); + polygonIndices = convexPolygonsIndices[i]; + convexPolygonsIndices.removeAt(i); + _polygonIndicesPool.free(polygonIndices); + } + } + + return convexPolygons; +} + +bool Triangulator::isConcave(int index, int vertexCount, Vector &vertices, Vector &indices) { + int previous = indices[(vertexCount + index - 1) % vertexCount] << 1; + int current = indices[index] << 1; + int next = indices[(index + 1) % vertexCount] << 1; + + return !positiveArea(vertices[previous], vertices[previous + 1], + vertices[current], vertices[current + 1], + vertices[next], vertices[next + 1]); +} + +bool Triangulator::positiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; +} + +int Triangulator::winding(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { + float px = p2x - p1x, py = p2y - p1y; + return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1; +} diff --git a/cocos/editor-support/spine/Triangulator.h b/cocos/editor-support/spine/Triangulator.h new file mode 100644 index 0000000..f34b8c7 --- /dev/null +++ b/cocos/editor-support/spine/Triangulator.h @@ -0,0 +1,64 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Triangulator_h +#define Spine_Triangulator_h + +#include +#include + +namespace spine { +class SP_API Triangulator : public SpineObject { +public: + ~Triangulator(); + + Vector &triangulate(Vector &vertices); + + Vector *> &decompose(Vector &vertices, Vector &triangles); + +private: + Vector *> _convexPolygons; + Vector *> _convexPolygonsIndices; + + Vector _indices; + Vector _isConcaveArray; + Vector _triangles; + + Pool > _polygonPool; + Pool > _polygonIndicesPool; + + static bool isConcave(int index, int vertexCount, Vector &vertices, Vector &indices); + + static bool positiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y); + + static int winding(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y); +}; +} // namespace spine + +#endif /* Spine_Triangulator_h */ diff --git a/cocos/editor-support/spine/TwoColorTimeline.cpp b/cocos/editor-support/spine/TwoColorTimeline.cpp new file mode 100644 index 0000000..e2eb7bc --- /dev/null +++ b/cocos/editor-support/spine/TwoColorTimeline.cpp @@ -0,0 +1,181 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(TwoColorTimeline, CurveTimeline) + +const int TwoColorTimeline::ENTRIES = 8; +const int TwoColorTimeline::PREV_TIME = -8; +const int TwoColorTimeline::PREV_R = -7; +const int TwoColorTimeline::PREV_G = -6; +const int TwoColorTimeline::PREV_B = -5; +const int TwoColorTimeline::PREV_A = -4; +const int TwoColorTimeline::PREV_R2 = -3; +const int TwoColorTimeline::PREV_G2 = -2; +const int TwoColorTimeline::PREV_B2 = -1; +const int TwoColorTimeline::R = 1; +const int TwoColorTimeline::G = 2; +const int TwoColorTimeline::B = 3; +const int TwoColorTimeline::A = 4; +const int TwoColorTimeline::R2 = 5; +const int TwoColorTimeline::G2 = 6; +const int TwoColorTimeline::B2 = 7; + +TwoColorTimeline::TwoColorTimeline(int frameCount) : CurveTimeline(frameCount), _slotIndex(0) { + _frames.ensureCapacity(frameCount * ENTRIES); + _frames.setSize(frameCount * ENTRIES, 0); +} + +void TwoColorTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slotP = skeleton._slots[_slotIndex]; + Slot &slot = *slotP; + if (!slot._bone.isActive()) return; + + if (time < _frames[0]) { + // Time is before first frame. + switch (blend) { + case MixBlend_Setup: + slot.getColor().set(slot.getData().getColor()); + slot.getDarkColor().set(slot.getData().getDarkColor()); + return; + case MixBlend_First: { + Color &color = slot.getColor(); + color.r += (color.r - slot._data.getColor().r) * alpha; + color.g += (color.g - slot._data.getColor().g) * alpha; + color.b += (color.b - slot._data.getColor().b) * alpha; + color.a += (color.a - slot._data.getColor().a) * alpha; + + Color &darkColor = slot.getDarkColor(); + darkColor.r += (darkColor.r - slot._data.getDarkColor().r) * alpha; + darkColor.g += (darkColor.g - slot._data.getDarkColor().g) * alpha; + darkColor.b += (darkColor.b - slot._data.getDarkColor().b) * alpha; + return; + } + default: + return; + } + } + + float r, g, b, a, r2, g2, b2; + if (time >= _frames[_frames.size() - ENTRIES]) { + // Time is after last frame. + size_t i = _frames.size(); + r = _frames[i + PREV_R]; + g = _frames[i + PREV_G]; + b = _frames[i + PREV_B]; + a = _frames[i + PREV_A]; + r2 = _frames[i + PREV_R2]; + g2 = _frames[i + PREV_G2]; + b2 = _frames[i + PREV_B2]; + } else { + // Interpolate between the previous frame and the current frame. + size_t frame = (size_t)Animation::binarySearch(_frames, time, ENTRIES); + r = _frames[frame + PREV_R]; + g = _frames[frame + PREV_G]; + b = _frames[frame + PREV_B]; + a = _frames[frame + PREV_A]; + r2 = _frames[frame + PREV_R2]; + g2 = _frames[frame + PREV_G2]; + b2 = _frames[frame + PREV_B2]; + float frameTime = _frames[frame]; + float percent = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (_frames[frame + PREV_TIME] - frameTime)); + + r += (_frames[frame + R] - r) * percent; + g += (_frames[frame + G] - g) * percent; + b += (_frames[frame + B] - b) * percent; + a += (_frames[frame + A] - a) * percent; + r2 += (_frames[frame + R2] - r2) * percent; + g2 += (_frames[frame + G2] - g2) * percent; + b2 += (_frames[frame + B2] - b2) * percent; + } + + if (alpha == 1) { + Color &color = slot.getColor(); + color.set(r, g, b, a); + + Color &darkColor = slot.getDarkColor(); + darkColor.set(r2, g2, b2, 1); + } else { + Color &light = slot._color; + Color &dark = slot._darkColor; + if (blend == MixBlend_Setup) { + light.set(slot._data._color); + dark.set(slot._data._darkColor); + } + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha); + dark.add((r2 - dark.r) * alpha, (g2 - dark.g) * alpha, (b2 - dark.b) * alpha, 0); + } +} + +int TwoColorTimeline::getPropertyId() { + return ((int)TimelineType_TwoColor << 24) + _slotIndex; +} + +void TwoColorTimeline::setFrame(int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) { + frameIndex *= ENTRIES; + _frames[frameIndex] = time; + _frames[frameIndex + R] = r; + _frames[frameIndex + G] = g; + _frames[frameIndex + B] = b; + _frames[frameIndex + A] = a; + _frames[frameIndex + R2] = r2; + _frames[frameIndex + G2] = g2; + _frames[frameIndex + B2] = b2; +} + +int TwoColorTimeline::getSlotIndex() { + return _slotIndex; +} + +void TwoColorTimeline::setSlotIndex(int inValue) { + assert(inValue >= 0); + _slotIndex = inValue; +} diff --git a/cocos/editor-support/spine/TwoColorTimeline.h b/cocos/editor-support/spine/TwoColorTimeline.h new file mode 100644 index 0000000..ee8dcc9 --- /dev/null +++ b/cocos/editor-support/spine/TwoColorTimeline.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TwoColorTimeline_h +#define Spine_TwoColorTimeline_h + +#include + +namespace spine { + +class SP_API TwoColorTimeline : public CurveTimeline { + friend class SkeletonBinary; + friend class SkeletonJson; + + RTTI_DECL + +public: + static const int ENTRIES; + + explicit TwoColorTimeline(int frameCount); + + virtual void apply(Skeleton& skeleton, float lastTime, float time, Vector* pEvents, float alpha, MixBlend blend, MixDirection direction); + + virtual int getPropertyId(); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2); + + int getSlotIndex(); + void setSlotIndex(int inValue); + +private: + static const int PREV_TIME; + static const int PREV_R; + static const int PREV_G; + static const int PREV_B; + static const int PREV_A; + static const int PREV_R2; + static const int PREV_G2; + static const int PREV_B2; + static const int R; + static const int G; + static const int B; + static const int A; + static const int R2; + static const int G2; + static const int B2; + + Vector _frames; // time, r, g, b, a, r2, g2, b2, ... + int _slotIndex; +}; +} // namespace spine + +#endif /* Spine_TwoColorTimeline_h */ diff --git a/cocos/editor-support/spine/Updatable.cpp b/cocos/editor-support/spine/Updatable.cpp new file mode 100644 index 0000000..2aae065 --- /dev/null +++ b/cocos/editor-support/spine/Updatable.cpp @@ -0,0 +1,44 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(Updatable) + +Updatable::Updatable() { +} + +Updatable::~Updatable() { +} diff --git a/cocos/editor-support/spine/Updatable.h b/cocos/editor-support/spine/Updatable.h new file mode 100644 index 0000000..98d8cd0 --- /dev/null +++ b/cocos/editor-support/spine/Updatable.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Updatable_h +#define Spine_Updatable_h + +#include +#include + +namespace spine { +class SP_API Updatable : public SpineObject { + RTTI_DECL + +public: + Updatable(); + + virtual ~Updatable(); + + virtual void update() = 0; + + virtual bool isActive() = 0; + + virtual void setActive(bool inValue) = 0; +}; +} // namespace spine + +#endif /* Spine_Updatable_h */ diff --git a/cocos/editor-support/spine/Vector.h b/cocos/editor-support/spine/Vector.h new file mode 100644 index 0000000..91ee93f --- /dev/null +++ b/cocos/editor-support/spine/Vector.h @@ -0,0 +1,222 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Vector_h +#define Spine_Vector_h + +#include +#include +#include +#include + +namespace spine { +template +class SP_API Vector : public SpineObject { +public: + Vector() : _size(0), _capacity(0), _buffer(NULL) { + } + + Vector(const Vector &inVector) : _size(inVector._size), _capacity(inVector._capacity), _buffer(NULL) { + if (_capacity > 0) { + _buffer = allocate(_capacity); + for (size_t i = 0; i < _size; ++i) { + construct(_buffer + i, inVector._buffer[i]); + } + } + } + + ~Vector() { + clear(); + deallocate(_buffer); + } + + inline void clear() { + for (size_t i = 0; i < _size; ++i) { + destroy(_buffer + (_size - 1 - i)); + } + + _size = 0; + } + + inline size_t getCapacity() const { + return _capacity; + } + + inline size_t size() const { + return _size; + } + + inline void setSize(size_t newSize, const T &defaultValue) { + assert(newSize >= 0); + size_t oldSize = _size; + _size = newSize; + if (_capacity < newSize) { + _capacity = (int)(_size * 1.75f); + if (_capacity < 8) _capacity = 8; + _buffer = spine::SpineExtension::realloc(_buffer, _capacity, __FILE__, __LINE__); + } + if (oldSize < _size) { + for (size_t i = oldSize; i < _size; i++) { + construct(_buffer + i, defaultValue); + } + } + } + + inline void ensureCapacity(size_t newCapacity = 0) { + if (_capacity >= newCapacity) return; + _capacity = newCapacity; + _buffer = SpineExtension::realloc(_buffer, newCapacity, __FILE__, __LINE__); + } + + inline void add(const T &inValue) { + if (_size == _capacity) { + // inValue might reference an element in this buffer + // When we reallocate, the reference becomes invalid. + // We thus need to create a defensive copy before + // reallocating. + T valueCopy = inValue; + _capacity = (int)(_size * 1.75f); + if (_capacity < 8) _capacity = 8; + _buffer = spine::SpineExtension::realloc(_buffer, _capacity, __FILE__, __LINE__); + construct(_buffer + _size++, valueCopy); + } else { + construct(_buffer + _size++, inValue); + } + } + + inline void addAll(Vector &inValue) { + ensureCapacity(this->size() + inValue.size()); + for (size_t i = 0; i < inValue.size(); i++) { + add(inValue[i]); + } + } + + inline void clearAndAddAll(Vector &inValue) { + this->clear(); + this->addAll(inValue); + } + + inline void removeAt(size_t inIndex) { + assert(inIndex < _size); + + --_size; + + if (inIndex != _size) { + for (size_t i = inIndex; i < _size; ++i) { + T tmp(_buffer[i]); + _buffer[i] = _buffer[i + 1]; + _buffer[i + 1] = tmp; + } + } + + destroy(_buffer + _size); + } + + inline bool contains(const T &inValue) { + for (size_t i = 0; i < _size; ++i) { + if (_buffer[i] == inValue) { + return true; + } + } + + return false; + } + + inline int indexOf(const T &inValue) { + for (size_t i = 0; i < _size; ++i) { + if (_buffer[i] == inValue) { + return (int)i; + } + } + + return -1; + } + + inline T &operator[](size_t inIndex) { + assert(inIndex < _size); + + return _buffer[inIndex]; + } + + inline friend bool operator==(Vector &lhs, Vector &rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + + for (size_t i = 0, n = lhs.size(); i < n; ++i) { + if (lhs[i] != rhs[i]) { + return false; + } + } + + return true; + } + + inline friend bool operator!=(Vector &lhs, Vector &rhs) { + return !(lhs == rhs); + } + + inline T *buffer() { + return _buffer; + } + +private: + size_t _size; + size_t _capacity; + T *_buffer; + + inline T *allocate(size_t n) { + assert(n > 0); + + T *ptr = SpineExtension::calloc(n, __FILE__, __LINE__); + + assert(ptr); + + return ptr; + } + + inline void deallocate(T *buffer) { + if (_buffer) { + SpineExtension::free(buffer, __FILE__, __LINE__); + } + } + + inline void construct(T *buffer, const T &val) { + new (buffer) T(val); + } + + inline void destroy(T *buffer) { + buffer->~T(); + } + + // Vector &operator=(const Vector &inVector) {}; +}; +} // namespace spine + +#endif /* Spine_Vector_h */ diff --git a/cocos/editor-support/spine/VertexAttachment.cpp b/cocos/editor-support/spine/VertexAttachment.cpp new file mode 100644 index 0000000..58a0979 --- /dev/null +++ b/cocos/editor-support/spine/VertexAttachment.cpp @@ -0,0 +1,169 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include + +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(VertexAttachment, Attachment) + +VertexAttachment::VertexAttachment(const String &name) : Attachment(name), _worldVerticesLength(0), _deformAttachment(this), _id(getNextID()) { +} + +VertexAttachment::~VertexAttachment() { +} + +void VertexAttachment::computeWorldVertices(Slot &slot, Vector &worldVertices) { + computeWorldVertices(slot, 0, _worldVerticesLength, worldVertices, 0); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, float *worldVertices) { + computeWorldVertices(slot, 0, _worldVerticesLength, worldVertices, 0); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, Vector &worldVertices, size_t offset, size_t stride) { + computeWorldVertices(slot, start, count, worldVertices.buffer(), offset, stride); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, size_t stride) { + count = offset + (count >> 1) * stride; + Skeleton &skeleton = slot._bone._skeleton; + Vector *deformArray = &slot.getDeform(); + Vector *vertices = &_vertices; + Vector &bones = _bones; + if (bones.size() == 0) { + if (deformArray->size() > 0) vertices = deformArray; + + Bone &bone = slot._bone; + float x = bone._worldX; + float y = bone._worldY; + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + for (size_t vv = start, w = offset; w < count; vv += 2, w += stride) { + float vx = (*vertices)[vv]; + float vy = (*vertices)[vv + 1]; + worldVertices[w] = vx * a + vy * b + x; + worldVertices[w + 1] = vx * c + vy * d + y; + } + return; + } + + int v = 0, skip = 0; + for (size_t i = 0; i < start; i += 2) { + int n = bones[v]; + v += n + 1; + skip += n; + } + + Vector &skeletonBones = skeleton.getBones(); + if (deformArray->size() == 0) { + for (size_t w = offset, b = skip * 3; w < count; w += stride) { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) { + Bone *boneP = skeletonBones[bones[v]]; + Bone &bone = *boneP; + float vx = (*vertices)[b]; + float vy = (*vertices)[b + 1]; + float weight = (*vertices)[b + 2]; + wx += (vx * bone._a + vy * bone._b + bone._worldX) * weight; + wy += (vx * bone._c + vy * bone._d + bone._worldY) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } else { + for (size_t w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) { + Bone *boneP = skeletonBones[bones[v]]; + Bone &bone = *boneP; + float vx = (*vertices)[b] + (*deformArray)[f]; + float vy = (*vertices)[b + 1] + (*deformArray)[f + 1]; + float weight = (*vertices)[b + 2]; + wx += (vx * bone._a + vy * bone._b + bone._worldX) * weight; + wy += (vx * bone._c + vy * bone._d + bone._worldY) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } +} + +int VertexAttachment::getId() { + return _id; +} + +Vector &VertexAttachment::getBones() { + return _bones; +} + +Vector &VertexAttachment::getVertices() { + return _vertices; +} + +size_t VertexAttachment::getWorldVerticesLength() { + return _worldVerticesLength; +} + +void VertexAttachment::setWorldVerticesLength(size_t inValue) { + _worldVerticesLength = inValue; +} + +VertexAttachment *VertexAttachment::getDeformAttachment() { + return _deformAttachment; +} + +void VertexAttachment::setDeformAttachment(VertexAttachment *attachment) { + _deformAttachment = attachment; +} + +int VertexAttachment::getNextID() { + static int nextID = 0; + + return (nextID++ & 65535) << 11; +} + +void VertexAttachment::copyTo(VertexAttachment *other) { + other->_bones.clearAndAddAll(this->_bones); + other->_vertices.clearAndAddAll(this->_vertices); + other->_worldVerticesLength = this->_worldVerticesLength; + other->_deformAttachment = this->_deformAttachment; +} diff --git a/cocos/editor-support/spine/VertexAttachment.h b/cocos/editor-support/spine/VertexAttachment.h new file mode 100644 index 0000000..d522f4a --- /dev/null +++ b/cocos/editor-support/spine/VertexAttachment.h @@ -0,0 +1,93 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_VertexAttachment_h +#define Spine_VertexAttachment_h + +#include + +#include + +namespace spine { +class Slot; + +/// An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. +class SP_API VertexAttachment : public Attachment { + friend class SkeletonBinary; + friend class SkeletonJson; + friend class DeformTimeline; + + RTTI_DECL + +public: + explicit VertexAttachment(const String& name); + + virtual ~VertexAttachment(); + + void computeWorldVertices(Slot& slot, float* worldVertices); + void computeWorldVertices(Slot& slot, Vector& worldVertices); + + /// Transforms local vertices to world coordinates. + /// @param start The index of the first Vertices value to transform. Each vertex has 2 values, x and y. + /// @param count The number of world vertex values to output. Must be less than or equal to WorldVerticesLength - start. + /// @param worldVertices The output world vertices. Must have a length greater than or equal to offset + count. + /// @param offset The worldVertices index to begin writing values. + /// @param stride The number of worldVertices entries between the value pairs written. + void computeWorldVertices(Slot& slot, size_t start, size_t count, float* worldVertices, size_t offset, size_t stride = 2); + void computeWorldVertices(Slot& slot, size_t start, size_t count, Vector& worldVertices, size_t offset, size_t stride = 2); + + /// Gets a unique ID for this attachment. + int getId(); + + Vector& getBones(); + + Vector& getVertices(); + + size_t getWorldVerticesLength(); + void setWorldVerticesLength(size_t inValue); + + VertexAttachment* getDeformAttachment(); + void setDeformAttachment(VertexAttachment* attachment); + + void copyTo(VertexAttachment* other); + +protected: + Vector _bones; + Vector _vertices; + size_t _worldVerticesLength; + VertexAttachment* _deformAttachment; + +private: + const int _id; + + static int getNextID(); +}; +} // namespace spine + +#endif /* Spine_VertexAttachment_h */ diff --git a/cocos/editor-support/spine/VertexEffect.cpp b/cocos/editor-support/spine/VertexEffect.cpp new file mode 100644 index 0000000..12b4346 --- /dev/null +++ b/cocos/editor-support/spine/VertexEffect.cpp @@ -0,0 +1,157 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifdef SPINE_UE4 + #include "SpinePluginPrivatePCH.h" +#endif + +#include +#include +#include + +using namespace spine; + +JitterVertexEffect::JitterVertexEffect(float jitterX, float jitterY) : _jitterX(jitterX), _jitterY(jitterY) { +} + +void JitterVertexEffect::begin(Skeleton &skeleton) { + SP_UNUSED(skeleton); +} + +void JitterVertexEffect::transform(float &x, float &y) { + // SP_UNUSED(u); + // SP_UNUSED(v); + // SP_UNUSED(light); + // SP_UNUSED(dark); + float jitterX = _jitterX; + float jitterY = _jitterY; + x += MathUtil::randomTriangular(-jitterX, jitterX); + y += MathUtil::randomTriangular(-jitterX, jitterY); +} + +void JitterVertexEffect::end() { +} + +void JitterVertexEffect::setJitterX(float jitterX) { + _jitterX = jitterX; +} + +float JitterVertexEffect::getJitterX() { + return _jitterX; +} + +void JitterVertexEffect::setJitterY(float jitterY) { + _jitterY = jitterY; +} + +float JitterVertexEffect::getJitterY() { + return _jitterY; +} + +SwirlVertexEffect::SwirlVertexEffect(float radius, Interpolation &interpolation) : _centerX(0), + _centerY(0), + _radius(radius), + _angle(0), + _worldX(0), + _worldY(0), + _interpolation(interpolation) { +} + +void SwirlVertexEffect::begin(Skeleton &skeleton) { + _worldX = skeleton.getX() + _centerX; + _worldY = skeleton.getY() + _centerY; +} + +void SwirlVertexEffect::transform(float &positionX, float &positionY) { + // SP_UNUSED(u); + // SP_UNUSED(v); + // SP_UNUSED(light); + // SP_UNUSED(dark); + + float x = positionX - _worldX; + float y = positionY - _worldY; + float dist = (float)MathUtil::sqrt(x * x + y * y); + if (dist < _radius) { + float theta = _interpolation.interpolate(0, _angle, (_radius - dist) / _radius); + float cos = MathUtil::cos(theta), sin = MathUtil::sin(theta); + positionX = cos * x - sin * y + _worldX; + positionY = sin * x + cos * y + _worldY; + } +} + +void SwirlVertexEffect::end() { +} + +void SwirlVertexEffect::setCenterX(float centerX) { + _centerX = centerX; +} + +float SwirlVertexEffect::getCenterX() { + return _centerX; +} + +void SwirlVertexEffect::setCenterY(float centerY) { + _centerY = centerY; +} + +float SwirlVertexEffect::getCenterY() { + return _centerY; +} + +void SwirlVertexEffect::setRadius(float radius) { + _radius = radius; +} + +float SwirlVertexEffect::getRadius() { + return _radius; +} + +void SwirlVertexEffect::setAngle(float angle) { + _angle = angle * MathUtil::Deg_Rad; +} + +float SwirlVertexEffect::getAngle() { + return _angle; +} + +void SwirlVertexEffect::setWorldX(float worldX) { + _worldX = worldX; +} + +float SwirlVertexEffect::getWorldX() { + return _worldX; +} + +void SwirlVertexEffect::setWorldY(float worldY) { + _worldY = worldY; +} + +float SwirlVertexEffect::getWorldY() { + return _worldY; +} diff --git a/cocos/editor-support/spine/VertexEffect.h b/cocos/editor-support/spine/VertexEffect.h new file mode 100644 index 0000000..71a21d2 --- /dev/null +++ b/cocos/editor-support/spine/VertexEffect.h @@ -0,0 +1,105 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_VertexEffect_h +#define Spine_VertexEffect_h + +#include +#include + +namespace spine { + +class Skeleton; +class Color; + +class SP_API VertexEffect : public SpineObject { +public: + virtual void begin(Skeleton &skeleton) = 0; + virtual void transform(float &x, float &y) = 0; + virtual void end() = 0; +}; + +class SP_API JitterVertexEffect : public VertexEffect { +public: + JitterVertexEffect(float jitterX, float jitterY); + + void begin(Skeleton &skeleton); + void transform(float &x, float &y); + void end(); + + void setJitterX(float jitterX); + float getJitterX(); + + void setJitterY(float jitterY); + float getJitterY(); + +protected: + float _jitterX; + float _jitterY; +}; + +class SP_API SwirlVertexEffect : public VertexEffect { +public: + SwirlVertexEffect(float radius, Interpolation &interpolation); + + void begin(Skeleton &skeleton); + void transform(float &x, float &y); + void end(); + + void setCenterX(float centerX); + float getCenterX(); + + void setCenterY(float centerY); + float getCenterY(); + + void setRadius(float radius); + float getRadius(); + + void setAngle(float angle); + float getAngle(); + + void setWorldX(float worldX); + float getWorldX(); + + void setWorldY(float worldY); + float getWorldY(); + +protected: + float _centerX; + float _centerY; + float _radius; + float _angle; + float _worldX; + float _worldY; + + Interpolation &_interpolation; +}; +} // namespace spine + +#endif /* Spine_VertexEffect_h */ diff --git a/cocos/editor-support/spine/Vertices.h b/cocos/editor-support/spine/Vertices.h new file mode 100644 index 0000000..903f8d2 --- /dev/null +++ b/cocos/editor-support/spine/Vertices.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Vertices_h +#define Spine_Vertices_h + +#include + +namespace spine { +class SP_API Vertices : public SpineObject { +public: + Vector _bones; + Vector _vertices; +}; +} // namespace spine + +#endif /* Spine_Vertices_h */ diff --git a/cocos/editor-support/spine/dll.h b/cocos/editor-support/spine/dll.h new file mode 100644 index 0000000..5f500f5 --- /dev/null +++ b/cocos/editor-support/spine/dll.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_SHAREDLIB_H +#define SPINE_SHAREDLIB_H + +#ifdef _WIN32 + #define DLLIMPORT __declspec(dllimport) + #define DLLEXPORT __declspec(dllexport) +#else + #ifndef DLLIMPORT + #define DLLIMPORT + #endif + #ifndef DLLEXPORT + #define DLLEXPORT + #endif +#endif + +#ifdef SPINEPLUGIN_API + #define SP_API SPINEPLUGIN_API +#else + #define SP_API +#endif + +#endif /* SPINE_SHAREDLIB_H */ diff --git a/cocos/editor-support/spine/spine.h b/cocos/editor-support/spine/spine.h new file mode 100644 index 0000000..39d989b --- /dev/null +++ b/cocos/editor-support/spine/spine.h @@ -0,0 +1,112 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_SPINE_H_ +#define SPINE_SPINE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/cocos/engine/BaseEngine.cpp b/cocos/engine/BaseEngine.cpp new file mode 100644 index 0000000..ba0e329 --- /dev/null +++ b/cocos/engine/BaseEngine.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "cocos/engine/BaseEngine.h" +#include "cocos/engine/Engine.h" +#include "cocos/platform/BasePlatform.h" +#include "cocos/platform/interfaces/modules/ISystemWindow.h" + +namespace cc { + +// static +BaseEngine::Ptr BaseEngine::createEngine() { + return std::make_shared(); +} + +} // namespace cc diff --git a/cocos/engine/BaseEngine.h b/cocos/engine/BaseEngine.h new file mode 100644 index 0000000..9a7bc95 --- /dev/null +++ b/cocos/engine/BaseEngine.h @@ -0,0 +1,109 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +#include "base/Scheduler.h" +#include "base/TypeDef.h" +#include "core/event/EventTarget.h" +#include "platform/BasePlatform.h" + +namespace cc { + +class CC_DLL BaseEngine : public std::enable_shared_from_this { +public: + enum EngineStatus { + ON_START, + ON_PAUSE, + ON_RESUME, + ON_CLOSE, + UNKNOWN, + }; + ~BaseEngine() = default; + using Ptr = std::shared_ptr; + + IMPL_EVENT_TARGET(BaseEngine) + + DECLARE_TARGET_EVENT_BEGIN(BaseEngine) + TARGET_EVENT_ARG1(EngineStatusChange, EngineStatus) + DECLARE_TARGET_EVENT_END() + /** + @brief Get operating system interface template. + */ + template + T *getInterface() const { + BasePlatform *platform = BasePlatform::getPlatform(); + return platform->getInterface(); + } + /** + @brief Create default engine. + */ + static BaseEngine::Ptr createEngine(); + + /** + @brief Initialization engine interface. + */ + virtual int32_t init() = 0; + /** + @brief Run engine main logical interface. + */ + virtual int32_t run() = 0; + /** + @brief Pause engine main logical. + */ + virtual void pause() = 0; + /** + @brief Resume engine main logical. + */ + virtual void resume() = 0; + /** + @brief Restart engine main logical. + */ + virtual int restart() = 0; + /** + @brief Close engine main logical. + */ + virtual void close() = 0; + /** + @brief Gets the total number of frames in the main loop. + */ + virtual uint getTotalFrames() const = 0; + /** + * @brief Sets the preferred frame rate for main loop callback. + * @param fps The preferred frame rate for main loop callback. + */ + virtual void setPreferredFramesPerSecond(int fps) = 0; + + using SchedulerPtr = std::shared_ptr; + /** + @brief Get engine scheduler. + */ + virtual SchedulerPtr getScheduler() const = 0; + + virtual bool isInited() const = 0; +}; + +} // namespace cc diff --git a/cocos/engine/Engine.cpp b/cocos/engine/Engine.cpp new file mode 100644 index 0000000..7e286c7 --- /dev/null +++ b/cocos/engine/Engine.cpp @@ -0,0 +1,372 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "engine/Engine.h" +#include +#include +#include +#include +#include "base/DeferredReleasePool.h" +#include "base/Macros.h" +#include "bindings/jswrapper/SeApi.h" +#include "core/builtin/BuiltinResMgr.h" +#include "engine/EngineEvents.h" +#include "platform/BasePlatform.h" +#include "platform/FileUtils.h" +#include "renderer/GFXDeviceManager.h" +#include "renderer/core/ProgramLib.h" +#include "renderer/pipeline/RenderPipeline.h" +#include "renderer/pipeline/custom/RenderingModule.h" + +#if CC_USE_AUDIO + #include "cocos/audio/include/AudioEngine.h" +#endif + +#if CC_USE_SOCKET + #include "cocos/network/WebSocket.h" +#endif + +#if CC_USE_DRAGONBONES + #include "editor-support/dragonbones-creator-support/ArmatureCacheMgr.h" +#endif + +#if CC_USE_SPINE + #include "editor-support/spine-creator-support/SkeletonCacheMgr.h" +#endif + +#include "application/ApplicationManager.h" +#include "application/BaseApplication.h" +#include "base/Scheduler.h" +#include "bindings/event/EventDispatcher.h" +#include "core/assets/FreeTypeFont.h" +#include "network/HttpClient.h" +#include "platform/UniversalPlatform.h" +#include "platform/interfaces/modules/IScreen.h" +#include "platform/interfaces/modules/ISystemWindow.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" +#if CC_USE_DEBUG_RENDERER + #include "profiler/DebugRenderer.h" +#endif +#include "profiler/Profiler.h" + +namespace { + +bool setCanvasCallback(se::Object *global) { + se::AutoHandleScope scope; + se::ScriptEngine *se = se::ScriptEngine::getInstance(); + auto *window = CC_GET_MAIN_SYSTEM_WINDOW(); + auto handler = window->getWindowHandle(); + auto viewSize = window->getViewSize(); + auto dpr = cc::BasePlatform::getPlatform()->getInterface()->getDevicePixelRatio(); + + se::Value jsbVal; + bool ok = global->getProperty("jsb", &jsbVal); + if (!jsbVal.isObject()) { + se::HandleObject jsbObj(se::Object::createPlainObject()); + global->setProperty("jsb", se::Value(jsbObj)); + jsbVal.setObject(jsbObj, true); + } + + se::Value windowVal; + jsbVal.toObject()->getProperty("window", &windowVal); + if (!windowVal.isObject()) { + se::HandleObject windowObj(se::Object::createPlainObject()); + jsbVal.toObject()->setProperty("window", se::Value(windowObj)); + windowVal.setObject(windowObj, true); + } + + int width = static_cast(viewSize.width / dpr); + int height = static_cast(viewSize.height / dpr); + windowVal.toObject()->setProperty("innerWidth", se::Value(width)); + windowVal.toObject()->setProperty("innerHeight", se::Value(height)); + + if (sizeof(handler) == 8) { // use bigint + windowVal.toObject()->setProperty("windowHandle", se::Value(static_cast(handler))); + } else { + windowVal.toObject()->setProperty("windowHandle", se::Value(static_cast(handler))); + } + + return true; +} + +} // namespace + +namespace cc { + +Engine::Engine() { + _scriptEngine = ccnew se::ScriptEngine(); + + _windowEventListener.bind([this](const cc::WindowEvent &ev) { redirectWindowEvent(ev); }); +} + +Engine::~Engine() { + destroy(); + + delete _scriptEngine; + _scriptEngine = nullptr; +} + +int32_t Engine::init() { + _scheduler = std::make_shared(); + _fs = createFileUtils(); + // May create gfx device in render subsystem in future. + _gfxDevice = gfx::DeviceManager::create(); + _programLib = ccnew ProgramLib(); + _builtinResMgr = ccnew BuiltinResMgr; + +#if CC_USE_DEBUG_RENDERER + _debugRenderer = ccnew DebugRenderer(); +#endif + +#if CC_USE_PROFILER + _profiler = ccnew Profiler(); +#endif + + EventDispatcher::init(); + + BasePlatform *platform = BasePlatform::getPlatform(); + + se::ScriptEngine::getInstance()->addRegisterCallback(setCanvasCallback); + emit(ON_START); + _inited = true; + return 0; +} + +void Engine::destroy() { + cc::DeferredReleasePool::clear(); + cc::network::HttpClient::destroyInstance(); + _scheduler->removeAllFunctionsToBePerformedInCocosThread(); + _scheduler->unscheduleAll(); + CCObject::deferredDestroy(); + +#if CC_USE_AUDIO + AudioEngine::end(); +#endif + + EventDispatcher::destroy(); + + // Should delete it before deleting DeviceManager as ScriptEngine will check gpu resource usage, + // and ScriptEngine will hold gfx objects. + // Because the user registration interface needs to be added during initialization. + // ScriptEngine cannot be released here. + _scriptEngine->cleanup(); + +#if CC_USE_PROFILER + delete _profiler; +#endif + // Profiler depends on DebugRenderer, should delete it after deleting Profiler, + // and delete DebugRenderer after RenderPipeline::destroy which destroy DebugRenderer. +#if CC_USE_DEBUG_RENDERER + delete _debugRenderer; +#endif + + // TODO(): Delete some global objects. +#if CC_USE_DEBUG_RENDERER + // FreeTypeFontFace is only used in DebugRenderer now, so use CC_USE_DEBUG_RENDERER macro temporarily + FreeTypeFontFace::destroyFreeType(); +#endif + +#if CC_USE_DRAGONBONES + dragonBones::ArmatureCacheMgr::destroyInstance(); +#endif + +#if CC_USE_SPINE + spine::SkeletonCacheMgr::destroyInstance(); +#endif + +#if CC_USE_MIDDLEWARE + cc::middleware::MiddlewareManager::destroyInstance(); +#endif + + CCObject::deferredDestroy(); + + delete _builtinResMgr; + delete _programLib; + + if (cc::render::getRenderingModule()) { + cc::render::Factory::destroy(cc::render::getRenderingModule()); + } + + CC_SAFE_DESTROY_AND_DELETE(_gfxDevice); + delete _fs; + _scheduler.reset(); + + _inited = false; +} + +int32_t Engine::run() { + BasePlatform *platform = BasePlatform::getPlatform(); + _xr = CC_GET_XR_INTERFACE(); + platform->runInPlatformThread([&]() { + tick(); + }); + return 0; +} + +void Engine::pause() { + // TODO(cc) : Follow-up support +} + +void Engine::resume() { + // TODO(cc) : Follow-up support +} + +int Engine::restart() { + _needRestart = true; + return 0; +} + +void Engine::close() { // NOLINT + +#if CC_USE_AUDIO + cc::AudioEngine::stopAll(); +#endif + + // #if CC_USE_SOCKET + // cc::network::WebSocket::closeAllConnections(); + // #endif + + cc::DeferredReleasePool::clear(); + _scheduler->removeAllFunctionsToBePerformedInCocosThread(); + _scheduler->unscheduleAll(); +} + +uint Engine::getTotalFrames() const { + return _totalFrames; +} + +void Engine::setPreferredFramesPerSecond(int fps) { + if (fps == 0) { + return; + } + BasePlatform *platform = BasePlatform::getPlatform(); + platform->setFps(fps); + _preferredNanosecondsPerFrame = static_cast(1.0 / fps * NANOSECONDS_PER_SECOND); // NOLINT(google-runtime-int) +} + +void Engine::tick() { + CC_PROFILER_BEGIN_FRAME; + { + CC_PROFILE(EngineTick); + + _gfxDevice->frameSync(); + + if (_needRestart) { + doRestart(); + _needRestart = false; + } + + static std::chrono::steady_clock::time_point prevTime; + static std::chrono::steady_clock::time_point now; + static float dt = 0.F; + static double dtNS = NANOSECONDS_60FPS; + + ++_totalFrames; + + // iOS/macOS use its own fps limitation algorithm. + // Windows for Editor should not sleep,because Editor call tick function synchronously +#if (CC_PLATFORM == CC_PLATFORM_ANDROID || (CC_PLATFORM == CC_PLATFORM_WINDOWS && !CC_EDITOR) || CC_PLATFORM == CC_PLATFORM_OHOS || CC_PLATFORM == CC_PLATFORM_OPENHARMONY || CC_PLATFORM == CC_PLATFORM_MACOS) + if (dtNS < static_cast(_preferredNanosecondsPerFrame)) { + CC_PROFILE(EngineSleep); + std::this_thread::sleep_for( + std::chrono::nanoseconds(_preferredNanosecondsPerFrame - static_cast(dtNS))); + dtNS = static_cast(_preferredNanosecondsPerFrame); + } +#endif + + events::BeforeTick::broadcast(); + + prevTime = std::chrono::steady_clock::now(); + if (_xr) _xr->beginRenderFrame(); + _scheduler->update(dt); + + se::ScriptEngine::getInstance()->handlePromiseExceptions(); + events::Tick::broadcast(dt); + se::ScriptEngine::getInstance()->mainLoopUpdate(); + + cc::DeferredReleasePool::clear(); + if (_xr) _xr->endRenderFrame(); + now = std::chrono::steady_clock::now(); + dtNS = dtNS * 0.1 + 0.9 * static_cast(std::chrono::duration_cast(now - prevTime).count()); + dt = static_cast(dtNS) / NANOSECONDS_PER_SECOND; + + events::AfterTick::broadcast(); + } + + CC_PROFILER_END_FRAME; +} + +void Engine::doRestart() { + events::RestartVM::broadcast(); + destroy(); + CC_CURRENT_APPLICATION()->init(); +} + +Engine::SchedulerPtr Engine::getScheduler() const { + return _scheduler; +} + +bool Engine::redirectWindowEvent(const WindowEvent &ev) { + bool isHandled = false; + if (ev.type == WindowEvent::Type::SHOW || + ev.type == WindowEvent::Type::RESTORED) { + emit(ON_RESUME); +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + events::WindowRecreated::broadcast(ev.windowId); +#endif + events::EnterForeground::broadcast(); + isHandled = true; + } else if (ev.type == WindowEvent::Type::SIZE_CHANGED || + ev.type == WindowEvent::Type::RESIZED) { + auto *w = CC_GET_SYSTEM_WINDOW(ev.windowId); + CC_ASSERT(w); + w->setViewSize(ev.width, ev.height); + // Because the ts layer calls the getviewsize interface in response to resize. + // So we need to set the view size when sending the message. + events::Resize::broadcast(ev.width, ev.height, ev.windowId); + isHandled = true; + } else if (ev.type == WindowEvent::Type::HIDDEN || + ev.type == WindowEvent::Type::MINIMIZED) { + emit(ON_PAUSE); +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + events::WindowDestroy::broadcast(ev.windowId); +#endif + events::EnterBackground::broadcast(); + + isHandled = true; + } else if (ev.type == WindowEvent::Type::CLOSE) { + emit(ON_CLOSE); + events::Close::broadcast(); + // Increase the frame rate and get the program to exit as quickly as possible + setPreferredFramesPerSecond(1000); + isHandled = true; + } else if (ev.type == WindowEvent::Type::QUIT) { + // There is no need to process the quit message, + // the quit message is a custom message for the application + isHandled = true; + } + return isHandled; +} + +} // namespace cc diff --git a/cocos/engine/Engine.h b/cocos/engine/Engine.h new file mode 100644 index 0000000..74c989c --- /dev/null +++ b/cocos/engine/Engine.h @@ -0,0 +1,140 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Config.h" +#include "base/TypeDef.h" +#include "engine/BaseEngine.h" +#include "engine/EngineEvents.h" +#include "math/Vec2.h" + +#include +#include + +namespace se { +class ScriptEngine; +} + +namespace cc { + +namespace gfx { +class Device; +} + +class FileUtils; +class DebugRenderer; +class Profiler; +class BuiltinResMgr; +class ProgramLib; +class IXRInterface; + +#define NANOSECONDS_PER_SECOND 1000000000 +#define NANOSECONDS_60FPS 16666667L + +class CC_DLL Engine : public BaseEngine { +public: + /** + @brief Constructor of Engine. + */ + Engine(); + /** + @brief Constructor of Engine. + */ + ~Engine(); + /** + @brief Implement initialization engine. + */ + int32_t init() override; + /** + @brief Implement the main logic of the running engine. + */ + int32_t run() override; + /** + @brief Implement pause engine running. + */ + void pause() override; + /** + @brief Implement resume engine running. + */ + void resume() override; + /** + @brief Implement restart engine running. + */ + int restart() override; + /** + @brief Implement close engine running. + */ + void close() override; + /** + * @brief Sets the preferred frame rate for main loop callback. + * @param fps The preferred frame rate for main loop callback. + */ + void setPreferredFramesPerSecond(int fps) override; + /** + @brief Gets the total number of frames in the main loop. + */ + uint getTotalFrames() const override; + /** + @brief Get engine scheduler. + */ + SchedulerPtr getScheduler() const override; + + bool isInited() const override { return _inited; } + +private: + void destroy(); + void tick(); + bool redirectWindowEvent(const WindowEvent &ev); + void doRestart(); + + SchedulerPtr _scheduler{nullptr}; + int64_t _preferredNanosecondsPerFrame{NANOSECONDS_60FPS}; + uint _totalFrames{0}; + cc::Vec2 _viewLogicalSize{0, 0}; + bool _needRestart{false}; + bool _inited{false}; + + // Some global objects. + FileUtils *_fs{nullptr}; +#if CC_USE_PROFILER + Profiler *_profiler{nullptr}; +#endif + DebugRenderer *_debugRenderer{nullptr}; + se::ScriptEngine *_scriptEngine{nullptr}; + // Should move to renderer system in future. + gfx::Device *_gfxDevice{nullptr}; + + // Should move them into material system in future. + BuiltinResMgr *_builtinResMgr{nullptr}; + ProgramLib *_programLib{nullptr}; + + events::WindowEvent::Listener _windowEventListener; + + CC_DISALLOW_COPY_MOVE_ASSIGN(Engine); + + IXRInterface *_xr{nullptr}; +}; + +} // namespace cc diff --git a/cocos/engine/EngineEvents.h b/cocos/engine/EngineEvents.h new file mode 100644 index 0000000..0a4b2b0 --- /dev/null +++ b/cocos/engine/EngineEvents.h @@ -0,0 +1,352 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" +#include "core/event/EventBus.h" + +namespace cc { + +class ISystemWindow; + +enum class OSEventType { + KEYBOARD_OSEVENT = 0, + TOUCH_OSEVENT = 1, + MOUSE_OSEVENT = 2, + CUSTOM_OSEVENT = 3, + DEVICE_OSEVENT = 4, + WINDOW_OSEVENT = 5, + APP_OSEVENT = 6, + CONTROLLER_OSEVENT = 7, + UNKNOWN_OSEVENT = 8 +}; + +class WindowEvent { +public: + WindowEvent() = default; + enum class Type { + QUIT = 0, + SHOW, + RESTORED, + SIZE_CHANGED, + RESIZED, + HIDDEN, + MINIMIZED, + CLOSE, + UNKNOWN, + }; + Type type = Type::UNKNOWN; + int width = 0; + int height = 0; + uint32_t windowId = 0; +}; +// Touch event related + +class TouchInfo { +public: + float x = 0; + float y = 0; + int index = 0; + + TouchInfo(float x, float y, int index) + : x(x), + y(y), + index(index) {} +}; + +class TouchEvent { +public: + TouchEvent() = default; + enum class Type { + BEGAN, + MOVED, + ENDED, + CANCELLED, + UNKNOWN + }; + + ccstd::vector touches; + Type type = Type::UNKNOWN; + uint32_t windowId = 0; +}; + +enum class StickKeyCode { + UNDEFINE = 0, + A, + B, + X, + Y, + L1, + R1, + MINUS, + PLUS, + L3, + R3, + MENU, + START, + TRIGGER_LEFT, + TRIGGER_RIGHT, +}; + +enum class StickAxisCode { + UNDEFINE = 0, + X, + Y, + LEFT_STICK_X, + LEFT_STICK_Y, + RIGHT_STICK_X, + RIGHT_STICK_Y, + L2, + R2, + LEFT_GRIP, + RIGHT_GRIP, +}; + +enum class StickTouchCode { + UNDEFINE = 0, + A, + B, + X, + Y, + LEFT_TRIGGER, + RIGHT_TRIGGER, + LEFT_THUMBSTICK, + RIGHT_THUMBSTICK, +}; + +struct ControllerInfo { + struct AxisInfo { + StickAxisCode axis{StickAxisCode::UNDEFINE}; + float value{0.F}; + AxisInfo(StickAxisCode axis, float value) : axis(axis), value(value) {} + }; + struct ButtonInfo { + StickKeyCode key{StickKeyCode::UNDEFINE}; + bool isPress{false}; + ButtonInfo(StickKeyCode key, bool isPress) : key(key), isPress(isPress) {} + }; + struct TouchInfo { + StickTouchCode key{StickTouchCode::UNDEFINE}; + float value{0.F}; + TouchInfo(StickTouchCode key, float value) : key(key), value(value) {} + }; + int napdId{0}; + std::vector axisInfos; + std::vector buttonInfos; + std::vector touchInfos; +}; + +struct ControllerEvent { + ControllerEvent() = default; + enum class Type { + GAMEPAD, + HANDLE, + UNKNOWN + }; + Type type = Type::UNKNOWN; + ccstd::vector> controllerInfos; +}; + +struct ControllerChangeEvent { + ccstd::vector controllerIds; +}; + +class MouseEvent { +public: + MouseEvent() = default; + enum class Type { + DOWN, + UP, + MOVE, + WHEEL, + UNKNOWN + }; + + float x = 0.0F; + float y = 0.0F; + float xDelta = 0.0F; + float yDelta = 0.0F; + // The button number that was pressed when the mouse event was fired: Left button=0, middle button=1 (if present), right button=2. + // For mice configured for left handed use in which the button actions are reversed the values are instead read from right to left. + uint16_t button = 0; + Type type = Type::UNKNOWN; + uint32_t windowId = 0; +}; + +enum class KeyCode { + /** + * @en The back key on mobile phone + * @zh 移动端返回键 + */ + MOBILE_BACK = 6, + BACKSPACE = 8, + TAB = 9, + NUM_LOCK = 12, + NUMPAD_ENTER = 20013, + ENTER = 13, + SHIFT_RIGHT = 20016, + SHIFT_LEFT = 16, + CONTROL_LEFT = 17, + CONTROL_RIGHT = 20017, + ALT_RIGHT = 20018, + ALT_LEFT = 18, + PAUSE = 19, + CAPS_LOCK = 20, + ESCAPE = 27, + SPACE = 32, + PAGE_UP = 33, + PAGE_DOWN = 34, + END = 35, + HOME = 36, + ARROW_LEFT = 37, + ARROW_UP = 38, + ARROW_RIGHT = 39, + ARROW_DOWN = 40, + INSERT = 45, + DELETE_KEY = 46, // DELETE has conflict + META_LEFT = 91, + CONTEXT_MENU = 20093, + PRINT_SCREEN = 20094, + META_RIGHT = 93, + NUMPAD_MULTIPLY = 106, + NUMPAD_PLUS = 107, + NUMPAD_MINUS = 109, + NUMPAD_DECIMAL = 110, + NUMPAD_DIVIDE = 111, + SCROLLLOCK = 145, + SEMICOLON = 186, + EQUAL = 187, + COMMA = 188, + MINUS = 189, + PERIOD = 190, + SLASH = 191, + BACKQUOTE = 192, + BRACKET_LEFT = 219, + BACKSLASH = 220, + BRACKET_RIGHT = 221, + QUOTE = 222, + NUMPAD_0 = 10048, + NUMPAD_1 = 10049, + NUMPAD_2 = 10050, + NUMPAD_3 = 10051, + NUMPAD_4 = 10052, + NUMPAD_5 = 10053, + NUMPAD_6 = 10054, + NUMPAD_7 = 10055, + NUMPAD_8 = 10056, + NUMPAD_9 = 10057, + DPAD_UP = 1003, + DPAD_LEFT = 1000, + DPAD_DOWN = 1004, + DPAD_RIGHT = 1001, + DPAD_CENTER = 1005 +}; + +class KeyboardEvent { +public: + KeyboardEvent() = default; + enum class Action { + PRESS, + RELEASE, + REPEAT, + UNKNOWN + }; + + uint32_t windowId = 0; + int key = -1; + Action action = Action::UNKNOWN; + bool altKeyActive = false; + bool ctrlKeyActive = false; + bool metaKeyActive = false; + bool shiftKeyActive = false; + ccstd::string code; + // TODO(mingo): support caps lock? +}; +union EventParameterType { + void *ptrVal; + int32_t longVal; + int intVal; + int16_t shortVal; + char charVal; + bool boolVal; +}; + +class CustomEvent { +public: + CustomEvent() = default; + ccstd::string name; + EventParameterType args[10]; + + virtual ~CustomEvent() = default; // NOLINT(modernize-use-nullptr) +}; + +class DeviceEvent { +public: + DeviceEvent() = default; + enum class Type { + MEMORY, + ORIENTATION, + UNKNOWN + }; + EventParameterType args[3]; + Type type{Type::UNKNOWN}; // NOLINT(modernize-use-nullptr) +}; + +enum class ScriptEngineEvent { + BEFORE_INIT, + AFTER_INIT, + BEFORE_CLEANUP, + AFTER_CLEANUP, +}; + +namespace events { +DECLARE_EVENT_BUS(Engine) + +DECLARE_BUS_EVENT_ARG0(EnterForeground, Engine) +DECLARE_BUS_EVENT_ARG0(EnterBackground, Engine) +DECLARE_BUS_EVENT_ARG1(WindowRecreated, Engine, uint32_t /* windowId*/) +DECLARE_BUS_EVENT_ARG1(WindowDestroy, Engine, uint32_t /*windowId*/) +DECLARE_BUS_EVENT_ARG1(WindowEvent, Engine, const cc::WindowEvent &) +DECLARE_BUS_EVENT_ARG1(WindowChanged, Engine, cc::WindowEvent::Type) +DECLARE_BUS_EVENT_ARG0(LowMemory, Engine) +DECLARE_BUS_EVENT_ARG1(Touch, Engine, const cc::TouchEvent &) +DECLARE_BUS_EVENT_ARG1(Mouse, Engine, const cc::MouseEvent &) +DECLARE_BUS_EVENT_ARG1(Keyboard, Engine, const cc::KeyboardEvent &) +DECLARE_BUS_EVENT_ARG1(Controller, Engine, const cc::ControllerEvent &) +DECLARE_BUS_EVENT_ARG1(ControllerChange, Engine, const cc::ControllerChangeEvent &) +DECLARE_BUS_EVENT_ARG1(Tick, Engine, float) +DECLARE_BUS_EVENT_ARG0(BeforeTick, Engine) +DECLARE_BUS_EVENT_ARG0(AfterTick, Engine) +DECLARE_BUS_EVENT_ARG3(Resize, Engine, int, int, uint32_t /* windowId*/) +DECLARE_BUS_EVENT_ARG1(Orientation, Engine, int) +DECLARE_BUS_EVENT_ARG1(PointerLock, Engine, bool) +DECLARE_BUS_EVENT_ARG0(RestartVM, Engine) +DECLARE_BUS_EVENT_ARG0(Close, Engine) +DECLARE_BUS_EVENT_ARG0(SceneLoad, Engine) +DECLARE_BUS_EVENT_ARG1(ScriptEngine, Engine, ScriptEngineEvent) +} // namespace events +} // namespace cc diff --git a/cocos/gi/light-probe/AutoPlacement.cpp b/cocos/gi/light-probe/AutoPlacement.cpp new file mode 100644 index 0000000..d124145 --- /dev/null +++ b/cocos/gi/light-probe/AutoPlacement.cpp @@ -0,0 +1,78 @@ + +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "AutoPlacement.h" +#include "base/Macros.h" + +namespace cc { +namespace gi { + +ccstd::vector AutoPlacement::generate(const PlacementInfo &info) { + switch (info.method) { + case PlaceMethod::UNIFORM: + return doGenerateUniform(info); + case PlaceMethod::ADAPTIVE: + return doGenerateAdaptive(info); + default: + CC_ABORT(); + } + + return {}; +} + +ccstd::vector AutoPlacement::doGenerateUniform(const PlacementInfo &info) { + if (info.nProbesX < 2U || info.nProbesY < 2U || info.nProbesZ < 2U) { + return {}; + } + + ccstd::vector probes; + Vec3 position{0.0F, 0.0F, 0.0F}; + Vec3 gridSize{ + (info.maxPos.x - info.minPos.x) / static_cast(info.nProbesX - 1U), + (info.maxPos.y - info.minPos.y) / static_cast(info.nProbesY - 1U), + (info.maxPos.z - info.minPos.z) / static_cast(info.nProbesZ - 1U)}; + + for (auto x = 0U; x < info.nProbesX; x++) { + position.x = static_cast(x) * gridSize.x + info.minPos.x; + + for (auto y = 0U; y < info.nProbesY; y++) { + position.y = static_cast(y) * gridSize.y + info.minPos.y; + + for (auto z = 0U; z < info.nProbesZ; z++) { + position.z = static_cast(z) * gridSize.z + info.minPos.z; + probes.push_back(position); + } + } + } + + return probes; +} + +ccstd::vector AutoPlacement::doGenerateAdaptive(const PlacementInfo &info) { + return doGenerateUniform(info); +} + +} // namespace gi +} // namespace cc diff --git a/cocos/gi/light-probe/AutoPlacement.h b/cocos/gi/light-probe/AutoPlacement.h new file mode 100644 index 0000000..abf4560 --- /dev/null +++ b/cocos/gi/light-probe/AutoPlacement.h @@ -0,0 +1,58 @@ + +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "base/std/container/array.h" +#include "base/std/container/vector.h" +#include "math/Vec3.h" + +namespace cc { +namespace gi { + +enum class PlaceMethod { + UNIFORM = 0, + ADAPTIVE = 1, +}; + +struct PlacementInfo { + PlaceMethod method = PlaceMethod::UNIFORM; + uint32_t nProbesX{3U}; + uint32_t nProbesY{3U}; + uint32_t nProbesZ{3U}; + Vec3 minPos{-10.0F, -10.0F, -10.0F}; + Vec3 maxPos{10.0F, 10.0F, 10.0F}; +}; + +class AutoPlacement { +public: + static ccstd::vector generate(const PlacementInfo &info); + +private: + static ccstd::vector doGenerateUniform(const PlacementInfo &info); + static ccstd::vector doGenerateAdaptive(const PlacementInfo &info); +}; + +} // namespace gi +} // namespace cc diff --git a/cocos/gi/light-probe/Delaunay.cpp b/cocos/gi/light-probe/Delaunay.cpp new file mode 100644 index 0000000..f118199 --- /dev/null +++ b/cocos/gi/light-probe/Delaunay.cpp @@ -0,0 +1,446 @@ + +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Delaunay.h" +#include +#include "base/Log.h" +#include "core/platform/Debug.h" +#include "math/Mat3.h" +#define CC_USE_TETGEN 1 +#if CC_USE_TETGEN + #include "tetgen.h" +#endif + +namespace cc { +namespace gi { + +void CircumSphere::init(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3) { + // calculate circumsphere of 4 points in R^3 space. + Mat3 mat(p1.x - p0.x, p1.y - p0.y, p1.z - p0.z, + p2.x - p0.x, p2.y - p0.y, p2.z - p0.z, + p3.x - p0.x, p3.y - p0.y, p3.z - p0.z); + + mat.inverse(); + mat.transpose(); + + Vec3 n(((p1.x + p0.x) * (p1.x - p0.x) + (p1.y + p0.y) * (p1.y - p0.y) + (p1.z + p0.z) * (p1.z - p0.z)) * 0.5F, + ((p2.x + p0.x) * (p2.x - p0.x) + (p2.y + p0.y) * (p2.y - p0.y) + (p2.z + p0.z) * (p2.z - p0.z)) * 0.5F, + ((p3.x + p0.x) * (p3.x - p0.x) + (p3.y + p0.y) * (p3.y - p0.y) + (p3.z + p0.z) * (p3.z - p0.z)) * 0.5F); + + center.transformMat3(n, mat); + radiusSquared = p0.distanceSquared(center); +} + +Tetrahedron::Tetrahedron(const Delaunay *delaunay, int32_t v0, int32_t v1, int32_t v2, int32_t v3 /* = -1*/) +: vertex0(v0), vertex1(v1), vertex2(v2), vertex3(v3) { + // inner tetrahedron + if (v3 >= 0) { + const auto &probes = delaunay->_probes; + const auto &p0 = probes[vertex0].position; + const auto &p1 = probes[vertex1].position; + const auto &p2 = probes[vertex2].position; + const auto &p3 = probes[vertex3].position; + sphere.init(p0, p1, p2, p3); + } +} + +ccstd::vector Delaunay::build() { + reset(); + tetrahedralize(); + computeAdjacency(); + computeMatrices(); + + return std::move(_tetrahedrons); +} + +void Delaunay::reset() { + _tetrahedrons.clear(); + _triangles.clear(); + _edges.clear(); +} + +#if CC_USE_TETGEN +void Delaunay::tetrahedralize() { + tetgenio in; + tetgenio out; + + in.numberofpoints = static_cast(_probes.size()); + in.pointlist = new REAL[_probes.size() * 3]; + + constexpr float minFloat = std::numeric_limits::min(); + constexpr float maxFloat = std::numeric_limits::max(); + + Vec3 minPos = {maxFloat, maxFloat, maxFloat}; + Vec3 maxPos = {minFloat, minFloat, minFloat}; + + for (auto i = 0; i < _probes.size(); i++) { + const auto &position = _probes[i].position; + + in.pointlist[i * 3 + 0] = position.x; + in.pointlist[i * 3 + 1] = position.y; + in.pointlist[i * 3 + 2] = position.z; + + minPos.x = std::min(minPos.x, position.x); + maxPos.x = std::max(maxPos.x, position.x); + + minPos.y = std::min(minPos.y, position.y); + maxPos.y = std::max(maxPos.y, position.y); + + minPos.z = std::min(minPos.z, position.z); + maxPos.z = std::max(maxPos.z, position.z); + } + + const Vec3 center = (maxPos + minPos) * 0.5F; + + tetgenbehavior options; + options.neighout = 0; + options.quiet = 1; + ::tetrahedralize(&options, &in, &out); + + for (auto i = 0; i < out.numberoftetrahedra; i++) { + _tetrahedrons.emplace_back(this, out.tetrahedronlist[i * 4], out.tetrahedronlist[i * 4 + 1], out.tetrahedronlist[i * 4 + 2], out.tetrahedronlist[i * 4 + 3]); + } + + reorder(center); +} +#else +void Delaunay::tetrahedralize() { + // get probe count first + const auto probeCount = _probes.size(); + + // init a super tetrahedron containing all probes + const auto center = initTetrahedron(); + + for (auto i = 0; i < probeCount; i++) { + addProbe(i); + } + + // remove all tetrahedrons which contain the super tetrahedron's vertices + _tetrahedrons.erase(std::remove_if(_tetrahedrons.begin(), _tetrahedrons.end(), + [probeCount](Tetrahedron &tetrahedron) { + auto vertexIndex = static_cast(probeCount); + return (tetrahedron.contain(vertexIndex) || + tetrahedron.contain(vertexIndex + 1) || + tetrahedron.contain(vertexIndex + 2) || + tetrahedron.contain(vertexIndex + 3)); + }), + _tetrahedrons.end()); + + // remove all additional points in the super tetrahedron + _probes.erase(_probes.begin() + probeCount, _probes.end()); + + reorder(center); +} +#endif + +Vec3 Delaunay::initTetrahedron() { + constexpr float minFloat = std::numeric_limits::min(); + constexpr float maxFloat = std::numeric_limits::max(); + + Vec3 minPos = {maxFloat, maxFloat, maxFloat}; + Vec3 maxPos = {minFloat, minFloat, minFloat}; + + for (const auto &probe : _probes) { + const auto &position = probe.position; + minPos.x = std::min(minPos.x, position.x); + maxPos.x = std::max(maxPos.x, position.x); + + minPos.y = std::min(minPos.y, position.y); + maxPos.y = std::max(maxPos.y, position.y); + + minPos.z = std::min(minPos.z, position.z); + maxPos.z = std::max(maxPos.z, position.z); + } + + const Vec3 center = (maxPos + minPos) * 0.5F; + const Vec3 extent = maxPos - minPos; + float offset = std::max({extent.x, extent.y, extent.z}) * 10.0F; + + Vec3 p0 = center + Vec3(0.0F, offset, 0.0F); + Vec3 p1 = center + Vec3(-offset, -offset, -offset); + Vec3 p2 = center + Vec3(-offset, -offset, offset); + Vec3 p3 = center + Vec3(offset, -offset, 0.0F); + + auto index = static_cast(_probes.size()); + _probes.emplace_back(p0); + _probes.emplace_back(p1); + _probes.emplace_back(p2); + _probes.emplace_back(p3); + + _tetrahedrons.emplace_back(this, index, index + 1, index + 2, index + 3); + + return center; +} + +void Delaunay::addTriangle(uint32_t index, int32_t tet, int32_t i, int32_t v0, int32_t v1, int32_t v2, int32_t v3) { + if (index < static_cast(_triangles.size())) { + _triangles[index].set(tet, i, v0, v1, v2, v3); + } else { + _triangles.emplace_back(tet, i, v0, v1, v2, v3); + } +} + +void Delaunay::addEdge(uint32_t index, int32_t tet, int32_t i, int32_t v0, int32_t v1) { + if (index < static_cast(_edges.size())) { + _edges[index].set(tet, i, v0, v1); + } else { + _edges.emplace_back(tet, i, v0, v1); + } +} + +void Delaunay::addProbe(int32_t vertexIndex) { + const auto &probe = _probes[vertexIndex]; + const auto position = probe.position; + + auto triangleIndex = 0; + for (auto i = 0; i < static_cast(_tetrahedrons.size()); i++) { + auto &tetrahedron = _tetrahedrons[i]; + if (tetrahedron.isInCircumSphere(position)) { + tetrahedron.invalid = true; + + addTriangle(triangleIndex, i, 0, tetrahedron.vertex1, tetrahedron.vertex3, tetrahedron.vertex2, tetrahedron.vertex0); + addTriangle(triangleIndex + 1, i, 1, tetrahedron.vertex0, tetrahedron.vertex2, tetrahedron.vertex3, tetrahedron.vertex1); + addTriangle(triangleIndex + 2, i, 2, tetrahedron.vertex0, tetrahedron.vertex3, tetrahedron.vertex1, tetrahedron.vertex2); + addTriangle(triangleIndex + 3, i, 3, tetrahedron.vertex0, tetrahedron.vertex1, tetrahedron.vertex2, tetrahedron.vertex3); + triangleIndex += 4; + } + } + + for (auto i = 0; i < triangleIndex; i++) { + if (_triangles[i].invalid) { + continue; + } + + for (auto k = i + 1; k < triangleIndex; k++) { + if (_triangles[i].isSame(_triangles[k])) { + _triangles[i].invalid = true; + _triangles[k].invalid = true; + break; + } + } + } + + // remove containing tetrahedron + _tetrahedrons.erase(std::remove_if(_tetrahedrons.begin(), _tetrahedrons.end(), + [](Tetrahedron &tetrahedron) { return tetrahedron.invalid; }), + _tetrahedrons.end()); + + for (auto i = 0; i < triangleIndex; i++) { + const auto &triangle = _triangles[i]; + if (!triangle.invalid) { + _tetrahedrons.emplace_back(this, triangle.vertex0, triangle.vertex1, triangle.vertex2, vertexIndex); + } + } +} + +void Delaunay::reorder(const Vec3 ¢er) { + // The tetrahedron in the middle is placed at the front of the vector + std::sort(_tetrahedrons.begin(), _tetrahedrons.end(), [center](Tetrahedron &a, Tetrahedron &b) { + return a.sphere.center.distanceSquared(center) < b.sphere.center.distanceSquared(center); + }); +} + +void Delaunay::computeAdjacency() { + Vec3 normal; + + const auto tetrahedronCount = static_cast(_tetrahedrons.size()); + + auto triangleIndex = 0; + for (auto i = 0; i < static_cast(_tetrahedrons.size()); i++) { + const auto &tetrahedron = _tetrahedrons[i]; + + addTriangle(triangleIndex, i, 0, tetrahedron.vertex1, tetrahedron.vertex3, tetrahedron.vertex2, tetrahedron.vertex0); + addTriangle(triangleIndex + 1, i, 1, tetrahedron.vertex0, tetrahedron.vertex2, tetrahedron.vertex3, tetrahedron.vertex1); + addTriangle(triangleIndex + 2, i, 2, tetrahedron.vertex0, tetrahedron.vertex3, tetrahedron.vertex1, tetrahedron.vertex2); + addTriangle(triangleIndex + 3, i, 3, tetrahedron.vertex0, tetrahedron.vertex1, tetrahedron.vertex2, tetrahedron.vertex3); + triangleIndex += 4; + } + + for (auto i = 0; i < triangleIndex; i++) { + if (!_triangles[i].isOuterFace) { + continue; + } + + for (auto k = i + 1; k < triangleIndex; k++) { + if (_triangles[i].isSame(_triangles[k])) { + // update adjacency between tetrahedrons + _tetrahedrons[_triangles[i].tetrahedron].neighbours[_triangles[i].index] = _triangles[k].tetrahedron; + _tetrahedrons[_triangles[k].tetrahedron].neighbours[_triangles[k].index] = _triangles[i].tetrahedron; + _triangles[i].isOuterFace = false; + _triangles[k].isOuterFace = false; + break; + } + } + + if (_triangles[i].isOuterFace) { + auto &probe0 = _probes[_triangles[i].vertex0]; + auto &probe1 = _probes[_triangles[i].vertex1]; + auto &probe2 = _probes[_triangles[i].vertex2]; + auto &probe3 = _probes[_triangles[i].vertex3]; + + auto edge1 = probe1.position - probe0.position; + auto edge2 = probe2.position - probe0.position; + Vec3::cross(edge1, edge2, &normal); + + auto edge3 = probe3.position - probe0.position; + auto negative = normal.dot(edge3); + if (negative > 0.0F) { + normal.negate(); + } + + // accumulate weighted normal + probe0.normal += normal; + probe1.normal += normal; + probe2.normal += normal; + + // create an outer cell with normal facing out + auto v0 = _triangles[i].vertex0; + auto v1 = negative > 0.0F ? _triangles[i].vertex2 : _triangles[i].vertex1; + auto v2 = negative > 0.0F ? _triangles[i].vertex1 : _triangles[i].vertex2; + Tetrahedron tetrahedron(this, v0, v1, v2); + + // update adjacency between tetrahedron and outer cell + tetrahedron.neighbours[3] = _triangles[i].tetrahedron; + _tetrahedrons[_triangles[i].tetrahedron].neighbours[_triangles[i].index] = static_cast(_tetrahedrons.size()); + _tetrahedrons.push_back(tetrahedron); + } + } + + // start from outer cell index + auto edgeIndex = 0; + for (auto i = tetrahedronCount; i < static_cast(_tetrahedrons.size()); i++) { + const auto &tetrahedron = _tetrahedrons[i]; + + addEdge(edgeIndex, i, 0, tetrahedron.vertex1, tetrahedron.vertex2); + addEdge(edgeIndex + 1, i, 1, tetrahedron.vertex2, tetrahedron.vertex0); + addEdge(edgeIndex + 2, i, 2, tetrahedron.vertex0, tetrahedron.vertex1); + edgeIndex += 3; + } + + for (auto i = 0; i < edgeIndex; i++) { + for (auto k = i + 1; k < edgeIndex; k++) { + if (_edges[i].isSame(_edges[k])) { + // update adjacency between outer cells + _tetrahedrons[_edges[i].tetrahedron].neighbours[_edges[i].index] = _edges[k].tetrahedron; + _tetrahedrons[_edges[k].tetrahedron].neighbours[_edges[k].index] = _edges[i].tetrahedron; + } + } + } + + // normalize all convex hull probes' normal + for (auto &probe : _probes) { + if (!probe.normal.isZero()) { + probe.normal.normalize(); + } + } +} + +void Delaunay::computeMatrices() { + for (auto &tetrahedron : _tetrahedrons) { + if (tetrahedron.vertex3 >= 0) { + computeTetrahedronMatrix(tetrahedron); + } else { + computeOuterCellMatrix(tetrahedron); + } + } +} + +void Delaunay::computeTetrahedronMatrix(Tetrahedron &tetrahedron) { + const auto &p0 = _probes[tetrahedron.vertex0].position; + const auto &p1 = _probes[tetrahedron.vertex1].position; + const auto &p2 = _probes[tetrahedron.vertex2].position; + const auto &p3 = _probes[tetrahedron.vertex3].position; + + tetrahedron.matrix.set( + p0.x - p3.x, p1.x - p3.x, p2.x - p3.x, + p0.y - p3.y, p1.y - p3.y, p2.y - p3.y, + p0.z - p3.z, p1.z - p3.z, p2.z - p3.z); + + tetrahedron.matrix.inverse(); + tetrahedron.matrix.transpose(); +} + +void Delaunay::computeOuterCellMatrix(Tetrahedron &tetrahedron) { + Vec3 v[3]; + Vec3 p[3]; + + v[0] = _probes[tetrahedron.vertex0].normal; + v[1] = _probes[tetrahedron.vertex1].normal; + v[2] = _probes[tetrahedron.vertex2].normal; + + p[0] = _probes[tetrahedron.vertex0].position; + p[1] = _probes[tetrahedron.vertex1].position; + p[2] = _probes[tetrahedron.vertex2].position; + + Vec3 a = p[0] - p[2]; + Vec3 ap = v[0] - v[2]; + Vec3 b = p[1] - p[2]; + Vec3 bp = v[1] - v[2]; + Vec3 p2 = p[2]; + Vec3 cp = -v[2]; + + float m[12]; + + m[0] = ap.y * bp.z - ap.z * bp.y; + m[3] = -ap.x * bp.z + ap.z * bp.x; + m[6] = ap.x * bp.y - ap.y * bp.x; + m[9] = a.x * bp.y * cp.z - a.y * bp.x * cp.z + ap.x * b.y * cp.z - ap.y * b.x * cp.z + a.z * bp.x * cp.y - a.z * bp.y * cp.x + ap.z * b.x * cp.y - ap.z * b.y * cp.x - a.x * bp.z * cp.y + a.y * bp.z * cp.x - ap.x * b.z * cp.y + ap.y * b.z * cp.x; + m[9] -= p2.x * m[0] + p2.y * m[3] + p2.z * m[6]; + + m[1] = ap.y * b.z + a.y * bp.z - ap.z * b.y - a.z * bp.y; + m[4] = -a.x * bp.z - ap.x * b.z + a.z * bp.x + ap.z * b.x; + m[7] = a.x * bp.y - a.y * bp.x + ap.x * b.y - ap.y * b.x; + m[10] = a.x * b.y * cp.z - a.y * b.x * cp.z - a.x * b.z * cp.y + a.y * b.z * cp.x + a.z * b.x * cp.y - a.z * b.y * cp.x; + m[10] -= p2.x * m[1] + p2.y * m[4] + p2.z * m[7]; + + m[2] = -a.z * b.y + a.y * b.z; + m[5] = -a.x * b.z + a.z * b.x; + m[8] = a.x * b.y - a.y * b.x; + m[11] = 0.0F; + m[11] -= p2.x * m[2] + p2.y * m[5] + p2.z * m[8]; + + // coefficient of t^3 + float c = ap.x * bp.y * cp.z - ap.y * bp.x * cp.z + ap.z * bp.x * cp.y - ap.z * bp.y * cp.x + ap.y * bp.z * cp.x - ap.x * bp.z * cp.y; + + if (std::abs(c) > mathutils::EPSILON) { + // t^3 + p * t^2 + q * t + r = 0 + for (float &k : m) { + k /= c; + } + } else { + // set last vertex index of outer cell to -2 + // p * t^2 + q * t + r = 0 + tetrahedron.vertex3 = -2; + } + + // transpose the matrix + tetrahedron.matrix.set(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); + + // last column of mat3x4 + tetrahedron.offset.set(m[9], m[10], m[11]); +} + +} // namespace gi +} // namespace cc diff --git a/cocos/gi/light-probe/Delaunay.h b/cocos/gi/light-probe/Delaunay.h new file mode 100644 index 0000000..004e6cd --- /dev/null +++ b/cocos/gi/light-probe/Delaunay.h @@ -0,0 +1,248 @@ + +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include "base/Macros.h" +#include "base/std/container/array.h" +#include "base/std/container/vector.h" +#include "core/geometry/AABB.h" +#include "math/Utils.h" +#include "math/Vec3.h" + +namespace cc { +namespace gi { + +class Delaunay; + +struct Vertex { + ccstd::vector coefficients; + Vec3 position; + Vec3 normal; + + Vertex() = default; + explicit Vertex(const Vec3 &pos) + : position(pos) { + } +}; + +struct Edge { + int32_t tetrahedron{-1}; // tetrahedron index this edge belongs to + int32_t index{-1}; // index in triangle's three edges of an outer cell + int32_t vertex0{-1}; + int32_t vertex1{-1}; + + Edge() = default; + Edge(int32_t tet, int32_t i, int32_t v0, int32_t v1) + : tetrahedron(tet), index(i) { + if (v0 < v1) { + vertex0 = v0; + vertex1 = v1; + } else { + vertex0 = v1; + vertex1 = v0; + } + } + + inline void set(int32_t tet, int32_t i, int32_t v0, int32_t v1) { + tetrahedron = tet; + index = i; + + if (v0 < v1) { + vertex0 = v0; + vertex1 = v1; + } else { + vertex0 = v1; + vertex1 = v0; + } + } + + inline bool isSame(const Edge &other) const { + return (vertex0 == other.vertex0 && vertex1 == other.vertex1); + } +}; + +struct Triangle { + bool invalid{false}; + bool isOuterFace{true}; + int32_t tetrahedron{-1}; // tetrahedron index this triangle belongs to + int32_t index{-1}; // index in tetrahedron's four triangles + int32_t vertex0{-1}; + int32_t vertex1{-1}; + int32_t vertex2{-1}; + int32_t vertex3{-1}; // tetrahedron's last vertex index used to compute normal direction + + Triangle() = default; + Triangle(int32_t tet, int32_t i, int32_t v0, int32_t v1, int32_t v2, int32_t v3) + : tetrahedron(tet), index(i), vertex3(v3) { + if (v0 < v1 && v0 < v2) { + vertex0 = v0; + if (v1 < v2) { + vertex1 = v1; + vertex2 = v2; + } else { + vertex1 = v2; + vertex2 = v1; + } + } else if (v1 < v0 && v1 < v2) { + vertex0 = v1; + if (v0 < v2) { + vertex1 = v0; + vertex2 = v2; + } else { + vertex1 = v2; + vertex2 = v0; + } + } else { + vertex0 = v2; + if (v0 < v1) { + vertex1 = v0; + vertex2 = v1; + } else { + vertex1 = v1; + vertex2 = v0; + } + } + } + + inline void set(int32_t tet, int32_t i, int32_t v0, int32_t v1, int32_t v2, int32_t v3) { + invalid = false; + isOuterFace = true; + + tetrahedron = tet; + index = i; + vertex3 = v3; + + if (v0 < v1 && v0 < v2) { + vertex0 = v0; + if (v1 < v2) { + vertex1 = v1; + vertex2 = v2; + } else { + vertex1 = v2; + vertex2 = v1; + } + } else if (v1 < v0 && v1 < v2) { + vertex0 = v1; + if (v0 < v2) { + vertex1 = v0; + vertex2 = v2; + } else { + vertex1 = v2; + vertex2 = v0; + } + } else { + vertex0 = v2; + if (v0 < v1) { + vertex1 = v0; + vertex2 = v1; + } else { + vertex1 = v1; + vertex2 = v0; + } + } + } + + inline bool isSame(const Triangle &other) const { + return (vertex0 == other.vertex0 && vertex1 == other.vertex1 && vertex2 == other.vertex2); + } +}; + +struct CircumSphere { + float radiusSquared{0.0F}; + Vec3 center; + + CircumSphere() = default; + void init(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &p3); +}; + +/** + * inner tetrahedron or outer cell structure + */ +struct Tetrahedron { + bool invalid{false}; + int32_t vertex0{-1}; + int32_t vertex1{-1}; + int32_t vertex2{-1}; + int32_t vertex3{-1}; // -1 means outer cell, otherwise inner tetrahedron + ccstd::array neighbours{-1, -1, -1, -1}; + + Mat3 matrix; + Vec3 offset; // only valid in outer cell + CircumSphere sphere; // only valid in inner tetrahedron + + // inner tetrahedron or outer cell constructor + Tetrahedron(const Delaunay *delaunay, int32_t v0, int32_t v1, int32_t v2, int32_t v3 = -1); + Tetrahedron() = default; + + inline bool isInCircumSphere(const Vec3 &point) const { + return point.distanceSquared(sphere.center) < sphere.radiusSquared - mathutils::EPSILON; + } + + inline bool contain(int32_t vertexIndex) const { + return (vertex0 == vertexIndex || vertex1 == vertexIndex || + vertex2 == vertexIndex || vertex3 == vertexIndex); + } + + inline bool isInnerTetrahedron() const { + return vertex3 >= 0; + } + + inline bool isOuterCell() const { + return vertex3 < 0; // -1 or -2 + } +}; + +class Delaunay { +public: + explicit Delaunay(ccstd::vector &probes) : _probes(probes) {} + ~Delaunay() = default; + + ccstd::vector build(); + +private: + void reset(); + void tetrahedralize(); // Bowyer-Watson algorithm + Vec3 initTetrahedron(); + void addTriangle(uint32_t index, int32_t tet, int32_t i, int32_t v0, int32_t v1, int32_t v2, int32_t v3); + void addEdge(uint32_t index, int32_t tet, int32_t i, int32_t v0, int32_t v1); + void addProbe(int32_t vertexIndex); + void reorder(const Vec3 ¢er); + void computeAdjacency(); + void computeMatrices(); + void computeTetrahedronMatrix(Tetrahedron &tetrahedron); + void computeOuterCellMatrix(Tetrahedron &tetrahedron); + + ccstd::vector &_probes; + ccstd::vector _tetrahedrons; + + ccstd::vector _triangles; + ccstd::vector _edges; + + CC_DISALLOW_COPY_MOVE_ASSIGN(Delaunay); + friend class Tetrahedron; +}; + +} // namespace gi +} // namespace cc diff --git a/cocos/gi/light-probe/LightProbe.cpp b/cocos/gi/light-probe/LightProbe.cpp new file mode 100644 index 0000000..3593410 --- /dev/null +++ b/cocos/gi/light-probe/LightProbe.cpp @@ -0,0 +1,351 @@ + +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "LightProbe.h" +#include "PolynomialSolver.h" +#include "core/Root.h" +#include "core/scene-graph/Node.h" +#include "core/scene-graph/Scene.h" +#include "math/Math.h" +#include "math/Utils.h" +#include "renderer/pipeline/custom/RenderInterfaceTypes.h" + +namespace cc { +namespace gi { + +void LightProbesData::updateProbes(ccstd::vector &points) { + _probes.clear(); + + auto pointCount = points.size(); + _probes.reserve(pointCount); + for (auto i = 0; i < pointCount; i++) { + _probes.emplace_back(points[i]); + } +} + +void LightProbesData::updateTetrahedrons() { + Delaunay delaunay(_probes); + _tetrahedrons = delaunay.build(); +} + +bool LightProbesData::getInterpolationSHCoefficients(int32_t tetIndex, const Vec4 &weights, ccstd::vector &coefficients) const { + if (!hasCoefficients()) { + return false; + } + + const auto length = SH::getBasisCount(); + coefficients.resize(length); + + const auto &tetrahedron = _tetrahedrons[tetIndex]; + const auto &c0 = _probes[tetrahedron.vertex0].coefficients; + const auto &c1 = _probes[tetrahedron.vertex1].coefficients; + const auto &c2 = _probes[tetrahedron.vertex2].coefficients; + + if (tetrahedron.vertex3 >= 0) { + const auto &c3 = _probes[tetrahedron.vertex3].coefficients; + + for (auto i = 0; i < length; i++) { + coefficients[i] = c0[i] * weights.x + c1[i] * weights.y + c2[i] * weights.z + c3[i] * weights.w; + } + } else { + for (auto i = 0; i < length; i++) { + coefficients[i] = c0[i] * weights.x + c1[i] * weights.y + c2[i] * weights.z; + } + } + + return true; +} + +int32_t LightProbesData::getInterpolationWeights(const Vec3 &position, int32_t tetIndex, Vec4 &weights) const { + const auto tetrahedronCount = _tetrahedrons.size(); + if (tetIndex < 0 || tetIndex >= tetrahedronCount) { + tetIndex = 0; + } + + int32_t lastIndex = -1; + int32_t nextIndex = -1; + + for (auto i = 0; i < tetrahedronCount; i++) { + const auto &tetrahedron = _tetrahedrons[tetIndex]; + getBarycentricCoord(position, tetrahedron, weights); + if (weights.x >= 0.0F && weights.y >= 0.0F && weights.z >= 0.0F && weights.w >= 0.0F) { + break; + } + + if (weights.x < weights.y && weights.x < weights.z && weights.x < weights.w) { + nextIndex = tetrahedron.neighbours[0]; + } else if (weights.y < weights.z && weights.y < weights.w) { + nextIndex = tetrahedron.neighbours[1]; + } else if (weights.z < weights.w) { + nextIndex = tetrahedron.neighbours[2]; + } else { + nextIndex = tetrahedron.neighbours[3]; + } + + // return directly due to numerical precision error + if (lastIndex == nextIndex) { + break; + } + + lastIndex = tetIndex; + tetIndex = nextIndex; + } + + return tetIndex; +} + +Vec3 LightProbesData::getTriangleBarycentricCoord(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &position) { + Vec3 normal; + Vec3::cross(p1 - p0, p2 - p0, &normal); + + if (normal.lengthSquared() <= mathutils::EPSILON) { + return Vec3(0.0F, 0.0F, 0.0F); + } + + const Vec3 n = normal.getNormalized(); + const float area012Inv = 1.0F / (n.dot(normal)); + + Vec3 crossP12; + Vec3::cross(p1 - position, p2 - position, &crossP12); + const float areaP12 = n.dot(crossP12); + const float alpha = areaP12 * area012Inv; + + Vec3 crossP20; + Vec3::cross(p2 - position, p0 - position, &crossP20); + const float areaP20 = n.dot(crossP20); + const float beta = areaP20 * area012Inv; + + return Vec3(alpha, beta, 1.0F - alpha - beta); +} + +void LightProbesData::getBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const { + if (tetrahedron.vertex3 >= 0) { + getTetrahedronBarycentricCoord(position, tetrahedron, weights); + } else { + getOuterCellBarycentricCoord(position, tetrahedron, weights); + } +} + +void LightProbesData::getTetrahedronBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const { + Vec3 result = position - _probes[tetrahedron.vertex3].position; + result.transformMat3(result, tetrahedron.matrix); + + weights.set(result.x, result.y, result.z, 1.0F - result.x - result.y - result.z); +} + +void LightProbesData::getOuterCellBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const { + const auto &p0 = _probes[tetrahedron.vertex0].position; + const auto &p1 = _probes[tetrahedron.vertex1].position; + const auto &p2 = _probes[tetrahedron.vertex2].position; + + Vec3 normal; + const auto edge1 = p1 - p0; + const auto edge2 = p2 - p0; + Vec3::cross(edge1, edge2, &normal); + float t = Vec3::dot(position - p0, normal); + if (t < 0.0F) { + // test tetrahedron in next iterator + weights.set(0.0F, 0.0F, 0.0F, -1.0F); + return; + } + + Vec3 coefficients; + coefficients.transformMat3(position, tetrahedron.matrix); + coefficients += tetrahedron.offset; + + if (tetrahedron.vertex3 == -1) { + t = PolynomialSolver::getCubicUniqueRoot(coefficients.x, coefficients.y, coefficients.z); + } else { + t = PolynomialSolver::getQuadraticUniqueRoot(coefficients.x, coefficients.y, coefficients.z); + } + + const auto v0 = p0 + _probes[tetrahedron.vertex0].normal * t; + const auto v1 = p1 + _probes[tetrahedron.vertex1].normal * t; + const auto v2 = p2 + _probes[tetrahedron.vertex2].normal * t; + const auto result = getTriangleBarycentricCoord(v0, v1, v2, position); + + weights.set(result.x, result.y, result.z, 0.0F); +} + +void LightProbes::initialize(LightProbeInfo *info) { + _giScale = info->getGIScale(); + _giSamples = info->getGISamples(); + _bounces = info->getBounces(); + _reduceRinging = info->getReduceRinging(); + _showProbe = info->isShowProbe(); + _showWireframe = info->isShowWireframe(); + _lightProbeSphereVolume = info->getLightProbeSphereVolume(); + _showConvex = info->isShowConvex(); + _data = info->getData(); +} + +void LightProbeInfo::activate(Scene *scene, LightProbes *resource) { + _scene = scene; + _resource = resource; + _resource->initialize(this); +} + +void LightProbeInfo::onProbeBakeFinished() { + onProbeBakingChanged(_scene); +} + +void LightProbeInfo::onProbeBakeCleared() { + clearSHCoefficients(); + onProbeBakingChanged(_scene); +} + +void LightProbeInfo::clearSHCoefficients() { + if (!_data) { + return; + } + + auto &probes = _data->getProbes(); + for (auto &probe : probes) { + probe.coefficients.clear(); + } + + clearAllSHUBOs(); +} + +bool LightProbeInfo::addNode(Node *node) { + if (!node) { + return false; + } + + for (auto &item : _nodes) { + if (item.node == node) { + return false; + } + } + + _nodes.emplace_back(node); + + return true; +} + +bool LightProbeInfo::removeNode(Node *node) { + if (!node) { + return false; + } + + for (auto iter = _nodes.begin(); iter != _nodes.end(); ++iter) { + if (iter->node == node) { + _nodes.erase(iter); + return true; + } + } + + return false; +} + +void LightProbeInfo::syncData(Node *node, const ccstd::vector &probes) { + for (auto &item : _nodes) { + if (item.node == node) { + item.probes = probes; + return; + } + } +} + +void LightProbeInfo::update(bool updateTet) { + if (!_data) { + _data = new LightProbesData(); + if (_resource) { + _resource->setData(_data); + } + } + + ccstd::vector points; + + for (auto &item : _nodes) { + auto *node = item.node; + auto &probes = item.probes; + const auto &worldPosition = node->getWorldPosition(); + + for (auto &probe : probes) { + points.push_back(probe + worldPosition); + } + } + + auto pointCount = points.size(); + if (pointCount < 4) { + resetAllTetraIndices(); + _data->reset(); + return; + } + + _data->updateProbes(points); + + if (updateTet) { + resetAllTetraIndices(); + _data->updateTetrahedrons(); + } +} + +void LightProbeInfo::onProbeBakingChanged(Node *node) { // NOLINT(misc-no-recursion) + if (!node) { + return; + } + + node->emit(); + + const auto &children = node->getChildren(); + for (const auto &child: children) { + onProbeBakingChanged(child); + } +} + +void LightProbeInfo::clearAllSHUBOs() { + if (!_scene) { + return; + } + + auto *renderScene = _scene->getRenderScene(); + if (!renderScene) { + return; + } + + for (const auto &model : renderScene->getModels()) { + model->clearSHUBOs(); + } +} + +void LightProbeInfo::resetAllTetraIndices() { + if (!_scene) { + return; + } + + auto *renderScene = _scene->getRenderScene(); + if (!renderScene) { + return; + } + + for (const auto &model : renderScene->getModels()) { + model->setTetrahedronIndex(-1); + } +} + +} // namespace gi +} // namespace cc diff --git a/cocos/gi/light-probe/LightProbe.h b/cocos/gi/light-probe/LightProbe.h new file mode 100644 index 0000000..3127623 --- /dev/null +++ b/cocos/gi/light-probe/LightProbe.h @@ -0,0 +1,280 @@ + +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "Delaunay.h" +#include "SH.h" +#include "base/Macros.h" +#include "base/Ptr.h" +#include "base/RefCounted.h" +#include "base/std/container/vector.h" +#include "math/Vec3.h" +#include "math/Vec4.h" + +namespace cc { +class Scene; +class Node; + +namespace gi { + +class LightProbeInfo; + +class LightProbesData : public RefCounted { +public: + LightProbesData() = default; + + inline ccstd::vector &getProbes() { return _probes; } + inline void setProbes(const ccstd::vector &probes) { _probes = probes; } + inline ccstd::vector &getTetrahedrons() { return _tetrahedrons; } + inline void setTetrahedrons(const ccstd::vector &tetrahedrons) { _tetrahedrons = tetrahedrons; } + + inline bool empty() const { return _probes.empty() || _tetrahedrons.empty(); } + inline void reset() { + _probes.clear(); + _tetrahedrons.clear(); + } + void updateProbes(ccstd::vector &points); + void updateTetrahedrons(); + + inline bool hasCoefficients() const { return !empty() && !_probes[0].coefficients.empty(); } + bool getInterpolationSHCoefficients(int32_t tetIndex, const Vec4 &weights, ccstd::vector &coefficients) const; + int32_t getInterpolationWeights(const Vec3 &position, int32_t tetIndex, Vec4 &weights) const; + +private: + static Vec3 getTriangleBarycentricCoord(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec3 &position); + void getBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const; + void getTetrahedronBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const; + void getOuterCellBarycentricCoord(const Vec3 &position, const Tetrahedron &tetrahedron, Vec4 &weights) const; + +public: + ccstd::vector _probes; + ccstd::vector _tetrahedrons; +}; + +class LightProbes final { +public: + LightProbes() = default; + ~LightProbes() = default; + + void initialize(LightProbeInfo *info); + + inline bool empty() const { + if (!_data) { + return true; + } + + return _data->empty(); + } + + inline void setGIScale(float val) { _giScale = val; } + inline float getGIScale() const { return _giScale; } + + inline void setLightProbeSphereVolume(float val) { _lightProbeSphereVolume = val; } + inline float getLightProbeSphereVolume() const { return _lightProbeSphereVolume; } + + inline void setGISamples(uint32_t val) { _giSamples = val; } + inline uint32_t getGISamples() const { return _giSamples; } + + inline void setBounces(uint32_t val) { _bounces = val; } + inline uint32_t getBounces() const { return _bounces; } + + inline void setReduceRinging(float val) { _reduceRinging = val; } + inline float getReduceRinging() const { return _reduceRinging; } + + inline void setShowProbe(bool val) { _showProbe = val; } + inline bool isShowProbe() const { return _showProbe; } + + inline void setShowWireframe(bool val) { _showWireframe = val; } + inline bool isShowWireframe() const { return _showWireframe; } + + inline void setShowConvex(bool val) { _showConvex = val; } + inline bool isShowConvex() const { return _showConvex; } + + inline void setData(LightProbesData *data) { _data = data; } + inline LightProbesData *getData() const { return _data.get(); } + + float _giScale{1.0F}; + float _lightProbeSphereVolume{1.0F}; + uint32_t _giSamples{1024U}; + uint32_t _bounces{2U}; + float _reduceRinging{0.0F}; + bool _showProbe{true}; + bool _showWireframe{true}; + bool _showConvex{false}; + IntrusivePtr _data; +}; + +struct ILightProbeNode { + Node *node{nullptr}; + ccstd::vector probes; + + explicit ILightProbeNode(Node *n) + : node(n) {} +}; + +class LightProbeInfo : public RefCounted { +public: + LightProbeInfo() = default; + ~LightProbeInfo() override = default; + + void activate(Scene *scene, LightProbes *resource); + void onProbeBakeFinished(); + void onProbeBakeCleared(); + void clearSHCoefficients(); + inline bool isUniqueNode() const { return _nodes.size() == 1; } + bool addNode(Node *node); + bool removeNode(Node *node); + void syncData(Node *node, const ccstd::vector &probes); + void update(bool updateTet = true); + + inline void setGIScale(float val) { + if (_giScale == val) { + return; + } + + _giScale = val; + if (_resource) { + _resource->setGIScale(val); + } + } + inline float getGIScale() const { return _giScale; } + + inline void setLightProbeSphereVolume(float val) { + if (_lightProbeSphereVolume == val) { + return; + } + + _lightProbeSphereVolume = val; + if (_resource) { + _resource->setLightProbeSphereVolume(val); + } + } + inline float getLightProbeSphereVolume() const { return _lightProbeSphereVolume; } + + inline void setGISamples(uint32_t val) { + if (_giSamples == val) { + return; + } + + _giSamples = val; + if (_resource) { + _resource->setGISamples(val); + } + } + inline uint32_t getGISamples() const { return _giSamples; } + + inline void setBounces(uint32_t val) { + if (_bounces == val) { + return; + } + + _bounces = val; + if (_resource) { + _resource->setBounces(val); + } + } + inline uint32_t getBounces() const { return _bounces; } + + inline void setReduceRinging(float val) { + if (_reduceRinging == val) { + return; + } + + _reduceRinging = val; + if (_resource) { + _resource->setReduceRinging(val); + } + } + inline float getReduceRinging() const { return _reduceRinging; } + + inline void setShowProbe(bool val) { + if (_showProbe == val) { + return; + } + + _showProbe = val; + if (_resource) { + _resource->setShowProbe(val); + } + } + inline bool isShowProbe() const { return _showProbe; } + + inline void setShowWireframe(bool val) { + if (_showWireframe == val) { + return; + } + + _showWireframe = val; + if (_resource) { + _resource->setShowWireframe(val); + } + } + inline bool isShowWireframe() const { return _showWireframe; } + + inline void setShowConvex(bool val) { + if (_showConvex == val) { + return; + } + + _showConvex = val; + if (_resource) { + _resource->setShowConvex(val); + } + } + inline bool isShowConvex() const { return _showConvex; } + + inline void setData(LightProbesData *data) { + _data = data; + if (_resource) { + _resource->setData(data); + } + } + + inline LightProbesData *getData() const { return _data.get(); } + + //cjh JSB need to bind the property, so need to make it public + float _giScale{1.0F}; + float _lightProbeSphereVolume{1.0F}; + uint32_t _giSamples{1024U}; + uint32_t _bounces{2U}; + float _reduceRinging{0.0F}; + bool _showProbe{true}; + bool _showWireframe{true}; + bool _showConvex{false}; + IntrusivePtr _data; + +private: + void onProbeBakingChanged(Node *node); + void clearAllSHUBOs(); + void resetAllTetraIndices(); + + Scene *_scene{nullptr}; + ccstd::vector _nodes; + LightProbes *_resource{nullptr}; +}; + +} // namespace gi +} // namespace cc diff --git a/cocos/gi/light-probe/PolynomialSolver.cpp b/cocos/gi/light-probe/PolynomialSolver.cpp new file mode 100644 index 0000000..c1797e7 --- /dev/null +++ b/cocos/gi/light-probe/PolynomialSolver.cpp @@ -0,0 +1,93 @@ + +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "PolynomialSolver.h" +#include +#include "base/std/container/vector.h" +#include "math/Math.h" + +namespace cc { +namespace gi { + +float PolynomialSolver::getQuadraticUniqueRoot(float b, float c, float d) { + // quadratic case + if (b != 0.0F) { + // the discriminant should be 0 + return -c / (2.0F * b); + } + + // linear case + if (c != 0.0F) { + return -d / c; + } + + // never reach here + return 0.0F; +} + +float PolynomialSolver::getCubicUniqueRoot(float b, float c, float d) { + ccstd::vector roots; + + // let x = y - b / 3, convert equation to: y^3 + 3 * p * y + 2 * q = 0 + // where p = c / 3 - b^2 / 9, q = d / 2 + b^3 / 27 - b * c / 6 + const auto offset = -b / 3.0F; + const auto p = c / 3.0F - (b * b) / 9.0F; + const auto q = d / 2.0F + (b * b * b) / 27.0F - (b * c) / 6.0F; + const auto delta = p * p * p + q * q; // discriminant + + if (delta > 0.0F) { + // only one real root + const auto sqrtDelta = std::sqrt(delta); + roots.push_back(std::cbrt(-q + sqrtDelta) + std::cbrt(-q - sqrtDelta)); + } else if (delta < 0.0F) { + // three different real roots + const auto angle = std::acos(-q * std::sqrt(-p) / (p * p)) / 3.0F; + roots.push_back(2.0F * std::sqrt(-p) * std::cos(angle)); + roots.push_back(2.0F * std::sqrt(-p) * std::cos(angle + 2.0F * math::PI / 3.0F)); + roots.push_back(2.0F * std::sqrt(-p) * std::cos(angle + 4.0F * math::PI / 3.0F)); + } else { + // three real roots, at least two equal roots + if (q == 0.0F) { + roots.push_back(0.0F); + } else { + const auto root = std::cbrt(q); + roots.push_back(root); + roots.push_back(-2.0F * root); + } + } + + // return the unique positive root + for (float root : roots) { + if (root + offset >= 0.0F) { + return root + offset; + } + } + + // never reach here + return 0.0; +} + +} // namespace gi +} // namespace cc diff --git a/cocos/gi/light-probe/PolynomialSolver.h b/cocos/gi/light-probe/PolynomialSolver.h new file mode 100644 index 0000000..16e2a0b --- /dev/null +++ b/cocos/gi/light-probe/PolynomialSolver.h @@ -0,0 +1,47 @@ + +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +namespace cc { +namespace gi { + +class PolynomialSolver { +public: + /** + * solve quadratic equation: b * t^2 + c * t + d = 0 + * return the unique real positive root + */ + static float getQuadraticUniqueRoot(float b, float c, float d); + + /** + * solve cubic equation: t^3 + b * t^2 + c * t + d = 0 + * return the unique real positive root + */ + static float getCubicUniqueRoot(float b, float c, float d); +}; + +} // namespace gi +} // namespace cc diff --git a/cocos/gi/light-probe/SH.cpp b/cocos/gi/light-probe/SH.cpp new file mode 100644 index 0000000..e3c7f47 --- /dev/null +++ b/cocos/gi/light-probe/SH.cpp @@ -0,0 +1,249 @@ + +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "SH.h" +#include "base/Macros.h" + +namespace cc { +namespace gi { + +Vec3 LightProbeSampler::uniformSampleSphere(float u1, float u2) { + float z = 1.0F - 2.0F * u1; + float r = std::sqrt(std::max(0.0F, 1.0F - z * z)); + float phi = 2.0F * math::PI * u2; + + float x = r * std::cos(phi); + float y = r * std::sin(phi); + + return Vec3(x, y, z); +} + +ccstd::vector LightProbeSampler::uniformSampleSphereAll(uint32_t sampleCount) { + CC_ASSERT_GT(sampleCount, 0U); + + const auto uCount1 = static_cast(std::sqrt(sampleCount)); + const auto uCount2 = uCount1; + + ccstd::vector samples; + const auto uDelta1 = 1.0F / static_cast(uCount1); + const auto uDelta2 = 1.0F / static_cast(uCount2); + + for (auto i = 0U; i < uCount1; i++) { + const auto u1 = (static_cast(i) + 0.5F) * uDelta1; + + for (auto j = 0U; j < uCount2; j++) { + const auto u2 = (static_cast(j) + 0.5F) * uDelta2; + const auto sample = uniformSampleSphere(u1, u2); + samples.push_back(sample); + } + } + + return samples; +} + +ccstd::vector SH::basisFunctions = { + [](const Vec3& /*v*/) -> float { return 0.282095F; }, // 0.5F * std::sqrt(math::PI_INV) + [](const Vec3& v) -> float { return 0.488603F * v.y; }, // 0.5F * std::sqrt(3.0F * math::PI_INV) * v.y + [](const Vec3& v) -> float { return 0.488603F * v.z; }, // 0.5F * std::sqrt(3.0F * math::PI_INV) * v.z + [](const Vec3& v) -> float { return 0.488603F * v.x; }, // 0.5F * std::sqrt(3.0F * math::PI_INV) * v.x + [](const Vec3& v) -> float { return 1.09255F * v.y * v.x; }, // 0.5F * std::sqrt(15.0F * math::PI_INV) * v.y * v.x + [](const Vec3& v) -> float { return 1.09255F * v.y * v.z; }, // 0.5F * std::sqrt(15.0F * math::PI_INV) * v.y * v.z + [](const Vec3& v) -> float { return 0.946175F * (v.z * v.z - 1.0F / 3.0F); }, // 0.75F * std::sqrt(5.0F * math::PI_INV) * (v.z * v.z - 1.0F / 3.0F) + [](const Vec3& v) -> float { return 1.09255F * v.z * v.x; }, // 0.5F * std::sqrt(15.0F * math::PI_INV) * v.z * v.x + [](const Vec3& v) -> float { return 0.546274F * (v.x * v.x - v.y * v.y); }, // 0.25F * std::sqrt(15.0F * math::PI_INV) * (v.x * v.x - v.y * v.y) +}; + +ccstd::vector SH::basisOverPI = { + 0.0897936F, // 0.282095 / Math.PI + 0.155527F, // 0.488603 / Math.PI + 0.155527F, // 0.488603 / Math.PI + 0.155527F, // 0.488603 / Math.PI + 0.347769F, // 1.09255 / Math.PI + 0.347769F, // 1.09255 / Math.PI + 0.301177F, // 0.946175 / Math.PI + 0.347769F, // 1.09255 / Math.PI + 0.173884F, // 0.546274 / Math.PI +}; + +void SH::updateUBOData(Float32Array& data, int32_t offset, ccstd::vector& coefficients) { + // cc_sh_linear_const_r + data[offset++] = coefficients[3].x * basisOverPI[3]; + data[offset++] = coefficients[1].x * basisOverPI[1]; + data[offset++] = coefficients[2].x * basisOverPI[2]; + data[offset++] = coefficients[0].x * basisOverPI[0] - coefficients[6].x * basisOverPI[6] / 3.0F; + + // cc_sh_linear_const_g + data[offset++] = coefficients[3].y * basisOverPI[3]; + data[offset++] = coefficients[1].y * basisOverPI[1]; + data[offset++] = coefficients[2].y * basisOverPI[2]; + data[offset++] = coefficients[0].y * basisOverPI[0] - coefficients[6].y * basisOverPI[6] / 3.0F; + + // cc_sh_linear_const_b + data[offset++] = coefficients[3].z * basisOverPI[3]; + data[offset++] = coefficients[1].z * basisOverPI[1]; + data[offset++] = coefficients[2].z * basisOverPI[2]; + data[offset++] = coefficients[0].z * basisOverPI[0] - coefficients[6].z * basisOverPI[6] / 3.0F; + + // cc_sh_quadratic_r + data[offset++] = coefficients[4].x * basisOverPI[4]; + data[offset++] = coefficients[5].x * basisOverPI[5]; + data[offset++] = coefficients[6].x * basisOverPI[6]; + data[offset++] = coefficients[7].x * basisOverPI[7]; + + // cc_sh_quadratic_g + data[offset++] = coefficients[4].y * basisOverPI[4]; + data[offset++] = coefficients[5].y * basisOverPI[5]; + data[offset++] = coefficients[6].y * basisOverPI[6]; + data[offset++] = coefficients[7].y * basisOverPI[7]; + + // cc_sh_quadratic_b + data[offset++] = coefficients[4].z * basisOverPI[4]; + data[offset++] = coefficients[5].z * basisOverPI[5]; + data[offset++] = coefficients[6].z * basisOverPI[6]; + data[offset++] = coefficients[7].z * basisOverPI[7]; + + // cc_sh_quadratic_a + data[offset++] = coefficients[8].x * basisOverPI[8]; + data[offset++] = coefficients[8].y * basisOverPI[8]; + data[offset++] = coefficients[8].z * basisOverPI[8]; + data[offset++] = 0.0; +} + +Vec3 SH::shaderEvaluate(const Vec3& normal, ccstd::vector& coefficients) { + const Vec4 linearConstR = { + coefficients[3].x * basisOverPI[3], + coefficients[1].x * basisOverPI[1], + coefficients[2].x * basisOverPI[2], + coefficients[0].x * basisOverPI[0] - coefficients[6].x * basisOverPI[6] / 3.0F}; + + const Vec4 linearConstG = { + coefficients[3].y * basisOverPI[3], + coefficients[1].y * basisOverPI[1], + coefficients[2].y * basisOverPI[2], + coefficients[0].y * basisOverPI[0] - coefficients[6].y * basisOverPI[6] / 3.0F}; + + const Vec4 linearConstB = { + coefficients[3].z * basisOverPI[3], + coefficients[1].z * basisOverPI[1], + coefficients[2].z * basisOverPI[2], + coefficients[0].z * basisOverPI[0] - coefficients[6].z * basisOverPI[6] / 3.0F}; + + const Vec4 quadraticR = { + coefficients[4].x * basisOverPI[4], + coefficients[5].x * basisOverPI[5], + coefficients[6].x * basisOverPI[6], + coefficients[7].x * basisOverPI[7]}; + + const Vec4 quadraticG = { + coefficients[4].y * basisOverPI[4], + coefficients[5].y * basisOverPI[5], + coefficients[6].y * basisOverPI[6], + coefficients[7].y * basisOverPI[7]}; + + const Vec4 quadraticB = { + coefficients[4].z * basisOverPI[4], + coefficients[5].z * basisOverPI[5], + coefficients[6].z * basisOverPI[6], + coefficients[7].z * basisOverPI[7]}; + + const Vec3 quadraticA = { + coefficients[8].x * basisOverPI[8], + coefficients[8].y * basisOverPI[8], + coefficients[8].z * basisOverPI[8]}; + + Vec3 result{0.0F, 0.0F, 0.0F}; + Vec4 normal4{normal.x, normal.y, normal.z, 1.0F}; + + // calculate linear and const terms + result.x = linearConstR.dot(normal4); + result.y = linearConstG.dot(normal4); + result.z = linearConstB.dot(normal4); + + // calculate quadratic terms + Vec4 n14{normal.x * normal.y, normal.y * normal.z, normal.z * normal.z, normal.z * normal.x}; + float n5 = normal.x * normal.x - normal.y * normal.y; + + result.x += quadraticR.dot(n14); + result.y += quadraticG.dot(n14); + result.z += quadraticB.dot(n14); + result += quadraticA * n5; + + return result; +} + +Vec3 SH::evaluate(const Vec3& sample, const ccstd::vector& coefficients) { + Vec3 result{0.0F, 0.0F, 0.0F}; + + const auto size = coefficients.size(); + for (auto i = 0; i < size; i++) { + const Vec3& c = coefficients[i]; + result += c * evaluateBasis(i, sample); + } + + return result; +} + +ccstd::vector SH::project(const ccstd::vector& samples, const ccstd::vector& values) { + CC_ASSERT(!samples.empty() && samples.size() == values.size()); + + // integral using Monte Carlo method + const auto basisCount = getBasisCount(); + const auto sampleCount = samples.size(); + const auto scale = 1.0F / (LightProbeSampler::uniformSpherePdf() * static_cast(sampleCount)); + + ccstd::vector coefficients; + coefficients.reserve(basisCount); + + for (auto i = 0; i < basisCount; i++) { + Vec3 coefficient{0.0F, 0.0F, 0.0F}; + + for (auto k = 0; k < sampleCount; k++) { + coefficient += values[k] * evaluateBasis(i, samples[k]); + } + + coefficient *= scale; + coefficients.push_back(coefficient); + } + + return coefficients; +} + +ccstd::vector SH::convolveCosine(const ccstd::vector& radianceCoefficients) { + static const float COS_THETA[3] = {0.8862268925F, 1.0233267546F, 0.4954159260F}; + ccstd::vector irradianceCoefficients; + + for (auto l = 0; l <= LMAX; l++) { + for (auto m = -l; m <= l; m++) { + auto i = toIndex(l, m); + Vec3 coefficient = lambda(l) * COS_THETA[l] * radianceCoefficients[i]; + irradianceCoefficients.push_back(coefficient); + } + } + + return irradianceCoefficients; +} + +} // namespace gi +} // namespace cc diff --git a/cocos/gi/light-probe/SH.h b/cocos/gi/light-probe/SH.h new file mode 100644 index 0000000..eeebf4e --- /dev/null +++ b/cocos/gi/light-probe/SH.h @@ -0,0 +1,137 @@ + +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/std/container/vector.h" +#include "core/TypedArray.h" +#include "math/Math.h" +#include "math/Vec3.h" + +namespace cc { +namespace gi { + +#define SH_BASIS_COUNT 9 + +class LightProbeSampler { +public: + /** + * generate one sample from sphere uniformly + */ + static Vec3 uniformSampleSphere(float u1, float u2); + + /** + * generate ucount1 * ucount2 samples from sphere uniformly + */ + static ccstd::vector uniformSampleSphereAll(uint32_t sampleCount); + + /** + * probability density function of uniform distribution on spherical surface + */ + static inline float uniformSpherePdf() { return 1.0F / (4.0F * math::PI); } +}; + +/** + * Spherical Harmonics utility class + */ +class SH { +public: + using BasisFunction = std::function; + + /** + * update ubo data by coefficients + */ + static void updateUBOData(Float32Array& data, int32_t offset, ccstd::vector& coefficients); + + /** + * recreate a function from sh coefficients, which is same as SHEvaluate in shader + */ + static Vec3 shaderEvaluate(const Vec3& normal, ccstd::vector& coefficients); + + /** + * recreate a function from sh coefficients + */ + static Vec3 evaluate(const Vec3& sample, const ccstd::vector& coefficients); + + /** + * project a function to sh coefficients + */ + static ccstd::vector project(const ccstd::vector& samples, const ccstd::vector& values); + + /** + * calculate irradiance's sh coefficients from radiance's sh coefficients directly + */ + static ccstd::vector convolveCosine(const ccstd::vector& radianceCoefficients); + + /** + * return basis function count + */ + static inline uint32_t getBasisCount() { + return SH_BASIS_COUNT; + } + + /** + * evaluate from a basis function + */ + static inline float evaluateBasis(uint32_t index, const Vec3& sample) { + CC_ASSERT(index < getBasisCount()); + const auto& func = basisFunctions[index]; + + return func(sample); + } + + static inline void reduceRinging(ccstd::vector& coefficients, float lambda) { + if (lambda == 0.0F) { + return; + } + + for (int32_t l = 0; l <= LMAX; ++l) { + auto level = static_cast(l); + float scale = 1.0F / (1.0F + lambda * level * level * (level + 1) * (level + 1)); + for (int32_t m = -l; m <= l; ++m) { + const int32_t i = toIndex(l, m); + coefficients[i] *= scale; + } + } + } + +private: + static inline float lambda(int32_t l) { + return std::sqrt((4.0F * math::PI) / (2.0F * static_cast(l) + 1.0F)); + } + + static inline int32_t toIndex(int32_t l, int32_t m) { + return l * l + l + m; + } + + static constexpr int32_t LMAX = 2; + static ccstd::vector basisFunctions; + static ccstd::vector basisOverPI; +}; + +} // namespace gi +} // namespace cc diff --git a/cocos/gi/light-probe/predicates.cpp b/cocos/gi/light-probe/predicates.cpp new file mode 100644 index 0000000..bb71370 --- /dev/null +++ b/cocos/gi/light-probe/predicates.cpp @@ -0,0 +1,4710 @@ +/*****************************************************************************/ +/* */ +/* Routines for Arbitrary Precision Floating-point Arithmetic */ +/* and Fast Robust Geometric Predicates */ +/* (predicates.c) */ +/* */ +/* May 18, 1996 */ +/* */ +/* Placed in the public domain by */ +/* Jonathan Richard Shewchuk */ +/* School of Computer Science */ +/* Carnegie Mellon University */ +/* 5000 Forbes Avenue */ +/* Pittsburgh, Pennsylvania 15213-3891 */ +/* jrs@cs.cmu.edu */ +/* */ +/* This file contains C implementation of algorithms for exact addition */ +/* and multiplication of floating-point numbers, and predicates for */ +/* robustly performing the orientation and incircle tests used in */ +/* computational geometry. The algorithms and underlying theory are */ +/* described in Jonathan Richard Shewchuk. "Adaptive Precision Floating- */ +/* Point Arithmetic and Fast Robust Geometric Predicates." Technical */ +/* Report CMU-CS-96-140, School of Computer Science, Carnegie Mellon */ +/* University, Pittsburgh, Pennsylvania, May 1996. (Submitted to */ +/* Discrete & Computational Geometry.) */ +/* */ +/* This file, the paper listed above, and other information are available */ +/* from the Web page http://www.cs.cmu.edu/~quake/robust.html . */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Using this code: */ +/* */ +/* First, read the short or long version of the paper (from the Web page */ +/* above). */ +/* */ +/* Be sure to call exactinit() once, before calling any of the arithmetic */ +/* functions or geometric predicates. Also be sure to turn on the */ +/* optimizer when compiling this file. */ +/* */ +/* */ +/* Several geometric predicates are defined. Their parameters are all */ +/* points. Each point is an array of two or three floating-point */ +/* numbers. The geometric predicates, described in the papers, are */ +/* */ +/* orient2d(pa, pb, pc) */ +/* orient2dfast(pa, pb, pc) */ +/* orient3d(pa, pb, pc, pd) */ +/* orient3dfast(pa, pb, pc, pd) */ +/* incircle(pa, pb, pc, pd) */ +/* incirclefast(pa, pb, pc, pd) */ +/* insphere(pa, pb, pc, pd, pe) */ +/* inspherefast(pa, pb, pc, pd, pe) */ +/* */ +/* Those with suffix "fast" are approximate, non-robust versions. Those */ +/* without the suffix are adaptive precision, robust versions. There */ +/* are also versions with the suffices "exact" and "slow", which are */ +/* non-adaptive, exact arithmetic versions, which I use only for timings */ +/* in my arithmetic papers. */ +/* */ +/* */ +/* An expansion is represented by an array of floating-point numbers, */ +/* sorted from smallest to largest magnitude (possibly with interspersed */ +/* zeros). The length of each expansion is stored as a separate integer, */ +/* and each arithmetic function returns an integer which is the length */ +/* of the expansion it created. */ +/* */ +/* Several arithmetic functions are defined. Their parameters are */ +/* */ +/* e, f Input expansions */ +/* elen, flen Lengths of input expansions (must be >= 1) */ +/* h Output expansion */ +/* b Input scalar */ +/* */ +/* The arithmetic functions are */ +/* */ +/* grow_expansion(elen, e, b, h) */ +/* grow_expansion_zeroelim(elen, e, b, h) */ +/* expansion_sum(elen, e, flen, f, h) */ +/* expansion_sum_zeroelim1(elen, e, flen, f, h) */ +/* expansion_sum_zeroelim2(elen, e, flen, f, h) */ +/* fast_expansion_sum(elen, e, flen, f, h) */ +/* fast_expansion_sum_zeroelim(elen, e, flen, f, h) */ +/* linear_expansion_sum(elen, e, flen, f, h) */ +/* linear_expansion_sum_zeroelim(elen, e, flen, f, h) */ +/* scale_expansion(elen, e, b, h) */ +/* scale_expansion_zeroelim(elen, e, b, h) */ +/* compress(elen, e, h) */ +/* */ +/* All of these are described in the long version of the paper; some are */ +/* described in the short version. All return an integer that is the */ +/* length of h. Those with suffix _zeroelim perform zero elimination, */ +/* and are recommended over their counterparts. The procedure */ +/* fast_expansion_sum_zeroelim() (or linear_expansion_sum_zeroelim() on */ +/* processors that do not use the round-to-even tiebreaking rule) is */ +/* recommended over expansion_sum_zeroelim(). Each procedure has a */ +/* little note next to it (in the code below) that tells you whether or */ +/* not the output expansion may be the same array as one of the input */ +/* expansions. */ +/* */ +/* */ +/* If you look around below, you'll also find macros for a bunch of */ +/* simple unrolled arithmetic operations, and procedures for printing */ +/* expansions (commented out because they don't work with all C */ +/* compilers) and for generating random floating-point numbers whose */ +/* significand bits are all random. Most of the macros have undocumented */ +/* requirements that certain of their parameters should not be the same */ +/* variable; for safety, better to make sure all the parameters are */ +/* distinct variables. Feel free to send email to jrs@cs.cmu.edu if you */ +/* have questions. */ +/* */ +/*****************************************************************************/ + +#include +#include +#include +#ifdef CPU86 +#include +#endif /* CPU86 */ +#ifdef LINUX +#include +#endif /* LINUX */ + +#include "tetgen.h" // Defines the symbol REAL (float or double). + +#ifdef USE_CGAL_PREDICATES + #include + typedef CGAL::Exact_predicates_inexact_constructions_kernel cgalEpick; + typedef cgalEpick::Point_3 Point; + cgalEpick cgal_pred_obj; +#endif // #ifdef USE_CGAL_PREDICATES + +/* On some machines, the exact arithmetic routines might be defeated by the */ +/* use of internal extended precision floating-point registers. Sometimes */ +/* this problem can be fixed by defining certain values to be volatile, */ +/* thus forcing them to be stored to memory and rounded off. This isn't */ +/* a great solution, though, as it slows the arithmetic down. */ +/* */ +/* To try this out, write "#define INEXACT volatile" below. Normally, */ +/* however, INEXACT should be defined to be nothing. ("#define INEXACT".) */ + +#define INEXACT /* Nothing */ +/* #define INEXACT volatile */ + +/* #define REAL double */ /* float or double */ +#define REALPRINT doubleprint +#define REALRAND doublerand +#define NARROWRAND narrowdoublerand +#define UNIFORMRAND uniformdoublerand + +/* Which of the following two methods of finding the absolute values is */ +/* fastest is compiler-dependent. A few compilers can inline and optimize */ +/* the fabs() call; but most will incur the overhead of a function call, */ +/* which is disastrously slow. A faster way on IEEE machines might be to */ +/* mask the appropriate bit, but that's difficult to do in C. */ + +//#define Absolute(a) ((a) >= 0.0 ? (a) : -(a)) +#define Absolute(a) fabs(a) + +/* Many of the operations are broken up into two pieces, a main part that */ +/* performs an approximate operation, and a "tail" that computes the */ +/* roundoff error of that operation. */ +/* */ +/* The operations Fast_Two_Sum(), Fast_Two_Diff(), Two_Sum(), Two_Diff(), */ +/* Split(), and Two_Product() are all implemented as described in the */ +/* reference. Each of these macros requires certain variables to be */ +/* defined in the calling routine. The variables `bvirt', `c', `abig', */ +/* `_i', `_j', `_k', `_l', `_m', and `_n' are declared `INEXACT' because */ +/* they store the result of an operation that may incur roundoff error. */ +/* The input parameter `x' (or the highest numbered `x_' parameter) must */ +/* also be declared `INEXACT'. */ + +#define Fast_Two_Sum_Tail(a, b, x, y) \ + bvirt = x - a; \ + y = b - bvirt + +#define Fast_Two_Sum(a, b, x, y) \ + x = (REAL) (a + b); \ + Fast_Two_Sum_Tail(a, b, x, y) + +#define Fast_Two_Diff_Tail(a, b, x, y) \ + bvirt = a - x; \ + y = bvirt - b + +#define Fast_Two_Diff(a, b, x, y) \ + x = (REAL) (a - b); \ + Fast_Two_Diff_Tail(a, b, x, y) + +#define Two_Sum_Tail(a, b, x, y) \ + bvirt = (REAL) (x - a); \ + avirt = x - bvirt; \ + bround = b - bvirt; \ + around = a - avirt; \ + y = around + bround + +#define Two_Sum(a, b, x, y) \ + x = (REAL) (a + b); \ + Two_Sum_Tail(a, b, x, y) + +#define Two_Diff_Tail(a, b, x, y) \ + bvirt = (REAL) (a - x); \ + avirt = x + bvirt; \ + bround = bvirt - b; \ + around = a - avirt; \ + y = around + bround + +#define Two_Diff(a, b, x, y) \ + x = (REAL) (a - b); \ + Two_Diff_Tail(a, b, x, y) + +#define Split(a, ahi, alo) \ + c = (REAL) (splitter * a); \ + abig = (REAL) (c - a); \ + ahi = c - abig; \ + alo = a - ahi + +#define Two_Product_Tail(a, b, x, y) \ + Split(a, ahi, alo); \ + Split(b, bhi, blo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +#define Two_Product(a, b, x, y) \ + x = (REAL) (a * b); \ + Two_Product_Tail(a, b, x, y) + +/* Two_Product_Presplit() is Two_Product() where one of the inputs has */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_Presplit(a, b, bhi, blo, x, y) \ + x = (REAL) (a * b); \ + Split(a, ahi, alo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Two_Product_2Presplit() is Two_Product() where both of the inputs have */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_2Presplit(a, ahi, alo, b, bhi, blo, x, y) \ + x = (REAL) (a * b); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Square() can be done more quickly than Two_Product(). */ + +#define Square_Tail(a, x, y) \ + Split(a, ahi, alo); \ + err1 = x - (ahi * ahi); \ + err3 = err1 - ((ahi + ahi) * alo); \ + y = (alo * alo) - err3 + +#define Square(a, x, y) \ + x = (REAL) (a * a); \ + Square_Tail(a, x, y) + +/* Macros for summing expansions of various fixed lengths. These are all */ +/* unrolled versions of Expansion_Sum(). */ + +#define Two_One_Sum(a1, a0, b, x2, x1, x0) \ + Two_Sum(a0, b , _i, x0); \ + Two_Sum(a1, _i, x2, x1) + +#define Two_One_Diff(a1, a0, b, x2, x1, x0) \ + Two_Diff(a0, b , _i, x0); \ + Two_Sum( a1, _i, x2, x1) + +#define Two_Two_Sum(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b0, _j, _0, x0); \ + Two_One_Sum(_j, _0, b1, x3, x2, x1) + +#define Two_Two_Diff(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Diff(a1, a0, b0, _j, _0, x0); \ + Two_One_Diff(_j, _0, b1, x3, x2, x1) + +#define Four_One_Sum(a3, a2, a1, a0, b, x4, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b , _j, x1, x0); \ + Two_One_Sum(a3, a2, _j, x4, x3, x2) + +#define Four_Two_Sum(a3, a2, a1, a0, b1, b0, x5, x4, x3, x2, x1, x0) \ + Four_One_Sum(a3, a2, a1, a0, b0, _k, _2, _1, _0, x0); \ + Four_One_Sum(_k, _2, _1, _0, b1, x5, x4, x3, x2, x1) + +#define Four_Four_Sum(a3, a2, a1, a0, b4, b3, b1, b0, x7, x6, x5, x4, x3, x2, \ + x1, x0) \ + Four_Two_Sum(a3, a2, a1, a0, b1, b0, _l, _2, _1, _0, x1, x0); \ + Four_Two_Sum(_l, _2, _1, _0, b4, b3, x7, x6, x5, x4, x3, x2) + +#define Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b, x8, x7, x6, x5, x4, \ + x3, x2, x1, x0) \ + Four_One_Sum(a3, a2, a1, a0, b , _j, x3, x2, x1, x0); \ + Four_One_Sum(a7, a6, a5, a4, _j, x8, x7, x6, x5, x4) + +#define Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, x9, x8, x7, \ + x6, x5, x4, x3, x2, x1, x0) \ + Eight_One_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b0, _k, _6, _5, _4, _3, _2, \ + _1, _0, x0); \ + Eight_One_Sum(_k, _6, _5, _4, _3, _2, _1, _0, b1, x9, x8, x7, x6, x5, x4, \ + x3, x2, x1) + +#define Eight_Four_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b4, b3, b1, b0, x11, \ + x10, x9, x8, x7, x6, x5, x4, x3, x2, x1, x0) \ + Eight_Two_Sum(a7, a6, a5, a4, a3, a2, a1, a0, b1, b0, _l, _6, _5, _4, _3, \ + _2, _1, _0, x1, x0); \ + Eight_Two_Sum(_l, _6, _5, _4, _3, _2, _1, _0, b4, b3, x11, x10, x9, x8, \ + x7, x6, x5, x4, x3, x2) + +/* Macros for multiplying expansions of various fixed lengths. */ + +#define Two_One_Product(a1, a0, b, x3, x2, x1, x0) \ + Split(b, bhi, blo); \ + Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \ + Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x1); \ + Fast_Two_Sum(_j, _k, x3, x2) + +#define Four_One_Product(a3, a2, a1, a0, b, x7, x6, x5, x4, x3, x2, x1, x0) \ + Split(b, bhi, blo); \ + Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \ + Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x1); \ + Fast_Two_Sum(_j, _k, _i, x2); \ + Two_Product_Presplit(a2, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x3); \ + Fast_Two_Sum(_j, _k, _i, x4); \ + Two_Product_Presplit(a3, b, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, x5); \ + Fast_Two_Sum(_j, _k, x7, x6) + +#define Two_Two_Product(a1, a0, b1, b0, x7, x6, x5, x4, x3, x2, x1, x0) \ + Split(a0, a0hi, a0lo); \ + Split(b0, bhi, blo); \ + Two_Product_2Presplit(a0, a0hi, a0lo, b0, bhi, blo, _i, x0); \ + Split(a1, a1hi, a1lo); \ + Two_Product_2Presplit(a1, a1hi, a1lo, b0, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _k, _1); \ + Fast_Two_Sum(_j, _k, _l, _2); \ + Split(b1, bhi, blo); \ + Two_Product_2Presplit(a0, a0hi, a0lo, b1, bhi, blo, _i, _0); \ + Two_Sum(_1, _0, _k, x1); \ + Two_Sum(_2, _k, _j, _1); \ + Two_Sum(_l, _j, _m, _2); \ + Two_Product_2Presplit(a1, a1hi, a1lo, b1, bhi, blo, _j, _0); \ + Two_Sum(_i, _0, _n, _0); \ + Two_Sum(_1, _0, _i, x2); \ + Two_Sum(_2, _i, _k, _1); \ + Two_Sum(_m, _k, _l, _2); \ + Two_Sum(_j, _n, _k, _0); \ + Two_Sum(_1, _0, _j, x3); \ + Two_Sum(_2, _j, _i, _1); \ + Two_Sum(_l, _i, _m, _2); \ + Two_Sum(_1, _k, _i, x4); \ + Two_Sum(_2, _i, _k, x5); \ + Two_Sum(_m, _k, x7, x6) + +/* An expansion of length two can be squared more quickly than finding the */ +/* product of two different expansions of length two, and the result is */ +/* guaranteed to have no more than six (rather than eight) components. */ + +#define Two_Square(a1, a0, x5, x4, x3, x2, x1, x0) \ + Square(a0, _j, x0); \ + _0 = a0 + a0; \ + Two_Product(a1, _0, _k, _1); \ + Two_One_Sum(_k, _1, _j, _l, _2, x1); \ + Square(a1, _j, _1); \ + Two_Two_Sum(_j, _1, _l, _2, x5, x4, x3, x2) + +/* splitter = 2^ceiling(p / 2) + 1. Used to split floats in half. */ +static REAL splitter; +static REAL epsilon; /* = 2^(-p). Used to estimate roundoff errors. */ +/* A set of coefficients used to calculate maximum roundoff errors. */ +static REAL resulterrbound; +static REAL ccwerrboundA, ccwerrboundB, ccwerrboundC; +static REAL o3derrboundA, o3derrboundB, o3derrboundC; +static REAL iccerrboundA, iccerrboundB, iccerrboundC; +static REAL isperrboundA, isperrboundB, isperrboundC; + +// Options to choose types of geometric computtaions. +// Added by H. Si, 2012-08-23. +static int _use_inexact_arith; // -X option. +static int _use_static_filter; // Default option, disable it by -X1 + +// Static filters for orient3d() and insphere(). +// They are pre-calcualted and set in exactinit(). +// Added by H. Si, 2012-08-23. +static REAL o3dstaticfilter; +static REAL ispstaticfilter; + + + +// The following codes were part of "IEEE 754 floating-point test software" +// http://www.math.utah.edu/~beebe/software/ieee/ +// The original program was "fpinfo2.c". + +static double fppow2(int n) +{ + double x, power; + x = (n < 0) ? ((double)1.0/(double)2.0) : (double)2.0; + n = (n < 0) ? -n : n; + power = (double)1.0; + while (n-- > 0) + power *= x; + return (power); +} + +#ifdef SINGLE + +static float fstore(float x) +{ + return (x); +} + +static int test_float(int verbose) +{ + float x; + int pass = 1; + + //(void)printf("float:\n"); + + if (verbose) { + (void)printf(" sizeof(float) = %2u\n", (unsigned int)sizeof(float)); +#ifdef CPU86 // + (void)printf(" FLT_MANT_DIG = %2d\n", FLT_MANT_DIG); +#endif + } + + x = (float)1.0; + while (fstore((float)1.0 + x/(float)2.0) != (float)1.0) + x /= (float)2.0; + if (verbose) + (void)printf(" machine epsilon = %13.5e ", x); + + if (x == (float)fppow2(-23)) { + if (verbose) + (void)printf("[IEEE 754 32-bit macheps]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; + } + + x = (float)1.0; + while (fstore(x / (float)2.0) != (float)0.0) + x /= (float)2.0; + if (verbose) + (void)printf(" smallest positive number = %13.5e ", x); + + if (x == (float)fppow2(-149)) { + if (verbose) + (void)printf("[smallest 32-bit subnormal]\n"); + } else if (x == (float)fppow2(-126)) { + if (verbose) + (void)printf("[smallest 32-bit normal]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; + } + + return pass; +} + +# else + +static double dstore(double x) +{ + return (x); +} + +static int test_double(int verbose) +{ + double x; + int pass = 1; + + // (void)printf("double:\n"); + if (verbose) { + (void)printf(" sizeof(double) = %2u\n", (unsigned int)sizeof(double)); +#ifdef CPU86 // + (void)printf(" DBL_MANT_DIG = %2d\n", DBL_MANT_DIG); +#endif + } + + x = 1.0; + while (dstore(1.0 + x/2.0) != 1.0) + x /= 2.0; + if (verbose) + (void)printf(" machine epsilon = %13.5le ", x); + + if (x == (double)fppow2(-52)) { + if (verbose) + (void)printf("[IEEE 754 64-bit macheps]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; + } + + x = 1.0; + while (dstore(x / 2.0) != 0.0) + x /= 2.0; + //if (verbose) + // (void)printf(" smallest positive number = %13.5le ", x); + + if (x == (double)fppow2(-1074)) { + //if (verbose) + // (void)printf("[smallest 64-bit subnormal]\n"); + } else if (x == (double)fppow2(-1022)) { + //if (verbose) + // (void)printf("[smallest 64-bit normal]\n"); + } else { + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; + } + + return pass; +} + +#endif + +/*****************************************************************************/ +/* */ +/* exactinit() Initialize the variables used for exact arithmetic. */ +/* */ +/* `epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in */ +/* floating-point arithmetic. `epsilon' bounds the relative roundoff */ +/* error. It is used for floating-point error analysis. */ +/* */ +/* `splitter' is used to split floating-point numbers into two half- */ +/* length significands for exact multiplication. */ +/* */ +/* I imagine that a highly optimizing compiler might be too smart for its */ +/* own good, and somehow cause this routine to fail, if it pretends that */ +/* floating-point arithmetic is too much like real arithmetic. */ +/* */ +/* Don't change this routine unless you fully understand it. */ +/* */ +/*****************************************************************************/ + +void exactinit(int verbose, int noexact, int nofilter, REAL maxx, REAL maxy, + REAL maxz) +{ + REAL half; + REAL check, lastcheck; + int every_other; +#ifdef LINUX + int cword; +#endif /* LINUX */ + +#ifdef CPU86 +#ifdef SINGLE + _control87(_PC_24, _MCW_PC); /* Set FPU control word for single precision. */ +#else /* not SINGLE */ + _control87(_PC_53, _MCW_PC); /* Set FPU control word for double precision. */ +#endif /* not SINGLE */ +#endif /* CPU86 */ +#ifdef LINUX +#ifdef SINGLE + /* cword = 4223; */ + cword = 4210; /* set FPU control word for single precision */ +#else /* not SINGLE */ + /* cword = 4735; */ + cword = 4722; /* set FPU control word for double precision */ +#endif /* not SINGLE */ + _FPU_SETCW(cword); +#endif /* LINUX */ + + if (verbose) { + printf(" Initializing robust predicates.\n"); + } + +#ifdef USE_CGAL_PREDICATES + if (cgal_pred_obj.Has_static_filters) { + printf(" Use static filter.\n"); + } else { + printf(" No static filter.\n"); + } +#endif // USE_CGAL_PREDICATES + +#ifdef SINGLE + test_float(verbose); +#else + test_double(verbose); +#endif + + every_other = 1; + half = 0.5; + epsilon = 1.0; + splitter = 1.0; + check = 1.0; + /* Repeatedly divide `epsilon' by two until it is too small to add to */ + /* one without causing roundoff. (Also check if the sum is equal to */ + /* the previous sum, for machines that round up instead of using exact */ + /* rounding. Not that this library will work on such machines anyway. */ + do { + lastcheck = check; + epsilon *= half; + if (every_other) { + splitter *= 2.0; + } + every_other = !every_other; + check = 1.0 + epsilon; + } while ((check != 1.0) && (check != lastcheck)); + splitter += 1.0; + + /* Error bounds for orientation and incircle tests. */ + resulterrbound = (3.0 + 8.0 * epsilon) * epsilon; + ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon; + ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon; + ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon; + o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon; + o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon; + o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon; + iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon; + iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon; + iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon; + isperrboundA = (16.0 + 224.0 * epsilon) * epsilon; + isperrboundB = (5.0 + 72.0 * epsilon) * epsilon; + isperrboundC = (71.0 + 1408.0 * epsilon) * epsilon * epsilon; + + // Set TetGen options. Added by H. Si, 2012-08-23. + _use_inexact_arith = noexact; + _use_static_filter = !nofilter; + + // Calculate the two static filters for orient3d() and insphere() tests. + // Added by H. Si, 2012-08-23. + + // Sort maxx < maxy < maxz. Re-use 'half' for swapping. + if (maxx > maxz) { + half = maxx; maxx = maxz; maxz = half; + } + if (maxy > maxz) { + half = maxy; maxy = maxz; maxz = half; + } + else if (maxy < maxx) { + half = maxy; maxy = maxx; maxx = half; + } + + o3dstaticfilter = 5.1107127829973299e-15 * maxx * maxy * maxz; + ispstaticfilter = 1.2466136531027298e-13 * maxx * maxy * maxz * (maxz * maxz); + +} + +/*****************************************************************************/ +/* */ +/* grow_expansion() Add a scalar to an expansion. */ +/* */ +/* Sets h = e + b. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int grow_expansion(int elen, REAL *e, REAL b, REAL *h) +/* e and h can be the same. */ +{ + REAL Q; + INEXACT REAL Qnew; + int eindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = b; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, h[eindex]); + Q = Qnew; + } + h[eindex] = Q; + return eindex + 1; +} + +/*****************************************************************************/ +/* */ +/* grow_expansion_zeroelim() Add a scalar to an expansion, eliminating */ +/* zero components from the output expansion. */ +/* */ +/* Sets h = e + b. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int grow_expansion_zeroelim(int elen, REAL *e, REAL b, REAL *h) +/* e and h can be the same. */ +{ + REAL Q, hh; + INEXACT REAL Qnew; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + hindex = 0; + Q = b; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* e and h can be the same, but f and h cannot. */ +{ + REAL Q; + INEXACT REAL Qnew; + int findex, hindex, hlast; + REAL hnow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = f[0]; + for (hindex = 0; hindex < elen; hindex++) { + hnow = e[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + Q = f[findex]; + for (hindex = findex; hindex <= hlast; hindex++) { + hnow = h[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[++hlast] = Q; + } + return hlast + 1; +} + +/*****************************************************************************/ +/* */ +/* expansion_sum_zeroelim1() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum_zeroelim1(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* e and h can be the same, but f and h cannot. */ +{ + REAL Q; + INEXACT REAL Qnew; + int index, findex, hindex, hlast; + REAL hnow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + Q = f[0]; + for (hindex = 0; hindex < elen; hindex++) { + hnow = e[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + Q = f[findex]; + for (hindex = findex; hindex <= hlast; hindex++) { + hnow = h[hindex]; + Two_Sum(Q, hnow, Qnew, h[hindex]); + Q = Qnew; + } + h[++hlast] = Q; + } + hindex = -1; + for (index = 0; index <= hlast; index++) { + hnow = h[index]; + if (hnow != 0.0) { + h[++hindex] = hnow; + } + } + if (hindex == -1) { + return 1; + } else { + return hindex + 1; + } +} + +/*****************************************************************************/ +/* */ +/* expansion_sum_zeroelim2() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the nonadjacent property as well. (That is, */ +/* if e has one of these properties, so will h.) Does NOT maintain the */ +/* strongly nonoverlapping property. */ +/* */ +/*****************************************************************************/ + +int expansion_sum_zeroelim2(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* e and h can be the same, but f and h cannot. */ +{ + REAL Q, hh; + INEXACT REAL Qnew; + int eindex, findex, hindex, hlast; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + + hindex = 0; + Q = f[0]; + for (eindex = 0; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + h[hindex] = Q; + hlast = hindex; + for (findex = 1; findex < flen; findex++) { + hindex = 0; + Q = f[findex]; + for (eindex = 0; eindex <= hlast; eindex++) { + enow = h[eindex]; + Two_Sum(Q, enow, Qnew, hh); + Q = Qnew; + if (hh != 0) { + h[hindex++] = hh; + } + } + h[hindex] = Q; + hlast = hindex; + } + return hlast + 1; +} + +/*****************************************************************************/ +/* */ +/* fast_expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* If round-to-even is used (as with IEEE 754), maintains the strongly */ +/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */ +/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */ +/* properties. */ +/* */ +/*****************************************************************************/ + +static int fast_expansion_sum(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* h cannot be e or f. */ +{ + REAL Q; + INEXACT REAL Qnew; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Fast_Two_Sum(enow, Q, Qnew, h[0]); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, Q, Qnew, h[0]); + fnow = f[++findex]; + } + Q = Qnew; + hindex = 1; + while ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Two_Sum(Q, enow, Qnew, h[hindex]); + enow = e[++eindex]; + } else { + Two_Sum(Q, fnow, Qnew, h[hindex]); + fnow = f[++findex]; + } + Q = Qnew; + hindex++; + } + } + while (eindex < elen) { + Two_Sum(Q, enow, Qnew, h[hindex]); + enow = e[++eindex]; + Q = Qnew; + hindex++; + } + while (findex < flen) { + Two_Sum(Q, fnow, Qnew, h[hindex]); + fnow = f[++findex]; + Q = Qnew; + hindex++; + } + h[hindex] = Q; + return hindex + 1; +} + +/*****************************************************************************/ +/* */ +/* fast_expansion_sum_zeroelim() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See the long version of my paper for details. */ +/* */ +/* If round-to-even is used (as with IEEE 754), maintains the strongly */ +/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */ +/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */ +/* properties. */ +/* */ +/*****************************************************************************/ +static +int fast_expansion_sum_zeroelim(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* h cannot be e or f. */ +{ + REAL Q; + INEXACT REAL Qnew; + INEXACT REAL hh; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Fast_Two_Sum(enow, Q, Qnew, hh); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, Q, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + while ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + } else { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + } + while (eindex < elen) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + while (findex < flen) { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* linear_expansion_sum() Sum two expansions. */ +/* */ +/* Sets h = e + f. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. (That is, if e is */ +/* nonoverlapping, h will be also.) */ +/* */ +/*****************************************************************************/ + +int linear_expansion_sum(int elen, REAL *e, int flen, REAL *f, REAL *h) +/* h cannot be e or f. */ +{ + REAL Q, q; + INEXACT REAL Qnew; + INEXACT REAL R; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + REAL g0; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + g0 = enow; + enow = e[++eindex]; + } else { + g0 = fnow; + fnow = f[++findex]; + } + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, g0, Qnew, q); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, g0, Qnew, q); + fnow = f[++findex]; + } + Q = Qnew; + for (hindex = 0; hindex < elen + flen - 2; hindex++) { + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, q, R, h[hindex]); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, q, R, h[hindex]); + fnow = f[++findex]; + } + Two_Sum(Q, R, Qnew, q); + Q = Qnew; + } + h[hindex] = q; + h[hindex + 1] = Q; + return hindex + 2; +} + +/*****************************************************************************/ +/* */ +/* linear_expansion_sum_zeroelim() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. (That is, if e is */ +/* nonoverlapping, h will be also.) */ +/* */ +/*****************************************************************************/ + +int linear_expansion_sum_zeroelim(int elen, REAL *e, int flen, REAL *f, + REAL *h) +/* h cannot be e or f. */ +{ + REAL Q, q, hh; + INEXACT REAL Qnew; + INEXACT REAL R; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + int count; + REAL enow, fnow; + REAL g0; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + hindex = 0; + if ((fnow > enow) == (fnow > -enow)) { + g0 = enow; + enow = e[++eindex]; + } else { + g0 = fnow; + fnow = f[++findex]; + } + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, g0, Qnew, q); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, g0, Qnew, q); + fnow = f[++findex]; + } + Q = Qnew; + for (count = 2; count < elen + flen; count++) { + if ((eindex < elen) && ((findex >= flen) + || ((fnow > enow) == (fnow > -enow)))) { + Fast_Two_Sum(enow, q, R, hh); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, q, R, hh); + fnow = f[++findex]; + } + Two_Sum(Q, R, Qnew, q); + Q = Qnew; + if (hh != 0) { + h[hindex++] = hh; + } + } + if (q != 0) { + h[hindex++] = q; + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* scale_expansion() Multiply an expansion by a scalar. */ +/* */ +/* Sets h = be. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ +static +int scale_expansion(int elen, REAL *e, REAL b, REAL *h) +/* e and h cannot be the same. */ +{ + INEXACT REAL Q; + INEXACT REAL sum; + INEXACT REAL product1; + REAL product0; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + + Split(b, bhi, blo); + Two_Product_Presplit(e[0], b, bhi, blo, Q, h[0]); + hindex = 1; + for (eindex = 1; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Product_Presplit(enow, b, bhi, blo, product1, product0); + Two_Sum(Q, product0, sum, h[hindex]); + hindex++; + Two_Sum(product1, sum, Q, h[hindex]); + hindex++; + } + h[hindex] = Q; + return elen + elen; +} + +/*****************************************************************************/ +/* */ +/* scale_expansion_zeroelim() Multiply an expansion by a scalar, */ +/* eliminating zero components from the */ +/* output expansion. */ +/* */ +/* Sets h = be. See either version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ +static +int scale_expansion_zeroelim(int elen, REAL *e, REAL b, REAL *h) +/* e and h cannot be the same. */ +{ + INEXACT REAL Q, sum; + REAL hh; + INEXACT REAL product1; + REAL product0; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + + Split(b, bhi, blo); + Two_Product_Presplit(e[0], b, bhi, blo, Q, hh); + hindex = 0; + if (hh != 0) { + h[hindex++] = hh; + } + for (eindex = 1; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Product_Presplit(enow, b, bhi, blo, product1, product0); + Two_Sum(Q, product0, sum, hh); + if (hh != 0) { + h[hindex++] = hh; + } + Fast_Two_Sum(product1, sum, Q, hh); + if (hh != 0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* compress() Compress an expansion. */ +/* */ +/* See the long version of my paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), then any nonoverlapping expansion is converted to a */ +/* nonadjacent expansion. */ +/* */ +/*****************************************************************************/ +static +int compress(int elen, REAL *e, REAL *h) +/* e and h may be the same. */ +{ + REAL Q, q; + INEXACT REAL Qnew; + int eindex, hindex; + INEXACT REAL bvirt; + REAL enow, hnow; + int top, bottom; + + bottom = elen - 1; + Q = e[bottom]; + for (eindex = elen - 2; eindex >= 0; eindex--) { + enow = e[eindex]; + Fast_Two_Sum(Q, enow, Qnew, q); + if (q != 0) { + h[bottom--] = Qnew; + Q = q; + } else { + Q = Qnew; + } + } + top = 0; + for (hindex = bottom + 1; hindex < elen; hindex++) { + hnow = h[hindex]; + Fast_Two_Sum(hnow, Q, Qnew, q); + if (q != 0) { + h[top++] = q; + } + Q = Qnew; + } + h[top] = Q; + return top + 1; +} + +/*****************************************************************************/ +/* */ +/* estimate() Produce a one-word estimate of an expansion's value. */ +/* */ +/* See either version of my paper for details. */ +/* */ +/*****************************************************************************/ +static +REAL estimate(int elen, REAL *e) +{ + REAL Q; + int eindex; + + Q = e[0]; + for (eindex = 1; eindex < elen; eindex++) { + Q += e[eindex]; + } + return Q; +} + +/*****************************************************************************/ +/* */ +/* orient2dfast() Approximate 2D orientation test. Nonrobust. */ +/* orient2dexact() Exact 2D orientation test. Robust. */ +/* orient2dslow() Another exact 2D orientation test. Robust. */ +/* orient2d() Adaptive exact 2D orientation test. Robust. */ +/* */ +/* Return a positive value if the points pa, pb, and pc occur */ +/* in counterclockwise order; a negative value if they occur */ +/* in clockwise order; and zero if they are collinear. The */ +/* result is also a rough approximation of twice the signed */ +/* area of the triangle defined by the three points. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In orient2d() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, orient2d() is usually quite */ +/* fast, but will run more slowly when the input points are collinear or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ +static +REAL orient2dfast(REAL *pa, REAL *pb, REAL *pc) +{ + REAL acx, bcx, acy, bcy; + + acx = pa[0] - pc[0]; + bcx = pb[0] - pc[0]; + acy = pa[1] - pc[1]; + bcy = pb[1] - pc[1]; + return acx * bcy - acy * bcx; +} + +//static +REAL orient2dexact(REAL *pa, REAL *pb, REAL *pc) +{ + INEXACT REAL axby1, axcy1, bxcy1, bxay1, cxay1, cxby1; + REAL axby0, axcy0, bxcy0, bxay0, cxay0, cxby0; + REAL aterms[4], bterms[4], cterms[4]; + INEXACT REAL aterms3, bterms3, cterms3; + REAL v[8], w[12]; + int vlength, wlength; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Two_Diff(axby1, axby0, axcy1, axcy0, + aterms3, aterms[2], aterms[1], aterms[0]); + aterms[3] = aterms3; + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(bxcy1, bxcy0, bxay1, bxay0, + bterms3, bterms[2], bterms[1], bterms[0]); + bterms[3] = bterms3; + + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(cxay1, cxay0, cxby1, cxby0, + cterms3, cterms[2], cterms[1], cterms[0]); + cterms[3] = cterms3; + + vlength = fast_expansion_sum_zeroelim(4, aterms, 4, bterms, v); + wlength = fast_expansion_sum_zeroelim(vlength, v, 4, cterms, w); + + return w[wlength - 1]; +} + +REAL orient2dslow(REAL *pa, REAL *pb, REAL *pc) +{ + INEXACT REAL acx, acy, bcx, bcy; + REAL acxtail, acytail; + REAL bcxtail, bcytail; + REAL negate, negatetail; + REAL axby[8], bxay[8]; + INEXACT REAL axby7, bxay7; + REAL deter[16]; + int deterlen; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pc[0], acx, acxtail); + Two_Diff(pa[1], pc[1], acy, acytail); + Two_Diff(pb[0], pc[0], bcx, bcxtail); + Two_Diff(pb[1], pc[1], bcy, bcytail); + + Two_Two_Product(acx, acxtail, bcy, bcytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -acy; + negatetail = -acytail; + Two_Two_Product(bcx, bcxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + + deterlen = fast_expansion_sum_zeroelim(8, axby, 8, bxay, deter); + + return deter[deterlen - 1]; +} + +REAL orient2dadapt(REAL *pa, REAL *pb, REAL *pc, REAL detsum) +{ + INEXACT REAL acx, acy, bcx, bcy; + REAL acxtail, acytail, bcxtail, bcytail; + INEXACT REAL detleft, detright; + REAL detlefttail, detrighttail; + REAL det, errbound; + REAL B[4], C1[8], C2[12], D[16]; + INEXACT REAL B3; + int C1length, C2length, Dlength; + REAL u[4]; + INEXACT REAL u3; + INEXACT REAL s1, t1; + REAL s0, t0; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + acx = (REAL) (pa[0] - pc[0]); + bcx = (REAL) (pb[0] - pc[0]); + acy = (REAL) (pa[1] - pc[1]); + bcy = (REAL) (pb[1] - pc[1]); + + Two_Product(acx, bcy, detleft, detlefttail); + Two_Product(acy, bcx, detright, detrighttail); + + Two_Two_Diff(detleft, detlefttail, detright, detrighttail, + B3, B[2], B[1], B[0]); + B[3] = B3; + + det = estimate(4, B); + errbound = ccwerrboundB * detsum; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pc[0], acx, acxtail); + Two_Diff_Tail(pb[0], pc[0], bcx, bcxtail); + Two_Diff_Tail(pa[1], pc[1], acy, acytail); + Two_Diff_Tail(pb[1], pc[1], bcy, bcytail); + + if ((acxtail == 0.0) && (acytail == 0.0) + && (bcxtail == 0.0) && (bcytail == 0.0)) { + return det; + } + + errbound = ccwerrboundC * detsum + resulterrbound * Absolute(det); + det += (acx * bcytail + bcy * acxtail) + - (acy * bcxtail + bcx * acytail); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Product(acxtail, bcy, s1, s0); + Two_Product(acytail, bcx, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C1length = fast_expansion_sum_zeroelim(4, B, 4, u, C1); + + Two_Product(acx, bcytail, s1, s0); + Two_Product(acy, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C2length = fast_expansion_sum_zeroelim(C1length, C1, 4, u, C2); + + Two_Product(acxtail, bcytail, s1, s0); + Two_Product(acytail, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + Dlength = fast_expansion_sum_zeroelim(C2length, C2, 4, u, D); + + return(D[Dlength - 1]); +} + +REAL orient2d(REAL *pa, REAL *pb, REAL *pc) +{ + REAL detleft, detright, det; + REAL detsum, errbound; + + detleft = (pa[0] - pc[0]) * (pb[1] - pc[1]); + detright = (pa[1] - pc[1]) * (pb[0] - pc[0]); + det = detleft - detright; + + if (detleft > 0.0) { + if (detright <= 0.0) { + return det; + } else { + detsum = detleft + detright; + } + } else if (detleft < 0.0) { + if (detright >= 0.0) { + return det; + } else { + detsum = -detleft - detright; + } + } else { + return det; + } + + errbound = ccwerrboundA * detsum; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return orient2dadapt(pa, pb, pc, detsum); +} + +/*****************************************************************************/ +/* */ +/* orient3dfast() Approximate 3D orientation test. Nonrobust. */ +/* orient3dexact() Exact 3D orientation test. Robust. */ +/* orient3dslow() Another exact 3D orientation test. Robust. */ +/* orient3d() Adaptive exact 3D orientation test. Robust. */ +/* */ +/* Return a positive value if the point pd lies below the */ +/* plane passing through pa, pb, and pc; "below" is defined so */ +/* that pa, pb, and pc appear in counterclockwise order when */ +/* viewed from above the plane. Returns a negative value if */ +/* pd lies above the plane. Returns zero if the points are */ +/* coplanar. The result is also a rough approximation of six */ +/* times the signed volume of the tetrahedron defined by the */ +/* four points. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In orient3d() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, orient3d() is usually quite */ +/* fast, but will run more slowly when the input points are coplanar or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, bdx, cdx; + REAL ady, bdy, cdy; + REAL adz, bdz, cdz; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + return adx * (bdy * cdz - bdz * cdy) + + bdx * (cdy * adz - cdz * ady) + + cdx * (ady * bdz - adz * bdy); +} + +REAL orient3dexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxay1, axcy1, bxdy1; + INEXACT REAL bxay1, cxby1, dxcy1, axdy1, cxay1, dxby1; + REAL axby0, bxcy0, cxdy0, dxay0, axcy0, bxdy0; + REAL bxay0, cxby0, dxcy0, axdy0, cxay0, dxby0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + REAL temp8[8]; + int templen; + REAL abc[12], bcd[12], cda[12], dab[12]; + int abclen, bcdlen, cdalen, dablen; + REAL adet[24], bdet[24], cdet[24], ddet[24]; + int alen, blen, clen, dlen; + REAL abdet[48], cddet[48]; + int ablen, cdlen; + REAL deter[96]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + templen = fast_expansion_sum_zeroelim(4, cd, 4, da, temp8); + cdalen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, cda); + templen = fast_expansion_sum_zeroelim(4, da, 4, ab, temp8); + dablen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, dab); + for (i = 0; i < 4; i++) { + bd[i] = -bd[i]; + ac[i] = -ac[i]; + } + templen = fast_expansion_sum_zeroelim(4, ab, 4, bc, temp8); + abclen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, abc); + templen = fast_expansion_sum_zeroelim(4, bc, 4, cd, temp8); + bcdlen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, bcd); + + alen = scale_expansion_zeroelim(bcdlen, bcd, pa[2], adet); + blen = scale_expansion_zeroelim(cdalen, cda, -pb[2], bdet); + clen = scale_expansion_zeroelim(dablen, dab, pc[2], cdet); + dlen = scale_expansion_zeroelim(abclen, abc, -pd[2], ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL orient3dslow(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + INEXACT REAL adx, ady, adz, bdx, bdy, bdz, cdx, cdy, cdz; + REAL adxtail, adytail, adztail; + REAL bdxtail, bdytail, bdztail; + REAL cdxtail, cdytail, cdztail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, axcy7, bxay7, cxby7, cxay7; + REAL axby[8], bxcy[8], axcy[8], bxay[8], cxby[8], cxay[8]; + REAL temp16[16], temp32[32], temp32t[32]; + int temp16len, temp32len, temp32tlen; + REAL adet[64], bdet[64], cdet[64]; + int alen, blen, clen; + REAL abdet[128]; + int ablen; + REAL deter[192]; + int deterlen; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pd[0], adx, adxtail); + Two_Diff(pa[1], pd[1], ady, adytail); + Two_Diff(pa[2], pd[2], adz, adztail); + Two_Diff(pb[0], pd[0], bdx, bdxtail); + Two_Diff(pb[1], pd[1], bdy, bdytail); + Two_Diff(pb[2], pd[2], bdz, bdztail); + Two_Diff(pc[0], pd[0], cdx, cdxtail); + Two_Diff(pc[1], pd[1], cdy, cdytail); + Two_Diff(pc[2], pd[2], cdz, cdztail); + + Two_Two_Product(adx, adxtail, bdy, bdytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -ady; + negatetail = -adytail; + Two_Two_Product(bdx, bdxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + Two_Two_Product(bdx, bdxtail, cdy, cdytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bdy; + negatetail = -bdytail; + Two_Two_Product(cdx, cdxtail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + Two_Two_Product(cdx, cdxtail, ady, adytail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + negate = -cdy; + negatetail = -cdytail; + Two_Two_Product(adx, adxtail, negate, negatetail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + + temp16len = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, adz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, adztail, temp32t); + alen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + adet); + + temp16len = fast_expansion_sum_zeroelim(8, cxay, 8, axcy, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, bdz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, bdztail, temp32t); + blen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + bdet); + + temp16len = fast_expansion_sum_zeroelim(8, axby, 8, bxay, temp16); + temp32len = scale_expansion_zeroelim(temp16len, temp16, cdz, temp32); + temp32tlen = scale_expansion_zeroelim(temp16len, temp16, cdztail, temp32t); + clen = fast_expansion_sum_zeroelim(temp32len, temp32, temp32tlen, temp32t, + cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, deter); + + return deter[deterlen - 1]; +} + +REAL orient3dadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL permanent) +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL adet[8], bdet[8], cdet[8]; + int alen, blen, clen; + REAL abdet[16]; + int ablen; + REAL *finnow, *finother, *finswap; + REAL fin1[192], fin2[192]; + int finlength; + + + REAL adxtail, bdxtail, cdxtail; + REAL adytail, bdytail, cdytail; + REAL adztail, bdztail, cdztail; + INEXACT REAL at_blarge, at_clarge; + INEXACT REAL bt_clarge, bt_alarge; + INEXACT REAL ct_alarge, ct_blarge; + REAL at_b[4], at_c[4], bt_c[4], bt_a[4], ct_a[4], ct_b[4]; + int at_blen, at_clen, bt_clen, bt_alen, ct_alen, ct_blen; + INEXACT REAL bdxt_cdy1, cdxt_bdy1, cdxt_ady1; + INEXACT REAL adxt_cdy1, adxt_bdy1, bdxt_ady1; + REAL bdxt_cdy0, cdxt_bdy0, cdxt_ady0; + REAL adxt_cdy0, adxt_bdy0, bdxt_ady0; + INEXACT REAL bdyt_cdx1, cdyt_bdx1, cdyt_adx1; + INEXACT REAL adyt_cdx1, adyt_bdx1, bdyt_adx1; + REAL bdyt_cdx0, cdyt_bdx0, cdyt_adx0; + REAL adyt_cdx0, adyt_bdx0, bdyt_adx0; + REAL bct[8], cat[8], abt[8]; + int bctlen, catlen, abtlen; + INEXACT REAL bdxt_cdyt1, cdxt_bdyt1, cdxt_adyt1; + INEXACT REAL adxt_cdyt1, adxt_bdyt1, bdxt_adyt1; + REAL bdxt_cdyt0, cdxt_bdyt0, cdxt_adyt0; + REAL adxt_cdyt0, adxt_bdyt0, bdxt_adyt0; + REAL u[4], v[12], w[16]; + INEXACT REAL u3; + int vlength, wlength; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k; + REAL _0; + + + adx = (REAL) (pa[0] - pd[0]); + bdx = (REAL) (pb[0] - pd[0]); + cdx = (REAL) (pc[0] - pd[0]); + ady = (REAL) (pa[1] - pd[1]); + bdy = (REAL) (pb[1] - pd[1]); + cdy = (REAL) (pc[1] - pd[1]); + adz = (REAL) (pa[2] - pd[2]); + bdz = (REAL) (pb[2] - pd[2]); + cdz = (REAL) (pc[2] - pd[2]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + alen = scale_expansion_zeroelim(4, bc, adz, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + blen = scale_expansion_zeroelim(4, ca, bdz, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + clen = scale_expansion_zeroelim(4, ab, cdz, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = o3derrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + Two_Diff_Tail(pa[2], pd[2], adz, adztail); + Two_Diff_Tail(pb[2], pd[2], bdz, bdztail); + Two_Diff_Tail(pc[2], pd[2], cdz, cdztail); + + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0) + && (adztail == 0.0) && (bdztail == 0.0) && (cdztail == 0.0)) { + return det; + } + + errbound = o3derrboundC * permanent + resulterrbound * Absolute(det); + det += (adz * ((bdx * cdytail + cdy * bdxtail) + - (bdy * cdxtail + cdx * bdytail)) + + adztail * (bdx * cdy - bdy * cdx)) + + (bdz * ((cdx * adytail + ady * cdxtail) + - (cdy * adxtail + adx * cdytail)) + + bdztail * (cdx * ady - cdy * adx)) + + (cdz * ((adx * bdytail + bdy * adxtail) + - (ady * bdxtail + bdx * adytail)) + + cdztail * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if (adxtail == 0.0) { + if (adytail == 0.0) { + at_b[0] = 0.0; + at_blen = 1; + at_c[0] = 0.0; + at_clen = 1; + } else { + negate = -adytail; + Two_Product(negate, bdx, at_blarge, at_b[0]); + at_b[1] = at_blarge; + at_blen = 2; + Two_Product(adytail, cdx, at_clarge, at_c[0]); + at_c[1] = at_clarge; + at_clen = 2; + } + } else { + if (adytail == 0.0) { + Two_Product(adxtail, bdy, at_blarge, at_b[0]); + at_b[1] = at_blarge; + at_blen = 2; + negate = -adxtail; + Two_Product(negate, cdy, at_clarge, at_c[0]); + at_c[1] = at_clarge; + at_clen = 2; + } else { + Two_Product(adxtail, bdy, adxt_bdy1, adxt_bdy0); + Two_Product(adytail, bdx, adyt_bdx1, adyt_bdx0); + Two_Two_Diff(adxt_bdy1, adxt_bdy0, adyt_bdx1, adyt_bdx0, + at_blarge, at_b[2], at_b[1], at_b[0]); + at_b[3] = at_blarge; + at_blen = 4; + Two_Product(adytail, cdx, adyt_cdx1, adyt_cdx0); + Two_Product(adxtail, cdy, adxt_cdy1, adxt_cdy0); + Two_Two_Diff(adyt_cdx1, adyt_cdx0, adxt_cdy1, adxt_cdy0, + at_clarge, at_c[2], at_c[1], at_c[0]); + at_c[3] = at_clarge; + at_clen = 4; + } + } + if (bdxtail == 0.0) { + if (bdytail == 0.0) { + bt_c[0] = 0.0; + bt_clen = 1; + bt_a[0] = 0.0; + bt_alen = 1; + } else { + negate = -bdytail; + Two_Product(negate, cdx, bt_clarge, bt_c[0]); + bt_c[1] = bt_clarge; + bt_clen = 2; + Two_Product(bdytail, adx, bt_alarge, bt_a[0]); + bt_a[1] = bt_alarge; + bt_alen = 2; + } + } else { + if (bdytail == 0.0) { + Two_Product(bdxtail, cdy, bt_clarge, bt_c[0]); + bt_c[1] = bt_clarge; + bt_clen = 2; + negate = -bdxtail; + Two_Product(negate, ady, bt_alarge, bt_a[0]); + bt_a[1] = bt_alarge; + bt_alen = 2; + } else { + Two_Product(bdxtail, cdy, bdxt_cdy1, bdxt_cdy0); + Two_Product(bdytail, cdx, bdyt_cdx1, bdyt_cdx0); + Two_Two_Diff(bdxt_cdy1, bdxt_cdy0, bdyt_cdx1, bdyt_cdx0, + bt_clarge, bt_c[2], bt_c[1], bt_c[0]); + bt_c[3] = bt_clarge; + bt_clen = 4; + Two_Product(bdytail, adx, bdyt_adx1, bdyt_adx0); + Two_Product(bdxtail, ady, bdxt_ady1, bdxt_ady0); + Two_Two_Diff(bdyt_adx1, bdyt_adx0, bdxt_ady1, bdxt_ady0, + bt_alarge, bt_a[2], bt_a[1], bt_a[0]); + bt_a[3] = bt_alarge; + bt_alen = 4; + } + } + if (cdxtail == 0.0) { + if (cdytail == 0.0) { + ct_a[0] = 0.0; + ct_alen = 1; + ct_b[0] = 0.0; + ct_blen = 1; + } else { + negate = -cdytail; + Two_Product(negate, adx, ct_alarge, ct_a[0]); + ct_a[1] = ct_alarge; + ct_alen = 2; + Two_Product(cdytail, bdx, ct_blarge, ct_b[0]); + ct_b[1] = ct_blarge; + ct_blen = 2; + } + } else { + if (cdytail == 0.0) { + Two_Product(cdxtail, ady, ct_alarge, ct_a[0]); + ct_a[1] = ct_alarge; + ct_alen = 2; + negate = -cdxtail; + Two_Product(negate, bdy, ct_blarge, ct_b[0]); + ct_b[1] = ct_blarge; + ct_blen = 2; + } else { + Two_Product(cdxtail, ady, cdxt_ady1, cdxt_ady0); + Two_Product(cdytail, adx, cdyt_adx1, cdyt_adx0); + Two_Two_Diff(cdxt_ady1, cdxt_ady0, cdyt_adx1, cdyt_adx0, + ct_alarge, ct_a[2], ct_a[1], ct_a[0]); + ct_a[3] = ct_alarge; + ct_alen = 4; + Two_Product(cdytail, bdx, cdyt_bdx1, cdyt_bdx0); + Two_Product(cdxtail, bdy, cdxt_bdy1, cdxt_bdy0); + Two_Two_Diff(cdyt_bdx1, cdyt_bdx0, cdxt_bdy1, cdxt_bdy0, + ct_blarge, ct_b[2], ct_b[1], ct_b[0]); + ct_b[3] = ct_blarge; + ct_blen = 4; + } + } + + bctlen = fast_expansion_sum_zeroelim(bt_clen, bt_c, ct_blen, ct_b, bct); + wlength = scale_expansion_zeroelim(bctlen, bct, adz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + catlen = fast_expansion_sum_zeroelim(ct_alen, ct_a, at_clen, at_c, cat); + wlength = scale_expansion_zeroelim(catlen, cat, bdz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + abtlen = fast_expansion_sum_zeroelim(at_blen, at_b, bt_alen, bt_a, abt); + wlength = scale_expansion_zeroelim(abtlen, abt, cdz, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + + if (adztail != 0.0) { + vlength = scale_expansion_zeroelim(4, bc, adztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdztail != 0.0) { + vlength = scale_expansion_zeroelim(4, ca, bdztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdztail != 0.0) { + vlength = scale_expansion_zeroelim(4, ab, cdztail, v); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if (adxtail != 0.0) { + if (bdytail != 0.0) { + Two_Product(adxtail, bdytail, adxt_bdyt1, adxt_bdyt0); + Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdztail != 0.0) { + Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (cdytail != 0.0) { + negate = -adxtail; + Two_Product(negate, cdytail, adxt_cdyt1, adxt_cdyt0); + Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdztail != 0.0) { + Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + if (bdxtail != 0.0) { + if (cdytail != 0.0) { + Two_Product(bdxtail, cdytail, bdxt_cdyt1, bdxt_cdyt0); + Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adztail != 0.0) { + Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (adytail != 0.0) { + negate = -bdxtail; + Two_Product(negate, adytail, bdxt_adyt1, bdxt_adyt0); + Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdztail != 0.0) { + Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + if (cdxtail != 0.0) { + if (adytail != 0.0) { + Two_Product(cdxtail, adytail, cdxt_adyt1, cdxt_adyt0); + Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdztail != 0.0) { + Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if (bdytail != 0.0) { + negate = -cdxtail; + Two_Product(negate, bdytail, cdxt_bdyt1, cdxt_bdyt0); + Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adz, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adztail != 0.0) { + Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adztail, u3, u[2], u[1], u[0]); + u[3] = u3; + finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + } + + if (adztail != 0.0) { + wlength = scale_expansion_zeroelim(bctlen, bct, adztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdztail != 0.0) { + wlength = scale_expansion_zeroelim(catlen, cat, bdztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdztail != 0.0) { + wlength = scale_expansion_zeroelim(abtlen, abt, cdztail, w); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w, + finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + return finnow[finlength - 1]; +} + +#ifdef USE_CGAL_PREDICATES + +REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + return (REAL) + - cgal_pred_obj.orientation_3_object() + (Point(pa[0], pa[1], pa[2]), + Point(pb[0], pb[1], pb[2]), + Point(pc[0], pc[1], pc[2]), + Point(pd[0], pd[1], pd[2])); +} + +#else + +REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL det; + + + adx = pa[0] - pd[0]; + ady = pa[1] - pd[1]; + adz = pa[2] - pd[2]; + bdx = pb[0] - pd[0]; + bdy = pb[1] - pd[1]; + bdz = pb[2] - pd[2]; + cdx = pc[0] - pd[0]; + cdy = pc[1] - pd[1]; + cdz = pc[2] - pd[2]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + + det = adz * (bdxcdy - cdxbdy) + + bdz * (cdxady - adxcdy) + + cdz * (adxbdy - bdxady); + + if (_use_inexact_arith) { + return det; + } + + if (_use_static_filter) { + //if (fabs(det) > o3dstaticfilter) return det; + if (det > o3dstaticfilter) return det; + if (det < -o3dstaticfilter) return det; + } + + + REAL permanent, errbound; + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * Absolute(adz) + + (Absolute(cdxady) + Absolute(adxcdy)) * Absolute(bdz) + + (Absolute(adxbdy) + Absolute(bdxady)) * Absolute(cdz); + errbound = o3derrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return orient3dadapt(pa, pb, pc, pd, permanent); +} + +#endif // #ifdef USE_CGAL_PREDICATES + +/*****************************************************************************/ +/* */ +/* incirclefast() Approximate 2D incircle test. Nonrobust. */ +/* incircleexact() Exact 2D incircle test. Robust. */ +/* incircleslow() Another exact 2D incircle test. Robust. */ +/* incircle() Adaptive exact 2D incircle test. Robust. */ +/* */ +/* Return a positive value if the point pd lies inside the */ +/* circle passing through pa, pb, and pc; a negative value if */ +/* it lies outside; and zero if the four points are cocircular.*/ +/* The points pa, pb, and pc must be in counterclockwise */ +/* order, or the sign of the result will be reversed. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In incircle() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, incircle() is usually quite */ +/* fast, but will run more slowly when the input points are cocircular or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL incirclefast(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, ady, bdx, bdy, cdx, cdy; + REAL abdet, bcdet, cadet; + REAL alift, blift, clift; + + adx = pa[0] - pd[0]; + ady = pa[1] - pd[1]; + bdx = pb[0] - pd[0]; + bdy = pb[1] - pd[1]; + cdx = pc[0] - pd[0]; + cdy = pc[1] - pd[1]; + + abdet = adx * bdy - bdx * ady; + bcdet = bdx * cdy - cdx * bdy; + cadet = cdx * ady - adx * cdy; + alift = adx * adx + ady * ady; + blift = bdx * bdx + bdy * bdy; + clift = cdx * cdx + cdy * cdy; + + return alift * bcdet + blift * cadet + clift * abdet; +} + +REAL incircleexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxay1, axcy1, bxdy1; + INEXACT REAL bxay1, cxby1, dxcy1, axdy1, cxay1, dxby1; + REAL axby0, bxcy0, cxdy0, dxay0, axcy0, bxdy0; + REAL bxay0, cxby0, dxcy0, axdy0, cxay0, dxby0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + REAL temp8[8]; + int templen; + REAL abc[12], bcd[12], cda[12], dab[12]; + int abclen, bcdlen, cdalen, dablen; + REAL det24x[24], det24y[24], det48x[48], det48y[48]; + int xlen, ylen; + REAL adet[96], bdet[96], cdet[96], ddet[96]; + int alen, blen, clen, dlen; + REAL abdet[192], cddet[192]; + int ablen, cdlen; + REAL deter[384]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + templen = fast_expansion_sum_zeroelim(4, cd, 4, da, temp8); + cdalen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, cda); + templen = fast_expansion_sum_zeroelim(4, da, 4, ab, temp8); + dablen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, dab); + for (i = 0; i < 4; i++) { + bd[i] = -bd[i]; + ac[i] = -ac[i]; + } + templen = fast_expansion_sum_zeroelim(4, ab, 4, bc, temp8); + abclen = fast_expansion_sum_zeroelim(templen, temp8, 4, ac, abc); + templen = fast_expansion_sum_zeroelim(4, bc, 4, cd, temp8); + bcdlen = fast_expansion_sum_zeroelim(templen, temp8, 4, bd, bcd); + + xlen = scale_expansion_zeroelim(bcdlen, bcd, pa[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, pa[0], det48x); + ylen = scale_expansion_zeroelim(bcdlen, bcd, pa[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, pa[1], det48y); + alen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, adet); + + xlen = scale_expansion_zeroelim(cdalen, cda, pb[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, -pb[0], det48x); + ylen = scale_expansion_zeroelim(cdalen, cda, pb[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, -pb[1], det48y); + blen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, bdet); + + xlen = scale_expansion_zeroelim(dablen, dab, pc[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, pc[0], det48x); + ylen = scale_expansion_zeroelim(dablen, dab, pc[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, pc[1], det48y); + clen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, cdet); + + xlen = scale_expansion_zeroelim(abclen, abc, pd[0], det24x); + xlen = scale_expansion_zeroelim(xlen, det24x, -pd[0], det48x); + ylen = scale_expansion_zeroelim(abclen, abc, pd[1], det24y); + ylen = scale_expansion_zeroelim(ylen, det24y, -pd[1], det48y); + dlen = fast_expansion_sum_zeroelim(xlen, det48x, ylen, det48y, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL incircleslow(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy; + REAL adxtail, bdxtail, cdxtail; + REAL adytail, bdytail, cdytail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, axcy7, bxay7, cxby7, cxay7; + REAL axby[8], bxcy[8], axcy[8], bxay[8], cxby[8], cxay[8]; + REAL temp16[16]; + int temp16len; + REAL detx[32], detxx[64], detxt[32], detxxt[64], detxtxt[64]; + int xlen, xxlen, xtlen, xxtlen, xtxtlen; + REAL x1[128], x2[192]; + int x1len, x2len; + REAL dety[32], detyy[64], detyt[32], detyyt[64], detytyt[64]; + int ylen, yylen, ytlen, yytlen, ytytlen; + REAL y1[128], y2[192]; + int y1len, y2len; + REAL adet[384], bdet[384], cdet[384], abdet[768], deter[1152]; + int alen, blen, clen, ablen, deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pd[0], adx, adxtail); + Two_Diff(pa[1], pd[1], ady, adytail); + Two_Diff(pb[0], pd[0], bdx, bdxtail); + Two_Diff(pb[1], pd[1], bdy, bdytail); + Two_Diff(pc[0], pd[0], cdx, cdxtail); + Two_Diff(pc[1], pd[1], cdy, cdytail); + + Two_Two_Product(adx, adxtail, bdy, bdytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -ady; + negatetail = -adytail; + Two_Two_Product(bdx, bdxtail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + Two_Two_Product(bdx, bdxtail, cdy, cdytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bdy; + negatetail = -bdytail; + Two_Two_Product(cdx, cdxtail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + Two_Two_Product(cdx, cdxtail, ady, adytail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + negate = -cdy; + negatetail = -cdytail; + Two_Two_Product(adx, adxtail, negate, negatetail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + + + temp16len = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, adx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, adx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, adxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, adx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, adxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, ady, dety); + yylen = scale_expansion_zeroelim(ylen, dety, ady, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, adytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, ady, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, adytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + alen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, adet); + + + temp16len = fast_expansion_sum_zeroelim(8, cxay, 8, axcy, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, bdx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, bdx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, bdxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, bdx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, bdxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, bdy, dety); + yylen = scale_expansion_zeroelim(ylen, dety, bdy, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, bdytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, bdy, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, bdytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + blen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, bdet); + + + temp16len = fast_expansion_sum_zeroelim(8, axby, 8, bxay, temp16); + + xlen = scale_expansion_zeroelim(temp16len, temp16, cdx, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, cdx, detxx); + xtlen = scale_expansion_zeroelim(temp16len, temp16, cdxtail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, cdx, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, cdxtail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + + ylen = scale_expansion_zeroelim(temp16len, temp16, cdy, dety); + yylen = scale_expansion_zeroelim(ylen, dety, cdy, detyy); + ytlen = scale_expansion_zeroelim(temp16len, temp16, cdytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, cdy, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, cdytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + + clen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, deter); + + return deter[deterlen - 1]; +} + +REAL incircleadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL permanent) +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL axbc[8], axxbc[16], aybc[8], ayybc[16], adet[32]; + int axbclen, axxbclen, aybclen, ayybclen, alen; + REAL bxca[8], bxxca[16], byca[8], byyca[16], bdet[32]; + int bxcalen, bxxcalen, bycalen, byycalen, blen; + REAL cxab[8], cxxab[16], cyab[8], cyyab[16], cdet[32]; + int cxablen, cxxablen, cyablen, cyyablen, clen; + REAL abdet[64]; + int ablen; + REAL fin1[1152], fin2[1152]; + REAL *finnow, *finother, *finswap; + int finlength; + + REAL adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail; + INEXACT REAL adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1; + REAL adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0; + REAL aa[4], bb[4], cc[4]; + INEXACT REAL aa3, bb3, cc3; + INEXACT REAL ti1, tj1; + REAL ti0, tj0; + REAL u[4], v[4]; + INEXACT REAL u3, v3; + REAL temp8[8], temp16a[16], temp16b[16], temp16c[16]; + REAL temp32a[32], temp32b[32], temp48[48], temp64[64]; + int temp8len, temp16alen, temp16blen, temp16clen; + int temp32alen, temp32blen, temp48len, temp64len; + REAL axtbb[8], axtcc[8], aytbb[8], aytcc[8]; + int axtbblen, axtcclen, aytbblen, aytcclen; + REAL bxtaa[8], bxtcc[8], bytaa[8], bytcc[8]; + int bxtaalen, bxtcclen, bytaalen, bytcclen; + REAL cxtaa[8], cxtbb[8], cytaa[8], cytbb[8]; + int cxtaalen, cxtbblen, cytaalen, cytbblen; + REAL axtbc[8], aytbc[8], bxtca[8], bytca[8], cxtab[8], cytab[8]; + int axtbclen, aytbclen, bxtcalen, bytcalen, cxtablen, cytablen; + REAL axtbct[16], aytbct[16], bxtcat[16], bytcat[16], cxtabt[16], cytabt[16]; + int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen; + REAL axtbctt[8], aytbctt[8], bxtcatt[8]; + REAL bytcatt[8], cxtabtt[8], cytabtt[8]; + int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen; + REAL abt[8], bct[8], cat[8]; + int abtlen, bctlen, catlen; + REAL abtt[4], bctt[4], catt[4]; + int abttlen, bcttlen, cattlen; + INEXACT REAL abtt3, bctt3, catt3; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + // Avoid compiler warnings. H. Si, 2012-02-16. + axtbclen = aytbclen = bxtcalen = bytcalen = cxtablen = cytablen = 0; + + adx = (REAL) (pa[0] - pd[0]); + bdx = (REAL) (pb[0] - pd[0]); + cdx = (REAL) (pc[0] - pd[0]); + ady = (REAL) (pa[1] - pd[1]); + bdy = (REAL) (pb[1] - pd[1]); + cdy = (REAL) (pc[1] - pd[1]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + axbclen = scale_expansion_zeroelim(4, bc, adx, axbc); + axxbclen = scale_expansion_zeroelim(axbclen, axbc, adx, axxbc); + aybclen = scale_expansion_zeroelim(4, bc, ady, aybc); + ayybclen = scale_expansion_zeroelim(aybclen, aybc, ady, ayybc); + alen = fast_expansion_sum_zeroelim(axxbclen, axxbc, ayybclen, ayybc, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + bxcalen = scale_expansion_zeroelim(4, ca, bdx, bxca); + bxxcalen = scale_expansion_zeroelim(bxcalen, bxca, bdx, bxxca); + bycalen = scale_expansion_zeroelim(4, ca, bdy, byca); + byycalen = scale_expansion_zeroelim(bycalen, byca, bdy, byyca); + blen = fast_expansion_sum_zeroelim(bxxcalen, bxxca, byycalen, byyca, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + cxablen = scale_expansion_zeroelim(4, ab, cdx, cxab); + cxxablen = scale_expansion_zeroelim(cxablen, cxab, cdx, cxxab); + cyablen = scale_expansion_zeroelim(4, ab, cdy, cyab); + cyyablen = scale_expansion_zeroelim(cyablen, cyab, cdy, cyyab); + clen = fast_expansion_sum_zeroelim(cxxablen, cxxab, cyyablen, cyyab, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = iccerrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0)) { + return det; + } + + errbound = iccerrboundC * permanent + resulterrbound * Absolute(det); + det += ((adx * adx + ady * ady) * ((bdx * cdytail + cdy * bdxtail) + - (bdy * cdxtail + cdx * bdytail)) + + 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) + + ((bdx * bdx + bdy * bdy) * ((cdx * adytail + ady * cdxtail) + - (cdy * adxtail + adx * cdytail)) + + 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) + + ((cdx * cdx + cdy * cdy) * ((adx * bdytail + bdy * adxtail) + - (ady * bdxtail + bdx * adytail)) + + 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Square(adx, adxadx1, adxadx0); + Square(ady, adyady1, adyady0); + Two_Two_Sum(adxadx1, adxadx0, adyady1, adyady0, aa3, aa[2], aa[1], aa[0]); + aa[3] = aa3; + } + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Square(bdx, bdxbdx1, bdxbdx0); + Square(bdy, bdybdy1, bdybdy0); + Two_Two_Sum(bdxbdx1, bdxbdx0, bdybdy1, bdybdy0, bb3, bb[2], bb[1], bb[0]); + bb[3] = bb3; + } + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Square(cdx, cdxcdx1, cdxcdx0); + Square(cdy, cdycdy1, cdycdy0); + Two_Two_Sum(cdxcdx1, cdxcdx0, cdycdy1, cdycdy0, cc3, cc[2], cc[1], cc[0]); + cc[3] = cc3; + } + + if (adxtail != 0.0) { + axtbclen = scale_expansion_zeroelim(4, bc, adxtail, axtbc); + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, 2.0 * adx, + temp16a); + + axtcclen = scale_expansion_zeroelim(4, cc, adxtail, axtcc); + temp16blen = scale_expansion_zeroelim(axtcclen, axtcc, bdy, temp16b); + + axtbblen = scale_expansion_zeroelim(4, bb, adxtail, axtbb); + temp16clen = scale_expansion_zeroelim(axtbblen, axtbb, -cdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + aytbclen = scale_expansion_zeroelim(4, bc, adytail, aytbc); + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, 2.0 * ady, + temp16a); + + aytbblen = scale_expansion_zeroelim(4, bb, adytail, aytbb); + temp16blen = scale_expansion_zeroelim(aytbblen, aytbb, cdx, temp16b); + + aytcclen = scale_expansion_zeroelim(4, cc, adytail, aytcc); + temp16clen = scale_expansion_zeroelim(aytcclen, aytcc, -bdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdxtail != 0.0) { + bxtcalen = scale_expansion_zeroelim(4, ca, bdxtail, bxtca); + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, 2.0 * bdx, + temp16a); + + bxtaalen = scale_expansion_zeroelim(4, aa, bdxtail, bxtaa); + temp16blen = scale_expansion_zeroelim(bxtaalen, bxtaa, cdy, temp16b); + + bxtcclen = scale_expansion_zeroelim(4, cc, bdxtail, bxtcc); + temp16clen = scale_expansion_zeroelim(bxtcclen, bxtcc, -ady, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + bytcalen = scale_expansion_zeroelim(4, ca, bdytail, bytca); + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, 2.0 * bdy, + temp16a); + + bytcclen = scale_expansion_zeroelim(4, cc, bdytail, bytcc); + temp16blen = scale_expansion_zeroelim(bytcclen, bytcc, adx, temp16b); + + bytaalen = scale_expansion_zeroelim(4, aa, bdytail, bytaa); + temp16clen = scale_expansion_zeroelim(bytaalen, bytaa, -cdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdxtail != 0.0) { + cxtablen = scale_expansion_zeroelim(4, ab, cdxtail, cxtab); + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, 2.0 * cdx, + temp16a); + + cxtbblen = scale_expansion_zeroelim(4, bb, cdxtail, cxtbb); + temp16blen = scale_expansion_zeroelim(cxtbblen, cxtbb, ady, temp16b); + + cxtaalen = scale_expansion_zeroelim(4, aa, cdxtail, cxtaa); + temp16clen = scale_expansion_zeroelim(cxtaalen, cxtaa, -bdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + cytablen = scale_expansion_zeroelim(4, ab, cdytail, cytab); + temp16alen = scale_expansion_zeroelim(cytablen, cytab, 2.0 * cdy, + temp16a); + + cytaalen = scale_expansion_zeroelim(4, aa, cdytail, cytaa); + temp16blen = scale_expansion_zeroelim(cytaalen, cytaa, bdx, temp16b); + + cytbblen = scale_expansion_zeroelim(4, bb, cdytail, cytbb); + temp16clen = scale_expansion_zeroelim(cytbblen, cytbb, -adx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if ((adxtail != 0.0) || (adytail != 0.0)) { + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Two_Product(bdxtail, cdy, ti1, ti0); + Two_Product(bdx, cdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -bdy; + Two_Product(cdxtail, negate, ti1, ti0); + negate = -bdytail; + Two_Product(cdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + bctlen = fast_expansion_sum_zeroelim(4, u, 4, v, bct); + + Two_Product(bdxtail, cdytail, ti1, ti0); + Two_Product(cdxtail, bdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, bctt3, bctt[2], bctt[1], bctt[0]); + bctt[3] = bctt3; + bcttlen = 4; + } else { + bct[0] = 0.0; + bctlen = 1; + bctt[0] = 0.0; + bcttlen = 1; + } + + if (adxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, adxtail, temp16a); + axtbctlen = scale_expansion_zeroelim(bctlen, bct, adxtail, axtbct); + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, 2.0 * adx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, -adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, adxtail, + temp32a); + axtbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adxtail, axtbctt); + temp16alen = scale_expansion_zeroelim(axtbcttlen, axtbctt, 2.0 * adx, + temp16a); + temp16blen = scale_expansion_zeroelim(axtbcttlen, axtbctt, adxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, adytail, temp16a); + aytbctlen = scale_expansion_zeroelim(bctlen, bct, adytail, aytbct); + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, 2.0 * ady, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, adytail, + temp32a); + aytbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adytail, aytbctt); + temp16alen = scale_expansion_zeroelim(aytbcttlen, aytbctt, 2.0 * ady, + temp16a); + temp16blen = scale_expansion_zeroelim(aytbcttlen, aytbctt, adytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((bdxtail != 0.0) || (bdytail != 0.0)) { + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Two_Product(cdxtail, ady, ti1, ti0); + Two_Product(cdx, adytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -cdy; + Two_Product(adxtail, negate, ti1, ti0); + negate = -cdytail; + Two_Product(adx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + catlen = fast_expansion_sum_zeroelim(4, u, 4, v, cat); + + Two_Product(cdxtail, adytail, ti1, ti0); + Two_Product(adxtail, cdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, catt3, catt[2], catt[1], catt[0]); + catt[3] = catt3; + cattlen = 4; + } else { + cat[0] = 0.0; + catlen = 1; + catt[0] = 0.0; + cattlen = 1; + } + + if (bdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, bdxtail, temp16a); + bxtcatlen = scale_expansion_zeroelim(catlen, cat, bdxtail, bxtcat); + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, 2.0 * bdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, -bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, bdxtail, + temp32a); + bxtcattlen = scale_expansion_zeroelim(cattlen, catt, bdxtail, bxtcatt); + temp16alen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, 2.0 * bdx, + temp16a); + temp16blen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, bdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, bdytail, temp16a); + bytcatlen = scale_expansion_zeroelim(catlen, cat, bdytail, bytcat); + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, 2.0 * bdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, bdytail, + temp32a); + bytcattlen = scale_expansion_zeroelim(cattlen, catt, bdytail, bytcatt); + temp16alen = scale_expansion_zeroelim(bytcattlen, bytcatt, 2.0 * bdy, + temp16a); + temp16blen = scale_expansion_zeroelim(bytcattlen, bytcatt, bdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((cdxtail != 0.0) || (cdytail != 0.0)) { + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Two_Product(adxtail, bdy, ti1, ti0); + Two_Product(adx, bdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -ady; + Two_Product(bdxtail, negate, ti1, ti0); + negate = -adytail; + Two_Product(bdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + abtlen = fast_expansion_sum_zeroelim(4, u, 4, v, abt); + + Two_Product(adxtail, bdytail, ti1, ti0); + Two_Product(bdxtail, adytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, abtt3, abtt[2], abtt[1], abtt[0]); + abtt[3] = abtt3; + abttlen = 4; + } else { + abt[0] = 0.0; + abtlen = 1; + abtt[0] = 0.0; + abttlen = 1; + } + + if (cdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, cdxtail, temp16a); + cxtabtlen = scale_expansion_zeroelim(abtlen, abt, cdxtail, cxtabt); + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, 2.0 * cdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, -cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, cdxtail, + temp32a); + cxtabttlen = scale_expansion_zeroelim(abttlen, abtt, cdxtail, cxtabtt); + temp16alen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, 2.0 * cdx, + temp16a); + temp16blen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, cdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(cytablen, cytab, cdytail, temp16a); + cytabtlen = scale_expansion_zeroelim(abtlen, abt, cdytail, cytabt); + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, 2.0 * cdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, cdytail, + temp32a); + cytabttlen = scale_expansion_zeroelim(abttlen, abtt, cdytail, cytabtt); + temp16alen = scale_expansion_zeroelim(cytabttlen, cytabtt, 2.0 * cdy, + temp16a); + temp16blen = scale_expansion_zeroelim(cytabttlen, cytabtt, cdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + + return finnow[finlength - 1]; +} + +REAL incircle(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, bdx, cdx, ady, bdy, cdy; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL alift, blift, clift; + REAL det; + REAL permanent, errbound; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + alift = adx * adx + ady * ady; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + blift = bdx * bdx + bdy * bdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + clift = cdx * cdx + cdy * cdy; + + det = alift * (bdxcdy - cdxbdy) + + blift * (cdxady - adxcdy) + + clift * (adxbdy - bdxady); + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * alift + + (Absolute(cdxady) + Absolute(adxcdy)) * blift + + (Absolute(adxbdy) + Absolute(bdxady)) * clift; + errbound = iccerrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return incircleadapt(pa, pb, pc, pd, permanent); +} + +/*****************************************************************************/ +/* */ +/* inspherefast() Approximate 3D insphere test. Nonrobust. */ +/* insphereexact() Exact 3D insphere test. Robust. */ +/* insphereslow() Another exact 3D insphere test. Robust. */ +/* insphere() Adaptive exact 3D insphere test. Robust. */ +/* */ +/* Return a positive value if the point pe lies inside the */ +/* sphere passing through pa, pb, pc, and pd; a negative value */ +/* if it lies outside; and zero if the five points are */ +/* cospherical. The points pa, pb, pc, and pd must be ordered */ +/* so that they have a positive orientation (as defined by */ +/* orient3d()), or the sign of the result will be reversed. */ +/* */ +/* Only the first and last routine should be used; the middle two are for */ +/* timings. */ +/* */ +/* The last three use exact arithmetic to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. In insphere() only, */ +/* this determinant is computed adaptively, in the sense that exact */ +/* arithmetic is used only to the degree it is needed to ensure that the */ +/* returned value has the correct sign. Hence, insphere() is usually quite */ +/* fast, but will run more slowly when the input points are cospherical or */ +/* nearly so. */ +/* */ +/*****************************************************************************/ + +REAL inspherefast(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL alift, blift, clift, dlift; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + + ab = aex * bey - bex * aey; + bc = bex * cey - cex * bey; + cd = cex * dey - dex * cey; + da = dex * aey - aex * dey; + + ac = aex * cey - cex * aey; + bd = bex * dey - dex * bey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + alift = aex * aex + aey * aey + aez * aez; + blift = bex * bex + bey * bey + bez * bez; + clift = cex * cex + cey * cey + cez * cez; + dlift = dex * dex + dey * dey + dez * dez; + + return (dlift * abc - clift * dab) + (blift * cda - alift * bcd); +} + +REAL insphereexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxey1, exay1; + INEXACT REAL bxay1, cxby1, dxcy1, exdy1, axey1; + INEXACT REAL axcy1, bxdy1, cxey1, dxay1, exby1; + INEXACT REAL cxay1, dxby1, excy1, axdy1, bxey1; + REAL axby0, bxcy0, cxdy0, dxey0, exay0; + REAL bxay0, cxby0, dxcy0, exdy0, axey0; + REAL axcy0, bxdy0, cxey0, dxay0, exby0; + REAL cxay0, dxby0, excy0, axdy0, bxey0; + REAL ab[4], bc[4], cd[4], de[4], ea[4]; + REAL ac[4], bd[4], ce[4], da[4], eb[4]; + REAL temp8a[8], temp8b[8], temp16[16]; + int temp8alen, temp8blen, temp16len; + REAL abc[24], bcd[24], cde[24], dea[24], eab[24]; + REAL abd[24], bce[24], cda[24], deb[24], eac[24]; + int abclen, bcdlen, cdelen, dealen, eablen; + int abdlen, bcelen, cdalen, deblen, eaclen; + REAL temp48a[48], temp48b[48]; + int temp48alen, temp48blen; + REAL abcd[96], bcde[96], cdea[96], deab[96], eabc[96]; + int abcdlen, bcdelen, cdealen, deablen, eabclen; + REAL temp192[192]; + REAL det384x[384], det384y[384], det384z[384]; + int xlen, ylen, zlen; + REAL detxy[768]; + int xylen; + REAL adet[1152], bdet[1152], cdet[1152], ddet[1152], edet[1152]; + int alen, blen, clen, dlen, elen; + REAL abdet[2304], cddet[2304], cdedet[3456]; + int ablen, cdlen; + REAL deter[5760]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pe[1], dxey1, dxey0); + Two_Product(pe[0], pd[1], exdy1, exdy0); + Two_Two_Diff(dxey1, dxey0, exdy1, exdy0, de[3], de[2], de[1], de[0]); + + Two_Product(pe[0], pa[1], exay1, exay0); + Two_Product(pa[0], pe[1], axey1, axey0); + Two_Two_Diff(exay1, exay0, axey1, axey0, ea[3], ea[2], ea[1], ea[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + Two_Product(pc[0], pe[1], cxey1, cxey0); + Two_Product(pe[0], pc[1], excy1, excy0); + Two_Two_Diff(cxey1, cxey0, excy1, excy0, ce[3], ce[2], ce[1], ce[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pe[0], pb[1], exby1, exby0); + Two_Product(pb[0], pe[1], bxey1, bxey0); + Two_Two_Diff(exby1, exby0, bxey1, bxey0, eb[3], eb[2], eb[1], eb[0]); + + temp8alen = scale_expansion_zeroelim(4, bc, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pc[2], temp8a); + abclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abc); + + temp8alen = scale_expansion_zeroelim(4, cd, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pd[2], temp8a); + bcdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bcd); + + temp8alen = scale_expansion_zeroelim(4, de, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, -pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pe[2], temp8a); + cdelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cde); + + temp8alen = scale_expansion_zeroelim(4, ea, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, -pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pa[2], temp8a); + dealen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + dea); + + temp8alen = scale_expansion_zeroelim(4, ab, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, -pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pb[2], temp8a); + eablen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eab); + + temp8alen = scale_expansion_zeroelim(4, bd, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pd[2], temp8a); + abdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abd); + + temp8alen = scale_expansion_zeroelim(4, ce, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pe[2], temp8a); + bcelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bce); + + temp8alen = scale_expansion_zeroelim(4, da, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pa[2], temp8a); + cdalen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cda); + + temp8alen = scale_expansion_zeroelim(4, eb, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pb[2], temp8a); + deblen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + deb); + + temp8alen = scale_expansion_zeroelim(4, ac, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pc[2], temp8a); + eaclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eac); + + temp48alen = fast_expansion_sum_zeroelim(cdelen, cde, bcelen, bce, temp48a); + temp48blen = fast_expansion_sum_zeroelim(deblen, deb, bcdlen, bcd, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + bcdelen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, bcde); + xlen = scale_expansion_zeroelim(bcdelen, bcde, pa[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pa[0], det384x); + ylen = scale_expansion_zeroelim(bcdelen, bcde, pa[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pa[1], det384y); + zlen = scale_expansion_zeroelim(bcdelen, bcde, pa[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pa[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + alen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, adet); + + temp48alen = fast_expansion_sum_zeroelim(dealen, dea, cdalen, cda, temp48a); + temp48blen = fast_expansion_sum_zeroelim(eaclen, eac, cdelen, cde, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + cdealen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, cdea); + xlen = scale_expansion_zeroelim(cdealen, cdea, pb[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pb[0], det384x); + ylen = scale_expansion_zeroelim(cdealen, cdea, pb[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pb[1], det384y); + zlen = scale_expansion_zeroelim(cdealen, cdea, pb[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pb[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + blen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, bdet); + + temp48alen = fast_expansion_sum_zeroelim(eablen, eab, deblen, deb, temp48a); + temp48blen = fast_expansion_sum_zeroelim(abdlen, abd, dealen, dea, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + deablen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, deab); + xlen = scale_expansion_zeroelim(deablen, deab, pc[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pc[0], det384x); + ylen = scale_expansion_zeroelim(deablen, deab, pc[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pc[1], det384y); + zlen = scale_expansion_zeroelim(deablen, deab, pc[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pc[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + clen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, cdet); + + temp48alen = fast_expansion_sum_zeroelim(abclen, abc, eaclen, eac, temp48a); + temp48blen = fast_expansion_sum_zeroelim(bcelen, bce, eablen, eab, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + eabclen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, eabc); + xlen = scale_expansion_zeroelim(eabclen, eabc, pd[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pd[0], det384x); + ylen = scale_expansion_zeroelim(eabclen, eabc, pd[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pd[1], det384y); + zlen = scale_expansion_zeroelim(eabclen, eabc, pd[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pd[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + dlen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, ddet); + + temp48alen = fast_expansion_sum_zeroelim(bcdlen, bcd, abdlen, abd, temp48a); + temp48blen = fast_expansion_sum_zeroelim(cdalen, cda, abclen, abc, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + abcdlen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, abcd); + xlen = scale_expansion_zeroelim(abcdlen, abcd, pe[0], temp192); + xlen = scale_expansion_zeroelim(xlen, temp192, pe[0], det384x); + ylen = scale_expansion_zeroelim(abcdlen, abcd, pe[1], temp192); + ylen = scale_expansion_zeroelim(ylen, temp192, pe[1], det384y); + zlen = scale_expansion_zeroelim(abcdlen, abcd, pe[2], temp192); + zlen = scale_expansion_zeroelim(zlen, temp192, pe[2], det384z); + xylen = fast_expansion_sum_zeroelim(xlen, det384x, ylen, det384y, detxy); + elen = fast_expansion_sum_zeroelim(xylen, detxy, zlen, det384z, edet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + cdelen = fast_expansion_sum_zeroelim(cdlen, cddet, elen, edet, cdedet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdelen, cdedet, deter); + + return deter[deterlen - 1]; +} + +REAL insphereslow(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + REAL negate, negatetail; + INEXACT REAL axby7, bxcy7, cxdy7, dxay7, axcy7, bxdy7; + INEXACT REAL bxay7, cxby7, dxcy7, axdy7, cxay7, dxby7; + REAL axby[8], bxcy[8], cxdy[8], dxay[8], axcy[8], bxdy[8]; + REAL bxay[8], cxby[8], dxcy[8], axdy[8], cxay[8], dxby[8]; + REAL ab[16], bc[16], cd[16], da[16], ac[16], bd[16]; + int ablen, bclen, cdlen, dalen, aclen, bdlen; + REAL temp32a[32], temp32b[32], temp64a[64], temp64b[64], temp64c[64]; + int temp32alen, temp32blen, temp64alen, temp64blen, temp64clen; + REAL temp128[128], temp192[192]; + int temp128len, temp192len; + REAL detx[384], detxx[768], detxt[384], detxxt[768], detxtxt[768]; + int xlen, xxlen, xtlen, xxtlen, xtxtlen; + REAL x1[1536], x2[2304]; + int x1len, x2len; + REAL dety[384], detyy[768], detyt[384], detyyt[768], detytyt[768]; + int ylen, yylen, ytlen, yytlen, ytytlen; + REAL y1[1536], y2[2304]; + int y1len, y2len; + REAL detz[384], detzz[768], detzt[384], detzzt[768], detztzt[768]; + int zlen, zzlen, ztlen, zztlen, ztztlen; + REAL z1[1536], z2[2304]; + int z1len, z2len; + REAL detxy[4608]; + int xylen; + REAL adet[6912], bdet[6912], cdet[6912], ddet[6912]; + int alen, blen, clen, dlen; + REAL abdet[13824], cddet[13824], deter[27648]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL a0hi, a0lo, a1hi, a1lo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j, _k, _l, _m, _n; + REAL _0, _1, _2; + + Two_Diff(pa[0], pe[0], aex, aextail); + Two_Diff(pa[1], pe[1], aey, aeytail); + Two_Diff(pa[2], pe[2], aez, aeztail); + Two_Diff(pb[0], pe[0], bex, bextail); + Two_Diff(pb[1], pe[1], bey, beytail); + Two_Diff(pb[2], pe[2], bez, beztail); + Two_Diff(pc[0], pe[0], cex, cextail); + Two_Diff(pc[1], pe[1], cey, ceytail); + Two_Diff(pc[2], pe[2], cez, ceztail); + Two_Diff(pd[0], pe[0], dex, dextail); + Two_Diff(pd[1], pe[1], dey, deytail); + Two_Diff(pd[2], pe[2], dez, deztail); + + Two_Two_Product(aex, aextail, bey, beytail, + axby7, axby[6], axby[5], axby[4], + axby[3], axby[2], axby[1], axby[0]); + axby[7] = axby7; + negate = -aey; + negatetail = -aeytail; + Two_Two_Product(bex, bextail, negate, negatetail, + bxay7, bxay[6], bxay[5], bxay[4], + bxay[3], bxay[2], bxay[1], bxay[0]); + bxay[7] = bxay7; + ablen = fast_expansion_sum_zeroelim(8, axby, 8, bxay, ab); + Two_Two_Product(bex, bextail, cey, ceytail, + bxcy7, bxcy[6], bxcy[5], bxcy[4], + bxcy[3], bxcy[2], bxcy[1], bxcy[0]); + bxcy[7] = bxcy7; + negate = -bey; + negatetail = -beytail; + Two_Two_Product(cex, cextail, negate, negatetail, + cxby7, cxby[6], cxby[5], cxby[4], + cxby[3], cxby[2], cxby[1], cxby[0]); + cxby[7] = cxby7; + bclen = fast_expansion_sum_zeroelim(8, bxcy, 8, cxby, bc); + Two_Two_Product(cex, cextail, dey, deytail, + cxdy7, cxdy[6], cxdy[5], cxdy[4], + cxdy[3], cxdy[2], cxdy[1], cxdy[0]); + cxdy[7] = cxdy7; + negate = -cey; + negatetail = -ceytail; + Two_Two_Product(dex, dextail, negate, negatetail, + dxcy7, dxcy[6], dxcy[5], dxcy[4], + dxcy[3], dxcy[2], dxcy[1], dxcy[0]); + dxcy[7] = dxcy7; + cdlen = fast_expansion_sum_zeroelim(8, cxdy, 8, dxcy, cd); + Two_Two_Product(dex, dextail, aey, aeytail, + dxay7, dxay[6], dxay[5], dxay[4], + dxay[3], dxay[2], dxay[1], dxay[0]); + dxay[7] = dxay7; + negate = -dey; + negatetail = -deytail; + Two_Two_Product(aex, aextail, negate, negatetail, + axdy7, axdy[6], axdy[5], axdy[4], + axdy[3], axdy[2], axdy[1], axdy[0]); + axdy[7] = axdy7; + dalen = fast_expansion_sum_zeroelim(8, dxay, 8, axdy, da); + Two_Two_Product(aex, aextail, cey, ceytail, + axcy7, axcy[6], axcy[5], axcy[4], + axcy[3], axcy[2], axcy[1], axcy[0]); + axcy[7] = axcy7; + negate = -aey; + negatetail = -aeytail; + Two_Two_Product(cex, cextail, negate, negatetail, + cxay7, cxay[6], cxay[5], cxay[4], + cxay[3], cxay[2], cxay[1], cxay[0]); + cxay[7] = cxay7; + aclen = fast_expansion_sum_zeroelim(8, axcy, 8, cxay, ac); + Two_Two_Product(bex, bextail, dey, deytail, + bxdy7, bxdy[6], bxdy[5], bxdy[4], + bxdy[3], bxdy[2], bxdy[1], bxdy[0]); + bxdy[7] = bxdy7; + negate = -bey; + negatetail = -beytail; + Two_Two_Product(dex, dextail, negate, negatetail, + dxby7, dxby[6], dxby[5], dxby[4], + dxby[3], dxby[2], dxby[1], dxby[0]); + dxby[7] = dxby7; + bdlen = fast_expansion_sum_zeroelim(8, bxdy, 8, dxby, bd); + + temp32alen = scale_expansion_zeroelim(cdlen, cd, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(cdlen, cd, -beztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(bdlen, bd, cez, temp32a); + temp32blen = scale_expansion_zeroelim(bdlen, bd, ceztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(bclen, bc, -dez, temp32a); + temp32blen = scale_expansion_zeroelim(bclen, bc, -deztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, aex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, aex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, aextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, aex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, aextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, aey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, aey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, aeytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, aey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, aeytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, aez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, aez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, aeztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, aez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, aeztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + alen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, adet); + + temp32alen = scale_expansion_zeroelim(dalen, da, cez, temp32a); + temp32blen = scale_expansion_zeroelim(dalen, da, ceztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(aclen, ac, dez, temp32a); + temp32blen = scale_expansion_zeroelim(aclen, ac, deztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(cdlen, cd, aez, temp32a); + temp32blen = scale_expansion_zeroelim(cdlen, cd, aeztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, bex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, bex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, bextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, bex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, bextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, bey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, bey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, beytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, bey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, beytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, bez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, bez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, beztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, bez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, beztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + blen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, bdet); + + temp32alen = scale_expansion_zeroelim(ablen, ab, -dez, temp32a); + temp32blen = scale_expansion_zeroelim(ablen, ab, -deztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(bdlen, bd, -aez, temp32a); + temp32blen = scale_expansion_zeroelim(bdlen, bd, -aeztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(dalen, da, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(dalen, da, -beztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, cex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, cex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, cextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, cex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, cextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, cey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, cey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, ceytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, cey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, ceytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, cez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, cez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, ceztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, cez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, ceztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + clen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, cdet); + + temp32alen = scale_expansion_zeroelim(bclen, bc, aez, temp32a); + temp32blen = scale_expansion_zeroelim(bclen, bc, aeztail, temp32b); + temp64alen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64a); + temp32alen = scale_expansion_zeroelim(aclen, ac, -bez, temp32a); + temp32blen = scale_expansion_zeroelim(aclen, ac, -beztail, temp32b); + temp64blen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64b); + temp32alen = scale_expansion_zeroelim(ablen, ab, cez, temp32a); + temp32blen = scale_expansion_zeroelim(ablen, ab, ceztail, temp32b); + temp64clen = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64c); + temp128len = fast_expansion_sum_zeroelim(temp64alen, temp64a, + temp64blen, temp64b, temp128); + temp192len = fast_expansion_sum_zeroelim(temp64clen, temp64c, + temp128len, temp128, temp192); + xlen = scale_expansion_zeroelim(temp192len, temp192, dex, detx); + xxlen = scale_expansion_zeroelim(xlen, detx, dex, detxx); + xtlen = scale_expansion_zeroelim(temp192len, temp192, dextail, detxt); + xxtlen = scale_expansion_zeroelim(xtlen, detxt, dex, detxxt); + for (i = 0; i < xxtlen; i++) { + detxxt[i] *= 2.0; + } + xtxtlen = scale_expansion_zeroelim(xtlen, detxt, dextail, detxtxt); + x1len = fast_expansion_sum_zeroelim(xxlen, detxx, xxtlen, detxxt, x1); + x2len = fast_expansion_sum_zeroelim(x1len, x1, xtxtlen, detxtxt, x2); + ylen = scale_expansion_zeroelim(temp192len, temp192, dey, dety); + yylen = scale_expansion_zeroelim(ylen, dety, dey, detyy); + ytlen = scale_expansion_zeroelim(temp192len, temp192, deytail, detyt); + yytlen = scale_expansion_zeroelim(ytlen, detyt, dey, detyyt); + for (i = 0; i < yytlen; i++) { + detyyt[i] *= 2.0; + } + ytytlen = scale_expansion_zeroelim(ytlen, detyt, deytail, detytyt); + y1len = fast_expansion_sum_zeroelim(yylen, detyy, yytlen, detyyt, y1); + y2len = fast_expansion_sum_zeroelim(y1len, y1, ytytlen, detytyt, y2); + zlen = scale_expansion_zeroelim(temp192len, temp192, dez, detz); + zzlen = scale_expansion_zeroelim(zlen, detz, dez, detzz); + ztlen = scale_expansion_zeroelim(temp192len, temp192, deztail, detzt); + zztlen = scale_expansion_zeroelim(ztlen, detzt, dez, detzzt); + for (i = 0; i < zztlen; i++) { + detzzt[i] *= 2.0; + } + ztztlen = scale_expansion_zeroelim(ztlen, detzt, deztail, detztzt); + z1len = fast_expansion_sum_zeroelim(zzlen, detzz, zztlen, detzzt, z1); + z2len = fast_expansion_sum_zeroelim(z1len, z1, ztztlen, detztzt, z2); + xylen = fast_expansion_sum_zeroelim(x2len, x2, y2len, y2, detxy); + dlen = fast_expansion_sum_zeroelim(z2len, z2, xylen, detxy, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, deter); + + return deter[deterlen - 1]; +} + +REAL insphereadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, + REAL permanent) +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + REAL det, errbound; + + INEXACT REAL aexbey1, bexaey1, bexcey1, cexbey1; + INEXACT REAL cexdey1, dexcey1, dexaey1, aexdey1; + INEXACT REAL aexcey1, cexaey1, bexdey1, dexbey1; + REAL aexbey0, bexaey0, bexcey0, cexbey0; + REAL cexdey0, dexcey0, dexaey0, aexdey0; + REAL aexcey0, cexaey0, bexdey0, dexbey0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + INEXACT REAL ab3, bc3, cd3, da3, ac3, bd3; + REAL abeps, bceps, cdeps, daeps, aceps, bdeps; + REAL temp8a[8], temp8b[8], temp8c[8], temp16[16], temp24[24], temp48[48]; + int temp8alen, temp8blen, temp8clen, temp16len, temp24len, temp48len; + REAL xdet[96], ydet[96], zdet[96], xydet[192]; + int xlen, ylen, zlen, xylen; + REAL adet[288], bdet[288], cdet[288], ddet[288]; + int alen, blen, clen, dlen; + REAL abdet[576], cddet[576]; + int ablen, cdlen; + REAL fin1[1152]; + int finlength; + + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + + aex = (REAL) (pa[0] - pe[0]); + bex = (REAL) (pb[0] - pe[0]); + cex = (REAL) (pc[0] - pe[0]); + dex = (REAL) (pd[0] - pe[0]); + aey = (REAL) (pa[1] - pe[1]); + bey = (REAL) (pb[1] - pe[1]); + cey = (REAL) (pc[1] - pe[1]); + dey = (REAL) (pd[1] - pe[1]); + aez = (REAL) (pa[2] - pe[2]); + bez = (REAL) (pb[2] - pe[2]); + cez = (REAL) (pc[2] - pe[2]); + dez = (REAL) (pd[2] - pe[2]); + + Two_Product(aex, bey, aexbey1, aexbey0); + Two_Product(bex, aey, bexaey1, bexaey0); + Two_Two_Diff(aexbey1, aexbey0, bexaey1, bexaey0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + + Two_Product(bex, cey, bexcey1, bexcey0); + Two_Product(cex, bey, cexbey1, cexbey0); + Two_Two_Diff(bexcey1, bexcey0, cexbey1, cexbey0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + + Two_Product(cex, dey, cexdey1, cexdey0); + Two_Product(dex, cey, dexcey1, dexcey0); + Two_Two_Diff(cexdey1, cexdey0, dexcey1, dexcey0, cd3, cd[2], cd[1], cd[0]); + cd[3] = cd3; + + Two_Product(dex, aey, dexaey1, dexaey0); + Two_Product(aex, dey, aexdey1, aexdey0); + Two_Two_Diff(dexaey1, dexaey0, aexdey1, aexdey0, da3, da[2], da[1], da[0]); + da[3] = da3; + + Two_Product(aex, cey, aexcey1, aexcey0); + Two_Product(cex, aey, cexaey1, cexaey0); + Two_Two_Diff(aexcey1, aexcey0, cexaey1, cexaey0, ac3, ac[2], ac[1], ac[0]); + ac[3] = ac3; + + Two_Product(bex, dey, bexdey1, bexdey0); + Two_Product(dex, bey, dexbey1, dexbey0); + Two_Two_Diff(bexdey1, bexdey0, dexbey1, dexbey0, bd3, bd[2], bd[1], bd[0]); + bd[3] = bd3; + + temp8alen = scale_expansion_zeroelim(4, cd, bez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -cez, temp8b); + temp8clen = scale_expansion_zeroelim(4, bc, dez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, -aex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, -aey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, aez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, -aez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + alen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, adet); + + temp8alen = scale_expansion_zeroelim(4, da, cez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, dez, temp8b); + temp8clen = scale_expansion_zeroelim(4, cd, aez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, bex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, bey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, bez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, bez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + blen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, bdet); + + temp8alen = scale_expansion_zeroelim(4, ab, dez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, aez, temp8b); + temp8clen = scale_expansion_zeroelim(4, da, bez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, -cex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, -cey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, cez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, -cez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + clen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, cdet); + + temp8alen = scale_expansion_zeroelim(4, bc, aez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -bez, temp8b); + temp8clen = scale_expansion_zeroelim(4, ab, cez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dex, temp48); + xlen = scale_expansion_zeroelim(temp48len, temp48, dex, xdet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dey, temp48); + ylen = scale_expansion_zeroelim(temp48len, temp48, dey, ydet); + temp48len = scale_expansion_zeroelim(temp24len, temp24, dez, temp48); + zlen = scale_expansion_zeroelim(temp48len, temp48, dez, zdet); + xylen = fast_expansion_sum_zeroelim(xlen, xdet, ylen, ydet, xydet); + dlen = fast_expansion_sum_zeroelim(xylen, xydet, zlen, zdet, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, fin1); + + det = estimate(finlength, fin1); + errbound = isperrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pe[0], aex, aextail); + Two_Diff_Tail(pa[1], pe[1], aey, aeytail); + Two_Diff_Tail(pa[2], pe[2], aez, aeztail); + Two_Diff_Tail(pb[0], pe[0], bex, bextail); + Two_Diff_Tail(pb[1], pe[1], bey, beytail); + Two_Diff_Tail(pb[2], pe[2], bez, beztail); + Two_Diff_Tail(pc[0], pe[0], cex, cextail); + Two_Diff_Tail(pc[1], pe[1], cey, ceytail); + Two_Diff_Tail(pc[2], pe[2], cez, ceztail); + Two_Diff_Tail(pd[0], pe[0], dex, dextail); + Two_Diff_Tail(pd[1], pe[1], dey, deytail); + Two_Diff_Tail(pd[2], pe[2], dez, deztail); + if ((aextail == 0.0) && (aeytail == 0.0) && (aeztail == 0.0) + && (bextail == 0.0) && (beytail == 0.0) && (beztail == 0.0) + && (cextail == 0.0) && (ceytail == 0.0) && (ceztail == 0.0) + && (dextail == 0.0) && (deytail == 0.0) && (deztail == 0.0)) { + return det; + } + + errbound = isperrboundC * permanent + resulterrbound * Absolute(det); + abeps = (aex * beytail + bey * aextail) + - (aey * bextail + bex * aeytail); + bceps = (bex * ceytail + cey * bextail) + - (bey * cextail + cex * beytail); + cdeps = (cex * deytail + dey * cextail) + - (cey * dextail + dex * ceytail); + daeps = (dex * aeytail + aey * dextail) + - (dey * aextail + aex * deytail); + aceps = (aex * ceytail + cey * aextail) + - (aey * cextail + cex * aeytail); + bdeps = (bex * deytail + dey * bextail) + - (bey * dextail + dex * beytail); + det += (((bex * bex + bey * bey + bez * bez) + * ((cez * daeps + dez * aceps + aez * cdeps) + + (ceztail * da3 + deztail * ac3 + aeztail * cd3)) + + (dex * dex + dey * dey + dez * dez) + * ((aez * bceps - bez * aceps + cez * abeps) + + (aeztail * bc3 - beztail * ac3 + ceztail * ab3))) + - ((aex * aex + aey * aey + aez * aez) + * ((bez * cdeps - cez * bdeps + dez * bceps) + + (beztail * cd3 - ceztail * bd3 + deztail * bc3)) + + (cex * cex + cey * cey + cez * cez) + * ((dez * abeps + aez * bdeps + bez * daeps) + + (deztail * ab3 + aeztail * bd3 + beztail * da3)))) + + 2.0 * (((bex * bextail + bey * beytail + bez * beztail) + * (cez * da3 + dez * ac3 + aez * cd3) + + (dex * dextail + dey * deytail + dez * deztail) + * (aez * bc3 - bez * ac3 + cez * ab3)) + - ((aex * aextail + aey * aeytail + aez * aeztail) + * (bez * cd3 - cez * bd3 + dez * bc3) + + (cex * cextail + cey * ceytail + cez * ceztail) + * (dez * ab3 + aez * bd3 + bez * da3))); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return insphereexact(pa, pb, pc, pd, pe); +} + +#ifdef USE_CGAL_PREDICATES + +REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + return (REAL) + - cgal_pred_obj.side_of_oriented_sphere_3_object() + (Point(pa[0], pa[1], pa[2]), + Point(pb[0], pb[1], pb[2]), + Point(pc[0], pc[1], pc[2]), + Point(pd[0], pd[1], pd[2]), + Point(pe[0], pe[1], pe[2])); +} + +#else + +REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL aexbey, bexaey, bexcey, cexbey, cexdey, dexcey, dexaey, aexdey; + REAL aexcey, cexaey, bexdey, dexbey; + REAL alift, blift, clift, dlift; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + REAL det; + + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + + aexbey = aex * bey; + bexaey = bex * aey; + ab = aexbey - bexaey; + bexcey = bex * cey; + cexbey = cex * bey; + bc = bexcey - cexbey; + cexdey = cex * dey; + dexcey = dex * cey; + cd = cexdey - dexcey; + dexaey = dex * aey; + aexdey = aex * dey; + da = dexaey - aexdey; + + aexcey = aex * cey; + cexaey = cex * aey; + ac = aexcey - cexaey; + bexdey = bex * dey; + dexbey = dex * bey; + bd = bexdey - dexbey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + alift = aex * aex + aey * aey + aez * aez; + blift = bex * bex + bey * bey + bez * bez; + clift = cex * cex + cey * cey + cez * cez; + dlift = dex * dex + dey * dey + dez * dez; + + det = (dlift * abc - clift * dab) + (blift * cda - alift * bcd); + + if (_use_inexact_arith) { + return det; + } + + if (_use_static_filter) { + if (fabs(det) > ispstaticfilter) return det; + //if (det > ispstaticfilter) return det; + //if (det < minus_ispstaticfilter) return det; + + } + + REAL aezplus, bezplus, cezplus, dezplus; + REAL aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus; + REAL cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus; + REAL aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus; + REAL permanent, errbound; + + aezplus = Absolute(aez); + bezplus = Absolute(bez); + cezplus = Absolute(cez); + dezplus = Absolute(dez); + aexbeyplus = Absolute(aexbey); + bexaeyplus = Absolute(bexaey); + bexceyplus = Absolute(bexcey); + cexbeyplus = Absolute(cexbey); + cexdeyplus = Absolute(cexdey); + dexceyplus = Absolute(dexcey); + dexaeyplus = Absolute(dexaey); + aexdeyplus = Absolute(aexdey); + aexceyplus = Absolute(aexcey); + cexaeyplus = Absolute(cexaey); + bexdeyplus = Absolute(bexdey); + dexbeyplus = Absolute(dexbey); + permanent = ((cexdeyplus + dexceyplus) * bezplus + + (dexbeyplus + bexdeyplus) * cezplus + + (bexceyplus + cexbeyplus) * dezplus) + * alift + + ((dexaeyplus + aexdeyplus) * cezplus + + (aexceyplus + cexaeyplus) * dezplus + + (cexdeyplus + dexceyplus) * aezplus) + * blift + + ((aexbeyplus + bexaeyplus) * dezplus + + (bexdeyplus + dexbeyplus) * aezplus + + (dexaeyplus + aexdeyplus) * bezplus) + * clift + + ((bexceyplus + cexbeyplus) * aezplus + + (cexaeyplus + aexceyplus) * bezplus + + (aexbeyplus + bexaeyplus) * cezplus) + * dlift; + errbound = isperrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return insphereadapt(pa, pb, pc, pd, pe, permanent); +} + +#endif // #ifdef USE_CGAL_PREDICATES + +/*****************************************************************************/ +/* */ +/* orient4d() Return a positive value if the point pe lies above the */ +/* hyperplane passing through pa, pb, pc, and pd; "above" is */ +/* defined in a manner best found by trial-and-error. Returns */ +/* a negative value if pe lies below the hyperplane. Returns */ +/* zero if the points are co-hyperplanar (not affinely */ +/* independent). The result is also a rough approximation of */ +/* 24 times the signed volume of the 4-simplex defined by the */ +/* five points. */ +/* */ +/* Uses exact arithmetic if necessary to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. This determinant is */ +/* computed adaptively, in the sense that exact arithmetic is used only to */ +/* the degree it is needed to ensure that the returned value has the */ +/* correct sign. Hence, orient4d() is usually quite fast, but will run */ +/* more slowly when the input points are hyper-coplanar or nearly so. */ +/* */ +/* See my Robust Predicates paper for details. */ +/* */ +/*****************************************************************************/ +//static +REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, REAL dheight, + REAL eheight) +{ + INEXACT REAL axby1, bxcy1, cxdy1, dxey1, exay1; + INEXACT REAL bxay1, cxby1, dxcy1, exdy1, axey1; + INEXACT REAL axcy1, bxdy1, cxey1, dxay1, exby1; + INEXACT REAL cxay1, dxby1, excy1, axdy1, bxey1; + REAL axby0, bxcy0, cxdy0, dxey0, exay0; + REAL bxay0, cxby0, dxcy0, exdy0, axey0; + REAL axcy0, bxdy0, cxey0, dxay0, exby0; + REAL cxay0, dxby0, excy0, axdy0, bxey0; + REAL ab[4], bc[4], cd[4], de[4], ea[4]; + REAL ac[4], bd[4], ce[4], da[4], eb[4]; + REAL temp8a[8], temp8b[8], temp16[16]; + int temp8alen, temp8blen, temp16len; + REAL abc[24], bcd[24], cde[24], dea[24], eab[24]; + REAL abd[24], bce[24], cda[24], deb[24], eac[24]; + int abclen, bcdlen, cdelen, dealen, eablen; + int abdlen, bcelen, cdalen, deblen, eaclen; + REAL temp48a[48], temp48b[48]; + int temp48alen, temp48blen; + REAL abcd[96], bcde[96], cdea[96], deab[96], eabc[96]; + int abcdlen, bcdelen, cdealen, deablen, eabclen; + REAL adet[192], bdet[192], cdet[192], ddet[192], edet[192]; + int alen, blen, clen, dlen, elen; + REAL abdet[384], cddet[384], cdedet[576]; + int ablen, cdlen; + REAL deter[960]; + int deterlen; + int i; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + + Two_Product(pa[0], pb[1], axby1, axby0); + Two_Product(pb[0], pa[1], bxay1, bxay0); + Two_Two_Diff(axby1, axby0, bxay1, bxay0, ab[3], ab[2], ab[1], ab[0]); + + Two_Product(pb[0], pc[1], bxcy1, bxcy0); + Two_Product(pc[0], pb[1], cxby1, cxby0); + Two_Two_Diff(bxcy1, bxcy0, cxby1, cxby0, bc[3], bc[2], bc[1], bc[0]); + + Two_Product(pc[0], pd[1], cxdy1, cxdy0); + Two_Product(pd[0], pc[1], dxcy1, dxcy0); + Two_Two_Diff(cxdy1, cxdy0, dxcy1, dxcy0, cd[3], cd[2], cd[1], cd[0]); + + Two_Product(pd[0], pe[1], dxey1, dxey0); + Two_Product(pe[0], pd[1], exdy1, exdy0); + Two_Two_Diff(dxey1, dxey0, exdy1, exdy0, de[3], de[2], de[1], de[0]); + + Two_Product(pe[0], pa[1], exay1, exay0); + Two_Product(pa[0], pe[1], axey1, axey0); + Two_Two_Diff(exay1, exay0, axey1, axey0, ea[3], ea[2], ea[1], ea[0]); + + Two_Product(pa[0], pc[1], axcy1, axcy0); + Two_Product(pc[0], pa[1], cxay1, cxay0); + Two_Two_Diff(axcy1, axcy0, cxay1, cxay0, ac[3], ac[2], ac[1], ac[0]); + + Two_Product(pb[0], pd[1], bxdy1, bxdy0); + Two_Product(pd[0], pb[1], dxby1, dxby0); + Two_Two_Diff(bxdy1, bxdy0, dxby1, dxby0, bd[3], bd[2], bd[1], bd[0]); + + Two_Product(pc[0], pe[1], cxey1, cxey0); + Two_Product(pe[0], pc[1], excy1, excy0); + Two_Two_Diff(cxey1, cxey0, excy1, excy0, ce[3], ce[2], ce[1], ce[0]); + + Two_Product(pd[0], pa[1], dxay1, dxay0); + Two_Product(pa[0], pd[1], axdy1, axdy0); + Two_Two_Diff(dxay1, dxay0, axdy1, axdy0, da[3], da[2], da[1], da[0]); + + Two_Product(pe[0], pb[1], exby1, exby0); + Two_Product(pb[0], pe[1], bxey1, bxey0); + Two_Two_Diff(exby1, exby0, bxey1, bxey0, eb[3], eb[2], eb[1], eb[0]); + + temp8alen = scale_expansion_zeroelim(4, bc, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pc[2], temp8a); + abclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abc); + + temp8alen = scale_expansion_zeroelim(4, cd, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pd[2], temp8a); + bcdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bcd); + + temp8alen = scale_expansion_zeroelim(4, de, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, -pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pe[2], temp8a); + cdelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cde); + + temp8alen = scale_expansion_zeroelim(4, ea, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, -pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pa[2], temp8a); + dealen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + dea); + + temp8alen = scale_expansion_zeroelim(4, ab, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, -pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pb[2], temp8a); + eablen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eab); + + temp8alen = scale_expansion_zeroelim(4, bd, pa[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, da, pb[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ab, pd[2], temp8a); + abdlen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + abd); + + temp8alen = scale_expansion_zeroelim(4, ce, pb[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, eb, pc[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, bc, pe[2], temp8a); + bcelen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + bce); + + temp8alen = scale_expansion_zeroelim(4, da, pc[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, pd[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, cd, pa[2], temp8a); + cdalen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + cda); + + temp8alen = scale_expansion_zeroelim(4, eb, pd[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, pe[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, de, pb[2], temp8a); + deblen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + deb); + + temp8alen = scale_expansion_zeroelim(4, ac, pe[2], temp8a); + temp8blen = scale_expansion_zeroelim(4, ce, pa[2], temp8b); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp8blen, temp8b, + temp16); + temp8alen = scale_expansion_zeroelim(4, ea, pc[2], temp8a); + eaclen = fast_expansion_sum_zeroelim(temp8alen, temp8a, temp16len, temp16, + eac); + + temp48alen = fast_expansion_sum_zeroelim(cdelen, cde, bcelen, bce, temp48a); + temp48blen = fast_expansion_sum_zeroelim(deblen, deb, bcdlen, bcd, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + bcdelen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, bcde); + alen = scale_expansion_zeroelim(bcdelen, bcde, aheight, adet); + + temp48alen = fast_expansion_sum_zeroelim(dealen, dea, cdalen, cda, temp48a); + temp48blen = fast_expansion_sum_zeroelim(eaclen, eac, cdelen, cde, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + cdealen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, cdea); + blen = scale_expansion_zeroelim(cdealen, cdea, bheight, bdet); + + temp48alen = fast_expansion_sum_zeroelim(eablen, eab, deblen, deb, temp48a); + temp48blen = fast_expansion_sum_zeroelim(abdlen, abd, dealen, dea, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + deablen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, deab); + clen = scale_expansion_zeroelim(deablen, deab, cheight, cdet); + + temp48alen = fast_expansion_sum_zeroelim(abclen, abc, eaclen, eac, temp48a); + temp48blen = fast_expansion_sum_zeroelim(bcelen, bce, eablen, eab, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + eabclen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, eabc); + dlen = scale_expansion_zeroelim(eabclen, eabc, dheight, ddet); + + temp48alen = fast_expansion_sum_zeroelim(bcdlen, bcd, abdlen, abd, temp48a); + temp48blen = fast_expansion_sum_zeroelim(cdalen, cda, abclen, abc, temp48b); + for (i = 0; i < temp48blen; i++) { + temp48b[i] = -temp48b[i]; + } + abcdlen = fast_expansion_sum_zeroelim(temp48alen, temp48a, + temp48blen, temp48b, abcd); + elen = scale_expansion_zeroelim(abcdlen, abcd, eheight, edet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + cdelen = fast_expansion_sum_zeroelim(cdlen, cddet, elen, edet, cdedet); + deterlen = fast_expansion_sum_zeroelim(ablen, abdet, cdelen, cdedet, deter); + + return deter[deterlen - 1]; +} + +static +REAL orient4dadapt(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, REAL dheight, + REAL eheight, REAL permanent) +{ + INEXACT REAL aex, bex, cex, dex, aey, bey, cey, dey, aez, bez, cez, dez; + INEXACT REAL aeheight, beheight, ceheight, deheight; + REAL det, errbound; + + INEXACT REAL aexbey1, bexaey1, bexcey1, cexbey1; + INEXACT REAL cexdey1, dexcey1, dexaey1, aexdey1; + INEXACT REAL aexcey1, cexaey1, bexdey1, dexbey1; + REAL aexbey0, bexaey0, bexcey0, cexbey0; + REAL cexdey0, dexcey0, dexaey0, aexdey0; + REAL aexcey0, cexaey0, bexdey0, dexbey0; + REAL ab[4], bc[4], cd[4], da[4], ac[4], bd[4]; + INEXACT REAL ab3, bc3, cd3, da3, ac3, bd3; + REAL abeps, bceps, cdeps, daeps, aceps, bdeps; + REAL temp8a[8], temp8b[8], temp8c[8], temp16[16], temp24[24]; + int temp8alen, temp8blen, temp8clen, temp16len, temp24len; + REAL adet[48], bdet[48], cdet[48], ddet[48]; + int alen, blen, clen, dlen; + REAL abdet[96], cddet[96]; + int ablen, cdlen; + REAL fin1[192]; + int finlength; + + REAL aextail, bextail, cextail, dextail; + REAL aeytail, beytail, ceytail, deytail; + REAL aeztail, beztail, ceztail, deztail; + REAL aeheighttail, beheighttail, ceheighttail, deheighttail; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + + aex = (REAL) (pa[0] - pe[0]); + bex = (REAL) (pb[0] - pe[0]); + cex = (REAL) (pc[0] - pe[0]); + dex = (REAL) (pd[0] - pe[0]); + aey = (REAL) (pa[1] - pe[1]); + bey = (REAL) (pb[1] - pe[1]); + cey = (REAL) (pc[1] - pe[1]); + dey = (REAL) (pd[1] - pe[1]); + aez = (REAL) (pa[2] - pe[2]); + bez = (REAL) (pb[2] - pe[2]); + cez = (REAL) (pc[2] - pe[2]); + dez = (REAL) (pd[2] - pe[2]); + aeheight = (REAL) (aheight - eheight); + beheight = (REAL) (bheight - eheight); + ceheight = (REAL) (cheight - eheight); + deheight = (REAL) (dheight - eheight); + + Two_Product(aex, bey, aexbey1, aexbey0); + Two_Product(bex, aey, bexaey1, bexaey0); + Two_Two_Diff(aexbey1, aexbey0, bexaey1, bexaey0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + + Two_Product(bex, cey, bexcey1, bexcey0); + Two_Product(cex, bey, cexbey1, cexbey0); + Two_Two_Diff(bexcey1, bexcey0, cexbey1, cexbey0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + + Two_Product(cex, dey, cexdey1, cexdey0); + Two_Product(dex, cey, dexcey1, dexcey0); + Two_Two_Diff(cexdey1, cexdey0, dexcey1, dexcey0, cd3, cd[2], cd[1], cd[0]); + cd[3] = cd3; + + Two_Product(dex, aey, dexaey1, dexaey0); + Two_Product(aex, dey, aexdey1, aexdey0); + Two_Two_Diff(dexaey1, dexaey0, aexdey1, aexdey0, da3, da[2], da[1], da[0]); + da[3] = da3; + + Two_Product(aex, cey, aexcey1, aexcey0); + Two_Product(cex, aey, cexaey1, cexaey0); + Two_Two_Diff(aexcey1, aexcey0, cexaey1, cexaey0, ac3, ac[2], ac[1], ac[0]); + ac[3] = ac3; + + Two_Product(bex, dey, bexdey1, bexdey0); + Two_Product(dex, bey, dexbey1, dexbey0); + Two_Two_Diff(bexdey1, bexdey0, dexbey1, dexbey0, bd3, bd[2], bd[1], bd[0]); + bd[3] = bd3; + + temp8alen = scale_expansion_zeroelim(4, cd, bez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, -cez, temp8b); + temp8clen = scale_expansion_zeroelim(4, bc, dez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + alen = scale_expansion_zeroelim(temp24len, temp24, -aeheight, adet); + + temp8alen = scale_expansion_zeroelim(4, da, cez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, dez, temp8b); + temp8clen = scale_expansion_zeroelim(4, cd, aez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + blen = scale_expansion_zeroelim(temp24len, temp24, beheight, bdet); + + temp8alen = scale_expansion_zeroelim(4, ab, dez, temp8a); + temp8blen = scale_expansion_zeroelim(4, bd, aez, temp8b); + temp8clen = scale_expansion_zeroelim(4, da, bez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + clen = scale_expansion_zeroelim(temp24len, temp24, -ceheight, cdet); + + temp8alen = scale_expansion_zeroelim(4, bc, aez, temp8a); + temp8blen = scale_expansion_zeroelim(4, ac, -bez, temp8b); + temp8clen = scale_expansion_zeroelim(4, ab, cez, temp8c); + temp16len = fast_expansion_sum_zeroelim(temp8alen, temp8a, + temp8blen, temp8b, temp16); + temp24len = fast_expansion_sum_zeroelim(temp8clen, temp8c, + temp16len, temp16, temp24); + dlen = scale_expansion_zeroelim(temp24len, temp24, deheight, ddet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + cdlen = fast_expansion_sum_zeroelim(clen, cdet, dlen, ddet, cddet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, cdlen, cddet, fin1); + + det = estimate(finlength, fin1); + errbound = isperrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pe[0], aex, aextail); + Two_Diff_Tail(pa[1], pe[1], aey, aeytail); + Two_Diff_Tail(pa[2], pe[2], aez, aeztail); + Two_Diff_Tail(aheight, eheight, aeheight, aeheighttail); + Two_Diff_Tail(pb[0], pe[0], bex, bextail); + Two_Diff_Tail(pb[1], pe[1], bey, beytail); + Two_Diff_Tail(pb[2], pe[2], bez, beztail); + Two_Diff_Tail(bheight, eheight, beheight, beheighttail); + Two_Diff_Tail(pc[0], pe[0], cex, cextail); + Two_Diff_Tail(pc[1], pe[1], cey, ceytail); + Two_Diff_Tail(pc[2], pe[2], cez, ceztail); + Two_Diff_Tail(cheight, eheight, ceheight, ceheighttail); + Two_Diff_Tail(pd[0], pe[0], dex, dextail); + Two_Diff_Tail(pd[1], pe[1], dey, deytail); + Two_Diff_Tail(pd[2], pe[2], dez, deztail); + Two_Diff_Tail(dheight, eheight, deheight, deheighttail); + if ((aextail == 0.0) && (aeytail == 0.0) && (aeztail == 0.0) + && (bextail == 0.0) && (beytail == 0.0) && (beztail == 0.0) + && (cextail == 0.0) && (ceytail == 0.0) && (ceztail == 0.0) + && (dextail == 0.0) && (deytail == 0.0) && (deztail == 0.0) + && (aeheighttail == 0.0) && (beheighttail == 0.0) + && (ceheighttail == 0.0) && (deheighttail == 0.0)) { + return det; + } + + errbound = isperrboundC * permanent + resulterrbound * Absolute(det); + abeps = (aex * beytail + bey * aextail) + - (aey * bextail + bex * aeytail); + bceps = (bex * ceytail + cey * bextail) + - (bey * cextail + cex * beytail); + cdeps = (cex * deytail + dey * cextail) + - (cey * dextail + dex * ceytail); + daeps = (dex * aeytail + aey * dextail) + - (dey * aextail + aex * deytail); + aceps = (aex * ceytail + cey * aextail) + - (aey * cextail + cex * aeytail); + bdeps = (bex * deytail + dey * bextail) + - (bey * dextail + dex * beytail); + det += ((beheight + * ((cez * daeps + dez * aceps + aez * cdeps) + + (ceztail * da3 + deztail * ac3 + aeztail * cd3)) + + deheight + * ((aez * bceps - bez * aceps + cez * abeps) + + (aeztail * bc3 - beztail * ac3 + ceztail * ab3))) + - (aeheight + * ((bez * cdeps - cez * bdeps + dez * bceps) + + (beztail * cd3 - ceztail * bd3 + deztail * bc3)) + + ceheight + * ((dez * abeps + aez * bdeps + bez * daeps) + + (deztail * ab3 + aeztail * bd3 + beztail * da3)))) + + ((beheighttail * (cez * da3 + dez * ac3 + aez * cd3) + + deheighttail * (aez * bc3 - bez * ac3 + cez * ab3)) + - (aeheighttail * (bez * cd3 - cez * bd3 + dez * bc3) + + ceheighttail * (dez * ab3 + aez * bd3 + bez * da3))); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return orient4dexact(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight); +} + +REAL orient4d(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, REAL dheight, + REAL eheight) +{ + REAL aex, bex, cex, dex; + REAL aey, bey, cey, dey; + REAL aez, bez, cez, dez; + REAL aexbey, bexaey, bexcey, cexbey, cexdey, dexcey, dexaey, aexdey; + REAL aexcey, cexaey, bexdey, dexbey; + REAL aeheight, beheight, ceheight, deheight; + REAL ab, bc, cd, da, ac, bd; + REAL abc, bcd, cda, dab; + REAL aezplus, bezplus, cezplus, dezplus; + REAL aexbeyplus, bexaeyplus, bexceyplus, cexbeyplus; + REAL cexdeyplus, dexceyplus, dexaeyplus, aexdeyplus; + REAL aexceyplus, cexaeyplus, bexdeyplus, dexbeyplus; + REAL det; + REAL permanent, errbound; + + + aex = pa[0] - pe[0]; + bex = pb[0] - pe[0]; + cex = pc[0] - pe[0]; + dex = pd[0] - pe[0]; + aey = pa[1] - pe[1]; + bey = pb[1] - pe[1]; + cey = pc[1] - pe[1]; + dey = pd[1] - pe[1]; + aez = pa[2] - pe[2]; + bez = pb[2] - pe[2]; + cez = pc[2] - pe[2]; + dez = pd[2] - pe[2]; + aeheight = aheight - eheight; + beheight = bheight - eheight; + ceheight = cheight - eheight; + deheight = dheight - eheight; + + aexbey = aex * bey; + bexaey = bex * aey; + ab = aexbey - bexaey; + bexcey = bex * cey; + cexbey = cex * bey; + bc = bexcey - cexbey; + cexdey = cex * dey; + dexcey = dex * cey; + cd = cexdey - dexcey; + dexaey = dex * aey; + aexdey = aex * dey; + da = dexaey - aexdey; + + aexcey = aex * cey; + cexaey = cex * aey; + ac = aexcey - cexaey; + bexdey = bex * dey; + dexbey = dex * bey; + bd = bexdey - dexbey; + + abc = aez * bc - bez * ac + cez * ab; + bcd = bez * cd - cez * bd + dez * bc; + cda = cez * da + dez * ac + aez * cd; + dab = dez * ab + aez * bd + bez * da; + + det = (deheight * abc - ceheight * dab) + (beheight * cda - aeheight * bcd); + + aezplus = Absolute(aez); + bezplus = Absolute(bez); + cezplus = Absolute(cez); + dezplus = Absolute(dez); + aexbeyplus = Absolute(aexbey); + bexaeyplus = Absolute(bexaey); + bexceyplus = Absolute(bexcey); + cexbeyplus = Absolute(cexbey); + cexdeyplus = Absolute(cexdey); + dexceyplus = Absolute(dexcey); + dexaeyplus = Absolute(dexaey); + aexdeyplus = Absolute(aexdey); + aexceyplus = Absolute(aexcey); + cexaeyplus = Absolute(cexaey); + bexdeyplus = Absolute(bexdey); + dexbeyplus = Absolute(dexbey); + permanent = ((cexdeyplus + dexceyplus) * bezplus + + (dexbeyplus + bexdeyplus) * cezplus + + (bexceyplus + cexbeyplus) * dezplus) + * Absolute(aeheight) + + ((dexaeyplus + aexdeyplus) * cezplus + + (aexceyplus + cexaeyplus) * dezplus + + (cexdeyplus + dexceyplus) * aezplus) + * Absolute(beheight) + + ((aexbeyplus + bexaeyplus) * dezplus + + (bexdeyplus + dexbeyplus) * aezplus + + (dexaeyplus + aexdeyplus) * bezplus) + * Absolute(ceheight) + + ((bexceyplus + cexbeyplus) * aezplus + + (cexaeyplus + aexceyplus) * bezplus + + (aexbeyplus + bexaeyplus) * cezplus) + * Absolute(deheight); + errbound = isperrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return orient4dadapt(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight, permanent); +} + + + + + + +//============================================================================== + + diff --git a/cocos/gi/light-probe/tetgen.cpp b/cocos/gi/light-probe/tetgen.cpp new file mode 100644 index 0000000..5873f45 --- /dev/null +++ b/cocos/gi/light-probe/tetgen.cpp @@ -0,0 +1,36576 @@ +//============================================================================// +// // +// TetGen // +// // +// A Quality Tetrahedral Mesh Generator and A 3D Delaunay Triangulator // +// // +// Version 1.6.0 // +// August 31, 2020 // +// // +// Copyright (C) 2002--2020 // +// // +// Hang Si // +// Research Group: Numerical Mathematics and Scientific Computing // +// Weierstrass Institute for Applied Analysis and Stochastics (WIAS) // +// Mohrenstr. 39, 10117 Berlin, Germany // +// si@wias-berlin.de // +// // +// TetGen is a tetrahedral mesh generator. It creates 3d triangulations of // +// polyhedral domains. It generates meshes with well-shaped elements whose // +// sizes are adapted to the geometric features or user-provided sizing // +// functions. It has applications in various applications in scientific // +// computing, such as computer graphics (CG), computer-aided design (CAD), // +// geometry processing (parametrizations and computer animation), and // +// physical simulations (finite element analysis). // +// // +// TetGen computes (weighted) Delaunay triangulations for three-dimensional // +// (weighted) point sets, and constrained Delaunay triangulations for // +// three-dimensional polyhedral domains. In the latter case, input edges // +// and triangles can be completely preserved in the output meshes. TetGen // +// can refine or coarsen an existing mesh to result in good quality and // +// size-adapted mesh according to the geometric features and user-defined // +// mesh sizing functions. // +// // +// TetGen implements theoretically proven algorithms for computing the // +// Delaunay and constrained Delaunay tetrahedralizations. TetGen achieves // +// robustness and efficiency by using advanced techniques in computational // +// geometry. A technical paper describes the algorithms and methods // +// implemented in TetGen is available in ACM-TOMS, Hang Si ``TetGen, a // +// Delaunay-Based Quality Tetrahedral Mesh Generator", ACM Transactions on // +// Mathematical Software, February 2015, https://doi.org/10.1145/2629697. // +// // +// TetGen is freely available through the website: http://www.tetgen.org. // +// It may be copied, modified, and redistributed for non-commercial use. // +// Please consult the file LICENSE for the detailed copyright notices. // +// // +//============================================================================// + +#include "tetgen.h" + +#ifdef __clang__ + #pragma clang diagnostic ignored "-Wshorten-64-to-32" +#endif + +#pragma warning(push) +#pragma warning(disable : 4101) +#pragma warning(disable : 4477) + +//== io_cxx ==================================================================// +// // +// // + +//============================================================================// +// // +// load_node_call() Read a list of points from a file. // +// // +// 'infile' is the file handle contains the node list. It may point to a // +// .node, or .poly or .smesh file. 'markers' indicates each node contains an // +// additional marker (integer) or not. 'uvflag' indicates each node contains // +// u,v coordinates or not. It is reuqired by a PSC. 'infilename' is the name // +// of the file being read, it is only used in error messages. // +// // +// The 'firstnumber' (0 or 1) is automatically determined by the number of // +// the first index of the first point. // +// // +//============================================================================// + +bool tetgenio::load_node_call(FILE* infile, int markers, int uvflag, + char* infilename) +{ + char inputline[INPUTLINESIZE]; + char *stringptr; + REAL x, y, z, attrib; + int firstnode, currentmarker; + int index, attribindex; + int i, j; + + // Initialize 'pointlist', 'pointattributelist', and 'pointmarkerlist'. + pointlist = new REAL[numberofpoints * 3]; + if (pointlist == (REAL *) NULL) { + terminatetetgen(NULL, 1); + } + if (numberofpointattributes > 0) { + pointattributelist = new REAL[numberofpoints * numberofpointattributes]; + if (pointattributelist == (REAL *) NULL) { + terminatetetgen(NULL, 1); + } + } + if (markers) { + pointmarkerlist = new int[numberofpoints]; + if (pointmarkerlist == (int *) NULL) { + terminatetetgen(NULL, 1); + } + } + if (uvflag) { + pointparamlist = new pointparam[numberofpoints]; + if (pointparamlist == NULL) { + terminatetetgen(NULL, 1); + } + } + + // Read the point section. + index = 0; + attribindex = 0; + for (i = 0; i < numberofpoints; i++) { + stringptr = readnumberline(inputline, infile, infilename); + if (useindex) { + if (i == 0) { + firstnode = (int) strtol (stringptr, &stringptr, 0); + if ((firstnode == 0) || (firstnode == 1)) { + firstnumber = firstnode; + } + } + stringptr = findnextnumber(stringptr); + } // if (useindex) + if (*stringptr == '\0') { + printf("Error: Point %d has no x coordinate.\n", firstnumber + i); + break; + } + x = (REAL) strtod(stringptr, &stringptr); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Point %d has no y coordinate.\n", firstnumber + i); + break; + } + y = (REAL) strtod(stringptr, &stringptr); + if (mesh_dim == 3) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Point %d has no z coordinate.\n", firstnumber + i); + break; + } + z = (REAL) strtod(stringptr, &stringptr); + } else { + z = 0.0; // mesh_dim == 2; + } + pointlist[index++] = x; + pointlist[index++] = y; + pointlist[index++] = z; + // Read the point attributes. + for (j = 0; j < numberofpointattributes; j++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + attrib = 0.0; + } else { + attrib = (REAL) strtod(stringptr, &stringptr); + } + pointattributelist[attribindex++] = attrib; + } + if (markers) { + // Read a point marker. + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + currentmarker = 0; + } else { + currentmarker = (int) strtol (stringptr, &stringptr, 0); + } + pointmarkerlist[i] = currentmarker; + } + } + if (i < numberofpoints) { + // Failed to read points due to some error. + delete [] pointlist; + pointlist = (REAL *) NULL; + if (markers) { + delete [] pointmarkerlist; + pointmarkerlist = (int *) NULL; + } + if (numberofpointattributes > 0) { + delete [] pointattributelist; + pointattributelist = (REAL *) NULL; + } + if (uvflag) { + delete [] pointparamlist; + pointparamlist = NULL; + } + numberofpoints = 0; + return false; + } + return true; +} + +//============================================================================// +// // +// load_node() Load a list of points from a .node file. // +// // +//============================================================================// + +bool tetgenio::load_node(char* filebasename) +{ + FILE *infile; + char innodefilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + bool okflag; + int markers; + int uvflag; // for psc input. + + // Assembling the actual file names we want to open. + strcpy(innodefilename, filebasename); + strcat(innodefilename, ".node"); + + // Try to open a .node file. + infile = fopen(innodefilename, "r"); + if (infile == (FILE *) NULL) { + printf(" Cannot access file %s.\n", innodefilename); + return false; + } + printf("Opening %s.\n", innodefilename); + + // Set initial flags. + mesh_dim = 3; + numberofpointattributes = 0; // no point attribute. + markers = 0; // no boundary marker. + uvflag = 0; // no uv parameters (required by a PSC). + + // Read the first line of the file. + stringptr = readnumberline(inputline, infile, innodefilename); + // Does this file contain an index column? + stringptr = strstr(inputline, "rbox"); + if (stringptr == NULL) { + // Read number of points, number of dimensions, number of point + // attributes, and number of boundary markers. + stringptr = inputline; + numberofpoints = (int) strtol (stringptr, &stringptr, 0); + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + mesh_dim = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + numberofpointattributes = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + markers = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + uvflag = (int) strtol (stringptr, &stringptr, 0); + } + } else { + // It is a rbox (qhull) input file. + stringptr = inputline; + // Get the dimension. + mesh_dim = (int) strtol (stringptr, &stringptr, 0); + // Get the number of points. + stringptr = readnumberline(inputline, infile, innodefilename); + numberofpoints = (int) strtol (stringptr, &stringptr, 0); + // There is no index column. + useindex = 0; + } + + // Load the list of nodes. + okflag = load_node_call(infile, markers, uvflag, innodefilename); + + fclose(infile); + return okflag; +} + +//============================================================================// +// // +// load_edge() Load a list of edges from a .edge file. // +// // +//============================================================================// + +bool tetgenio::load_edge(char* filebasename) +{ + FILE *infile; + char inedgefilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + int markers, corner; + int index; + int i, j; + + strcpy(inedgefilename, filebasename); + strcat(inedgefilename, ".edge"); + + infile = fopen(inedgefilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", inedgefilename); + } else { + //printf(" Cannot access file %s.\n", inedgefilename); + return false; + } + + // Read number of boundary edges. + stringptr = readnumberline(inputline, infile, inedgefilename); + numberofedges = (int) strtol (stringptr, &stringptr, 0); + if (numberofedges > 0) { + edgelist = new int[numberofedges * 2]; + if (edgelist == (int *) NULL) { + terminatetetgen(NULL, 1); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + markers = 0; // Default value. + } else { + markers = (int) strtol (stringptr, &stringptr, 0); + } + if (markers > 0) { + edgemarkerlist = new int[numberofedges]; + } + } + + // Read the list of edges. + index = 0; + for (i = 0; i < numberofedges; i++) { + // Read edge index and the edge's two endpoints. + stringptr = readnumberline(inputline, infile, inedgefilename); + for (j = 0; j < 2; j++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Edge %d is missing vertex %d in %s.\n", + i + firstnumber, j + 1, inedgefilename); + terminatetetgen(NULL, 1); + } + corner = (int) strtol(stringptr, &stringptr, 0); + if (corner < firstnumber || corner >= numberofpoints + firstnumber) { + printf("Error: Edge %d has an invalid vertex index.\n", + i + firstnumber); + terminatetetgen(NULL, 1); + } + edgelist[index++] = corner; + } + if (numberofcorners == 10) { + // Skip an extra vertex (generated by a previous -o2 option). + stringptr = findnextnumber(stringptr); + } + // Read the edge marker if it has. + if (markers) { + stringptr = findnextnumber(stringptr); + edgemarkerlist[i] = (int) strtol(stringptr, &stringptr, 0); + } + } + + fclose(infile); + return true; +} + +//============================================================================// +// // +// load_face() Load a list of faces (triangles) from a .face file. // +// // +//============================================================================// + +bool tetgenio::load_face(char* filebasename) +{ + FILE *infile; + char infilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + REAL attrib; + int markers, corner; + int index; + int i, j; + + strcpy(infilename, filebasename); + strcat(infilename, ".face"); + + infile = fopen(infilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", infilename); + } else { + return false; + } + + // Read number of faces, boundary markers. + stringptr = readnumberline(inputline, infile, infilename); + numberoftrifaces = (int) strtol (stringptr, &stringptr, 0); + stringptr = findnextnumber(stringptr); + if (mesh_dim == 2) { + // Skip a number. + stringptr = findnextnumber(stringptr); + } + if (*stringptr == '\0') { + markers = 0; // Default there is no marker per face. + } else { + markers = (int) strtol (stringptr, &stringptr, 0); + } + if (numberoftrifaces > 0) { + trifacelist = new int[numberoftrifaces * 3]; + if (trifacelist == (int *) NULL) { + terminatetetgen(NULL, 1); + } + if (markers) { + trifacemarkerlist = new int[numberoftrifaces]; + if (trifacemarkerlist == (int *) NULL) { + terminatetetgen(NULL, 1); + } + } + } + + // Read the list of faces. + index = 0; + for (i = 0; i < numberoftrifaces; i++) { + // Read face index and the face's three corners. + stringptr = readnumberline(inputline, infile, infilename); + for (j = 0; j < 3; j++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Face %d is missing vertex %d in %s.\n", + i + firstnumber, j + 1, infilename); + terminatetetgen(NULL, 1); + } + corner = (int) strtol(stringptr, &stringptr, 0); + if (corner < firstnumber || corner >= numberofpoints + firstnumber) { + printf("Error: Face %d has an invalid vertex index.\n", + i + firstnumber); + terminatetetgen(NULL, 1); + } + trifacelist[index++] = corner; + } + if (numberofcorners == 10) { + // Skip 3 extra vertices (generated by a previous -o2 option). + for (j = 0; j < 3; j++) { + stringptr = findnextnumber(stringptr); + } + } + // Read the boundary marker if it exists. + if (markers) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + attrib = 0.0; + } else { + attrib = (REAL) strtod(stringptr, &stringptr); + } + trifacemarkerlist[i] = (int) attrib; + } + } + + fclose(infile); + + return true; +} + +//============================================================================// +// // +// load_tet() Load a list of tetrahedra from a .ele file. // +// // +//============================================================================// + +bool tetgenio::load_tet(char* filebasename) +{ + FILE *infile; + char infilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + REAL attrib; + int corner; + int index, attribindex; + int i, j; + + strcpy(infilename, filebasename); + strcat(infilename, ".ele"); + + infile = fopen(infilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", infilename); + } else { + return false; + } + + // Read number of elements, number of corners (4 or 10), number of + // element attributes. + stringptr = readnumberline(inputline, infile, infilename); + numberoftetrahedra = (int) strtol (stringptr, &stringptr, 0); + if (numberoftetrahedra <= 0) { + printf("Error: Invalid number of tetrahedra.\n"); + fclose(infile); + return false; + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + numberofcorners = 4; // Default read 4 nodes per element. + } else { + numberofcorners = (int) strtol(stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + numberoftetrahedronattributes = 0; // Default no attribute. + } else { + numberoftetrahedronattributes = (int) strtol(stringptr, &stringptr, 0); + } + if (numberofcorners != 4 && numberofcorners != 10) { + printf("Error: Wrong number of corners %d (should be 4 or 10).\n", + numberofcorners); + fclose(infile); + return false; + } + + // Allocate memory for tetrahedra. + tetrahedronlist = new int[numberoftetrahedra * numberofcorners]; + if (tetrahedronlist == (int *) NULL) { + terminatetetgen(NULL, 1); + } + // Allocate memory for output tetrahedron attributes if necessary. + if (numberoftetrahedronattributes > 0) { + tetrahedronattributelist = new REAL[numberoftetrahedra * + numberoftetrahedronattributes]; + if (tetrahedronattributelist == (REAL *) NULL) { + terminatetetgen(NULL, 1); + } + } + + // Read the list of tetrahedra. + index = 0; + attribindex = 0; + for (i = 0; i < numberoftetrahedra; i++) { + // Read tetrahedron index and the tetrahedron's corners. + stringptr = readnumberline(inputline, infile, infilename); + for (j = 0; j < numberofcorners; j++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Tetrahedron %d is missing vertex %d in %s.\n", + i + firstnumber, j + 1, infilename); + terminatetetgen(NULL, 1); + } + corner = (int) strtol(stringptr, &stringptr, 0); + if (corner < firstnumber || corner >= numberofpoints + firstnumber) { + printf("Error: Tetrahedron %d has an invalid vertex index.\n", + i + firstnumber); + terminatetetgen(NULL, 1); + } + tetrahedronlist[index++] = corner; + } + // Read the tetrahedron's attributes. + for (j = 0; j < numberoftetrahedronattributes; j++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + attrib = 0.0; + } else { + attrib = (REAL) strtod(stringptr, &stringptr); + } + tetrahedronattributelist[attribindex++] = attrib; + } + } + + fclose(infile); + + return true; +} + +//============================================================================// +// // +// load_vol() Load a list of volume constraints from a .vol file. // +// // +//============================================================================// + +bool tetgenio::load_vol(char* filebasename) +{ + FILE *infile; + char inelefilename[FILENAMESIZE]; + char infilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + REAL volume; + int volelements; + int i; + + strcpy(infilename, filebasename); + strcat(infilename, ".vol"); + + infile = fopen(infilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", infilename); + } else { + return false; + } + + // Read number of tetrahedra. + stringptr = readnumberline(inputline, infile, infilename); + volelements = (int) strtol (stringptr, &stringptr, 0); + if (volelements != numberoftetrahedra) { + strcpy(inelefilename, filebasename); + strcat(infilename, ".ele"); + printf("Warning: %s and %s disagree on number of tetrahedra.\n", + inelefilename, infilename); + fclose(infile); + return false; + } + + tetrahedronvolumelist = new REAL[volelements]; + if (tetrahedronvolumelist == (REAL *) NULL) { + terminatetetgen(NULL, 1); + } + + // Read the list of volume constraints. + for (i = 0; i < volelements; i++) { + stringptr = readnumberline(inputline, infile, infilename); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + volume = -1.0; // No constraint on this tetrahedron. + } else { + volume = (REAL) strtod(stringptr, &stringptr); + } + tetrahedronvolumelist[i] = volume; + } + + fclose(infile); + + return true; +} + +//============================================================================// +// // +// load_var() Load constraints applied on facets, segments, and nodes // +// from a .var file. // +// // +//============================================================================// + +bool tetgenio::load_var(char* filebasename) +{ + FILE *infile; + char varfilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + int index; + int i; + + // Variant constraints are saved in file "filename.var". + strcpy(varfilename, filebasename); + strcat(varfilename, ".var"); + infile = fopen(varfilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", varfilename); + } else { + return false; + } + + // Read the facet constraint section. + stringptr = readnumberline(inputline, infile, varfilename); + if (stringptr == NULL) { + // No region list, return. + fclose(infile); + return true; + } + if (*stringptr != '\0') { + numberoffacetconstraints = (int) strtol (stringptr, &stringptr, 0); + } else { + numberoffacetconstraints = 0; + } + if (numberoffacetconstraints > 0) { + // Initialize 'facetconstraintlist'. + facetconstraintlist = new REAL[numberoffacetconstraints * 2]; + index = 0; + for (i = 0; i < numberoffacetconstraints; i++) { + stringptr = readnumberline(inputline, infile, varfilename); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: facet constraint %d has no facet marker.\n", + firstnumber + i); + break; + } else { + facetconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: facet constraint %d has no maximum area bound.\n", + firstnumber + i); + break; + } else { + facetconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + } + if (i < numberoffacetconstraints) { + // This must be caused by an error. + fclose(infile); + return false; + } + } + + // Read the segment constraint section. + stringptr = readnumberline(inputline, infile, varfilename); + if (stringptr == NULL) { + // No segment list, return. + fclose(infile); + return true; + } + if (*stringptr != '\0') { + numberofsegmentconstraints = (int) strtol (stringptr, &stringptr, 0); + } else { + numberofsegmentconstraints = 0; + } + if (numberofsegmentconstraints > 0) { + // Initialize 'segmentconstraintlist'. + segmentconstraintlist = new REAL[numberofsegmentconstraints * 3]; + index = 0; + for (i = 0; i < numberofsegmentconstraints; i++) { + stringptr = readnumberline(inputline, infile, varfilename); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: segment constraint %d has no frist endpoint.\n", + firstnumber + i); + break; + } else { + segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: segment constraint %d has no second endpoint.\n", + firstnumber + i); + break; + } else { + segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: segment constraint %d has no maximum length bound.\n", + firstnumber + i); + break; + } else { + segmentconstraintlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + } + if (i < numberofsegmentconstraints) { + // This must be caused by an error. + fclose(infile); + return false; + } + } + + fclose(infile); + return true; +} + +//============================================================================// +// // +// load_mtr() Load a size specification map from a .mtr file. // +// // +//============================================================================// + +bool tetgenio::load_mtr(char* filebasename) +{ + FILE *infile; + char mtrfilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr; + REAL mtr; + int ptnum; + int mtrindex; + int i, j; + + strcpy(mtrfilename, filebasename); + strcat(mtrfilename, ".mtr"); + infile = fopen(mtrfilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", mtrfilename); + } else { + return false; + } + + // Read the number of points. + stringptr = readnumberline(inputline, infile, mtrfilename); + ptnum = (int) strtol (stringptr, &stringptr, 0); + if (ptnum != numberofpoints) { + printf(" !! Point numbers are not equal. Ignored.\n"); + fclose(infile); + return false; + } + // Read the number of columns (1, 3, or 6). + stringptr = findnextnumber(stringptr); // Skip number of points. + if (*stringptr != '\0') { + numberofpointmtrs = (int) strtol (stringptr, &stringptr, 0); + } + if ((numberofpointmtrs != 1) && (numberofpointmtrs != 3) && + (numberofpointmtrs != 6)) { + // Column number doesn't match. + numberofpointmtrs = 0; + printf(" !! Metric size does not match (1, 3, or 6). Ignored.\n"); + fclose(infile); + return false; + } + + // Allocate space for pointmtrlist. + pointmtrlist = new REAL[numberofpoints * numberofpointmtrs]; + if (pointmtrlist == (REAL *) NULL) { + terminatetetgen(NULL, 1); + } + mtrindex = 0; + for (i = 0; i < numberofpoints; i++) { + // Read metrics. + stringptr = readnumberline(inputline, infile, mtrfilename); + for (j = 0; j < numberofpointmtrs; j++) { + if (*stringptr == '\0') { + printf("Error: Metric %d is missing value #%d in %s.\n", + i + firstnumber, j + 1, mtrfilename); + terminatetetgen(NULL, 1); + } + mtr = (REAL) strtod(stringptr, &stringptr); + pointmtrlist[mtrindex++] = mtr; + stringptr = findnextnumber(stringptr); + } + } + + fclose(infile); + return true; +} + +//============================================================================// +// // +// load_elem() Load a list of refine elements from an .elem file. // +// // +//============================================================================// + +bool tetgenio::load_elem(char* filebasename) +{ + FILE *infile; + char inelemfilename[FILENAMESIZE]; + char line[1024]; + + strcpy(inelemfilename, filebasename); + strcat(inelemfilename, ".elem"); + + // Try to open a .elem file. + infile = fopen(inelemfilename, "r"); + if (infile != (FILE *) NULL) { + printf("Opening %s.\n", inelemfilename); + } else { + return false; + } + + int elenum = 0; + float growth_ratio = 0.; + fgets(line, 1023, infile); + sscanf(line, "%d %f", &elenum, &growth_ratio); + + if (elenum == 0) { + fclose(infile); + return false; + } + + refine_elem_list = new int[elenum * 4]; + numberofrefineelems = elenum; + + int *idx, i; + for (i = 0; i < elenum; i++) { + fgets(line, 1023, infile); + idx = &(refine_elem_list[i*4]); + sscanf(line, "%d %d %d %d", &(idx[0]), &(idx[1]), &(idx[2]), &(idx[3])); + } + + fclose(infile); + return true; +} + +//============================================================================// +// // +// load_poly() Load a PL complex from a .poly or a .smesh file. // +// // +//============================================================================// + +bool tetgenio::load_poly(char* filebasename) +{ + FILE *infile; + char inpolyfilename[FILENAMESIZE]; + char insmeshfilename[FILENAMESIZE]; + char inputline[INPUTLINESIZE]; + char *stringptr, *infilename; + int smesh, markers, uvflag, currentmarker; + int index; + int i, j, k; + + // Assembling the actual file names we want to open. + strcpy(inpolyfilename, filebasename); + strcpy(insmeshfilename, filebasename); + strcat(inpolyfilename, ".poly"); + strcat(insmeshfilename, ".smesh"); + + // First assume it is a .poly file. + smesh = 0; + // Try to open a .poly file. + infile = fopen(inpolyfilename, "r"); + if (infile == (FILE *) NULL) { + // .poly doesn't exist! Try to open a .smesh file. + infile = fopen(insmeshfilename, "r"); + if (infile == (FILE *) NULL) { + printf(" Cannot access file %s and %s.\n", + inpolyfilename, insmeshfilename); + return false; + } else { + printf("Opening %s.\n", insmeshfilename); + infilename = insmeshfilename; + } + smesh = 1; + } else { + printf("Opening %s.\n", inpolyfilename); + infilename = inpolyfilename; + } + + // Initialize the default values. + mesh_dim = 3; // Three-dimensional coordinates. + numberofpointattributes = 0; // no point attribute. + markers = 0; // no boundary marker. + uvflag = 0; // no uv parameters (required by a PSC). + + // Read number of points, number of dimensions, number of point + // attributes, and number of boundary markers. + stringptr = readnumberline(inputline, infile, infilename); + numberofpoints = (int) strtol (stringptr, &stringptr, 0); + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + mesh_dim = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + numberofpointattributes = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + markers = (int) strtol (stringptr, &stringptr, 0); + } + if (*stringptr != '\0') { + uvflag = (int) strtol (stringptr, &stringptr, 0); + } + + if (numberofpoints > 0) { + // Load the list of nodes. + if (!load_node_call(infile, markers, uvflag, infilename)) { + fclose(infile); + return false; + } + } else { + // If the .poly or .smesh file claims there are zero points, that + // means the points should be read from a separate .node file. + if (!load_node(filebasename)) { + fclose(infile); + return false; + } + } + + if ((mesh_dim != 3) && (mesh_dim != 2)) { + printf("Input error: TetGen only works for 2D & 3D point sets.\n"); + fclose(infile); + return false; + } + if (numberofpoints < (mesh_dim + 1)) { + printf("Input error: TetGen needs at least %d points.\n", mesh_dim + 1); + fclose(infile); + return false; + } + + facet *f; + polygon *p; + + if (mesh_dim == 3) { + + // Read number of facets and number of boundary markers. + stringptr = readnumberline(inputline, infile, infilename); + if (stringptr == NULL) { + // No facet list, return. + fclose(infile); + return true; + } + numberoffacets = (int) strtol (stringptr, &stringptr, 0); + if (numberoffacets <= 0) { + // No facet list, return. + fclose(infile); + return true; + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + markers = 0; // no boundary marker. + } else { + markers = (int) strtol (stringptr, &stringptr, 0); + } + + // Initialize the 'facetlist', 'facetmarkerlist'. + facetlist = new facet[numberoffacets]; + if (markers == 1) { + facetmarkerlist = new int[numberoffacets]; + } + + // Read data into 'facetlist', 'facetmarkerlist'. + if (smesh == 0) { + // Facets are in .poly file format. + for (i = 1; i <= numberoffacets; i++) { + f = &(facetlist[i - 1]); + init(f); + f->numberofholes = 0; + currentmarker = 0; + // Read number of polygons, number of holes, and a boundary marker. + stringptr = readnumberline(inputline, infile, infilename); + f->numberofpolygons = (int) strtol (stringptr, &stringptr, 0); + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + f->numberofholes = (int) strtol (stringptr, &stringptr, 0); + if (markers == 1) { + stringptr = findnextnumber(stringptr); + if (*stringptr != '\0') { + currentmarker = (int) strtol(stringptr, &stringptr, 0); + } + } + } + // Initialize facetmarker if it needs. + if (markers == 1) { + facetmarkerlist[i - 1] = currentmarker; + } + // Each facet should has at least one polygon. + if (f->numberofpolygons <= 0) { + printf("Error: Wrong number of polygon in %d facet.\n", i); + break; + } + // Initialize the 'f->polygonlist'. + f->polygonlist = new polygon[f->numberofpolygons]; + // Go through all polygons, read in their vertices. + for (j = 1; j <= f->numberofpolygons; j++) { + p = &(f->polygonlist[j - 1]); + init(p); + // Read number of vertices of this polygon. + stringptr = readnumberline(inputline, infile, infilename); + p->numberofvertices = (int) strtol(stringptr, &stringptr, 0); + if (p->numberofvertices < 1) { + printf("Error: Wrong polygon %d in facet %d\n", j, i); + break; + } + // Initialize 'p->vertexlist'. + p->vertexlist = new int[p->numberofvertices]; + // Read all vertices of this polygon. + for (k = 1; k <= p->numberofvertices; k++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + // Try to load another non-empty line and continue to read the + // rest of vertices. + stringptr = readnumberline(inputline, infile, infilename); + if (*stringptr == '\0') { + printf("Error: Missing %d endpoints of polygon %d in facet %d", + p->numberofvertices - k, j, i); + break; + } + } + p->vertexlist[k - 1] = (int) strtol (stringptr, &stringptr, 0); + } + } + if (j <= f->numberofpolygons) { + // This must be caused by an error. However, there're j - 1 + // polygons have been read. Reset the 'f->numberofpolygon'. + if (j == 1) { + // This is the first polygon. + delete [] f->polygonlist; + } + f->numberofpolygons = j - 1; + // No hole will be read even it exists. + f->numberofholes = 0; + break; + } + // If this facet has hole pints defined, read them. + if (f->numberofholes > 0) { + // Initialize 'f->holelist'. + f->holelist = new REAL[f->numberofholes * 3]; + // Read the holes' coordinates. + index = 0; + for (j = 1; j <= f->numberofholes; j++) { + stringptr = readnumberline(inputline, infile, infilename); + for (k = 1; k <= 3; k++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Hole %d in facet %d has no coordinates", j, i); + break; + } + f->holelist[index++] = (REAL) strtod (stringptr, &stringptr); + } + if (k <= 3) { + // This must be caused by an error. + break; + } + } + if (j <= f->numberofholes) { + // This must be caused by an error. + break; + } + } + } + if (i <= numberoffacets) { + // This must be caused by an error. + numberoffacets = i - 1; + fclose(infile); + return false; + } + } else { // poly == 0 + // Read the facets from a .smesh file. + for (i = 1; i <= numberoffacets; i++) { + f = &(facetlist[i - 1]); + init(f); + // Initialize 'f->facetlist'. In a .smesh file, each facetlist only + // contains exactly one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new polygon[f->numberofpolygons]; + p = &(f->polygonlist[0]); + init(p); + // Read number of vertices of this polygon. + stringptr = readnumberline(inputline, infile, insmeshfilename); + p->numberofvertices = (int) strtol (stringptr, &stringptr, 0); + if (p->numberofvertices < 1) { + printf("Error: Wrong number of vertex in facet %d\n", i); + break; + } + // Initialize 'p->vertexlist'. + p->vertexlist = new int[p->numberofvertices]; + for (k = 1; k <= p->numberofvertices; k++) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + // Try to load another non-empty line and continue to read the + // rest of vertices. + stringptr = readnumberline(inputline, infile, infilename); + if (*stringptr == '\0') { + printf("Error: Missing %d endpoints in facet %d", + p->numberofvertices - k, i); + break; + } + } + p->vertexlist[k - 1] = (int) strtol (stringptr, &stringptr, 0); + } + if (k <= p->numberofvertices) { + // This must be caused by an error. + break; + } + // Read facet's boundary marker at last. + if (markers == 1) { + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + currentmarker = 0; + } else { + currentmarker = (int) strtol(stringptr, &stringptr, 0); + } + facetmarkerlist[i - 1] = currentmarker; + } + } + if (i <= numberoffacets) { + // This must be caused by an error. + numberoffacets = i - 1; + fclose(infile); + return false; + } + } + + // Read the hole section. + stringptr = readnumberline(inputline, infile, infilename); + if (stringptr == NULL) { + // No hole list, return. + fclose(infile); + return true; + } + if (*stringptr != '\0') { + numberofholes = (int) strtol (stringptr, &stringptr, 0); + } else { + numberofholes = 0; + } + if (numberofholes > 0) { + // Initialize 'holelist'. + holelist = new REAL[numberofholes * 3]; + for (i = 0; i < 3 * numberofholes; i += 3) { + stringptr = readnumberline(inputline, infile, infilename); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Hole %d has no x coord.\n", firstnumber + (i / 3)); + break; + } else { + holelist[i] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Hole %d has no y coord.\n", firstnumber + (i / 3)); + break; + } else { + holelist[i + 1] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Hole %d has no z coord.\n", firstnumber + (i / 3)); + break; + } else { + holelist[i + 2] = (REAL) strtod(stringptr, &stringptr); + } + } + if (i < 3 * numberofholes) { + // This must be caused by an error. + fclose(infile); + return false; + } + } + + // Read the region section. The 'region' section is optional, if we + // don't reach the end-of-file, try read it in. + stringptr = readnumberline(inputline, infile, NULL); + if (stringptr != (char *) NULL && *stringptr != '\0') { + numberofregions = (int) strtol (stringptr, &stringptr, 0); + } else { + numberofregions = 0; + } + if (numberofregions > 0) { + // Initialize 'regionlist'. + regionlist = new REAL[numberofregions * 5]; + index = 0; + for (i = 0; i < numberofregions; i++) { + stringptr = readnumberline(inputline, infile, infilename); + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Region %d has no x coordinate.\n", firstnumber + i); + break; + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Region %d has no y coordinate.\n", firstnumber + i); + break; + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Region %d has no z coordinate.\n", firstnumber + i); + break; + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + printf("Error: Region %d has no region attrib.\n", firstnumber + i); + break; + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findnextnumber(stringptr); + if (*stringptr == '\0') { + regionlist[index] = regionlist[index - 1]; + } else { + regionlist[index] = (REAL) strtod(stringptr, &stringptr); + } + index++; + } + if (i < numberofregions) { + // This must be caused by an error. + fclose(infile); + return false; + } + } + + } + + // End of reading poly/smesh file. + fclose(infile); + return true; +} + +//============================================================================// +// // +// load_off() Load a polyhedron from a .off file. // +// // +// The .off format is one of file formats of the Geomview, an interactive // +// program for viewing and manipulating geometric objects. More information // +// is available form: http://www.geomview.org. // +// // +//============================================================================// + +bool tetgenio::load_off(char* filebasename) +{ + FILE *fp; + tetgenio::facet *f; + tetgenio::polygon *p; + char infilename[FILENAMESIZE]; + char buffer[INPUTLINESIZE]; + char *bufferp; + double *coord; + int nverts = 0, iverts = 0; + int nfaces = 0, ifaces = 0; + int nedges = 0; + int line_count = 0, i; + + // Default, the off file's index is from '0'. We check it by remembering the + // smallest index we found in the file. It should be either 0 or 1. + int smallestidx = 0; + + strncpy(infilename, filebasename, 1024 - 1); + infilename[FILENAMESIZE - 1] = '\0'; + if (infilename[0] == '\0') { + printf("Error: No filename.\n"); + return false; + } + if (strcmp(&infilename[strlen(infilename) - 4], ".off") != 0) { + strcat(infilename, ".off"); + } + + if (!(fp = fopen(infilename, "r"))) { + printf(" Unable to open file %s\n", infilename); + return false; + } + printf("Opening %s.\n", infilename); + + while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { + // Check section + if (nverts == 0) { + // Read header + bufferp = strstr(bufferp, "OFF"); + if (bufferp != NULL) { + // Read mesh counts + bufferp = findnextnumber(bufferp); // Skip field "OFF". + if (*bufferp == '\0') { + // Read a non-empty line. + bufferp = readline(buffer, fp, &line_count); + } + if ((sscanf(bufferp, "%d%d%d", &nverts, &nfaces, &nedges) != 3) + || (nverts == 0)) { + printf("Syntax error reading header on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Allocate memory for 'tetgenio' + if (nverts > 0) { + numberofpoints = nverts; + pointlist = new REAL[nverts * 3]; + smallestidx = nverts + 1; // A bigger enough number. + } + if (nfaces > 0) { + numberoffacets = nfaces; + facetlist = new tetgenio::facet[nfaces]; + } + } + } else if (iverts < nverts) { + // Read vertex coordinates + coord = &pointlist[iverts * 3]; + for (i = 0; i < 3; i++) { + if (*bufferp == '\0') { + printf("Syntax error reading vertex coords on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + coord[i] = (REAL) strtod(bufferp, &bufferp); + bufferp = findnextnumber(bufferp); + } + iverts++; + } else if (ifaces < nfaces) { + // Get next face + f = &facetlist[ifaces]; + init(f); + // In .off format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + init(p); + // Read the number of vertices, it should be greater than 0. + p->numberofvertices = (int) strtol(bufferp, &bufferp, 0); + if (p->numberofvertices == 0) { + printf("Syntax error reading polygon on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Allocate memory for face vertices + p->vertexlist = new int[p->numberofvertices]; + for (i = 0; i < p->numberofvertices; i++) { + bufferp = findnextnumber(bufferp); + if (*bufferp == '\0') { + printf("Syntax error reading polygon on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + p->vertexlist[i] = (int) strtol(bufferp, &bufferp, 0); + // Detect the smallest index. + if (p->vertexlist[i] < smallestidx) { + smallestidx = p->vertexlist[i]; + } + } + ifaces++; + } else { + // Should never get here + printf("Found extra text starting at line %d in file %s\n", line_count, + infilename); + break; + } + } + + fclose(fp); + + // Decide the firstnumber of the index. + if (smallestidx == 0) { + firstnumber = 0; + } else if (smallestidx == 1) { + firstnumber = 1; + } else { + printf("A wrong smallest index (%d) was detected in file %s\n", + smallestidx, infilename); + return false; + } + + if (iverts != nverts) { + printf("Expected %d vertices, but read only %d vertices in file %s\n", + nverts, iverts, infilename); + return false; + } + if (ifaces != nfaces) { + printf("Expected %d faces, but read only %d faces in file %s\n", + nfaces, ifaces, infilename); + return false; + } + + return true; +} + +//============================================================================// +// // +// load_ply() Load a polyhedron from a .ply file. // +// // +// This is a simplified version of reading .ply files, which only reads the // +// set of vertices and the set of faces. Other informations (such as color, // +// material, texture, etc) in .ply file are ignored. Complete routines for // +// reading and writing ,ply files are available from: http://www.cc.gatech. // +// edu/projects/large_models/ply.html. Except the header section, ply file // +// format has exactly the same format for listing vertices and polygons as // +// off file format. // +// // +//============================================================================// + +bool tetgenio::load_ply(char* filebasename) +{ + FILE *fp; + tetgenio::facet *f; + tetgenio::polygon *p; + char infilename[FILENAMESIZE]; + char buffer[INPUTLINESIZE]; + char *bufferp, *str; + double *coord; + int endheader = 0, format = 0; + int nverts = 0, iverts = 0; + int nfaces = 0, ifaces = 0; + int line_count = 0, i; + + // Default, the ply file's index is from '0'. We check it by remembering the + // smallest index we found in the file. It should be either 0 or 1. + int smallestidx = 0; + + strncpy(infilename, filebasename, FILENAMESIZE - 1); + infilename[FILENAMESIZE - 1] = '\0'; + if (infilename[0] == '\0') { + printf("Error: No filename.\n"); + return false; + } + if (strcmp(&infilename[strlen(infilename) - 4], ".ply") != 0) { + strcat(infilename, ".ply"); + } + + if (!(fp = fopen(infilename, "r"))) { + printf("Error: Unable to open file %s\n", infilename); + return false; + } + printf("Opening %s.\n", infilename); + + while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { + if (!endheader) { + // Find if it is the keyword "end_header". + str = strstr(bufferp, "end_header"); + // strstr() is case sensitive. + if (!str) str = strstr(bufferp, "End_header"); + if (!str) str = strstr(bufferp, "End_Header"); + if (str) { + // This is the end of the header section. + endheader = 1; + continue; + } + // Parse the number of vertices and the number of faces. + if (nverts == 0 || nfaces == 0) { + // Find if it si the keyword "element". + str = strstr(bufferp, "element"); + if (!str) str = strstr(bufferp, "Element"); + if (str) { + bufferp = findnextfield(str); + if (*bufferp == '\0') { + printf("Syntax error reading element type on line%d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + if (nverts == 0) { + // Find if it is the keyword "vertex". + str = strstr(bufferp, "vertex"); + if (!str) str = strstr(bufferp, "Vertex"); + if (str) { + bufferp = findnextnumber(str); + if (*bufferp == '\0') { + printf("Syntax error reading vertex number on line"); + printf(" %d in file %s\n", line_count, infilename); + fclose(fp); + return false; + } + nverts = (int) strtol(bufferp, &bufferp, 0); + // Allocate memory for 'tetgenio' + if (nverts > 0) { + numberofpoints = nverts; + pointlist = new REAL[nverts * 3]; + smallestidx = nverts + 1; // A big enough index. + } + } + } + if (nfaces == 0) { + // Find if it is the keyword "face". + str = strstr(bufferp, "face"); + if (!str) str = strstr(bufferp, "Face"); + if (str) { + bufferp = findnextnumber(str); + if (*bufferp == '\0') { + printf("Syntax error reading face number on line"); + printf(" %d in file %s\n", line_count, infilename); + fclose(fp); + return false; + } + nfaces = (int) strtol(bufferp, &bufferp, 0); + // Allocate memory for 'tetgenio' + if (nfaces > 0) { + numberoffacets = nfaces; + facetlist = new tetgenio::facet[nfaces]; + } + } + } + } // It is not the string "element". + } + if (format == 0) { + // Find the keyword "format". + str = strstr(bufferp, "format"); + if (!str) str = strstr(bufferp, "Format"); + if (str) { + format = 1; + bufferp = findnextfield(str); + // Find if it is the string "ascii". + str = strstr(bufferp, "ascii"); + if (!str) str = strstr(bufferp, "ASCII"); + if (!str) { + printf("This routine only reads ascii format of ply files.\n"); + printf("Hint: You can convert the binary to ascii format by\n"); + printf(" using the provided ply tools:\n"); + printf(" ply2ascii < %s > ascii_%s\n", infilename, infilename); + fclose(fp); + return false; + } + } + } + } else if (iverts < nverts) { + // Read vertex coordinates + coord = &pointlist[iverts * 3]; + for (i = 0; i < 3; i++) { + if (*bufferp == '\0') { + printf("Syntax error reading vertex coords on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + coord[i] = (REAL) strtod(bufferp, &bufferp); + bufferp = findnextnumber(bufferp); + } + iverts++; + } else if (ifaces < nfaces) { + // Get next face + f = &facetlist[ifaces]; + init(f); + // In .off format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + init(p); + // Read the number of vertices, it should be greater than 0. + p->numberofvertices = (int) strtol(bufferp, &bufferp, 0); + if (p->numberofvertices == 0) { + printf("Syntax error reading polygon on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Allocate memory for face vertices + p->vertexlist = new int[p->numberofvertices]; + for (i = 0; i < p->numberofvertices; i++) { + bufferp = findnextnumber(bufferp); + if (*bufferp == '\0') { + printf("Syntax error reading polygon on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + p->vertexlist[i] = (int) strtol(bufferp, &bufferp, 0); + if (p->vertexlist[i] < smallestidx) { + smallestidx = p->vertexlist[i]; + } + } + ifaces++; + } else { + // Should never get here + printf("Found extra text starting at line %d in file %s\n", line_count, + infilename); + break; + } + } + + fclose(fp); + + // Decide the firstnumber of the index. + if (smallestidx == 0) { + firstnumber = 0; + } else if (smallestidx == 1) { + firstnumber = 1; + } else { + printf("A wrong smallest index (%d) was detected in file %s\n", + smallestidx, infilename); + return false; + } + + if (iverts != nverts) { + printf("Expected %d vertices, but read only %d vertices in file %s\n", + nverts, iverts, infilename); + return false; + } + if (ifaces != nfaces) { + printf("Expected %d faces, but read only %d faces in file %s\n", + nfaces, ifaces, infilename); + return false; + } + + return true; +} + +//============================================================================// +// // +// load_stl() Load a surface mesh from a .stl file. // +// // +// The .stl or stereolithography format is an ASCII or binary file used in // +// manufacturing. It is a list of the triangular surfaces that describe a // +// computer generated solid model. This is the standard input for most rapid // +// prototyping machines. // +// // +// Comment: A .stl file many contain many duplicated points. They will be // +// unified during the Delaunay tetrahedralization process. // +// // +//============================================================================// + +static void SwapBytes(char *array, int size, int n) +{ + char *x = new char[size]; + for(int i = 0; i < n; i++) { + char *a = &array[i * size]; + memcpy(x, a, size); + for(int c = 0; c < size; c++) + a[size - 1 - c] = x[c]; + } + delete [] x; +} + +bool tetgenio::load_stl(char* filebasename) +{ + FILE *fp; + tetgenmesh::arraypool *plist; + tetgenio::facet *f; + tetgenio::polygon *p; + char infilename[FILENAMESIZE]; + char buffer[INPUTLINESIZE]; + char *bufferp, *str; + double *coord; + int solid = 0; + int nverts = 0, iverts = 0; + int nfaces = 0; + int line_count = 0, i; + + strncpy(infilename, filebasename, FILENAMESIZE - 1); + infilename[FILENAMESIZE - 1] = '\0'; + if (infilename[0] == '\0') { + printf("Error: No filename.\n"); + return false; + } + if (strcmp(&infilename[strlen(infilename) - 4], ".stl") != 0) { + strcat(infilename, ".stl"); + } + + if (!(fp = fopen(infilename, "rb"))) { + printf("Error: Unable to open file %s\n", infilename); + return false; + } + printf("Opening %s.\n", infilename); + + // "solid", or binary data header + if(!fgets(buffer, sizeof(buffer), fp)){ fclose(fp); return 0; } + bool binary = strncmp(buffer, "solid", 5) && strncmp(buffer, "SOLID", 5); + + // STL file has no number of points available. Use a list to read points. + plist = new tetgenmesh::arraypool(sizeof(double) * 3, 10); + + if(!binary){ + solid = 1; + while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { + // The ASCII .stl file must start with the lower case keyword solid and + // end with endsolid. + if (solid == 0) { + // Read header + bufferp = strstr(bufferp, "solid"); + if (bufferp != NULL) { + solid = 1; + } + } else { + // We're inside the block of the solid. + str = bufferp; + // Is this the end of the solid. + bufferp = strstr(bufferp, "endsolid"); + if (bufferp != NULL) { + solid = 0; + } else { + // Read the XYZ coordinates if it is a vertex. + bufferp = str; + bufferp = strstr(bufferp, "vertex"); + if (bufferp != NULL) { + plist->newindex((void **) &coord); + for (i = 0; i < 3; i++) { + bufferp = findnextnumber(bufferp); + if (*bufferp == '\0') { + printf("Syntax error reading vertex coords on line %d\n", + line_count); + delete plist; + fclose(fp); + return false; + } + coord[i] = (REAL) strtod(bufferp, &bufferp); + } + } + } + } + } + } // if(!binary) + + else { + rewind(fp); + while(!feof(fp)) { + char header[80]; + if(!fread(header, sizeof(char), 80, fp)) break; + unsigned int nfacets = 0; + size_t ret = fread(&nfacets, sizeof(unsigned int), 1, fp); + bool swap = false; + if(nfacets > 100000000){ + //Msg::Info("Swapping bytes from binary file"); + swap = true; + SwapBytes((char*)&nfacets, sizeof(unsigned int), 1); + } + if(ret && nfacets){ + //points.resize(points.size() + 1); + char *data = new char[nfacets * 50 * sizeof(char)]; + ret = fread(data, sizeof(char), nfacets * 50, fp); + if(ret == nfacets * 50){ + for(unsigned int i = 0; i < nfacets; i++) { + float *xyz = (float *)&data[i * 50 * sizeof(char)]; + if(swap) SwapBytes((char*)xyz, sizeof(float), 12); + for(int j = 0; j < 3; j++){ + //SPoint3 p(xyz[3 + 3 * j], xyz[3 + 3 * j + 1], xyz[3 + 3 * j + 2]); + //points.back().push_back(p); + //bbox += p; + plist->newindex((void **) &coord); + coord[0] = xyz[3 + 3 * j]; + coord[1] = xyz[3 + 3 * j + 1]; + coord[2] = xyz[3 + 3 * j + 2]; + } + } + } + delete [] data; + } + } // while (!feof(fp)) + } // binary + + fclose(fp); + + nverts = (int) plist->objects; + // nverts should be an integer times 3 (every 3 vertices denote a face). + if (nverts == 0 || (nverts % 3 != 0)) { + printf("Error: Wrong number of vertices in file %s.\n", infilename); + delete plist; + return false; + } + numberofpoints = nverts; + pointlist = new REAL[nverts * 3]; + for (i = 0; i < nverts; i++) { + coord = (double *) fastlookup(plist, i); + iverts = i * 3; + pointlist[iverts] = (REAL) coord[0]; + pointlist[iverts + 1] = (REAL) coord[1]; + pointlist[iverts + 2] = (REAL) coord[2]; + } + + nfaces = (int) (nverts / 3); + numberoffacets = nfaces; + facetlist = new tetgenio::facet[nfaces]; + + // Default use '1' as the array starting index. + firstnumber = 1; + iverts = firstnumber; + for (i = 0; i < nfaces; i++) { + f = &facetlist[i]; + init(f); + // In .stl format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + init(p); + // Each polygon has three vertices. + p->numberofvertices = 3; + p->vertexlist = new int[p->numberofvertices]; + p->vertexlist[0] = iverts; + p->vertexlist[1] = iverts + 1; + p->vertexlist[2] = iverts + 2; + iverts += 3; + } + + delete plist; + return true; +} + +//============================================================================// +// // +// load_medit() Load a surface mesh from a .mesh file. // +// // +// The .mesh format is the file format of Medit, a user-friendly interactive // +// mesh viewer program. // +// // +//============================================================================// + +bool tetgenio::load_medit(char* filebasename, int istetmesh) +{ + FILE *fp; + tetgenio::facet *tmpflist, *f; + tetgenio::polygon *p; + char infilename[FILENAMESIZE]; + char buffer[INPUTLINESIZE]; + char *bufferp, *str; + double *coord; + int *tmpfmlist; + int dimension = 0; + int nverts = 0; + int nfaces = 0; + int ntets = 0; + int line_count = 0; + int corners = 0; // 3 (triangle) or 4 (quad). + int *plist; + int i, j; + + int smallestidx = 0; + + strncpy(infilename, filebasename, FILENAMESIZE - 1); + infilename[FILENAMESIZE - 1] = '\0'; + if (infilename[0] == '\0') { + //printf("Error: No filename.\n"); + return false; + } + if (strcmp(&infilename[strlen(infilename) - 5], ".mesh") != 0) { + strcat(infilename, ".mesh"); + } + + if (!(fp = fopen(infilename, "r"))) { + //printf("Error: Unable to open file %s\n", infilename); + return false; + } + printf("Opening %s.\n", infilename); + + while ((bufferp = readline(buffer, fp, &line_count)) != NULL) { + if (*bufferp == '#') continue; // A comment line is skipped. + if (dimension == 0) { + // Find if it is the keyword "Dimension". + str = strstr(bufferp, "Dimension"); + if (!str) str = strstr(bufferp, "dimension"); + if (!str) str = strstr(bufferp, "DIMENSION"); + if (str) { + // Read the dimensions + bufferp = findnextnumber(str); // Skip field "Dimension". + if (*bufferp == '\0') { + // Read a non-empty line. + bufferp = readline(buffer, fp, &line_count); + } + dimension = (int) strtol(bufferp, &bufferp, 0); + if (dimension != 2 && dimension != 3) { + printf("Unknown dimension in file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + mesh_dim = dimension; + } + } + if (nverts == 0) { + // Find if it is the keyword "Vertices". + str = strstr(bufferp, "Vertices"); + if (!str) str = strstr(bufferp, "vertices"); + if (!str) str = strstr(bufferp, "VERTICES"); + if (str) { + // Read the number of vertices. + bufferp = findnextnumber(str); // Skip field "Vertices". + if (*bufferp == '\0') { + // Read a non-empty line. + bufferp = readline(buffer, fp, &line_count); + } + nverts = (int) strtol(bufferp, &bufferp, 0); + // Initialize the smallest index. + smallestidx = nverts + 1; + // Allocate memory for 'tetgenio' + if (nverts > 0) { + numberofpoints = nverts; + pointlist = new REAL[nverts * 3]; + } + // Read the follwoing node list. + for (i = 0; i < nverts; i++) { + bufferp = readline(buffer, fp, &line_count); + if (bufferp == NULL) { + printf("Unexpected end of file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Read vertex coordinates + coord = &pointlist[i * 3]; + for (j = 0; j < 3; j++) { + if (*bufferp == '\0') { + printf("Syntax error reading vertex coords on line"); + printf(" %d in file %s\n", line_count, infilename); + fclose(fp); + return false; + } + if ((j < 2) || (dimension == 3)) { + coord[j] = (REAL) strtod(bufferp, &bufferp); + } else { + coord[j] = 0.0; + } + bufferp = findnextnumber(bufferp); + } + } + continue; + } + } + if ((ntets == 0) && istetmesh) { // Only read tetrahedra if 'istetmesh = 1'. + // Find if it is the keyword "Tetrahedra" + corners = 0; + str = strstr(bufferp, "Tetrahedra"); + if (!str) str = strstr(bufferp, "tetrahedra"); + if (!str) str = strstr(bufferp, "TETRAHEDRA"); + if (str) { + corners = 4; + } + if (corners == 4) { + // Read the number of tetrahedra + bufferp = findnextnumber(str); // Skip field "Tetrahedra". + if (*bufferp == '\0') { + // Read a non-empty line. + bufferp = readline(buffer, fp, &line_count); + } + ntets = strtol(bufferp, &bufferp, 0); + if (ntets > 0) { + // It is a tetrahedral mesh. + numberoftetrahedra = ntets; + numberofcorners = 4; + numberoftetrahedronattributes = 1; + tetrahedronlist = new int[ntets * 4]; + tetrahedronattributelist = new REAL[ntets]; + } + } // if (corners == 4) + // Read the list of tetrahedra. + for (i = 0; i < numberoftetrahedra; i++) { + plist = &(tetrahedronlist[i * 4]); + bufferp = readline(buffer, fp, &line_count); + if (bufferp == NULL) { + printf("Unexpected end of file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Read the vertices of the tet. + for (j = 0; j < corners; j++) { + if (*bufferp == '\0') { + printf("Syntax error reading face on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + plist[j] = (int) strtol(bufferp, &bufferp, 0); + // Remember the smallest index. + if (plist[j] < smallestidx) smallestidx = plist[j]; + bufferp = findnextnumber(bufferp); + } + // Read the attribute of the tet if it exists. + tetrahedronattributelist[i] = 0; + if (*bufferp != '\0') { + tetrahedronattributelist[i] = (REAL) strtol(bufferp, &bufferp, 0); + } + } // i + } // Tetrahedra + if (nfaces == 0) { + // Find if it is the keyword "Triangles" or "Quadrilaterals". + corners = 0; + str = strstr(bufferp, "Triangles"); + if (!str) str = strstr(bufferp, "triangles"); + if (!str) str = strstr(bufferp, "TRIANGLES"); + if (str) { + corners = 3; + } else { + str = strstr(bufferp, "Quadrilaterals"); + if (!str) str = strstr(bufferp, "quadrilaterals"); + if (!str) str = strstr(bufferp, "QUADRILATERALS"); + if (str) { + corners = 4; + } + } + if (corners == 3 || corners == 4) { + // Read the number of triangles (or quadrilaterals). + bufferp = findnextnumber(str); // Skip field "Triangles". + if (*bufferp == '\0') { + // Read a non-empty line. + bufferp = readline(buffer, fp, &line_count); + } + nfaces = strtol(bufferp, &bufferp, 0); + // Allocate memory for 'tetgenio' + if (nfaces > 0) { + if (!istetmesh) { + // It is a PLC surface mesh. + if (numberoffacets > 0) { + // facetlist has already been allocated. Enlarge arrays. + // This happens when the surface mesh contains mixed cells. + tmpflist = new tetgenio::facet[numberoffacets + nfaces]; + tmpfmlist = new int[numberoffacets + nfaces]; + // Copy the data of old arrays into new arrays. + for (i = 0; i < numberoffacets; i++) { + f = &(tmpflist[i]); + tetgenio::init(f); + *f = facetlist[i]; + tmpfmlist[i] = facetmarkerlist[i]; + } + // Release old arrays. + delete [] facetlist; + delete [] facetmarkerlist; + // Remember the new arrays. + facetlist = tmpflist; + facetmarkerlist = tmpfmlist; + } else { + // This is the first time to allocate facetlist. + facetlist = new tetgenio::facet[nfaces]; + facetmarkerlist = new int[nfaces]; + } + } else { + if (corners == 3) { + // It is a surface mesh of a tetrahedral mesh. + numberoftrifaces = nfaces; + trifacelist = new int[nfaces * 3]; + trifacemarkerlist = new int[nfaces]; + } + } + } // if (nfaces > 0) + // Read the following list of faces. + if (!istetmesh) { + for (i = numberoffacets; i < numberoffacets + nfaces; i++) { + bufferp = readline(buffer, fp, &line_count); + if (bufferp == NULL) { + printf("Unexpected end of file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + f = &facetlist[i]; + tetgenio::init(f); + // In .mesh format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + tetgenio::init(p); + p->numberofvertices = corners; + // Allocate memory for face vertices + p->vertexlist = new int[p->numberofvertices]; + // Read the vertices of the face. + for (j = 0; j < corners; j++) { + if (*bufferp == '\0') { + printf("Syntax error reading face on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + p->vertexlist[j] = (int) strtol(bufferp, &bufferp, 0); + // Remember the smallest index. + if (p->vertexlist[j] < smallestidx) { + smallestidx = p->vertexlist[j]; + } + bufferp = findnextnumber(bufferp); + } + // Read the marker of the face if it exists. + facetmarkerlist[i] = 0; + if (*bufferp != '\0') { + facetmarkerlist[i] = (int) strtol(bufferp, &bufferp, 0); + } + } + // Have read in a list of triangles/quads. + numberoffacets += nfaces; + nfaces = 0; + } else { + // It is a surface mesh of a tetrahedral mesh. + if (corners == 3) { + for (i = 0; i < numberoftrifaces; i++) { + plist = &(trifacelist[i * 3]); + bufferp = readline(buffer, fp, &line_count); + if (bufferp == NULL) { + printf("Unexpected end of file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Read the vertices of the face. + for (j = 0; j < corners; j++) { + if (*bufferp == '\0') { + printf("Syntax error reading face on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + plist[j] = (int) strtol(bufferp, &bufferp, 0); + // Remember the smallest index. + if (plist[j] < smallestidx) { + smallestidx = plist[j]; + } + bufferp = findnextnumber(bufferp); + } + // Read the marker of the face if it exists. + trifacemarkerlist[i] = 0; + if (*bufferp != '\0') { + trifacemarkerlist[i] = (int) strtol(bufferp, &bufferp, 0); + } + } // i + } // if (corners == 3) + } // if (b->refine) + } // if (corners == 3 || corners == 4) + } + } + + fclose(fp); + + // Decide the firstnumber of the index. + if (smallestidx == 0) { + firstnumber = 0; + } else if (smallestidx == 1) { + firstnumber = 1; + } else { + printf("A wrong smallest index (%d) was detected in file %s\n", + smallestidx, infilename); + return false; + } + + return true; +} + +//============================================================================// +// // +// load_vtk() Load VTK surface mesh from file (.vtk ascii or binary). // +// // +// This function is contributed by: Bryn Lloyd, Computer Vision Laboratory, // +// ETH, Zuerich. May 7, 2007. // +// // +//============================================================================// + +// Two inline functions used in read/write VTK files. + +void swapBytes(unsigned char* var, int size) +{ + int i = 0; + int j = size - 1; + char c; + + while (i < j) { + c = var[i]; var[i] = var[j]; var[j] = c; + i++, j--; + } +} + +bool testIsBigEndian() +{ + short word = 0x4321; + if((*(char *)& word) != 0x21) + return true; + else + return false; +} + +bool tetgenio::load_vtk(char* filebasename) +{ + FILE *fp; + tetgenio::facet *f; + tetgenio::polygon *p; + char infilename[FILENAMESIZE]; + char line[INPUTLINESIZE]; + char mode[128], id[256], fmt[64]; + char *bufferp; + double *coord; + float _x, _y, _z; + int nverts = 0; + int nfaces = 0; + int line_count = 0; + int dummy; + int id1, id2, id3; + int nn = -1; + int nn_old = -1; + int i, j; + bool ImALittleEndian = !testIsBigEndian(); + + int smallestidx = 0; + + strncpy(infilename, filebasename, FILENAMESIZE - 1); + infilename[FILENAMESIZE - 1] = '\0'; + if (infilename[0] == '\0') { + printf("Error: No filename.\n"); + return false; + } + if (strcmp(&infilename[strlen(infilename) - 4], ".vtk") != 0) { + strcat(infilename, ".vtk"); + } + if (!(fp = fopen(infilename, "r"))) { + printf("Error: Unable to open file %s\n", infilename); + return false; + } + printf("Opening %s.\n", infilename); + + // Default uses the index starts from '0'. + firstnumber = 0; + strcpy(mode, "BINARY"); + + while((bufferp = readline(line, fp, &line_count)) != NULL) { + if(strlen(line) == 0) continue; + //swallow lines beginning with a comment sign or white space + if(line[0] == '#' || line[0]=='\n' || line[0] == 10 || line[0] == 13 || + line[0] == 32) continue; + + sscanf(line, "%s", id); + if(!strcmp(id, "ASCII")) { + strcpy(mode, "ASCII"); + } + + if(!strcmp(id, "POINTS")) { + sscanf(line, "%s %d %s", id, &nverts, fmt); + if (nverts > 0) { + numberofpoints = nverts; + pointlist = new REAL[nverts * 3]; + smallestidx = nverts + 1; + } + + if(!strcmp(mode, "BINARY")) { + for(i = 0; i < nverts; i++) { + coord = &pointlist[i * 3]; + if(!strcmp(fmt, "double")) { + fread((char*)(&(coord[0])), sizeof(double), 1, fp); + fread((char*)(&(coord[1])), sizeof(double), 1, fp); + fread((char*)(&(coord[2])), sizeof(double), 1, fp); + if(ImALittleEndian){ + swapBytes((unsigned char *) &(coord[0]), sizeof(coord[0])); + swapBytes((unsigned char *) &(coord[1]), sizeof(coord[1])); + swapBytes((unsigned char *) &(coord[2]), sizeof(coord[2])); + } + } else if(!strcmp(fmt, "float")) { + fread((char*)(&_x), sizeof(float), 1, fp); + fread((char*)(&_y), sizeof(float), 1, fp); + fread((char*)(&_z), sizeof(float), 1, fp); + if(ImALittleEndian){ + swapBytes((unsigned char *) &_x, sizeof(_x)); + swapBytes((unsigned char *) &_y, sizeof(_y)); + swapBytes((unsigned char *) &_z, sizeof(_z)); + } + coord[0] = double(_x); + coord[1] = double(_y); + coord[2] = double(_z); + } else { + printf("Error: Only float or double formats are supported!\n"); + return false; + } + } + } else if(!strcmp(mode, "ASCII")) { + for(i = 0; i < nverts; i++){ + bufferp = readline(line, fp, &line_count); + if (bufferp == NULL) { + printf("Unexpected end of file on line %d in file %s\n", + line_count, infilename); + fclose(fp); + return false; + } + // Read vertex coordinates + coord = &pointlist[i * 3]; + for (j = 0; j < 3; j++) { + if (*bufferp == '\0') { + printf("Syntax error reading vertex coords on line"); + printf(" %d in file %s\n", line_count, infilename); + fclose(fp); + return false; + } + coord[j] = (REAL) strtod(bufferp, &bufferp); + bufferp = findnextnumber(bufferp); + } + } + } + continue; + } + + if(!strcmp(id, "POLYGONS")) { + sscanf(line, "%s %d %d", id, &nfaces, &dummy); + if (nfaces > 0) { + numberoffacets = nfaces; + facetlist = new tetgenio::facet[nfaces]; + } + + if(!strcmp(mode, "BINARY")) { + for(i = 0; i < nfaces; i++){ + fread((char*)(&nn), sizeof(int), 1, fp); + if(ImALittleEndian){ + swapBytes((unsigned char *) &nn, sizeof(nn)); + } + if (i == 0) + nn_old = nn; + if (nn != nn_old) { + printf("Error: No mixed cells are allowed.\n"); + return false; + } + + if(nn == 3){ + fread((char*)(&id1), sizeof(int), 1, fp); + fread((char*)(&id2), sizeof(int), 1, fp); + fread((char*)(&id3), sizeof(int), 1, fp); + if(ImALittleEndian){ + swapBytes((unsigned char *) &id1, sizeof(id1)); + swapBytes((unsigned char *) &id2, sizeof(id2)); + swapBytes((unsigned char *) &id3, sizeof(id3)); + } + f = &facetlist[i]; + init(f); + // In .off format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + init(p); + // Set number of vertices + p->numberofvertices = 3; + // Allocate memory for face vertices + p->vertexlist = new int[p->numberofvertices]; + p->vertexlist[0] = id1; + p->vertexlist[1] = id2; + p->vertexlist[2] = id3; + // Detect the smallest index. + for (j = 0; j < 3; j++) { + if (p->vertexlist[j] < smallestidx) { + smallestidx = p->vertexlist[j]; + } + } + } else { + printf("Error: Only triangles are supported\n"); + return false; + } + } + } else if(!strcmp(mode, "ASCII")) { + for(i = 0; i < nfaces; i++) { + bufferp = readline(line, fp, &line_count); + nn = (int) strtol(bufferp, &bufferp, 0); + if (i == 0) + nn_old = nn; + if (nn != nn_old) { + printf("Error: No mixed cells are allowed.\n"); + return false; + } + + if (nn == 3) { + bufferp = findnextnumber(bufferp); // Skip the first field. + id1 = (int) strtol(bufferp, &bufferp, 0); + bufferp = findnextnumber(bufferp); + id2 = (int) strtol(bufferp, &bufferp, 0); + bufferp = findnextnumber(bufferp); + id3 = (int) strtol(bufferp, &bufferp, 0); + f = &facetlist[i]; + init(f); + // In .off format, each facet has one polygon, no hole. + f->numberofpolygons = 1; + f->polygonlist = new tetgenio::polygon[1]; + p = &f->polygonlist[0]; + init(p); + // Set number of vertices + p->numberofvertices = 3; + // Allocate memory for face vertices + p->vertexlist = new int[p->numberofvertices]; + p->vertexlist[0] = id1; + p->vertexlist[1] = id2; + p->vertexlist[2] = id3; + // Detect the smallest index. + for (j = 0; j < 3; j++) { + if (p->vertexlist[j] < smallestidx) { + smallestidx = p->vertexlist[j]; + } + } + } else { + printf("Error: Only triangles are supported.\n"); + return false; + } + } + } + + fclose(fp); + + // Decide the firstnumber of the index. + if (smallestidx == 0) { + firstnumber = 0; + } else if (smallestidx == 1) { + firstnumber = 1; + } else { + printf("A wrong smallest index (%d) was detected in file %s\n", + smallestidx, infilename); + return false; + } + + return true; + } + + if(!strcmp(id,"LINES") || !strcmp(id,"CELLS")){ + printf("Warning: load_vtk(): cannot read formats LINES, CELLS.\n"); + } + } // while () + + return true; +} + +//============================================================================// +// // +// load_plc() Load a piecewise linear complex from file(s). // +// // +//============================================================================// + +bool tetgenio::load_plc(char* filebasename, int object) +{ + bool success; + + if (object == (int) tetgenbehavior::NODES) { + success = load_node(filebasename); + } else if (object == (int) tetgenbehavior::POLY) { + success = load_poly(filebasename); + } else if (object == (int) tetgenbehavior::OFF) { + success = load_off(filebasename); + } else if (object == (int) tetgenbehavior::PLY) { + success = load_ply(filebasename); + } else if (object == (int) tetgenbehavior::STL) { + success = load_stl(filebasename); + } else if (object == (int) tetgenbehavior::MEDIT) { + success = load_medit(filebasename, 0); + } else if (object == (int) tetgenbehavior::VTK) { + success = load_vtk(filebasename); + } else { + success = load_poly(filebasename); + } + + if (success) { + // Try to load the following files (.edge, .var, .mtr). + load_edge(filebasename); + load_var(filebasename); + load_mtr(filebasename); + } + + return success; +} + +//============================================================================// +// // +// load_mesh() Load a tetrahedral mesh from file(s). // +// // +//============================================================================// + +bool tetgenio::load_tetmesh(char* filebasename, int object) +{ + bool success = false; + + if (object == (int) tetgenbehavior::MEDIT) { + success = load_medit(filebasename, 1); + } else if (object == (int) tetgenbehavior::NEU_MESH) { + //success = load_neumesh(filebasename, 1); + } else { + success = load_node(filebasename); + if (success) { + success = load_tet(filebasename); + } + if (success) { + // Try to load the following files (.face, .edge, .vol). + load_face(filebasename); + load_edge(filebasename); + load_vol(filebasename); + } + } + + if (success) { + // Try to load the following files (.var, .mtr). + load_var(filebasename); + load_mtr(filebasename); + load_elem(filebasename); + } + + return success; +} + +//============================================================================// +// // +// save_nodes() Save points to a .node file. // +// // +//============================================================================// + +void tetgenio::save_nodes(const char *filebasename) +{ + FILE *fout; + char outnodefilename[FILENAMESIZE]; + char outmtrfilename[FILENAMESIZE]; + int i, j; + + sprintf(outnodefilename, "%s.node", filebasename); + printf("Saving nodes to %s\n", outnodefilename); + fout = fopen(outnodefilename, "w"); + fprintf(fout, "%d %d %d %d\n", numberofpoints, mesh_dim, + numberofpointattributes, pointmarkerlist != NULL ? 1 : 0); + for (i = 0; i < numberofpoints; i++) { + if (mesh_dim == 2) { + fprintf(fout, "%d %.16g %.16g", i + firstnumber, pointlist[i * 3], + pointlist[i * 3 + 1]); + } else { + fprintf(fout, "%d %.16g %.16g %.16g", i + firstnumber, + pointlist[i * 3], pointlist[i * 3 + 1], pointlist[i * 3 + 2]); + } + for (j = 0; j < numberofpointattributes; j++) { + fprintf(fout, " %.16g", + pointattributelist[i * numberofpointattributes + j]); + } + if (pointmarkerlist != NULL) { + fprintf(fout, " %d", pointmarkerlist[i]); + } + fprintf(fout, "\n"); + } + fclose(fout); + + // If the point metrics exist, output them to a .mtr file. + if ((numberofpointmtrs > 0) && (pointmtrlist != (REAL *) NULL)) { + sprintf(outmtrfilename, "%s.mtr", filebasename); + printf("Saving metrics to %s\n", outmtrfilename); + fout = fopen(outmtrfilename, "w"); + fprintf(fout, "%d %d\n", numberofpoints, numberofpointmtrs); + for (i = 0; i < numberofpoints; i++) { + for (j = 0; j < numberofpointmtrs; j++) { + fprintf(fout, "%.16g ", pointmtrlist[i * numberofpointmtrs + j]); + } + fprintf(fout, "\n"); + } + fclose(fout); + } +} + +//============================================================================// +// // +// save_elements() Save elements to a .ele file. // +// // +//============================================================================// + +void tetgenio::save_elements(const char* filebasename) +{ + FILE *fout; + char outelefilename[FILENAMESIZE]; + int i, j; + + sprintf(outelefilename, "%s.ele", filebasename); + printf("Saving elements to %s\n", outelefilename); + fout = fopen(outelefilename, "w"); + if (mesh_dim == 3) { + fprintf(fout, "%d %d %d\n", numberoftetrahedra, numberofcorners, + numberoftetrahedronattributes); + for (i = 0; i < numberoftetrahedra; i++) { + fprintf(fout, "%d", i + firstnumber); + for (j = 0; j < numberofcorners; j++) { + fprintf(fout, " %5d", tetrahedronlist[i * numberofcorners + j]); + } + for (j = 0; j < numberoftetrahedronattributes; j++) { + fprintf(fout, " %g", + tetrahedronattributelist[i * numberoftetrahedronattributes + j]); + } + fprintf(fout, "\n"); + } + } else { + // Save a two-dimensional mesh. + fprintf(fout, "%d %d %d\n",numberoftrifaces,3,trifacemarkerlist ? 1 : 0); + for (i = 0; i < numberoftrifaces; i++) { + fprintf(fout, "%d", i + firstnumber); + for (j = 0; j < 3; j++) { + fprintf(fout, " %5d", trifacelist[i * 3 + j]); + } + if (trifacemarkerlist != NULL) { + fprintf(fout, " %d", trifacemarkerlist[i]); + } + fprintf(fout, "\n"); + } + } + + fclose(fout); +} + +//============================================================================// +// // +// save_faces() Save faces to a .face file. // +// // +//============================================================================// + +void tetgenio::save_faces(const char* filebasename) +{ + FILE *fout; + char outfacefilename[FILENAMESIZE]; + int i; + + sprintf(outfacefilename, "%s.face", filebasename); + printf("Saving faces to %s\n", outfacefilename); + fout = fopen(outfacefilename, "w"); + fprintf(fout, "%d %d\n", numberoftrifaces, + trifacemarkerlist != NULL ? 1 : 0); + for (i = 0; i < numberoftrifaces; i++) { + fprintf(fout, "%d %5d %5d %5d", i + firstnumber, trifacelist[i * 3], + trifacelist[i * 3 + 1], trifacelist[i * 3 + 2]); + if (trifacemarkerlist != NULL) { + fprintf(fout, " %d", trifacemarkerlist[i]); + } + fprintf(fout, "\n"); + } + + fclose(fout); +} + +//============================================================================// +// // +// save_edges() Save egdes to a .edge file. // +// // +//============================================================================// + +void tetgenio::save_edges(char* filebasename) +{ + FILE *fout; + char outedgefilename[FILENAMESIZE]; + int i; + + sprintf(outedgefilename, "%s.edge", filebasename); + printf("Saving edges to %s\n", outedgefilename); + fout = fopen(outedgefilename, "w"); + fprintf(fout, "%d %d\n", numberofedges, edgemarkerlist != NULL ? 1 : 0); + for (i = 0; i < numberofedges; i++) { + fprintf(fout, "%d %4d %4d", i + firstnumber, edgelist[i * 2], + edgelist[i * 2 + 1]); + if (edgemarkerlist != NULL) { + fprintf(fout, " %d", edgemarkerlist[i]); + } + fprintf(fout, "\n"); + } + + fclose(fout); +} + +//============================================================================// +// // +// save_neighbors() Save egdes to a .neigh file. // +// // +//============================================================================// + +void tetgenio::save_neighbors(char* filebasename) +{ + FILE *fout; + char outneighborfilename[FILENAMESIZE]; + int i; + + sprintf(outneighborfilename, "%s.neigh", filebasename); + printf("Saving neighbors to %s\n", outneighborfilename); + fout = fopen(outneighborfilename, "w"); + fprintf(fout, "%d %d\n", numberoftetrahedra, mesh_dim + 1); + for (i = 0; i < numberoftetrahedra; i++) { + if (mesh_dim == 2) { + fprintf(fout, "%d %5d %5d %5d", i + firstnumber, neighborlist[i * 3], + neighborlist[i * 3 + 1], neighborlist[i * 3 + 2]); + } else { + fprintf(fout, "%d %5d %5d %5d %5d", i + firstnumber, + neighborlist[i * 4], neighborlist[i * 4 + 1], + neighborlist[i * 4 + 2], neighborlist[i * 4 + 3]); + } + fprintf(fout, "\n"); + } + + fclose(fout); +} + +//============================================================================// +// // +// save_poly() Save segments or facets to a .poly file. // +// // +// It only save the facets, holes and regions. No .node file is saved. // +// // +//============================================================================// + +void tetgenio::save_poly(const char *filebasename) +{ + FILE *fout; + facet *f; + polygon *p; + char outpolyfilename[FILENAMESIZE]; + int i, j, k; + + sprintf(outpolyfilename, "%s.poly", filebasename); + printf("Saving poly to %s\n", outpolyfilename); + fout = fopen(outpolyfilename, "w"); + + // The zero indicates that the vertices are in a separate .node file. + // Followed by number of dimensions, number of vertex attributes, + // and number of boundary markers (zero or one). + fprintf(fout, "%d %d %d %d\n", 0, mesh_dim, numberofpointattributes, + pointmarkerlist != NULL ? 1 : 0); + + // Save segments or facets. + if (mesh_dim == 2) { + // Number of segments, number of boundary markers (zero or one). + fprintf(fout, "%d %d\n", numberofedges, edgemarkerlist != NULL ? 1 : 0); + for (i = 0; i < numberofedges; i++) { + fprintf(fout, "%d %4d %4d", i + firstnumber, edgelist[i * 2], + edgelist[i * 2 + 1]); + if (edgemarkerlist != NULL) { + fprintf(fout, " %d", edgemarkerlist[i]); + } + fprintf(fout, "\n"); + } + } else { + // Number of facets, number of boundary markers (zero or one). + fprintf(fout, "%d %d\n", numberoffacets, facetmarkerlist != NULL ? 1 : 0); + for (i = 0; i < numberoffacets; i++) { + f = &(facetlist[i]); + fprintf(fout, "%d %d %d # %d\n", f->numberofpolygons,f->numberofholes, + facetmarkerlist != NULL ? facetmarkerlist[i] : 0, i + firstnumber); + // Output polygons of this facet. + for (j = 0; j < f->numberofpolygons; j++) { + p = &(f->polygonlist[j]); + fprintf(fout, "%d ", p->numberofvertices); + for (k = 0; k < p->numberofvertices; k++) { + if (((k + 1) % 10) == 0) { + fprintf(fout, "\n "); + } + fprintf(fout, " %d", p->vertexlist[k]); + } + fprintf(fout, "\n"); + } + // Output holes of this facet. + for (j = 0; j < f->numberofholes; j++) { + fprintf(fout, "%d %.12g %.12g %.12g\n", j + firstnumber, + f->holelist[j * 3], f->holelist[j * 3 + 1], f->holelist[j * 3 + 2]); + } + } + } + + // Save holes. + fprintf(fout, "%d\n", numberofholes); + for (i = 0; i < numberofholes; i++) { + // Output x, y coordinates. + fprintf(fout, "%d %.12g %.12g", i + firstnumber, holelist[i * mesh_dim], + holelist[i * mesh_dim + 1]); + if (mesh_dim == 3) { + // Output z coordinate. + fprintf(fout, " %.12g", holelist[i * mesh_dim + 2]); + } + fprintf(fout, "\n"); + } + + // Save regions. + fprintf(fout, "%d\n", numberofregions); + for (i = 0; i < numberofregions; i++) { + if (mesh_dim == 2) { + // Output the index, x, y coordinates, attribute (region number) + // and maximum area constraint (maybe -1). + fprintf(fout, "%d %.12g %.12g %.12g %.12g\n", i + firstnumber, + regionlist[i * 4], regionlist[i * 4 + 1], + regionlist[i * 4 + 2], regionlist[i * 4 + 3]); + } else { + // Output the index, x, y, z coordinates, attribute (region number) + // and maximum volume constraint (maybe -1). + fprintf(fout, "%d %.12g %.12g %.12g %.12g %.12g\n", i + firstnumber, + regionlist[i * 5], regionlist[i * 5 + 1], + regionlist[i * 5 + 2], regionlist[i * 5 + 3], + regionlist[i * 5 + 4]); + } + } + + fclose(fout); +} + +//============================================================================// +// // +// save_faces2smesh() Save triangular faces to a .smesh file. // +// // +// It only save the facets. No holes and regions. No .node file. // +// // +//============================================================================// + +void tetgenio::save_faces2smesh(char* filebasename) +{ + FILE *fout; + char outsmeshfilename[FILENAMESIZE]; + int i, j; + + sprintf(outsmeshfilename, "%s.smesh", filebasename); + printf("Saving faces to %s\n", outsmeshfilename); + fout = fopen(outsmeshfilename, "w"); + + // The zero indicates that the vertices are in a separate .node file. + // Followed by number of dimensions, number of vertex attributes, + // and number of boundary markers (zero or one). + fprintf(fout, "%d %d %d %d\n", 0, mesh_dim, numberofpointattributes, + pointmarkerlist != NULL ? 1 : 0); + + // Number of facets, number of boundary markers (zero or one). + fprintf(fout, "%d %d\n", numberoftrifaces, + trifacemarkerlist != NULL ? 1 : 0); + + // Output triangular facets. + for (i = 0; i < numberoftrifaces; i++) { + j = i * 3; + fprintf(fout, "3 %d %d %d", trifacelist[j], trifacelist[j + 1], + trifacelist[j + 2]); + if (trifacemarkerlist != NULL) { + fprintf(fout, " %d", trifacemarkerlist[i]); + } + fprintf(fout, "\n"); + } + + // No holes and regions. + fprintf(fout, "0\n"); + fprintf(fout, "0\n"); + + fclose(fout); +} + +//============================================================================// +// // +// readline() Read a nonempty line from a file. // +// // +// A line is considered "nonempty" if it contains something more than white // +// spaces. If a line is considered empty, it will be dropped and the next // +// line will be read, this process ends until reaching the end-of-file or a // +// non-empty line. Return NULL if it is the end-of-file, otherwise, return // +// a pointer to the first non-whitespace character of the line. // +// // +//============================================================================// + +char* tetgenio::readline(char *string, FILE *infile, int *linenumber) +{ + char *result; + + // Search for a non-empty line. + do { + result = fgets(string, INPUTLINESIZE - 1, infile); + if (linenumber) (*linenumber)++; + if (result == (char *) NULL) { + return (char *) NULL; + } + // Skip white spaces. + while ((*result == ' ') || (*result == '\t')) result++; + // If it's end of line, read another line and try again. + } while ((*result == '\0') || (*result == '\r') || (*result == '\n')); + return result; +} + +//============================================================================// +// // +// findnextfield() Find the next field of a string. // +// // +// Jumps past the current field by searching for whitespace or a comma, then // +// jumps past the whitespace or the comma to find the next field. // +// // +//============================================================================// + +char* tetgenio::findnextfield(char *string) +{ + char *result; + + result = string; + // Skip the current field. Stop upon reaching whitespace or a comma. + while ((*result != '\0') && (*result != ' ') && (*result != '\t') && + (*result != ',') && (*result != ';')) { + result++; + } + // Now skip the whitespace or the comma, stop at anything else that looks + // like a character, or the end of a line. + while ((*result == ' ') || (*result == '\t') || (*result == ',') || + (*result == ';')) { + result++; + } + return result; +} + +//============================================================================// +// // +// readnumberline() Read a nonempty number line from a file. // +// // +// A line is considered "nonempty" if it contains something that looks like // +// a number. Comments (prefaced by `#') are ignored. // +// // +//============================================================================// + +char* tetgenio::readnumberline(char *string, FILE *infile, char *infilename) +{ + char *result; + + // Search for something that looks like a number. + do { + result = fgets(string, INPUTLINESIZE, infile); + if (result == (char *) NULL) { + return result; + } + // Skip anything that doesn't look like a number, a comment, + // or the end of a line. + while ((*result != '\0') && (*result != '#') + && (*result != '.') && (*result != '+') && (*result != '-') + && ((*result < '0') || (*result > '9'))) { + result++; + } + // If it's a comment or end of line, read another line and try again. + } while ((*result == '#') || (*result == '\0')); + return result; +} + +//============================================================================// +// // +// findnextnumber() Find the next field of a number string. // +// // +// Jumps past the current field by searching for whitespace or a comma, then // +// jumps past the whitespace or the comma to find the next field that looks // +// like a number. // +// // +//============================================================================// + +char* tetgenio::findnextnumber(char *string) +{ + char *result; + + result = string; + // Skip the current field. Stop upon reaching whitespace or a comma. + while ((*result != '\0') && (*result != '#') && (*result != ' ') && + (*result != '\t') && (*result != ',')) { + result++; + } + // Now skip the whitespace and anything else that doesn't look like a + // number, a comment, or the end of a line. + while ((*result != '\0') && (*result != '#') + && (*result != '.') && (*result != '+') && (*result != '-') + && ((*result < '0') || (*result > '9'))) { + result++; + } + // Check for a comment (prefixed with `#'). + if (*result == '#') { + *result = '\0'; + } + return result; +} + +// // +// // +//== io_cxx ==================================================================// + + +//== behavior_cxx ============================================================// +// // +// // + +//============================================================================// +// // +// syntax() Print list of command line switches. // +// // +//============================================================================// + +void tetgenbehavior::syntax() +{ + printf(" tetgen [-pYrq_Aa_miO_S_T_XMwcdzfenvgkJBNEFICQVh] input_file\n"); + printf(" -p Tetrahedralizes a piecewise linear complex (PLC).\n"); + printf(" -Y Preserves the input surface mesh (does not modify it).\n"); + printf(" -r Reconstructs a previously generated mesh.\n"); + printf(" -q Refines mesh (to improve mesh quality).\n"); + printf(" -R Mesh coarsening (to reduce the mesh elements).\n"); + printf(" -A Assigns attributes to tetrahedra in different regions.\n"); + printf(" -a Applies a maximum tetrahedron volume constraint.\n"); + printf(" -m Applies a mesh sizing function.\n"); + printf(" -i Inserts a list of additional points.\n"); + printf(" -O Specifies the level of mesh optimization.\n"); + printf(" -S Specifies maximum number of added points.\n"); + printf(" -T Sets a tolerance for coplanar test (default 1e-8).\n"); + printf(" -X Suppresses use of exact arithmetic.\n"); + printf(" -M No merge of coplanar facets or very close vertices.\n"); + printf(" -w Generates weighted Delaunay (regular) triangulation.\n"); + printf(" -c Retains the convex hull of the PLC.\n"); + printf(" -d Detects self-intersections of facets of the PLC.\n"); + printf(" -z Numbers all output items starting from zero.\n"); + printf(" -f Outputs all faces to .face file.\n"); + printf(" -e Outputs all edges to .edge file.\n"); + printf(" -n Outputs tetrahedra neighbors to .neigh file.\n"); + printf(" -g Outputs mesh to .mesh file for viewing by Medit.\n"); + printf(" -k Outputs mesh to .vtk file for viewing by Paraview.\n"); + printf(" -J No jettison of unused vertices from output .node file.\n"); + printf(" -B Suppresses output of boundary information.\n"); + printf(" -N Suppresses output of .node file.\n"); + printf(" -E Suppresses output of .ele file.\n"); + printf(" -F Suppresses output of .face and .edge file.\n"); + printf(" -I Suppresses mesh iteration numbers.\n"); + printf(" -C Checks the consistency of the final mesh.\n"); + printf(" -Q Quiet: No terminal output except errors.\n"); + printf(" -V Verbose: Detailed information, more terminal output.\n"); + printf(" -h Help: A brief instruction for using TetGen.\n"); +} + +//============================================================================// +// // +// usage() Print a brief instruction for using TetGen. // +// // +//============================================================================// + +void tetgenbehavior::usage() +{ + printf("TetGen\n"); + printf("A Quality Tetrahedral Mesh Generator and 3D Delaunay "); + printf("Triangulator\n"); + printf("Version 1.6\n"); + printf("August, 2020\n"); + printf("\n"); + printf("Copyright (C) 2002 - 2020\n"); + printf("\n"); + printf("What Can TetGen Do?\n"); + printf("\n"); + printf(" TetGen generates Delaunay tetrahedralizations, constrained\n"); + printf(" Delaunay tetrahedralizations, and quality tetrahedral meshes.\n"); + printf("\n"); + printf("Command Line Syntax:\n"); + printf("\n"); + printf(" Below is the basic command line syntax of TetGen with a list of "); + printf("short\n"); + printf(" descriptions. Underscores indicate that numbers may optionally\n"); + printf(" follow certain switches. Do not leave any space between a "); + printf("switch\n"); + printf(" and its numeric parameter. \'input_file\' contains input data\n"); + printf(" depending on the switches you supplied, which may be a "); + printf(" piecewise\n"); + printf(" linear complex or a list of nodes. File formats and detailed\n"); + printf(" description of command line switches are found in the user's "); + printf("manual.\n"); + printf("\n"); + syntax(); + printf("\n"); + printf("Examples of How to Use TetGen:\n"); + printf("\n"); + printf(" \'tetgen object\' reads vertices from object.node, and writes "); + printf("their\n Delaunay tetrahedralization to object.1.node, "); + printf("object.1.ele\n (tetrahedra), and object.1.face"); + printf(" (convex hull faces).\n"); + printf("\n"); + printf(" \'tetgen -p object\' reads a PLC from object.poly or object."); + printf("smesh (and\n possibly object.node) and writes its constrained "); + printf("Delaunay\n tetrahedralization to object.1.node, object.1.ele, "); + printf("object.1.face,\n"); + printf(" (boundary faces) and object.1.edge (boundary edges).\n"); + printf("\n"); + printf(" \'tetgen -pq1.414a.1 object\' reads a PLC from object.poly or\n"); + printf(" object.smesh (and possibly object.node), generates a mesh "); + printf("whose\n tetrahedra have radius-edge ratio smaller than 1.414 and "); + printf("have volume\n of 0.1 or less, and writes the mesh to "); + printf("object.1.node, object.1.ele,\n object.1.face, and object.1.edge\n"); + printf("\n"); + printf("Please send bugs/comments to Hang Si \n"); + terminatetetgen(NULL, 0); +} + +//============================================================================// +// // +// parse_commandline() Read the command line, identify switches, and set // +// up options and file names. // +// // +// 'argc' and 'argv' are the same parameters passed to the function main() // +// of a C/C++ program. They together represent the command line user invoked // +// from an environment in which TetGen is running. // +// // +//============================================================================// + +bool tetgenbehavior::parse_commandline(int argc, char **argv) +{ + int startindex; + int increment; + int meshnumber; + int i, j, k; + char workstring[1024]; + + // First determine the input style of the switches. + if (argc == 0) { + startindex = 0; // Switches are given without a dash. + argc = 1; // For running the following for-loop once. + commandline[0] = '\0'; + } else { + startindex = 1; + strcpy(commandline, argv[0]); + strcat(commandline, " "); + } + + for (i = startindex; i < argc; i++) { + // Remember the command line for output. + strcat(commandline, argv[i]); + strcat(commandline, " "); + if (startindex == 1) { + // Is this string a filename? + if (argv[i][0] != '-') { + strncpy(infilename, argv[i], 1024 - 1); + infilename[1024 - 1] = '\0'; + continue; + } + } + // Parse the individual switch from the string. + for (j = startindex; argv[i][j] != '\0'; j++) { + if (argv[i][j] == 'p') { + plc = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + facet_separate_ang_tol = (REAL) strtod(workstring, (char **) NULL); + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + //facet_overlap_ang_tol = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + facet_small_ang_tol = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + collinear_ang_tol = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'Y') { + nobisect++; + if (cdt > 0) { + printf("Warning: switch -D is omitted.\n"); + cdt = 0; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + supsteiner_level = (argv[i][j + 1] - '0'); + j++; + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + addsteiner_algo = (argv[i][j + 1] - '0'); + j++; + } + } + } else if (argv[i][j] == 'r') { + refine = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + elem_growth_ratio = (REAL) strtod(workstring, (char **) NULL); + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + refine_progress_ratio = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'q') { + quality = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + minratio = (REAL) strtod(workstring, (char **) NULL); + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + mindihedral = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'R') { + coarsen = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + coarsen_param = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + coarsen_percent = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'w') { + weighted = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + weighted_param = (argv[i][j + 1] - '0'); + j++; + } + } else if (argv[i][j] == 'b') { + // -b(brio_threshold/brio_ratio/hilbert_limit/hilbert_order) + brio_hilbert = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + brio_threshold = (int) strtol(workstring, (char **) &workstring, 0); + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + brio_ratio = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + hilbert_limit = (int) strtol(workstring, (char **) &workstring, 0); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + hilbert_order = (REAL) strtod(workstring, (char **) NULL); + } + } + if (brio_threshold == 0) { // -b0 + brio_hilbert = 0; // Turn off BRIO-Hilbert sorting. + } + if (brio_ratio >= 1.0) { // -b/1 + no_sort = 1; + brio_hilbert = 0; // Turn off BRIO-Hilbert sorting. + } + } else if (argv[i][j] == 'L') { + flipinsert = 1; + } else if (argv[i][j] == 'm') { + metric = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + metric_scale = (REAL) strtod(workstring, (char **) NULL); + } + } else if (argv[i][j] == 'a') { + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + fixedvolume = 1; + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + maxvolume = (REAL) strtod(workstring, (char **) NULL); + maxvolume_length = pow(maxvolume, 1./3.) / 3.; + } else { + varvolume = 1; + } + } else if (argv[i][j] == 'A') { + regionattrib = 1; + } else if (argv[i][j] == 'D') { + if ((argv[i][j + 1] >= '1') && (argv[i][j + 1] <= '7')) { + // -D# (with a number following it.) + cdtrefine = (argv[i][j + 1] - '1') + 1; + j++; + } else { + cdt = 1; // -D without a number following it. + } + } else if (argv[i][j] == 'i') { + insertaddpoints = 1; + } else if (argv[i][j] == 'd') { + diagnose = 1; + } else if (argv[i][j] == 'c') { + convex = 1; + } else if (argv[i][j] == 'M') { + nomergefacet = 1; + nomergevertex = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '1')) { + nomergefacet = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '1')) { + nomergevertex = (argv[i][j + 1] - '0'); + j++; + } + } + } else if (argv[i][j] == 'X') { + if (argv[i][j + 1] == '1') { + nostaticfilter = 1; + j++; + } else { + noexact = 1; + } + } else if (argv[i][j] == 'z') { + if (argv[i][j + 1] == '1') { // -z1 + reversetetori = 1; + j++; + } else { + zeroindex = 1; // -z + } + } else if (argv[i][j] == 'f') { + facesout++; + } else if (argv[i][j] == 'e') { + edgesout++; + } else if (argv[i][j] == 'n') { + neighout++; + } else if (argv[i][j] == 'g') { + meditview = 1; + } else if (argv[i][j] == 'k') { + if (argv[i][j + 1] == '2') { // -k2 + vtksurfview = 1; + j++; + } + else { + vtkview = 1; + } + } else if (argv[i][j] == 'J') { + nojettison = 1; + } else if (argv[i][j] == 'B') { + nobound = 1; + } else if (argv[i][j] == 'N') { + nonodewritten = 1; + } else if (argv[i][j] == 'E') { + noelewritten = 1; + } else if (argv[i][j] == 'F') { + nofacewritten = 1; + } else if (argv[i][j] == 'I') { + noiterationnum = 1; + } else if (argv[i][j] == 'S') { + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + steinerleft = (int) strtol(workstring, (char **) NULL, 0); + } + } else if (argv[i][j] == 'o') { + if (argv[i][j + 1] == '2') { + order = 2; + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -o/# + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + optmaxdihedral = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -o//# + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + opt_max_asp_ratio = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -o///# + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + opt_max_edge_ratio = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'O') { + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { // -O# + opt_max_flip_level = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -O/# + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '7')) { + opt_scheme = (argv[i][j + 1] - '0'); + j++; + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -O//# + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + opt_iterations = (int) strtol(workstring, (char **) NULL, 0); + j++; + } + } + } else if (argv[i][j] == 's') { + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { // -s# + smooth_cirterion = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -s#/# + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + smooth_maxiter = (int) strtol(workstring, (char **) NULL, 0); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { // -s#/#/# + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + smooth_alpha = (REAL) strtod(workstring, (char **) NULL); + } + } + } else if (argv[i][j] == 'T') { + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + epsilon = (REAL) strtod(workstring, (char **) NULL); + } + } else if (argv[i][j] == 'C') { + docheck++; + } else if (argv[i][j] == 'Q') { + quiet = 1; + } else if (argv[i][j] == 'W') { + nowarning = 1; + } else if (argv[i][j] == 'V') { + verbose++; + } else if (argv[i][j] == 'l') { + //refine_list = 1; //incrflip = 1; + } else if (argv[i][j] == 'x') { + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + tetrahedraperblock = (int) strtol(workstring, (char **) NULL, 0); + if (tetrahedraperblock > 8188) { + vertexperblock = tetrahedraperblock / 2; + shellfaceperblock = vertexperblock / 2; + } else { + tetrahedraperblock = 8188; + } + } + } else if (argv[i][j] == 'H') { + if (argv[i+1][0] != '-') { + hole_mesh = 1; + // It is a filename following by -H + strncpy(hole_mesh_filename, argv[i+1], 1024 - 1); + hole_mesh_filename[1024 - 1] = '\0'; + i++; // Skip the next string. + break; // j + } + } else if ((argv[i][j] == 'h') || // (argv[i][j] == 'H') + (argv[i][j] == '?')) { + usage(); + } else { + printf("Warning: Unknown switch -%c.\n", argv[i][j]); + } + } + } + + if (startindex == 0) { + // Set a temporary filename for debugging output. + strcpy(infilename, "tetgen-tmpfile"); + } else { + if (infilename[0] == '\0') { + // No input file name. Print the syntax and exit. + syntax(); + terminatetetgen(NULL, 0); + } + // Recognize the object from file extension if it is available. + if (!strcmp(&infilename[strlen(infilename) - 5], ".node")) { + infilename[strlen(infilename) - 5] = '\0'; + object = NODES; + } else if (!strcmp(&infilename[strlen(infilename) - 5], ".poly")) { + infilename[strlen(infilename) - 5] = '\0'; + object = POLY; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 6], ".smesh")) { + infilename[strlen(infilename) - 6] = '\0'; + object = POLY; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".off")) { + infilename[strlen(infilename) - 4] = '\0'; + object = OFF; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".ply")) { + infilename[strlen(infilename) - 4] = '\0'; + object = PLY; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".stl")) { + infilename[strlen(infilename) - 4] = '\0'; + object = STL; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 5], ".mesh")) { + infilename[strlen(infilename) - 5] = '\0'; + object = MEDIT; + if (!refine) plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".vtk")) { + infilename[strlen(infilename) - 4] = '\0'; + object = VTK; + plc = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".ele")) { + infilename[strlen(infilename) - 4] = '\0'; + object = MESH; + refine = 1; + } else if (!strcmp(&infilename[strlen(infilename) - 4], ".neu")) { + infilename[strlen(infilename) - 4] = '\0'; + object = NEU_MESH; + refine = 1; + } + } + + if (nobisect && (!plc && !refine)) { // -Y + plc = 1; // Default -p option. + } + if (quality && (!plc && !refine)) { // -q + plc = 1; // Default -p option. + } + if (diagnose && !plc) { // -d + plc = 1; + } + if (refine && !quality) { // -r only + // Reconstruct a mesh, no mesh optimization. + opt_max_flip_level = 0; + opt_iterations = 0; + } + if (insertaddpoints && (opt_max_flip_level == 0)) { // with -i option + opt_max_flip_level = 2; + } + if (coarsen && (opt_max_flip_level == 0)) { // with -R option + opt_max_flip_level = 2; + } + + // Detect improper combinations of switches. + if ((refine || plc) && weighted) { + printf("Error: Switches -w cannot use together with -p or -r.\n"); + return false; + } + + if (convex) { // -c + if (plc && !regionattrib) { + // -A (region attribute) is needed for marking exterior tets (-1). + regionattrib = 1; + } + } + + // Note: -A must not used together with -r option. + // Be careful not to add an extra attribute to each element unless the + // input supports it (PLC in, but not refining a preexisting mesh). + if (refine || !plc) { + regionattrib = 0; + } + // Be careful not to allocate space for element area constraints that + // will never be assigned any value (other than the default -1.0). + if (!refine && !plc) { + varvolume = 0; + } + // If '-a' or '-aa' is in use, enable '-q' option too. + if (fixedvolume || varvolume) { + if (quality == 0) { + quality = 1; + if (!plc && !refine) { + plc = 1; // enable -p. + } + } + } + if (!quality) { + // If no user-specified dihedral angle bound. Use default ones. + if (optmaxdihedral == 177.0) { // set by -o/# + optmaxdihedral = 179.9; + } + } + + if (quiet > 0) { + verbose = 0; // No printf output during the execution. + } + + increment = 0; + strcpy(workstring, infilename); + j = 1; + while (workstring[j] != '\0') { + if ((workstring[j] == '.') && (workstring[j + 1] != '\0')) { + increment = j + 1; + } + j++; + } + meshnumber = 0; + if (increment > 0) { + j = increment; + do { + if ((workstring[j] >= '0') && (workstring[j] <= '9')) { + meshnumber = meshnumber * 10 + (int) (workstring[j] - '0'); + } else { + increment = 0; + } + j++; + } while (workstring[j] != '\0'); + } + if (noiterationnum) { + strcpy(outfilename, infilename); + } else if (increment == 0) { + strcpy(outfilename, infilename); + strcat(outfilename, ".1"); + } else { + workstring[increment] = '%'; + workstring[increment + 1] = 'd'; + workstring[increment + 2] = '\0'; + sprintf(outfilename, workstring, meshnumber + 1); + } + // Additional input file name has the end ".a". + strcpy(addinfilename, infilename); + strcat(addinfilename, ".a"); + // Background filename has the form "*.b.ele", "*.b.node", ... + strcpy(bgmeshfilename, infilename); + strcat(bgmeshfilename, ".b"); + + return true; +} + +// // +// // +//== behavior_cxx ============================================================// + +//== mempool_cxx =============================================================// +// // +// // + +// Initialize fast lookup tables for mesh maniplulation primitives. + +int tetgenmesh::bondtbl[12][12] = {{0,},}; +int tetgenmesh::enexttbl[12] = {0,}; +int tetgenmesh::eprevtbl[12] = {0,}; +int tetgenmesh::enextesymtbl[12] = {0,}; +int tetgenmesh::eprevesymtbl[12] = {0,}; +int tetgenmesh::eorgoppotbl[12] = {0,}; +int tetgenmesh::edestoppotbl[12] = {0,}; +int tetgenmesh::fsymtbl[12][12] = {{0,},}; +int tetgenmesh::facepivot1[12] = {0,}; +int tetgenmesh::facepivot2[12][12] = {{0,},}; +int tetgenmesh::tsbondtbl[12][6] = {{0,},}; +int tetgenmesh::stbondtbl[12][6] = {{0,},}; +int tetgenmesh::tspivottbl[12][6] = {{0,},}; +int tetgenmesh::stpivottbl[12][6] = {{0,},}; + +// Table 'esymtbl' takes an directed edge (version) as input, returns the +// inversed edge (version) of it. + +int tetgenmesh::esymtbl[12] = {9, 6, 11, 4, 3, 7, 1, 5, 10, 0, 8, 2}; + +// The following four tables give the 12 permutations of the set {0,1,2,3}. +// An offset 4 is added to each element for a direct access of the points +// in the tetrahedron data structure. + +int tetgenmesh:: orgpivot[12] = {7, 7, 5, 5, 6, 4, 4, 6, 5, 6, 7, 4}; +int tetgenmesh::destpivot[12] = {6, 4, 4, 6, 5, 6, 7, 4, 7, 7, 5, 5}; +int tetgenmesh::apexpivot[12] = {5, 6, 7, 4, 7, 7, 5, 5, 6, 4, 4, 6}; +int tetgenmesh::oppopivot[12] = {4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7}; + +// The twelve versions correspond to six undirected edges. The following two +// tables map a version to an undirected edge and vice versa. + +int tetgenmesh::ver2edge[12] = {0, 1, 2, 3, 3, 5, 1, 5, 4, 0, 4, 2}; +int tetgenmesh::edge2ver[ 6] = {0, 1, 2, 3, 8, 5}; + +// Edge versions whose apex or opposite may be dummypoint. + +int tetgenmesh::epivot[12] = {4, 5, 2, 11, 4, 5, 2, 11, 4, 5, 2, 11}; + + +// Table 'snextpivot' takes an edge version as input, returns the next edge +// version in the same edge ring. + +int tetgenmesh::snextpivot[6] = {2, 5, 4, 1, 0, 3}; + +// The following three tables give the 6 permutations of the set {0,1,2}. +// An offset 3 is added to each element for a direct access of the points +// in the triangle data structure. + +int tetgenmesh::sorgpivot [6] = {3, 4, 4, 5, 5, 3}; +int tetgenmesh::sdestpivot[6] = {4, 3, 5, 4, 3, 5}; +int tetgenmesh::sapexpivot[6] = {5, 5, 3, 3, 4, 4}; + +//============================================================================// +// // +// inittable() Initialize the look-up tables. // +// // +//============================================================================// + +void tetgenmesh::inittables() +{ + int soffset, toffset; + int i, j; + + + // i = t1.ver; j = t2.ver; + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); + } + } + + + // i = t1.ver; j = t2.ver + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + fsymtbl[i][j] = (j + 12 - (i & 12)) % 12; + } + } + + + for (i = 0; i < 12; i++) { + facepivot1[i] = (esymtbl[i] & 3); + } + + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + facepivot2[i][j] = fsymtbl[esymtbl[i]][j]; + } + } + + for (i = 0; i < 12; i++) { + enexttbl[i] = (i + 4) % 12; + eprevtbl[i] = (i + 8) % 12; + } + + for (i = 0; i < 12; i++) { + enextesymtbl[i] = esymtbl[enexttbl[i]]; + eprevesymtbl[i] = esymtbl[eprevtbl[i]]; + } + + for (i = 0; i < 12; i++) { + eorgoppotbl [i] = eprevtbl[esymtbl[enexttbl[i]]]; + edestoppotbl[i] = enexttbl[esymtbl[eprevtbl[i]]]; + } + + + // i = t.ver, j = s.shver + for (i = 0; i < 12; i++) { + for (j = 0; j < 6; j++) { + if ((j & 1) == 0) { + soffset = (6 - ((i & 12) >> 1)) % 6; + toffset = (12 - ((j & 6) << 1)) % 12; + } else { + soffset = (i & 12) >> 1; + toffset = (j & 6) << 1; + } + tsbondtbl[i][j] = (j & 1) + (((j & 6) + soffset) % 6); + stbondtbl[i][j] = (i & 3) + (((i & 12) + toffset) % 12); + } + } + + + // i = t.ver, j = s.shver + for (i = 0; i < 12; i++) { + for (j = 0; j < 6; j++) { + if ((j & 1) == 0) { + soffset = (i & 12) >> 1; + toffset = (j & 6) << 1; + } else { + soffset = (6 - ((i & 12) >> 1)) % 6; + toffset = (12 - ((j & 6) << 1)) % 12; + } + tspivottbl[i][j] = (j & 1) + (((j & 6) + soffset) % 6); + stpivottbl[i][j] = (i & 3) + (((i & 12) + toffset) % 12); + } + } +} + + +//============================================================================// +// // +// restart() Deallocate all objects in this pool. // +// // +// The pool returns to a fresh state, like after it was initialized, except // +// that no memory is freed to the operating system. Rather, the previously // +// allocated blocks are ready to be used. // +// // +//============================================================================// + +void tetgenmesh::arraypool::restart() +{ + objects = 0l; +} + +//============================================================================// +// // +// poolinit() Initialize an arraypool for allocation of objects. // +// // +// Before the pool may be used, it must be initialized by this procedure. // +// After initialization, memory can be allocated and freed in this pool. // +// // +//============================================================================// + +void tetgenmesh::arraypool::poolinit(int sizeofobject, int log2objperblk) +{ + // Each object must be at least one byte long. + objectbytes = sizeofobject > 1 ? sizeofobject : 1; + + log2objectsperblock = log2objperblk; + // Compute the number of objects in each block. + objectsperblock = ((int) 1) << log2objectsperblock; + objectsperblockmark = objectsperblock - 1; + + // No memory has been allocated. + totalmemory = 0l; + // The top array has not been allocated yet. + toparray = (char **) NULL; + toparraylen = 0; + + // Ready all indices to be allocated. + restart(); +} + +//============================================================================// +// // +// arraypool() The constructor and destructor. // +// // +//============================================================================// + +tetgenmesh::arraypool::arraypool(int sizeofobject, int log2objperblk) +{ + poolinit(sizeofobject, log2objperblk); +} + +tetgenmesh::arraypool::~arraypool() +{ + int i; + + // Has anything been allocated at all? + if (toparray != (char **) NULL) { + // Walk through the top array. + for (i = 0; i < toparraylen; i++) { + // Check every pointer; NULLs may be scattered randomly. + if (toparray[i] != (char *) NULL) { + // Free an allocated block. + free((void *) toparray[i]); + } + } + // Free the top array. + free((void *) toparray); + } + + // The top array is no longer allocated. + toparray = (char **) NULL; + toparraylen = 0; + objects = 0; + totalmemory = 0; +} + +//============================================================================// +// // +// getblock() Return (and perhaps create) the block containing the object // +// with a given index. // +// // +// This function takes care of allocating or resizing the top array if nece- // +// ssary, and of allocating the block if it hasn't yet been allocated. // +// // +// Return a pointer to the beginning of the block (NOT the object). // +// // +//============================================================================// + +char* tetgenmesh::arraypool::getblock(int objectindex) +{ + char **newarray; + char *block; + int newsize; + int topindex; + int i; + + // Compute the index in the top array (upper bits). + topindex = objectindex >> log2objectsperblock; + // Does the top array need to be allocated or resized? + if (toparray == (char **) NULL) { + // Allocate the top array big enough to hold 'topindex', and NULL out + // its contents. + newsize = topindex + 128; + toparray = (char **) malloc((size_t) (newsize * sizeof(char *))); + toparraylen = newsize; + for (i = 0; i < newsize; i++) { + toparray[i] = (char *) NULL; + } + // Account for the memory. + totalmemory = newsize * (uintptr_t) sizeof(char *); + } else if (topindex >= toparraylen) { + // Resize the top array, making sure it holds 'topindex'. + newsize = 3 * toparraylen; + if (topindex >= newsize) { + newsize = topindex + 128; + } + // Allocate the new array, copy the contents, NULL out the rest, and + // free the old array. + newarray = (char **) malloc((size_t) (newsize * sizeof(char *))); + for (i = 0; i < toparraylen; i++) { + newarray[i] = toparray[i]; + } + for (i = toparraylen; i < newsize; i++) { + newarray[i] = (char *) NULL; + } + free(toparray); + // Account for the memory. + totalmemory += (newsize - toparraylen) * sizeof(char *); + toparray = newarray; + toparraylen = newsize; + } + + // Find the block, or learn that it hasn't been allocated yet. + block = toparray[topindex]; + if (block == (char *) NULL) { + // Allocate a block at this index. + block = (char *) malloc((size_t) (objectsperblock * objectbytes)); + toparray[topindex] = block; + // Account for the memory. + totalmemory += objectsperblock * objectbytes; + } + + // Return a pointer to the block. + return block; +} + +//============================================================================// +// // +// lookup() Return the pointer to the object with a given index, or NULL // +// if the object's block doesn't exist yet. // +// // +//============================================================================// + +void* tetgenmesh::arraypool::lookup(int objectindex) +{ + char *block; + int topindex; + + // Has the top array been allocated yet? + if (toparray == (char **) NULL) { + return (void *) NULL; + } + + // Compute the index in the top array (upper bits). + topindex = objectindex >> log2objectsperblock; + // Does the top index fit in the top array? + if (topindex >= toparraylen) { + return (void *) NULL; + } + + // Find the block, or learn that it hasn't been allocated yet. + block = toparray[topindex]; + if (block == (char *) NULL) { + return (void *) NULL; + } + + // Compute a pointer to the object with the given index. Note that + // 'objectsperblock' is a power of two, so the & operation is a bit mask + // that preserves the lower bits. + return (void *)(block + (objectindex & (objectsperblock - 1)) * objectbytes); +} + +//============================================================================// +// // +// newindex() Allocate space for a fresh object from the pool. // +// // +// 'newptr' returns a pointer to the new object (it must not be a NULL). // +// // +//============================================================================// + +int tetgenmesh::arraypool::newindex(void **newptr) +{ + // Allocate an object at index 'firstvirgin'. + int newindex = objects; + *newptr = (void *) (getblock(objects) + + (objects & (objectsperblock - 1)) * objectbytes); + objects++; + + return newindex; +} + + +/////////////////////////////////////////////////////////////////////////////// +// // +// memorypool() The constructors of memorypool. // +// // +/////////////////////////////////////////////////////////////////////////////// + +tetgenmesh::memorypool::memorypool() +{ + firstblock = nowblock = (void **) NULL; + nextitem = (void *) NULL; + deaditemstack = (void *) NULL; + pathblock = (void **) NULL; + pathitem = (void *) NULL; + alignbytes = 0; + itembytes = itemwords = 0; + itemsperblock = 0; + items = maxitems = 0l; + unallocateditems = 0; + pathitemsleft = 0; +} + +tetgenmesh::memorypool::memorypool(int bytecount, int itemcount, int wsize, + int alignment) +{ + poolinit(bytecount, itemcount, wsize, alignment); +} + +//============================================================================// +// // +// ~memorypool() Free to the operating system all memory taken by a pool. // +// // +//============================================================================// + +tetgenmesh::memorypool::~memorypool() +{ + while (firstblock != (void **) NULL) { + nowblock = (void **) *(firstblock); + free(firstblock); + firstblock = nowblock; + } +} + +//============================================================================// +// // +// poolinit() Initialize a pool of memory for allocation of items. // +// // +// A `pool' is created whose records have size at least `bytecount'. Items // +// will be allocated in `itemcount'-item blocks. Each item is assumed to be // +// a collection of words, and either pointers or floating-point values are // +// assumed to be the "primary" word type. (The "primary" word type is used // +// to determine alignment of items.) If `alignment' isn't zero, all items // +// will be `alignment'-byte aligned in memory. `alignment' must be either a // +// multiple or a factor of the primary word size; powers of two are safe. // +// `alignment' is normally used to create a few unused bits at the bottom of // +// each item's pointer, in which information may be stored. // +// // +//============================================================================// + +void tetgenmesh::memorypool::poolinit(int bytecount,int itemcount,int wordsize, + int alignment) +{ + // Find the proper alignment, which must be at least as large as: + // - The parameter `alignment'. + // - The primary word type, to avoid unaligned accesses. + // - sizeof(void *), so the stack of dead items can be maintained + // without unaligned accesses. + if (alignment > wordsize) { + alignbytes = alignment; + } else { + alignbytes = wordsize; + } + if ((int) sizeof(void *) > alignbytes) { + alignbytes = (int) sizeof(void *); + } + itemwords = ((bytecount + alignbytes - 1) / alignbytes) + * (alignbytes / wordsize); + itembytes = itemwords * wordsize; + itemsperblock = itemcount; + + // Allocate a block of items. Space for `itemsperblock' items and one + // pointer (to point to the next block) are allocated, as well as space + // to ensure alignment of the items. + firstblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) + + alignbytes); + if (firstblock == (void **) NULL) { + terminatetetgen(NULL, 1); + } + // Set the next block pointer to NULL. + *(firstblock) = (void *) NULL; + restart(); +} + +//============================================================================// +// // +// restart() Deallocate all items in this pool. // +// // +// The pool is returned to its starting state, except that no memory is // +// freed to the operating system. Rather, the previously allocated blocks // +// are ready to be reused. // +// // +//============================================================================// + +void tetgenmesh::memorypool::restart() +{ + uintptr_t alignptr; + + items = 0; + maxitems = 0; + + // Set the currently active block. + nowblock = firstblock; + // Find the first item in the pool. Increment by the size of (void *). + alignptr = (uintptr_t) (nowblock + 1); + // Align the item on an `alignbytes'-byte boundary. + nextitem = (void *) + (alignptr + (uintptr_t) alignbytes - + (alignptr % (uintptr_t) alignbytes)); + // There are lots of unallocated items left in this block. + unallocateditems = itemsperblock; + // The stack of deallocated items is empty. + deaditemstack = (void *) NULL; +} + +//============================================================================// +// // +// alloc() Allocate space for an item. // +// // +//============================================================================// + +void* tetgenmesh::memorypool::alloc() +{ + void *newitem; + void **newblock; + uintptr_t alignptr; + + // First check the linked list of dead items. If the list is not + // empty, allocate an item from the list rather than a fresh one. + if (deaditemstack != (void *) NULL) { + newitem = deaditemstack; // Take first item in list. + deaditemstack = * (void **) deaditemstack; + } else { + // Check if there are any free items left in the current block. + if (unallocateditems == 0) { + // Check if another block must be allocated. + if (*nowblock == (void *) NULL) { + // Allocate a new block of items, pointed to by the previous block. + newblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) + + alignbytes); + if (newblock == (void **) NULL) { + terminatetetgen(NULL, 1); + } + *nowblock = (void *) newblock; + // The next block pointer is NULL. + *newblock = (void *) NULL; + } + // Move to the new block. + nowblock = (void **) *nowblock; + // Find the first item in the block. + // Increment by the size of (void *). + alignptr = (uintptr_t) (nowblock + 1); + // Align the item on an `alignbytes'-byte boundary. + nextitem = (void *) + (alignptr + (uintptr_t) alignbytes - + (alignptr % (uintptr_t) alignbytes)); + // There are lots of unallocated items left in this block. + unallocateditems = itemsperblock; + } + // Allocate a new item. + newitem = nextitem; + // Advance `nextitem' pointer to next free item in block. + nextitem = (void *) ((uintptr_t) nextitem + itembytes); + unallocateditems--; + maxitems++; + } + items++; + return newitem; +} + +//============================================================================// +// // +// dealloc() Deallocate space for an item. // +// // +// The deallocated space is stored in a queue for later reuse. // +// // +//============================================================================// + +void tetgenmesh::memorypool::dealloc(void *dyingitem) +{ + // Push freshly killed item onto stack. + *((void **) dyingitem) = deaditemstack; + deaditemstack = dyingitem; + items--; +} + +//============================================================================// +// // +// traversalinit() Prepare to traverse the entire list of items. // +// // +// This routine is used in conjunction with traverse(). // +// // +//============================================================================// + +void tetgenmesh::memorypool::traversalinit() +{ + uintptr_t alignptr; + + // Begin the traversal in the first block. + pathblock = firstblock; + // Find the first item in the block. Increment by the size of (void *). + alignptr = (uintptr_t) (pathblock + 1); + // Align with item on an `alignbytes'-byte boundary. + pathitem = (void *) + (alignptr + (uintptr_t) alignbytes - + (alignptr % (uintptr_t) alignbytes)); + // Set the number of items left in the current block. + pathitemsleft = itemsperblock; +} + +//============================================================================// +// // +// traverse() Find the next item in the list. // +// // +// This routine is used in conjunction with traversalinit(). Be forewarned // +// that this routine successively returns all items in the list, including // +// deallocated ones on the deaditemqueue. It's up to you to figure out which // +// ones are actually dead. It can usually be done more space-efficiently by // +// a routine that knows something about the structure of the item. // +// // +//============================================================================// + +void* tetgenmesh::memorypool::traverse() +{ + void *newitem; + uintptr_t alignptr; + + // Stop upon exhausting the list of items. + if (pathitem == nextitem) { + return (void *) NULL; + } + // Check whether any untraversed items remain in the current block. + if (pathitemsleft == 0) { + // Find the next block. + pathblock = (void **) *pathblock; + // Find the first item in the block. Increment by the size of (void *). + alignptr = (uintptr_t) (pathblock + 1); + // Align with item on an `alignbytes'-byte boundary. + pathitem = (void *) + (alignptr + (uintptr_t) alignbytes - + (alignptr % (uintptr_t) alignbytes)); + // Set the number of items left in the current block. + pathitemsleft = itemsperblock; + } + newitem = pathitem; + // Find the next item in the block. + pathitem = (void *) ((uintptr_t) pathitem + itembytes); + pathitemsleft--; + return newitem; +} + +//============================================================================// +// // +// makeindex2pointmap() Create a map from index to vertices. // +// // +// 'idx2verlist' returns the created map. Traverse all vertices, a pointer // +// to each vertex is set into the array. The pointer to the first vertex is // +// saved in 'idx2verlist[in->firstnumber]'. // +// // +//============================================================================// + +void tetgenmesh::makeindex2pointmap(point*& idx2verlist) +{ + point pointloop; + int idx; + + if (b->verbose > 1) { + printf(" Constructing mapping from indices to points.\n"); + } + + idx2verlist = new point[points->items + 1]; + + points->traversalinit(); + pointloop = pointtraverse(); + idx = in->firstnumber; + while (pointloop != (point) NULL) { + idx2verlist[idx++] = pointloop; + pointloop = pointtraverse(); + } +} + +//============================================================================// +// // +// makesubfacemap() Create a map from vertex to subfaces incident at it. // +// // +// The map is returned in two arrays 'idx2faclist' and 'facperverlist'. All // +// subfaces incident at i-th vertex (i is counted from 0) are found in the // +// array facperverlist[j], where idx2faclist[i] <= j < idx2faclist[i + 1]. // +// Each entry in facperverlist[j] is a subface whose origin is the vertex. // +// // +// NOTE: These two arrays will be created inside this routine, don't forget // +// to free them after using. // +// // +//============================================================================// + +void tetgenmesh::makepoint2submap(memorypool* pool, int*& idx2faclist, + face*& facperverlist) +{ + face shloop; + int i, j, k; + + if (b->verbose > 1) { + printf(" Making a map from points to subfaces.\n"); + } + + // Initialize 'idx2faclist'. + idx2faclist = new int[points->items + 1]; + for (i = 0; i < points->items + 1; i++) idx2faclist[i] = 0; + + // Loop all subfaces, counter the number of subfaces incident at a vertex. + pool->traversalinit(); + shloop.sh = shellfacetraverse(pool); + while (shloop.sh != (shellface *) NULL) { + // Increment the number of incident subfaces for each vertex. + j = pointmark((point) shloop.sh[3]) - in->firstnumber; + idx2faclist[j]++; + j = pointmark((point) shloop.sh[4]) - in->firstnumber; + idx2faclist[j]++; + // Skip the third corner if it is a segment. + if (shloop.sh[5] != NULL) { + j = pointmark((point) shloop.sh[5]) - in->firstnumber; + idx2faclist[j]++; + } + shloop.sh = shellfacetraverse(pool); + } + + // Calculate the total length of array 'facperverlist'. + j = idx2faclist[0]; + idx2faclist[0] = 0; // Array starts from 0 element. + for (i = 0; i < points->items; i++) { + k = idx2faclist[i + 1]; + idx2faclist[i + 1] = idx2faclist[i] + j; + j = k; + } + + // The total length is in the last unit of idx2faclist. + facperverlist = new face[idx2faclist[i]]; + + // Loop all subfaces again, remember the subfaces at each vertex. + pool->traversalinit(); + shloop.sh = shellfacetraverse(pool); + while (shloop.sh != (shellface *) NULL) { + j = pointmark((point) shloop.sh[3]) - in->firstnumber; + shloop.shver = 0; // save the origin. + facperverlist[idx2faclist[j]] = shloop; + idx2faclist[j]++; + // Is it a subface or a subsegment? + if (shloop.sh[5] != NULL) { + j = pointmark((point) shloop.sh[4]) - in->firstnumber; + shloop.shver = 2; // save the origin. + facperverlist[idx2faclist[j]] = shloop; + idx2faclist[j]++; + j = pointmark((point) shloop.sh[5]) - in->firstnumber; + shloop.shver = 4; // save the origin. + facperverlist[idx2faclist[j]] = shloop; + idx2faclist[j]++; + } else { + j = pointmark((point) shloop.sh[4]) - in->firstnumber; + shloop.shver = 1; // save the origin. + facperverlist[idx2faclist[j]] = shloop; + idx2faclist[j]++; + } + shloop.sh = shellfacetraverse(pool); + } + + // Contents in 'idx2faclist' are shifted, now shift them back. + for (i = points->items - 1; i >= 0; i--) { + idx2faclist[i + 1] = idx2faclist[i]; + } + idx2faclist[0] = 0; +} + +//============================================================================// +// // +// tetrahedrondealloc() Deallocate space for a tet., marking it dead. // +// // +//============================================================================// + +void tetgenmesh::tetrahedrondealloc(tetrahedron *dyingtetrahedron) +{ + // Set tetrahedron's vertices to NULL. This makes it possible to detect + // dead tetrahedra when traversing the list of all tetrahedra. + dyingtetrahedron[4] = (tetrahedron) NULL; + + // Dealloc the space to subfaces/subsegments. + if (dyingtetrahedron[8] != NULL) { + tet2segpool->dealloc((shellface *) dyingtetrahedron[8]); + } + if (dyingtetrahedron[9] != NULL) { + tet2subpool->dealloc((shellface *) dyingtetrahedron[9]); + } + + tetrahedrons->dealloc((void *) dyingtetrahedron); +} + +//============================================================================// +// // +// tetrahedrontraverse() Traverse the tetrahedra, skipping dead ones. // +// // +//============================================================================// + +tetgenmesh::tetrahedron* tetgenmesh::tetrahedrontraverse() +{ + tetrahedron *newtetrahedron; + + do { + newtetrahedron = (tetrahedron *) tetrahedrons->traverse(); + if (newtetrahedron == (tetrahedron *) NULL) { + return (tetrahedron *) NULL; + } + } while ((newtetrahedron[4] == (tetrahedron) NULL) || + ((point) newtetrahedron[7] == dummypoint)); + return newtetrahedron; +} + +tetgenmesh::tetrahedron* tetgenmesh::alltetrahedrontraverse() +{ + tetrahedron *newtetrahedron; + + do { + newtetrahedron = (tetrahedron *) tetrahedrons->traverse(); + if (newtetrahedron == (tetrahedron *) NULL) { + return (tetrahedron *) NULL; + } + } while (newtetrahedron[4] == (tetrahedron) NULL); // Skip dead ones. + return newtetrahedron; +} + +//============================================================================// +// // +// shellfacedealloc() Deallocate space for a shellface, marking it dead. // +// Used both for dealloc a subface and subsegment. // +// // +//============================================================================// + +void tetgenmesh::shellfacedealloc(memorypool *pool, shellface *dyingsh) +{ + // Set shellface's vertices to NULL. This makes it possible to detect dead + // shellfaces when traversing the list of all shellfaces. + dyingsh[3] = (shellface) NULL; + pool->dealloc((void *) dyingsh); +} + +//============================================================================// +// // +// shellfacetraverse() Traverse the subfaces, skipping dead ones. Used // +// for both subfaces and subsegments pool traverse. // +// // +//============================================================================// + +tetgenmesh::shellface* tetgenmesh::shellfacetraverse(memorypool *pool) +{ + shellface *newshellface; + + do { + newshellface = (shellface *) pool->traverse(); + if (newshellface == (shellface *) NULL) { + return (shellface *) NULL; + } + } while (newshellface[3] == (shellface) NULL); // Skip dead ones. + return newshellface; +} + + +//============================================================================// +// // +// pointdealloc() Deallocate space for a point, marking it dead. // +// // +//============================================================================// + +void tetgenmesh::pointdealloc(point dyingpoint) +{ + // Mark the point as dead. This makes it possible to detect dead points + // when traversing the list of all points. + setpointtype(dyingpoint, DEADVERTEX); + points->dealloc((void *) dyingpoint); +} + +//============================================================================// +// // +// pointtraverse() Traverse the points, skipping dead ones. // +// // +//============================================================================// + +tetgenmesh::point tetgenmesh::pointtraverse() +{ + point newpoint; + + do { + newpoint = (point) points->traverse(); + if (newpoint == (point) NULL) { + return (point) NULL; + } + } while (pointtype(newpoint) == DEADVERTEX); // Skip dead ones. + return newpoint; +} + +//============================================================================// +// // +// maketetrahedron() Create a new tetrahedron. // +// // +//============================================================================// + +void tetgenmesh::maketetrahedron(triface *newtet) +{ + newtet->tet = (tetrahedron *) tetrahedrons->alloc(); + + // Initialize the four adjoining tetrahedra to be "outer space". + newtet->tet[0] = NULL; + newtet->tet[1] = NULL; + newtet->tet[2] = NULL; + newtet->tet[3] = NULL; + // Four NULL vertices. + newtet->tet[4] = NULL; + newtet->tet[5] = NULL; + newtet->tet[6] = NULL; + newtet->tet[7] = NULL; + // No attached segments and subfaces yet. + newtet->tet[8] = NULL; + newtet->tet[9] = NULL; + + newtet->tet[10] = NULL; // used by mesh improvement + + // Init the volume to be zero. + //REAL *polar = get_polar(newtet->tet); + //polar[4] = 0.0; + // Initialize the marker (clear all flags). + setelemmarker(newtet->tet, 0); + for (int i = 0; i < numelemattrib; i++) { + setelemattribute(newtet->tet, i, 0.0); + } + if (b->varvolume) { + setvolumebound(newtet->tet, -1.0); + } + + // Initialize the version to be Zero. + newtet->ver = 11; +} + +void tetgenmesh::maketetrahedron2(triface* newtet, point pa, point pb, + point pc, point pd) +{ + newtet->tet = (tetrahedron *) tetrahedrons->alloc(); + + // Initialize the four adjoining tetrahedra to be "outer space". + newtet->tet[0] = NULL; + newtet->tet[1] = NULL; + newtet->tet[2] = NULL; + newtet->tet[3] = NULL; + // Set four vertices. + newtet->tet[4] = (tetrahedron) pa; + newtet->tet[5] = (tetrahedron) pb; + newtet->tet[6] = (tetrahedron) pc; + newtet->tet[7] = (tetrahedron) pd; // may be dummypoint + // No attached segments and subfaces yet. + newtet->tet[8] = NULL; + newtet->tet[9] = NULL; + + newtet->tet[10] = NULL; // used by mesh improvement + + + // Initialize the marker (clear all flags). + setelemmarker(newtet->tet, 0); + for (int i = 0; i < numelemattrib; i++) { + setelemattribute(newtet->tet, i, 0.0); + } + if (b->varvolume) { + setvolumebound(newtet->tet, -1.0); + } + + // Initialize the version to be Zero. + newtet->ver = 11; +} + +//============================================================================// +// // +// makeshellface() Create a new shellface with version zero. Used for // +// both subfaces and subsegments. // +// // +//============================================================================// + +void tetgenmesh::makeshellface(memorypool *pool, face *newface) +{ + newface->sh = (shellface *) pool->alloc(); + + // No adjointing subfaces. + newface->sh[0] = NULL; + newface->sh[1] = NULL; + newface->sh[2] = NULL; + // Three NULL vertices. + newface->sh[3] = NULL; + newface->sh[4] = NULL; + newface->sh[5] = NULL; + // No adjoining subsegments. + newface->sh[6] = NULL; + newface->sh[7] = NULL; + newface->sh[8] = NULL; + // No adjoining tetrahedra. + newface->sh[9] = NULL; + newface->sh[10] = NULL; + if (checkconstraints) { + // Initialize the maximum area bound. + setareabound(*newface, 0.0); + } + // Set the boundary marker to zero. + setshellmark(*newface, 0); + // Clear the infection and marktest bits. + ((int *) (newface->sh))[shmarkindex + 1] = 0; + if (useinsertradius) { + setfacetindex(*newface, 0); + } + + newface->shver = 0; +} + +//============================================================================// +// // +// makepoint() Create a new point. // +// // +//============================================================================// + +void tetgenmesh::makepoint(point* pnewpoint, enum verttype vtype) +{ + int i; + + *pnewpoint = (point) points->alloc(); + + // Initialize the point attributes. + for (i = 0; i < numpointattrib; i++) { + (*pnewpoint)[3 + i] = 0.0; + } + // Initialize the metric tensor. + for (i = 0; i < sizeoftensor; i++) { + (*pnewpoint)[pointmtrindex + i] = 0.0; + } + setpoint2tet(*pnewpoint, NULL); + setpoint2ppt(*pnewpoint, NULL); + if (b->plc || b->refine) { + // Initialize the point-to-simplex field. + setpoint2sh(*pnewpoint, NULL); + if (b->metric && (bgm != NULL)) { + setpoint2bgmtet(*pnewpoint, NULL); + } + } + // Initialize the point marker (starting from in->firstnumber). + setpointmark(*pnewpoint, (int) (points->items) - (!in->firstnumber)); + // Clear all flags. + ((int *) (*pnewpoint))[pointmarkindex + 1] = 0; + // Initialize (set) the point type. + setpointtype(*pnewpoint, vtype); +} + +//============================================================================// +// // +// initializepools() Calculate the sizes of the point, tetrahedron, and // +// subface. Initialize their memory pools. // +// // +// This routine also computes the indices 'pointmarkindex', 'point2simindex', // +// 'point2pbcptindex', 'elemattribindex', and 'volumeboundindex'. They are // +// used to find values within each point and tetrahedron, respectively. // +// // +//============================================================================// + +void tetgenmesh::initializepools() +{ + int pointsize = 0, elesize = 0, shsize = 0; + int i; + + if (b->verbose) { + printf(" Initializing memorypools.\n"); + printf(" tetrahedron per block: %d.\n", b->tetrahedraperblock); + } + + inittables(); + + // There are three input point lists available, which are in, addin, + // and bgm->in. These point lists may have different number of + // attributes. Decide the maximum number. + numpointattrib = in->numberofpointattributes; + if (bgm != NULL) { + if (bgm->in->numberofpointattributes > numpointattrib) { + numpointattrib = bgm->in->numberofpointattributes; + } + } + if (addin != NULL) { + if (addin->numberofpointattributes > numpointattrib) { + numpointattrib = addin->numberofpointattributes; + } + } + if (b->weighted || b->flipinsert) { // -w or -L. + // The internal number of point attribute needs to be at least 1 + // (for storing point weights). + if (numpointattrib == 0) { + numpointattrib = 1; + } + } + + // Default varconstraint = 0; + if (in->segmentconstraintlist || in->facetconstraintlist) { + checkconstraints = 1; + } + if (b->plc || b->refine || b->quality) { + // Save the insertion radius for Steiner points if boundaries + // are allowed be split. + //if (!b->nobisect || checkconstraints) { + useinsertradius = 1; + //} + } + + // The index within each point at which its metric tensor is found. + // Each vertex has three coordinates. + if (b->psc) { + // '-s' option (PSC), the u,v coordinates are provided. + pointmtrindex = 5 + numpointattrib; + // The index within each point at which its u, v coordinates are found. + // Comment: They are saved after the list of point attributes. + pointparamindex = pointmtrindex - 2; + } else { + pointmtrindex = 3 + numpointattrib; + } + // For '-m' option. A tensor field is provided (*.mtr or *.b.mtr file). + if (b->metric) { + // Decide the size (1, 3, or 6) of the metric tensor. + if (bgm != (tetgenmesh *) NULL) { + // A background mesh is allocated. It may not exist though. + sizeoftensor = (bgm->in != (tetgenio *) NULL) ? + bgm->in->numberofpointmtrs : in->numberofpointmtrs; + } else { + // No given background mesh - Itself is a background mesh. + sizeoftensor = in->numberofpointmtrs; + } + // Make sure sizeoftensor is at least 1. + sizeoftensor = (sizeoftensor > 0) ? sizeoftensor : 1; + } else { + // For '-q' option. Make sure to have space for saving a scalar value. + sizeoftensor = b->quality ? 1 : 0; + } + if (useinsertradius) { + // Increase a space (REAL) for saving point insertion radius, it is + // saved directly after the metric. + sizeoftensor++; + } + pointinsradiusindex = pointmtrindex + sizeoftensor - 1; + // The index within each point at which an element pointer is found, where + // the index is measured in pointers. Ensure the index is aligned to a + // sizeof(tetrahedron)-byte address. + point2simindex = ((pointmtrindex + sizeoftensor) * sizeof(REAL) + + sizeof(tetrahedron) - 1) / sizeof(tetrahedron); + if (b->plc || b->refine /*|| b->voroout*/) { + // Increase the point size by three pointers, which are: + // - a pointer to a tet, read by point2tet(); + // - a pointer to a parent point, read by point2ppt()). + // - a pointer to a subface or segment, read by point2sh(); + if (b->metric && (bgm != (tetgenmesh *) NULL)) { + // Increase one pointer into the background mesh, point2bgmtet(). + pointsize = (point2simindex + 4) * sizeof(tetrahedron); + } else { + pointsize = (point2simindex + 3) * sizeof(tetrahedron); + } + } else { + // Increase the point size by two pointer, which are: + // - a pointer to a tet, read by point2tet(); + // - a pointer to a parent point, read by point2ppt()). -- Used by btree. + pointsize = (point2simindex + 2) * sizeof(tetrahedron); + } + // The index within each point at which the boundary marker is found, + // Ensure the point marker is aligned to a sizeof(int)-byte address. + pointmarkindex = (pointsize + sizeof(int) - 1) / sizeof(int); + // Now point size is the ints (indicated by pointmarkindex) plus: + // - an integer for boundary marker; + // - an integer for vertex type; + // - an integer for local index (for vertex insertion) + pointsize = (pointmarkindex + 3) * sizeof(tetrahedron); + + // Initialize the pool of vertices. + points = new memorypool(pointsize, b->vertexperblock, sizeof(REAL), 0); + + if (b->verbose) { + printf(" Size of a point: %d bytes.\n", points->itembytes); + } + + // Initialize the infinite vertex. + dummypoint = (point) new char[pointsize]; + // Initialize all fields of this point. + dummypoint[0] = 0.0; + dummypoint[1] = 0.0; + dummypoint[2] = 0.0; + for (i = 0; i < numpointattrib; i++) { + dummypoint[3 + i] = 0.0; + } + // Initialize the metric tensor. + for (i = 0; i < sizeoftensor; i++) { + dummypoint[pointmtrindex + i] = 0.0; + } + setpoint2tet(dummypoint, NULL); + setpoint2ppt(dummypoint, NULL); + if (b->plc || b->psc || b->refine) { + // Initialize the point-to-simplex field. + setpoint2sh(dummypoint, NULL); + if (b->metric && (bgm != NULL)) { + setpoint2bgmtet(dummypoint, NULL); + } + } + // Initialize the point marker (starting from in->firstnumber). + setpointmark(dummypoint, -1); // The unique marker for dummypoint. + // Clear all flags. + ((int *) (dummypoint))[pointmarkindex + 1] = 0; + // Initialize (set) the point type. + setpointtype(dummypoint, UNUSEDVERTEX); // Does not matter. + + // The number of bytes occupied by a tetrahedron is varying by the user- + // specified options. The contents of the first 12 pointers are listed + // in the following table: + // [0] |__ neighbor at f0 __| + // [1] |__ neighbor at f1 __| + // [2] |__ neighbor at f2 __| + // [3] |__ neighbor at f3 __| + // [4] |_____ vertex p0 ____| + // [5] |_____ vertex p1 ____| + // [6] |_____ vertex p2 ____| + // [7] |_____ vertex p3 ____| + // [8] |__ segments array __| (used by -p) + // [9] |__ subfaces array __| (used by -p) + // [10] |_____ reserved _____| + // [11] |___ elem marker ____| (used as an integer) + + elesize = 12 * sizeof(tetrahedron); + + // The index to find the element markers. An integer containing varies + // flags and element counter. + if (!(sizeof(int) <= sizeof(tetrahedron)) || + ((sizeof(tetrahedron) % sizeof(int)))) { + terminatetetgen(this, 2); + } + elemmarkerindex = (elesize - sizeof(tetrahedron)) / sizeof(int); + + // Let (cx, cy, cz) be the circumcenter of this element, r be the radius + // of the circumsphere, and V be the (positive) volume of this element. + // We save the following five values: + // 2*cx, 2*cy, 2*cz, cx^2 + cy^2 + cz^2 - r^2 (height), 6*v. + // where the first four values define the polar plane of this element, + // the fifth value should be postive. It is used to guard the correctness + // of the polar plane. Otherwise, use exact arithmetics to calculate. + + // The index within each element which its polar parameters are found, + // this index is measured in REALs. + polarindex = (elesize + sizeof(REAL) - 1) / sizeof(REAL); + // The index within each element at which its attributes are found, where + // the index is measured in REALs. + //elemattribindex = (elesize + sizeof(REAL) - 1) / sizeof(REAL); + elemattribindex = polarindex; // polarindex + 5; + // The actual number of element attributes. Note that if the + // `b->regionattrib' flag is set, an additional attribute will be added. + numelemattrib = in->numberoftetrahedronattributes + (b->regionattrib > 0); + // The index within each element at which the maximum volume bound is + // found, where the index is measured in REALs. + volumeboundindex = elemattribindex + numelemattrib; + // If element attributes or an constraint are needed, increase the number + // of bytes occupied by an element. + if (!b->varvolume) { + if (b->refine && (in->refine_elem_list != NULL)) { + b->varvolume = 1; // refine a given element list. + } + } + if (b->varvolume) { + elesize = (volumeboundindex + 1) * sizeof(REAL); + } else { + elesize = volumeboundindex * sizeof(REAL); + } + + + // Having determined the memory size of an element, initialize the pool. + tetrahedrons = new memorypool(elesize, b->tetrahedraperblock, sizeof(void *), + 16); + + if (b->verbose) { + printf(" Size of a tetrahedron: %d (%d) bytes.\n", elesize, + tetrahedrons->itembytes); + } + + if (b->plc || b->refine) { // if (b->useshelles) { + // The number of bytes occupied by a subface. The list of pointers + // stored in a subface are: three to other subfaces, three to corners, + // three to subsegments, two to tetrahedra. + shsize = 11 * sizeof(shellface); + // The index within each subface at which the maximum area bound is + // found, where the index is measured in REALs. + areaboundindex = (shsize + sizeof(REAL) - 1) / sizeof(REAL); + // If -q switch is in use, increase the number of bytes occupied by + // a subface for saving maximum area bound. + if (checkconstraints) { + shsize = (areaboundindex + 1) * sizeof(REAL); + } else { + shsize = areaboundindex * sizeof(REAL); + } + // The index within subface at which the facet marker is found. Ensure + // the marker is aligned to a sizeof(int)-byte address. + shmarkindex = (shsize + sizeof(int) - 1) / sizeof(int); + // Increase the number of bytes by two or three integers, one for facet + // marker, one for shellface type and flags, and optionally one + // for storing facet index (for mesh refinement). + shsize = (shmarkindex + 2 + useinsertradius) * sizeof(shellface); + + // Initialize the pool of subfaces. Each subface record is eight-byte + // aligned so it has room to store an edge version (from 0 to 5) in + // the least three bits. + subfaces = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); + + if (b->verbose) { + printf(" Size of a shellface: %d (%d) bytes.\n", shsize, + subfaces->itembytes); + } + + // Initialize the pool of subsegments. The subsegment's record is same + // with subface. + subsegs = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); + + // Initialize the pool for tet-subseg connections. + tet2segpool = new memorypool(6 * sizeof(shellface), b->shellfaceperblock, + sizeof(void *), 0); + // Initialize the pool for tet-subface connections. + tet2subpool = new memorypool(4 * sizeof(shellface), b->shellfaceperblock, + sizeof(void *), 0); + + // Initialize arraypools for segment & facet recovery. + subsegstack = new arraypool(sizeof(face), 10); + subfacstack = new arraypool(sizeof(face), 10); + subvertstack = new arraypool(sizeof(point), 8); + + // Initialize arraypools for surface point insertion/deletion. + caveshlist = new arraypool(sizeof(face), 8); + caveshbdlist = new arraypool(sizeof(face), 8); + cavesegshlist = new arraypool(sizeof(face), 4); + + cavetetshlist = new arraypool(sizeof(face), 8); + cavetetseglist = new arraypool(sizeof(face), 8); + caveencshlist = new arraypool(sizeof(face), 8); + caveencseglist = new arraypool(sizeof(face), 8); + } + + // Initialize the pools for flips. + flippool = new memorypool(sizeof(badface), 1024, sizeof(void *), 0); + later_unflip_queue = new arraypool(sizeof(badface), 10); + unflipqueue = new arraypool(sizeof(badface), 10); + + // Initialize the arraypools for point insertion. + cavetetlist = new arraypool(sizeof(triface), 10); + cavebdrylist = new arraypool(sizeof(triface), 10); + caveoldtetlist = new arraypool(sizeof(triface), 10); + cavetetvertlist = new arraypool(sizeof(point), 10); + cave_oldtet_list = new arraypool(sizeof(tetrahedron*), 10); +} + +// // +// // +//== mempool_cxx =============================================================// + +//== geom_cxx ================================================================// +// // +// // + +// PI is the ratio of a circle's circumference to its diameter. +REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; + +//============================================================================// +// // +// insphere_s() Insphere test with symbolic perturbation. // +// // +// Given four points pa, pb, pc, and pd, test if the point pe lies inside or // +// outside the circumscribed sphere of the four points. // +// // +// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // +// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // +// points pa, pb, and pc. Otherwise, the returned sign is flipped. // +// // +// Return a positive value (> 0) if pe lies inside, a negative value (< 0) // +// if pe lies outside the sphere, the returned value will not be zero. // +// // +//============================================================================// + +REAL tetgenmesh::insphere_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe) +{ + REAL sign; + + sign = insphere(pa, pb, pc, pd, pe); + if (sign != 0.0) { + return sign; + } + + // Symbolic perturbation. + point pt[5], swappt; + REAL oriA, oriB; + int swaps, count; + int n, i; + + pt[0] = pa; + pt[1] = pb; + pt[2] = pc; + pt[3] = pd; + pt[4] = pe; + + // Sort the five points such that their indices are in the increasing + // order. An optimized bubble sort algorithm is used, i.e., it has + // the worst case O(n^2) runtime, but it is usually much faster. + swaps = 0; // Record the total number of swaps. + n = 5; + do { + count = 0; + n = n - 1; + for (i = 0; i < n; i++) { + if (pointmark(pt[i]) > pointmark(pt[i+1])) { + swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; + count++; + } + } + swaps += count; + } while (count > 0); // Continue if some points are swapped. + + oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); + if (oriA != 0.0) { + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriA = -oriA; + return oriA; + } + + oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); + if (oriB == 0.0) { + terminatetetgen(this, 2); + } + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriB = -oriB; + return oriB; +} + +//============================================================================// +// // +// orient4d_s() 4d orientation test with symbolic perturbation. // +// // +// Given four lifted points pa', pb', pc', and pd' in R^4,test if the lifted // +// point pe' in R^4 lies below or above the hyperplane passing through the // +// four points pa', pb', pc', and pd'. // +// // +// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // +// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // +// the points pa, pb, and pc. Otherwise, the returned sign is flipped. // +// // +// Return a positive value (> 0) if pe' lies below, a negative value (< 0) // +// if pe' lies above the hyperplane, the returned value should not be zero. // +// // +//============================================================================// + +REAL tetgenmesh::orient4d_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, + REAL dheight, REAL eheight) +{ + REAL sign; + + sign = orient4d(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight); + if (sign != 0.0) { + return sign; + } + + // Symbolic perturbation. + point pt[5], swappt; + REAL oriA, oriB; + int swaps, count; + int n, i; + + pt[0] = pa; + pt[1] = pb; + pt[2] = pc; + pt[3] = pd; + pt[4] = pe; + + // Sort the five points such that their indices are in the increasing + // order. An optimized bubble sort algorithm is used, i.e., it has + // the worst case O(n^2) runtime, but it is usually much faster. + swaps = 0; // Record the total number of swaps. + n = 5; + do { + count = 0; + n = n - 1; + for (i = 0; i < n; i++) { + if (pointmark(pt[i]) > pointmark(pt[i+1])) { + swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; + count++; + } + } + swaps += count; + } while (count > 0); // Continue if some points are swapped. + + oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); + if (oriA != 0.0) { + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriA = -oriA; + return oriA; + } + + oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); + if (oriB == 0.0) { + terminatetetgen(this, 2); + } + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriB = -oriB; + return oriB; +} + +//============================================================================// +// // +// tri_edge_test() Triangle-edge intersection test. // +// // +// This routine takes a triangle T (with vertices A, B, C) and an edge E (P, // +// Q) in 3D, and tests if they intersect each other. // +// // +// If the point 'R' is not NULL, it lies strictly above the plane defined by // +// A, B, C. It is used in test when T and E are coplanar. // +// // +// If T and E intersect each other, they may intersect in different ways. If // +// 'level' > 0, their intersection type will be reported 'types' and 'pos'. // +// // +// The return value indicates one of the following cases: // +// - 0, T and E are disjoint. // +// - 1, T and E intersect each other. // +// - 2, T and E are not coplanar. They intersect at a single point. // +// - 4, T and E are coplanar. They intersect at a single point or a line // +// segment (if types[1] != DISJOINT). // +// // +//============================================================================// + +#define SETVECTOR3(V, a0, a1, a2) (V)[0] = (a0); (V)[1] = (a1); (V)[2] = (a2) + +#define SWAP2(a0, a1, tmp) (tmp) = (a0); (a0) = (a1); (a1) = (tmp) + +int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, + point R, int level, int *types, int *pos) +{ + point U[3], V[3]; // The permuted vectors of points. + int pu[3], pv[3]; // The original positions of points. + REAL abovept[3]; + REAL sA, sB, sC; + REAL s1, s2, s3, s4; + int z1; + + if (R == NULL) { + // Calculate a lift point. + if (1) { + REAL n[3], len; + // Calculate a lift point, saved in dummypoint. + facenormal(A, B, C, n, 1, NULL); + len = sqrt(dot(n, n)); + if (len != 0) { + n[0] /= len; + n[1] /= len; + n[2] /= len; + len = distance(A, B); + len += distance(B, C); + len += distance(C, A); + len /= 3.0; + R = abovept; //dummypoint; + R[0] = A[0] + len * n[0]; + R[1] = A[1] + len * n[1]; + R[2] = A[2] + len * n[2]; + } else { + // The triangle [A,B,C] is (nearly) degenerate, i.e., it is (close) + // to a line. We need a line-line intersection test. + // !!! A non-save return value.!!! + return 0; // DISJOINT + } + } + } + + // Test A's, B's, and C's orientations wrt plane PQR. + sA = orient3d(P, Q, R, A); + sB = orient3d(P, Q, R, B); + sC = orient3d(P, Q, R, C); + + + if (sA < 0) { + if (sB < 0) { + if (sC < 0) { // (---). + return 0; + } else { + if (sC > 0) { // (--+). + // All points are in the right positions. + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 0; + } else { // (--0). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 1; + } + } + } else { + if (sB > 0) { + if (sC < 0) { // (-+-). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 0, 1, 2); + z1 = 0; + } else { + if (sC > 0) { // (-++). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 1, 0, 2); + z1 = 0; + } else { // (-+0). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 0, 1, 2); + z1 = 2; + } + } + } else { + if (sC < 0) { // (-0-). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 0, 1, 2); + z1 = 1; + } else { + if (sC > 0) { // (-0+). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 1, 0, 2); + z1 = 2; + } else { // (-00). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 1, 0, 2); + z1 = 3; + } + } + } + } + } else { + if (sA > 0) { + if (sB < 0) { + if (sC < 0) { // (+--). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 0, 1, 2); + z1 = 0; + } else { + if (sC > 0) { // (+-+). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 1, 0, 2); + z1 = 0; + } else { // (+-0). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 1, 0, 2); + z1 = 2; + } + } + } else { + if (sB > 0) { + if (sC < 0) { // (++-). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 0; + } else { + if (sC > 0) { // (+++). + return 0; + } else { // (++0). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 1; + } + } + } else { // (+0#) + if (sC < 0) { // (+0-). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 0, 1, 2); + z1 = 2; + } else { + if (sC > 0) { // (+0+). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 1, 0, 2); + z1 = 1; + } else { // (+00). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 0, 1, 2); + z1 = 3; + } + } + } + } + } else { + if (sB < 0) { + if (sC < 0) { // (0--). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 0, 1, 2); + z1 = 1; + } else { + if (sC > 0) { // (0-+). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 2; + } else { // (0-0). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 1, 0, 2); + z1 = 3; + } + } + } else { + if (sB > 0) { + if (sC < 0) { // (0+-). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 2; + } else { + if (sC > 0) { // (0++). + SETVECTOR3(U, B, C, A); // PT = ST x ST + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 1, 2, 0); + SETVECTOR3(pv, 1, 0, 2); + z1 = 1; + } else { // (0+0). + SETVECTOR3(U, C, A, B); // PT = ST + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 2, 0, 1); + SETVECTOR3(pv, 0, 1, 2); + z1 = 3; + } + } + } else { // (00#) + if (sC < 0) { // (00-). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, Q, P, R); // PL = SL + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 3; + } else { + if (sC > 0) { // (00+). + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 3; + } else { // (000) + // Not possible unless ABC is degenerate. + // Avoiding compiler warnings. + SETVECTOR3(U, A, B, C); // I3 + SETVECTOR3(V, P, Q, R); // I2 + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 4; + } + } + } + } + } + } + + s1 = orient3d(U[0], U[2], R, V[1]); // A, C, R, Q + s2 = orient3d(U[1], U[2], R, V[0]); // B, C, R, P + + if (s1 > 0) { + return 0; + } + if (s2 < 0) { + return 0; + } + + if (level == 0) { + return 1; // They are intersected. + } + + + if (z1 == 1) { + if (s1 == 0) { // (0###) + // C = Q. + types[0] = (int) SHAREVERT; + pos[0] = pu[2]; // C + pos[1] = pv[1]; // Q + types[1] = (int) DISJOINT; + } else { + if (s2 == 0) { // (#0##) + // C = P. + types[0] = (int) SHAREVERT; + pos[0] = pu[2]; // C + pos[1] = pv[0]; // P + types[1] = (int) DISJOINT; + } else { // (-+##) + // C in [P, Q]. + types[0] = (int) ACROSSVERT; + pos[0] = pu[2]; // C + pos[1] = pv[0]; // [P, Q] + types[1] = (int) DISJOINT; + } + } + return 4; + } + + s3 = orient3d(U[0], U[2], R, V[0]); // A, C, R, P + s4 = orient3d(U[1], U[2], R, V[1]); // B, C, R, Q + + if (z1 == 0) { // (tritri-03) + if (s1 < 0) { + if (s3 > 0) { + if (s4 > 0) { + // [P, Q] overlaps [k, l] (-+++). + types[0] = (int) ACROSSEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // [P, Q] + types[1] = (int) TOUCHFACE; + pos[2] = 3; // [A, B, C] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = l, [P, Q] contains [k, l] (-++0). + types[0] = (int) ACROSSEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // [P, Q] + types[1] = (int) TOUCHEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] contains [k, l] (-++-). + types[0] = (int) ACROSSEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // [P, Q] + types[1] = (int) ACROSSEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[0]; // [P, Q] + } + } + } else { + if (s3 == 0) { + if (s4 > 0) { + // P = k, [P, Q] in [k, l] (-+0+). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // P + types[1] = (int) TOUCHFACE; + pos[2] = 3; // [A, B, C] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // [P, Q] = [k, l] (-+00). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // P + types[1] = (int) TOUCHEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[1]; // Q + } else { + // P = k, [P, Q] contains [k, l] (-+0-). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[0]; // P + types[1] = (int) ACROSSEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[0]; // [P, Q] + } + } + } else { // s3 < 0 + if (s2 > 0) { + if (s4 > 0) { + // [P, Q] in [k, l] (-+-+). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[1] = (int) TOUCHFACE; + pos[2] = 3; // [A, B, C] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = l, [P, Q] in [k, l] (-+-0). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[1] = (int) TOUCHEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] overlaps [k, l] (-+--). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[1] = (int) ACROSSEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[0]; // [P, Q] + } + } + } else { // s2 == 0 + // P = l (#0##). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = pv[0]; // P + types[1] = (int) DISJOINT; + } + } + } + } else { // s1 == 0 + // Q = k (0####) + types[0] = (int) TOUCHEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[1]; // Q + types[1] = (int) DISJOINT; + } + } else if (z1 == 2) { // (tritri-23) + if (s1 < 0) { + if (s3 > 0) { + if (s4 > 0) { + // [P, Q] overlaps [A, l] (-+++). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) TOUCHFACE; + pos[2] = 3; // [A, B, C] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = l, [P, Q] contains [A, l] (-++0). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) TOUCHEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] contains [A, l] (-++-). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) ACROSSEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[0]; // [P, Q] + } + } + } else { + if (s3 == 0) { + if (s4 > 0) { + // P = A, [P, Q] in [A, l] (-+0+). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // P + types[1] = (int) TOUCHFACE; + pos[2] = 3; // [A, B, C] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // [P, Q] = [A, l] (-+00). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // P + types[1] = (int) TOUCHEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // Q = l, [P, Q] in [A, l] (-+0-). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // P + types[1] = (int) ACROSSEDGE; + pos[2] = pu[1]; // [B, C] + pos[3] = pv[0]; // [P, Q] + } + } + } else { // s3 < 0 + if (s2 > 0) { + if (s4 > 0) { + // [P, Q] in [A, l] (-+-+). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = l, [P, Q] in [A, l] (-+-0). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[0] = (int) TOUCHEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] overlaps [A, l] (-+--). + types[0] = (int) TOUCHFACE; + pos[0] = 3; // [A, B, C] + pos[1] = pv[0]; // P + types[0] = (int) ACROSSEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = pv[0]; // [P, Q] + } + } + } else { // s2 == 0 + // P = l (#0##). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = pv[0]; // P + types[1] = (int) DISJOINT; + } + } + } + } else { // s1 == 0 + // Q = A (0###). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[1]; // Q + types[1] = (int) DISJOINT; + } + } else if (z1 == 3) { // (tritri-33) + if (s1 < 0) { + if (s3 > 0) { + if (s4 > 0) { + // [P, Q] overlaps [A, B] (-+++). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) TOUCHEDGE; + pos[2] = pu[0]; // [A, B] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = B, [P, Q] contains [A, B] (-++0). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) SHAREVERT; + pos[2] = pu[1]; // B + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] contains [A, B] (-++-). + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // [P, Q] + types[1] = (int) ACROSSVERT; + pos[2] = pu[1]; // B + pos[3] = pv[0]; // [P, Q] + } + } + } else { + if (s3 == 0) { + if (s4 > 0) { + // P = A, [P, Q] in [A, B] (-+0+). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // P + types[1] = (int) TOUCHEDGE; + pos[2] = pu[0]; // [A, B] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // [P, Q] = [A, B] (-+00). + types[0] = (int) SHAREEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = pv[0]; // [P, Q] + types[1] = (int) DISJOINT; + } else { // s4 < 0 + // P= A, [P, Q] in [A, B] (-+0-). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[0]; // P + types[1] = (int) ACROSSVERT; + pos[2] = pu[1]; // B + pos[3] = pv[0]; // [P, Q] + } + } + } else { // s3 < 0 + if (s2 > 0) { + if (s4 > 0) { + // [P, Q] in [A, B] (-+-+). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = pv[0]; // P + types[1] = (int) TOUCHEDGE; + pos[2] = pu[0]; // [A, B] + pos[3] = pv[1]; // Q + } else { + if (s4 == 0) { + // Q = B, [P, Q] in [A, B] (-+-0). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = pv[0]; // P + types[1] = (int) SHAREVERT; + pos[2] = pu[1]; // B + pos[3] = pv[1]; // Q + } else { // s4 < 0 + // [P, Q] overlaps [A, B] (-+--). + types[0] = (int) TOUCHEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = pv[0]; // P + types[1] = (int) ACROSSVERT; + pos[2] = pu[1]; // B + pos[3] = pv[0]; // [P, Q] + } + } + } else { // s2 == 0 + // P = B (#0##). + types[0] = (int) SHAREVERT; + pos[0] = pu[1]; // B + pos[1] = pv[0]; // P + types[1] = (int) DISJOINT; + } + } + } + } else { // s1 == 0 + // Q = A (0###). + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[1]; // Q + types[1] = (int) DISJOINT; + } + } + + return 4; +} + +int tetgenmesh::tri_edge_tail(point A,point B,point C,point P,point Q,point R, + REAL sP,REAL sQ,int level,int *types,int *pos) +{ + point U[3], V[3]; //, Ptmp; + int pu[3], pv[3]; //, itmp; + REAL s1, s2, s3; + int z1; + + + if (sP < 0) { + if (sQ < 0) { // (--) disjoint + return 0; + } else { + if (sQ > 0) { // (-+) + SETVECTOR3(U, A, B, C); + SETVECTOR3(V, P, Q, R); + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 0; + } else { // (-0) + SETVECTOR3(U, A, B, C); + SETVECTOR3(V, P, Q, R); + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 1; + } + } + } else { + if (sP > 0) { // (+-) + if (sQ < 0) { + SETVECTOR3(U, A, B, C); + SETVECTOR3(V, Q, P, R); // P and Q are flipped. + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 0; + } else { + if (sQ > 0) { // (++) disjoint + return 0; + } else { // (+0) + SETVECTOR3(U, B, A, C); // A and B are flipped. + SETVECTOR3(V, P, Q, R); + SETVECTOR3(pu, 1, 0, 2); + SETVECTOR3(pv, 0, 1, 2); + z1 = 1; + } + } + } else { // sP == 0 + if (sQ < 0) { // (0-) + SETVECTOR3(U, A, B, C); + SETVECTOR3(V, Q, P, R); // P and Q are flipped. + SETVECTOR3(pu, 0, 1, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 1; + } else { + if (sQ > 0) { // (0+) + SETVECTOR3(U, B, A, C); // A and B are flipped. + SETVECTOR3(V, Q, P, R); // P and Q are flipped. + SETVECTOR3(pu, 1, 0, 2); + SETVECTOR3(pv, 1, 0, 2); + z1 = 1; + } else { // (00) + // A, B, C, P, and Q are coplanar. + z1 = 2; + } + } + } + } + + if (z1 == 2) { + // The triangle and the edge are coplanar. + return tri_edge_2d(A, B, C, P, Q, R, level, types, pos); + } + + s1 = orient3d(U[0], U[1], V[0], V[1]); + if (s1 < 0) { + return 0; + } + + s2 = orient3d(U[1], U[2], V[0], V[1]); + if (s2 < 0) { + return 0; + } + + s3 = orient3d(U[2], U[0], V[0], V[1]); + if (s3 < 0) { + return 0; + } + + if (level == 0) { + return 1; // The are intersected. + } + + types[1] = (int) DISJOINT; // No second intersection point. + + if (z1 == 0) { + if (s1 > 0) { + if (s2 > 0) { + if (s3 > 0) { // (+++) + // [P, Q] passes interior of [A, B, C]. + types[0] = (int) ACROSSFACE; + pos[0] = 3; // interior of [A, B, C] + pos[1] = 0; // [P, Q] + } else { // s3 == 0 (++0) + // [P, Q] intersects [C, A]. + types[0] = (int) ACROSSEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = 0; // [P, Q] + } + } else { // s2 == 0 + if (s3 > 0) { // (+0+) + // [P, Q] intersects [B, C]. + types[0] = (int) ACROSSEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = 0; // [P, Q] + } else { // s3 == 0 (+00) + // [P, Q] passes C. + types[0] = (int) ACROSSVERT; + pos[0] = pu[2]; // C + pos[1] = 0; // [P, Q] + } + } + } else { // s1 == 0 + if (s2 > 0) { + if (s3 > 0) { // (0++) + // [P, Q] intersects [A, B]. + types[0] = (int) ACROSSEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = 0; // [P, Q] + } else { // s3 == 0 (0+0) + // [P, Q] passes A. + types[0] = (int) ACROSSVERT; + pos[0] = pu[0]; // A + pos[1] = 0; // [P, Q] + } + } else { // s2 == 0 + if (s3 > 0) { // (00+) + // [P, Q] passes B. + types[0] = (int) ACROSSVERT; + pos[0] = pu[1]; // B + pos[1] = 0; // [P, Q] + } + } + } + } else { // z1 == 1 + if (s1 > 0) { + if (s2 > 0) { + if (s3 > 0) { // (+++) + // Q lies in [A, B, C]. + types[0] = (int) TOUCHFACE; + pos[0] = 0; // [A, B, C] + pos[1] = pv[1]; // Q + } else { // s3 == 0 (++0) + // Q lies on [C, A]. + types[0] = (int) TOUCHEDGE; + pos[0] = pu[2]; // [C, A] + pos[1] = pv[1]; // Q + } + } else { // s2 == 0 + if (s3 > 0) { // (+0+) + // Q lies on [B, C]. + types[0] = (int) TOUCHEDGE; + pos[0] = pu[1]; // [B, C] + pos[1] = pv[1]; // Q + } else { // s3 == 0 (+00) + // Q = C. + types[0] = (int) SHAREVERT; + pos[0] = pu[2]; // C + pos[1] = pv[1]; // Q + } + } + } else { // s1 == 0 + if (s2 > 0) { + if (s3 > 0) { // (0++) + // Q lies on [A, B]. + types[0] = (int) TOUCHEDGE; + pos[0] = pu[0]; // [A, B] + pos[1] = pv[1]; // Q + } else { // s3 == 0 (0+0) + // Q = A. + types[0] = (int) SHAREVERT; + pos[0] = pu[0]; // A + pos[1] = pv[1]; // Q + } + } else { // s2 == 0 + if (s3 > 0) { // (00+) + // Q = B. + types[0] = (int) SHAREVERT; + pos[0] = pu[1]; // B + pos[1] = pv[1]; // Q + } + } + } + } + + // T and E intersect in a single point. + return 2; +} + +int tetgenmesh::tri_edge_test(point A, point B, point C, point P, point Q, + point R, int level, int *types, int *pos) +{ + REAL sP, sQ; + + // Test the locations of P and Q with respect to ABC. + sP = orient3d(A, B, C, P); + sQ = orient3d(A, B, C, Q); + + return tri_edge_tail(A, B, C, P, Q, R, sP, sQ, level, types, pos); +} + +//============================================================================// +// // +// tri_tri_inter() Test whether two triangle (abc) and (opq) are // +// intersecting or not. // +// // +// Return 0 if they are disjoint. Otherwise, return 1. 'type' returns one of // +// the four cases: SHAREVERTEX, SHAREEDGE, SHAREFACE, and INTERSECT. // +// // +//============================================================================// + +int tetgenmesh::tri_edge_inter_tail(REAL* A, REAL* B, REAL* C, REAL* P, + REAL* Q, REAL s_p, REAL s_q) +{ + int types[2], pos[4]; + int ni; // =0, 2, 4 + + ni = tri_edge_tail(A, B, C, P, Q, NULL, s_p, s_q, 1, types, pos); + + if (ni > 0) { + if (ni == 2) { + // Get the intersection type. + if (types[0] == (int) SHAREVERT) { + return (int) SHAREVERT; + } else { + return (int) INTERSECT; + } + } else if (ni == 4) { + // There may be two intersections. + if (types[0] == (int) SHAREVERT) { + if (types[1] == (int) DISJOINT) { + return (int) SHAREVERT; + } else { + return (int) INTERSECT; + } + } else { + if (types[0] == (int) SHAREEDGE) { + return (int) SHAREEDGE; + } else { + return (int) INTERSECT; + } + } + } + } + + return (int) DISJOINT; +} + +int tetgenmesh::tri_tri_inter(REAL* A,REAL* B,REAL* C,REAL* O,REAL* P,REAL* Q) +{ + REAL s_o, s_p, s_q; + REAL s_a, s_b, s_c; + + s_o = orient3d(A, B, C, O); + s_p = orient3d(A, B, C, P); + s_q = orient3d(A, B, C, Q); + if ((s_o * s_p > 0.0) && (s_o * s_q > 0.0)) { + // o, p, q are all in the same halfspace of ABC. + return 0; // DISJOINT; + } + + s_a = orient3d(O, P, Q, A); + s_b = orient3d(O, P, Q, B); + s_c = orient3d(O, P, Q, C); + if ((s_a * s_b > 0.0) && (s_a * s_c > 0.0)) { + // a, b, c are all in the same halfspace of OPQ. + return 0; // DISJOINT; + } + + int abcop, abcpq, abcqo; + int shareedge = 0; + + abcop = tri_edge_inter_tail(A, B, C, O, P, s_o, s_p); + if (abcop == (int) INTERSECT) { + return (int) INTERSECT; + } else if (abcop == (int) SHAREEDGE) { + shareedge++; + } + abcpq = tri_edge_inter_tail(A, B, C, P, Q, s_p, s_q); + if (abcpq == (int) INTERSECT) { + return (int) INTERSECT; + } else if (abcpq == (int) SHAREEDGE) { + shareedge++; + } + abcqo = tri_edge_inter_tail(A, B, C, Q, O, s_q, s_o); + if (abcqo == (int) INTERSECT) { + return (int) INTERSECT; + } else if (abcqo == (int) SHAREEDGE) { + shareedge++; + } + if (shareedge == 3) { + // opq are coincident with abc. + return (int) SHAREFACE; + } + + // Continue to detect whether opq and abc are intersecting or not. + int opqab, opqbc, opqca; + + opqab = tri_edge_inter_tail(O, P, Q, A, B, s_a, s_b); + if (opqab == (int) INTERSECT) { + return (int) INTERSECT; + } + opqbc = tri_edge_inter_tail(O, P, Q, B, C, s_b, s_c); + if (opqbc == (int) INTERSECT) { + return (int) INTERSECT; + } + opqca = tri_edge_inter_tail(O, P, Q, C, A, s_c, s_a); + if (opqca == (int) INTERSECT) { + return (int) INTERSECT; + } + + // At this point, two triangles are not intersecting and not coincident. + // They may be share an edge, or share a vertex, or disjoint. + if (abcop == (int) SHAREEDGE) { + // op is coincident with an edge of abc. + return (int) SHAREEDGE; + } + if (abcpq == (int) SHAREEDGE) { + // pq is coincident with an edge of abc. + return (int) SHAREEDGE; + } + if (abcqo == (int) SHAREEDGE) { + // qo is coincident with an edge of abc. + return (int) SHAREEDGE; + } + + // They may share a vertex or disjoint. + if (abcop == (int) SHAREVERT) { + return (int) SHAREVERT; + } + if (abcpq == (int) SHAREVERT) { + // q is the coincident vertex. + return (int) SHAREVERT; + } + + // They are disjoint. + return (int) DISJOINT; +} + +//============================================================================// +// // +// lu_decmp() Compute the LU decomposition of a matrix. // +// // +// Compute the LU decomposition of a (non-singular) square matrix A using // +// partial pivoting and implicit row exchanges. The result is: // +// A = P * L * U, // +// where P is a permutation matrix, L is unit lower triangular, and U is // +// upper triangular. The factored form of A is used in combination with // +// 'lu_solve()' to solve linear equations: Ax = b, or invert a matrix. // +// // +// The inputs are a square matrix 'lu[N..n+N-1][N..n+N-1]', it's size is 'n'. // +// On output, 'lu' is replaced by the LU decomposition of a rowwise permuta- // +// tion of itself, 'ps[N..n+N-1]' is an output vector that records the row // +// permutation effected by the partial pivoting, effectively, 'ps' array // +// tells the user what the permutation matrix P is; 'd' is output as +1/-1 // +// depending on whether the number of row interchanges was even or odd, // +// respectively. // +// // +// Return true if the LU decomposition is successfully computed, otherwise, // +// return false in case that A is a singular matrix. // +// // +//============================================================================// + +bool tetgenmesh::lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N) +{ + REAL scales[4]; + REAL pivot, biggest, mult, tempf; + int pivotindex = 0; + int i, j, k; + + *d = 1.0; // No row interchanges yet. + + for (i = N; i < n + N; i++) { // For each row. + // Find the largest element in each row for row equilibration + biggest = 0.0; + for (j = N; j < n + N; j++) + if (biggest < (tempf = fabs(lu[i][j]))) + biggest = tempf; + if (biggest != 0.0) + scales[i] = 1.0 / biggest; + else { + scales[i] = 0.0; + return false; // Zero row: singular matrix. + } + ps[i] = i; // Initialize pivot sequence. + } + + for (k = N; k < n + N - 1; k++) { // For each column. + // Find the largest element in each column to pivot around. + biggest = 0.0; + for (i = k; i < n + N; i++) { + if (biggest < (tempf = fabs(lu[ps[i]][k]) * scales[ps[i]])) { + biggest = tempf; + pivotindex = i; + } + } + if (biggest == 0.0) { + return false; // Zero column: singular matrix. + } + if (pivotindex != k) { // Update pivot sequence. + j = ps[k]; + ps[k] = ps[pivotindex]; + ps[pivotindex] = j; + *d = -(*d); // ...and change the parity of d. + } + + // Pivot, eliminating an extra variable each time + pivot = lu[ps[k]][k]; + for (i = k + 1; i < n + N; i++) { + lu[ps[i]][k] = mult = lu[ps[i]][k] / pivot; + if (mult != 0.0) { + for (j = k + 1; j < n + N; j++) + lu[ps[i]][j] -= mult * lu[ps[k]][j]; + } + } + } + + // (lu[ps[n + N - 1]][n + N - 1] == 0.0) ==> A is singular. + return lu[ps[n + N - 1]][n + N - 1] != 0.0; +} + +//============================================================================// +// // +// lu_solve() Solves the linear equation: Ax = b, after the matrix A // +// has been decomposed into the lower and upper triangular // +// matrices L and U, where A = LU. // +// // +// 'lu[N..n+N-1][N..n+N-1]' is input, not as the matrix 'A' but rather as // +// its LU decomposition, computed by the routine 'lu_decmp'; 'ps[N..n+N-1]' // +// is input as the permutation vector returned by 'lu_decmp'; 'b[N..n+N-1]' // +// is input as the right-hand side vector, and returns with the solution // +// vector. 'lu', 'n', and 'ps' are not modified by this routine and can be // +// left in place for successive calls with different right-hand sides 'b'. // +// // +//============================================================================// + +void tetgenmesh::lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N) +{ + int i, j; + REAL X[4], dot; + + for (i = N; i < n + N; i++) X[i] = 0.0; + + // Vector reduction using U triangular matrix. + for (i = N; i < n + N; i++) { + dot = 0.0; + for (j = N; j < i + N; j++) + dot += lu[ps[i]][j] * X[j]; + X[i] = b[ps[i]] - dot; + } + + // Back substitution, in L triangular matrix. + for (i = n + N - 1; i >= N; i--) { + dot = 0.0; + for (j = i + 1; j < n + N; j++) + dot += lu[ps[i]][j] * X[j]; + X[i] = (X[i] - dot) / lu[ps[i]][i]; + } + + for (i = N; i < n + N; i++) b[i] = X[i]; +} + +//============================================================================// +// // +// incircle3d() 3D in-circle test. // +// // +// Return a negative value if pd is inside the circumcircle of the triangle // +// pa, pb, and pc. // +// // +// IMPORTANT: It assumes that [a,b] is the common edge, i.e., the two input // +// triangles are [a,b,c] and [b,a,d]. // +// // +//============================================================================// + +REAL tetgenmesh::incircle3d(point pa, point pb, point pc, point pd) +{ + REAL area2[2], n1[3], n2[3], c[3]; + REAL sign, r, d; + + // Calculate the areas of the two triangles [a, b, c] and [b, a, d]. + facenormal(pa, pb, pc, n1, 1, NULL); + area2[0] = dot(n1, n1); + facenormal(pb, pa, pd, n2, 1, NULL); + area2[1] = dot(n2, n2); + + if (area2[0] > area2[1]) { + // Choose [a, b, c] as the base triangle. + circumsphere(pa, pb, pc, NULL, c, &r); + d = distance(c, pd); + } else { + // Choose [b, a, d] as the base triangle. + if (area2[1] > 0) { + circumsphere(pb, pa, pd, NULL, c, &r); + d = distance(c, pc); + } else { + // The four points are collinear. This case only happens on the boundary. + return 0; // Return "not inside". + } + } + + sign = d - r; + if (fabs(sign) / r < b->epsilon) { + sign = 0; + } + + return sign; +} + +//============================================================================// +// // +// facenormal() Calculate the normal of the face. // +// // +// The normal of the face abc can be calculated by the cross product of 2 of // +// its 3 edge vectors. A better choice of two edge vectors will reduce the // +// numerical error during the calculation. Burdakov proved that the optimal // +// basis problem is equivalent to the minimum spanning tree problem with the // +// edge length be the functional, see Burdakov, "A greedy algorithm for the // +// optimal basis problem", BIT 37:3 (1997), 591-599. If 'pivot' > 0, the two // +// short edges in abc are chosen for the calculation. // +// // +// If 'lav' is not NULL and if 'pivot' is set, the average edge length of // +// the edges of the face [a,b,c] is returned. // +// // +//============================================================================// + +void tetgenmesh::facenormal(point pa, point pb, point pc, REAL *n, int pivot, + REAL* lav) +{ + REAL v1[3], v2[3], v3[3], *pv1, *pv2; + REAL L1, L2, L3; + + v1[0] = pb[0] - pa[0]; // edge vector v1: a->b + v1[1] = pb[1] - pa[1]; + v1[2] = pb[2] - pa[2]; + v2[0] = pa[0] - pc[0]; // edge vector v2: c->a + v2[1] = pa[1] - pc[1]; + v2[2] = pa[2] - pc[2]; + + // Default, normal is calculated by: v1 x (-v2) (see Fig. fnormal). + if (pivot > 0) { + // Choose edge vectors by Burdakov's algorithm. + v3[0] = pc[0] - pb[0]; // edge vector v3: b->c + v3[1] = pc[1] - pb[1]; + v3[2] = pc[2] - pb[2]; + L1 = dot(v1, v1); + L2 = dot(v2, v2); + L3 = dot(v3, v3); + // Sort the three edge lengths. + if (L1 < L2) { + if (L2 < L3) { + pv1 = v1; pv2 = v2; // n = v1 x (-v2). + } else { + pv1 = v3; pv2 = v1; // n = v3 x (-v1). + } + } else { + if (L1 < L3) { + pv1 = v1; pv2 = v2; // n = v1 x (-v2). + } else { + pv1 = v2; pv2 = v3; // n = v2 x (-v3). + } + } + if (lav) { + // return the average edge length. + *lav = (sqrt(L1) + sqrt(L2) + sqrt(L3)) / 3.0; + } + } else { + pv1 = v1; pv2 = v2; // n = v1 x (-v2). + } + + // Calculate the face normal. + cross(pv1, pv2, n); + // Inverse the direction; + n[0] = -n[0]; + n[1] = -n[1]; + n[2] = -n[2]; +} + +//============================================================================// +// // +// facedihedral() Return the dihedral angle (in radian) between two // +// adjoining faces. // +// // +// 'pa', 'pb' are the shared edge of these two faces, 'pc1', and 'pc2' are // +// apexes of these two faces. Return the angle (between 0 to 2*pi) between // +// the normal of face (pa, pb, pc1) and normal of face (pa, pb, pc2). // +// // +//============================================================================// + +REAL tetgenmesh::facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2) +{ + REAL n1[3], n2[3]; + REAL n1len, n2len; + REAL costheta, ori; + REAL theta; + + facenormal(pa, pb, pc1, n1, 1, NULL); + facenormal(pa, pb, pc2, n2, 1, NULL); + n1len = sqrt(dot(n1, n1)); + n2len = sqrt(dot(n2, n2)); + costheta = dot(n1, n2) / (n1len * n2len); + // Be careful rounding error! + if (costheta > 1.0) { + costheta = 1.0; + } else if (costheta < -1.0) { + costheta = -1.0; + } + theta = acos(costheta); + ori = orient3d(pa, pb, pc1, pc2); + if (ori > 0.0) { + theta = 2 * PI - theta; + } + + return theta; +} + +//============================================================================// +// // +// triarea() Return the area of a triangle. // +// // +//============================================================================// + +REAL tetgenmesh::triarea(REAL* pa, REAL* pb, REAL* pc) +{ + REAL A[4][4]; + + // Compute the coefficient matrix A (3x3). + A[0][0] = pb[0] - pa[0]; + A[0][1] = pb[1] - pa[1]; + A[0][2] = pb[2] - pa[2]; // vector V1 (pa->pb) + A[1][0] = pc[0] - pa[0]; + A[1][1] = pc[1] - pa[1]; + A[1][2] = pc[2] - pa[2]; // vector V2 (pa->pc) + + cross(A[0], A[1], A[2]); // vector V3 (V1 X V2) + + return 0.5 * sqrt(dot(A[2], A[2])); // The area of [a,b,c]. +} + +REAL tetgenmesh::orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, bdx, cdx; + REAL ady, bdy, cdy; + REAL adz, bdz, cdz; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + return adx * (bdy * cdz - bdz * cdy) + + bdx * (cdy * adz - cdz * ady) + + cdx * (ady * bdz - adz * bdy); +} + +//============================================================================// +// // +// interiorangle() Return the interior angle (0 - 2 * PI) between vectors // +// o->p1 and o->p2. // +// // +// 'n' is the normal of the plane containing face (o, p1, p2). The interior // +// angle is the total angle rotating from o->p1 around n to o->p2. Exchange // +// the position of p1 and p2 will get the complement angle of the other one. // +// i.e., interiorangle(o, p1, p2) = 2 * PI - interiorangle(o, p2, p1). Set // +// 'n' be NULL if you only want the interior angle between 0 - PI. // +// // +//============================================================================// + +REAL tetgenmesh::interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n) +{ + REAL v1[3], v2[3], np[3]; + REAL theta, costheta, lenlen; + REAL ori, len1, len2; + + // Get the interior angle (0 - PI) between o->p1, and o->p2. + v1[0] = p1[0] - o[0]; + v1[1] = p1[1] - o[1]; + v1[2] = p1[2] - o[2]; + v2[0] = p2[0] - o[0]; + v2[1] = p2[1] - o[1]; + v2[2] = p2[2] - o[2]; + len1 = sqrt(dot(v1, v1)); + len2 = sqrt(dot(v2, v2)); + lenlen = len1 * len2; + + costheta = dot(v1, v2) / lenlen; + if (costheta > 1.0) { + costheta = 1.0; // Roundoff. + } else if (costheta < -1.0) { + costheta = -1.0; // Roundoff. + } + theta = acos(costheta); + if (n != NULL) { + // Get a point above the face (o, p1, p2); + np[0] = o[0] + n[0]; + np[1] = o[1] + n[1]; + np[2] = o[2] + n[2]; + // Adjust theta (0 - 2 * PI). + ori = orient3d(p1, o, np, p2); + if (ori > 0.0) { + theta = 2 * PI - theta; + } + } + + return theta; +} + +REAL tetgenmesh::cos_interiorangle(REAL* o, REAL* p1, REAL* p2) +{ + REAL v1[3], v2[3], np[3]; + REAL theta, costheta, lenlen; + REAL ori, len1, len2; + + // Get the interior angle (0 - PI) between o->p1, and o->p2. + v1[0] = p1[0] - o[0]; + v1[1] = p1[1] - o[1]; + v1[2] = p1[2] - o[2]; + v2[0] = p2[0] - o[0]; + v2[1] = p2[1] - o[1]; + v2[2] = p2[2] - o[2]; + len1 = sqrt(dot(v1, v1)); + len2 = sqrt(dot(v2, v2)); + lenlen = len1 * len2; + + costheta = dot(v1, v2) / lenlen; + + if (costheta > 1.0) { + costheta = 1.0; // Roundoff. + } else if (costheta < -1.0) { + costheta = -1.0; // Roundoff. + } + + return costheta; +} + +//============================================================================// +// // +// projpt2edge() Return the projection point from a point to an edge. // +// // +//============================================================================// + +void tetgenmesh::projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj) +{ + REAL v1[3], v2[3]; + REAL len, l_p; + + v1[0] = e2[0] - e1[0]; + v1[1] = e2[1] - e1[1]; + v1[2] = e2[2] - e1[2]; + v2[0] = p[0] - e1[0]; + v2[1] = p[1] - e1[1]; + v2[2] = p[2] - e1[2]; + + len = sqrt(dot(v1, v1)); + v1[0] /= len; + v1[1] /= len; + v1[2] /= len; + l_p = dot(v1, v2); + + prj[0] = e1[0] + l_p * v1[0]; + prj[1] = e1[1] + l_p * v1[1]; + prj[2] = e1[2] + l_p * v1[2]; +} + +//============================================================================// +// // +// projpt2face() Return the projection point from a point to a face. // +// // +//============================================================================// + +void tetgenmesh::projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj) +{ + REAL fnormal[3], v1[3]; + REAL len, dist; + + // Get the unit face normal. + facenormal(f1, f2, f3, fnormal, 1, NULL); + len = sqrt(fnormal[0]*fnormal[0] + fnormal[1]*fnormal[1] + + fnormal[2]*fnormal[2]); + fnormal[0] /= len; + fnormal[1] /= len; + fnormal[2] /= len; + // Get the vector v1 = |p - f1|. + v1[0] = p[0] - f1[0]; + v1[1] = p[1] - f1[1]; + v1[2] = p[2] - f1[2]; + // Get the project distance. + dist = dot(fnormal, v1); + + // Get the project point. + prj[0] = p[0] - dist * fnormal[0]; + prj[1] = p[1] - dist * fnormal[1]; + prj[2] = p[2] - dist * fnormal[2]; +} + +//============================================================================// +// // +// circumsphere() Calculate the smallest circumsphere (center and radius) // +// of the given three or four points. // +// // +// The circumsphere of four points (a tetrahedron) is unique if they are not // +// degenerate. If 'pd = NULL', the smallest circumsphere of three points is // +// the diametral sphere of the triangle if they are not degenerate. // +// // +// Return TRUE if the input points are not degenerate and the circumcenter // +// and circumradius are returned in 'cent' and 'radius' respectively if they // +// are not NULLs. Otherwise, return FALSE, the four points are co-planar. // +// // +//============================================================================// + +bool tetgenmesh::circumsphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, + REAL* cent, REAL* radius) +{ + REAL A[4][4], rhs[4], D; + int indx[4]; + + // Compute the coefficient matrix A (3x3). + A[0][0] = pb[0] - pa[0]; + A[0][1] = pb[1] - pa[1]; + A[0][2] = pb[2] - pa[2]; + A[1][0] = pc[0] - pa[0]; + A[1][1] = pc[1] - pa[1]; + A[1][2] = pc[2] - pa[2]; + if (pd != NULL) { + A[2][0] = pd[0] - pa[0]; + A[2][1] = pd[1] - pa[1]; + A[2][2] = pd[2] - pa[2]; + } else { + cross(A[0], A[1], A[2]); + } + + // Compute the right hand side vector b (3x1). + rhs[0] = 0.5 * dot(A[0], A[0]); + rhs[1] = 0.5 * dot(A[1], A[1]); + if (pd != NULL) { + rhs[2] = 0.5 * dot(A[2], A[2]); + } else { + rhs[2] = 0.0; + } + + // Solve the 3 by 3 equations use LU decomposition with partial pivoting + // and backward and forward substitute.. + if (!lu_decmp(A, 3, indx, &D, 0)) { + if (radius != (REAL *) NULL) *radius = 0.0; + return false; + } + lu_solve(A, 3, indx, rhs, 0); + if (cent != (REAL *) NULL) { + cent[0] = pa[0] + rhs[0]; + cent[1] = pa[1] + rhs[1]; + cent[2] = pa[2] + rhs[2]; + } + if (radius != (REAL *) NULL) { + *radius = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); + } + return true; +} + +//============================================================================// +// // +// orthosphere() Calulcate the orthosphere of four weighted points. // +// // +// A weighted point (p, P^2) can be interpreted as a sphere centered at the // +// point 'p' with a radius 'P'. The 'height' of 'p' is pheight = p[0]^2 + // +// p[1]^2 + p[2]^2 - P^2. // +// // +//============================================================================// + +bool tetgenmesh::orthosphere(REAL* pa, REAL* pb, REAL* pc, REAL* pd, + REAL aheight, REAL bheight, REAL cheight, + REAL dheight, REAL* orthocent, REAL* radius) +{ + REAL A[4][4], rhs[4], D; + int indx[4]; + + // Set the coefficient matrix A (4 x 4). + A[0][0] = 1.0; A[0][1] = pa[0]; A[0][2] = pa[1]; A[0][3] = pa[2]; + A[1][0] = 1.0; A[1][1] = pb[0]; A[1][2] = pb[1]; A[1][3] = pb[2]; + A[2][0] = 1.0; A[2][1] = pc[0]; A[2][2] = pc[1]; A[2][3] = pc[2]; + A[3][0] = 1.0; A[3][1] = pd[0]; A[3][2] = pd[1]; A[3][3] = pd[2]; + + // Set the right hand side vector (4 x 1). + rhs[0] = 0.5 * aheight; + rhs[1] = 0.5 * bheight; + rhs[2] = 0.5 * cheight; + rhs[3] = 0.5 * dheight; + + // Solve the 4 by 4 equations use LU decomposition with partial pivoting + // and backward and forward substitute.. + if (!lu_decmp(A, 4, indx, &D, 0)) { + if (radius != (REAL *) NULL) *radius = 0.0; + return false; + } + lu_solve(A, 4, indx, rhs, 0); + + if (orthocent != (REAL *) NULL) { + orthocent[0] = rhs[1]; + orthocent[1] = rhs[2]; + orthocent[2] = rhs[3]; + } + if (radius != (REAL *) NULL) { + // rhs[0] = - rheight / 2; + // rheight = - 2 * rhs[0]; + // = r[0]^2 + r[1]^2 + r[2]^2 - radius^2 + // radius^2 = r[0]^2 + r[1]^2 + r[2]^2 -rheight + // = r[0]^2 + r[1]^2 + r[2]^2 + 2 * rhs[0] + *radius = sqrt(rhs[1] * rhs[1] + rhs[2] * rhs[2] + rhs[3] * rhs[3] + + 2.0 * rhs[0]); + } + return true; +} + +//============================================================================// +// // +// planelineint() Calculate the intersection of a line and a plane. // +// // +// The equation of a plane (points P are on the plane with normal N and P3 // +// on the plane) can be written as: N dot (P - P3) = 0. The equation of the // +// line (points P on the line passing through P1 and P2) can be written as: // +// P = P1 + u (P2 - P1). The intersection of these two occurs when: // +// N dot (P1 + u (P2 - P1)) = N dot P3. // +// Solving for u gives: // +// N dot (P3 - P1) // +// u = ------------------. // +// N dot (P2 - P1) // +// If the denominator is 0 then N (the normal to the plane) is perpendicular // +// to the line. Thus the line is either parallel to the plane and there are // +// no solutions or the line is on the plane in which case there are an infi- // +// nite number of solutions. // +// // +// The plane is given by three points pa, pb, and pc, e1 and e2 defines the // +// line. If u is non-zero, The intersection point (if exists) returns in ip. // +// // +//============================================================================// + +void tetgenmesh::planelineint(REAL* pa, REAL* pb, REAL* pc, REAL* e1, REAL* e2, + REAL* ip, REAL* u) +{ + REAL *U = e1, *V = e2; + REAL Vuv[3]; // vector U->V + + Vuv[0] = V[0] - U[0]; + Vuv[1] = V[1] - U[1]; + Vuv[2] = V[2] - U[2]; + + REAL A[4], B[4], C[4], D[4], O[4]; + + A[0] = pa[0]; A[1] = pb[0]; A[2] = pc[0]; A[3] = -Vuv[0]; + B[0] = pa[1]; B[1] = pb[1]; B[2] = pc[1]; B[3] = -Vuv[1]; + C[0] = pa[2]; C[1] = pb[2]; C[2] = pc[2]; C[3] = -Vuv[2]; + D[0] = 1.; D[1] = 1.; D[2] = 1.; D[3] = 0.; + O[0] = 0.; O[1] = 0.; O[2] = 0.; O[3] = 0.; + + REAL det, det1; + + det = orient4dexact(A, B, C, D, O, A[3], B[3], C[3], D[3], O[3]); + + if (det != 0.0) { + det1 = orient3dexact(pa, pb, pc, U); + + *u = det1 / det; + + ip[0] = U[0] + *u * Vuv[0]; // (V[0] - U[0]); + ip[1] = U[1] + *u * Vuv[1]; // (V[1] - U[1]); + ip[2] = U[2] + *u * Vuv[2]; // (V[2] - U[2]); + } else { + *u = 0.0; + ip[0] = ip[1] = ip[2] = 0.; + } + +} + +//============================================================================// +// // +// linelineint() Calculate the intersection(s) of two line segments. // +// // +// Calculate the line segment [P, Q] that is the shortest route between two // +// lines from A to B and C to D. Calculate also the values of tp and tq // +// where: P = A + tp (B - A), and Q = C + tq (D - C). // +// // +// Return 1 if the line segment exists. Otherwise, return 0. // +// // +//============================================================================// + +int tetgenmesh::linelineint(REAL* A, REAL* B, REAL* C, REAL* D, REAL* P, + REAL* Q, REAL* tp, REAL* tq) +{ + REAL vab[3], vcd[3], vca[3]; + REAL vab_vab, vcd_vcd, vab_vcd; + REAL vca_vab, vca_vcd; + REAL det, eps; + int i; + + for (i = 0; i < 3; i++) { + vab[i] = B[i] - A[i]; + vcd[i] = D[i] - C[i]; + vca[i] = A[i] - C[i]; + } + + vab_vab = dot(vab, vab); + vcd_vcd = dot(vcd, vcd); + vab_vcd = dot(vab, vcd); + + det = vab_vab * vcd_vcd - vab_vcd * vab_vcd; + // Round the result. + eps = det / (fabs(vab_vab * vcd_vcd) + fabs(vab_vcd * vab_vcd)); + if (eps < b->epsilon) { + return 0; + } + + vca_vab = dot(vca, vab); + vca_vcd = dot(vca, vcd); + + *tp = (vcd_vcd * (- vca_vab) + vab_vcd * vca_vcd) / det; + *tq = (vab_vcd * (- vca_vab) + vab_vab * vca_vcd) / det; + + for (i = 0; i < 3; i++) P[i] = A[i] + (*tp) * vab[i]; + for (i = 0; i < 3; i++) Q[i] = C[i] + (*tq) * vcd[i]; + + return 1; +} + +//============================================================================// +// // +// tetprismvol() Calculate the volume of a tetrahedral prism in 4D. // +// // +// A tetrahedral prism is a convex uniform polychoron (four dimensional poly- // +// tope). It has 6 polyhedral cells: 2 tetrahedra connected by 4 triangular // +// prisms. It has 14 faces: 8 triangular and 6 square. It has 16 edges and 8 // +// vertices. (Wikipedia). // +// // +// Let 'p0', ..., 'p3' be four affinely independent points in R^3. They form // +// the lower tetrahedral facet of the prism. The top tetrahedral facet is // +// formed by four vertices, 'p4', ..., 'p7' in R^4, which is obtained by // +// lifting each vertex of the lower facet into R^4 by a weight (height). A // +// canonical choice of the weights is the square of Euclidean norm of of the // +// points (vectors). // +// // +// // +// The return value is (4!) 24 times of the volume of the tetrahedral prism. // +// // +//============================================================================// + +REAL tetgenmesh::tetprismvol(REAL* p0, REAL* p1, REAL* p2, REAL* p3) +{ + REAL *p4, *p5, *p6, *p7; + REAL w4, w5, w6, w7; + REAL vol[4]; + + p4 = p0; + p5 = p1; + p6 = p2; + p7 = p3; + + // TO DO: these weights can be pre-calculated! + w4 = dot(p0, p0); + w5 = dot(p1, p1); + w6 = dot(p2, p2); + w7 = dot(p3, p3); + + // Calculate the volume of the tet-prism. + vol[0] = orient4d(p5, p6, p4, p3, p7, w5, w6, w4, 0, w7); + vol[1] = orient4d(p3, p6, p2, p0, p1, 0, w6, 0, 0, 0); + vol[2] = orient4d(p4, p6, p3, p0, p1, w4, w6, 0, 0, 0); + vol[3] = orient4d(p6, p5, p4, p3, p1, w6, w5, w4, 0, 0); + + return fabs(vol[0]) + fabs(vol[1]) + fabs(vol[2]) + fabs(vol[3]); +} + +//============================================================================// +// // +// calculateabovepoint() Calculate a point above a facet in 'dummypoint'. // +// // +//============================================================================// + +bool tetgenmesh::calculateabovepoint(arraypool *facpoints, point *ppa, + point *ppb, point *ppc) +{ + point *ppt, pa, pb, pc; + REAL v1[3], v2[3], n[3]; + REAL lab, len, A, area; + REAL x, y, z; + int i; + + ppt = (point *) fastlookup(facpoints, 0); + pa = *ppt; // a is the first point. + pb = pc = NULL; // Avoid compiler warnings. + + // Get a point b s.t. the length of [a, b] is maximal. + lab = 0; + for (i = 1; i < facpoints->objects; i++) { + ppt = (point *) fastlookup(facpoints, i); + x = (*ppt)[0] - pa[0]; + y = (*ppt)[1] - pa[1]; + z = (*ppt)[2] - pa[2]; + len = x * x + y * y + z * z; + if (len > lab) { + lab = len; + pb = *ppt; + } + } + lab = sqrt(lab); + if (lab == 0) { + if (!b->quiet) { + printf("Warning: All points of a facet are coincident with %d.\n", + pointmark(pa)); + } + return false; + } + + // Get a point c s.t. the area of [a, b, c] is maximal. + v1[0] = pb[0] - pa[0]; + v1[1] = pb[1] - pa[1]; + v1[2] = pb[2] - pa[2]; + A = 0; + for (i = 1; i < facpoints->objects; i++) { + ppt = (point *) fastlookup(facpoints, i); + v2[0] = (*ppt)[0] - pa[0]; + v2[1] = (*ppt)[1] - pa[1]; + v2[2] = (*ppt)[2] - pa[2]; + cross(v1, v2, n); + area = dot(n, n); + if (area > A) { + A = area; + pc = *ppt; + } + } + if (A == 0) { + // All points are collinear. No above point. + if (!b->quiet) { + printf("Warning: All points of a facet are collinaer with [%d, %d].\n", + pointmark(pa), pointmark(pb)); + } + return false; + } + + // Calculate an above point of this facet. + facenormal(pa, pb, pc, n, 1, NULL); + len = sqrt(dot(n, n)); + n[0] /= len; + n[1] /= len; + n[2] /= len; + lab /= 2.0; // Half the maximal length. + dummypoint[0] = pa[0] + lab * n[0]; + dummypoint[1] = pa[1] + lab * n[1]; + dummypoint[2] = pa[2] + lab * n[2]; + + if (ppa != NULL) { + // Return the three points. + *ppa = pa; + *ppb = pb; + *ppc = pc; + } + + return true; +} + +//============================================================================// +// // +// Calculate an above point. It lies above the plane containing the subface // +// [a,b,c], and save it in dummypoint. Moreover, the vector pa->dummypoint // +// is the normal of the plane. // +// // +//============================================================================// + +void tetgenmesh::calculateabovepoint4(point pa, point pb, point pc, point pd) +{ + REAL n1[3], n2[3], *norm; + REAL len, len1, len2; + + // Select a base. + facenormal(pa, pb, pc, n1, 1, NULL); + len1 = sqrt(dot(n1, n1)); + facenormal(pa, pb, pd, n2, 1, NULL); + len2 = sqrt(dot(n2, n2)); + if (len1 > len2) { + norm = n1; + len = len1; + } else { + norm = n2; + len = len2; + } + norm[0] /= len; + norm[1] /= len; + norm[2] /= len; + len = distance(pa, pb); + dummypoint[0] = pa[0] + len * norm[0]; + dummypoint[1] = pa[1] + len * norm[1]; + dummypoint[2] = pa[2] + len * norm[2]; +} + + +// // +// // +//== geom_cxx ================================================================// + +//== flip_cxx ================================================================// +// // +// // + +//============================================================================// +// // +// flippush() Push a face (possibly will be flipped) into flipstack. // +// // +// The face is marked. The flag is used to check the validity of the face on // +// its popup. Some other flips may change it already. // +// // +//============================================================================// + +void tetgenmesh::flippush(badface*& fstack, triface* flipface) +{ + if (!facemarked(*flipface)) { + badface *newflipface = (badface *) flippool->alloc(); + newflipface->tt = *flipface; + markface(newflipface->tt); + // Push this face into stack. + newflipface->nextitem = fstack; + fstack = newflipface; + } +} + +//============================================================================// +// // +// flip23() Perform a 2-to-3 flip (face-to-edge flip). // +// // +// 'fliptets' is an array of three tets (handles), where the [0] and [1] are // +// [a,b,c,d] and [b,a,c,e]. The three new tets: [e,d,a,b], [e,d,b,c], and // +// [e,d,c,a] are returned in [0], [1], and [2] of 'fliptets'. As a result, // +// the face [a,b,c] is removed, and the edge [d,e] is created. // +// // +// If 'hullflag' > 0, hull tets may be involved in this flip, i.e., one of // +// the five vertices may be 'dummypoint'. There are two canonical cases: // +// (1) d is 'dummypoint', then all three new tets are hull tets. If e is // +// 'dummypoint', we reconfigure e to d, i.e., to turn it up-side down. // +// (2) c is 'dummypoint', then two new tets: [e,d,b,c] and [e,d,c,a], are // +// hull tets. If a (or b) is 'dummypoint', we reconfigure it to c, // +// i.e., to rotate the three tets counterclockwisely (right-hand rule) // +// until a (or b) is in c's position. // +// // +// If 'fc->enqflag > 0', faces on the convex hull of {a,b,c,d,e} will be // +// queued for flipping. // +// In particular, if 'fc->enqflag = 1', it is called by incrementalflip() // +// after the insertion of a new point. It is assumed that 'd' is the new // +// point. In this case, only link faces of 'd' are queued. // +// // +//============================================================================// + +void tetgenmesh::flip23(triface* fliptets, int hullflag, flipconstraints *fc) +{ + triface topcastets[3], botcastets[3]; + triface newface, casface; + point pa, pb, pc, pd, pe; + REAL attrib, volume; + int dummyflag = 0; // range = {-1, 0, 1, 2}. + int i; + + if (hullflag > 0) { + // Check if e is dummypoint. + if (oppo(fliptets[1]) == dummypoint) { + // Swap the two old tets. + newface = fliptets[0]; + fliptets[0] = fliptets[1]; + fliptets[1] = newface; + dummyflag = -1; // d is dummypoint. + } else { + // Check if either a or b is dummypoint. + if (org(fliptets[0]) == dummypoint) { + dummyflag = 1; // a is dummypoint. + enextself(fliptets[0]); + eprevself(fliptets[1]); + } else if (dest(fliptets[0]) == dummypoint) { + dummyflag = 2; // b is dummypoint. + eprevself(fliptets[0]); + enextself(fliptets[1]); + } else { + dummyflag = 0; // either c or d may be dummypoint. + } + } + } + + pa = org(fliptets[0]); + pb = dest(fliptets[0]); + pc = apex(fliptets[0]); + pd = oppo(fliptets[0]); + pe = oppo(fliptets[1]); + + flip23count++; + + // Get the outer boundary faces. + for (i = 0; i < 3; i++) { + fnext(fliptets[0], topcastets[i]); + enextself(fliptets[0]); + } + for (i = 0; i < 3; i++) { + fnext(fliptets[1], botcastets[i]); + eprevself(fliptets[1]); + } + + // Re-use fliptets[0] and fliptets[1]. + fliptets[0].ver = 11; + fliptets[1].ver = 11; + setelemmarker(fliptets[0].tet, 0); // Clear all flags. + setelemmarker(fliptets[1].tet, 0); + // NOTE: the element attributes and volume constraint remain unchanged. + if (checksubsegflag) { + // Dealloc the space to subsegments. + if (fliptets[0].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[0].tet[8]); + fliptets[0].tet[8] = NULL; + } + if (fliptets[1].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[1].tet[8]); + fliptets[1].tet[8] = NULL; + } + } + if (checksubfaceflag) { + // Dealloc the space to subfaces. + if (fliptets[0].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[0].tet[9]); + fliptets[0].tet[9] = NULL; + } + if (fliptets[1].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[1].tet[9]); + fliptets[1].tet[9] = NULL; + } + } + // Create a new tet. + maketetrahedron(&(fliptets[2])); + // The new tet have the same attributes from the old tet. + for (i = 0; i < numelemattrib; i++) { + attrib = elemattribute(fliptets[0].tet, i); + setelemattribute(fliptets[2].tet, i, attrib); + } + if (b->varvolume) { + volume = volumebound(fliptets[0].tet); + setvolumebound(fliptets[2].tet, volume); + } + + if (hullflag > 0) { + // Check if d is dummytet. + if (pd != dummypoint) { + setvertices(fliptets[0], pe, pd, pa, pb); // [e,d,a,b] * + setvertices(fliptets[1], pe, pd, pb, pc); // [e,d,b,c] * + // Check if c is dummypoint. + if (pc != dummypoint) { + setvertices(fliptets[2], pe, pd, pc, pa); // [e,d,c,a] * + } else { + setvertices(fliptets[2], pd, pe, pa, pc); // [d,e,a,c] + esymself(fliptets[2]); // [e,d,c,a] * + } + // The hullsize does not change. + } else { + // d is dummypoint. + setvertices(fliptets[0], pa, pb, pe, pd); // [a,b,e,d] + setvertices(fliptets[1], pb, pc, pe, pd); // [b,c,e,d] + setvertices(fliptets[2], pc, pa, pe, pd); // [c,a,e,d] + // Adjust the faces to [e,d,a,b], [e,d,b,c], [e,d,c,a] * + for (i = 0; i < 3; i++) { + eprevesymself(fliptets[i]); + enextself(fliptets[i]); + } + // We deleted one hull tet, and created three hull tets. + hullsize += 2; + } + } else { + setvertices(fliptets[0], pe, pd, pa, pb); // [e,d,a,b] * + setvertices(fliptets[1], pe, pd, pb, pc); // [e,d,b,c] * + setvertices(fliptets[2], pe, pd, pc, pa); // [e,d,c,a] * + } + + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[2], volpos[3], vol_diff; + if (pd != dummypoint) { + if (pc != dummypoint) { + volpos[0] = tetprismvol(pe, pd, pa, pb); + volpos[1] = tetprismvol(pe, pd, pb, pc); + volpos[2] = tetprismvol(pe, pd, pc, pa); + volneg[0] = tetprismvol(pa, pb, pc, pd); + volneg[1] = tetprismvol(pb, pa, pc, pe); + } else { // pc == dummypoint + volpos[0] = tetprismvol(pe, pd, pa, pb); + volpos[1] = 0.; + volpos[2] = 0.; + volneg[0] = 0.; + volneg[1] = 0.; + } + } else { // pd == dummypoint. + volpos[0] = 0.; + volpos[1] = 0.; + volpos[2] = 0.; + volneg[0] = 0.; + volneg[1] = tetprismvol(pb, pa, pc, pe); + } + vol_diff = volpos[0] + volpos[1] + volpos[2] - volneg[0] - volneg[1]; + fc->tetprism_vol_sum += vol_diff; // Update the total sum. + } + + // Bond three new tets together. + for (i = 0; i < 3; i++) { + esym(fliptets[i], newface); + bond(newface, fliptets[(i + 1) % 3]); + } + // Bond to top outer boundary faces (at [a,b,c,d]). + for (i = 0; i < 3; i++) { + eorgoppo(fliptets[i], newface); // At edges [b,a], [c,b], [a,c]. + bond(newface, topcastets[i]); + } + // Bond bottom outer boundary faces (at [b,a,c,e]). + for (i = 0; i < 3; i++) { + edestoppo(fliptets[i], newface); // At edges [a,b], [b,c], [c,a]. + bond(newface, botcastets[i]); + } + + if (checksubsegflag) { + // Bond subsegments if there are. + // Each new tet has 5 edges to be checked (except the edge [e,d]). + face checkseg; + // The middle three: [a,b], [b,c], [c,a]. + for (i = 0; i < 3; i++) { + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); + eorgoppo(fliptets[i], newface); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + } + // The top three: [d,a], [d,b], [d,c]. Two tets per edge. + for (i = 0; i < 3; i++) { + eprev(topcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); + enext(fliptets[i], newface); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + esym(fliptets[(i + 2) % 3], newface); + eprevself(newface); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + } + // The bot three: [a,e], [b,e], [c,e]. Two tets per edge. + for (i = 0; i < 3; i++) { + enext(botcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); + eprev(fliptets[i], newface); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + esym(fliptets[(i + 2) % 3], newface); + enextself(newface); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + } + } // if (checksubsegflag) + + if (checksubfaceflag) { + // Bond 6 subfaces if there are. + face checksh; + for (i = 0; i < 3; i++) { + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); + eorgoppo(fliptets[i], newface); + sesymself(checksh); + tsbond(newface, checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + } + for (i = 0; i < 3; i++) { + if (issubface(botcastets[i])) { + tspivot(botcastets[i], checksh); + edestoppo(fliptets[i], newface); + sesymself(checksh); + tsbond(newface, checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + } + } // if (checksubfaceflag) + + if (fc->chkencflag & 4) { + // Put three new tets into check list. + for (i = 0; i < 3; i++) { + enqueuetetrahedron(&(fliptets[i])); + } + } + + // Update the point-to-tet map. + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[1].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); + setpoint2tet(pe, (tetrahedron) fliptets[0].tet); + + if (hullflag > 0) { + if (dummyflag != 0) { + // Restore the original position of the points (for flipnm()). + if (dummyflag == -1) { + // Reverse the edge. + for (i = 0; i < 3; i++) { + esymself(fliptets[i]); + } + // Swap the last two new tets. + newface = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = newface; + } else { + // either a or b were swapped. + if (dummyflag == 1) { + // a is dummypoint. + newface = fliptets[0]; + fliptets[0] = fliptets[2]; + fliptets[2] = fliptets[1]; + fliptets[1] = newface; + } else { // dummyflag == 2 + // b is dummypoint. + newface = fliptets[0]; + fliptets[0] = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = newface; + } + } + } + } + + if (fc->enqflag > 0) { + // Queue faces which may be locally non-Delaunay. + for (i = 0; i < 3; i++) { + eprevesym(fliptets[i], newface); + flippush(flipstack, &newface); + } + if (fc->enqflag > 1) { + for (i = 0; i < 3; i++) { + enextesym(fliptets[i], newface); + flippush(flipstack, &newface); + } + } + } + + recenttet = fliptets[0]; +} + +//============================================================================// +// // +// flip32() Perform a 3-to-2 flip (edge-to-face flip). // +// // +// 'fliptets' is an array of three tets (handles), which are [e,d,a,b], // +// [e,d,b,c], and [e,d,c,a]. The two new tets: [a,b,c,d] and [b,a,c,e] are // +// returned in [0] and [1] of 'fliptets'. As a result, the edge [e,d] is // +// replaced by the face [a,b,c]. // +// // +// If 'hullflag' > 0, hull tets may be involved in this flip, i.e., one of // +// the five vertices may be 'dummypoint'. There are two canonical cases: // +// (1) d is 'dummypoint', then [a,b,c,d] is hull tet. If e is 'dummypoint', // +// we reconfigure e to d, i.e., turnover it. // +// (2) c is 'dummypoint' then both [a,b,c,d] and [b,a,c,e] are hull tets. // +// If a or b is 'dummypoint', we reconfigure it to c, i.e., rotate the // +// three old tets counterclockwisely (right-hand rule) until a or b // +// is in c's position. // +// // +// If 'fc->enqflag' is set, convex hull faces will be queued for flipping. // +// In particular, if 'fc->enqflag' is 1, it is called by incrementalflip() // +// after the insertion of a new point. It is assumed that 'a' is the new // +// point. In this case, only link faces of 'a' are queued. // +// // +// If 'checksubfaceflag' is on (global variable), and assume [e,d] is not a // +// segment. There may be two (interior) subfaces sharing at [e,d], which are // +// [e,d,p] and [e,d,q], where the pair (p,q) may be either (a,b), or (b,c), // +// or (c,a) In such case, a 2-to-2 flip is performed on these two subfaces // +// and two new subfaces [p,q,e] and [p,q,d] are created. They are inserted // +// back into the tetrahedralization. // +// // +//============================================================================// + +void tetgenmesh::flip32(triface* fliptets, int hullflag, flipconstraints *fc) +{ + triface topcastets[3], botcastets[3]; + triface newface, casface; + face flipshs[3]; + face checkseg; + point pa, pb, pc, pd, pe; + REAL attrib, volume; + int dummyflag = 0; // Rangle = {-1, 0, 1, 2} + int spivot = -1, scount = 0; // for flip22() + int t1ver; + int i, j; + + if (hullflag > 0) { + // Check if e is 'dummypoint'. + if (org(fliptets[0]) == dummypoint) { + // Reverse the edge. + for (i = 0; i < 3; i++) { + esymself(fliptets[i]); + } + // Swap the last two tets. + newface = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = newface; + dummyflag = -1; // e is dummypoint. + } else { + // Check if a or b is the 'dummypoint'. + if (apex(fliptets[0]) == dummypoint) { + dummyflag = 1; // a is dummypoint. + newface = fliptets[0]; + fliptets[0] = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = newface; + } else if (apex(fliptets[1]) == dummypoint) { + dummyflag = 2; // b is dummypoint. + newface = fliptets[0]; + fliptets[0] = fliptets[2]; + fliptets[2] = fliptets[1]; + fliptets[1] = newface; + } else { + dummyflag = 0; // either c or d may be dummypoint. + } + } + } + + pa = apex(fliptets[0]); + pb = apex(fliptets[1]); + pc = apex(fliptets[2]); + pd = dest(fliptets[0]); + pe = org(fliptets[0]); + + flip32count++; + + // Get the outer boundary faces. + for (i = 0; i < 3; i++) { + eorgoppo(fliptets[i], casface); + fsym(casface, topcastets[i]); + } + for (i = 0; i < 3; i++) { + edestoppo(fliptets[i], casface); + fsym(casface, botcastets[i]); + } + + if (checksubfaceflag) { + // Check if there are interior subfaces at the edge [e,d]. + for (i = 0; i < 3; i++) { + tspivot(fliptets[i], flipshs[i]); + if (flipshs[i].sh != NULL) { + // Found an interior subface. + stdissolve(flipshs[i]); // Disconnect the sub-tet bond. + scount++; + } else { + spivot = i; + } + } + } + + // Re-use fliptets[0] and fliptets[1]. + fliptets[0].ver = 11; + fliptets[1].ver = 11; + setelemmarker(fliptets[0].tet, 0); // Clear all flags. + setelemmarker(fliptets[1].tet, 0); + if (checksubsegflag) { + // Dealloc the space to subsegments. + if (fliptets[0].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[0].tet[8]); + fliptets[0].tet[8] = NULL; + } + if (fliptets[1].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[1].tet[8]); + fliptets[1].tet[8] = NULL; + } + } + if (checksubfaceflag) { + // Dealloc the space to subfaces. + if (fliptets[0].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[0].tet[9]); + fliptets[0].tet[9] = NULL; + } + if (fliptets[1].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[1].tet[9]); + fliptets[1].tet[9] = NULL; + } + } + if (checksubfaceflag) { + if (scount > 0) { + // The element attributes and volume constraint must be set correctly. + // There are two subfaces involved in this flip. The three tets are + // separated into two different regions, one may be exterior. The + // first region has two tets, and the second region has only one. + // The two created tets must be in the same region as the first region. + // The element attributes and volume constraint must be set correctly. + //assert(spivot != -1); + // The tet fliptets[spivot] is in the first region. + for (j = 0; j < 2; j++) { + for (i = 0; i < numelemattrib; i++) { + attrib = elemattribute(fliptets[spivot].tet, i); + setelemattribute(fliptets[j].tet, i, attrib); + } + if (b->varvolume) { + volume = volumebound(fliptets[spivot].tet); + setvolumebound(fliptets[j].tet, volume); + } + } + } + } + // Delete an old tet. + tetrahedrondealloc(fliptets[2].tet); + + if (hullflag > 0) { + // Check if c is dummypointc. + if (pc != dummypoint) { + // Check if d is dummypoint. + if (pd != dummypoint) { + // No hull tet is involved. + } else { + // We deleted three hull tets, and created one hull tet. + hullsize -= 2; + } + setvertices(fliptets[0], pa, pb, pc, pd); + setvertices(fliptets[1], pb, pa, pc, pe); + } else { + // c is dummypoint. The two new tets are hull tets. + setvertices(fliptets[0], pb, pa, pd, pc); + setvertices(fliptets[1], pa, pb, pe, pc); + // Adjust badc -> abcd. + esymself(fliptets[0]); + // Adjust abec -> bace. + esymself(fliptets[1]); + // The hullsize does not change. + } + } else { + setvertices(fliptets[0], pa, pb, pc, pd); + setvertices(fliptets[1], pb, pa, pc, pe); + } + + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[3], volpos[2], vol_diff; + if (pc != dummypoint) { + if (pd != dummypoint) { + volneg[0] = tetprismvol(pe, pd, pa, pb); + volneg[1] = tetprismvol(pe, pd, pb, pc); + volneg[2] = tetprismvol(pe, pd, pc, pa); + volpos[0] = tetprismvol(pa, pb, pc, pd); + volpos[1] = tetprismvol(pb, pa, pc, pe); + } else { // pd == dummypoint + volneg[0] = 0.; + volneg[1] = 0.; + volneg[2] = 0.; + volpos[0] = 0.; + volpos[1] = tetprismvol(pb, pa, pc, pe); + } + } else { // pc == dummypoint. + volneg[0] = tetprismvol(pe, pd, pa, pb); + volneg[1] = 0.; + volneg[2] = 0.; + volpos[0] = 0.; + volpos[1] = 0.; + } + vol_diff = volpos[0] + volpos[1] - volneg[0] - volneg[1] - volneg[2]; + fc->tetprism_vol_sum += vol_diff; // Update the total sum. + } + + // Bond abcd <==> bace. + bond(fliptets[0], fliptets[1]); + // Bond new faces to top outer boundary faces (at abcd). + for (i = 0; i < 3; i++) { + esym(fliptets[0], newface); + bond(newface, topcastets[i]); + enextself(fliptets[0]); + } + // Bond new faces to bottom outer boundary faces (at bace). + for (i = 0; i < 3; i++) { + esym(fliptets[1], newface); + bond(newface, botcastets[i]); + eprevself(fliptets[1]); + } + + if (checksubsegflag) { + // Bond 9 segments to new (flipped) tets. + for (i = 0; i < 3; i++) { // edges a->b, b->c, c->a. + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); + tssbond1(fliptets[0], checkseg); + sstbond1(checkseg, fliptets[0]); + tssbond1(fliptets[1], checkseg); + sstbond1(checkseg, fliptets[1]); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + enextself(fliptets[0]); + eprevself(fliptets[1]); + } + // The three top edges. + for (i = 0; i < 3; i++) { // edges b->d, c->d, a->d. + esym(fliptets[0], newface); + eprevself(newface); + enext(topcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + enextself(fliptets[0]); + } + // The three bot edges. + for (i = 0; i < 3; i++) { // edges b<-e, c<-e, a<-e. + esym(fliptets[1], newface); + enextself(newface); + eprev(botcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + eprevself(fliptets[1]); + } + } // if (checksubsegflag) + + if (checksubfaceflag) { + face checksh; + // Bond the top three casing subfaces. + for (i = 0; i < 3; i++) { // At edges [b,a], [c,b], [a,c] + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); + esym(fliptets[0], newface); + sesymself(checksh); + tsbond(newface, checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + enextself(fliptets[0]); + } + // Bond the bottom three casing subfaces. + for (i = 0; i < 3; i++) { // At edges [a,b], [b,c], [c,a] + if (issubface(botcastets[i])) { + tspivot(botcastets[i], checksh); + esym(fliptets[1], newface); + sesymself(checksh); + tsbond(newface, checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + eprevself(fliptets[1]); + } + + if (scount > 0) { + face flipfaces[2]; + // Perform a 2-to-2 flip in subfaces. + flipfaces[0] = flipshs[(spivot + 1) % 3]; + flipfaces[1] = flipshs[(spivot + 2) % 3]; + sesymself(flipfaces[1]); + flip22(flipfaces, 0, fc->chkencflag); + // Connect the flipped subfaces to flipped tets. + // First go to the corresponding flipping edge. + // Re-use top- and botcastets[0]. + topcastets[0] = fliptets[0]; + botcastets[0] = fliptets[1]; + for (i = 0; i < ((spivot + 1) % 3); i++) { + enextself(topcastets[0]); + eprevself(botcastets[0]); + } + // Connect the top subface to the top tets. + esymself(topcastets[0]); + sesymself(flipfaces[0]); + // Check if there already exists a subface. + tspivot(topcastets[0], checksh); + if (checksh.sh == NULL) { + tsbond(topcastets[0], flipfaces[0]); + fsymself(topcastets[0]); + sesymself(flipfaces[0]); + tsbond(topcastets[0], flipfaces[0]); + } else { + // An invalid 2-to-2 flip. Report a bug. + terminatetetgen(this, 2); + } + // Connect the bot subface to the bottom tets. + esymself(botcastets[0]); + sesymself(flipfaces[1]); + // Check if there already exists a subface. + tspivot(botcastets[0], checksh); + if (checksh.sh == NULL) { + tsbond(botcastets[0], flipfaces[1]); + fsymself(botcastets[0]); + sesymself(flipfaces[1]); + tsbond(botcastets[0], flipfaces[1]); + } else { + // An invalid 2-to-2 flip. Report a bug. + terminatetetgen(this, 2); + } + } // if (scount > 0) + } // if (checksubfaceflag) + + if (fc->chkencflag & 4) { + // Put two new tets into check list. + for (i = 0; i < 2; i++) { + enqueuetetrahedron(&(fliptets[i])); + } + } + + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[0].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); + setpoint2tet(pe, (tetrahedron) fliptets[1].tet); + + if (hullflag > 0) { + if (dummyflag != 0) { + // Restore the original position of the points (for flipnm()). + if (dummyflag == -1) { + // e were dummypoint. Swap the two new tets. + newface = fliptets[0]; + fliptets[0] = fliptets[1]; + fliptets[1] = newface; + } else { + // a or b was dummypoint. + if (dummyflag == 1) { + eprevself(fliptets[0]); + enextself(fliptets[1]); + } else { // dummyflag == 2 + enextself(fliptets[0]); + eprevself(fliptets[1]); + } + } + } + } + + if (fc->enqflag > 0) { + // Queue faces which may be locally non-Delaunay. + // pa = org(fliptets[0]); // 'a' may be a new vertex. + enextesym(fliptets[0], newface); + flippush(flipstack, &newface); + eprevesym(fliptets[1], newface); + flippush(flipstack, &newface); + if (fc->enqflag > 1) { + //pb = dest(fliptets[0]); + eprevesym(fliptets[0], newface); + flippush(flipstack, &newface); + enextesym(fliptets[1], newface); + flippush(flipstack, &newface); + //pc = apex(fliptets[0]); + esym(fliptets[0], newface); + flippush(flipstack, &newface); + esym(fliptets[1], newface); + flippush(flipstack, &newface); + } + } + + recenttet = fliptets[0]; +} + +//============================================================================// +// // +// flip41() Perform a 4-to-1 flip (Remove a vertex). // +// // +// 'fliptets' is an array of four tetrahedra in the star of the removing // +// vertex 'p'. Let the four vertices in the star of p be a, b, c, and d. The // +// four tets in 'fliptets' are: [p,d,a,b], [p,d,b,c], [p,d,c,a], and [a,b,c, // +// p]. On return, 'fliptets[0]' is the new tet [a,b,c,d]. // +// // +// If 'hullflag' is set (> 0), one of the five vertices may be 'dummypoint'. // +// The 'hullsize' may be changed. Note that p may be dummypoint. In this // +// case, four hull tets are replaced by one real tet. // +// // +// If 'checksubface' flag is set (>0), it is possible that there are three // +// interior subfaces connecting at p. If so, a 3-to-1 flip is performed to // +// to remove p from the surface triangulation. // +// // +// If it is called by the routine incrementalflip(), we assume that d is the // +// newly inserted vertex. // +// // +//============================================================================// + +void tetgenmesh::flip41(triface* fliptets, int hullflag, flipconstraints *fc) +{ + triface topcastets[3], botcastet; + triface newface, neightet; + face flipshs[4]; + point pa, pb, pc, pd, pp; + int dummyflag = 0; // in {0, 1, 2, 3, 4} + int spivot = -1, scount = 0; + int t1ver; + int i; + + pa = org(fliptets[3]); + pb = dest(fliptets[3]); + pc = apex(fliptets[3]); + pd = dest(fliptets[0]); + pp = org(fliptets[0]); // The removing vertex. + + flip41count++; + + // Get the outer boundary faces. + for (i = 0; i < 3; i++) { + enext(fliptets[i], topcastets[i]); + fnextself(topcastets[i]); // [d,a,b,#], [d,b,c,#], [d,c,a,#] + enextself(topcastets[i]); // [a,b,d,#], [b,c,d,#], [c,a,d,#] + } + fsym(fliptets[3], botcastet); // [b,a,c,#] + + if (checksubfaceflag) { + // Check if there are three subfaces at 'p'. + // Re-use 'newface'. + for (i = 0; i < 3; i++) { + fnext(fliptets[3], newface); // [a,b,p,d],[b,c,p,d],[c,a,p,d]. + tspivot(newface, flipshs[i]); + if (flipshs[i].sh != NULL) { + spivot = i; // Remember this subface. + scount++; + } + enextself(fliptets[3]); + } + if (scount > 0) { + // There are three subfaces connecting at p. + if (scount < 3) { + // The new subface is one of {[a,b,d], [b,c,d], [c,a,d]}. + // Go to the tet containing the three subfaces. + fsym(topcastets[spivot], neightet); + // Get the three subfaces connecting at p. + for (i = 0; i < 3; i++) { + esym(neightet, newface); + tspivot(newface, flipshs[i]); + eprevself(neightet); + } + } else { + spivot = 3; // The new subface is [a,b,c]. + } + } + } // if (checksubfaceflag) + + + // Re-use fliptets[0] for [a,b,c,d]. + fliptets[0].ver = 11; + setelemmarker(fliptets[0].tet, 0); // Clean all flags. + // NOTE: the element attributes and volume constraint remain unchanged. + if (checksubsegflag) { + // Dealloc the space to subsegments. + if (fliptets[0].tet[8] != NULL) { + tet2segpool->dealloc((shellface *) fliptets[0].tet[8]); + fliptets[0].tet[8] = NULL; + } + } + if (checksubfaceflag) { + // Dealloc the space to subfaces. + if (fliptets[0].tet[9] != NULL) { + tet2subpool->dealloc((shellface *) fliptets[0].tet[9]); + fliptets[0].tet[9] = NULL; + } + } + // Delete the other three tets. + for (i = 1; i < 4; i++) { + tetrahedrondealloc(fliptets[i].tet); + } + + if (pp != dummypoint) { + // Mark the point pp as unused. + setpointtype(pp, UNUSEDVERTEX); + unuverts++; + } + + // Create the new tet [a,b,c,d]. + if (hullflag > 0) { + // One of the five vertices may be 'dummypoint'. + if (pa == dummypoint) { + // pa is dummypoint. + setvertices(fliptets[0], pc, pb, pd, pa); + esymself(fliptets[0]); // [b,c,a,d] + eprevself(fliptets[0]); // [a,b,c,d] + dummyflag = 1; + } else if (pb == dummypoint) { + setvertices(fliptets[0], pa, pc, pd, pb); + esymself(fliptets[0]); // [c,a,b,d] + enextself(fliptets[0]); // [a,b,c,d] + dummyflag = 2; + } else if (pc == dummypoint) { + setvertices(fliptets[0], pb, pa, pd, pc); + esymself(fliptets[0]); // [a,b,c,d] + dummyflag = 3; + } else if (pd == dummypoint) { + setvertices(fliptets[0], pa, pb, pc, pd); + dummyflag = 4; + } else { + setvertices(fliptets[0], pa, pb, pc, pd); + if (pp == dummypoint) { + dummyflag = -1; + } else { + dummyflag = 0; + } + } + if (dummyflag > 0) { + // We deleted 3 hull tets, and create 1 hull tet. + hullsize -= 2; + } else if (dummyflag < 0) { + // We deleted 4 hull tets. + hullsize -= 4; + // meshedges does not change. + } + } else { + setvertices(fliptets[0], pa, pb, pc, pd); + } + + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[4], volpos[1], vol_diff; + if (dummyflag > 0) { + if (pa == dummypoint) { + volneg[0] = 0.; + volneg[1] = tetprismvol(pp, pd, pb, pc); + volneg[2] = 0.; + volneg[3] = 0.; + } else if (pb == dummypoint) { + volneg[0] = 0.; + volneg[1] = 0.; + volneg[2] = tetprismvol(pp, pd, pc, pa); + volneg[3] = 0.; + } else if (pc == dummypoint) { + volneg[0] = tetprismvol(pp, pd, pa, pb); + volneg[1] = 0.; + volneg[2] = 0.; + volneg[3] = 0.; + } else { // pd == dummypoint + volneg[0] = 0.; + volneg[1] = 0.; + volneg[2] = 0.; + volneg[3] = tetprismvol(pa, pb, pc, pp); + } + volpos[0] = 0.; + } else if (dummyflag < 0) { + volneg[0] = 0.; + volneg[1] = 0.; + volneg[2] = 0.; + volneg[3] = 0.; + volpos[0] = tetprismvol(pa, pb, pc, pd); + } else { + volneg[0] = tetprismvol(pp, pd, pa, pb); + volneg[1] = tetprismvol(pp, pd, pb, pc); + volneg[2] = tetprismvol(pp, pd, pc, pa); + volneg[3] = tetprismvol(pa, pb, pc, pp); + volpos[0] = tetprismvol(pa, pb, pc, pd); + } + vol_diff = volpos[0] - volneg[0] - volneg[1] - volneg[2] - volneg[3]; + fc->tetprism_vol_sum += vol_diff; // Update the total sum. + } + + // Bond the new tet to adjacent tets. + for (i = 0; i < 3; i++) { + esym(fliptets[0], newface); // At faces [b,a,d], [c,b,d], [a,c,d]. + bond(newface, topcastets[i]); + enextself(fliptets[0]); + } + bond(fliptets[0], botcastet); + + if (checksubsegflag) { + face checkseg; + // Bond 6 segments (at edges of [a,b,c,d]) if there there are. + for (i = 0; i < 3; i++) { + eprev(topcastets[i], newface); // At edges [d,a],[d,b],[d,c]. + if (issubseg(newface)) { + tsspivot1(newface, checkseg); + esym(fliptets[0], newface); + enextself(newface); // At edges [a,d], [b,d], [c,d]. + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + enextself(fliptets[0]); + } + for (i = 0; i < 3; i++) { + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); // At edges [a,b],[b,c],[c,a]. + tssbond1(fliptets[0], checkseg); + sstbond1(checkseg, fliptets[0]); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + enextself(fliptets[0]); + } + } + + if (checksubfaceflag) { + face checksh; + // Bond 4 subfaces (at faces of [a,b,c,d]) if there are. + for (i = 0; i < 3; i++) { + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); // At faces [a,b,d],[b,c,d],[c,a,d] + esym(fliptets[0], newface); // At faces [b,a,d],[c,b,d],[a,c,d] + sesymself(checksh); + tsbond(newface, checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + enextself(fliptets[0]); + } + if (issubface(botcastet)) { + tspivot(botcastet, checksh); // At face [b,a,c] + sesymself(checksh); + tsbond(fliptets[0], checksh); + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); + } + } + + if (spivot >= 0) { + // Perform a 3-to-1 flip in surface triangulation. + // Depending on the value of 'spivot', the three subfaces are: + // - 0: [a,b,p], [b,d,p], [d,a,p] + // - 1: [b,c,p], [c,d,p], [d,b,p] + // - 2: [c,a,p], [a,d,p], [d,c,p] + // - 3: [a,b,p], [b,c,p], [c,a,p] + // Adjust the three subfaces such that their origins are p, i.e., + // - 3: [p,a,b], [p,b,c], [p,c,a]. (Required by the flip31()). + for (i = 0; i < 3; i++) { + senext2self(flipshs[i]); + } + flip31(flipshs, 0); + // Delete the three old subfaces. + for (i = 0; i < 3; i++) { + shellfacedealloc(subfaces, flipshs[i].sh); + } + if (spivot < 3) { + // // Bond the new subface to the new tet [a,b,c,d]. + tsbond(topcastets[spivot], flipshs[3]); + fsym(topcastets[spivot], newface); + sesym(flipshs[3], checksh); + tsbond(newface, checksh); + } else { + // Bound the new subface [a,b,c] to the new tet [a,b,c,d]. + tsbond(fliptets[0], flipshs[3]); + fsym(fliptets[0], newface); + sesym(flipshs[3], checksh); + tsbond(newface, checksh); + } + } // if (spivot > 0) + } // if (checksubfaceflag) + + if (fc->chkencflag & 4) { + enqueuetetrahedron(&(fliptets[0])); + } + + // Update the point-to-tet map. + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[0].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); + + if (fc->enqflag > 0) { + // Queue faces which may be locally non-Delaunay. + flippush(flipstack, &(fliptets[0])); // [a,b,c] (opposite to new point). + if (fc->enqflag > 1) { + for (i = 0; i < 3; i++) { + esym(fliptets[0], newface); + flippush(flipstack, &newface); + enextself(fliptets[0]); + } + } + } + + recenttet = fliptets[0]; +} + +//============================================================================// +// // +// flipnm() Flip an edge through a sequence of elementary flips. // +// // +// 'abtets' is an array of 'n' tets in the star of edge [a,b].These tets are // +// ordered in a counterclockwise cycle with respect to the vector a->b, i.e., // +// use the right-hand rule. // +// // +// 'level' (>= 0) indicates the current link level. If 'level > 0', we are // +// flipping a link edge of an edge [a',b'], and 'abedgepivot' indicates // +// which link edge, i.e., [c',b'] or [a',c'], is [a,b] These two parameters // +// allow us to determine the new tets after a 3-to-2 flip, i.e., tets that // +// do not inside the reduced star of edge [a',b']. // +// // +// If the flag 'fc->unflip' is set, this routine un-does the flips performed // +// in flipnm([a,b]) so that the mesh is returned to its original state // +// before doing the flipnm([a,b]) operation. // +// // +// The return value is an integer nn, where nn <= n. If nn is 2, then the // +// edge is flipped. The first and the second tets in 'abtets' are new tets. // +// Otherwise, nn > 2, the edge is not flipped, and nn is the number of tets // +// in the current star of [a,b]. // +// // +// ASSUMPTIONS: // +// - Neither a nor b is 'dummypoint'. // +// - [a,b] must not be a segment. // +// // +//============================================================================// + +int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, + flipconstraints* fc) +{ + triface fliptets[3], spintet, flipedge; + triface *tmpabtets, *parytet; + point pa, pb, pc, pd, pe, pf; + REAL ori; + int hullflag, hulledgeflag; + int reducflag, rejflag; + int reflexlinkedgecount; + int edgepivot; + int n1, nn; + int t1ver; + int i, j; + + pa = org(abtets[0]); + pb = dest(abtets[0]); + + if (n > 3) { + // Try to reduce the size of the Star(ab) by flipping a face in it. + reflexlinkedgecount = 0; + + for (i = 0; i < n; i++) { + // Let the face of 'abtets[i]' be [a,b,c]. + if (checksubfaceflag) { + if (issubface(abtets[i])) { + continue; // Skip a subface. + } + } + // Do not flip this face if it is involved in two Stars. + if ((elemcounter(abtets[i]) > 1) || + (elemcounter(abtets[(i - 1 + n) % n]) > 1)) { + continue; + } + + pc = apex(abtets[i]); + pd = apex(abtets[(i + 1) % n]); + pe = apex(abtets[(i - 1 + n) % n]); + if ((pd == dummypoint) || (pe == dummypoint)) { + continue; // [a,b,c] is a hull face. + } + + + // Decide whether [a,b,c] is flippable or not. + reducflag = 0; + + hullflag = (pc == dummypoint); // pc may be dummypoint. + hulledgeflag = 0; + if (hullflag == 0) { + ori = orient3d(pb, pc, pd, pe); // Is [b,c] locally convex? + if (ori > 0) { + ori = orient3d(pc, pa, pd, pe); // Is [c,a] locally convex? + if (ori > 0) { + // Test if [a,b] is locally convex OR flat. + ori = orient3d(pa, pb, pd, pe); + if (ori > 0) { + // Found a 2-to-3 flip: [a,b,c] => [e,d] + reducflag = 1; + } else if (ori == 0) { + // [a,b] is flat. + if (n == 4) { + // The "flat" tet can be removed immediately by a 3-to-2 flip. + reducflag = 1; + // Check if [e,d] is a hull edge. + pf = apex(abtets[(i + 2) % n]); + hulledgeflag = (pf == dummypoint); + } + } + } + } + if (!reducflag) { + reflexlinkedgecount++; + } + } else { + // 'c' is dummypoint. + if (n == 4) { + // Let the vertex opposite to 'c' is 'f'. + // A 4-to-4 flip is possible if the two tets [d,e,f,a] and [e,d,f,b] + // are valid tets. + // Note: When the mesh is not convex, it is possible that [a,b] is + // locally non-convex (at hull faces [a,b,e] and [b,a,d]). + // In this case, an edge flip [a,b] to [e,d] is still possible. + pf = apex(abtets[(i + 2) % n]); + ori = orient3d(pd, pe, pf, pa); + if (ori < 0) { + ori = orient3d(pe, pd, pf, pb); + if (ori < 0) { + // Found a 4-to-4 flip: [a,b] => [e,d] + reducflag = 1; + ori = 0; // Signal as a 4-to-4 flip (like a co-planar case). + hulledgeflag = 1; // [e,d] is a hull edge. + } + } + } + } // if (hullflag) + + if (reducflag) { + if (nonconvex && hulledgeflag) { + // We will create a hull edge [e,d]. Make sure it does not exist. + if (getedge(pe, pd, &spintet)) { + // The 2-to-3 flip is not a topological valid flip. + reducflag = 0; + } + } + } + + + + if (reducflag) { + triface checktet = abtets[i]; + if (!valid_constrained_f23(checktet, pd, pe)) { + reducflag = 0; + } + } + + if (reducflag) { + // [a,b,c] could be removed by a 2-to-3 flip. + rejflag = 0; + if (fc->checkflipeligibility) { + // Check if the flip can be performed. + rejflag = checkflipeligibility(1, pa, pb, pc, pd, pe, level, + abedgepivot, fc); + } + if (!rejflag) { + // Do flip: [a,b,c] => [e,d]. + fliptets[0] = abtets[i]; + fsym(fliptets[0], fliptets[1]); // abtets[i-1]. + flip23(fliptets, hullflag, fc); + + // Shrink the array 'abtets', maintain the original order. + // Two tets 'abtets[i-1] ([a,b,e,c])' and 'abtets[i] ([a,b,c,d])' + // are flipped, i.e., they do not in Star(ab) anymore. + // 'fliptets[0]' ([e,d,a,b]) is in Star(ab), it is saved in + // 'abtets[i-1]' (adjust it to be [a,b,e,d]), see below: + // + // before after + // [0] |___________| [0] |___________| + // ... |___________| ... |___________| + // [i-1] |_[a,b,e,c]_| [i-1] |_[a,b,e,d]_| + // [i] |_[a,b,c,d]_| --> [i] |_[a,b,d,#]_| + // [i+1] |_[a,b,d,#]_| [i+1] |_[a,b,#,*]_| + // ... |___________| ... |___________| + // [n-2] |___________| [n-2] |___________| + // [n-1] |___________| [n-1] |_[i]_2-t-3_| + // + edestoppoself(fliptets[0]); // [a,b,e,d] + // Increase the counter of this new tet (it is in Star(ab)). + increaseelemcounter(fliptets[0]); + abtets[(i - 1 + n) % n] = fliptets[0]; + for (j = i; j < n - 1; j++) { + abtets[j] = abtets[j + 1]; // Upshift + } + // The last entry 'abtets[n-1]' is empty. It is used in two ways: + // (i) it remembers the vertex 'c' (in 'abtets[n-1].tet'), and + // (ii) it remembers the position [i] where this flip took place. + // These information let us to either undo this flip or recover + // the original edge link (for collecting new created tets). + abtets[n - 1].tet = (tetrahedron *) pc; + abtets[n - 1].ver = 0; // Clear it. + // 'abtets[n - 1].ver' is in range [0,11] -- only uses 4 bits. + // Use the 5th bit in 'abtets[n - 1].ver' to signal a 2-to-3 flip. + abtets[n - 1].ver |= (1 << 4); + // The poisition [i] of this flip is saved above the 7th bit. + abtets[n - 1].ver |= (i << 6); + + if (fc->collectnewtets) { + // Push the two new tets [e,d,b,c] and [e,d,c,a] into a stack. + // Re-use the global array 'cavetetlist'. + for (j = 1; j < 3; j++) { + cavetetlist->newindex((void **) &parytet); + *parytet = fliptets[j]; // fliptets[1], fliptets[2]. + } + } + + // Star(ab) is reduced. Try to flip the edge [a,b]. + nn = flipnm(abtets, n - 1, level, abedgepivot, fc); + + if (nn == 2) { + // The edge has been flipped. + return nn; + } else { // if (nn > 2) + // The edge is not flipped. + if (fc->unflip || (ori == 0)) { + // Undo the previous 2-to-3 flip, i.e., do a 3-to-2 flip to + // transform [e,d] => [a,b,c]. + // 'ori == 0' means that the previous flip created a degenerated + // tet. It must be removed. + // Remember that 'abtets[i-1]' is [a,b,e,d]. We can use it to + // find another two tets [e,d,b,c] and [e,d,c,a]. + fliptets[0] = abtets[(i-1 + (n-1)) % (n-1)]; // [a,b,e,d] + edestoppoself(fliptets[0]); // [e,d,a,b] + fnext(fliptets[0], fliptets[1]); // [1] is [e,d,b,c] + fnext(fliptets[1], fliptets[2]); // [2] is [e,d,c,a] + // Restore the two original tets in Star(ab). + flip32(fliptets, hullflag, fc); + // Marktest the two restored tets in Star(ab). + for (j = 0; j < 2; j++) { + increaseelemcounter(fliptets[j]); + } + // Expand the array 'abtets', maintain the original order. + for (j = n - 2; j>= i; j--) { + abtets[j + 1] = abtets[j]; // Downshift + } + // Insert the two new tets 'fliptets[0]' [a,b,c,d] and + // 'fliptets[1]' [b,a,c,e] into the (i-1)-th and i-th entries, + // respectively. + esym(fliptets[1], abtets[(i - 1 + n) % n]); // [a,b,e,c] + abtets[i] = fliptets[0]; // [a,b,c,d] + nn++; + if (fc->collectnewtets) { + // Pop two (flipped) tets from the stack. + cavetetlist->objects -= 2; + } + } // if (unflip || (ori == 0)) + } // if (nn > 2) + + if (!fc->unflip) { + // The flips are not reversed. The current Star(ab) can not be + // further reduced. Return its current size (# of tets). + return nn; + } + // unflip is set. + // Continue the search for flips. + } + } // if (reducflag) + } // i + + // The Star(ab) is not reduced. + if (reflexlinkedgecount > 0) { + // There are reflex edges in the Link(ab). + if (((b->fliplinklevel < 0) && (level < autofliplinklevel)) || + ((b->fliplinklevel >= 0) && (level < b->fliplinklevel))) { + // Try to reduce the Star(ab) by flipping a reflex edge in Link(ab). + for (i = 0; i < n; i++) { + // Do not flip this face [a,b,c] if there are two Stars involved. + if ((elemcounter(abtets[i]) > 1) || + (elemcounter(abtets[(i - 1 + n) % n]) > 1)) { + continue; + } + pc = apex(abtets[i]); + if (pc == dummypoint) { + continue; // [a,b] is a hull edge. + } + pd = apex(abtets[(i + 1) % n]); + pe = apex(abtets[(i - 1 + n) % n]); + if ((pd == dummypoint) || (pe == dummypoint)) { + continue; // [a,b,c] is a hull face. + } + + + edgepivot = 0; // No edge is selected yet. + + // Test if [b,c] is locally convex or flat. + ori = orient3d(pb, pc, pd, pe); + if (ori <= 0) { + // Select the edge [c,b]. + enext(abtets[i], flipedge); // [b,c,a,d] + edgepivot = 1; + } + if (!edgepivot) { + // Test if [c,a] is locally convex or flat. + ori = orient3d(pc, pa, pd, pe); + if (ori <= 0) { + // Select the edge [a,c]. + eprev(abtets[i], flipedge); // [c,a,b,d]. + edgepivot = 2; + } + } + + if (!edgepivot) continue; + + // An edge is selected. + if (checksubsegflag) { + // Do not flip it if it is a segment. + if (issubseg(flipedge)) { + if (fc->collectencsegflag) { + face checkseg, *paryseg; + tsspivot1(flipedge, checkseg); + if (!sinfected(checkseg)) { + // Queue this segment in list. + sinfect(checkseg); + caveencseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } + continue; + } + } + + // Try to flip the selected edge ([c,b] or [a,c]). + esymself(flipedge); + // Count the number of tets at the edge. + int subface_count = 0; + n1 = 0; + j = 0; // Sum of the star counters. + spintet = flipedge; + while (1) { + if (issubface(spintet)) subface_count++; + n1++; + j += (elemcounter(spintet)); + fnextself(spintet); + if (spintet.tet == flipedge.tet) break; + } + if (n1 < 3) { + // This is only possible when the mesh contains inverted + // elements. Reprot a bug. + terminatetetgen(this, 2); + } + if (j > 2) { + // The Star(flipedge) overlaps other Stars. + continue; // Do not flip this edge. + } + + if (fc->noflip_in_surface) { + if (subface_count > 0) { + continue; + } + } + + if ((b->flipstarsize > 0) && (n1 > b->flipstarsize)) { + // The star size exceeds the given limit. + continue; // Do not flip it. + } + + // Allocate spaces for Star(flipedge). + tmpabtets = new triface[n1]; + // Form the Star(flipedge). + spintet = flipedge; + for (j = 0; j < n1; j++) { + tmpabtets[j] = spintet; + // Increase the star counter of this tet. + increaseelemcounter(tmpabtets[j]); + fnextself(spintet); + } + + // Try to flip the selected edge away. + nn = flipnm(tmpabtets, n1, level + 1, edgepivot, fc); + + if (nn == 2) { + // The edge is flipped. Star(ab) is reduced. + // Shrink the array 'abtets', maintain the original order. + if (edgepivot == 1) { + // 'tmpabtets[0]' is [d,a,e,b] => contains [a,b]. + spintet = tmpabtets[0]; // [d,a,e,b] + enextself(spintet); + esymself(spintet); + enextself(spintet); // [a,b,e,d] + } else { + // 'tmpabtets[1]' is [b,d,e,a] => contains [a,b]. + spintet = tmpabtets[1]; // [b,d,e,a] + eprevself(spintet); + esymself(spintet); + eprevself(spintet); // [a,b,e,d] + } // edgepivot == 2 + increaseelemcounter(spintet); // It is in Star(ab). + // Put the new tet at [i-1]-th entry. + abtets[(i - 1 + n) % n] = spintet; + for (j = i; j < n - 1; j++) { + abtets[j] = abtets[j + 1]; // Upshift + } + // Remember the flips in the last entry of the array 'abtets'. + // They can be used to recover the flipped edge. + abtets[n - 1].tet = (tetrahedron *) tmpabtets; // The star(fedge). + abtets[n - 1].ver = 0; // Clear it. + // Use the 1st and 2nd bit to save 'edgepivot' (1 or 2). + abtets[n - 1].ver |= edgepivot; + // Use the 6th bit to signal this n1-to-m1 flip. + abtets[n - 1].ver |= (1 << 5); + // The poisition [i] of this flip is saved from 7th to 19th bit. + abtets[n - 1].ver |= (i << 6); + // The size of the star 'n1' is saved from 20th bit. + abtets[n - 1].ver |= (n1 << 19); + + // Remember the flipped link vertex 'c'. It can be used to recover + // the original edge link of [a,b], and to collect new tets. + tmpabtets[0].tet = (tetrahedron *) pc; + tmpabtets[0].ver = (1 << 5); // Flag it as a vertex handle. + + // Continue to flip the edge [a,b]. + nn = flipnm(abtets, n - 1, level, abedgepivot, fc); + + if (nn == 2) { + // The edge has been flipped. + return nn; + } else { // if (nn > 2) { + // The edge is not flipped. + if (fc->unflip) { + // Recover the flipped edge ([c,b] or [a,c]). + // The sequence of flips are saved in 'tmpabtets'. + // abtets[(i-1) % (n-1)] is [a,b,e,d], i.e., the tet created by + // the flipping of edge [c,b] or [a,c].It must still exist in + // Star(ab). It is the start tet to recover the flipped edge. + if (edgepivot == 1) { + // The flip edge is [c,b]. + tmpabtets[0] = abtets[((i-1)+(n-1))%(n-1)]; // [a,b,e,d] + eprevself(tmpabtets[0]); + esymself(tmpabtets[0]); + eprevself(tmpabtets[0]); // [d,a,e,b] + fsym(tmpabtets[0], tmpabtets[1]); // [a,d,e,c] + } else { + // The flip edge is [a,c]. + tmpabtets[1] = abtets[((i-1)+(n-1))%(n-1)]; // [a,b,e,d] + enextself(tmpabtets[1]); + esymself(tmpabtets[1]); + enextself(tmpabtets[1]); // [b,d,e,a] + fsym(tmpabtets[1], tmpabtets[0]); // [d,b,e,c] + } // if (edgepivot == 2) + + // Recover the flipped edge ([c,b] or [a,c]). + flipnm_post(tmpabtets, n1, 2, edgepivot, fc); + + // Insert the two recovered tets into Star(ab). + for (j = n - 2; j >= i; j--) { + abtets[j + 1] = abtets[j]; // Downshift + } + if (edgepivot == 1) { + // tmpabtets[0] is [c,b,d,a] ==> contains [a,b] + // tmpabtets[1] is [c,b,a,e] ==> contains [a,b] + // tmpabtets[2] is [c,b,e,d] + fliptets[0] = tmpabtets[1]; + enextself(fliptets[0]); + esymself(fliptets[0]); // [a,b,e,c] + fliptets[1] = tmpabtets[0]; + esymself(fliptets[1]); + eprevself(fliptets[1]); // [a,b,c,d] + } else { + // tmpabtets[0] is [a,c,d,b] ==> contains [a,b] + // tmpabtets[1] is [a,c,b,e] ==> contains [a,b] + // tmpabtets[2] is [a,c,e,d] + fliptets[0] = tmpabtets[1]; + eprevself(fliptets[0]); + esymself(fliptets[0]); // [a,b,e,c] + fliptets[1] = tmpabtets[0]; + esymself(fliptets[1]); + enextself(fliptets[1]); // [a,b,c,d] + } // edgepivot == 2 + for (j = 0; j < 2; j++) { + increaseelemcounter(fliptets[j]); + } + // Insert the two recovered tets into Star(ab). + abtets[(i - 1 + n) % n] = fliptets[0]; + abtets[i] = fliptets[1]; + nn++; + // Release the allocated spaces. + delete [] tmpabtets; + } // if (unflip) + } // if (nn > 2) + + if (!fc->unflip) { + // The flips are not reversed. The current Star(ab) can not be + // further reduced. Return its size (# of tets). + return nn; + } + // unflip is set. + // Continue the search for flips. + } else { + // The selected edge is not flipped. + if (!fc->unflip) { + // Release the memory used in this attempted flip. + flipnm_post(tmpabtets, n1, nn, edgepivot, fc); + } + // Decrease the star counters of tets in Star(flipedge). + for (j = 0; j < nn; j++) { + decreaseelemcounter(tmpabtets[j]); + } + // Release the allocated spaces. + delete [] tmpabtets; + } + } // i + } // if (level...) + } // if (reflexlinkedgecount > 0) + } else { + // Check if a 3-to-2 flip is possible. + // Let the three apexes be c, d,and e. Hull tets may be involved. If so, + // we rearrange them such that the vertex e is dummypoint. + hullflag = 0; + + if (apex(abtets[0]) == dummypoint) { + pc = apex(abtets[1]); + pd = apex(abtets[2]); + pe = apex(abtets[0]); + hullflag = 1; + } else if (apex(abtets[1]) == dummypoint) { + pc = apex(abtets[2]); + pd = apex(abtets[0]); + pe = apex(abtets[1]); + hullflag = 2; + } else { + pc = apex(abtets[0]); + pd = apex(abtets[1]); + pe = apex(abtets[2]); + hullflag = (pe == dummypoint) ? 3 : 0; + } + + reducflag = 0; + rejflag = 0; + + + if (hullflag == 0) { + // Make sure that no inverted tet will be created, i.e. the new tets + // [d,c,e,a] and [c,d,e,b] must be valid tets. + ori = orient3d(pd, pc, pe, pa); + if (ori < 0) { + ori = orient3d(pc, pd, pe, pb); + if (ori < 0) { + reducflag = 1; + } + } + } else { + // [a,b] is a hull edge. + // Note: This can happen when it is in the middle of a 4-to-4 flip. + // Note: [a,b] may even be a non-convex hull edge. + if (!nonconvex) { + // The mesh is convex, only do flip if it is a coplanar hull edge. + ori = orient3d(pa, pb, pc, pd); + if (ori == 0) { + reducflag = 1; + } + } else { // nonconvex + reducflag = 1; + } + if (reducflag == 1) { + // [a,b], [a,b,c] and [a,b,d] are on the convex hull. + // Make sure that no inverted tet will be created. + point searchpt = NULL, chkpt; + REAL bigvol = 0.0, ori1, ori2; + // Search an interior vertex which is an apex of edge [c,d]. + // In principle, it can be arbitrary interior vertex. To avoid + // numerical issue, we choose the vertex which belongs to a tet + // 't' at edge [c,d] and 't' has the biggest volume. + fliptets[0] = abtets[hullflag % 3]; // [a,b,c,d]. + eorgoppoself(fliptets[0]); // [d,c,b,a] + spintet = fliptets[0]; + while (1) { + fnextself(spintet); + chkpt = oppo(spintet); + if (chkpt == pb) break; + if ((chkpt != dummypoint) && (apex(spintet) != dummypoint)) { + ori = -orient3d(pd, pc, apex(spintet), chkpt); + if (ori > bigvol) { + bigvol = ori; + searchpt = chkpt; + } + } + } + if (searchpt != NULL) { + // Now valid the configuration. + ori1 = orient3d(pd, pc, searchpt, pa); + ori2 = orient3d(pd, pc, searchpt, pb); + if (ori1 * ori2 >= 0.0) { + reducflag = 0; // Not valid. + } else { + ori1 = orient3d(pa, pb, searchpt, pc); + ori2 = orient3d(pa, pb, searchpt, pd); + if (ori1 * ori2 >= 0.0) { + reducflag = 0; // Not valid. + } + } + } else { + // No valid searchpt is found. + reducflag = 0; // Do not flip it. + } + } // if (reducflag == 1) + } // if (hullflag == 1) + + if (reducflag) { + // A 3-to-2 flip is possible. + if (checksubfaceflag) { + // This edge (must not be a segment) can be flipped ONLY IF it belongs + // to either 0 or 2 subfaces. In the latter case, a 2-to-2 flip in + // the surface mesh will be automatically performed within the + // 3-to-2 flip. + nn = 0; + edgepivot = -1; // Re-use it. + for (j = 0; j < 3; j++) { + if (issubface(abtets[j])) { + nn++; // Found a subface. + } else { + edgepivot = j; + } + } + if (nn == 1) { + // Found only 1 subface containing this edge. This can happen in + // the boundary recovery phase. The neighbor subface is not yet + // recovered. This edge should not be flipped at this moment. + rejflag = 1; + } else if (nn == 2) { + // Found two subfaces. A 2-to-2 flip is possible. Validate it. + // Below we check if the two faces [p,q,a] and [p,q,b] are subfaces. + eorgoppo(abtets[(edgepivot + 1) % 3], spintet); // [q,p,b,a] + if (issubface(spintet)) { + rejflag = 1; // Conflict to a 2-to-2 flip. + } else { + esymself(spintet); + if (issubface(spintet)) { + rejflag = 1; // Conflict to a 2-to-2 flip. + } + } + } else if (nn == 3) { + // Report a bug. + terminatetetgen(this, 2); + } + } + + if (!rejflag) { + if (!valid_constrained_f32(abtets, pa, pb)) { + rejflag = 1; + } + } + + if (!rejflag && fc->checkflipeligibility) { + // Here we must exchange 'a' and 'b'. Since in the check... function, + // we assume the following point sequence, 'a,b,c,d,e', where + // the face [a,b,c] will be flipped and the edge [e,d] will be + // created. The two new tets are [a,b,c,d] and [b,a,c,e]. + rejflag = checkflipeligibility(2, pc, pd, pe, pb, pa, level, + abedgepivot, fc); + } + if (!rejflag) { + // Do flip: [a,b] => [c,d,e] + flip32(abtets, hullflag, fc); + if (fc->remove_ndelaunay_edge) { + if (level == 0) { + // It is the desired removing edge. Check if we have improved + // the objective function. + if ((fc->tetprism_vol_sum >= 0.0) || + (fabs(fc->tetprism_vol_sum) < fc->bak_tetprism_vol)) { + // No improvement! flip back: [c,d,e] => [a,b]. + flip23(abtets, hullflag, fc); + // Increase the element counter -- They are in cavity. + for (j = 0; j < 3; j++) { + increaseelemcounter(abtets[j]); + } + return 3; + } + } // if (level == 0) + } + if (fc->collectnewtets) { + // Collect new tets. + if (level == 0) { + // Push the two new tets into stack. + for (j = 0; j < 2; j++) { + cavetetlist->newindex((void **) &parytet); + *parytet = abtets[j]; + } + } else { + // Only one of the new tets is collected. The other one is inside + // the reduced edge star. 'abedgepivot' is either '1' or '2'. + cavetetlist->newindex((void **) &parytet); + if (abedgepivot == 1) { // [c,b] + *parytet = abtets[1]; + } else { + *parytet = abtets[0]; + } + } + } // if (fc->collectnewtets) + return 2; + } + } // if (reducflag) + } // if (n == 3) + + // The current (reduced) Star size. + return n; +} + +//============================================================================// +// // +// flipnm_post() Post process a n-to-m flip. // +// // +// IMPORTANT: This routine only works when there is no other flip operation // +// is done after flipnm([a,b]) which attempts to remove an edge [a,b]. // +// // +// 'abtets' is an array of 'n' (>= 3) tets which are in the original star of // +// [a,b] before flipnm([a,b]). 'nn' (< n) is the value returned by flipnm. // +// If 'nn == 2', the edge [a,b] has been flipped. 'abtets[0]' and 'abtets[1]' // +// are [c,d,e,b] and [d,c,e,a], i.e., a 2-to-3 flip can recover the edge [a, // +// b] and its initial Star([a,b]). If 'nn >= 3' edge [a,b] still exists in // +// current mesh and 'nn' is the current number of tets in Star([a,b]). // +// // +// Each 'abtets[i]', where nn <= i < n, saves either a 2-to-3 flip or a // +// flipnm([p1,p2]) operation ([p1,p2] != [a,b]) which created the tet // +// 'abtets[t-1]', where '0 <= t <= i'. These information can be used to // +// undo the flips performed in flipnm([a,b]) or to collect new tets created // +// by the flipnm([a,b]) operation. // +// // +// Default, this routine only walks through the flips and frees the spaces // +// allocated during the flipnm([a,b]) operation. // +// // +// If the flag 'fc->unflip' is set, this routine un-does the flips performed // +// in flipnm([a,b]) so that the mesh is returned to its original state // +// before doing the flipnm([a,b]) operation. // +// // +// // +//============================================================================// + +int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, + flipconstraints* fc) +{ + triface fliptets[3], flipface; + triface *tmpabtets; + int fliptype; + int edgepivot; + int t, n1; + int i, j; + + + if (nn == 2) { + // The edge [a,b] has been flipped. + // 'abtets[0]' is [c,d,e,b] or [#,#,#,b]. + // 'abtets[1]' is [d,c,e,a] or [#,#,#,a]. + if (fc->unflip) { + // Do a 2-to-3 flip to recover the edge [a,b]. There may be hull tets. + flip23(abtets, 1, fc); + if (fc->collectnewtets) { + // Pop up new (flipped) tets from the stack. + if (abedgepivot == 0) { + // Two new tets were collected. + cavetetlist->objects -= 2; + } else { + // Only one of the two new tets was collected. + cavetetlist->objects -= 1; + } + } + } + // The initial size of Star(ab) is 3. + nn++; + } + + // Walk through the performed flips. + for (i = nn; i < n; i++) { + // At the beginning of each step 'i', the size of the Star([a,b]) is 'i'. + // At the end of this step, the size of the Star([a,b]) is 'i+1'. + // The sizes of the Link([a,b]) are the same. + fliptype = ((abtets[i].ver >> 4) & 3); // 0, 1, or 2. + if (fliptype == 1) { + // It was a 2-to-3 flip: [a,b,c]->[e,d]. + t = (abtets[i].ver >> 6); + if (fc->unflip) { + if (b->verbose > 3) { + printf(" Recover a 2-to-3 flip at f[%d].\n", t); + } + // 'abtets[(t-1)%i]' is the tet [a,b,e,d] in current Star(ab), i.e., + // it is created by a 2-to-3 flip [a,b,c] => [e,d]. + fliptets[0] = abtets[((t - 1) + i) % i]; // [a,b,e,d] + eprevself(fliptets[0]); + esymself(fliptets[0]); + enextself(fliptets[0]); // [e,d,a,b] + fnext(fliptets[0], fliptets[1]); // [e,d,b,c] + fnext(fliptets[1], fliptets[2]); // [e,d,c,a] + // Do a 3-to-2 flip: [e,d] => [a,b,c]. + // NOTE: hull tets may be invloved. + flip32(fliptets, 1, fc); + // Expand the array 'abtets', maintain the original order. + // The new array length is (i+1). + for (j = i - 1; j >= t; j--) { + abtets[j + 1] = abtets[j]; // Downshift + } + // The tet abtets[(t-1)%i] is deleted. Insert the two new tets + // 'fliptets[0]' [a,b,c,d] and 'fliptets[1]' [b,a,c,e] into + // the (t-1)-th and t-th entries, respectively. + esym(fliptets[1], abtets[((t-1) + (i+1)) % (i+1)]); // [a,b,e,c] + abtets[t] = fliptets[0]; // [a,b,c,d] + if (fc->collectnewtets) { + // Pop up two (flipped) tets from the stack. + cavetetlist->objects -= 2; + } + } + } else if (fliptype == 2) { + tmpabtets = (triface *) (abtets[i].tet); + n1 = ((abtets[i].ver >> 19) & 8191); // \sum_{i=0^12}{2^i} = 8191 + edgepivot = (abtets[i].ver & 3); + t = ((abtets[i].ver >> 6) & 8191); + if (fc->unflip) { + if (b->verbose > 3) { + printf(" Recover a %d-to-m flip at e[%d] of f[%d].\n", n1, + edgepivot, t); + } + // Recover the flipped edge ([c,b] or [a,c]). + // abtets[(t - 1 + i) % i] is [a,b,e,d], i.e., the tet created by + // the flipping of edge [c,b] or [a,c]. It must still exist in + // Star(ab). Use it to recover the flipped edge. + if (edgepivot == 1) { + // The flip edge is [c,b]. + tmpabtets[0] = abtets[(t - 1 + i) % i]; // [a,b,e,d] + eprevself(tmpabtets[0]); + esymself(tmpabtets[0]); + eprevself(tmpabtets[0]); // [d,a,e,b] + fsym(tmpabtets[0], tmpabtets[1]); // [a,d,e,c] + } else { + // The flip edge is [a,c]. + tmpabtets[1] = abtets[(t - 1 + i) % i]; // [a,b,e,d] + enextself(tmpabtets[1]); + esymself(tmpabtets[1]); + enextself(tmpabtets[1]); // [b,d,e,a] + fsym(tmpabtets[1], tmpabtets[0]); // [d,b,e,c] + } // if (edgepivot == 2) + + // Do a n1-to-m1 flip to recover the flipped edge ([c,b] or [a,c]). + flipnm_post(tmpabtets, n1, 2, edgepivot, fc); + + // Insert the two recovered tets into the original Star(ab). + for (j = i - 1; j >= t; j--) { + abtets[j + 1] = abtets[j]; // Downshift + } + if (edgepivot == 1) { + // tmpabtets[0] is [c,b,d,a] ==> contains [a,b] + // tmpabtets[1] is [c,b,a,e] ==> contains [a,b] + // tmpabtets[2] is [c,b,e,d] + fliptets[0] = tmpabtets[1]; + enextself(fliptets[0]); + esymself(fliptets[0]); // [a,b,e,c] + fliptets[1] = tmpabtets[0]; + esymself(fliptets[1]); + eprevself(fliptets[1]); // [a,b,c,d] + } else { + // tmpabtets[0] is [a,c,d,b] ==> contains [a,b] + // tmpabtets[1] is [a,c,b,e] ==> contains [a,b] + // tmpabtets[2] is [a,c,e,d] + fliptets[0] = tmpabtets[1]; + eprevself(fliptets[0]); + esymself(fliptets[0]); // [a,b,e,c] + fliptets[1] = tmpabtets[0]; + esymself(fliptets[1]); + enextself(fliptets[1]); // [a,b,c,d] + } // edgepivot == 2 + // Insert the two recovered tets into Star(ab). + abtets[((t-1) + (i+1)) % (i+1)] = fliptets[0]; + abtets[t] = fliptets[1]; + } + else { + // Only free the spaces. + flipnm_post(tmpabtets, n1, 2, edgepivot, fc); + } // if (!unflip) + if (b->verbose > 3) { + printf(" Release %d spaces at f[%d].\n", n1, i); + } + delete [] tmpabtets; + } + } // i + + return 1; +} + +//============================================================================// +// // +// insertpoint() Insert a point into current tetrahedralization. // +// // +// The Bowyer-Watson (B-W) algorithm is used to add a new point p into the // +// tetrahedralization T. It first finds a "cavity", denoted as C, in T, C // +// consists of tetrahedra in T that "conflict" with p. If T is a Delaunay // +// tetrahedralization, then all boundary faces (triangles) of C are visible // +// by p, i.e.,C is star-shaped. We can insert p into T by first deleting all // +// tetrahedra in C, then creating new tetrahedra formed by boundary faces of // +// C and p. If T is not a DT, then C may be not star-shaped. It must be // +// modified so that it becomes star-shaped. // +// // +//============================================================================// + +int tetgenmesh::insertpoint(point insertpt, triface *searchtet, face *splitsh, + face *splitseg, insertvertexflags *ivf) +{ + arraypool *swaplist; + triface *cavetet, spintet, neightet, neineitet, *parytet; + triface oldtet, newtet, newneitet; + face checksh, neighsh, *parysh; + face checkseg, *paryseg; + point *pts, pa, pb, pc, *parypt; + enum locateresult loc = OUTSIDE; + REAL sign, ori; + REAL attrib, volume; + bool enqflag; + int t1ver; + int i, j, k, s; + + if (b->verbose > 2) { + printf(" Insert point %d\n", pointmark(insertpt)); + } + + // Locate the point. + if (searchtet->tet != NULL) { + loc = (enum locateresult) ivf->iloc; + } + + if (loc == OUTSIDE) { + if (searchtet->tet == NULL) { + if (!b->weighted) { + randomsample(insertpt, searchtet); + } else { + // Weighted DT. There may exist dangling vertex. + *searchtet = recenttet; + } + } + // Locate the point. + loc = locate(insertpt, searchtet); + } + + ivf->iloc = (int) loc; // The return value. + + if (b->weighted) { + if (loc != OUTSIDE) { + // Check if this vertex is regular. + pts = (point *) searchtet->tet; + sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], insertpt, + pts[4][3], pts[5][3], pts[6][3], pts[7][3], + insertpt[3]); + if (sign > 0) { + // This new vertex lies above the lower hull. Do not insert it. + ivf->iloc = (int) NONREGULAR; + return 0; + } + } + } + + // Create the initial cavity C(p) which contains all tetrahedra that + // intersect p. It may include 1, 2, or n tetrahedra. + // If p lies on a segment or subface, also create the initial sub-cavity + // sC(p) which contains all subfaces (and segment) which intersect p. + + if (loc == OUTSIDE) { + flip14count++; + // The current hull will be enlarged. + // Add four adjacent boundary tets into list. + for (i = 0; i < 4; i++) { + decode(searchtet->tet[i], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + } + infect(*searchtet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *searchtet; + } else if (loc == INTETRAHEDRON) { + flip14count++; + // Add four adjacent boundary tets into list. + for (i = 0; i < 4; i++) { + decode(searchtet->tet[i], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + } + infect(*searchtet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *searchtet; + } else if (loc == ONFACE) { + flip26count++; + // Add six adjacent boundary tets into list. + j = (searchtet->ver & 3); // The current face number. + for (i = 1; i < 4; i++) { + decode(searchtet->tet[(j + i) % 4], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + } + decode(searchtet->tet[j], spintet); + j = (spintet.ver & 3); // The current face number. + for (i = 1; i < 4; i++) { + decode(spintet.tet[(j + i) % 4], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + } + infect(spintet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = spintet; + infect(*searchtet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *searchtet; + + if (ivf->splitbdflag) { + if ((splitsh != NULL) && (splitsh->sh != NULL)) { + // Create the initial sub-cavity sC(p). + smarktest(*splitsh); + caveshlist->newindex((void **) &parysh); + *parysh = *splitsh; + } + } // if (splitbdflag) + } else if (loc == ONEDGE) { + flipn2ncount++; + // Add all adjacent boundary tets into list. + spintet = *searchtet; + while (1) { + eorgoppo(spintet, neightet); + decode(neightet.tet[neightet.ver & 3], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + edestoppo(spintet, neightet); + decode(neightet.tet[neightet.ver & 3], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + infect(spintet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = spintet; + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + + if (ivf->splitbdflag) { + // Create the initial sub-cavity sC(p). + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + smarktest(*splitseg); + splitseg->shver = 0; + spivot(*splitseg, *splitsh); + } + if (splitsh != NULL) { + if (splitsh->sh != NULL) { + // Collect all subfaces share at this edge. + pa = sorg(*splitsh); + neighsh = *splitsh; + while (1) { + // Adjust the origin of its edge to be 'pa'. + if (sorg(neighsh) != pa) { + sesymself(neighsh); + } + // Add this face into list (in B-W cavity). + smarktest(neighsh); + caveshlist->newindex((void **) &parysh); + *parysh = neighsh; + // Add this face into face-at-splitedge list. + cavesegshlist->newindex((void **) &parysh); + *parysh = neighsh; + // Go to the next face at the edge. + spivotself(neighsh); + // Stop if all faces at the edge have been visited. + if (neighsh.sh == splitsh->sh) break; + if (neighsh.sh == NULL) break; + } // while (1) + } // if (not a dangling segment) + } + } // if (splitbdflag) + } else if (loc == INSTAR) { + // We assume that all tets in the star are given in 'caveoldtetlist', + // and they are all infected. + if (cavebdrylist->objects == 0) { + // Collect the boundary faces of the star. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + // Check its 4 neighbor tets. + for (j = 0; j < 4; j++) { + decode(cavetet->tet[j], neightet); + if (!infected(neightet)) { + // It's a boundary face. + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + } + } + } + } // if (cavebdrylist->objects == 0) + } else if (loc == ONVERTEX) { + // The point already exist. Do nothing and return. + return 0; + } else if (loc == ENCSUBFACE) { + ivf->iloc = (int) ENCSUBFACE; + return 0; + } else { + // Unknown case + terminatetetgen(this, 2); + } + + if (ivf->collect_inial_cavity_flag) { + tetrahedron **ptptr; + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = cavetet->tet; + } + // Do not insert this point. + insertpoint_abort(splitseg, ivf); + // ivf->iloc = NULLCAVITY; + return 0; + } // if (ivf->collect_inial_cavity_flag) + + if ((b->plc || b->quality) && (loc != INSTAR)) { + // Reject the new point if it lies too close to an existing point (b->plc), + // or it lies inside a protecting ball of near vertex (ivf->rejflag & 4). + // Collect the list of vertices of the initial cavity. + if (loc == OUTSIDE) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 3; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } else if (loc == INTETRAHEDRON) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 4; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } else if (loc == ONFACE) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 3; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + if (pts[3] != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[3]; + } + fsym(*searchtet, spintet); + if (oppo(spintet) != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = oppo(spintet); + } + } else if (loc == ONEDGE) { + spintet = *searchtet; + cavetetvertlist->newindex((void **) &parypt); + *parypt = org(spintet); + cavetetvertlist->newindex((void **) &parypt); + *parypt = dest(spintet); + while (1) { + if (apex(spintet) != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = apex(spintet); + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } + } + + int rejptflag = (ivf->rejflag & 4); + REAL rd, ins_radius; + pts = NULL; + + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + rd = distance(*parypt, insertpt); + // Is the point very close to an existing point? + if (rd < minedgelength) { + if ((!create_a_shorter_edge(insertpt, *parypt)) && + (!ivf->ignore_near_vertex)) { + pts = parypt; + loc = NEARVERTEX; + break; + } + } + if (ivf->check_insert_radius) { //if (useinsertradius) { + ins_radius = getpointinsradius(*parypt); + if (ins_radius > 0.0) { + if (rd < ins_radius) { + if (!create_a_shorter_edge(insertpt, *parypt)) { + // Reject the isnertion of this vertex. + pts = parypt; + loc = ENCVERTEX; + break; + } + } + } + } + if (rejptflag) { + // Is the point encroaches upon an existing point? + if (rd < (0.5 * (*parypt)[pointmtrindex])) { + pts = parypt; + loc = ENCVERTEX; + break; + } + } + } + cavetetvertlist->restart(); // Clear the work list. + + if (pts != NULL) { + // The point is either too close to an existing vertex (NEARVERTEX) + // or encroaches upon (inside the protecting ball) of that vertex. + if (loc == NEARVERTEX) { + point2tetorg(*pts, *searchtet); + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) loc; + return 0; + } else { // loc == ENCVERTEX + // The point lies inside the protection ball. + point2tetorg(*pts, *searchtet); + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) loc; + return 0; + } + } + } // if ((b->plc || b->quality) && (loc != INSTAR)) + + + if (ivf->assignmeshsize) { + // Assign mesh size for the new point. + if (bgm != NULL) { + // Interpolate the mesh size from the background mesh. + bgm->decode(point2bgmtet(org(*searchtet)), neightet); + int bgmloc = (int) bgm->scout_point(insertpt, &neightet, 0); + if (bgmloc != (int) OUTSIDE) { + insertpt[pointmtrindex] = + bgm->getpointmeshsize(insertpt, &neightet, bgmloc); + setpoint2bgmtet(insertpt, bgm->encode(neightet)); + } + } else { + insertpt[pointmtrindex] = getpointmeshsize(insertpt,searchtet,(int)loc); + } + } // if (assignmeshsize) + + if (ivf->bowywat) { + // Update the cavity C(p) using the Bowyer-Watson algorithm. + swaplist = cavetetlist; + cavetetlist = cavebdrylist; + cavebdrylist = swaplist; + for (i = 0; i < cavetetlist->objects; i++) { + // 'cavetet' is an adjacent tet at outside of the cavity. + cavetet = (triface *) fastlookup(cavetetlist, i); + // The tet may be tested and included in the (enlarged) cavity. + if (!infected(*cavetet)) { + // Check for two possible cases for this tet: + // (1) It is a cavity tet, or + // (2) it is a cavity boundary face. + enqflag = false; + if (!marktested(*cavetet)) { + // Do Delaunay (in-sphere) test. + pts = (point *) cavetet->tet; + if (pts[7] != dummypoint) { + // A volume tet. Operate on it. + if (b->weighted) { + sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], insertpt, + pts[4][3], pts[5][3], pts[6][3], pts[7][3], + insertpt[3]); + } else { + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], insertpt); + } + enqflag = (sign < 0.0); + } else { + if (!nonconvex) { + // Test if this hull face is visible by the new point. + ori = orient3d(pts[4], pts[5], pts[6], insertpt); + if (ori < 0) { + // A visible hull face. + // Include it in the cavity. The convex hull will be enlarged. + enqflag = true; + } else if (ori == 0.0) { + // A coplanar hull face. We need to test if this hull face is + // Delaunay or not. We test if the adjacent tet (not faked) + // of this hull face is Delaunay or not. + decode(cavetet->tet[3], neineitet); + if (!infected(neineitet)) { + if (!marktested(neineitet)) { + // Do Delaunay test on this tet. + pts = (point *) neineitet.tet; + if (b->weighted) { + sign = orient4d_s(pts[4],pts[5],pts[6],pts[7], insertpt, + pts[4][3], pts[5][3], pts[6][3], + pts[7][3], insertpt[3]); + } else { + sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); + } + enqflag = (sign < 0.0); + } + } else { + // The adjacent tet is non-Delaunay. The hull face is non- + // Delaunay as well. Include it in the cavity. + enqflag = true; + } // if (!infected(neineitet)) + } // if (ori == 0.0) + } else { + // A hull face (must be a subface). + // We FIRST include it in the initial cavity if the adjacent tet + // (not faked) of this hull face is not Delaunay wrt p. + // Whether it belongs to the final cavity will be determined + // during the validation process. 'validflag'. + decode(cavetet->tet[3], neineitet); + if (!infected(neineitet)) { + if (!marktested(neineitet)) { + // Do Delaunay test on this tet. + pts = (point *) neineitet.tet; + if (b->weighted) { + sign = orient4d_s(pts[4],pts[5],pts[6],pts[7], insertpt, + pts[4][3], pts[5][3], pts[6][3], + pts[7][3], insertpt[3]); + } else { + sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); + } + enqflag = (sign < 0.0); + } + } else { + // The adjacent tet is non-Delaunay. The hull face is non- + // Delaunay as well. Include it in the cavity. + enqflag = true; + } // if (infected(neineitet)) + } // if (nonconvex) + } // if (pts[7] != dummypoint) + marktest(*cavetet); // Only test it once. + } // if (!marktested(*cavetet)) + + if (enqflag) { + // Found a tet in the cavity. Put other three faces in check list. + k = (cavetet->ver & 3); // The current face number + for (j = 1; j < 4; j++) { + decode(cavetet->tet[(j + k) % 4], neightet); + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; + } + infect(*cavetet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *cavetet; + } else { + // Found a boundary face of the cavity. + cavetet->ver = epivot[cavetet->ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = *cavetet; + } + } // if (!infected(*cavetet)) + } // i + + cavetetlist->restart(); // Clear the working list. + } // if (ivf->bowywat) + + if (ivf->refineflag > 0) { + // The new point is inserted by Delaunay refinement, i.e., it is the + // circumcenter of a tetrahedron, or a subface, or a segment. + // Do not insert this point if the tetrahedron, or subface, or segment + // is not inside the final cavity. + if (((ivf->refineflag == 1) && !infected(ivf->refinetet))) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; + } + } // if (ivf->refineflag) + + if (checksubsegflag) { + // Collect all segments of C(p). + shellface *ssptr; + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if ((ssptr = (shellface*) cavetet->tet[8]) != NULL) { + for (j = 0; j < 6; j++) { + if (ssptr[j]) { + sdecode(ssptr[j], checkseg); + if (!sinfected(checkseg)) { + sinfect(checkseg); + cavetetseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } + } // j + } + } // i + // Uninfect collected segments. + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + suninfect(*paryseg); + } + + if (ivf->rejflag & 1) { + // Reject this point if it encroaches upon any segment. + face *paryseg1; + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg1 = (face *) fastlookup(cavetetseglist, i); + point *ppt = (point *) &(paryseg1->sh[3]); + if (check_encroachment(ppt[0], ppt[1], insertpt)) { + badface *bf = NULL; + encseglist->newindex((void **) &bf); + bf->init(); + bf->ss = *paryseg1; + bf->forg = sorg(bf->ss); + bf->fdest = sdest(bf->ss); + } + } // i + if ((ivf->rejflag & 1) && (encseglist->objects > 0)) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) ENCSEGMENT; + return 0; + } + } + } // if (checksubsegflag) + + if (checksubfaceflag) { + // Collect all subfaces of C(p). + shellface *sptr; + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if ((sptr = (shellface*) cavetet->tet[9]) != NULL) { + for (j = 0; j < 4; j++) { + if (sptr[j]) { + sdecode(sptr[j], checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + cavetetshlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + } // j + } + } // i + // Uninfect collected subfaces. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + suninfect(*parysh); + } + + if (ivf->rejflag & 2) { + REAL ccent[3], radius; + badface *bface; + // Reject this point if it encroaches upon any subface. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + if (get_subface_ccent(parysh, ccent)) { + point encpt = insertpt; + if (check_enc_subface(parysh, &encpt, ccent, &radius)) { + encshlist->newindex((void **) &bface); + bface->ss = *parysh; + bface->forg = sorg(*parysh); + bface->fdest = sdest(*parysh); + bface->fapex = sapex(*parysh); + bface->noppo = NULL; // no existing encroaching vertex. + for (j = 0; j < 3; j++) bface->cent[j] = ccent[j]; + for (j = 3; j < 6; j++) bface->cent[j] = 0.; + bface->key = radius; + } + } + } + if (encshlist->objects > 0) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) ENCSUBFACE; + return 0; + } + } + } // if (checksubfaceflag) + + if ((ivf->iloc == (int) OUTSIDE) && ivf->refineflag) { + // The vertex lies outside of the domain. And it does not encroach + // upon any boundary segment or subface. Do not insert it. + insertpoint_abort(splitseg, ivf); + return 0; + } + + if (ivf->splitbdflag) { + // The new point locates in surface mesh. Update the sC(p). + // We have already 'smarktested' the subfaces which directly intersect + // with p in 'caveshlist'. From them, we 'smarktest' their neighboring + // subfaces which are included in C(p). Do not across a segment. + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + checksh = *parysh; + for (j = 0; j < 3; j++) { + if (!isshsubseg(checksh)) { + spivot(checksh, neighsh); + if (!smarktested(neighsh)) { + stpivot(neighsh, neightet); + if (infected(neightet)) { + fsymself(neightet); + if (infected(neightet)) { + // This subface is inside C(p). + // Check if its diametrical circumsphere encloses 'p'. + // The purpose of this check is to avoid forming invalid + // subcavity in surface mesh. + sign = incircle3d(sorg(neighsh), sdest(neighsh), + sapex(neighsh), insertpt); + if (sign < 0) { + smarktest(neighsh); + caveshlist->newindex((void **) &parysh); + *parysh = neighsh; + } + } + } + } + } + senextself(checksh); + } // j + } // i + } // if (ivf->splitbdflag) + + if (ivf->validflag) { + // Validate C(p) and update it if it is not star-shaped. + int cutcount = 0; + + if (ivf->respectbdflag) { + // The initial cavity may include subfaces which are not on the facets + // being splitting. Find them and make them as boundary of C(p). + // Comment: We have already 'smarktested' the subfaces in sC(p). They + // are completely inside C(p). + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + stpivot(*parysh, neightet); + if (infected(neightet)) { + fsymself(neightet); + if (infected(neightet)) { + // Found a subface inside C(p). + if (!smarktested(*parysh)) { + // It is possible that this face is a boundary subface. + // Check if it is a hull face. + //assert(apex(neightet) != dummypoint); + if (oppo(neightet) != dummypoint) { + fsymself(neightet); + } + if (oppo(neightet) != dummypoint) { + ori = orient3d(org(neightet), dest(neightet), apex(neightet), + insertpt); + if (ori < 0) { + // A visible face, get its neighbor face. + fsymself(neightet); + ori = -ori; // It must be invisible by p. + } + } else { + // A hull tet. It needs to be cut. + ori = 1; + } + // Cut this tet if it is either invisible by or coplanar with p. + if (ori >= 0) { + uninfect(neightet); + unmarktest(neightet); + cutcount++; + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + // Add three new faces to find new boundaries. + for (j = 0; j < 3; j++) { + esym(neightet, neineitet); + neineitet.ver = epivot[neineitet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neineitet; + enextself(neightet); + } + } // if (ori >= 0) + } + } + } + } // i + + // The initial cavity may include segments in its interior. We need to + // Update the cavity so that these segments are on the boundary of + // the cavity. + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + // Check this segment if it is not a splitting segment. + if (!smarktested(*paryseg)) { + sstpivot1(*paryseg, neightet); + spintet = neightet; + while (1) { + if (!infected(spintet)) break; + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + if (infected(spintet)) { + // Find an adjacent tet at this segment such that both faces + // at this segment are not visible by p. + pa = org(neightet); + pb = dest(neightet); + spintet = neightet; + j = 0; + while (1) { + // Check if this face is visible by p. + pc = apex(spintet); + if (pc != dummypoint) { + ori = orient3d(pa, pb, pc, insertpt); + if (ori >= 0) { + // Not visible. Check another face in this tet. + esym(spintet, neineitet); + pc = apex(neineitet); + if (pc != dummypoint) { + ori = orient3d(pb, pa, pc, insertpt); + if (ori >= 0) { + // Not visible. Found this face. + j = 1; // Flag that it is found. + break; + } + } + } + } + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + if (j == 0) { + // Not found such a face. + terminatetetgen(this, 2); + } + neightet = spintet; + if (b->verbose > 4) { + printf(" Cut tet (%d, %d, %d, %d)\n", + pointmark(org(neightet)), pointmark(dest(neightet)), + pointmark(apex(neightet)), pointmark(oppo(neightet))); + } + uninfect(neightet); + unmarktest(neightet); + cutcount++; + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neightet; + // Add three new faces to find new boundaries. + for (j = 0; j < 3; j++) { + esym(neightet, neineitet); + neineitet.ver = epivot[neineitet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neineitet; + enextself(neightet); + } + } + } + } // i + } // if (ivf->respectbdflag) + + // Update the cavity by removing invisible faces until it is star-shaped. + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + // 'cavetet' is an exterior tet adjacent to the cavity. + // Check if its neighbor is inside C(p). + fsym(*cavetet, neightet); + if (infected(neightet)) { + if (apex(*cavetet) != dummypoint) { + // It is a cavity boundary face. Check its visibility. + if (oppo(neightet) != dummypoint) { + // Check if this face is visible by the new point. + if (issubface(neightet)) { + // Re-use 'volume' and 'attrib'. + pa = org(*cavetet); + pb = dest(*cavetet); + pc = apex(*cavetet); + volume = orient3dfast(pa, pb, pc, insertpt); + attrib = distance(pa, pb) * distance(pb, pc) * distance(pc, pa); + if ((fabs(volume) / attrib) < b->epsilon) { + ori = 0.0; + } else { + ori = orient3d(pa, pb, pc, insertpt); + } + } else { + ori = orient3d(org(*cavetet), dest(*cavetet), apex(*cavetet), + insertpt); + } + enqflag = (ori > 0); + // Comment: if ori == 0 (coplanar case), we also cut the tet. + } else { + // It is a hull face. And its adjacent tet (at inside of the + // domain) has been cut from the cavity. Cut it as well. + //assert(nonconvex); + enqflag = false; + } + } else { + enqflag = true; // A hull edge. + } + if (enqflag) { + // This face is valid, save it. + cavetetlist->newindex((void **) &parytet); + *parytet = *cavetet; + } else { + uninfect(neightet); + unmarktest(neightet); + cutcount++; + // Add three new faces to find new boundaries. + for (j = 0; j < 3; j++) { + esym(neightet, neineitet); + neineitet.ver = epivot[neineitet.ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = neineitet; + enextself(neightet); + } + // 'cavetet' is not on the cavity boundary anymore. + unmarktest(*cavetet); + } + } else { + // 'cavetet' is not on the cavity boundary anymore. + unmarktest(*cavetet); + } + } // i + + if (cutcount > 0) { + // The cavity has been updated. + // Update the cavity boundary faces. + cavebdrylist->restart(); + for (i = 0; i < cavetetlist->objects; i++) { + cavetet = (triface *) fastlookup(cavetetlist, i); + // 'cavetet' was an exterior tet adjacent to the cavity. + fsym(*cavetet, neightet); + if (infected(neightet)) { + // It is a cavity boundary face. + cavebdrylist->newindex((void **) &parytet); + *parytet = *cavetet; + } else { + // Not a cavity boundary face. + unmarktest(*cavetet); + } + } + + // Update the list of old tets. + cavetetlist->restart(); + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if (infected(*cavetet)) { + cavetetlist->newindex((void **) &parytet); + *parytet = *cavetet; + } + } + // Swap 'cavetetlist' and 'caveoldtetlist'. + swaplist = caveoldtetlist; + caveoldtetlist = cavetetlist; + cavetetlist = swaplist; + + // The cavity should contain at least one tet. + if (caveoldtetlist->objects == 0l) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) NULLCAVITY; // BADELEMENT; + return 0; + } + + if (ivf->splitbdflag) { + int cutshcount = 0; + // Update the sub-cavity sC(p). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + if (smarktested(*parysh)) { + enqflag = false; + stpivot(*parysh, neightet); + if (infected(neightet)) { + fsymself(neightet); + if (infected(neightet)) { + enqflag = true; + } + } + if (!enqflag) { + sunmarktest(*parysh); + // Use the last entry of this array to fill this entry. + j = caveshlist->objects - 1; + checksh = * (face *) fastlookup(caveshlist, j); + *parysh = checksh; + cutshcount++; + caveshlist->objects--; // The list is shrinked. + i--; + } + } + } + + if (cutshcount > 0) { + i = 0; // Count the number of invalid subfaces/segments. + // Valid the updated sub-cavity sC(p). + if (loc == ONFACE) { + if ((splitsh != NULL) && (splitsh->sh != NULL)) { + // The to-be split subface should be in sC(p). + if (!smarktested(*splitsh)) i++; + } + } else if (loc == ONEDGE) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // The to-be split segment should be in sC(p). + if (!smarktested(*splitseg)) i++; + } + if ((splitsh != NULL) && (splitsh->sh != NULL)) { + // All subfaces at this edge should be in sC(p). + pa = sorg(*splitsh); + neighsh = *splitsh; + while (1) { + // Adjust the origin of its edge to be 'pa'. + if (sorg(neighsh) != pa) { + sesymself(neighsh); + } + // Add this face into list (in B-W cavity). + if (!smarktested(neighsh)) i++; + // Go to the next face at the edge. + spivotself(neighsh); + // Stop if all faces at the edge have been visited. + if (neighsh.sh == splitsh->sh) break; + if (neighsh.sh == NULL) break; + } // while (1) + } + } + + if (i > 0) { + // The updated sC(p) is invalid. Do not insert this vertex. + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) NULLCAVITY; // BADELEMENT; + return 0; + } + } // if (cutshcount > 0) + } // if (ivf->splitbdflag) + } // if (cutcount > 0) + + } // if (ivf->validflag) + + if (ivf->refineflag) { + // The new point is inserted by Delaunay refinement, i.e., it is the + // circumcenter of a tetrahedron, or a subface, or a segment. + // Do not insert this point if the tetrahedron, or subface, or segment + // is not inside the final cavity. + if (((ivf->refineflag == 1) && !infected(ivf->refinetet)) || + ((ivf->refineflag == 2) && !smarktested(ivf->refinesh))) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; + } else { + // The following options are used in boundary recovery when we try to + // remove a crossing face (ivf->refineflag == 4) or a crossing edge + // (ivf->refineflag == 8). Reject this point if the face(or edge) + // survives after inserting this vertex. + bool bflag = false; + if (ivf->refineflag == 4) { + // Check if the face (ivf.refinetet) is removed. + // Both tets at this face should be in the cavity. + triface adjtet; + fsym(ivf->refinetet, adjtet); + if (!infected(ivf->refinetet) || !infected(adjtet)) { + bflag = true; + } + } else if (ivf->refineflag == 8) { + // Check if the edge (ivf.refinetet) is removed. + // All tets at this edge should be in the cavity. + triface spintet = ivf->refinetet; + while (true) { + if (!infected(spintet)) { + bflag = true; break; + } + fnextself(spintet); + if (spintet.tet == ivf->refinetet.tet) break; + } + } + if (bflag) { + // Reject this new point. + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; + } + } + } // if (ivf->refineflag) + + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // A segment will be split. It muts lie inside of the cavity. + sstpivot1(*splitseg, neightet); + if (neightet.tet != NULL) { + // This is an existing segment. + bool bflag = false; + spintet = neightet; + while (true) { + if (!infected(spintet)) { + bflag = true; break; + } + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + if (bflag) { + // Reject this new point. + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; + } + } // if (neightet.tet != NULL) + } + + if (b->weighted || ivf->cdtflag || ivf->smlenflag || ivf->validflag) { + // There may be other vertices inside C(p). We need to find them. + // Collect all vertices of C(p). + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + //assert(infected(*cavetet)); + pts = (point *) &(cavetet->tet[4]); + for (j = 0; j < 4; j++) { + if (pts[j] != dummypoint) { + if (!pinfected(pts[j])) { + pinfect(pts[j]); + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[j]; + } + } + } // j + } // i + // Uninfect all collected (cavity) vertices. + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + puninfect(*parypt); + } + if (ivf->smlenflag) { + REAL len; + // Get the length of the shortest edge connecting to 'newpt'. + parypt = (point *) fastlookup(cavetetvertlist, 0); + ivf->smlen = distance(*parypt, insertpt); + ivf->parentpt = *parypt; + for (i = 1; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + len = distance(*parypt, insertpt); + if (len < ivf->smlen) { + ivf->smlen = len; + ivf->parentpt = *parypt; + } + } + } + } + + + if (ivf->cdtflag) { + // Unmark tets. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + unmarktest(*cavetet); + } + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*cavetet); + } + // Clean up arrays which are not needed. + cavetetlist->restart(); + if (checksubsegflag) { + cavetetseglist->restart(); + } + if (checksubfaceflag) { + cavetetshlist->restart(); + } + ivf->iloc = (int) INSTAR; + return 1; + } + + // Before re-mesh C(p). Process the segments and subfaces which are on the + // boundary of C(p). Make sure that each such segment or subface is + // connecting to a tet outside C(p). So we can re-connect them to the + // new tets inside the C(p) later. + + if (checksubsegflag) { + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + // Operate on it if it is not the splitting segment, i.e., in sC(p). + if (!smarktested(*paryseg)) { + // Check if the segment is inside the cavity. + // 'j' counts the num of adjacent tets of this seg. + // 'k' counts the num of adjacent tets which are 'sinfected'. + j = k = 0; + sstpivot1(*paryseg, neightet); + spintet = neightet; + while (1) { + j++; + if (!infected(spintet)) { + neineitet = spintet; // An outer tet. Remember it. + } else { + k++; // An in tet. + } + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + // assert(j > 0); + if (k == 0) { + // The segment is not connect to C(p) anymore. Remove it by + // Replacing it by the last entry of this list. + s = cavetetseglist->objects - 1; + checkseg = * (face *) fastlookup(cavetetseglist, s); + *paryseg = checkseg; + cavetetseglist->objects--; + i--; + } else if (k < j) { + // The segment is on the boundary of C(p). + sstbond1(*paryseg, neineitet); + } else { // k == j + // The segment is inside C(p). + if (!ivf->splitbdflag) { + checkseg = *paryseg; + sinfect(checkseg); // Flag it as an interior segment. + caveencseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } else { + //assert(0); // Not possible. + terminatetetgen(this, 2); + } + } + } else { + // assert(smarktested(*paryseg)); + // Flag it as an interior segment. Do not queue it, since it will + // be deleted after the segment splitting. + sinfect(*paryseg); + } + } // i + } // if (checksubsegflag) + + if (checksubfaceflag) { + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + // Operate on it if it is not inside the sub-cavity sC(p). + if (!smarktested(*parysh)) { + // Check if this subface is inside the cavity. + k = 0; + for (j = 0; j < 2; j++) { + stpivot(*parysh, neightet); + if (!infected(neightet)) { + checksh = *parysh; // Remember this side. + } else { + k++; + } + sesymself(*parysh); + } + if (k == 0) { + // The subface is not connected to C(p). Remove it. + s = cavetetshlist->objects - 1; + checksh = * (face *) fastlookup(cavetetshlist, s); + *parysh = checksh; + cavetetshlist->objects--; + i--; + } else if (k == 1) { + // This side is the outer boundary of C(p). + *parysh = checksh; + } else { // k == 2 + if (!ivf->splitbdflag) { + checksh = *parysh; + sinfect(checksh); // Flag it. + caveencshlist->newindex((void **) &parysh); + *parysh = checksh; + } else { + //assert(0); // Not possible. + terminatetetgen(this, 2); + } + } + } else { + // assert(smarktested(*parysh)); + // Flag it as an interior subface. Do not queue it. It will be + // deleted after the facet point insertion. + sinfect(*parysh); + } + } // i + } // if (checksubfaceflag) + + // Create new tetrahedra to fill the cavity. + + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + neightet = *cavetet; + unmarktest(neightet); // Unmark it. + // Get the oldtet (inside the cavity). + fsym(neightet, oldtet); + if (apex(neightet) != dummypoint) { + // Create a new tet in the cavity. + maketetrahedron(&newtet); + setorg(newtet, dest(neightet)); + setdest(newtet, org(neightet)); + setapex(newtet, apex(neightet)); + setoppo(newtet, insertpt); + } else { + // Create a new hull tet. + hullsize++; + maketetrahedron(&newtet); + setorg(newtet, org(neightet)); + setdest(newtet, dest(neightet)); + setapex(newtet, insertpt); + setoppo(newtet, dummypoint); // It must opposite to face 3. + // Adjust back to the cavity bounday face. + esymself(newtet); + } + // The new tet inherits attribtes from the old tet. + for (j = 0; j < numelemattrib; j++) { + attrib = elemattribute(oldtet.tet, j); + setelemattribute(newtet.tet, j, attrib); + } + if (b->varvolume) { + volume = volumebound(oldtet.tet); + setvolumebound(newtet.tet, volume); + } + // Connect newtet <==> neightet, this also disconnect the old bond. + bond(newtet, neightet); + // oldtet still connects to neightet. + *cavetet = oldtet; // *cavetet = newtet; + } // i + + // Set a handle for speeding point location. + recenttet = newtet; + //setpoint2tet(insertpt, encode(newtet)); + setpoint2tet(insertpt, (tetrahedron) (newtet.tet)); + + // Re-use this list to save new interior cavity faces. + cavetetlist->restart(); + + // Connect adjacent new tetrahedra together. + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + // cavtet is an oldtet, get the newtet at this face. + oldtet = *cavetet; + fsym(oldtet, neightet); + fsym(neightet, newtet); + // Comment: oldtet and newtet must be at the same directed edge. + // Connect the three other faces of this newtet. + for (j = 0; j < 3; j++) { + esym(newtet, neightet); // Go to the face. + if (neightet.tet[neightet.ver & 3] == NULL) { + // Find the adjacent face of this newtet. + spintet = oldtet; + while (1) { + fnextself(spintet); + if (!infected(spintet)) break; + } + fsym(spintet, newneitet); + esymself(newneitet); + bond(neightet, newneitet); + if (ivf->lawson > 1) { + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; + } + } + //setpoint2tet(org(newtet), encode(newtet)); + setpoint2tet(org(newtet), (tetrahedron) (newtet.tet)); + enextself(newtet); + enextself(oldtet); + } + *cavetet = newtet; // Save the new tet. + } // i + + if (checksubfaceflag) { + // Connect subfaces on the boundary of the cavity to the new tets. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + // Connect it if it is not a missing subface. + if (!sinfected(*parysh)) { + stpivot(*parysh, neightet); + fsym(neightet, spintet); + sesymself(*parysh); + tsbond(spintet, *parysh); + } + } + } + + if (checksubsegflag) { + // Connect segments on the boundary of the cavity to the new tets. + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + // Connect it if it is not a missing segment. + if (!sinfected(*paryseg)) { + sstpivot1(*paryseg, neightet); + spintet = neightet; + while (1) { + tssbond1(spintet, *paryseg); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + } + } + } + + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { + // Split a subface or a segment. + sinsertvertex(insertpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat, 0); + } + + if (checksubfaceflag) { + if (ivf->splitbdflag) { + // Recover new subfaces in C(p). + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // The new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + // Note that the old subface still connects to adjacent old tets + // of C(p), which still connect to the tets outside C(p). + stpivot(*parysh, neightet); + // Find the adjacent tet containing the edge [a,b] outside C(p). + spintet = neightet; + while (1) { + fnextself(spintet); + if (!infected(spintet)) break; + if (spintet.tet == neightet.tet) { + terminatetetgen(this, 2); + } + } + // The adjacent tet connects to a new tet in C(p). + fsym(spintet, neightet); + // Find the tet containing the face [a, b, p]. + spintet = neightet; + while (1) { + fnextself(spintet); + if (apex(spintet) == insertpt) break; + } + // Adjust the edge direction in spintet and checksh. + if (sorg(checksh) != org(spintet)) { + sesymself(checksh); + } + // Connect the subface to two adjacent tets. + tsbond(spintet, checksh); + fsymself(spintet); + sesymself(checksh); + tsbond(spintet, checksh); + } // if (checksh.sh[3] != NULL) + } + } else { + // The Boundary recovery phase. + // Put all new subfaces into stack for recovery. + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // The new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + // Put all interior subfaces into stack for recovery. + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + // Some subfaces inside C(p) might be split in sinsertvertex(). + // Only queue those faces which are not split. + if (!smarktested(*parysh)) { + checksh = *parysh; + suninfect(checksh); + stdissolve(checksh); // Detach connections to old tets. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + } + } // if (checksubfaceflag) + + if (checksubsegflag) { + if (ivf->splitbdflag) { + if (splitseg != NULL) { + // Recover the two new subsegments in C(p). + for (i = 0; i < cavesegshlist->objects; i++) { + paryseg = (face *) fastlookup(cavesegshlist, i); + // Insert this subsegment into C(p). + checkseg = *paryseg; + // Get the adjacent new subface. + checkseg.shver = 0; + spivot(checkseg, checksh); + if (checksh.sh != NULL) { + // Get the adjacent new tetrahedron. + stpivot(checksh, neightet); + } else { + // It's a dangling segment. + point2tetorg(sorg(checkseg), neightet); + finddirection(&neightet, sdest(checkseg)); + } + if (isdeadtet(neightet)) { + terminatetetgen(this, 2); + } + sstbond1(checkseg, neightet); + spintet = neightet; + while (1) { + tssbond1(spintet, checkseg); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + } + } // if (splitseg != NULL) + } else { + // The Boundary Recovery Phase. + // Queue missing segments in C(p) for recovery. + if (splitseg != NULL) { + // Queue two new subsegments in C(p) for recovery. + for (i = 0; i < cavesegshlist->objects; i++) { + paryseg = (face *) fastlookup(cavesegshlist, i); + checkseg = *paryseg; + //sstdissolve1(checkseg); // It has not been connected yet. + subsegstack->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } // if (splitseg != NULL) + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + if (!smarktested(*paryseg)) { // It may be split. + checkseg = *paryseg; + suninfect(checkseg); + sstdissolve1(checkseg); // Detach connections to old tets. + s = randomnation(subsegstack->objects + 1); + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = checkseg; + } + } + } + } // if (checksubsegflag) + + if (b->weighted || ivf->validflag) { + // Some vertices may be completed inside the cavity. They must be + // detected and added to recovering list. + for (i = 0; i < cavetetvertlist->objects; i++) { + pts = (point *) fastlookup(cavetetvertlist, i); + decode(point2tet(*pts), *searchtet); + if (infected(*searchtet)) { + if (b->weighted) { + if (b->verbose > 4) { + printf(" Point #%d is non-regular after the insertion of #%d.\n", + pointmark(*pts), pointmark(insertpt)); + } + setpointtype(*pts, NREGULARVERTEX); + nonregularcount++; + } else { + if (b->verbose > 4) { + printf(" Deleting an interior vertex %d.\n", pointmark(*pts)); + } + // The cavity is updated such that no constrained segments and + // subfaces are in its interior. Interior vertices must be + // inside volume or on a boundary facet. + // The point has been removed. + point steinerpt = *pts; + enum verttype vt = pointtype(steinerpt); + if (vt != UNUSEDVERTEX) { + setpointtype(steinerpt, UNUSEDVERTEX); + unuverts++; + } + if (vt != VOLVERTEX) { + // Update the correspinding counters. + if (vt == FREESEGVERTEX) { + st_segref_count--; + } else if (vt == FREEFACETVERTEX) { + st_facref_count--; + } else if (vt == FREEVOLVERTEX) { + st_volref_count--; + } + if (steinerleft > 0) steinerleft++; + } + } + } + } + } + + if (ivf->chkencflag & 1) { + // Queue all segment outside C(p). + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + // Skip if it is the split segment. + if (!sinfected(*paryseg)) { + enqueuesubface(badsubsegs, paryseg); + } + } + if (splitseg != NULL) { + // Queue the two new subsegments inside C(p). + for (i = 0; i < cavesegshlist->objects; i++) { + paryseg = (face *) fastlookup(cavesegshlist, i); + enqueuesubface(badsubsegs, paryseg); + } + } + } // if (chkencflag & 1) + + if (ivf->chkencflag & 2) { + // Queue all subfaces outside C(p). + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + // Skip if it is a split subface. + if (!sinfected(*parysh)) { + enqueuesubface(badsubfacs, parysh); + } + } + // Queue all new subfaces inside C(p). + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // checksh is a new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + enqueuesubface(badsubfacs, &checksh); + } + } + } // if (chkencflag & 2) + + if (ivf->chkencflag & 4) { + // Queue all new tetrahedra in C(p). + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + enqueuetetrahedron(cavetet); + } + } + + // C(p) is re-meshed successfully. + + // Delete the old tets in C(p). + for (i = 0; i < caveoldtetlist->objects; i++) { + searchtet = (triface *) fastlookup(caveoldtetlist, i); + if (ishulltet(*searchtet)) { + hullsize--; + } + tetrahedrondealloc(searchtet->tet); + } + + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { + // Delete the old subfaces in sC(p). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + if (checksubfaceflag) {//if (bowywat == 2) { + // It is possible that this subface still connects to adjacent + // tets which are not in C(p). If so, clear connections in the + // adjacent tets at this subface. + stpivot(*parysh, neightet); + if (neightet.tet != NULL) { + if (neightet.tet[4] != NULL) { + // Found an adjacent tet. It must be not in C(p). + tsdissolve(neightet); + fsymself(neightet); + tsdissolve(neightet); + } + } + } + shellfacedealloc(subfaces, parysh->sh); + } + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // Delete the old segment in sC(p). + shellfacedealloc(subsegs, splitseg->sh); + } + } + + if (ivf->lawson) { + for (i = 0; i < cavebdrylist->objects; i++) { + searchtet = (triface *) fastlookup(cavebdrylist, i); + flippush(flipstack, searchtet); + } + if (ivf->lawson > 1) { + for (i = 0; i < cavetetlist->objects; i++) { + searchtet = (triface *) fastlookup(cavetetlist, i); + flippush(flipstack, searchtet); + } + } + } + + + // Clean the working lists. + + caveoldtetlist->restart(); + cavebdrylist->restart(); + cavetetlist->restart(); + + if (checksubsegflag) { + cavetetseglist->restart(); + caveencseglist->restart(); + } + + if (checksubfaceflag) { + cavetetshlist->restart(); + caveencshlist->restart(); + } + + if (b->weighted || ivf->smlenflag || ivf->validflag) { + cavetetvertlist->restart(); + } + + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { + caveshlist->restart(); + caveshbdlist->restart(); + cavesegshlist->restart(); + } + + return 1; // Point is inserted. +} + +//============================================================================// +// // +// insertpoint_abort() Abort the insertion of a new vertex. // +// // +// The cavity will be restored. All working lists are cleared. // +// // +//============================================================================// + +void tetgenmesh::insertpoint_abort(face *splitseg, insertvertexflags *ivf) +{ + triface *cavetet; + face *parysh; + int i; + + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + uninfect(*cavetet); + unmarktest(*cavetet); + } + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*cavetet); + } + cavetetlist->restart(); + cavebdrylist->restart(); + caveoldtetlist->restart(); + cavetetseglist->restart(); + cavetetshlist->restart(); + if (ivf->splitbdflag) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + sunmarktest(*splitseg); + } + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + sunmarktest(*parysh); + } + caveshlist->restart(); + cavesegshlist->restart(); + } +} + +// // +// // +//== flip_cxx ================================================================// + +//== delaunay_cxx ============================================================// +// // +// // + +//============================================================================// +// // +// transfernodes() Read the vertices from the input (tetgenio). // +// // +// Transferring all points from input ('in->pointlist') to TetGen's 'points'. // +// All points are indexed (the first point index is 'in->firstnumber'). Each // +// point's type is initialized as UNUSEDVERTEX. The bounding box (xmax, xmin, // +// ...) and the diameter (longest) of the point set are calculated. // +// // +//============================================================================// + +void tetgenmesh::transfernodes() +{ + point pointloop; + REAL x, y, z, w, mtr; + int coordindex; + int attribindex; + int mtrindex; + int i, j; + + // Read the points. + coordindex = 0; + attribindex = 0; + mtrindex = 0; + for (i = 0; i < in->numberofpoints; i++) { + makepoint(&pointloop, UNUSEDVERTEX); + // Read the point coordinates. + x = pointloop[0] = in->pointlist[coordindex++]; + y = pointloop[1] = in->pointlist[coordindex++]; + z = pointloop[2] = in->pointlist[coordindex++]; + // Read the point attributes. (Including point weights.) + for (j = 0; j < in->numberofpointattributes; j++) { + pointloop[3 + j] = in->pointattributelist[attribindex++]; + } + // Read the point metric tensor. + for (j = 0; j < in->numberofpointmtrs; j++) { + mtr = in->pointmtrlist[mtrindex++] * b->metric_scale; + pointloop[pointmtrindex + j] = mtr; // in->pointmtrlist[mtrindex++]; + } + if (b->weighted) { // -w option + if (in->numberofpointattributes > 0) { + // The first point attribute is its weight. + //w = in->pointattributelist[in->numberofpointattributes * i]; + w = pointloop[3]; + } else { + // No given weight available. Default choose the maximum + // absolute value among its coordinates. + w = fabs(x); + if (w < fabs(y)) w = fabs(y); + if (w < fabs(z)) w = fabs(z); + } + if (b->weighted_param == 0) { + pointloop[3] = x * x + y * y + z * z - w; // Weighted DT. + } else { // -w1 option + pointloop[3] = w; // Regular tetrahedralization. + } + } + // Determine the smallest and largest x, y and z coordinates. + if (i == 0) { + xmin = xmax = x; + ymin = ymax = y; + zmin = zmax = z; + } else { + xmin = (x < xmin) ? x : xmin; + xmax = (x > xmax) ? x : xmax; + ymin = (y < ymin) ? y : ymin; + ymax = (y > ymax) ? y : ymax; + zmin = (z < zmin) ? z : zmin; + zmax = (z > zmax) ? z : zmax; + } + } + + x = xmax - xmin; + y = ymax - ymin; + z = zmax - zmin; + + exactinit(b->verbose, b->noexact, b->nostaticfilter, x, y, z); + + // Use the number of points as the random seed. + srand(in->numberofpoints); + + // 'longest' is the largest possible edge length formed by input vertices. + longest = sqrt(x * x + y * y + z * z); + if (longest == 0.0) { + printf("Error: The point set is trivial.\n"); + terminatetetgen(this, 10); + } + // Two identical points are distinguished by 'minedgelength'. + minedgelength = longest * b->epsilon; + +#ifndef TETLIBRARY + /* + // Release the memory from the input data strutcure + delete [] in->pointlist; + in->pointlist = NULL; + if (in->pointattributelist != NULL) { + delete [] in->pointattributelist; + in->pointattributelist = NULL; + } + if (in->pointmtrlist != NULL) { + delete [] in->pointmtrlist; + in->pointmtrlist = NULL; + } + */ +#endif +} + +//============================================================================// +// // +// hilbert_init() Initialize the Gray code permutation table. // +// // +// The table 'transgc' has 8 x 3 x 8 entries. It contains all possible Gray // +// code sequences traveled by the 1st order Hilbert curve in 3 dimensions. // +// The first column is the Gray code of the entry point of the curve, and // +// the second column is the direction (0, 1, or 2, 0 means the x-axis) where // +// the exit point of curve lies. // +// // +// The table 'tsb1mod3' contains the numbers of trailing set '1' bits of the // +// indices from 0 to 7, modulo by '3'. The code for generating this table is // +// from: http://graphics.stanford.edu/~seander/bithacks.html. // +// // +//============================================================================// + +void tetgenmesh::hilbert_init(int n) +{ + int gc[8], N, mask, travel_bit; + int e, d, f, k, g; + int v, c; + int i; + + N = (n == 2) ? 4 : 8; + mask = (n == 2) ? 3 : 7; + + // Generate the Gray code sequence. + for (i = 0; i < N; i++) { + gc[i] = i ^ (i >> 1); + } + + for (e = 0; e < N; e++) { + for (d = 0; d < n; d++) { + // Calculate the end point (f). + f = e ^ (1 << d); // Toggle the d-th bit of 'e'. + // travel_bit = 2**p, the bit we want to travel. + travel_bit = e ^ f; + for (i = 0; i < N; i++) { + // // Rotate gc[i] left by (p + 1) % n bits. + k = gc[i] * (travel_bit * 2); + g = ((k | (k / N)) & mask); + // Calculate the permuted Gray code by xor with the start point (e). + transgc[e][d][i] = (g ^ e); + } + } // d + } // e + + // Count the consecutive '1' bits (trailing) on the right. + tsb1mod3[0] = 0; + for (i = 1; i < N; i++) { + v = ~i; // Count the 0s. + v = (v ^ (v - 1)) >> 1; // Set v's trailing 0s to 1s and zero rest + for (c = 0; v; c++) { + v >>= 1; + } + tsb1mod3[i] = c % n; + } +} + +//============================================================================// +// // +// hilbert_sort3() Sort points using the 3d Hilbert curve. // +// // +//============================================================================// + +int tetgenmesh::hilbert_split(point* vertexarray,int arraysize,int gc0,int gc1, + REAL bxmin, REAL bxmax, REAL bymin, REAL bymax, + REAL bzmin, REAL bzmax) +{ + point swapvert; + int axis, d; + REAL split; + int i, j; + + + // Find the current splitting axis. 'axis' is a value 0, or 1, or 2, which + // correspoding to x-, or y- or z-axis. + axis = (gc0 ^ gc1) >> 1; + + // Calulate the split position along the axis. + if (axis == 0) { + split = 0.5 * (bxmin + bxmax); + } else if (axis == 1) { + split = 0.5 * (bymin + bymax); + } else { // == 2 + split = 0.5 * (bzmin + bzmax); + } + + // Find the direction (+1 or -1) of the axis. If 'd' is +1, the direction + // of the axis is to the positive of the axis, otherwise, it is -1. + d = ((gc0 & (1< 0) { + do { + for (; i < arraysize; i++) { + if (vertexarray[i][axis] >= split) break; + } + for (; j >= 0; j--) { + if (vertexarray[j][axis] < split) break; + } + // Is the partition finished? + if (i == (j + 1)) break; + // Swap i-th and j-th vertices. + swapvert = vertexarray[i]; + vertexarray[i] = vertexarray[j]; + vertexarray[j] = swapvert; + // Continue patitioning the array; + } while (true); + } else { + do { + for (; i < arraysize; i++) { + if (vertexarray[i][axis] <= split) break; + } + for (; j >= 0; j--) { + if (vertexarray[j][axis] > split) break; + } + // Is the partition finished? + if (i == (j + 1)) break; + // Swap i-th and j-th vertices. + swapvert = vertexarray[i]; + vertexarray[i] = vertexarray[j]; + vertexarray[j] = swapvert; + // Continue patitioning the array; + } while (true); + } + + return i; +} + +void tetgenmesh::hilbert_sort3(point* vertexarray, int arraysize, int e, int d, + REAL bxmin, REAL bxmax, REAL bymin, REAL bymax, + REAL bzmin, REAL bzmax, int depth) +{ + REAL x1, x2, y1, y2, z1, z2; + int p[9], w, e_w, d_w, k, ei, di; + int n = 3, mask = 7; + + p[0] = 0; + p[8] = arraysize; + + // Sort the points according to the 1st order Hilbert curve in 3d. + p[4] = hilbert_split(vertexarray, p[8], transgc[e][d][3], transgc[e][d][4], + bxmin, bxmax, bymin, bymax, bzmin, bzmax); + p[2] = hilbert_split(vertexarray, p[4], transgc[e][d][1], transgc[e][d][2], + bxmin, bxmax, bymin, bymax, bzmin, bzmax); + p[1] = hilbert_split(vertexarray, p[2], transgc[e][d][0], transgc[e][d][1], + bxmin, bxmax, bymin, bymax, bzmin, bzmax); + p[3] = hilbert_split(&(vertexarray[p[2]]), p[4] - p[2], + transgc[e][d][2], transgc[e][d][3], + bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[2]; + p[6] = hilbert_split(&(vertexarray[p[4]]), p[8] - p[4], + transgc[e][d][5], transgc[e][d][6], + bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[4]; + p[5] = hilbert_split(&(vertexarray[p[4]]), p[6] - p[4], + transgc[e][d][4], transgc[e][d][5], + bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[4]; + p[7] = hilbert_split(&(vertexarray[p[6]]), p[8] - p[6], + transgc[e][d][6], transgc[e][d][7], + bxmin, bxmax, bymin, bymax, bzmin, bzmax) + p[6]; + + if (b->hilbert_order > 0) { + // A maximum order is prescribed. + if ((depth + 1) == b->hilbert_order) { + // The maximum prescribed order is reached. + return; + } + } + + // Recursively sort the points in sub-boxes. + for (w = 0; w < 8; w++) { + // w is the local Hilbert index (NOT Gray code). + // Sort into the sub-box either there are more than 2 points in it, or + // the prescribed order of the curve is not reached yet. + //if ((p[w+1] - p[w] > b->hilbert_limit) || (b->hilbert_order > 0)) { + if ((p[w+1] - p[w]) > b->hilbert_limit) { + // Calculcate the start point (ei) of the curve in this sub-box. + // update e = e ^ (e(w) left_rotate (d+1)). + if (w == 0) { + e_w = 0; + } else { + // calculate e(w) = gc(2 * floor((w - 1) / 2)). + k = 2 * ((w - 1) / 2); + e_w = k ^ (k >> 1); // = gc(k). + } + k = e_w; + e_w = ((k << (d+1)) & mask) | ((k >> (n-d-1)) & mask); + ei = e ^ e_w; + // Calulcate the direction (di) of the curve in this sub-box. + // update d = (d + d(w) + 1) % n + if (w == 0) { + d_w = 0; + } else { + d_w = ((w % 2) == 0) ? tsb1mod3[w - 1] : tsb1mod3[w]; + } + di = (d + d_w + 1) % n; + // Calculate the bounding box of the sub-box. + if (transgc[e][d][w] & 1) { // x-axis + x1 = 0.5 * (bxmin + bxmax); + x2 = bxmax; + } else { + x1 = bxmin; + x2 = 0.5 * (bxmin + bxmax); + } + if (transgc[e][d][w] & 2) { // y-axis + y1 = 0.5 * (bymin + bymax); + y2 = bymax; + } else { + y1 = bymin; + y2 = 0.5 * (bymin + bymax); + } + if (transgc[e][d][w] & 4) { // z-axis + z1 = 0.5 * (bzmin + bzmax); + z2 = bzmax; + } else { + z1 = bzmin; + z2 = 0.5 * (bzmin + bzmax); + } + hilbert_sort3(&(vertexarray[p[w]]), p[w+1] - p[w], ei, di, + x1, x2, y1, y2, z1, z2, depth+1); + } // if (p[w+1] - p[w] > 1) + } // w +} + +//============================================================================// +// // +// brio_multiscale_sort() Sort the points using BRIO and Hilbert curve. // +// // +//============================================================================// + +void tetgenmesh::brio_multiscale_sort(point* vertexarray, int arraysize, + int threshold, REAL ratio, int *depth) +{ + int middle; + + middle = 0; + if (arraysize >= threshold) { + (*depth)++; + middle = arraysize * ratio; + brio_multiscale_sort(vertexarray, middle, threshold, ratio, depth); + } + // Sort the right-array (rnd-th round) using the Hilbert curve. + hilbert_sort3(&(vertexarray[middle]), arraysize - middle, 0, 0, // e, d + xmin, xmax, ymin, ymax, zmin, zmax, 0); // depth. +} + +//============================================================================// +// // +// randomnation() Generate a random number between 0 and 'choices' - 1. // +// // +//============================================================================// + +unsigned long tetgenmesh::randomnation(unsigned int choices) +{ + unsigned long newrandom; + + if (choices >= 714025l) { + newrandom = (randomseed * 1366l + 150889l) % 714025l; + randomseed = (newrandom * 1366l + 150889l) % 714025l; + newrandom = newrandom * (choices / 714025l) + randomseed; + if (newrandom >= choices) { + return newrandom - choices; + } else { + return newrandom; + } + } else { + randomseed = (randomseed * 1366l + 150889l) % 714025l; + return randomseed % choices; + } +} + +//============================================================================// +// // +// randomsample() Randomly sample the tetrahedra for point loation. // +// // +// Searching begins from one of handles: the input 'searchtet', a recently // +// encountered tetrahedron 'recenttet', or from one chosen from a random // +// sample. The choice is made by determining which one's origin is closest // +// to the point we are searching for. // +// // +//============================================================================// + +void tetgenmesh::randomsample(point searchpt,triface *searchtet) +{ + tetrahedron *firsttet, *tetptr; + point torg; + void **sampleblock; + uintptr_t alignptr; + long sampleblocks, samplesperblock, samplenum; + long tetblocks, i, j; + REAL searchdist, dist; + + if (b->verbose > 2) { + printf(" Random sampling tetrahedra for searching point %d.\n", + pointmark(searchpt)); + } + + if (!nonconvex) { + if (searchtet->tet == NULL) { + // A null tet. Choose the recenttet as the starting tet. + *searchtet = recenttet; + } + + // 'searchtet' should be a valid tetrahedron. Choose the base face + // whose vertices must not be 'dummypoint'. + searchtet->ver = 3; + // Record the distance from its origin to the searching point. + torg = org(*searchtet); + searchdist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + + (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + + (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); + + // If a recently encountered tetrahedron has been recorded and has not + // been deallocated, test it as a good starting point. + if (recenttet.tet != searchtet->tet) { + recenttet.ver = 3; + torg = org(recenttet); + dist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + + (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + + (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); + if (dist < searchdist) { + *searchtet = recenttet; + searchdist = dist; + } + } + } else { + // The mesh is non-convex. Do not use 'recenttet'. + searchdist = longest; + } + + // Select "good" candidate using k random samples, taking the closest one. + // The number of random samples taken is proportional to the fourth root + // of the number of tetrahedra in the mesh. + while (samples * samples * samples * samples < tetrahedrons->items) { + samples++; + } + // Find how much blocks in current tet pool. + tetblocks = (tetrahedrons->maxitems + b->tetrahedraperblock - 1) + / b->tetrahedraperblock; + // Find the average samples per block. Each block at least have 1 sample. + samplesperblock = 1 + (samples / tetblocks); + sampleblocks = samples / samplesperblock; + if (sampleblocks == 0) { + sampleblocks = 1; // at least one sample block is needed. + } + sampleblock = tetrahedrons->firstblock; + for (i = 0; i < sampleblocks; i++) { + alignptr = (uintptr_t) (sampleblock + 1); + firsttet = (tetrahedron *) + (alignptr + (uintptr_t) tetrahedrons->alignbytes + - (alignptr % (uintptr_t) tetrahedrons->alignbytes)); + for (j = 0; j < samplesperblock; j++) { + if (i == tetblocks - 1) { + // This is the last block. + samplenum = randomnation((int) + (tetrahedrons->maxitems - (i * b->tetrahedraperblock))); + } else { + samplenum = randomnation(b->tetrahedraperblock); + } + tetptr = (tetrahedron *) + (firsttet + (samplenum * tetrahedrons->itemwords)); + torg = (point) tetptr[4]; + if (torg != (point) NULL) { + dist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + + (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + + (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); + if (dist < searchdist) { + searchtet->tet = tetptr; + searchtet->ver = 11; // torg = org(t); + searchdist = dist; + } + } else { + // A dead tet. Re-sample it. + if (i != tetblocks - 1) j--; + } + } + sampleblock = (void **) *sampleblock; + } +} + +//============================================================================// +// // +// locate() Find a tetrahedron containing a given point. // +// // +// Begins its search from 'searchtet', assume there is a line segment L from // +// a vertex of 'searchtet' to the query point 'searchpt', and simply walk // +// towards 'searchpt' by traversing all faces intersected by L. // +// // +// On completion, 'searchtet' is a tetrahedron that contains 'searchpt'. The // +// returned value indicates one of the following cases: // +// - ONVERTEX, the search point lies on the origin of 'searchtet'. // +// - ONEDGE, the search point lies on an edge of 'searchtet'. // +// - ONFACE, the search point lies on a face of 'searchtet'. // +// - INTET, the search point lies in the interior of 'searchtet'. // +// - OUTSIDE, the search point lies outside the mesh. 'searchtet' is a // +// hull face which is visible by the search point. // +// // +// WARNING: This routine is designed for convex triangulations, and will not // +// generally work after the holes and concavities have been carved. // +// // +//============================================================================// + +enum tetgenmesh::locateresult + tetgenmesh::locate_dt(point searchpt, triface* searchtet) +{ + //enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; + REAL ori, oriorg, oridest, oriapex; + enum locateresult loc = OUTSIDE; + point toppo; + int s, i; + + if (searchtet->tet == NULL) { + searchtet->tet = recenttet.tet; + } + + if (ishulltet(*searchtet)) { + // Get its adjacent tet (inside the hull). + searchtet->tet = decode_tet_only(searchtet->tet[3]); + } + + // Let searchtet be the face such that 'searchpt' lies above to it. + for (searchtet->ver = 0; searchtet->ver < 4; searchtet->ver++) { + ori = orient3d(org(*searchtet), dest(*searchtet), apex(*searchtet), searchpt); + if (ori < 0.0) break; + } + + if (searchtet->ver == 4) { + terminatetetgen(this, 2); + } + + // Walk through tetrahedra to locate the point. + do { + + toppo = oppo(*searchtet); + + // Check if the vertex is we seek. + if (toppo == searchpt) { + // Adjust the origin of searchtet to be searchpt. + esymself(*searchtet); + eprevself(*searchtet); + loc = ONVERTEX; // return ONVERTEX; + break; + } + + // We enter from one of serarchtet's faces, which face do we exit? + // Randomly choose one of three faces (containig toppo) of this tet. + s = rand() % 3; // s \in \{0,1,2\} + for (i = 0; i < s; i++) enextself(*searchtet); + + oriorg = orient3d(dest(*searchtet), apex(*searchtet), toppo, searchpt); + if (oriorg < 0) { + //nextmove = ORGMOVE; + enextesymself(*searchtet); + } else { + oridest = orient3d(apex(*searchtet), org(*searchtet), toppo, searchpt); + if (oridest < 0) { + //nextmove = DESTMOVE; + eprevesymself(*searchtet); + } else { + oriapex = orient3d(org(*searchtet), dest(*searchtet), toppo, searchpt); + if (oriapex < 0) { + //nextmove = APEXMOVE; + esymself(*searchtet); + } else { + // oriorg >= 0, oridest >= 0, oriapex >= 0 ==> found the point. + // The point we seek must be on the boundary of or inside this + // tetrahedron. Check for boundary cases first. + if (oriorg == 0) { + // Go to the face opposite to origin. + enextesymself(*searchtet); + if (oridest == 0) { + eprevself(*searchtet); // edge oppo->apex + if (oriapex == 0) { + // oppo is duplicated with p. + loc = ONVERTEX; // return ONVERTEX; + break; + } + loc = ONEDGE; // return ONEDGE; + break; + } + if (oriapex == 0) { + enextself(*searchtet); // edge dest->oppo + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oridest == 0) { + // Go to the face opposite to destination. + eprevesymself(*searchtet); + if (oriapex == 0) { + eprevself(*searchtet); // edge oppo->org + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oriapex == 0) { + // Go to the face opposite to apex + esymself(*searchtet); + loc = ONFACE; // return ONFACE; + break; + } + loc = INTETRAHEDRON; + break; + } + } + } // if (locateflag) + + // Move to the next tet adjacent to the selected face. + decode(searchtet->tet[searchtet->ver & 3], *searchtet); // fsymself + + if (ishulltet(*searchtet)) { + loc = OUTSIDE; // return OUTSIDE; + break; + } + + } while (true); + + return loc; +} + +enum tetgenmesh::locateresult + tetgenmesh::locate(point searchpt, triface* searchtet, int chkencflag) +{ + point torg, tdest, tapex, toppo; + enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; + REAL ori, oriorg, oridest, oriapex; + enum locateresult loc = OUTSIDE; + //int t1ver; + int s; + + torg = tdest = tapex = toppo = NULL; + + if (searchtet->tet == NULL) { + // A null tet. Choose the recenttet as the starting tet. + searchtet->tet = recenttet.tet; + } + + // Check if we are in the outside of the convex hull. + if (ishulltet(*searchtet)) { + // Get its adjacent tet (inside the hull). + searchtet->tet = decode_tet_only(searchtet->tet[3]); + } + + // Let searchtet be the face such that 'searchpt' lies above to it. + for (searchtet->ver = 0; searchtet->ver < 4; searchtet->ver++) { + torg = org(*searchtet); + tdest = dest(*searchtet); + tapex = apex(*searchtet); + ori = orient3d(torg, tdest, tapex, searchpt); + if (ori < 0.0) break; + } + if (searchtet->ver == 4) { + terminatetetgen(this, 2); + } + + // Walk through tetrahedra to locate the point. + while (true) { + toppo = oppo(*searchtet); + + // Check if the vertex is we seek. + if (toppo == searchpt) { + // Adjust the origin of searchtet to be searchpt. + esymself(*searchtet); + eprevself(*searchtet); + loc = ONVERTEX; // return ONVERTEX; + break; + } + + // We enter from one of serarchtet's faces, which face do we exit? + oriorg = orient3d(tdest, tapex, toppo, searchpt); + oridest = orient3d(tapex, torg, toppo, searchpt); + oriapex = orient3d(torg, tdest, toppo, searchpt); + + // Now decide which face to move. It is possible there are more than one + // faces are viable moves. If so, randomly choose one. + if (oriorg < 0) { + if (oridest < 0) { + if (oriapex < 0) { + // All three faces are possible. + s = randomnation(3); // 's' is in {0,1,2}. + if (s == 0) { + nextmove = ORGMOVE; + } else if (s == 1) { + nextmove = DESTMOVE; + } else { + nextmove = APEXMOVE; + } + } else { + // Two faces, opposite to origin and destination, are viable. + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = ORGMOVE; + } else { + nextmove = DESTMOVE; + } + } + } else { + if (oriapex < 0) { + // Two faces, opposite to origin and apex, are viable. + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = ORGMOVE; + } else { + nextmove = APEXMOVE; + } + } else { + // Only the face opposite to origin is viable. + nextmove = ORGMOVE; + } + } + } else { + if (oridest < 0) { + if (oriapex < 0) { + // Two faces, opposite to destination and apex, are viable. + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = DESTMOVE; + } else { + nextmove = APEXMOVE; + } + } else { + // Only the face opposite to destination is viable. + nextmove = DESTMOVE; + } + } else { + if (oriapex < 0) { + // Only the face opposite to apex is viable. + nextmove = APEXMOVE; + } else { + // The point we seek must be on the boundary of or inside this + // tetrahedron. Check for boundary cases. + if (oriorg == 0) { + // Go to the face opposite to origin. + enextesymself(*searchtet); + if (oridest == 0) { + eprevself(*searchtet); // edge oppo->apex + if (oriapex == 0) { + // oppo is duplicated with p. + loc = ONVERTEX; // return ONVERTEX; + break; + } + loc = ONEDGE; // return ONEDGE; + break; + } + if (oriapex == 0) { + enextself(*searchtet); // edge dest->oppo + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oridest == 0) { + // Go to the face opposite to destination. + eprevesymself(*searchtet); + if (oriapex == 0) { + eprevself(*searchtet); // edge oppo->org + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oriapex == 0) { + // Go to the face opposite to apex + esymself(*searchtet); + loc = ONFACE; // return ONFACE; + break; + } + loc = INTETRAHEDRON; // return INTETRAHEDRON; + break; + } + } + } + + // Move to the selected face. + if (nextmove == ORGMOVE) { + enextesymself(*searchtet); + } else if (nextmove == DESTMOVE) { + eprevesymself(*searchtet); + } else { + esymself(*searchtet); + } + if (chkencflag) { + // Check if we are walking across a subface. + if (issubface(*searchtet)) { + loc = ENCSUBFACE; + break; + } + } + // Move to the adjacent tetrahedron (maybe a hull tetrahedron). + decode(searchtet->tet[searchtet->ver & 3], *searchtet); // fsymself + if (ishulltet(*searchtet)) { + loc = OUTSIDE; // return OUTSIDE; + break; + } + + // Retreat the three vertices of the base face. + torg = org(*searchtet); + tdest = dest(*searchtet); + tapex = apex(*searchtet); + + } // while (true) + + return loc; +} + +//============================================================================// +// // +// insert_vertex_bw() Insert a vertex using the Bowyer-Watson algorithm. // +// // +// This function is only used for initial Delaunay triangulation construction.// +// It improves the speed of incremental algorithm. // +// // +//============================================================================// + +int tetgenmesh::insert_vertex_bw(point insertpt, triface *searchtet, + insertvertexflags *ivf) +{ + tetrahedron **ptptr, *tptr; + triface cavetet, spintet, neightet, neineitet, *parytet; + triface oldtet, newtet; //, newneitet; + point *pts; //, pa, pb, pc, *parypt; + enum locateresult loc = OUTSIDE; + REAL sign, ori; + //REAL attrib, volume; + bool enqflag; + int t1ver; + int i, j, k; //, s; + + if (b->verbose > 2) { + printf(" Insert point %d\n", pointmark(insertpt)); + } + + // Locate the point. + if (searchtet->tet != NULL) { + loc = (enum locateresult) ivf->iloc; + } + + if (loc == OUTSIDE) { + if (searchtet->tet == NULL) { + if (!b->weighted) { + randomsample(insertpt, searchtet); + } else { + // Weighted DT. There may exist dangling vertex. + *searchtet = recenttet; + } + } + loc = locate_dt(insertpt, searchtet); + } + + ivf->iloc = (int) loc; // The return value. + + if (b->weighted) { + if (loc != OUTSIDE) { + // Check if this vertex is regular. + pts = (point *) searchtet->tet; + sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], insertpt, + pts[4][3], pts[5][3], pts[6][3], pts[7][3], + insertpt[3]); + if (sign > 0) { + // This new vertex lies above the lower hull. Do not insert it. + ivf->iloc = (int) NONREGULAR; + return 0; + } + } + } + + // Create the initial cavity C(p) which contains all tetrahedra that + // intersect p. It may include 1, 2, or n tetrahedra. + + if (loc == OUTSIDE) { + infect(*searchtet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = searchtet->tet; + } else if (loc == INTETRAHEDRON) { + infect(*searchtet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = searchtet->tet; + } else if (loc == ONFACE) { + infect(*searchtet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = searchtet->tet; + neightet.tet = decode_tet_only(searchtet->tet[searchtet->ver & 3]); + infect(neightet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = neightet.tet; + } else if (loc == ONEDGE) { + + // Add all adjacent boundary tets into list. + spintet = *searchtet; + while (1) { + infect(spintet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = spintet.tet; + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + } else if (loc == ONVERTEX) { + // The point already exist. Do nothing and return. + return 0; + } + + // Create the cavity C(p). + + for (i = 0; i < cave_oldtet_list->objects; i++) { + ptptr = (tetrahedron **) fastlookup(cave_oldtet_list, i); + cavetet.tet = *ptptr; + for (cavetet.ver = 0; cavetet.ver < 4; cavetet.ver++) { + neightet.tet = decode_tet_only(cavetet.tet[cavetet.ver]); + if (!infected(neightet)) { + // neightet.tet is current outside the cavity. + enqflag = false; + if (!marktested(neightet)) { + if (!ishulltet(neightet)) { + pts = (point *) neightet.tet; + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], insertpt); + enqflag = (sign < 0.0); + } else { + pts = (point *) neightet.tet; + ori = orient3d(pts[4], pts[5], pts[6], insertpt); + if (ori < 0) { + // A visible hull face. + enqflag = true; + } else if (ori == 0.) { + // A coplanar hull face. We need to test if this hull face is + // Delaunay or not. We test if the adjacent tet (not faked) + // of this hull face is Delaunay or not. + triface neineitet; + neineitet.tet = decode_tet_only(neightet.tet[3]); + pts = (point *) neineitet.tet; + sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); + enqflag = (sign < 0.0); + } + } + marktest(neightet); + } + if (enqflag) { + infect(neightet); + cave_oldtet_list->newindex((void **) &ptptr); + *ptptr = neightet.tet; + } else { + // A boundary face. + cavebdrylist->newindex((void **) &parytet); + *parytet = cavetet; + } + } // if (!infected(neightet)) + } + } // i + + // Create new tetrahedra to fill the cavity. + int f_out = cavebdrylist->objects; + int v_out = (f_out + 4) / 2; + + + triface *pcavetet; + point V[3]; + int local_vcount = 0; // local index of vertex + int sidx[3]; + + static int row_v08_tbl[12] = {8,9,10,11,0,1,2,3,4,5,6,7}; + static int row_v11_tbl[12] = {8,9,10,11,0,1,2,3,4,5,6,7}; + static int col_v01_tbl[12] = {1,1,1,1,5,5,5,5,9,9,9,9}; + static int col_v02_tbl[12] = {2,2,2,2,6,6,6,6,10,10,10,10}; + static int col_v08_tbl[12] = {8,8,8,8,0,0,0,0,4,4,4,4}; + static int col_v11_tbl[12] = {11,11,11,11,3,3,3,3,7,7,7,7}; + + triface *tmp_bw_faces = NULL; + int shiftbits = 0; + + if (v_out < 64) { + shiftbits = 6; + tmp_bw_faces = _bw_faces; + } else if (v_out < 1024) { + // Dynamically allocate an array to store the adjacencies. + int arysize = 1; + int tmp = v_out; + shiftbits = 1; + while ((tmp >>= 1)) shiftbits++; + arysize <<= shiftbits; + tmp_bw_faces = new triface[arysize * arysize]; + } + + if (v_out < 1024) { + for (i = 0; i < f_out; i++) { + pcavetet = (triface *) fastlookup(cavebdrylist, i); + oldtet = *pcavetet; + + // Get the tet outside the cavity. + decode(oldtet.tet[oldtet.ver], neightet); + unmarktest(neightet); + + if (ishulltet(oldtet)) { + // neightet.tet may be also a hull tet (=> oldtet is a hull edge). + neightet.ver = epivot[neightet.ver]; + if ((apex(neightet) == dummypoint)) { + hullsize++; // Create a new hull tet. + } + } + + // Create a new tet in the cavity. + V[0] = dest(neightet); + V[1] = org(neightet); + V[2] = apex(neightet); + maketetrahedron2(&newtet, V[1], V[0], insertpt, V[2]); + //bond(newtet, neightet); + newtet.tet[2] = encode2(neightet.tet, neightet.ver); + neightet.tet[neightet.ver & 3] = encode2(newtet.tet, col_v02_tbl[neightet.ver]); + + // Fill the adjacency matrix, and count v_out. + for (j = 0; j < 3; j++) { + tptr = (tetrahedron *) point2tet(V[j]); + if (((point *) tptr)[6] != insertpt) { + // Found a unique vertex of the cavity. + setpointgeomtag(V[j], local_vcount++); + //local_vcount++; + setpoint2tet(V[j], (tetrahedron) (newtet.tet)); + } + sidx[j] = pointgeomtag(V[j]); + } // j + + neightet.tet = newtet.tet; + // Avoid using lookup tables. + neightet.ver = 11; + tmp_bw_faces[(sidx[1] << shiftbits) | sidx[0]] = neightet; + neightet.ver = 1; + tmp_bw_faces[(sidx[2] << shiftbits) | sidx[1]] = neightet; + neightet.ver = 8; + tmp_bw_faces[(sidx[0] << shiftbits) | sidx[2]] = neightet; + + *pcavetet = newtet; + } // i // f_out + + // Set a handle for speeding point location. + // Randomly pick a new tet. + i = rand() % f_out; + recenttet = * (triface *) fastlookup(cavebdrylist, i); + setpoint2tet(insertpt, (tetrahedron) (recenttet.tet)); + + for (i = 0; i < f_out; i++) { + neightet = * (triface *) fastlookup(cavebdrylist, i); + if (neightet.tet[3] == NULL) { + neightet.ver = 11; + j = pointgeomtag(org(neightet)); + k = pointgeomtag(dest(neightet)); + neineitet = tmp_bw_faces[(k << shiftbits) | j]; + // bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); + neightet.tet[3] = encode2(neineitet.tet, row_v11_tbl[neineitet.ver]); + neineitet.tet[neineitet.ver & 3] = encode2(neightet.tet, col_v11_tbl[neineitet.ver]); + } + if (neightet.tet[1] == NULL) { + neightet.ver = 1; + j = pointgeomtag(org(neightet)); + k = pointgeomtag(dest(neightet)); + neineitet = tmp_bw_faces[(k << shiftbits) | j]; + neightet.tet[1] = encode2(neineitet.tet, neineitet.ver); // row_v01_tbl + neineitet.tet[neineitet.ver & 3] = encode2(neightet.tet, col_v01_tbl[neineitet.ver]); + } + if (neightet.tet[0] == NULL) { + neightet.ver = 8; + j = pointgeomtag(org(neightet)); + k = pointgeomtag(dest(neightet)); + neineitet = tmp_bw_faces[(k << shiftbits) | j]; + // bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); + neightet.tet[0] = encode2(neineitet.tet, row_v08_tbl[neineitet.ver]); + neineitet.tet[neineitet.ver & 3] = encode2(neightet.tet, col_v08_tbl[neineitet.ver]); + } + } // i + + if (v_out >= 64) { + delete [] tmp_bw_faces; + } + } // v_out < 1024 + else { + // Fill a very large cavity with original neighboring searching method. + for (i = 0; i < f_out; i++) { + pcavetet = (triface *) fastlookup(cavebdrylist, i); + oldtet = *pcavetet; + + // Get the tet outside the cavity. + decode(oldtet.tet[oldtet.ver], neightet); + unmarktest(neightet); + + if (ishulltet(oldtet)) { + // neightet.tet may be also a hull tet (=> oldtet is a hull edge). + neightet.ver = epivot[neightet.ver]; + if ((apex(neightet) == dummypoint)) { + hullsize++; // Create a new hull tet. + } + } + + // Create a new tet in the cavity. + V[0] = dest(neightet); + V[1] = org(neightet); + V[2] = apex(neightet); + maketetrahedron2(&newtet, V[1], V[0], insertpt, V[2]); + //newtet.ver = 2; // esymself(newtet); + //assert(oppo(newtet) == insertpt); + + //bond(newtet, neightet); + newtet.tet[2] = encode2(neightet.tet, neightet.ver); + neightet.tet[neightet.ver & 3] = encode2(newtet.tet, col_v02_tbl[neightet.ver]); + + // Fill the adjacency matrix, and count v_out. + for (j = 0; j < 3; j++) { + tptr = (tetrahedron *) point2tet(V[j]); + if (((point *) tptr)[6] != insertpt) { + // Found a unique vertex of the cavity. + //setpointgeomtag(V[j], local_vcount); + local_vcount++; + setpoint2tet(V[j], (tetrahedron) (newtet.tet)); + } + //sidx[j] = pointgeomtag(V[j]); + } // j + } // i, f_out + + // Set a handle for speeding point location. + //recenttet = newtet; + //setpoint2tet(insertpt, (tetrahedron) (newtet.tet)); + i = rand() % f_out; + recenttet = * (triface *) fastlookup(cavebdrylist, i); + // This is still an oldtet. + fsymself(recenttet); + fsymself(recenttet); + setpoint2tet(insertpt, (tetrahedron) (recenttet.tet)); + + for (i = 0; i < f_out; i++) { + pcavetet = (triface *) fastlookup(cavebdrylist, i); + oldtet = *pcavetet; + + fsym(oldtet, neightet); + fsym(neightet, newtet); + // Comment: oldtet and newtet must be at the same directed edge. + // Connect the three other faces of this newtet. + for (j = 0; j < 3; j++) { + esym(newtet, neightet); // Go to the face. + if (neightet.tet[neightet.ver & 3] == NULL) { + // Find the adjacent face of this newtet. + spintet = oldtet; + while (1) { + fnextself(spintet); + if (!infected(spintet)) break; + } + fsym(spintet, neineitet); + esymself(neineitet); + bond(neightet, neineitet); + } + enextself(newtet); + enextself(oldtet); + } // j + } // i + } // fill cavity + + // C(p) is re-meshed successfully. + + // Delete the old tets in C(p). + for (i = 0; i < cave_oldtet_list->objects; i++) { + oldtet.tet = *(tetrahedron **) fastlookup(cave_oldtet_list, i); + if (ishulltet(oldtet)) { + hullsize--; + } + tetrahedrondealloc(oldtet.tet); + } + + cave_oldtet_list->restart(); + cavebdrylist->restart(); + + return 1; +} + +//============================================================================// +// // +// initialdelaunay() Create an initial Delaunay tetrahedralization. // +// // +// The tetrahedralization contains only one tetrahedron abcd, and four hull // +// tetrahedra. The points pa, pb, pc, and pd must be linearly independent. // +// // +//============================================================================// + +void tetgenmesh::initialdelaunay(point pa, point pb, point pc, point pd) +{ + triface firsttet, tetopa, tetopb, tetopc, tetopd; + triface worktet, worktet1; + + if (b->verbose > 2) { + printf(" Create init tet (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + } + + // Create the first tetrahedron. + maketetrahedron2(&firsttet, pa, pb, pc, pd); + //setvertices(firsttet, pa, pb, pc, pd); + + // Create four hull tetrahedra. + maketetrahedron2(&tetopa, pb, pc, pd, dummypoint); + //setvertices(tetopa, pb, pc, pd, dummypoint); + maketetrahedron2(&tetopb, pc, pa, pd, dummypoint); + //setvertices(tetopb, pc, pa, pd, dummypoint); + maketetrahedron2(&tetopc, pa, pb, pd, dummypoint); + //setvertices(tetopc, pa, pb, pd, dummypoint); + maketetrahedron2(&tetopd, pb, pa, pc, dummypoint); + //setvertices(tetopd, pb, pa, pc, dummypoint); + + hullsize += 4; + + // Connect hull tetrahedra to firsttet (at four faces of firsttet). + bond(firsttet, tetopd); + esym(firsttet, worktet); + bond(worktet, tetopc); // ab + enextesym(firsttet, worktet); + bond(worktet, tetopa); // bc + eprevesym(firsttet, worktet); + bond(worktet, tetopb); // ca + + // Connect hull tetrahedra together (at six edges of firsttet). + esym(tetopc, worktet); + esym(tetopd, worktet1); + bond(worktet, worktet1); // ab + esym(tetopa, worktet); + eprevesym(tetopd, worktet1); + bond(worktet, worktet1); // bc + esym(tetopb, worktet); + enextesym(tetopd, worktet1); + bond(worktet, worktet1); // ca + eprevesym(tetopc, worktet); + enextesym(tetopb, worktet1); + bond(worktet, worktet1); // da + eprevesym(tetopa, worktet); + enextesym(tetopc, worktet1); + bond(worktet, worktet1); // db + eprevesym(tetopb, worktet); + enextesym(tetopa, worktet1); + bond(worktet, worktet1); // dc + + // Set the vertex type. + if (pointtype(pa) == UNUSEDVERTEX) { + setpointtype(pa, VOLVERTEX); + } + if (pointtype(pb) == UNUSEDVERTEX) { + setpointtype(pb, VOLVERTEX); + } + if (pointtype(pc) == UNUSEDVERTEX) { + setpointtype(pc, VOLVERTEX); + } + if (pointtype(pd) == UNUSEDVERTEX) { + setpointtype(pd, VOLVERTEX); + } + + setpoint2tet(pa, encode(firsttet)); + setpoint2tet(pb, encode(firsttet)); + setpoint2tet(pc, encode(firsttet)); + setpoint2tet(pd, encode(firsttet)); + + setpoint2tet(dummypoint, encode(tetopa)); + + // Remember the first tetrahedron. + recenttet = firsttet; +} + + +//============================================================================// +// // +// incrementaldelaunay() Create a Delaunay tetrahedralization by // +// the incremental approach. // +// // +//============================================================================// + + +void tetgenmesh::incrementaldelaunay(clock_t& tv) +{ + triface searchtet; + point *permutarray, swapvertex; + REAL v1[3], v2[3], n[3]; + REAL bboxsize, bboxsize2, bboxsize3, ori; + int randindex; + int ngroup = 0; + int i, j; + + if (!b->quiet) { + printf("Delaunizing vertices...\n"); + } + // Form a random permuation (uniformly at random) of the set of vertices. + permutarray = new point[in->numberofpoints]; + points->traversalinit(); + + if (b->no_sort) { + if (b->verbose) { + printf(" Using the input order.\n"); + } + for (i = 0; i < in->numberofpoints; i++) { + permutarray[i] = (point) points->traverse(); + } + } else { + if (b->verbose) { + printf(" Permuting vertices.\n"); + } + srand(in->numberofpoints); + for (i = 0; i < in->numberofpoints; i++) { + randindex = rand() % (i + 1); // randomnation(i + 1); + permutarray[i] = permutarray[randindex]; + permutarray[randindex] = (point) points->traverse(); + } + if (b->brio_hilbert) { // -b option + if (b->verbose) { + printf(" Sorting vertices.\n"); + } + hilbert_init(in->mesh_dim); + brio_multiscale_sort(permutarray, in->numberofpoints, b->brio_threshold, + b->brio_ratio, &ngroup); + } + } + + tv = clock(); // Remember the time for sorting points. + + // Calculate the diagonal size of its bounding box. + bboxsize = sqrt(norm2(xmax - xmin, ymax - ymin, zmax - zmin)); + bboxsize2 = bboxsize * bboxsize; + bboxsize3 = bboxsize2 * bboxsize; + + // Make sure the second vertex is not identical with the first one. + i = 1; + while ((distance(permutarray[0],permutarray[i])/bboxsize)epsilon) { + i++; + if (i == in->numberofpoints - 1) { + printf("Exception: All vertices are (nearly) identical (Tol = %g).\n", + b->epsilon); + terminatetetgen(this, 10); + } + } + if (i > 1) { + // Swap to move the non-identical vertex from index i to index 1. + swapvertex = permutarray[i]; + permutarray[i] = permutarray[1]; + permutarray[1] = swapvertex; + } + + // Make sure the third vertex is not collinear with the first two. + i = 2; + for (j = 0; j < 3; j++) { + v1[j] = permutarray[1][j] - permutarray[0][j]; + v2[j] = permutarray[i][j] - permutarray[0][j]; + } + cross(v1, v2, n); + while ((sqrt(norm2(n[0], n[1], n[2])) / bboxsize2) < b->epsilon) { + i++; + if (i == in->numberofpoints - 1) { + printf("Exception: All vertices are (nearly) collinear (Tol = %g).\n", + b->epsilon); + terminatetetgen(this, 10); + } + for (j = 0; j < 3; j++) { + v2[j] = permutarray[i][j] - permutarray[0][j]; + } + cross(v1, v2, n); + } + if (i > 2) { + // Swap to move the non-identical vertex from index i to index 1. + swapvertex = permutarray[i]; + permutarray[i] = permutarray[2]; + permutarray[2] = swapvertex; + } + + // Make sure the fourth vertex is not coplanar with the first three. + i = 3; + ori = orient3dfast(permutarray[0], permutarray[1], permutarray[2], + permutarray[i]); + while ((fabs(ori) / bboxsize3) < b->epsilon) { + i++; + if (i == in->numberofpoints) { + printf("Exception: All vertices are coplanar (Tol = %g).\n", + b->epsilon); + terminatetetgen(this, 10); + } + ori = orient3dfast(permutarray[0], permutarray[1], permutarray[2], + permutarray[i]); + } + if (i > 3) { + // Swap to move the non-identical vertex from index i to index 1. + swapvertex = permutarray[i]; + permutarray[i] = permutarray[3]; + permutarray[3] = swapvertex; + } + + // Orient the first four vertices in permutarray so that they follow the + // right-hand rule. + if (ori > 0.0) { + // Swap the first two vertices. + swapvertex = permutarray[0]; + permutarray[0] = permutarray[1]; + permutarray[1] = swapvertex; + } + + // Create the initial Delaunay tetrahedralization. + initialdelaunay(permutarray[0], permutarray[1], permutarray[2], + permutarray[3]); + + if (b->verbose) { + printf(" Incrementally inserting vertices.\n"); + } + insertvertexflags ivf; + flipconstraints fc; + + ivf.bowywat = 1; // Use Bowyer-Watson algorithm + ivf.lawson = 0; + + + for (i = 4; i < in->numberofpoints; i++) { + if (pointtype(permutarray[i]) == UNUSEDVERTEX) { + setpointtype(permutarray[i], VOLVERTEX); + } + if (b->brio_hilbert || b->no_sort) { // -b or -b/1 + // Start the last updated tet. + searchtet.tet = recenttet.tet; + } else { // -b0 + // Randomly choose the starting tet for point location. + searchtet.tet = NULL; + } + ivf.iloc = (int) OUTSIDE; + // Insert the vertex. + if (!insert_vertex_bw(permutarray[i], &searchtet, &ivf)) { + if (ivf.iloc == (int) ONVERTEX) { + // The point already exists. Mark it and do nothing on it. + swapvertex = org(searchtet); + if (b->object != tetgenbehavior::STL) { + if (!b->quiet) { + printf("Warning: Point #%d is coincident with #%d. Ignored!\n", + pointmark(permutarray[i]), pointmark(swapvertex)); + } + } + setpoint2ppt(permutarray[i], swapvertex); + setpointtype(permutarray[i], DUPLICATEDVERTEX); + dupverts++; + } else if (ivf.iloc == (int) NEARVERTEX) { + // This should not happen by insert_point_bw(). + terminatetetgen(this, 2); // report a bug. + } else if (ivf.iloc == (int) NONREGULAR) { + // The point is non-regular. Skipped. + if (b->verbose) { + printf(" Point #%d is non-regular, skipped.\n", + pointmark(permutarray[i])); + } + setpointtype(permutarray[i], NREGULARVERTEX); + nonregularcount++; + } + } + } + + + + delete [] permutarray; +} + +// // +// // +//== delaunay_cxx ============================================================// + +//== surface_cxx =============================================================// +// // +// // + +//============================================================================// +// // +// flipshpush() Push a facet edge into flip stack. // +// // +//============================================================================// + +void tetgenmesh::flipshpush(face* flipedge) +{ + badface *newflipface; + + newflipface = (badface *) flippool->alloc(); + newflipface->ss = *flipedge; + newflipface->forg = sorg(*flipedge); + newflipface->fdest = sdest(*flipedge); + newflipface->nextitem = flipstack; + flipstack = newflipface; +} + +//============================================================================// +// // +// flip22() Perform a 2-to-2 flip in surface mesh. // +// // +// 'flipfaces' is an array of two subfaces. On input, they are [a,b,c] and // +// [b,a,d]. On output, they are [c,d,b] and [d,c,a]. As a result, edge [a,b] // +// is replaced by edge [c,d]. // +// // +//============================================================================// + +void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) +{ + face bdedges[4], outfaces[4], infaces[4]; + face bdsegs[4]; + face checkface; + point pa, pb, pc, pd; + int i; + + pa = sorg(flipfaces[0]); + pb = sdest(flipfaces[0]); + pc = sapex(flipfaces[0]); + pd = sapex(flipfaces[1]); + + if (sorg(flipfaces[1]) != pb) { + sesymself(flipfaces[1]); + } + + flip22count++; + + // Collect the four boundary edges. + senext(flipfaces[0], bdedges[0]); + senext2(flipfaces[0], bdedges[1]); + senext(flipfaces[1], bdedges[2]); + senext2(flipfaces[1], bdedges[3]); + + // Collect outer boundary faces. + for (i = 0; i < 4; i++) { + spivot(bdedges[i], outfaces[i]); + infaces[i] = outfaces[i]; + sspivot(bdedges[i], bdsegs[i]); + if (outfaces[i].sh != NULL) { + if (isshsubseg(bdedges[i])) { + spivot(infaces[i], checkface); + while (checkface.sh != bdedges[i].sh) { + infaces[i] = checkface; + spivot(infaces[i], checkface); + } + } + } + } + + // The flags set in these two subfaces do not change. + // Shellmark does not change. + // area constraint does not change. + + // Transform [a,b,c] -> [c,d,b]. + setshvertices(flipfaces[0], pc, pd, pb); + // Transform [b,a,d] -> [d,c,a]. + setshvertices(flipfaces[1], pd, pc, pa); + + // Update the point-to-subface map. + if (pointtype(pa) == FREEFACETVERTEX) { + setpoint2sh(pa, sencode(flipfaces[1])); + } + if (pointtype(pb) == FREEFACETVERTEX) { + setpoint2sh(pb, sencode(flipfaces[0])); + } + if (pointtype(pc) == FREEFACETVERTEX) { + setpoint2sh(pc, sencode(flipfaces[0])); + } + if (pointtype(pd) == FREEFACETVERTEX) { + setpoint2sh(pd, sencode(flipfaces[0])); + } + + // Reconnect boundary edges to outer boundary faces. + for (i = 0; i < 4; i++) { + if (outfaces[(3 + i) % 4].sh != NULL) { + // Make sure that the subface has the ori as the segment. + if (bdsegs[(3 + i) % 4].sh != NULL) { + bdsegs[(3 + i) % 4].shver = 0; + if (sorg(bdedges[i]) != sorg(bdsegs[(3 + i) % 4])) { + sesymself(bdedges[i]); + } + } + sbond1(bdedges[i], outfaces[(3 + i) % 4]); + sbond1(infaces[(3 + i) % 4], bdedges[i]); + } else { + sdissolve(bdedges[i]); + } + if (bdsegs[(3 + i) % 4].sh != NULL) { + ssbond(bdedges[i], bdsegs[(3 + i) % 4]); + if (chkencflag & 1) { + // Queue this segment for encroaching check. + enqueuesubface(badsubsegs, &(bdsegs[(3 + i) % 4])); + } + } else { + ssdissolve(bdedges[i]); + } + } + + if (chkencflag & 2) { + // Queue the flipped subfaces for quality/encroaching checks. + for (i = 0; i < 2; i++) { + enqueuesubface(badsubfacs, &(flipfaces[i])); + } + } + + recentsh = flipfaces[0]; + + if (flipflag) { + // Put the boundary edges into flip stack. + for (i = 0; i < 4; i++) { + flipshpush(&(bdedges[i])); + } + } +} + +//============================================================================// +// // +// flip31() Remove a vertex by transforming 3-to-1 subfaces. // +// // +// 'flipfaces' is an array of subfaces. Its length is at least 4. On input, // +// the first three faces are: [p,a,b], [p,b,c], and [p,c,a]. This routine // +// replaces them by one face [a,b,c], it is returned in flipfaces[3]. // +// // +// NOTE: The three old subfaces are not deleted within this routine. They // +// still hold pointers to their adjacent subfaces. These informations are // +// needed by the routine 'sremovevertex()' for recovering a segment. // +// The caller of this routine must delete the old subfaces after their uses. // +// // +//============================================================================// + +void tetgenmesh::flip31(face* flipfaces, int flipflag) +{ + face bdedges[3], outfaces[3], infaces[3]; + face bdsegs[3]; + face checkface; + point pa, pb, pc; + int i; + + pa = sdest(flipfaces[0]); + pb = sdest(flipfaces[1]); + pc = sdest(flipfaces[2]); + + flip31count++; + + // Collect all infos at the three boundary edges. + for (i = 0; i < 3; i++) { + senext(flipfaces[i], bdedges[i]); + spivot(bdedges[i], outfaces[i]); + infaces[i] = outfaces[i]; + sspivot(bdedges[i], bdsegs[i]); + if (outfaces[i].sh != NULL) { + if (isshsubseg(bdedges[i])) { + spivot(infaces[i], checkface); + while (checkface.sh != bdedges[i].sh) { + infaces[i] = checkface; + spivot(infaces[i], checkface); + } + } + } + } // i + + // Create a new subface. + makeshellface(subfaces, &(flipfaces[3])); + setshvertices(flipfaces[3], pa, pb,pc); + setshellmark(flipfaces[3], shellmark(flipfaces[0])); + if (checkconstraints) { + //area = areabound(flipfaces[0]); + setareabound(flipfaces[3], areabound(flipfaces[0])); + } + if (useinsertradius) { + setfacetindex(flipfaces[3], getfacetindex(flipfaces[0])); + } + + // Update the point-to-subface map. + if (pointtype(pa) == FREEFACETVERTEX) { + setpoint2sh(pa, sencode(flipfaces[3])); + } + if (pointtype(pb) == FREEFACETVERTEX) { + setpoint2sh(pb, sencode(flipfaces[3])); + } + if (pointtype(pc) == FREEFACETVERTEX) { + setpoint2sh(pc, sencode(flipfaces[3])); + } + + // Update the three new boundary edges. + bdedges[0] = flipfaces[3]; // [a,b] + senext(flipfaces[3], bdedges[1]); // [b,c] + senext2(flipfaces[3], bdedges[2]); // [c,a] + + // Reconnect boundary edges to outer boundary faces. + for (i = 0; i < 3; i++) { + if (outfaces[i].sh != NULL) { + // Make sure that the subface has the ori as the segment. + if (bdsegs[i].sh != NULL) { + bdsegs[i].shver = 0; + if (sorg(bdedges[i]) != sorg(bdsegs[i])) { + sesymself(bdedges[i]); + } + } + sbond1(bdedges[i], outfaces[i]); + sbond1(infaces[i], bdedges[i]); + } + if (bdsegs[i].sh != NULL) { + ssbond(bdedges[i], bdsegs[i]); + } + } + + recentsh = flipfaces[3]; + + if (flipflag) { + // Put the boundary edges into flip stack. + for (i = 0; i < 3; i++) { + flipshpush(&(bdedges[i])); + } + } +} + +//============================================================================// +// // +// lawsonflip() Flip non-locally Delaunay edges. // +// // +//============================================================================// + +long tetgenmesh::lawsonflip() +{ + badface *popface; + face flipfaces[2]; + point pa, pb, pc, pd; + REAL sign; + long flipcount = 0; + + if (b->verbose > 2) { + printf(" Lawson flip %ld edges.\n", flippool->items); + } + + while (flipstack != (badface *) NULL) { + + // Pop an edge from the stack. + popface = flipstack; + flipfaces[0] = popface->ss; + pa = popface->forg; + pb = popface->fdest; + flipstack = popface->nextitem; // The next top item in stack. + flippool->dealloc((void *) popface); + + // Skip it if it is dead. + if (flipfaces[0].sh[3] == NULL) continue; + // Skip it if it is not the same edge as we saved. + if ((sorg(flipfaces[0]) != pa) || (sdest(flipfaces[0]) != pb)) continue; + // Skip it if it is a subsegment. + if (isshsubseg(flipfaces[0])) continue; + + // Get the adjacent face. + spivot(flipfaces[0], flipfaces[1]); + if (flipfaces[1].sh == NULL) continue; // Skip a hull edge. + pc = sapex(flipfaces[0]); + pd = sapex(flipfaces[1]); + + sign = incircle3d(pa, pb, pc, pd); + + if (sign < 0) { + // It is non-locally Delaunay. Flip it. + flip22(flipfaces, 1, 0); + flipcount++; + } + } + + if (b->verbose > 2) { + printf(" Performed %ld flips.\n", flipcount); + } + + return flipcount; +} + +//============================================================================// +// // +// sinsertvertex() Insert a vertex into a triangulation of a facet. // +// // +// This function uses three global arrays: 'caveshlist', 'caveshbdlist', and // +// 'caveshseglist'. On return, 'caveshlist' contains old subfaces in C(p), // +// 'caveshbdlist' contains new subfaces in C(p). If the new point lies on a // +// segment, 'cavesegshlist' returns the two new subsegments. // +// // +// 'iloc' suggests the location of the point. If it is OUTSIDE, this routine // +// will first locate the point. It starts searching from 'searchsh' or 'rec- // +// entsh' if 'searchsh' is NULL. // +// // +// If 'bowywat' is set (1), the Bowyer-Watson algorithm is used to insert // +// the vertex. Otherwise, only insert the vertex in the initial cavity. // +// // +// If 'iloc' is 'INSTAR', this means the cavity of this vertex was already // +// provided in the list 'caveshlist'. // +// // +// If 'splitseg' is not NULL, the new vertex lies on the segment and it will // +// be split. 'iloc' must be either 'ONEDGE' or 'INSTAR'. // +// // +// 'rflag' (rounding) is a parameter passed to slocate() function. If it is // +// set, after the location of the point is found, either ONEDGE or ONFACE, // +// round the result using an epsilon. // +// // +// NOTE: the old subfaces in C(p) are not deleted. They're needed in case we // +// want to remove the new point immediately. // +// // +//============================================================================// + +int tetgenmesh::sinsertvertex(point insertpt, face *searchsh, face *splitseg, + int iloc, int bowywat, int rflag) +{ + face cavesh, neighsh, *parysh; + face newsh, casout, casin; + face checkseg; + point pa, pb; + enum locateresult loc = OUTSIDE; + REAL sign, ori; + int i, j; + + if (b->verbose > 2) { + printf(" Insert facet point %d.\n", pointmark(insertpt)); + } + + if (bowywat == 3) { + loc = INSTAR; + } + + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // A segment is going to be split, no point location. + spivot(*splitseg, *searchsh); + if (loc != INSTAR) loc = ONEDGE; + } else { + if (loc != INSTAR) loc = (enum locateresult) iloc; + if (loc == OUTSIDE) { + // Do point location in surface mesh. + if (searchsh->sh == NULL) { + *searchsh = recentsh; + } + // Search the vertex. An above point must be provided ('aflag' = 1). + loc = slocate(insertpt, searchsh, 1, 1, rflag); + } + } + + + // Form the initial sC(p). + if (loc == ONFACE) { + // Add the face into list (in B-W cavity). + smarktest(*searchsh); + caveshlist->newindex((void **) &parysh); + *parysh = *searchsh; + } else if (loc == ONEDGE) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + splitseg->shver = 0; + pa = sorg(*splitseg); + } else { + pa = sorg(*searchsh); + } + if (searchsh->sh != NULL) { + // Collect all subfaces share at this edge. + neighsh = *searchsh; + while (1) { + // Adjust the origin of its edge to be 'pa'. + if (sorg(neighsh) != pa) sesymself(neighsh); + // Add this face into list (in B-W cavity). + smarktest(neighsh); + caveshlist->newindex((void **) &parysh); + *parysh = neighsh; + // Add this face into face-at-splitedge list. + cavesegshlist->newindex((void **) &parysh); + *parysh = neighsh; + // Go to the next face at the edge. + spivotself(neighsh); + // Stop if all faces at the edge have been visited. + if (neighsh.sh == searchsh->sh) break; + if (neighsh.sh == NULL) break; + } + } // If (not a non-dangling segment). + } else if (loc == ONVERTEX) { + return (int) loc; + } else if (loc == OUTSIDE) { + // Comment: This should only happen during the surface meshing step. + // Enlarge the convex hull of the triangulation by including p. + // An above point of the facet is set in 'dummypoint' to replace + // orient2d tests by orient3d tests. + // Imagine that the current edge a->b (in 'searchsh') is horizontal in a + // plane, and a->b is directed from left to right, p lies above a->b. + // Find the right-most edge of the triangulation which is visible by p. + neighsh = *searchsh; + while (1) { + senext2self(neighsh); + spivot(neighsh, casout); + if (casout.sh == NULL) { + // A convex hull edge. Is it visible by p. + ori = orient3d(sorg(neighsh), sdest(neighsh), dummypoint, insertpt); + if (ori < 0) { + *searchsh = neighsh; // Visible, update 'searchsh'. + } else { + break; // 'searchsh' is the right-most visible edge. + } + } else { + if (sorg(casout) != sdest(neighsh)) sesymself(casout); + neighsh = casout; + } + } + // Create new triangles for all visible edges of p (from right to left). + casin.sh = NULL; // No adjacent face at right. + pa = sorg(*searchsh); + pb = sdest(*searchsh); + while (1) { + // Create a new subface on top of the (visible) edge. + makeshellface(subfaces, &newsh); + setshvertices(newsh, pb, pa, insertpt); + setshellmark(newsh, shellmark(*searchsh)); + if (checkconstraints) { + //area = areabound(*searchsh); + setareabound(newsh, areabound(*searchsh)); + } + if (useinsertradius) { + setfacetindex(newsh, getfacetindex(*searchsh)); + } + // Connect the new subface to the bottom subfaces. + sbond1(newsh, *searchsh); + sbond1(*searchsh, newsh); + // Connect the new subface to its right-adjacent subface. + if (casin.sh != NULL) { + senext(newsh, casout); + sbond1(casout, casin); + sbond1(casin, casout); + } + // The left-adjacent subface has not been created yet. + senext2(newsh, casin); + // Add the new face into list (inside the B-W cavity). + smarktest(newsh); + caveshlist->newindex((void **) &parysh); + *parysh = newsh; + // Move to the convex hull edge at the left of 'searchsh'. + neighsh = *searchsh; + while (1) { + senextself(neighsh); + spivot(neighsh, casout); + if (casout.sh == NULL) { + *searchsh = neighsh; + break; + } + if (sorg(casout) != sdest(neighsh)) sesymself(casout); + neighsh = casout; + } + // A convex hull edge. Is it visible by p. + pa = sorg(*searchsh); + pb = sdest(*searchsh); + ori = orient3d(pa, pb, dummypoint, insertpt); + // Finish the process if p is not visible by the hull edge. + if (ori >= 0) break; + } + } else if (loc == INSTAR) { + // Under this case, the sub-cavity sC(p) has already been formed in + // insertvertex(). + } + + // Form the Bowyer-Watson cavity sC(p). + for (i = 0; i < caveshlist->objects; i++) { + cavesh = * (face *) fastlookup(caveshlist, i); + for (j = 0; j < 3; j++) { + if (!isshsubseg(cavesh)) { + spivot(cavesh, neighsh); + if (neighsh.sh != NULL) { + // The adjacent face exists. + if (!smarktested(neighsh)) { + if (bowywat) { + if (loc == INSTAR) { // if (bowywat > 2) { + // It must be a boundary edge. + sign = 1; + } else { + // Check if this subface is connected to adjacent tet(s). + if (!isshtet(neighsh)) { + // Check if the subface is non-Delaunay wrt. the new pt. + sign = incircle3d(sorg(neighsh), sdest(neighsh), + sapex(neighsh), insertpt); + } else { + // It is connected to an adjacent tet. A boundary edge. + sign = 1; + } + } + if (sign < 0) { + // Add the adjacent face in list (in B-W cavity). + smarktest(neighsh); + caveshlist->newindex((void **) &parysh); + *parysh = neighsh; + } + } else { + sign = 1; // A boundary edge. + } + } else { + sign = -1; // Not a boundary edge. + } + } else { + // No adjacent face. It is a hull edge. + if (loc == OUTSIDE) { + // It is a boundary edge if it does not contain p. + if ((sorg(cavesh) == insertpt) || (sdest(cavesh) == insertpt)) { + sign = -1; // Not a boundary edge. + } else { + sign = 1; // A boundary edge. + } + } else { + sign = 1; // A boundary edge. + } + } + } else { + // Do not across a segment. It is a boundary edge. + sign = 1; + } + if (sign >= 0) { + // Add a boundary edge. + caveshbdlist->newindex((void **) &parysh); + *parysh = cavesh; + } + senextself(cavesh); + } // j + } // i + + + // Creating new subfaces. + for (i = 0; i < caveshbdlist->objects; i++) { + parysh = (face *) fastlookup(caveshbdlist, i); + sspivot(*parysh, checkseg); + if ((parysh->shver & 01) != 0) sesymself(*parysh); + pa = sorg(*parysh); + pb = sdest(*parysh); + // Create a new subface. + makeshellface(subfaces, &newsh); + setshvertices(newsh, pa, pb, insertpt); + setshellmark(newsh, shellmark(*parysh)); + if (checkconstraints) { + //area = areabound(*parysh); + setareabound(newsh, areabound(*parysh)); + } + if (useinsertradius) { + setfacetindex(newsh, getfacetindex(*parysh)); + } + // Update the point-to-subface map. + if (pointtype(pa) == FREEFACETVERTEX) { + setpoint2sh(pa, sencode(newsh)); + } + if (pointtype(pb) == FREEFACETVERTEX) { + setpoint2sh(pb, sencode(newsh)); + } + // Connect newsh to outer subfaces. + spivot(*parysh, casout); + if (casout.sh != NULL) { + casin = casout; + if (checkseg.sh != NULL) { + // Make sure that newsh has the right ori at this segment. + checkseg.shver = 0; + if (sorg(newsh) != sorg(checkseg)) { + sesymself(newsh); + sesymself(*parysh); // This side should also be inverse. + } + spivot(casin, neighsh); + while (neighsh.sh != parysh->sh) { + casin = neighsh; + spivot(casin, neighsh); + } + } + sbond1(newsh, casout); + sbond1(casin, newsh); + } + if (checkseg.sh != NULL) { + ssbond(newsh, checkseg); + } + // Connect oldsh <== newsh (for connecting adjacent new subfaces). + // *parysh and newsh point to the same edge and the same ori. + sbond1(*parysh, newsh); + } + + if (newsh.sh != NULL) { + // Set a handle for searching. + recentsh = newsh; + } + + // Update the point-to-subface map. + if (pointtype(insertpt) == FREEFACETVERTEX) { + setpoint2sh(insertpt, sencode(newsh)); + } + + // Connect adjacent new subfaces together. + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, newsh); // The new subface [a, b, p]. + senextself(newsh); // At edge [b, p]. + spivot(newsh, neighsh); + if (neighsh.sh == NULL) { + // Find the adjacent new subface at edge [b, p]. + pb = sdest(*parysh); + neighsh = *parysh; + while (1) { + senextself(neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (!smarktested(neighsh)) break; + if (sdest(neighsh) != pb) sesymself(neighsh); + } + if (neighsh.sh != NULL) { + // Now 'neighsh' is a new subface at edge [b, #]. + if (sorg(neighsh) != pb) sesymself(neighsh); + senext2self(neighsh); // Go to the open edge [p, b]. + sbond(newsh, neighsh); + } + } + spivot(*parysh, newsh); // The new subface [a, b, p]. + senext2self(newsh); // At edge [p, a]. + spivot(newsh, neighsh); + if (neighsh.sh == NULL) { + // Find the adjacent new subface at edge [p, a]. + pa = sorg(*parysh); + neighsh = *parysh; + while (1) { + senext2self(neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (!smarktested(neighsh)) break; + if (sorg(neighsh) != pa) sesymself(neighsh); + } + if (neighsh.sh != NULL) { + // Now 'neighsh' is a new subface at edge [#, a]. + if (sdest(neighsh) != pa) sesymself(neighsh); + senextself(neighsh); // Go to the open edge [a, p]. + sbond(newsh, neighsh); + } + } + } + + if ((loc == ONEDGE) || ((splitseg != NULL) && (splitseg->sh != NULL)) + || (cavesegshlist->objects > 0l)) { + // An edge is being split. We distinguish two cases: + // (1) the edge is not on the boundary of the cavity; + // (2) the edge is on the boundary of the cavity. + // In case (2), the edge is either a segment or a hull edge. There are + // degenerated new faces in the cavity. They must be removed. + face aseg, bseg, aoutseg, boutseg; + + for (i = 0; i < cavesegshlist->objects; i++) { + // Get the saved old subface. + parysh = (face *) fastlookup(cavesegshlist, i); + // Get a possible new degenerated subface. + spivot(*parysh, cavesh); + if (sapex(cavesh) == insertpt) { + // Found a degenerated new subface, i.e., case (2). + if (cavesegshlist->objects > 1) { + // There are more than one subface share at this edge. + j = (i + 1) % (int) cavesegshlist->objects; + parysh = (face *) fastlookup(cavesegshlist, j); + spivot(*parysh, neighsh); + // Adjust cavesh and neighsh both at edge a->b, and has p as apex. + if (sorg(neighsh) != sorg(cavesh)) { + sesymself(neighsh); + } + // Connect adjacent faces at two other edges of cavesh and neighsh. + // As a result, the two degenerated new faces are squeezed from the + // new triangulation of the cavity. Note that the squeezed faces + // still hold the adjacent informations which will be used in + // re-connecting subsegments (if they exist). + for (j = 0; j < 2; j++) { + senextself(cavesh); + senextself(neighsh); + spivot(cavesh, newsh); + spivot(neighsh, casout); + sbond1(newsh, casout); // newsh <- casout. + } + } else { + // There is only one subface containing this edge [a,b]. Squeeze the + // degenerated new face [a,b,c] by disconnecting it from its two + // adjacent subfaces at edges [b,c] and [c,a]. Note that the face + // [a,b,c] still hold the connection to them. + for (j = 0; j < 2; j++) { + senextself(cavesh); + spivot(cavesh, newsh); + sdissolve(newsh); + } + } + //recentsh = newsh; + // Update the point-to-subface map. + if (pointtype(insertpt) == FREEFACETVERTEX) { + setpoint2sh(insertpt, sencode(newsh)); + } + } + } + + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + if (loc != INSTAR) { // if (bowywat < 3) { + smarktest(*splitseg); // Mark it as being processed. + } + + aseg = *splitseg; + pa = sorg(*splitseg); + pb = sdest(*splitseg); + + // Insert the new point p. + makeshellface(subsegs, &aseg); + makeshellface(subsegs, &bseg); + + setshvertices(aseg, pa, insertpt, NULL); + setshvertices(bseg, insertpt, pb, NULL); + setshellmark(aseg, shellmark(*splitseg)); + setshellmark(bseg, shellmark(*splitseg)); + if (checkconstraints) { + setareabound(aseg, areabound(*splitseg)); + setareabound(bseg, areabound(*splitseg)); + } + if (useinsertradius) { + setfacetindex(aseg, getfacetindex(*splitseg)); + setfacetindex(bseg, getfacetindex(*splitseg)); + } + + // Connect [#, a]<->[a, p]. + senext2(*splitseg, boutseg); // Temporarily use boutseg. + spivotself(boutseg); + if (boutseg.sh != NULL) { + senext2(aseg, aoutseg); + sbond(boutseg, aoutseg); + } + // Connect [p, b]<->[b, #]. + senext(*splitseg, aoutseg); + spivotself(aoutseg); + if (aoutseg.sh != NULL) { + senext(bseg, boutseg); + sbond(boutseg, aoutseg); + } + // Connect [a, p] <-> [p, b]. + senext(aseg, aoutseg); + senext2(bseg, boutseg); + sbond(aoutseg, boutseg); + + // Connect subsegs [a, p] and [p, b] to adjacent new subfaces. + // Although the degenerated new faces have been squeezed. They still + // hold the connections to the actual new faces. + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + spivot(*parysh, neighsh); + // neighsh is a degenerated new face. + if (sorg(neighsh) != pa) { + sesymself(neighsh); + } + senext2(neighsh, newsh); + spivotself(newsh); // The edge [p, a] in newsh + ssbond(newsh, aseg); + senext(neighsh, newsh); + spivotself(newsh); // The edge [b, p] in newsh + ssbond(newsh, bseg); + } + + + // Let the point remember the segment it lies on. + if (pointtype(insertpt) == FREESEGVERTEX) { + setpoint2sh(insertpt, sencode(aseg)); + } + // Update the point-to-seg map. + if (pointtype(pa) == FREESEGVERTEX) { + setpoint2sh(pa, sencode(aseg)); + } + if (pointtype(pb) == FREESEGVERTEX) { + setpoint2sh(pb, sencode(bseg)); + } + } // if ((splitseg != NULL) && (splitseg->sh != NULL)) + + // Delete all degenerated new faces. + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + spivotself(*parysh); + if (sapex(*parysh) == insertpt) { + shellfacedealloc(subfaces, parysh->sh); + } + } + cavesegshlist->restart(); + + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + // Return the two new subsegments (for further process). + // Re-use 'cavesegshlist'. + cavesegshlist->newindex((void **) &parysh); + *parysh = aseg; + cavesegshlist->newindex((void **) &parysh); + *parysh = bseg; + } + } // if (loc == ONEDGE) + + + return (int) loc; +} + +//============================================================================// +// // +// sremovevertex() Remove a vertex from the surface mesh. // +// // +// 'delpt' (p) is the vertex to be removed. If 'parentseg' is not NULL, p is // +// a segment vertex, and the origin of 'parentseg' is p. Otherwise, p is a // +// facet vertex, and the origin of 'parentsh' is p. // +// // +// Within each facet, we first use a sequence of 2-to-2 flips to flip any // +// edge at p, finally use a 3-to-1 flip to remove p. // +// // +// All new created subfaces are returned in the global array 'caveshbdlist'. // +// The new segment (when p is on segment) is returned in 'parentseg'. // +// // +// If 'lawson' > 0, the Lawson flip algorithm is used to recover Delaunay- // +// ness after p is removed. // +// // +//============================================================================// + +int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, + int lawson) +{ + face flipfaces[4], spinsh, *parysh; + point pa, pb, pc, pd; + REAL ori1, ori2; + int it, i, j; + + if (parentseg != NULL) { + // 'delpt' (p) should be a Steiner point inserted in a segment [a,b], + // where 'parentseg' should be [p,b]. Find the segment [a,p]. + face startsh, neighsh, nextsh; + face abseg, prevseg, checkseg; + face adjseg1, adjseg2; + face fakesh; + senext2(*parentseg, prevseg); + spivotself(prevseg); + prevseg.shver = 0; + // Restore the original segment [a,b]. + pa = sorg(prevseg); + pb = sdest(*parentseg); + if (b->verbose > 2) { + printf(" Remove vertex %d from segment [%d, %d].\n", + pointmark(delpt), pointmark(pa), pointmark(pb)); + } + makeshellface(subsegs, &abseg); + setshvertices(abseg, pa, pb, NULL); + setshellmark(abseg, shellmark(*parentseg)); + if (checkconstraints) { + setareabound(abseg, areabound(*parentseg)); + } + if (useinsertradius) { + setfacetindex(abseg, getfacetindex(*parentseg)); + } + // Connect [#, a]<->[a, b]. + senext2(prevseg, adjseg1); + spivotself(adjseg1); + if (adjseg1.sh != NULL) { + adjseg1.shver = 0; + senextself(adjseg1); + senext2(abseg, adjseg2); + sbond(adjseg1, adjseg2); + } + // Connect [a, b]<->[b, #]. + senext(*parentseg, adjseg1); + spivotself(adjseg1); + if (adjseg1.sh != NULL) { + adjseg1.shver = 0; + senext2self(adjseg1); + senext(abseg, adjseg2); + sbond(adjseg1, adjseg2); + } + // Update the point-to-segment map. + setpoint2sh(pa, sencode(abseg)); + setpoint2sh(pb, sencode(abseg)); + + // Get the faces in face ring at segment [p, b]. + // Re-use array 'caveshlist'. + spivot(*parentseg, *parentsh); + if (parentsh->sh != NULL) { + spinsh = *parentsh; + while (1) { + // Save this face in list. + caveshlist->newindex((void **) &parysh); + *parysh = spinsh; + // Go to the next face in the ring. + spivotself(spinsh); + if (spinsh.sh == NULL) { + break; // It is possible there is only one facet. + } + if (spinsh.sh == parentsh->sh) break; + } + } + + // Create the face ring of the new segment [a,b]. Each face in the ring + // is [a,b,p] (degenerated!). It will be removed (automatically). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + startsh = *parysh; + if (sorg(startsh) != delpt) { + sesymself(startsh); + } + // startsh is [p, b, #1], find the subface [a, p, #2]. + neighsh = startsh; + while (1) { + senext2self(neighsh); + sspivot(neighsh, checkseg); + if (checkseg.sh != NULL) { + // It must be the segment [a, p]. + break; + } + spivotself(neighsh); + if (sorg(neighsh) != delpt) sesymself(neighsh); + } + // Now neighsh is [a, p, #2]. + if (neighsh.sh != startsh.sh) { + // Detach the two subsegments [a,p] and [p,b] from subfaces. + ssdissolve(startsh); + ssdissolve(neighsh); + // Create a degenerated subface [a,b,p]. It is used to: (1) hold the + // new segment [a,b]; (2) connect to the two adjacent subfaces + // [p,b,#] and [a,p,#]. + makeshellface(subfaces, &fakesh); + setshvertices(fakesh, pa, pb, delpt); + setshellmark(fakesh, shellmark(startsh)); + // Connect fakesh to the segment [a,b]. + ssbond(fakesh, abseg); + // Connect fakesh to adjacent subfaces: [p,b,#1] and [a,p,#2]. + senext(fakesh, nextsh); + sbond(nextsh, startsh); + senext2(fakesh, nextsh); + sbond(nextsh, neighsh); + smarktest(fakesh); // Mark it as faked. + } else { + // Special case. There exists already a degenerated face [a,b,p]! + // There is no need to create a faked subface here. + senext2self(neighsh); // [a,b,p] + // Since we will re-connect the face ring using the faked subfaces. + // We put the adjacent face of [a,b,p] to the list. + spivot(neighsh, startsh); // The original adjacent subface. + if (sorg(startsh) != pa) sesymself(startsh); + sdissolve(startsh); + // Connect fakesh to the segment [a,b]. + ssbond(startsh, abseg); + fakesh = startsh; // Do not mark it! + // Delete the degenerated subface. + shellfacedealloc(subfaces, neighsh.sh); + } + // Save the fakesh in list (for re-creating the face ring). + cavesegshlist->newindex((void **) &parysh); + *parysh = fakesh; + } // i + caveshlist->restart(); + + // Re-create the face ring. + if (cavesegshlist->objects > 1) { + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + fakesh = *parysh; + // Get the next face in the ring. + j = (i + 1) % cavesegshlist->objects; + parysh = (face *) fastlookup(cavesegshlist, j); + nextsh = *parysh; + sbond1(fakesh, nextsh); + } + } + + // Delete the two subsegments containing p. + shellfacedealloc(subsegs, parentseg->sh); + shellfacedealloc(subsegs, prevseg.sh); + // Return the new segment. + *parentseg = abseg; + } else { + // p is inside the surface. + if (b->verbose > 2) { + printf(" Remove vertex %d from surface.\n", pointmark(delpt)); + } + // Let 'delpt' be its apex. + senextself(*parentsh); + // For unifying the code, we add parentsh to list. + cavesegshlist->newindex((void **) &parysh); + *parysh = *parentsh; + } + + // Remove the point (p). + + for (it = 0; it < cavesegshlist->objects; it++) { + parentsh = (face *) fastlookup(cavesegshlist, it); // [a,b,p] + senextself(*parentsh); // [b,p,a]. + spivotself(*parentsh); + if (sorg(*parentsh) != delpt) sesymself(*parentsh); + // now parentsh is [p,b,#]. + if (sorg(*parentsh) != delpt) { + // The vertex has already been removed in above special case. + continue; + } + + while (1) { + // Initialize the flip edge list. Re-use 'caveshlist'. + spinsh = *parentsh; // [p, b, #] + while (1) { + caveshlist->newindex((void **) &parysh); + *parysh = spinsh; + senext2self(spinsh); + spivotself(spinsh); + if (spinsh.sh == parentsh->sh) break; + if (sorg(spinsh) != delpt) sesymself(spinsh); + } // while (1) + + if (caveshlist->objects == 3) { + // Delete the point by a 3-to-1 flip. + for (i = 0; i < 3; i++) { + parysh = (face *) fastlookup(caveshlist, i); + flipfaces[i] = *parysh; + } + flip31(flipfaces, lawson); + for (i = 0; i < 3; i++) { + shellfacedealloc(subfaces, flipfaces[i].sh); + } + caveshlist->restart(); + // Save the new subface. + caveshbdlist->newindex((void **) &parysh); + *parysh = flipfaces[3]; + // The vertex is removed. + break; + } + + // Search an edge to flip. + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + flipfaces[0] = *parysh; + spivot(flipfaces[0], flipfaces[1]); + if (sorg(flipfaces[0]) != sdest(flipfaces[1])) + sesymself(flipfaces[1]); + // Skip this edge if it belongs to a faked subface. + if (!smarktested(flipfaces[0]) && !smarktested(flipfaces[1])) { + pa = sorg(flipfaces[0]); + pb = sdest(flipfaces[0]); + pc = sapex(flipfaces[0]); + pd = sapex(flipfaces[1]); + calculateabovepoint4(pa, pb, pc, pd); + // Check if a 2-to-2 flip is possible. + ori1 = orient3d(pc, pd, dummypoint, pa); + ori2 = orient3d(pc, pd, dummypoint, pb); + if (ori1 * ori2 < 0) { + // A 2-to-2 flip is found. + flip22(flipfaces, lawson, 0); + // The i-th edge is flipped. The i-th and (i-1)-th subfaces are + // changed. The 'flipfaces[1]' contains p as its apex. + senext2(flipfaces[1], *parentsh); + // Save the new subface. + caveshbdlist->newindex((void **) &parysh); + *parysh = flipfaces[0]; + break; + } + } // + } // i + + if (i == caveshlist->objects) { + // Do a flip22 and a flip31 to remove p. + parysh = (face *) fastlookup(caveshlist, 0); + flipfaces[0] = *parysh; + spivot(flipfaces[0], flipfaces[1]); + if (sorg(flipfaces[0]) != sdest(flipfaces[1])) { + sesymself(flipfaces[1]); + } + flip22(flipfaces, lawson, 0); + senext2(flipfaces[1], *parentsh); + // Save the new subface. + caveshbdlist->newindex((void **) &parysh); + *parysh = flipfaces[0]; + } + + // The edge list at p are changed. + caveshlist->restart(); + } // while (1) + + } // it + + cavesegshlist->restart(); + + if (b->verbose > 2) { + printf(" Created %ld new subfaces.\n", caveshbdlist->objects); + } + + + if (lawson) { + lawsonflip(); + } + + return 0; +} + +//============================================================================// +// // +// slocate() Locate a point in a surface triangulation. // +// // +// Staring the search from 'searchsh'(it should not be NULL). Perform a line // +// walk search for a subface containing the point (p). // +// // +// If 'aflag' is set, the 'dummypoint' is pre-calculated so that it lies // +// above the 'searchsh' in its current orientation. The test if c is CCW to // +// the line a->b can be done by the test if c is below the oriented plane // +// a->b->dummypoint. // +// // +// If 'cflag' is not TRUE, the triangulation may not be convex. Stop search // +// when a segment is met and return OUTSIDE. // +// // +// If 'rflag' (rounding) is set, after the location of the point is found, // +// either ONEDGE or ONFACE, round the result using an epsilon. // +// // +// The returned value indicates the following cases: // +// - ONVERTEX, p is the origin of 'searchsh'. // +// - ONEDGE, p lies on the edge of 'searchsh'. // +// - ONFACE, p lies in the interior of 'searchsh'. // +// - OUTSIDE, p lies outside of the triangulation, p is on the left-hand // +// side of the edge 'searchsh'(s), i.e., org(s), dest(s), p are CW. // +// // +//============================================================================// + +enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, + face* searchsh, int aflag, int cflag, int rflag) +{ + face neighsh; + point pa, pb, pc; + enum locateresult loc; + enum {MOVE_BC, MOVE_CA} nextmove; + REAL ori, ori_bc, ori_ca; + int i; + + pa = sorg(*searchsh); + pb = sdest(*searchsh); + pc = sapex(*searchsh); + + if (!aflag) { + // No above point is given. Calculate an above point for this facet. + calculateabovepoint4(pa, pb, pc, searchpt); + } + + // 'dummypoint' is given. Make sure it is above [a,b,c] + ori = orient3d(pa, pb, pc, dummypoint); + if (ori > 0) { + sesymself(*searchsh); // Reverse the face orientation. + } else if (ori == 0.0) { + // This case should not happen theoretically. But... + return UNKNOWN; + } + + // Find an edge of the face s.t. p lies on its right-hand side (CCW). + for (i = 0; i < 3; i++) { + pa = sorg(*searchsh); + pb = sdest(*searchsh); + ori = orient3d(pa, pb, dummypoint, searchpt); + if (ori > 0) break; + senextself(*searchsh); + } + if (i == 3) { + return UNKNOWN; + } + + pc = sapex(*searchsh); + + if (pc == searchpt) { + senext2self(*searchsh); + return ONVERTEX; + } + + while (1) { + + ori_bc = orient3d(pb, pc, dummypoint, searchpt); + ori_ca = orient3d(pc, pa, dummypoint, searchpt); + + if (ori_bc < 0) { + if (ori_ca < 0) { // (--) + // Any of the edges is a viable move. + if (randomnation(2)) { + nextmove = MOVE_CA; + } else { + nextmove = MOVE_BC; + } + } else { // (-#) + // Edge [b, c] is viable. + nextmove = MOVE_BC; + } + } else { + if (ori_ca < 0) { // (#-) + // Edge [c, a] is viable. + nextmove = MOVE_CA; + } else { + if (ori_bc > 0) { + if (ori_ca > 0) { // (++) + loc = ONFACE; // Inside [a, b, c]. + break; + } else { // (+0) + senext2self(*searchsh); // On edge [c, a]. + loc = ONEDGE; + break; + } + } else { // ori_bc == 0 + if (ori_ca > 0) { // (0+) + senextself(*searchsh); // On edge [b, c]. + loc = ONEDGE; + break; + } else { // (00) + // p is coincident with vertex c. + senext2self(*searchsh); + return ONVERTEX; + } + } + } + } + + // Move to the next face. + if (nextmove == MOVE_BC) { + senextself(*searchsh); + } else { + senext2self(*searchsh); + } + if (!cflag) { + // NON-convex case. Check if we will cross a boundary. + if (isshsubseg(*searchsh)) { + return ENCSEGMENT; + } + } + spivot(*searchsh, neighsh); + if (neighsh.sh == NULL) { + return OUTSIDE; // A hull edge. + } + // Adjust the edge orientation. + if (sorg(neighsh) != sdest(*searchsh)) { + sesymself(neighsh); + } + + // Update the newly discovered face and its endpoints. + *searchsh = neighsh; + pa = sorg(*searchsh); + pb = sdest(*searchsh); + pc = sapex(*searchsh); + + if (pc == searchpt) { + senext2self(*searchsh); + return ONVERTEX; + } + + } // while (1) + + // assert(loc == ONFACE || loc == ONEDGE); + + + if (rflag) { + // Round the locate result before return. + REAL n[3], area_abc, area_abp, area_bcp, area_cap; + + pa = sorg(*searchsh); + pb = sdest(*searchsh); + pc = sapex(*searchsh); + + facenormal(pa, pb, pc, n, 1, NULL); + area_abc = sqrt(dot(n, n)); + + facenormal(pb, pc, searchpt, n, 1, NULL); + area_bcp = sqrt(dot(n, n)); + if ((area_bcp / area_abc) < b->epsilon) { + area_bcp = 0; // Rounding. + } + + facenormal(pc, pa, searchpt, n, 1, NULL); + area_cap = sqrt(dot(n, n)); + if ((area_cap / area_abc) < b->epsilon) { + area_cap = 0; // Rounding + } + + if ((loc == ONFACE) || (loc == OUTSIDE)) { + facenormal(pa, pb, searchpt, n, 1, NULL); + area_abp = sqrt(dot(n, n)); + if ((area_abp / area_abc) < b->epsilon) { + area_abp = 0; // Rounding + } + } else { // loc == ONEDGE + area_abp = 0; + } + + if (area_abp == 0) { + if (area_bcp == 0) { + senextself(*searchsh); + loc = ONVERTEX; // p is close to b. + } else { + if (area_cap == 0) { + loc = ONVERTEX; // p is close to a. + } else { + loc = ONEDGE; // p is on edge [a,b]. + } + } + } else if (area_bcp == 0) { + if (area_cap == 0) { + senext2self(*searchsh); + loc = ONVERTEX; // p is close to c. + } else { + senextself(*searchsh); + loc = ONEDGE; // p is on edge [b,c]. + } + } else if (area_cap == 0) { + senext2self(*searchsh); + loc = ONEDGE; // p is on edge [c,a]. + } else { + loc = ONFACE; // p is on face [a,b,c]. + } + } // if (rflag) + + return loc; +} + +//============================================================================// +// // +// sscoutsegment() Look for a segment in the surface triangulation. // +// // +// The segment is given by the origin of 'searchsh' and 'endpt'. // +// // +// If an edge in T is found matching this segment, the segment is "locked" // +// in T at the edge. Otherwise, flip the first edge in T that the segment // +// crosses. Continue the search from the flipped face. // +// // +// This routine uses 'orisent3d' to determine the search direction. It uses // +// 'dummypoint' as the 'lifted point' in 3d, and it assumes that it (dummy- // +// point) lies above the 'searchsh' (w.r.t the Right-hand rule). // +// // +//============================================================================// + +enum tetgenmesh::interresult tetgenmesh::sscoutsegment(face *searchsh, + point endpt, int insertsegflag, int reporterrorflag, int chkencflag) +{ + face flipshs[2], neighsh; + point startpt, pa, pb, pc, pd; + enum interresult dir; + enum {MOVE_AB, MOVE_CA} nextmove; + REAL ori_ab, ori_ca, len; + + pc = NULL; // Avoid warnings from MSVC + // The origin of 'searchsh' is fixed. + startpt = sorg(*searchsh); + nextmove = MOVE_AB; // Avoid compiler warning. + + if (b->verbose > 2) { + printf(" Scout segment (%d, %d).\n", pointmark(startpt), + pointmark(endpt)); + } + len = distance(startpt, endpt); + + // Search an edge in 'searchsh' on the path of this segment. + while (1) { + + pb = sdest(*searchsh); + if (pb == endpt) { + dir = SHAREEDGE; // Found! + break; + } + + pc = sapex(*searchsh); + if (pc == endpt) { + senext2self(*searchsh); + sesymself(*searchsh); + dir = SHAREEDGE; // Found! + break; + } + + + // Round the results. + if ((sqrt(triarea(startpt, pb, endpt)) / len) < b->epsilon) { + ori_ab = 0.0; + } else { + ori_ab = orient3d(startpt, pb, dummypoint, endpt); + } + if ((sqrt(triarea(pc, startpt, endpt)) / len) < b->epsilon) { + ori_ca = 0.0; + } else { + ori_ca = orient3d(pc, startpt, dummypoint, endpt); + } + + if (ori_ab < 0) { + if (ori_ca < 0) { // (--) + // Both sides are viable moves. + if (randomnation(2)) { + nextmove = MOVE_CA; + } else { + nextmove = MOVE_AB; + } + } else { // (-#) + nextmove = MOVE_AB; + } + } else { + if (ori_ca < 0) { // (#-) + nextmove = MOVE_CA; + } else { + if (ori_ab > 0) { + if (ori_ca > 0) { // (++) + // The segment intersects with edge [b, c]. + dir = ACROSSEDGE; + break; + } else { // (+0) + // The segment collinear with edge [c, a]. + senext2self(*searchsh); + sesymself(*searchsh); + dir = ACROSSVERT; + break; + } + } else { + if (ori_ca > 0) { // (0+) + // The segment is collinear with edge [a, b]. + dir = ACROSSVERT; + break; + } else { // (00) + // startpt == endpt. Not possible. + terminatetetgen(this, 2); + } + } + } + } + + // Move 'searchsh' to the next face, keep the origin unchanged. + if (nextmove == MOVE_AB) { + if (chkencflag) { + // Do not cross boundary. + if (isshsubseg(*searchsh)) { + return ACROSSEDGE; // ACROSS_SEG + } + } + spivot(*searchsh, neighsh); + if (neighsh.sh != NULL) { + if (sorg(neighsh) != pb) sesymself(neighsh); + senext(neighsh, *searchsh); + } else { + // This side (startpt->pb) is outside. It is caused by rounding error. + // Try the next side, i.e., (pc->startpt). + senext2(*searchsh, neighsh); + if (chkencflag) { + // Do not cross boundary. + if (isshsubseg(neighsh)) { + *searchsh = neighsh; + return ACROSSEDGE; // ACROSS_SEG + } + } + spivotself(neighsh); + if (sdest(neighsh) != pc) sesymself(neighsh); + *searchsh = neighsh; + } + } else { // MOVE_CA + senext2(*searchsh, neighsh); + if (chkencflag) { + // Do not cross boundary. + if (isshsubseg(neighsh)) { + *searchsh = neighsh; + return ACROSSEDGE; // ACROSS_SEG + } + } + spivotself(neighsh); + if (neighsh.sh != NULL) { + if (sdest(neighsh) != pc) sesymself(neighsh); + *searchsh = neighsh; + } else { + // The same reason as above. + // Try the next side, i.e., (startpt->pb). + if (chkencflag) { + // Do not cross boundary. + if (isshsubseg(*searchsh)) { + return ACROSSEDGE; // ACROSS_SEG + } + } + spivot(*searchsh, neighsh); + if (sorg(neighsh) != pb) sesymself(neighsh); + senext(neighsh, *searchsh); + } + } + } // while + + if (dir == SHAREEDGE) { + if (insertsegflag) { + // Insert the segment into the triangulation. + face newseg; + makeshellface(subsegs, &newseg); + setshvertices(newseg, startpt, endpt, NULL); + // Set the default segment marker. + setshellmark(newseg, -1); + ssbond(*searchsh, newseg); + spivot(*searchsh, neighsh); + if (neighsh.sh != NULL) { + ssbond(neighsh, newseg); + } + } + return dir; + } + + if (dir == ACROSSVERT) { + // A point is found collinear with this segment. + if (reporterrorflag) { + point pp = sdest(*searchsh); + printf("PLC Error: A vertex lies in a segment in facet #%d.\n", + shellmark(*searchsh)); + printf(" Vertex: [%d] (%g,%g,%g).\n",pointmark(pp),pp[0],pp[1],pp[2]); + printf(" Segment: [%d, %d]\n", pointmark(startpt), pointmark(endpt)); + } + return dir; + } + + if (dir == ACROSSEDGE) { + // Edge [b, c] intersects with the segment. + senext(*searchsh, flipshs[0]); + if (isshsubseg(flipshs[0])) { + if (reporterrorflag) { + REAL P[3], Q[3], tp = 0, tq = 0; + linelineint(startpt, endpt, pb, pc, P, Q, &tp, &tq); + printf("PLC Error: Two segments intersect at point (%g,%g,%g),", + P[0], P[1], P[2]); + printf(" in facet #%d.\n", shellmark(*searchsh)); + printf(" Segment 1: [%d, %d]\n", pointmark(pb), pointmark(pc)); + printf(" Segment 2: [%d, %d]\n", pointmark(startpt),pointmark(endpt)); + } + return dir; // ACROSS_SEG + } + // Flip edge [b, c], queue unflipped edges (for Delaunay checks). + spivot(flipshs[0], flipshs[1]); + if (sorg(flipshs[1]) != sdest(flipshs[0])) sesymself(flipshs[1]); + flip22(flipshs, 1, 0); + // The flip may create an inverted triangle, check it. + pa = sapex(flipshs[1]); + pb = sapex(flipshs[0]); + pc = sorg(flipshs[0]); + pd = sdest(flipshs[0]); + // Check if pa and pb are on the different sides of [pc, pd]. + // Re-use ori_ab, ori_ca for the tests. + ori_ab = orient3d(pc, pd, dummypoint, pb); + ori_ca = orient3d(pd, pc, dummypoint, pa); + if (ori_ab <= 0) { + flipshpush(&(flipshs[0])); + } else if (ori_ca <= 0) { + flipshpush(&(flipshs[1])); + } + // Set 'searchsh' s.t. its origin is 'startpt'. + *searchsh = flipshs[0]; + } + + return sscoutsegment(searchsh, endpt, insertsegflag, reporterrorflag, + chkencflag); +} + +//============================================================================// +// // +// scarveholes() Remove triangles not in the facet. // +// // +// This routine re-uses the two global arrays: caveshlist and caveshbdlist. // +// // +//============================================================================// + +void tetgenmesh::scarveholes(int holes, REAL* holelist) +{ + face *parysh, searchsh, neighsh; + enum locateresult loc; + int i, j; + + // Get all triangles. Infect unprotected convex hull triangles. + smarktest(recentsh); + caveshlist->newindex((void **) &parysh); + *parysh = recentsh; + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + searchsh = *parysh; + searchsh.shver = 0; + for (j = 0; j < 3; j++) { + spivot(searchsh, neighsh); + // Is this side on the convex hull? + if (neighsh.sh != NULL) { + if (!smarktested(neighsh)) { + smarktest(neighsh); + caveshlist->newindex((void **) &parysh); + *parysh = neighsh; + } + } else { + // A hull side. Check if it is protected by a segment. + if (!isshsubseg(searchsh)) { + // Not protected. Save this face. + if (!sinfected(searchsh)) { + sinfect(searchsh); + caveshbdlist->newindex((void **) &parysh); + *parysh = searchsh; + } + } + } + senextself(searchsh); + } + } + + // Infect the triangles in the holes. + for (i = 0; i < 3 * holes; i += 3) { + searchsh = recentsh; + loc = slocate(&(holelist[i]), &searchsh, 1, 1, 0); + if (loc != OUTSIDE) { + sinfect(searchsh); + caveshbdlist->newindex((void **) &parysh); + *parysh = searchsh; + } + } + + // Find and infect all exterior triangles. + for (i = 0; i < caveshbdlist->objects; i++) { + parysh = (face *) fastlookup(caveshbdlist, i); + searchsh = *parysh; + searchsh.shver = 0; + for (j = 0; j < 3; j++) { + spivot(searchsh, neighsh); + if (neighsh.sh != NULL) { + if (!isshsubseg(searchsh)) { + if (!sinfected(neighsh)) { + sinfect(neighsh); + caveshbdlist->newindex((void **) &parysh); + *parysh = neighsh; + } + } else { + sdissolve(neighsh); // Disconnect a protected face. + } + } + senextself(searchsh); + } + } + + // Delete exterior triangles, unmark interior triangles. + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + if (sinfected(*parysh)) { + shellfacedealloc(subfaces, parysh->sh); + } else { + sunmarktest(*parysh); + } + } + + caveshlist->restart(); + caveshbdlist->restart(); +} + +//============================================================================// +// // +// triangulate() Create a CDT for the facet. // +// // +// All vertices of the triangulation have type FACETVERTEX. The actual type // +// of boundary vertices are set by the routine unifysements(). // +// // +// All segments created here will have a default marker '-1'. Some of these // +// segments will get their actual marker defined in 'edgemarkerlist'. // +// // +//============================================================================// + +int tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, + int holes, REAL* holelist) +{ + face searchsh, newsh, *parysh; + face newseg, *paryseg; + point pa, pb, pc, *ppt, *cons; + int iloc; + int i, j; + + if (b->verbose > 2) { + printf(" f%d: %ld vertices, %ld segments", shmark, ptlist->objects, + conlist->objects); + if (holes > 0) { + printf(", %d holes", holes); + } + printf(".\n"); + } + + if (ptlist->objects < 2l) { + // Not a segment or a facet. + return 1; + } else if (ptlist->objects == 2l) { + pa = * (point *) fastlookup(ptlist, 0); + pb = * (point *) fastlookup(ptlist, 1); + if (distance(pa, pb) > 0) { + // It is a single segment. + makeshellface(subsegs, &newseg); + setshvertices(newseg, pa, pb, NULL); + setshellmark(newseg, -1); + } + if (pointtype(pa) == VOLVERTEX) { + setpointtype(pa, FACETVERTEX); + } + if (pointtype(pb) == VOLVERTEX) { + setpointtype(pb, FACETVERTEX); + } + return 1; + } else if (ptlist->objects == 3) { + pa = * (point *) fastlookup(ptlist, 0); + pb = * (point *) fastlookup(ptlist, 1); + pc = * (point *) fastlookup(ptlist, 2); + } else { + // Calculate an above point of this facet. + if (!calculateabovepoint(ptlist, &pa, &pb, &pc)) { + if (!b->quiet) { + printf("Warning: Unable to triangulate facet #%d. Skipped!\n",shmark); + } + return 0; // The point set is degenerate. + } + } + + // Create an initial triangulation. + makeshellface(subfaces, &newsh); + setshvertices(newsh, pa, pb, pc); + setshellmark(newsh, shmark); + recentsh = newsh; + + if (pointtype(pa) == VOLVERTEX) { + setpointtype(pa, FACETVERTEX); + } + if (pointtype(pb) == VOLVERTEX) { + setpointtype(pb, FACETVERTEX); + } + if (pointtype(pc) == VOLVERTEX) { + setpointtype(pc, FACETVERTEX); + } + + // Are there area constraints? + if (b->quality && (in->facetconstraintlist != NULL)) { + for (i = 0; i < in->numberoffacetconstraints; i++) { + if (shmark == ((int) in->facetconstraintlist[i * 2])) { + REAL area = in->facetconstraintlist[i * 2 + 1]; + setareabound(newsh, area); + break; + } + } + } + + if (ptlist->objects == 3) { + // The triangulation only has one element. + for (i = 0; i < 3; i++) { + makeshellface(subsegs, &newseg); + setshvertices(newseg, sorg(newsh), sdest(newsh), NULL); + setshellmark(newseg, -1); + ssbond(newsh, newseg); + senextself(newsh); + } + return 1; + } + + // Triangulate the facet. It may not success (due to rounding error, or + // incorrect input data), use 'caveencshlist' and 'caveencseglist' are + // re-used to store all the newly created subfaces and segments. So we + // can clean them if the triangulation is not successful. + caveencshlist->newindex((void **) &parysh); + *parysh = newsh; + + // Incrementally build the triangulation. + pinfect(pa); + pinfect(pb); + pinfect(pc); + for (i = 0; i < ptlist->objects; i++) { + ppt = (point *) fastlookup(ptlist, i); + if (!pinfected(*ppt)) { + searchsh = recentsh; // Start from 'recentsh'. + iloc = (int) OUTSIDE; + // Insert the vertex. Use Bowyer-Watson algo. Round the location. + iloc = sinsertvertex(*ppt, &searchsh, NULL, iloc, 1, 1); + if (iloc != ((int) ONVERTEX)) { + // Point inserted successfully. + if (pointtype(*ppt) == VOLVERTEX) { + setpointtype(*ppt, FACETVERTEX); + } + // Save the set of new subfaces. + for (j = 0; j < caveshbdlist->objects; j++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, j); + spivot(*parysh, searchsh); // The new subface [a, b, p]. + // Do not save a deleted new face (degenerated). + if (searchsh.sh[3] != NULL) { + caveencshlist->newindex((void **) &parysh); + *parysh = searchsh; + } + } + // Delete all removed subfaces. + for (j = 0; j < caveshlist->objects; j++) { + parysh = (face *) fastlookup(caveshlist, j); + shellfacedealloc(subfaces, parysh->sh); + } + // Clear the global lists. + caveshbdlist->restart(); + caveshlist->restart(); + cavesegshlist->restart(); + } else { + // The facet triangulation is failed. + break; + } + } + } // i + puninfect(pa); + puninfect(pb); + puninfect(pc); + + if (i < ptlist->objects) { + //The facet triangulation is failed. Clean the new subfaces. + // There is no new segment be created yet. + if (!b->quiet) { + printf("Warning: Fail to triangulate facet #%d. Skipped!\n", shmark); + } + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + if (parysh->sh[3] != NULL) { + shellfacedealloc(subfaces, parysh->sh); + } + } + caveencshlist->restart(); + return 0; + } + + // Insert the segments. + for (i = 0; i < conlist->objects; i++) { + cons = (point *) fastlookup(conlist, i); + searchsh = recentsh; + iloc = (int) slocate(cons[0], &searchsh, 1, 1, 0); + if (iloc != (int) ONVERTEX) { + // Not found due to roundoff errors. Do a brute-force search. + bool bflag = false; + subfaces->traversalinit(); + searchsh.sh = shellfacetraverse(subfaces); + while (searchsh.sh != NULL) { + // Only search the subface in the same facet. + if (shellmark(searchsh) == shmark) { + if ((point) searchsh.sh[3] == cons[0]) { + searchsh.shver = 0; bflag = true; //break; + } else if ((point) searchsh.sh[4] == cons[0]) { + searchsh.shver = 2; bflag = true; //break; + } else if ((point) searchsh.sh[5] == cons[0]) { + searchsh.shver = 4; bflag = true; //break; + } + } + if (bflag) { + // [2019-12-03] The subface is not guaranteed to be coplanar, + // only use "shmark" is not enough. + point pa = sorg(searchsh); + point pb = sdest(searchsh); + point pc = sapex(searchsh); + REAL chkori = orient3d(pa, pb, pc, cons[1]); + if (chkori != 0.0) { + REAL len = distance(pa, pb); + len += distance(pb, pc); + len += distance(pc, pa); + len /= 3.0; + REAL len3 = len * len * len; + REAL eps = fabs(chkori) / len3; + if (eps < 1e-5) { + break; // They are almost coplanar. + } + } else { + break; + } + bflag = false; // not this subface. + } + searchsh.sh = shellfacetraverse(subfaces); + } + //if (searchsh.sh == NULL) { + // // Failed to find a subface containing vertex cons[0]. + //} + } + if (searchsh.sh != NULL) { + // Recover the segment. Some edges may be flipped. + if (sscoutsegment(&searchsh, cons[1], 1, 1, 0) != SHAREEDGE) { + break; // Fail to recover a segment. + } + // Save this newseg. + sspivot(searchsh, newseg); + caveencseglist->newindex((void **) &paryseg); + *paryseg = newseg; + if (flipstack != NULL) { + // Recover locally Delaunay edges. + lawsonflip(); + } + } else { + break; // Failed to find a segment. + } + } // i + + if (i < conlist->objects) { + if (!b->quiet) { + printf("Warning: Fail to recover a segment in facet #%d. Skipped!\n", + shmark); + } + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + if (parysh->sh[3] != NULL) { + shellfacedealloc(subfaces, parysh->sh); + } + } + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + if (paryseg->sh[3] != NULL) { + shellfacedealloc(subsegs, paryseg->sh); + } + } + caveencshlist->restart(); + caveencseglist->restart(); + return 0; + } + + // Remove exterior and hole triangles. + scarveholes(holes, holelist); + + caveencshlist->restart(); + caveencseglist->restart(); + return 1; +} + +//============================================================================// +// // +// unifysegments() Remove redundant segments and create face links. // +// // +// After this routine, although segments are unique, but some of them may be // +// removed later by mergefacet(). All vertices still have type FACETVERTEX. // +// // +//============================================================================// + +void tetgenmesh::unifysegments() +{ + badface *facelink = NULL, *newlinkitem, *f1, *f2; + face *facperverlist, sface; + face subsegloop, testseg; + point torg, tdest; + REAL ori1, ori2; //, ori3; + REAL n1[3], n2[3]; + REAL cosang, ang, ang_tol; + int *idx2faclist; + int idx, k, m; + + if (b->verbose > 1) { + printf(" Unifying segments.\n"); + } + // The limit dihedral angle that two facets are not overlapping. + //ang_tol = b->facet_overlap_ang_tol / 180.0 * PI; + //if (ang_tol < 0.0) ang_tol = 0.0; + + // Create a mapping from vertices to subfaces. + makepoint2submap(subfaces, idx2faclist, facperverlist); + + + subsegloop.shver = 0; + subsegs->traversalinit(); + subsegloop.sh = shellfacetraverse(subsegs); + while (subsegloop.sh != (shellface *) NULL) { + torg = sorg(subsegloop); + tdest = sdest(subsegloop); + + idx = pointmark(torg) - in->firstnumber; + // Loop through the set of subfaces containing 'torg'. Get all the + // subfaces containing the edge (torg, tdest). Save and order them + // in 'sfacelist', the ordering is defined by the right-hand rule + // with thumb points from torg to tdest. + for (k = idx2faclist[idx]; k < idx2faclist[idx + 1]; k++) { + sface = facperverlist[k]; + // The face may be deleted if it is a duplicated face. + if (sface.sh[3] == NULL) continue; + // Search the edge torg->tdest. + if (sdest(sface) != tdest) { + senext2self(sface); + sesymself(sface); + } + if (sdest(sface) != tdest) continue; + + // Save the face f in facelink. + if (flippool->items >= 2) { + f1 = facelink; + for (m = 0; m < flippool->items - 1; m++) { + f2 = f1->nextitem; + ori1 = facedihedral(torg, tdest, sapex(f1->ss), sapex(f2->ss)); + ori2 = facedihedral(torg, tdest, sapex(f1->ss), sapex(sface)); + if (ori1 >= ori2) { + break; // insert this face between f1 and f2. + } + // Go to the next item; + f1 = f2; + } // for (m = 0; ...) + //if (sface.sh[3] != NULL) { + // Insert sface between f1 and f2. + newlinkitem = (badface *) flippool->alloc(); + newlinkitem->ss = sface; + newlinkitem->nextitem = f1->nextitem; + f1->nextitem = newlinkitem; + //} + } else if (flippool->items == 1) { + f1 = facelink; + // Add this face to link if it is not deleted. + //if (sface.sh[3] != NULL) { + // Add this face into link. + newlinkitem = (badface *) flippool->alloc(); + newlinkitem->ss = sface; + newlinkitem->nextitem = NULL; + f1->nextitem = newlinkitem; + //} + } else { + // The first face. + newlinkitem = (badface *) flippool->alloc(); + newlinkitem->ss = sface; + newlinkitem->nextitem = NULL; + facelink = newlinkitem; + } + } // for (k = idx2faclist[idx]; ...) + + + // Set the connection between this segment and faces containing it, + // at the same time, remove redundant segments. + f1 = facelink; + for (k = 0; k < flippool->items; k++) { + sspivot(f1->ss, testseg); + // If 'testseg' is not 'subsegloop' and is not dead, it is redundant. + if ((testseg.sh != subsegloop.sh) && (testseg.sh[3] != NULL)) { + shellfacedealloc(subsegs, testseg.sh); + } + // Bonds the subface and the segment together. + ssbond(f1->ss, subsegloop); + f1 = f1->nextitem; + } + + // Create the face ring at the segment. + if (flippool->items > 1) { + f1 = facelink; + for (k = 1; k <= flippool->items; k++) { + k < flippool->items ? f2 = f1->nextitem : f2 = facelink; + // Calculate the dihedral angle between the two facet. + facenormal(torg, tdest, sapex(f1->ss), n1, 1, NULL); + facenormal(torg, tdest, sapex(f2->ss), n2, 1, NULL); + cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); + // Rounding. + if (cosang > 1.0) cosang = 1.0; + else if (cosang < -1.0) cosang = -1.0; + ang = acos(cosang); + //if (ang < ang_tol) { + // // Two facets are treated as overlapping each other. + // report_overlapping_facets(&(f1->ss), &(f2->ss), ang); + //} else { + // Record the smallest input dihedral angle. + if (ang < minfacetdihed) { + minfacetdihed = ang; + } + sbond1(f1->ss, f2->ss); + //} + f1 = f2; + } + } + + flippool->restart(); + + // Are there length constraints? + if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + int e1, e2; + REAL len; + for (k = 0; k < in->numberofsegmentconstraints; k++) { + e1 = (int) in->segmentconstraintlist[k * 3]; + e2 = (int) in->segmentconstraintlist[k * 3 + 1]; + if (((pointmark(torg) == e1) && (pointmark(tdest) == e2)) || + ((pointmark(torg) == e2) && (pointmark(tdest) == e1))) { + len = in->segmentconstraintlist[k * 3 + 2]; + setareabound(subsegloop, len); + break; + } + } + } + + subsegloop.sh = shellfacetraverse(subsegs); + } + + delete [] idx2faclist; + delete [] facperverlist; +} + +//============================================================================// +// // +// identifyinputedges() Identify input edges. // +// // +// A set of input edges is provided in the 'in->edgelist'. We find these // +// edges in the surface mesh and make them segments of the mesh. // +// // +// It is possible that an input edge is not in any facet, i.e.,it is a float- // +// segment inside the volume. // +// // +//============================================================================// + +void tetgenmesh::identifyinputedges(point *idx2verlist) +{ + face* shperverlist; + int* idx2shlist; + face searchsh, neighsh; + face segloop, checkseg, newseg; + point checkpt, pa = NULL, pb = NULL; + int *endpts; + int edgemarker; + int idx, i, j; + + int e1, e2; + REAL len; + + if (!b->quiet) { + printf("Inserting edges ...\n"); + } + + // Construct a map from points to subfaces. + makepoint2submap(subfaces, idx2shlist, shperverlist); + + // Process the set of input edges. + for (i = 0; i < in->numberofedges; i++) { + endpts = &(in->edgelist[(i << 1)]); + if (endpts[0] == endpts[1]) { + if (!b->quiet) { + printf("Warning: Edge #%d is degenerated. Skipped.\n", i); + } + continue; // Skip a degenerated edge. + } else if (dupverts > 0l) { + // Replace duplicated vertices. + for (j = 0; j < 2; j++) { + checkpt = idx2verlist[endpts[j]]; + if (pointtype(checkpt) == DUPLICATEDVERTEX) { + point meshpt = point2ppt(checkpt); + endpts[j] = pointmark(meshpt); + } + } + } + // Recall that all existing segments have a default marker '-1'. + // We assign all identified segments a default marker '-2'. + edgemarker = in->edgemarkerlist ? in->edgemarkerlist[i] : -2; + + // Find a face contains the edge. + newseg.sh = NULL; + searchsh.sh = NULL; + idx = endpts[0] - in->firstnumber; + for (j = idx2shlist[idx]; j < idx2shlist[idx + 1]; j++) { + checkpt = sdest(shperverlist[j]); + if (pointmark(checkpt) == endpts[1]) { + searchsh = shperverlist[j]; + break; // Found. + } else { + checkpt = sapex(shperverlist[j]); + if (pointmark(checkpt) == endpts[1]) { + senext2(shperverlist[j], searchsh); + sesymself(searchsh); + break; + } + } + } // j + + if (searchsh.sh != NULL) { + // Check if this edge is already a segment of the mesh. + sspivot(searchsh, checkseg); + if (checkseg.sh != NULL) { + // This segment already exist. + newseg = checkseg; + } else { + // Create a new segment at this edge. + pa = sorg(searchsh); + pb = sdest(searchsh); + makeshellface(subsegs, &newseg); + setshvertices(newseg, pa, pb, NULL); + ssbond(searchsh, newseg); + spivot(searchsh, neighsh); + if (neighsh.sh != NULL) { + ssbond(neighsh, newseg); + } + } + } else { + // It is a dangling segment (not belong to any facets). + // Get the two endpoints of this segment. + pa = idx2verlist[endpts[0]]; + pb = idx2verlist[endpts[1]]; + if (pa == pb) { + if (!b->quiet) { + printf("Warning: Edge #%d is degenerated. Skipped.\n", i); + } + continue; + } + // Check if segment [a,b] already exists. + // TODO: Change the brute-force search. Slow! + point *ppt; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + ppt = (point *) &(segloop.sh[3]); + if (((ppt[0] == pa) && (ppt[1] == pb)) || + ((ppt[0] == pb) && (ppt[1] == pa))) { + // Found! + newseg = segloop; + break; + } + segloop.sh = shellfacetraverse(subsegs); + } + if (newseg.sh == NULL) { + makeshellface(subsegs, &newseg); + setshvertices(newseg, pa, pb, NULL); + } + } + + setshellmark(newseg, edgemarker); + + if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + for (i = 0; i < in->numberofsegmentconstraints; i++) { + e1 = (int) in->segmentconstraintlist[i * 3]; + e2 = (int) in->segmentconstraintlist[i * 3 + 1]; + if (((pointmark(pa) == e1) && (pointmark(pb) == e2)) || + ((pointmark(pa) == e2) && (pointmark(pb) == e1))) { + len = in->segmentconstraintlist[i * 3 + 2]; + setareabound(newseg, len); + break; + } + } + } + } // i + + delete [] shperverlist; + delete [] idx2shlist; +} + +//============================================================================// +// // +// mergefacets() Merge adjacent facets. // +// // +//============================================================================// + +void tetgenmesh::mergefacets() +{ + face parentsh, neighsh, neineish; + face segloop; + point pa, pb, pc, pd; + REAL n1[3], n2[3]; + REAL cosang, cosang_tol; + + // Allocate an array to save calcaulated dihedral angles at segments. + arraypool *dihedangarray = new arraypool(sizeof(double), 10); + REAL *paryang = NULL; + + // First, remove coplanar segments. + // The dihedral angle bound for two different facets. + cosang_tol = cos(b->facet_separate_ang_tol / 180.0 * PI); + + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != (shellface *) NULL) { + // Only remove a segment if it has a marker '-1'. + if (shellmark(segloop) != -1) { + segloop.sh = shellfacetraverse(subsegs); + continue; + } + spivot(segloop, parentsh); + if (parentsh.sh != NULL) { + spivot(parentsh, neighsh); + if (neighsh.sh != NULL) { + spivot(neighsh, neineish); + if (neineish.sh == parentsh.sh) { + // Exactly two subfaces at this segment. + // Only merge them if they have the same boundary marker. + if (shellmark(parentsh) == shellmark(neighsh)) { + pa = sorg(segloop); + pb = sdest(segloop); + pc = sapex(parentsh); + pd = sapex(neighsh); + // Calculate the dihedral angle at the segment [a,b]. + facenormal(pa, pb, pc, n1, 1, NULL); + facenormal(pa, pb, pd, n2, 1, NULL); + cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); + if (cosang < cosang_tol) { + ssdissolve(parentsh); + ssdissolve(neighsh); + shellfacedealloc(subsegs, segloop.sh); + // Add the edge to flip stack. + flipshpush(&parentsh); + } else { + // Save 'cosang' to avoid re-calculate it. + // Re-use the pointer at the first segment. + dihedangarray->newindex((void **) &paryang); + *paryang = cosang; + segloop.sh[6] = (shellface) paryang; + } + } + } // if (neineish.sh == parentsh.sh) + } + } + segloop.sh = shellfacetraverse(subsegs); + } + + // Second, remove ridge segments at small angles. + // The dihedral angle bound for two different facets. + cosang_tol = cos(b->facet_small_ang_tol / 180.0 * PI); + REAL cosang_sep_tol = cos((b->facet_separate_ang_tol - 5.0) / 180.0 * PI); + face shloop; + face seg1, seg2; + REAL cosang1, cosang2; + int i, j; + + subfaces->traversalinit(); + shloop.sh = shellfacetraverse(subfaces); + while (shloop.sh != (shellface *) NULL) { + for (i = 0; i < 3; i++) { + if (isshsubseg(shloop)) { + senext(shloop, neighsh); + if (isshsubseg(neighsh)) { + // Found two segments sharing at one vertex. + // Check if they form a small angle. + pa = sorg(shloop); + pb = sdest(shloop); + pc = sapex(shloop); + for (j = 0; j < 3; j++) n1[j] = pa[j] - pb[j]; + for (j = 0; j < 3; j++) n2[j] = pc[j] - pb[j]; + cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); + if (cosang > cosang_tol) { + // Found a small angle. + segloop.sh = NULL; + sspivot(shloop, seg1); + sspivot(neighsh, seg2); + if (seg1.sh[6] != NULL) { + paryang = (REAL *) (seg1.sh[6]); + cosang1 = *paryang; + } else { + cosang1 = 1.0; // 0 degree; + } + if (seg2.sh[6] != NULL) { + paryang = (REAL *) (seg2.sh[6]); + cosang2 = *paryang; + } else { + cosang2 = 1.0; // 0 degree; + } + if (cosang1 < cosang_sep_tol) { + if (cosang2 < cosang_sep_tol) { + if (cosang1 < cosang2) { + segloop = seg1; + } else { + segloop = seg2; + } + } else { + segloop = seg1; + } + } else { + if (cosang2 < cosang_sep_tol) { + segloop = seg2; + } + } + if (segloop.sh != NULL) { + // Remove this segment. + segloop.shver = 0; + spivot(segloop, parentsh); + spivot(parentsh, neighsh); + ssdissolve(parentsh); + ssdissolve(neighsh); + shellfacedealloc(subsegs, segloop.sh); + // Add the edge to flip stack. + flipshpush(&parentsh); + break; + } + } + } // if (isshsubseg) + } // if (isshsubseg) + senextself(shloop); + } + shloop.sh = shellfacetraverse(subfaces); + } + + delete dihedangarray; + + + if (flipstack != NULL) { + lawsonflip(); // Recover Delaunayness. + } +} + +//============================================================================// +// // +// meshsurface() Create a surface mesh of the input PLC. // +// // +//============================================================================// + +void tetgenmesh::meshsurface() +{ + arraypool *ptlist, *conlist; + point *idx2verlist; + point tstart, tend, *pnewpt, *cons; + tetgenio::facet *f; + tetgenio::polygon *p; + int end1, end2; + int shmark, i, j; + + if (!b->quiet) { + printf("Creating surface mesh ...\n"); + } + + // Create a map from indices to points. + makeindex2pointmap(idx2verlist); + + // Initialize arrays (block size: 2^8 = 256). + ptlist = new arraypool(sizeof(point *), 8); + conlist = new arraypool(2 * sizeof(point *), 8); + + // Loop the facet list, triangulate each facet. + for (shmark = 1; shmark <= in->numberoffacets; shmark++) { + + // Get a facet F. + f = &in->facetlist[shmark - 1]; + + // Process the duplicated points first, they are marked with type + // DUPLICATEDVERTEX. If p and q are duplicated, and p'index > q's, + // then p is substituted by q. + if (dupverts > 0l) { + // Loop all polygons of this facet. + for (i = 0; i < f->numberofpolygons; i++) { + p = &(f->polygonlist[i]); + // Loop other vertices of this polygon. + for (j = 0; j < p->numberofvertices; j++) { + end1 = p->vertexlist[j]; + tstart = idx2verlist[end1]; + if (pointtype(tstart) == DUPLICATEDVERTEX) { + // Reset the index of vertex-j. + tend = point2ppt(tstart); + end2 = pointmark(tend); + p->vertexlist[j] = end2; + } + } + } + } + + // Loop polygons of F, get the set of vertices and segments. + for (i = 0; i < f->numberofpolygons; i++) { + // Get a polygon. + p = &(f->polygonlist[i]); + // Get the first vertex. + end1 = p->vertexlist[0]; + if ((end1 < in->firstnumber) || + (end1 >= in->firstnumber + in->numberofpoints)) { + if (!b->quiet) { + printf("Warning: Invalid the 1st vertex %d of polygon", end1); + printf(" %d in facet %d.\n", i + 1, shmark); + } + continue; // Skip this polygon. + } + tstart = idx2verlist[end1]; + // Add tstart to V if it haven't been added yet. + if (!pinfected(tstart)) { + pinfect(tstart); + ptlist->newindex((void **) &pnewpt); + *pnewpt = tstart; + } + // Loop other vertices of this polygon. + for (j = 1; j <= p->numberofvertices; j++) { + // get a vertex. + if (j < p->numberofvertices) { + end2 = p->vertexlist[j]; + } else { + end2 = p->vertexlist[0]; // Form a loop from last to first. + } + if ((end2 < in->firstnumber) || + (end2 >= in->firstnumber + in->numberofpoints)) { + if (!b->quiet) { + printf("Warning: Invalid vertex %d in polygon %d", end2, i + 1); + printf(" in facet %d.\n", shmark); + } + } else { + if (end1 != end2) { + // 'end1' and 'end2' form a segment. + tend = idx2verlist[end2]; + // Add tstart to V if it haven't been added yet. + if (!pinfected(tend)) { + pinfect(tend); + ptlist->newindex((void **) &pnewpt); + *pnewpt = tend; + } + // Save the segment in S (conlist). + conlist->newindex((void **) &cons); + cons[0] = tstart; + cons[1] = tend; + // Set the start for next continuous segment. + end1 = end2; + tstart = tend; + } else { + // Two identical vertices mean an isolated vertex of F. + if (p->numberofvertices > 2) { + // This may be an error in the input, anyway, we can continue + // by simply skipping this segment. + if (!b->quiet) { + printf("Warning: Polygon %d has two identical verts", i + 1); + printf(" in facet %d.\n", shmark); + } + } + // Ignore this vertex. + } + } + // Is the polygon degenerate (a segment or a vertex)? + if (p->numberofvertices == 2) break; + } + } + // Unmark vertices. + for (i = 0; i < ptlist->objects; i++) { + pnewpt = (point *) fastlookup(ptlist, i); + puninfect(*pnewpt); + } + + // Triangulate F into a CDT. + // If in->facetmarklist is NULL, use the default marker -1. + triangulate(in->facetmarkerlist ? in->facetmarkerlist[shmark - 1] : -1, + ptlist, conlist, f->numberofholes, f->holelist); + + // Clear working lists. + ptlist->restart(); + conlist->restart(); + } + + + // Remove redundant segments and build the face links. + unifysegments(); + if (in->numberofedges > 0) { + // There are input segments. Insert them. + identifyinputedges(idx2verlist); + } + if (!b->diagnose && !b->nomergefacet && !b->nobisect) { // No -d -M -Y + // Merge coplanar facets. + mergefacets(); + } + + // Mark all segment vertices to be RIDGEVERTEX. + face segloop; + point *ppt; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + ppt = (point *) &(segloop.sh[3]); + for (i = 0; i < 2; i++) { + setpointtype(ppt[i], RIDGEVERTEX); + } + segloop.sh = shellfacetraverse(subsegs); + } + + if (b->object == tetgenbehavior::STL) { + // Remove redundant vertices (for .stl input mesh). + jettisonnodes(); + // Update the number of input vertices. + in->numberofpoints = points->items; + } + + if (b->verbose) { + printf(" %ld (%ld) subfaces (segments).\n", subfaces->items, + subsegs->items); + } + + // The total number of iunput segments. + insegments = subsegs->items; + + delete [] idx2verlist; + delete ptlist; + delete conlist; +} + +// // +// // +//== surface_cxx =============================================================// + +//== constrained_cxx =========================================================// +// // +// // + +//============================================================================// +// // +// finddirection() Find the tet on the path from one point to another. // +// // +// The path starts from 'searchtet''s origin and ends at 'endpt'. On finish, // +// 'searchtet' contains a tet on the path, its origin does not change. // +// // +// The return value indicates one of the following cases (let 'searchtet' be // +// abcd, a is the origin of the path): // +// - ACROSSVERT, edge ab is collinear with the path; // +// - ACROSSEDGE, edge bc intersects with the path; // +// - ACROSSFACE, face bcd intersects with the path. // +// // +// WARNING: This routine is designed for convex triangulations, and will not // +// generally work after the holes and concavities have been carved. // +// // +//============================================================================// + +enum tetgenmesh::interresult + tetgenmesh::finddirection(triface* searchtet, point endpt) +{ + triface neightet; + point pa, pb, pc, pd; + enum {HMOVE, RMOVE, LMOVE} nextmove; + REAL hori, rori, lori; + int t1ver; + int s; + + // The origin is fixed. + pa = org(*searchtet); + if ((point) searchtet->tet[7] == dummypoint) { + // A hull tet. Choose the neighbor of its base face. + decode(searchtet->tet[3], *searchtet); + // Reset the origin to be pa. + if ((point) searchtet->tet[4] == pa) { + searchtet->ver = 11; + } else if ((point) searchtet->tet[5] == pa) { + searchtet->ver = 3; + } else if ((point) searchtet->tet[6] == pa) { + searchtet->ver = 7; + } else { + searchtet->ver = 0; + } + } + + pb = dest(*searchtet); + // Check whether the destination or apex is 'endpt'. + if (pb == endpt) { + // pa->pb is the search edge. + return ACROSSVERT; + } + + pc = apex(*searchtet); + if (pc == endpt) { + // pa->pc is the search edge. + eprevesymself(*searchtet); + return ACROSSVERT; + } + + // Walk through tets around pa until the right one is found. + while (1) { + + pd = oppo(*searchtet); + // Check whether the opposite vertex is 'endpt'. + if (pd == endpt) { + // pa->pd is the search edge. + esymself(*searchtet); + enextself(*searchtet); + return ACROSSVERT; + } + // Check if we have entered outside of the domain. + if (pd == dummypoint) { + // This is possible when the mesh is non-convex. + if (nonconvex) { + return ACROSSFACE; // return ACROSSSUB; // Hit a bounday. + } else { + terminatetetgen(this, 2); + } + } + + // Now assume that the base face abc coincides with the horizon plane, + // and d lies above the horizon. The search point 'endpt' may lie + // above or below the horizon. We test the orientations of 'endpt' + // with respect to three planes: abc (horizon), bad (right plane), + // and acd (left plane). + hori = orient3d(pa, pb, pc, endpt); + rori = orient3d(pb, pa, pd, endpt); + lori = orient3d(pa, pc, pd, endpt); + + // Now decide the tet to move. It is possible there are more than one + // tets are viable moves. Is so, randomly choose one. + if (hori > 0) { + if (rori > 0) { + if (lori > 0) { + // Any of the three neighbors is a viable move. + s = randomnation(3); + if (s == 0) { + nextmove = HMOVE; + } else if (s == 1) { + nextmove = RMOVE; + } else { + nextmove = LMOVE; + } + } else { + // Two tets, below horizon and below right, are viable. + if (randomnation(2)) { + nextmove = HMOVE; + } else { + nextmove = RMOVE; + } + } + } else { + if (lori > 0) { + // Two tets, below horizon and below left, are viable. + if (randomnation(2)) { + nextmove = HMOVE; + } else { + nextmove = LMOVE; + } + } else { + // The tet below horizon is chosen. + nextmove = HMOVE; + } + } + } else { + if (rori > 0) { + if (lori > 0) { + // Two tets, below right and below left, are viable. + if (randomnation(2)) { + nextmove = RMOVE; + } else { + nextmove = LMOVE; + } + } else { + // The tet below right is chosen. + nextmove = RMOVE; + } + } else { + if (lori > 0) { + // The tet below left is chosen. + nextmove = LMOVE; + } else { + // 'endpt' lies either on the plane(s) or across face bcd. + if (hori == 0) { + if (rori == 0) { + // pa->'endpt' is COLLINEAR with pa->pb. + return ACROSSVERT; + } + if (lori == 0) { + // pa->'endpt' is COLLINEAR with pa->pc. + eprevesymself(*searchtet); // [a,c,d] + return ACROSSVERT; + } + // pa->'endpt' crosses the edge pb->pc. + return ACROSSEDGE; + } + if (rori == 0) { + if (lori == 0) { + // pa->'endpt' is COLLINEAR with pa->pd. + esymself(*searchtet); // face bad. + enextself(*searchtet); // face [a,d,b] + return ACROSSVERT; + } + // pa->'endpt' crosses the edge pb->pd. + esymself(*searchtet); // face bad. + enextself(*searchtet); // face adb + return ACROSSEDGE; + } + if (lori == 0) { + // pa->'endpt' crosses the edge pc->pd. + eprevesymself(*searchtet); // [a,c,d] + return ACROSSEDGE; + } + // pa->'endpt' crosses the face bcd. + return ACROSSFACE; + } + } + } + + // Move to the next tet, fix pa as its origin. + if (nextmove == RMOVE) { + fnextself(*searchtet); + } else if (nextmove == LMOVE) { + eprevself(*searchtet); + fnextself(*searchtet); + enextself(*searchtet); + } else { // HMOVE + fsymself(*searchtet); + enextself(*searchtet); + } + if (org(*searchtet) != pa) { + terminatetetgen(this, 2); + } + pb = dest(*searchtet); + pc = apex(*searchtet); + + } // while (1) + +} + +//============================================================================// +// // +// scoutsegment() Search an edge in the tetrahedralization. // +// // +// If the edge is found, it returns SHAREEDGE, and 'searchtet' returns the // +// edge from startpt to endpt. // +// // +// If the edge is missing, it returns either ACROSSEDGE or ACROSSFACE, which // +// indicates that the edge intersects an edge or a face. If 'refpt' is NULL, // +// 'searchtet' returns the edge or face. If 'refpt' is not NULL, it returns // +// a vertex which encroaches upon this edge, and 'searchtet' returns a tet // +// which containing 'refpt'. // +// // +// The parameter 'sedge' is used to report self-intersection. It is the // +// whose endpoints are 'startpt' and 'endpt'. It must not be a NULL. // +// // +//============================================================================// + +enum tetgenmesh::interresult tetgenmesh::scoutsegment(point startpt,point endpt, + face *sedge, triface* searchtet, point* refpt, arraypool* intfacelist) +{ + point pd; + enum interresult dir; + int t1ver; + + if (b->verbose > 2) { + printf(" Scout seg (%d, %d).\n",pointmark(startpt),pointmark(endpt)); + } + + point2tetorg(startpt, *searchtet); + dir = finddirection(searchtet, endpt); + + if (dir == ACROSSVERT) { + pd = dest(*searchtet); + if (pd == endpt) { + if (issubseg(*searchtet)) { + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + } + return SHAREEDGE; + } else { + // A point is on the path. + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + return ACROSSVERT; + } + } + + // dir is either ACROSSEDGE or ACROSSFACE. + enextesymself(*searchtet); // Go to the opposite face. + fsymself(*searchtet); // Enter the adjacent tet. + + if (dir == ACROSSEDGE) { + // Check whether two segments are intersecting. + if (issubseg(*searchtet)) { + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + } + } else if (dir == ACROSSFACE) { + if (checksubfaceflag) { + // Check whether a segment and a subface are intersecting. + if (issubface(*searchtet)) { + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + } + } + } else { + terminatetetgen(this, 2); + } + + if (refpt == NULL) { + // Do not need a reference point. Return. + return dir; + } + + triface neightet, reftet; + point pa, pb, pc; + REAL angmax, ang; + int types[2], poss[4]; + int pos = 0, i, j; + + pa = org(*searchtet); + angmax = interiorangle(pa, startpt, endpt, NULL); + *refpt = pa; + pb = dest(*searchtet); + ang = interiorangle(pb, startpt, endpt, NULL); + if (ang > angmax) { + angmax = ang; + *refpt = pb; + } + pc = apex(*searchtet); + ang = interiorangle(pc, startpt, endpt, NULL); + if (ang > angmax) { + angmax = ang; + *refpt = pc; + } + reftet = *searchtet; // Save the tet containing the refpt. + + // Search intersecting faces along the segment. + while (1) { + + + pd = oppo(*searchtet); + + + // Stop if we meet 'endpt'. + if (pd == endpt) break; + + ang = interiorangle(pd, startpt, endpt, NULL); + if (ang > angmax) { + angmax = ang; + *refpt = pd; + reftet = *searchtet; + } + + // Find a face intersecting the segment. + if (dir == ACROSSFACE) { + // One of the three oppo faces in 'searchtet' intersects the segment. + neightet = *searchtet; + j = (neightet.ver & 3); // j is the current face number. + for (i = j + 1; i < j + 4; i++) { + neightet.ver = (i % 4); + pa = org(neightet); + pb = dest(neightet); + pc = apex(neightet); + pd = oppo(neightet); // The above point. + if (tri_edge_test(pa, pb, pc, startpt, endpt, pd, 1, types, poss)) { + dir = (enum interresult) types[0]; + pos = poss[0]; + break; + } else { + dir = DISJOINT; + pos = 0; + } + } + } else if (dir == ACROSSEDGE) { + // Check the two opposite faces (of the edge) in 'searchtet'. + for (i = 0; i < 2; i++) { + if (i == 0) { + enextesym(*searchtet, neightet); + } else { + eprevesym(*searchtet, neightet); + } + pa = org(neightet); + pb = dest(neightet); + pc = apex(neightet); + pd = oppo(neightet); // The above point. + if (tri_edge_test(pa, pb, pc, startpt, endpt, pd, 1, types, poss)) { + dir = (enum interresult) types[0]; + pos = poss[0]; + break; + } else { + dir = DISJOINT; + pos = 0; + } + } + if (dir == DISJOINT) { + // No intersection. Rotate to the next tet at the edge. + dir = ACROSSEDGE; + fnextself(*searchtet); + continue; + } + } + + if (dir == ACROSSVERT) { + // This segment passing a vertex. Choose it and return. + for (i = 0; i < pos; i++) { + enextself(neightet); + } + eprev(neightet, *searchtet); + // dest(*searchtet) lies on the segment. + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + return ACROSSVERT; + } else if (dir == ACROSSEDGE) { + // Get the edge intersects with the segment. + for (i = 0; i < pos; i++) { + enextself(neightet); + } + } + // Go to the next tet. + fsym(neightet, *searchtet); + + if (dir == ACROSSEDGE) { + // Check whether two segments are intersecting. + if (issubseg(*searchtet)) { + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + } + } else if (dir == ACROSSFACE) { + if (checksubfaceflag) { + // Check whether a segment and a subface are intersecting. + if (issubface(*searchtet)) { + //report_selfint_edge(startpt, endpt, sedge, searchtet, dir); + terminatetetgen(this, 3); + } + } + } else { + terminatetetgen(this, 2); + } + + } // while (1) + + // A valid reference point should inside the diametrial circumsphere of + // the missing segment, i.e., it encroaches upon it. + if (2.0 * angmax < PI) { + *refpt = NULL; + } + + + *searchtet = reftet; + return dir; +} + +//============================================================================// +// // +// getsteinerpointonsegment() Get a Steiner point on a segment. // +// // +// Return '1' if 'refpt' lies on an adjacent segment of this segment. Other- // +// wise, return '0'. // +// // +//============================================================================// + +int tetgenmesh::getsteinerptonsegment(face* seg, point refpt, point steinpt) +{ + point ei = sorg(*seg); + point ej = sdest(*seg); + int adjflag = 0, i; + + if (refpt != NULL) { + REAL L, L1, t; + + if (pointtype(refpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(refpt), parentseg); + int sidx1 = getfacetindex(parentseg); + point far_pi = segmentendpointslist[sidx1 * 2]; + point far_pj = segmentendpointslist[sidx1 * 2 + 1]; + int sidx2 = getfacetindex(*seg); + point far_ei = segmentendpointslist[sidx2 * 2]; + point far_ej = segmentendpointslist[sidx2 * 2 + 1]; + if ((far_pi == far_ei) || (far_pj == far_ei)) { + // Create a Steiner point at the intersection of the segment + // [far_ei, far_ej] and the sphere centered at far_ei with + // radius |far_ei - refpt|. + L = distance(far_ei, far_ej); + L1 = distance(far_ei, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); + } + adjflag = 1; + } else if ((far_pi == far_ej) || (far_pj == far_ej)) { + L = distance(far_ei, far_ej); + L1 = distance(far_ej, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); + } + adjflag = 1; + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + } + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + } + + // Make sure that steinpt is not too close to ei and ej. + L = distance(ei, ej); + L1 = distance(steinpt, ei); + t = L1 / L; + if ((t < 0.2) || (t > 0.8)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + } else { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + + + return adjflag; +} + + + +//============================================================================// +// // +// delaunizesegments() Recover segments in a DT. // +// // +// All segments need to be recovered are in 'subsegstack' (Q). They will be // +// be recovered one by one (in a random order). // +// // +// Given a segment s in the Q, this routine first queries s in the DT, if s // +// matches an edge in DT, it is 'locked' at the edge. Otherwise, s is split // +// by inserting a new point p in both the DT and itself. The two new subseg- // +// ments of s are queued in Q. The process continues until Q is empty. // +// // +//============================================================================// + +void tetgenmesh::delaunizesegments() +{ + triface searchtet, spintet; + face searchsh; + face sseg, *psseg; + point refpt, newpt; + enum interresult dir; + insertvertexflags ivf; + int t1ver; + + + ivf.bowywat = 1; // Use Bowyer-Watson insertion. + ivf.sloc = (int) ONEDGE; // on 'sseg'. + ivf.sbowywat = 1; // Use Bowyer-Watson insertion. + ivf.assignmeshsize = b->metric; + ivf.smlenflag = useinsertradius; // Return the closet mesh vertex. + + // Loop until 'subsegstack' is empty. + while (subsegstack->objects > 0l) { + // seglist is used as a stack. + subsegstack->objects--; + psseg = (face *) fastlookup(subsegstack, subsegstack->objects); + sseg = *psseg; + + // Check if this segment has been recovered. + sstpivot1(sseg, searchtet); + if (searchtet.tet != NULL) { + continue; // Not a missing segment. + } + + // Search the segment. + dir = scoutsegment(sorg(sseg), sdest(sseg), &sseg,&searchtet,&refpt,NULL); + + if (dir == SHAREEDGE) { + // Found this segment, insert it. + // Let the segment remember an adjacent tet. + sstbond1(sseg, searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, sseg); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + } else { + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + // The segment is missing. Split it. + // Create a new point. + makepoint(&newpt, FREESEGVERTEX); + //setpointtype(newpt, FREESEGVERTEX); + getsteinerptonsegment(&sseg, refpt, newpt); + + // Start searching from 'searchtet'. + ivf.iloc = (int) OUTSIDE; + // Insert the new point into the tetrahedralization T. + // Missing segments and subfaces are queued for recovery. + // Note that T is convex (nonconvex = 0). + if (insertpoint(newpt, &searchtet, &searchsh, &sseg, &ivf)) { + // The new point has been inserted. + st_segref_count++; + if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + //save_segmentpoint_insradius(newpt, ivf.parentpt, ivf.smlen); + } + } else { + if (ivf.iloc == (int) NEARVERTEX) { + // The new point (in the segment) is very close to an existing + // vertex -- a small feature is detected. + point nearpt = org(searchtet); + if (pointtype(nearpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(nearpt), parentseg); + point p1 = farsorg(sseg); + point p2 = farsdest(sseg); + point p3 = farsorg(parentseg); + point p4 = farsdest(parentseg); + printf("Two segments are very close to each other.\n"); + printf(" Segment 1: [%d, %d] #%d\n", pointmark(p1), + pointmark(p2), shellmark(sseg)); + printf(" Segment 2: [%d, %d] #%d\n", pointmark(p3), + pointmark(p4), shellmark(parentseg)); + terminatetetgen(this, 4); + } else { + terminatetetgen(this, 2); + } + } else if (ivf.iloc == (int) ONVERTEX) { + // The new point (in the segment) is coincident with an existing + // vertex -- a self-intersection is detected. + eprevself(searchtet); + //report_selfint_edge(sorg(sseg), sdest(sseg), &sseg, &searchtet, + // ACROSSVERT); + terminatetetgen(this, 3); + } else { + // An unknown case. Report a bug. + terminatetetgen(this, 2); + } + } + } else { + // An unknown case. Report a bug. + terminatetetgen(this, 2); + } + } + } // while +} + +//============================================================================// +// // +// scoutsubface() Search subface in the tetrahedralization. // +// // +// 'searchsh' is searched in T. If it exists, it is 'locked' at the face in // +// T. 'searchtet' refers to the face. Otherwise, it is missing. // +// // +// The parameter 'shflag' indicates whether 'searchsh' is a boundary face or // +// not. It is possible that 'searchsh' is a temporarily subface that is used // +// as a cavity boundary face. // +// // +//============================================================================// + +int tetgenmesh::scoutsubface(face* searchsh, triface* searchtet, int shflag) +{ + point pa = sorg(*searchsh); + point pb = sdest(*searchsh); + + // Get a tet whose origin is a. + point2tetorg(pa, *searchtet); + // Search the edge [a,b]. + enum interresult dir = finddirection(searchtet, pb); + if (dir == ACROSSVERT) { + // Check validity of a PLC. + if (dest(*searchtet) != pb) { + if (shflag) { + // A vertex lies on the search edge. + //report_selfint_edge(pa, pb, searchsh, searchtet, dir); + terminatetetgen(this, 3); + } else { + terminatetetgen(this, 2); + } + } + int t1ver; + // The edge exists. Check if the face exists. + point pc = sapex(*searchsh); + // Searchtet holds edge [a,b]. Search a face with apex c. + triface spintet = *searchtet; + while (1) { + if (apex(spintet) == pc) { + // Found a face matching to 'searchsh'! + if (!issubface(spintet)) { + // Insert 'searchsh'. + tsbond(spintet, *searchsh); + fsymself(spintet); + sesymself(*searchsh); + tsbond(spintet, *searchsh); + *searchtet = spintet; + return 1; + } else { + terminatetetgen(this, 2); + } + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } + } + + return 0; +} + +//============================================================================// +// // +// formregion() Form the missing region of a missing subface. // +// // +// 'missh' is a missing subface. From it we form a missing region R which is // +// a connected region formed by a set of missing subfaces of a facet. // +// Comment: There should be no segment inside R. // +// // +// 'missingshs' returns the list of subfaces in R. All subfaces in this list // +// are oriented as the 'missh'. 'missingshbds' returns the list of boundary // +// edges (tetrahedral handles) of R. 'missingshverts' returns all vertices // +// of R. They are all pmarktested. // +// // +// Except the first one (which is 'missh') in 'missingshs', each subface in // +// this list represents an internal edge of R, i.e., it is missing in the // +// tetrahedralization. Since R may contain interior vertices, not all miss- // +// ing edges can be found by this way. // +//============================================================================// + +void tetgenmesh::formregion(face* missh, arraypool* missingshs, + arraypool* missingshbds, arraypool* missingshverts) +{ + triface searchtet, spintet; + face neighsh, *parysh; + face neighseg, fakeseg; + point pa, pb, *parypt; + enum interresult dir; + int t1ver; + int i, j; + + smarktest(*missh); + missingshs->newindex((void **) &parysh); + *parysh = *missh; + + // Incrementally find other missing subfaces. + for (i = 0; i < missingshs->objects; i++) { + missh = (face *) fastlookup(missingshs, i); + for (j = 0; j < 3; j++) { + pa = sorg(*missh); + pb = sdest(*missh); + point2tetorg(pa, searchtet); + dir = finddirection(&searchtet, pb); + if (dir != ACROSSVERT) { + // This edge is missing. Its neighbor is a missing subface. + spivot(*missh, neighsh); + if (!smarktested(neighsh)) { + // Adjust the face orientation. + if (sorg(neighsh) != pb) sesymself(neighsh); + smarktest(neighsh); + missingshs->newindex((void **) &parysh); + *parysh = neighsh; + } + } else { + if (dest(searchtet) != pb) { + // Report a PLC problem. + //report_selfint_edge(pa, pb, missh, &searchtet, dir); + terminatetetgen(this, 3); + } + } + // Collect the vertices of R. + if (!pmarktested(pa)) { + pmarktest(pa); + missingshverts->newindex((void **) &parypt); + *parypt = pa; + } + senextself(*missh); + } // j + } // i + + // Get the boundary edges of R. + for (i = 0; i < missingshs->objects; i++) { + missh = (face *) fastlookup(missingshs, i); + for (j = 0; j < 3; j++) { + spivot(*missh, neighsh); + if ((neighsh.sh == NULL) || !smarktested(neighsh)) { + // A boundary edge of R. + // Let the segment point to the adjacent tet. + point2tetorg(sorg(*missh), searchtet); + finddirection(&searchtet, sdest(*missh)); + missingshbds->newindex((void **) &parysh); + *parysh = *missh; + // Check if this edge is a segment. + sspivot(*missh, neighseg); + if (neighseg.sh == NULL) { + // Temporarily create a segment at this edge. + makeshellface(subsegs, &fakeseg); + setsorg(fakeseg, sorg(*missh)); + setsdest(fakeseg, sdest(*missh)); + sinfect(fakeseg); // Mark it as faked. + // Connect it to all tets at this edge. + spintet = searchtet; + while (1) { + tssbond1(spintet, fakeseg); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + neighseg = fakeseg; + } + // Let the segment and the boundary edge point to each other. + ssbond(*missh, neighseg); + sstbond1(neighseg, searchtet); + } + senextself(*missh); + } // j + } // i + + + // Unmarktest collected missing subfaces. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + sunmarktest(*parysh); + } +} + +//============================================================================// +// // +// scoutcrossedge() Search an edge that crosses the missing region. // +// // +// Return 1 if a crossing edge is found. It is returned by 'crosstet'. More- // +// over, the edge is oriented such that its origin lies below R. Return 0 // +// if no such edge is found. // +// // +// Assumption: All vertices of the missing region are marktested. // +// // +//============================================================================// + +int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* missingshbds, + arraypool* missingshs) +{ + triface searchtet, spintet, neightet; + face oldsh, searchsh, *parysh; + face neighseg; + point pa, pb, pc, pd, pe; + REAL ori; + int types[2], poss[4]; + int searchflag, interflag; + int t1ver; + int i, j; + + searchflag = 0; + + // Search the first new subface to fill the region. + for (i = 0; i < missingshbds->objects && !searchflag; i++) { + parysh = (face *) fastlookup(missingshbds, i); + sspivot(*parysh, neighseg); + sstpivot1(neighseg, searchtet); + if (org(searchtet) != sorg(*parysh)) { + esymself(searchtet); + } + spintet = searchtet; + while (1) { + if (pmarktested(apex(spintet))) { + // A possible interior face. + neightet = spintet; + oldsh = *parysh; + // Try to recover an interior edge. + for (j = 0; j < 2; j++) { + enextself(neightet); + if (!issubseg(neightet)) { + if (j == 0) { + senext(oldsh, searchsh); + } else { + senext2(oldsh, searchsh); + sesymself(searchsh); + esymself(neightet); + } + // Calculate a lifted point. + pa = sorg(searchsh); + pb = sdest(searchsh); + pc = sapex(searchsh); + pd = dest(neightet); + calculateabovepoint4(pa, pb, pc, pd); + // The lifted point must lie above 'searchsh'. + ori = orient3d(pa, pb, pc, dummypoint); + if (ori > 0) { + sesymself(searchsh); + senextself(searchsh); + } else if (ori == 0) { + terminatetetgen(this, 2); + } + if (sscoutsegment(&searchsh,dest(neightet),0,0,1)==SHAREEDGE) { + // Insert a temp segment to protect the recovered edge. + face tmpseg; + makeshellface(subsegs, &tmpseg); + ssbond(searchsh, tmpseg); + spivotself(searchsh); + ssbond(searchsh, tmpseg); + // Recover locally Delaunay edges. + lawsonflip(); + // Delete the tmp segment. + spivot(tmpseg, searchsh); + ssdissolve(searchsh); + spivotself(searchsh); + ssdissolve(searchsh); + shellfacedealloc(subsegs, tmpseg.sh); + searchflag = 1; + } else { + // Undo the performed flips. + if (flipstack != NULL) { + lawsonflip(); + } + } + break; + } // if (!issubseg(neightet)) + } // j + if (searchflag) break; + } // if (pmarktested(apex(spintet))) + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + } // i + + if (searchflag) { + // Remove faked segments. + face checkseg; + // Remark: We should not use the array 'missingshbds', since the flips may + // change the subfaces. We search them from the subfaces in R. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + oldsh = *parysh; + for (j = 0; j < 3; j++) { + if (isshsubseg(oldsh)) { + sspivot(oldsh, checkseg); + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + sstpivot1(checkseg, searchtet); + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(oldsh); + } + } + senextself(oldsh); + } // j + } + + fillregioncount++; + + return 0; + } // if (i < missingshbds->objects) + + searchflag = -1; + + for (j = 0; j < missingshbds->objects && (searchflag == -1); j++) { + parysh = (face *) fastlookup(missingshbds, j); + sspivot(*parysh, neighseg); + sstpivot1(neighseg, searchtet); + interflag = 0; + // Let 'spintet' be [#,#,d,e] where [#,#] is the boundary edge of R. + spintet = searchtet; + while (1) { + pd = apex(spintet); + pe = oppo(spintet); + // Skip a hull edge. + if ((pd != dummypoint) && (pe != dummypoint)) { + // Skip an edge containing a vertex of R. + if (!pmarktested(pd) && !pmarktested(pe)) { + // Check if [d,e] intersects R. + for (i = 0; i < missingshs->objects && !interflag; i++) { + parysh = (face *) fastlookup(missingshs, i); + pa = sorg(*parysh); + pb = sdest(*parysh); + pc = sapex(*parysh); + interflag=tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss); + if (interflag > 0) { + if (interflag == 2) { + // They intersect at a single point. + if ((types[0] == (int) ACROSSFACE) || + (types[0] == (int) ACROSSEDGE)) { + // Go to the crossing edge [d,e,#,#]. + edestoppo(spintet, crosstet); // // [d,e,#,#]. + if (issubseg(crosstet)) { + // It is a segment. Report a PLC problem. + //report_selfint_face(pa, pb, pc, parysh, &crosstet, + // interflag, types, poss); + terminatetetgen(this, 3); + } else { + triface chkface = crosstet; + while (1) { + if (issubface(chkface)) break; + fsymself(chkface); + if (chkface.tet == crosstet.tet) break; + } + if (issubface(chkface)) { + // Two subfaces are intersecting. + //report_selfint_face(pa, pb, pc, parysh, &chkface, + // interflag, types, poss); + terminatetetgen(this, 3); + } + } + // Adjust the edge such that d lies below [a,b,c]. + ori = orient3d(pa, pb, pc, pd); + if (ori < 0) { + esymself(crosstet); + } + searchflag = 1; + } else { + // An improper intersection type, ACROSSVERT, TOUCHFACE, + // TOUCHEDGE, SHAREVERT, ... + // Maybe it is due to a PLC problem. + //report_selfint_face(pa, pb, pc, parysh, &crosstet, + // interflag, types, poss); + terminatetetgen(this, 3); + } + } + break; + } // if (interflag > 0) + } + } + } + // Leave search at this bdry edge if an intersection is found. + if (interflag > 0) break; + // Go to the next tetrahedron. + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } // while (1) + } // j + + return searchflag; +} + +//============================================================================// +// // +// formcavity() Form the cavity of a missing region. // +// // +// The missing region R is formed by a set of missing subfaces 'missingshs'. // +// In the following, we assume R is horizontal and oriented. (All subfaces // +// of R are oriented in the same way.) 'searchtet' is a tetrahedron [d,e,#, // +// #] which intersects R in its interior, where the edge [d,e] intersects R, // +// and d lies below R. // +// // +// 'crosstets' returns the set of crossing tets. Every tet in it has the // +// form [d,e,#,#] where [d,e] is a crossing edge, and d lies below R. The // +// set of tets form the cavity C, which is divided into two parts by R, one // +// at top and one at bottom. 'topfaces' and 'botfaces' return the upper and // +// lower boundary faces of C. 'toppoints' contains vertices of 'crosstets' // +// in the top part of C, and so does 'botpoints'. Both 'toppoints' and // +// 'botpoints' contain vertices of R. // +// // +// Important: This routine assumes all vertices of the facet containing this // +// subface are marked, i.e., pmarktested(p) returns true. // +// // +//============================================================================// + +bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, + arraypool* crosstets, arraypool* topfaces, + arraypool* botfaces, arraypool* toppoints, + arraypool* botpoints) +{ + arraypool *crossedges; + triface spintet, neightet, chkface, *parytet; + face *parysh = NULL; + point pa, pd, pe, *parypt; + bool testflag, invalidflag; + int intflag, types[2], poss[4]; + int t1ver; + int i, j, k; + + // Temporarily re-use 'topfaces' for all crossing edges. + crossedges = topfaces; + + if (b->verbose > 2) { + printf(" Form the cavity of a missing region.\n"); + } + // Mark this edge to avoid testing it later. + markedge(*searchtet); + crossedges->newindex((void **) &parytet); + *parytet = *searchtet; + + invalidflag = 0; + // Collect all crossing tets. Each cross tet is saved in the standard + // form [d,e,#,#], where [d,e] is a crossing edge, d lies below R. + // NEITHER d NOR e is a vertex of R (!pmarktested). + for (i = 0; i < crossedges->objects && !invalidflag; i++) { + // Get a crossing edge [d,e,#,#]. + searchtet = (triface *) fastlookup(crossedges, i); + // Sort vertices into the bottom and top arrays. + pd = org(*searchtet); + if (!pinfected(pd)) { + pinfect(pd); + botpoints->newindex((void **) &parypt); + *parypt = pd; + } + pe = dest(*searchtet); + if (!pinfected(pe)) { + pinfect(pe); + toppoints->newindex((void **) &parypt); + *parypt = pe; + } + + // All tets sharing this edge are crossing tets. + spintet = *searchtet; + while (1) { + if (!infected(spintet)) { + infect(spintet); + crosstets->newindex((void **) &parytet); + *parytet = spintet; + } + // Go to the next crossing tet. + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + + // Detect new crossing edges. + spintet = *searchtet; + while (1) { + // spintet is [d,e,a,#], where d lies below R, and e lies above R. + pa = apex(spintet); + if (pa != dummypoint) { + if (!pmarktested(pa)) { + // There exists a crossing edge, either [e,a] or [a,d]. First check + // if the crossing edge has already be added, i.e.,to check if one + // of the tetrahedron at this edge has been marked. + testflag = true; + for (j = 0; j < 2 && testflag; j++) { + if (j == 0) { + enext(spintet, neightet); + } else { + eprev(spintet, neightet); + } + while (1) { + if (edgemarked(neightet)) { + // This crossing edge has already been tested. Skip it. + testflag = false; + break; + } + fnextself(neightet); + if (neightet.tet == spintet.tet) break; + } + } // j + if (testflag) { + // Test if [e,a] or [a,d] intersects R. + // Do a brute-force search in the set of subfaces of R. Slow! + // Need to be improved! + pd = org(spintet); + pe = dest(spintet); + for (k = 0; k < missingshs->objects; k++) { + parysh = (face *) fastlookup(missingshs, k); + intflag = tri_edge_test(sorg(*parysh), sdest(*parysh), + sapex(*parysh), pe, pa, NULL, 1, types, poss); + if (intflag > 0) { + // Found intersection. 'a' lies below R. + if (intflag == 2) { + enext(spintet, neightet); + if ((types[0] == (int) ACROSSFACE) || + (types[0] == (int) ACROSSEDGE)) { + // Only this case is valid. + } else { + // A non-valid intersection. Maybe a PLC problem. + invalidflag = 1; + } + } else { + // Coplanar intersection. Maybe a PLC problem. + invalidflag = 1; + } + break; + } + intflag = tri_edge_test(sorg(*parysh), sdest(*parysh), + sapex(*parysh), pa, pd, NULL, 1, types, poss); + if (intflag > 0) { + // Found intersection. 'a' lies above R. + if (intflag == 2) { + eprev(spintet, neightet); + if ((types[0] == (int) ACROSSFACE) || + (types[0] == (int) ACROSSEDGE)) { + // Only this case is valid. + } else { + // A non-valid intersection. Maybe a PLC problem. + invalidflag = 1; + } + } else { + // Coplanar intersection. Maybe a PLC problem. + invalidflag = 1; + } + break; + } + } // k + if (k < missingshs->objects) { + // Found a pair of triangle - edge intersection. + if (invalidflag) { + break; // the while (1) loop + } + // Adjust the edge direction, so that its origin lies below R, + // and its destination lies above R. + esymself(neightet); + // This edge may be a segment. + if (issubseg(neightet)) { + //report_selfint_face(sorg(*parysh), sdest(*parysh), + // sapex(*parysh),parysh,&neightet,intflag,types,poss); + terminatetetgen(this, 3); + } + // Check if it is an edge of a subface. + chkface = neightet; + while (1) { + if (issubface(chkface)) break; + fsymself(chkface); + if (chkface.tet == neightet.tet) break; + } + if (issubface(chkface)) { + // Two subfaces are intersecting. + //report_selfint_face(sorg(*parysh), sdest(*parysh), + // sapex(*parysh),parysh,&chkface,intflag,types,poss); + terminatetetgen(this, 3); + } + + // Mark this edge to avoid testing it again. + markedge(neightet); + crossedges->newindex((void **) &parytet); + *parytet = neightet; + } else { + // No intersection is found. It may be a PLC problem. + invalidflag = 1; + break; // the while (1) loop + } // if (k == missingshs->objects) + } // if (testflag) + } + } // if (pa != dummypoint) + // Go to the next crossing tet. + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + } // i + + // Unmark all marked edges. + for (i = 0; i < crossedges->objects; i++) { + searchtet = (triface *) fastlookup(crossedges, i); + unmarkedge(*searchtet); + } + crossedges->restart(); + + + if (invalidflag) { + // Unmark all collected tets. + for (i = 0; i < crosstets->objects; i++) { + searchtet = (triface *) fastlookup(crosstets, i); + uninfect(*searchtet); + } + // Unmark all collected vertices. + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + puninfect(*parypt); + } + for (i = 0; i < toppoints->objects; i++) { + parypt = (point *) fastlookup(toppoints, i); + puninfect(*parypt); + } + crosstets->restart(); + botpoints->restart(); + toppoints->restart(); + + // Randomly split an interior edge of R. + i = randomnation(missingshs->objects - 1); + recentsh = * (face *) fastlookup(missingshs, i); + return false; + } + + if (b->verbose > 2) { + printf(" Formed cavity: %ld (%ld) cross tets (edges).\n", + crosstets->objects, crossedges->objects); + } + + // Collect the top and bottom faces and the middle vertices. Since all top + // and bottom vertices have been infected. Uninfected vertices must be + // middle vertices (i.e., the vertices of R). + // NOTE 1: Hull tets may be collected. Process them as a normal one. + // NOTE 2: Some previously recovered subfaces may be completely inside the + // cavity. In such case, we remove these subfaces from the cavity and put + // them into 'subfacstack'. They will be recovered later. + // NOTE 3: Some segments may be completely inside the cavity, e.g., they + // attached to a subface which is inside the cavity. Such segments are + // put in 'subsegstack'. They will be recovered later. + // NOTE4 : The interior subfaces and segments mentioned in NOTE 2 and 3 + // are identified in the routine "carvecavity()". + + for (i = 0; i < crosstets->objects; i++) { + searchtet = (triface *) fastlookup(crosstets, i); + // searchtet is [d,e,a,b]. + eorgoppo(*searchtet, spintet); + fsym(spintet, neightet); // neightet is [a,b,e,#] + if (!infected(neightet)) { + // A top face. + topfaces->newindex((void **) &parytet); + *parytet = neightet; + } + edestoppo(*searchtet, spintet); + fsym(spintet, neightet); // neightet is [b,a,d,#] + if (!infected(neightet)) { + // A bottom face. + botfaces->newindex((void **) &parytet); + *parytet = neightet; + } + // Add middle vertices if there are (skip dummypoint). + pa = org(neightet); + if (!pinfected(pa)) { + if (pa != dummypoint) { + pinfect(pa); + botpoints->newindex((void **) &parypt); + *parypt = pa; + toppoints->newindex((void **) &parypt); + *parypt = pa; + } + } + pa = dest(neightet); + if (!pinfected(pa)) { + if (pa != dummypoint) { + pinfect(pa); + botpoints->newindex((void **) &parypt); + *parypt = pa; + toppoints->newindex((void **) &parypt); + *parypt = pa; + } + } + } // i + + // Uninfect all collected top, bottom, and middle vertices. + for (i = 0; i < toppoints->objects; i++) { + parypt = (point *) fastlookup(toppoints, i); + puninfect(*parypt); + } + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + puninfect(*parypt); + } + cavitycount++; + + return true; +} + +//============================================================================// +// // +// delaunizecavity() Fill a cavity by Delaunay tetrahedra. // +// // +// The cavity C to be tetrahedralized is the top or bottom part of a whole // +// cavity. 'cavfaces' contains the boundary faces of C. NOTE: faces in 'cav- // +// faces' do not form a closed polyhedron. The "open" side are subfaces of // +// the missing facet. These faces will be recovered later in fillcavity(). // +// // +// This routine first constructs the DT of the vertices. Then it identifies // +// the half boundary faces of the cavity in DT. Possiblely the cavity C will // +// be enlarged. // +// // +// The DT is returned in 'newtets'. // +// // +//============================================================================// + +void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, + arraypool *cavshells, arraypool *newtets, + arraypool *crosstets, arraypool *misfaces) +{ + triface searchtet, neightet, *parytet, *parytet1; + face tmpsh, *parysh; + point pa, pb, pc, pd, pt[3], *parypt; + insertvertexflags ivf; + REAL ori; + long baknum, bakhullsize; + int bakchecksubsegflag, bakchecksubfaceflag; + int t1ver; + int i, j; + + if (b->verbose > 2) { + printf(" Delaunizing cavity: %ld points, %ld faces.\n", + cavpoints->objects, cavfaces->objects); + } + // Remember the current number of crossing tets. It may be enlarged later. + baknum = crosstets->objects; + bakhullsize = hullsize; + bakchecksubsegflag = checksubsegflag; + bakchecksubfaceflag = checksubfaceflag; + hullsize = 0l; + checksubsegflag = 0; + checksubfaceflag = 0; + b->verbose--; // Suppress informations for creating Delaunay tetra. + b->plc = 0; // Do not check near vertices. + + ivf.bowywat = 1; // Use Bowyer-Watson algorithm. + + // Get four non-coplanar points (no dummypoint). + pa = pb = pc = NULL; + for (i = 0; i < cavfaces->objects; i++) { + parytet = (triface *) fastlookup(cavfaces, i); + parytet->ver = epivot[parytet->ver]; + if (apex(*parytet) != dummypoint) { + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + break; + } + } + pd = NULL; + for (; i < cavfaces->objects; i++) { + parytet = (triface *) fastlookup(cavfaces, i); + pt[0] = org(*parytet); + pt[1] = dest(*parytet); + pt[2] = apex(*parytet); + for (j = 0; j < 3; j++) { + if (pt[j] != dummypoint) { // Do not include a hull point. + ori = orient3d(pa, pb, pc, pt[j]); + if (ori != 0) { + pd = pt[j]; + if (ori > 0) { // Swap pa and pb. + pt[j] = pa; pa = pb; pb = pt[j]; + } + break; + } + } + } + if (pd != NULL) break; + } + + // Create an init DT. + initialdelaunay(pa, pb, pc, pd); + + // Incrementally insert the vertices (duplicated vertices are ignored). + for (i = 0; i < cavpoints->objects; i++) { + pt[0] = * (point *) fastlookup(cavpoints, i); + searchtet = recenttet; + ivf.iloc = (int) OUTSIDE; + insertpoint(pt[0], &searchtet, NULL, NULL, &ivf); + } + + if (b->verbose > 2) { + printf(" Identifying %ld boundary faces of the cavity.\n", + cavfaces->objects); + } + + while (1) { + + // Identify boundary faces. Mark interior tets. Save missing faces. + for (i = 0; i < cavfaces->objects; i++) { + parytet = (triface *) fastlookup(cavfaces, i); + // Skip an interior face (due to the enlargement of the cavity). + if (infected(*parytet)) continue; + parytet->ver = epivot[parytet->ver]; + pt[0] = org(*parytet); + pt[1] = dest(*parytet); + pt[2] = apex(*parytet); + // Create a temp subface. + makeshellface(subfaces, &tmpsh); + setshvertices(tmpsh, pt[0], pt[1], pt[2]); + // Insert tmpsh in DT. + searchtet.tet = NULL; + if (scoutsubface(&tmpsh, &searchtet, 0)) { // shflag = 0 + // Inserted! 'tmpsh' must face toward the inside of the cavity. + // Remember the boundary tet (outside the cavity) in tmpsh + // (use the adjacent tet slot). + tmpsh.sh[0] = (shellface) encode(*parytet); + // Save this subface. + cavshells->newindex((void **) &parysh); + *parysh = tmpsh; + } + else { + // This boundary face is missing. + shellfacedealloc(subfaces, tmpsh.sh); + // Save this face in list. + misfaces->newindex((void **) &parytet1); + *parytet1 = *parytet; + } + } // i + + if (misfaces->objects > 0) { + if (b->verbose > 2) { + printf(" Enlarging the cavity. %ld missing bdry faces\n", + misfaces->objects); + } + + // Removing all temporary subfaces. + for (i = 0; i < cavshells->objects; i++) { + parysh = (face *) fastlookup(cavshells, i); + stpivot(*parysh, neightet); + tsdissolve(neightet); // Detach it from adj. tets. + fsymself(neightet); + tsdissolve(neightet); + shellfacedealloc(subfaces, parysh->sh); + } + cavshells->restart(); + + // Infect the points which are of the cavity. + for (i = 0; i < cavpoints->objects; i++) { + pt[0] = * (point *) fastlookup(cavpoints, i); + pinfect(pt[0]); // Mark it as inserted. + } + + // Enlarge the cavity. + for (i = 0; i < misfaces->objects; i++) { + // Get a missing face. + parytet = (triface *) fastlookup(misfaces, i); + if (!infected(*parytet)) { + // Put it into crossing tet list. + infect(*parytet); + crosstets->newindex((void **) &parytet1); + *parytet1 = *parytet; + // Insert the opposite point if it is not in DT. + pd = oppo(*parytet); + if (!pinfected(pd)) { + searchtet = recenttet; + ivf.iloc = (int) OUTSIDE; + insertpoint(pd, &searchtet, NULL, NULL, &ivf); + pinfect(pd); + cavpoints->newindex((void **) &parypt); + *parypt = pd; + } + // Add three opposite faces into the boundary list. + for (j = 0; j < 3; j++) { + esym(*parytet, neightet); + fsymself(neightet); + if (!infected(neightet)) { + cavfaces->newindex((void **) &parytet1); + *parytet1 = neightet; + } + enextself(*parytet); + } // j + } // if (!infected(parytet)) + } // i + + // Uninfect the points which are of the cavity. + for (i = 0; i < cavpoints->objects; i++) { + pt[0] = * (point *) fastlookup(cavpoints, i); + puninfect(pt[0]); + } + + misfaces->restart(); + continue; + } // if (misfaces->objects > 0) + + break; + + } // while (1) + + // Collect all tets of the DT. All new tets are marktested. + marktest(recenttet); + newtets->newindex((void **) &parytet); + *parytet = recenttet; + for (i = 0; i < newtets->objects; i++) { + searchtet = * (triface *) fastlookup(newtets, i); + for (j = 0; j < 4; j++) { + decode(searchtet.tet[j], neightet); + if (!marktested(neightet)) { + marktest(neightet); + newtets->newindex((void **) &parytet); + *parytet = neightet; + } + } + } + + cavpoints->restart(); + cavfaces->restart(); + + if (crosstets->objects > baknum) { + // The cavity has been enlarged. + cavityexpcount++; + } + + // Restore the original values. + hullsize = bakhullsize; + checksubsegflag = bakchecksubsegflag; + checksubfaceflag = bakchecksubfaceflag; + b->verbose++; + b->plc = 1; +} + +//============================================================================// +// // +// fillcavity() Fill new tets into the cavity. // +// // +// The new tets are stored in two disjoint sets(which share the same facet). // +// 'topfaces' and 'botfaces' are the boundaries of these two sets, respect- // +// ively. 'midfaces' is empty on input, and will store faces in the facet. // +// // +// Important: This routine assumes all vertices of the missing region R are // +// marktested, i.e., pmarktested(p) returns true. // +// // +//============================================================================// + +bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, + arraypool* midfaces, arraypool* missingshs, + arraypool* topnewtets, arraypool* botnewtets, + triface* crossedge) +{ + arraypool *cavshells; + triface bdrytet, neightet, *parytet; + triface searchtet, spintet; + face *parysh; + face checkseg; + point pa, pb, pc; + bool mflag; + int t1ver; + int i, j; + + // Connect newtets to tets outside the cavity. These connections are needed + // for identifying the middle faces (which belong to R). + for (j = 0; j < 2; j++) { + cavshells = (j == 0 ? topshells : botshells); + if (cavshells != NULL) { + for (i = 0; i < cavshells->objects; i++) { + // Get a temp subface. + parysh = (face *) fastlookup(cavshells, i); + // Get the boundary tet outside the cavity (saved in sh[0]). + decode(parysh->sh[0], bdrytet); + pa = org(bdrytet); + pb = dest(bdrytet); + pc = apex(bdrytet); + // Get the adjacent new tet inside the cavity. + stpivot(*parysh, neightet); + // Mark neightet as an interior tet of this cavity. + infect(neightet); + // Connect the two tets (the old connections are replaced). + bond(bdrytet, neightet); + tsdissolve(neightet); // Clear the pointer to tmpsh. + // Update the point-to-tets map. + setpoint2tet(pa, (tetrahedron) neightet.tet); + setpoint2tet(pb, (tetrahedron) neightet.tet); + setpoint2tet(pc, (tetrahedron) neightet.tet); + } // i + } // if (cavshells != NULL) + } // j + + if (crossedge != NULL) { + // Glue top and bottom tets at their common facet. + triface toptet, bottet, spintet, *midface; + point pd, pe; + REAL ori; + int types[2], poss[4]; + int interflag; + int bflag; + + mflag = false; + pd = org(*crossedge); + pe = dest(*crossedge); + + // Search the first (middle) face in R. + // Since R may be non-convex, we must make sure that the face is in the + // interior of R. We search a face in 'topnewtets' whose three vertices + // are on R and it intersects 'crossedge' in its interior. Then search + // a matching face in 'botnewtets'. + for (i = 0; i < topnewtets->objects && !mflag; i++) { + searchtet = * (triface *) fastlookup(topnewtets, i); + for (searchtet.ver = 0; searchtet.ver < 4 && !mflag; searchtet.ver++) { + pa = org(searchtet); + if (pmarktested(pa)) { + pb = dest(searchtet); + if (pmarktested(pb)) { + pc = apex(searchtet); + if (pmarktested(pc)) { + // Check if this face intersects [d,e]. + interflag = tri_edge_test(pa,pb,pc,pd,pe,NULL,1,types,poss); + if (interflag == 2) { + // They intersect at a single point. Found. + toptet = searchtet; + // The face lies in the interior of R. + // Get the tet (in topnewtets) which lies above R. + ori = orient3d(pa, pb, pc, pd); + if (ori < 0) { + fsymself(toptet); + pa = org(toptet); + pb = dest(toptet); + } else if (ori == 0) { + terminatetetgen(this, 2); + } + // Search the face [b,a,c] in 'botnewtets'. + for (j = 0; j < botnewtets->objects; j++) { + neightet = * (triface *) fastlookup(botnewtets, j); + // Is neightet contains 'b'. + if ((point) neightet.tet[4] == pb) { + neightet.ver = 11; + } else if ((point) neightet.tet[5] == pb) { + neightet.ver = 3; + } else if ((point) neightet.tet[6] == pb) { + neightet.ver = 7; + } else if ((point) neightet.tet[7] == pb) { + neightet.ver = 0; + } else { + continue; + } + // Is the 'neightet' contains edge [b,a]. + if (dest(neightet) == pa) { + // 'neightet' is just the edge. + } else if (apex(neightet) == pa) { + eprevesymself(neightet); + } else if (oppo(neightet) == pa) { + esymself(neightet); + enextself(neightet); + } else { + continue; + } + // Is 'neightet' the face [b,a,c]. + if (apex(neightet) == pc) { + bottet = neightet; + mflag = true; + break; + } + } // j + } // if (interflag == 2) + } // pc + } // pb + } // pa + } // toptet.ver + } // i + + if (mflag) { + // Found a pair of matched faces in 'toptet' and 'bottet'. + bond(toptet, bottet); + // Both are interior tets. + infect(toptet); + infect(bottet); + // Add this face into search list. + markface(toptet); + midfaces->newindex((void **) &parytet); + *parytet = toptet; + } else { + // No pair of 'toptet' and 'bottet'. + toptet.tet = NULL; + // Randomly split an interior edge of R. + i = randomnation(missingshs->objects - 1); + recentsh = * (face *) fastlookup(missingshs, i); + } + + // Find other middle faces, connect top and bottom tets. + for (i = 0; i < midfaces->objects && mflag; i++) { + // Get a matched middle face [a, b, c] + midface = (triface *) fastlookup(midfaces, i); + // Check the neighbors at the edges of this face. + for (j = 0; j < 3 && mflag; j++) { + toptet = *midface; + bflag = false; + while (1) { + // Go to the next face in the same tet. + esymself(toptet); + pc = apex(toptet); + if (pmarktested(pc)) { + break; // Find a subface. + } + if (pc == dummypoint) { + terminatetetgen(this, 2); // Check this case. + break; // Find a subface. + } + // Go to the adjacent tet. + fsymself(toptet); + // Do we walk outside the cavity? + if (!marktested(toptet)) { + // Yes, the adjacent face is not a middle face. + bflag = true; break; + } + } + if (!bflag) { + if (!facemarked(toptet)) { + fsym(*midface, bottet); + spintet = bottet; + while (1) { + esymself(bottet); + pd = apex(bottet); + if (pd == pc) break; // Face matched. + fsymself(bottet); + if (bottet.tet == spintet.tet) { + // Not found a matched bottom face. + mflag = false; + break; + } + } // while (1) + if (mflag) { + if (marktested(bottet)) { + // Connect two tets together. + bond(toptet, bottet); + // Both are interior tets. + infect(toptet); + infect(bottet); + // Add this face into list. + markface(toptet); + midfaces->newindex((void **) &parytet); + *parytet = toptet; + } + else { + // The 'bottet' is not inside the cavity! + terminatetetgen(this, 2); // Check this case + } + } else { // mflag == false + // Adjust 'toptet' and 'bottet' to be the crossing edges. + fsym(*midface, bottet); + spintet = bottet; + while (1) { + esymself(bottet); + pd = apex(bottet); + if (pmarktested(pd)) { + // assert(pd != pc); + // Let 'toptet' be [a,b,c,#], and 'bottet' be [b,a,d,*]. + // Adjust 'toptet' and 'bottet' to be the crossing edges. + // Test orient3d(b,c,#,d). + ori = orient3d(dest(toptet), pc, oppo(toptet), pd); + if (ori < 0) { + // Edges [a,d] and [b,c] cross each other. + enextself(toptet); // [b,c] + enextself(bottet); // [a,d] + } else if (ori > 0) { + // Edges [a,c] and [b,d] cross each other. + eprevself(toptet); // [c,a] + eprevself(bottet); // [d,b] + } else { + // b,c,#,and d are coplanar!. + terminatetetgen(this, 2); //assert(0); + } + break; // Not matched + } + fsymself(bottet); + } + } // if (!mflag) + } // if (!facemarked(toptet)) + } // if (!bflag) + enextself(*midface); + } // j + } // i + + if (mflag) { + if (b->verbose > 2) { + printf(" Found %ld middle subfaces.\n", midfaces->objects); + } + face oldsh, newsh, casout, casin, neighsh; + + oldsh = * (face *) fastlookup(missingshs, 0); + + // Create new subfaces to fill the region R. + for (i = 0; i < midfaces->objects; i++) { + // Get a matched middle face [a, b, c] + midface = (triface *) fastlookup(midfaces, i); + unmarkface(*midface); + makeshellface(subfaces, &newsh); + setsorg(newsh, org(*midface)); + setsdest(newsh, dest(*midface)); + setsapex(newsh, apex(*midface)); + // The new subface gets its markers from the old one. + setshellmark(newsh, shellmark(oldsh)); + if (checkconstraints) { + setareabound(newsh, areabound(oldsh)); + } + if (useinsertradius) { + setfacetindex(newsh, getfacetindex(oldsh)); + } + // Connect the new subface to adjacent tets. + tsbond(*midface, newsh); + fsym(*midface, neightet); + sesymself(newsh); + tsbond(neightet, newsh); + } + + // Connect new subfaces together and to the bdry of R. + // Delete faked segments. + for (i = 0; i < midfaces->objects; i++) { + // Get a matched middle face [a, b, c] + midface = (triface *) fastlookup(midfaces, i); + for (j = 0; j < 3; j++) { + tspivot(*midface, newsh); + spivot(newsh, casout); + if (casout.sh == NULL) { + // Search its neighbor. + fnext(*midface, searchtet); + while (1) { + // (1) First check if this side is a bdry edge of R. + tsspivot1(searchtet, checkseg); + if (checkseg.sh != NULL) { + // It's a bdry edge of R. + // Get the old subface. + checkseg.shver = 0; + spivot(checkseg, oldsh); + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(oldsh); + checkseg.sh = NULL; + } + spivot(oldsh, casout); + if (casout.sh != NULL) { + casin = casout; + if (checkseg.sh != NULL) { + // Make sure that the subface has the right ori at the + // segment. + checkseg.shver = 0; + if (sorg(newsh) != sorg(checkseg)) { + sesymself(newsh); + } + spivot(casin, neighsh); + while (neighsh.sh != oldsh.sh) { + casin = neighsh; + spivot(casin, neighsh); + } + } + sbond1(newsh, casout); + sbond1(casin, newsh); + } + if (checkseg.sh != NULL) { + ssbond(newsh, checkseg); + } + break; + } // if (checkseg.sh != NULL) + // (2) Second check if this side is an interior edge of R. + tspivot(searchtet, neighsh); + if (neighsh.sh != NULL) { + // Found an adjacent subface of newsh (an interior edge). + sbond(newsh, neighsh); + break; + } + fnextself(searchtet); + } // while (1) + } // if (casout.sh == NULL) + enextself(*midface); + } // j + } // i + + // Delete old subfaces. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + shellfacedealloc(subfaces, parysh->sh); + } + } else { + if (toptet.tet != NULL) { + // Faces at top and bottom are not matched. + // Choose a Steiner point in R. + // Split one of the crossing edges. + pa = org(toptet); + pb = dest(toptet); + pc = org(bottet); + pd = dest(bottet); + // Search an edge in R which is either [a,b] or [c,d]. + // Reminder: Subfaces in this list 'missingshs', except the first + // one, represents an interior edge of R. + parysh = NULL; // Avoid a warning in MSVC + for (i = 1; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + if (((sorg(*parysh) == pa) && (sdest(*parysh) == pb)) || + ((sorg(*parysh) == pb) && (sdest(*parysh) == pa))) break; + if (((sorg(*parysh) == pc) && (sdest(*parysh) == pd)) || + ((sorg(*parysh) == pd) && (sdest(*parysh) == pc))) break; + } + if (i < missingshs->objects) { + // Found. Return it. + recentsh = *parysh; + } else { + terminatetetgen(this, 2); //assert(0); + } + } else { + //terminatetetgen(this, 2); // Report a bug + } + } + + midfaces->restart(); + } else { + mflag = true; + } + + // Delete the temp subfaces. + for (j = 0; j < 2; j++) { + cavshells = (j == 0 ? topshells : botshells); + if (cavshells != NULL) { + for (i = 0; i < cavshells->objects; i++) { + parysh = (face *) fastlookup(cavshells, i); + shellfacedealloc(subfaces, parysh->sh); + } + } + } + + topshells->restart(); + if (botshells != NULL) { + botshells->restart(); + } + + return mflag; +} + +//============================================================================// +// // +// carvecavity() Delete old tets and outer new tets of the cavity. // +// // +//============================================================================// + +void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, + arraypool *botnewtets) +{ + arraypool *newtets; + shellface *sptr, *ssptr; + triface *parytet, *pnewtet, newtet, neightet, spintet; + face checksh, *parysh; + face checkseg, *paryseg; + int t1ver; + int i, j; + + if (b->verbose > 2) { + printf(" Carve cavity: %ld old tets.\n", crosstets->objects); + } + + // First process subfaces and segments which are adjacent to the cavity. + // They must be re-connected to new tets in the cavity. + // Comment: It is possible that some subfaces and segments are completely + // inside the cavity. This can happen even if the cavity is not enlarged. + // Before deleting the old tets, find and queue all interior subfaces + // and segments. They will be recovered later. 2010-05-06. + + // Collect all subfaces and segments which attached to the old tets. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + if ((sptr = (shellface*) parytet->tet[9]) != NULL) { + for (j = 0; j < 4; j++) { + if (sptr[j]) { + sdecode(sptr[j], checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + cavetetshlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + } // j + } + if ((ssptr = (shellface*) parytet->tet[8]) != NULL) { + for (j = 0; j < 6; j++) { + if (ssptr[j]) { + sdecode(ssptr[j], checkseg); + // Skip a deleted segment (was a faked segment) + if (checkseg.sh[3] != NULL) { + if (!sinfected(checkseg)) { + sinfect(checkseg); + cavetetseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } + } + } // j + } + } // i + + // Uninfect collected subfaces. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + suninfect(*parysh); + } + // Uninfect collected segments. + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg = (face *) fastlookup(cavetetseglist, i); + suninfect(*paryseg); + } + + // Connect subfaces to new tets. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + // Get an adjacent tet at this subface. + stpivot(*parysh, neightet); + // Does this tet lie inside the cavity. + if (infected(neightet)) { + // Yes. Get the other adjacent tet at this subface. + sesymself(*parysh); + stpivot(*parysh, neightet); + // Does this tet lie inside the cavity. + if (infected(neightet)) { + checksh = *parysh; + stdissolve(checksh); + caveencshlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + if (!infected(neightet)) { + // Found an outside tet. Re-connect this subface to a new tet. + fsym(neightet, newtet); + sesymself(*parysh); + tsbond(newtet, *parysh); + } + } // i + + + for (i = 0; i < cavetetseglist->objects; i++) { + checkseg = * (face *) fastlookup(cavetetseglist, i); + // Check if the segment is inside the cavity. + sstpivot1(checkseg, neightet); + spintet = neightet; + while (1) { + if (!infected(spintet)) { + // This segment is on the boundary of the cavity. + break; + } + fnextself(spintet); + if (spintet.tet == neightet.tet) { + sstdissolve1(checkseg); + caveencseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + break; + } + } + if (!infected(spintet)) { + // A boundary segment. Connect this segment to the new tets. + sstbond1(checkseg, spintet); + neightet = spintet; + while (1) { + tssbond1(spintet, checkseg); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + } + } // i + + + cavetetshlist->restart(); + cavetetseglist->restart(); + + // Delete the old tets in cavity. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + if (ishulltet(*parytet)) { + hullsize--; + } + tetrahedrondealloc(parytet->tet); + } + + crosstets->restart(); // crosstets will be re-used. + + // Collect new tets in cavity. Some new tets have already been found + // (and infected) in the fillcavity(). We first collect them. + for (j = 0; j < 2; j++) { + newtets = (j == 0 ? topnewtets : botnewtets); + if (newtets != NULL) { + for (i = 0; i < newtets->objects; i++) { + parytet = (triface *) fastlookup(newtets, i); + if (infected(*parytet)) { + crosstets->newindex((void **) &pnewtet); + *pnewtet = *parytet; + } + } // i + } + } // j + + // Now we collect all new tets in cavity. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + for (j = 0; j < 4; j++) { + decode(parytet->tet[j], neightet); + if (marktested(neightet)) { // Is it a new tet? + if (!infected(neightet)) { + // Find an interior tet. + //assert((point) neightet.tet[7] != dummypoint); // SELF_CHECK + infect(neightet); + crosstets->newindex((void **) &pnewtet); + *pnewtet = neightet; + } + } + } // j + } // i + + parytet = (triface *) fastlookup(crosstets, 0); + recenttet = *parytet; // Remember a live handle. + + // Delete outer new tets. + for (j = 0; j < 2; j++) { + newtets = (j == 0 ? topnewtets : botnewtets); + if (newtets != NULL) { + for (i = 0; i < newtets->objects; i++) { + parytet = (triface *) fastlookup(newtets, i); + if (infected(*parytet)) { + // This is an interior tet. + uninfect(*parytet); + unmarktest(*parytet); + if (ishulltet(*parytet)) { + hullsize++; + } + } else { + // An outer tet. Delete it. + tetrahedrondealloc(parytet->tet); + } + } + } + } + + crosstets->restart(); + topnewtets->restart(); + if (botnewtets != NULL) { + botnewtets->restart(); + } +} + +//============================================================================// +// // +// restorecavity() Reconnect old tets and delete new tets of the cavity. // +// // +//============================================================================// + +void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, + arraypool *botnewtets, arraypool *missingshbds) +{ + triface *parytet, neightet, spintet; + face *parysh; + face checkseg; + point *ppt; + int t1ver; + int i, j; + + // Reconnect crossing tets to cavity boundary. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + parytet->ver = 0; + for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { + fsym(*parytet, neightet); + if (!infected(neightet)) { + // Restore the old connections of tets. + bond(*parytet, neightet); + } + } + // Update the point-to-tet map. + parytet->ver = 0; + ppt = (point *) &(parytet->tet[4]); + for (j = 0; j < 4; j++) { + setpoint2tet(ppt[j], encode(*parytet)); + } + } + + // Uninfect all crossing tets. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + uninfect(*parytet); + } + + // Remember a live handle. + if (crosstets->objects > 0) { + recenttet = * (triface *) fastlookup(crosstets, 0); + } + + // Delete faked segments. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + sspivot(*parysh, checkseg); + if (checkseg.sh[3] != NULL) { + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + sstpivot1(checkseg, neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(*parysh); + //checkseg.sh = NULL; + } + } + } // i + + // Delete new tets. + for (i = 0; i < topnewtets->objects; i++) { + parytet = (triface *) fastlookup(topnewtets, i); + tetrahedrondealloc(parytet->tet); + } + + if (botnewtets != NULL) { + for (i = 0; i < botnewtets->objects; i++) { + parytet = (triface *) fastlookup(botnewtets, i); + tetrahedrondealloc(parytet->tet); + } + } + + crosstets->restart(); + topnewtets->restart(); + if (botnewtets != NULL) { + botnewtets->restart(); + } +} + +//============================================================================// +// // +// flipcertify() Insert a crossing face into priority queue. // +// // +// A crossing face of a facet must have at least one top and one bottom ver- // +// tex of the facet. // +// // +//============================================================================// + +void tetgenmesh::flipcertify(triface *chkface,badface **pqueue,point plane_pa, + point plane_pb, point plane_pc) +{ + badface *parybf, *prevbf, *nextbf; + triface neightet; + face checksh; + point p[5]; + REAL w[5]; + REAL insph, ori4; + int topi, boti; + int i; + + // Compute the flip time \tau. + fsym(*chkface, neightet); + + p[0] = org(*chkface); + p[1] = dest(*chkface); + p[2] = apex(*chkface); + p[3] = oppo(*chkface); + p[4] = oppo(neightet); + + // Check if the face is a crossing face. + topi = boti = 0; + for (i = 0; i < 3; i++) { + if (pmarktest2ed(p[i])) topi++; + if (pmarktest3ed(p[i])) boti++; + } + if ((topi == 0) || (boti == 0)) { + // It is not a crossing face. + // return; + for (i = 3; i < 5; i++) { + if (pmarktest2ed(p[i])) topi++; + if (pmarktest3ed(p[i])) boti++; + } + if ((topi == 0) || (boti == 0)) { + // The two tets sharing at this face are on one side of the facet. + // Check if this face is locally Delaunay (due to rounding error). + if ((p[3] != dummypoint) && (p[4] != dummypoint)) { + // Do not check it if it is a subface. + tspivot(*chkface, checksh); + if (checksh.sh == NULL) { + insph = insphere_s(p[1], p[0], p[2], p[3], p[4]); + if (insph > 0) { + // Add the face into queue. + if (b->verbose > 2) { + printf(" A locally non-Delanay face (%d, %d, %d)-%d,%d\n", + pointmark(p[0]), pointmark(p[1]), pointmark(p[2]), + pointmark(p[3]), pointmark(p[4])); + } + parybf = (badface *) flippool->alloc(); + parybf->key = 0.; // tau = 0, do immediately. + parybf->tt = *chkface; + parybf->forg = p[0]; + parybf->fdest = p[1]; + parybf->fapex = p[2]; + parybf->foppo = p[3]; + parybf->noppo = p[4]; + // Add it at the top of the priority queue. + if (*pqueue == NULL) { + *pqueue = parybf; + parybf->nextitem = NULL; + } else { + parybf->nextitem = *pqueue; + *pqueue = parybf; + } + } // if (insph > 0) + } // if (checksh.sh == NULL) + } + } + return; // Test: omit this face. + } + + // Decide the "height" for each point. + for (i = 0; i < 5; i++) { + if (pmarktest2ed(p[i])) { + // A top point has a positive weight. + w[i] = orient3dfast(plane_pa, plane_pb, plane_pc, p[i]); + if (w[i] < 0) w[i] = -w[i]; + } else { + w[i] = 0; + } + } + + insph = insphere(p[1], p[0], p[2], p[3], p[4]); + ori4 = orient4d(p[1], p[0], p[2], p[3], p[4], w[1], w[0], w[2], w[3], w[4]); + if (ori4 > 0) { + // Add the face into queue. + if (b->verbose > 2) { + printf(" Insert face (%d, %d, %d) - %d, %d\n", pointmark(p[0]), + pointmark(p[1]), pointmark(p[2]), pointmark(p[3]), pointmark(p[4])); + } + + parybf = (badface *) flippool->alloc(); + + parybf->key = -insph / ori4; + parybf->tt = *chkface; + parybf->forg = p[0]; + parybf->fdest = p[1]; + parybf->fapex = p[2]; + parybf->foppo = p[3]; + parybf->noppo = p[4]; + + // Push the face into priority queue. + //pq.push(bface); + if (*pqueue == NULL) { + *pqueue = parybf; + parybf->nextitem = NULL; + } else { + // Search an item whose key is larger or equal to current key. + prevbf = NULL; + nextbf = *pqueue; + //if (!b->flipinsert_random) { // Default use a priority queue. + // Insert the item into priority queue. + while (nextbf != NULL) { + if (nextbf->key < parybf->key) { + prevbf = nextbf; + nextbf = nextbf->nextitem; + } else { + break; + } + } + //} // if (!b->flipinsert_random) + // Insert the new item between prev and next items. + if (prevbf == NULL) { + *pqueue = parybf; + } else { + prevbf->nextitem = parybf; + } + parybf->nextitem = nextbf; + } + } else if (ori4 == 0) { + + } +} + +//============================================================================// +// // +// flipinsertfacet() Insert a facet into a CDT by flips. // +// // +// The algorithm is described in Shewchuk's paper "Updating and Constructing // +// Constrained Delaunay and Constrained Regular Triangulations by Flips", in // +// Proc. 19th Ann. Symp. on Comput. Geom., 86--95, 2003. // +// // +// 'crosstets' contains the set of crossing tetrahedra (infected) of the // +// facet. 'toppoints' and 'botpoints' are points lies above and below the // +// facet, not on the facet. // +// // +//============================================================================// + +void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, + arraypool *botpoints, arraypool *midpoints) +{ + arraypool *crossfaces, *bfacearray; + triface fliptets[6], baktets[2], fliptet, newface; + triface neightet, *parytet; + badface *pqueue; + badface *popbf, bface; + point plane_pa, plane_pb, plane_pc; + point p1, p2, pd, pe; + point *parypt; + flipconstraints fc; + REAL ori[3]; + int convcount, copcount; + int flipflag, fcount; + int n, i; + long f23count, f32count, f44count; + long totalfcount; + + f23count = flip23count; + f32count = flip32count; + f44count = flip44count; + + // Get three affinely independent vertices in the missing region R. + calculateabovepoint(midpoints, &plane_pa, &plane_pb, &plane_pc); + + // Mark top and bottom points. Do not mark midpoints. + for (i = 0; i < toppoints->objects; i++) { + parypt = (point *) fastlookup(toppoints, i); + if (!pmarktested(*parypt)) { + pmarktest2(*parypt); + } + } + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + if (!pmarktested(*parypt)) { + pmarktest3(*parypt); + } + } + + // Collect crossing faces. + crossfaces = cavetetlist; // Re-use array 'cavetetlist'. + + // Each crossing face contains at least one bottom vertex and + // one top vertex. + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + fliptet = *parytet; + for (fliptet.ver = 0; fliptet.ver < 4; fliptet.ver++) { + fsym(fliptet, neightet); + if (infected(neightet)) { // It is an interior face. + if (!marktested(neightet)) { // It is an unprocessed face. + crossfaces->newindex((void **) &parytet); + *parytet = fliptet; + } + } + } + marktest(fliptet); + } + + if (b->verbose > 1) { + printf(" Found %ld crossing faces.\n", crossfaces->objects); + } + + for (i = 0; i < crosstets->objects; i++) { + parytet = (triface *) fastlookup(crosstets, i); + unmarktest(*parytet); + uninfect(*parytet); + } + + // Initialize the priority queue. + pqueue = NULL; + + for (i = 0; i < crossfaces->objects; i++) { + parytet = (triface *) fastlookup(crossfaces, i); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); + } + crossfaces->restart(); + + // The list for temporarily storing unflipable faces. + bfacearray = new arraypool(sizeof(triface), 4); + + + fcount = 0; // Count the number of flips. + + // Flip insert the facet. + while (pqueue != NULL) { + + // Pop a face from the priority queue. + popbf = pqueue; + bface = *popbf; + // Update the queue. + pqueue = pqueue->nextitem; + // Delete the popped item from the pool. + flippool->dealloc((void *) popbf); + + if (!isdeadtet(bface.tt)) { + if ((org(bface.tt) == bface.forg) && (dest(bface.tt) == bface.fdest) && + (apex(bface.tt) == bface.fapex) && (oppo(bface.tt) == bface.foppo)) { + // It is still a crossing face of R. + fliptet = bface.tt; + fsym(fliptet, neightet); + if (oppo(neightet) == bface.noppo) { + pd = oppo(fliptet); + pe = oppo(neightet); + + if (b->verbose > 2) { + printf(" Get face (%d, %d, %d) - %d, %d, tau = %.17g\n", + pointmark(bface.forg), pointmark(bface.fdest), + pointmark(bface.fapex), pointmark(bface.foppo), + pointmark(bface.noppo), bface.key); + } + flipflag = 0; + + // Check for which type of flip can we do. + convcount = 3; + copcount = 0; + for (i = 0; i < 3; i++) { + p1 = org(fliptet); + p2 = dest(fliptet); + ori[i] = orient3d(p1, p2, pd, pe); + if (ori[i] < 0) { + convcount--; + //break; + } else if (ori[i] == 0) { + convcount--; // Possible 4-to-4 flip. + copcount++; + //break; + } + enextself(fliptet); + } + + if (convcount == 3) { + // A 2-to-3 flip is found. + fliptets[0] = fliptet; // abcd, d may be the new vertex. + fliptets[1] = neightet; // bace. + flip23(fliptets, 1, &fc); + // Put the link faces into check list. + for (i = 0; i < 3; i++) { + eprevesym(fliptets[i], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + } + for (i = 0; i < 3; i++) { + enextesym(fliptets[i], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + } + flipflag = 1; + } else if (convcount == 2) { + //if (copcount <= 1) { + // A 3-to-2 or 4-to-4 may be possible. + // Get the edge which is locally non-convex or flat. + for (i = 0; i < 3; i++) { + if (ori[i] <= 0) break; + enextself(fliptet); + } + + // Collect tets sharing at this edge. + esym(fliptet, fliptets[0]); // [b,a,d,c] + n = 0; + do { + p1 = apex(fliptets[n]); + if (!(pmarktested(p1) || pmarktest2ed(p1) || pmarktest3ed(p1))) { + // This apex is not on the cavity. Hence the face does not + // lie inside the cavity. Do not flip this edge. + n = 1000; break; + } + fnext(fliptets[n], fliptets[n + 1]); + n++; + } while ((fliptets[n].tet != fliptet.tet) && (n < 5)); + + if (n == 3) { + // Found a 3-to-2 flip. + flip32(fliptets, 1, &fc); + // Put the link faces into check list. + for (i = 0; i < 3; i++) { + esym(fliptets[0], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + enextself(fliptets[0]); + } + for (i = 0; i < 3; i++) { + esym(fliptets[1], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + enextself(fliptets[1]); + } + flipflag = 1; + } else if (n == 4) { + if (copcount == 1) { + // Found a 4-to-4 flip. + // Let the six vertices are: a,b,c,d,e,f, where + // fliptets[0] = [b,a,d,c] + // [1] = [b,a,c,e] + // [2] = [b,a,e,f] + // [3] = [b,a,f,d] + // After the 4-to-4 flip, edge [a,b] is flipped, edge [e,d] + // is created. + // First do a 2-to-3 flip. + // Comment: This flip temporarily creates a degenerated + // tet (whose volume is zero). It will be removed by the + // followed 3-to-2 flip. + fliptets[0] = fliptet; // = [a,b,c,d], d is the new vertex. + // fliptets[1]; // = [b,a,c,e]. + baktets[0] = fliptets[2]; // = [b,a,e,f] + baktets[1] = fliptets[3]; // = [b,a,f,d] + // The flip may involve hull tets. + flip23(fliptets, 1, &fc); + // Put the "outer" link faces into check list. + // fliptets[0] = [e,d,a,b] => will be flipped, so + // [a,b,d] and [a,b,e] are not "outer" link faces. + for (i = 1; i < 3; i++) { + eprevesym(fliptets[i], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + } + for (i = 1; i < 3; i++) { + enextesym(fliptets[i], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + } + // Then do a 3-to-2 flip. + enextesymself(fliptets[0]); // fliptets[0] is [e,d,a,b]. + eprevself(fliptets[0]); // = [b,a,d,c], d is the new vertex. + fliptets[1] = baktets[0]; // = [b,a,e,f] + fliptets[2] = baktets[1]; // = [b,a,f,d] + flip32(fliptets, 1, &fc); + // Put the "outer" link faces into check list. + // fliptets[0] = [d,e,f,a] + // fliptets[1] = [e,d,f,b] + // Faces [a,b,d] and [a,b,e] are not "outer" link faces. + enextself(fliptets[0]); + for (i = 1; i < 3; i++) { + esym(fliptets[0], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + enextself(fliptets[0]); + } + enextself(fliptets[1]); + for (i = 1; i < 3; i++) { + esym(fliptets[1], newface); + crossfaces->newindex((void **) &parytet); + *parytet = newface; + enextself(fliptets[1]); + } + flip23count--; + flip32count--; + flip44count++; + flipflag = 1; + } + } + } else { + // There are more than 1 non-convex or coplanar cases. + flipflag = -1; // Ignore this face. + if (b->verbose > 2) { + printf(" Ignore face (%d, %d, %d) - %d, %d, tau = %.17g\n", + pointmark(bface.forg), pointmark(bface.fdest), + pointmark(bface.fapex), pointmark(bface.foppo), + pointmark(bface.noppo), bface.key); + } + } // if (convcount == 1) + + if (flipflag == 1) { + // Update the priority queue. + for (i = 0; i < crossfaces->objects; i++) { + parytet = (triface *) fastlookup(crossfaces, i); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); + } + crossfaces->restart(); + if (1) { // if (!b->flipinsert_random) { + // Insert all queued unflipped faces. + for (i = 0; i < bfacearray->objects; i++) { + parytet = (triface *) fastlookup(bfacearray, i); + // This face may be changed. + if (!isdeadtet(*parytet)) { + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); + } + } + bfacearray->restart(); + } + fcount++; + } else if (flipflag == 0) { + // Queue an unflippable face. To process it later. + bfacearray->newindex((void **) &parytet); + *parytet = fliptet; + } + } // if (pe == bface.noppo) + } // if ((pa == bface.forg) && ...) + } // if (bface.tt != NULL) + + } // while (pqueue != NULL) + + if (bfacearray->objects > 0) { + if (fcount == 0) { + printf("!! No flip is found in %ld faces.\n", bfacearray->objects); + terminatetetgen(this, 2); //assert(0); + } + } + + delete bfacearray; + + // Un-mark top and bottom points. + for (i = 0; i < toppoints->objects; i++) { + parypt = (point *) fastlookup(toppoints, i); + punmarktest2(*parypt); + } + for (i = 0; i < botpoints->objects; i++) { + parypt = (point *) fastlookup(botpoints, i); + punmarktest3(*parypt); + } + + f23count = flip23count - f23count; + f32count = flip32count - f32count; + f44count = flip44count - f44count; + totalfcount = f23count + f32count + f44count; + if (b->verbose > 2) { + printf(" Total %ld flips. f23(%ld), f32(%ld), f44(%ld).\n", + totalfcount, f23count, f32count, f44count); + } +} + +//============================================================================// +// // +// insertpoint_cdt() Insert a new point into a CDT. // +// // +//============================================================================// + +int tetgenmesh::insertpoint_cdt(point newpt, triface *searchtet, face *splitsh, + face *splitseg, insertvertexflags *ivf, + arraypool *cavpoints, arraypool *cavfaces, + arraypool *cavshells, arraypool *newtets, + arraypool *crosstets, arraypool *misfaces) +{ + triface neightet, *parytet; + face checksh, *parysh, *parysh1; + face *paryseg, *paryseg1; + point *parypt; + int t1ver; + int i; + + if (b->verbose > 2) { + printf(" Insert point %d into CDT\n", pointmark(newpt)); + } + + if (!insertpoint(newpt, searchtet, NULL, NULL, ivf)) { + // Point is not inserted. Check ivf->iloc for reason. + return 0; + } + + + for (i = 0; i < cavetetvertlist->objects; i++) { + cavpoints->newindex((void **) &parypt); + *parypt = * (point *) fastlookup(cavetetvertlist, i); + } + // Add the new point into the point list. + cavpoints->newindex((void **) &parypt); + *parypt = newpt; + + for (i = 0; i < cavebdrylist->objects; i++) { + cavfaces->newindex((void **) &parytet); + *parytet = * (triface *) fastlookup(cavebdrylist, i); + } + + for (i = 0; i < caveoldtetlist->objects; i++) { + crosstets->newindex((void **) &parytet); + *parytet = * (triface *) fastlookup(caveoldtetlist, i); + } + + cavetetvertlist->restart(); + cavebdrylist->restart(); + caveoldtetlist->restart(); + + // Insert the point using the cavity algorithm. + delaunizecavity(cavpoints, cavfaces, cavshells, newtets, crosstets, + misfaces); + fillcavity(cavshells, NULL, NULL, NULL, NULL, NULL, NULL); + carvecavity(crosstets, newtets, NULL); + + if ((splitsh != NULL) || (splitseg != NULL)) { + // Insert the point into the surface mesh. + sinsertvertex(newpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat, 0); + + // Put all new subfaces into stack. + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // The new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + + if (splitseg != NULL) { + // Queue two new subsegments in C(p) for recovery. + for (i = 0; i < cavesegshlist->objects; i++) { + paryseg = (face *) fastlookup(cavesegshlist, i); + subsegstack->newindex((void **) &paryseg1); + *paryseg1 = *paryseg; + } + } // if (splitseg != NULL) + + // Delete the old subfaces in sC(p). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + if (checksubfaceflag) { + // It is possible that this subface still connects to adjacent + // tets which are not in C(p). If so, clear connections in the + // adjacent tets at this subface. + stpivot(*parysh, neightet); + if (neightet.tet != NULL) { + if (neightet.tet[4] != NULL) { + // Found an adjacent tet. It must be not in C(p). + tsdissolve(neightet); + fsymself(neightet); + tsdissolve(neightet); + } + } + } + shellfacedealloc(subfaces, parysh->sh); + } + if (splitseg != NULL) { + // Delete the old segment in sC(p). + shellfacedealloc(subsegs, splitseg->sh); + } + + // Clear working lists. + caveshlist->restart(); + caveshbdlist->restart(); + cavesegshlist->restart(); + } // if ((splitsh != NULL) || (splitseg != NULL)) + + // Put all interior subfaces into stack for recovery. + // They were collected in carvecavity(). + // Note: Some collected subfaces may be deleted by sinsertvertex(). + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + if (parysh->sh[3] != NULL) { + subfacstack->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + } + + // Put all interior segments into stack for recovery. + // They were collected in carvecavity(). + // Note: Some collected segments may be deleted by sinsertvertex(). + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + if (paryseg->sh[3] != NULL) { + subsegstack->newindex((void **) &paryseg1); + *paryseg1 = *paryseg; + } + } + + caveencshlist->restart(); + caveencseglist->restart(); + + return 1; +} + +//============================================================================// +// // +// refineregion() Refine a missing region by inserting points. // +// // +// 'splitsh' represents an edge of the facet to be split. It must not be a // +// segment. // +// // +// Assumption: The current mesh is a CDT and is convex. // +// // +//============================================================================// + +void tetgenmesh::refineregion(face &splitsh, arraypool *cavpoints, + arraypool *cavfaces, arraypool *cavshells, + arraypool *newtets, arraypool *crosstets, + arraypool *misfaces) +{ + triface searchtet, spintet; + face splitseg, *paryseg; + point steinpt, pa, pb, refpt; + insertvertexflags ivf; + enum interresult dir; + long baknum = points->items; + int t1ver; + int i; + + // Do not split a segment. + for (i = 0; i < 3; i++) { + sspivot(splitsh, splitseg); + if (splitseg.sh == NULL) break; + senextself(splitsh); + } + + if (b->verbose > 2) { + printf(" Refining region at edge (%d, %d, %d).\n", + pointmark(sorg(splitsh)), pointmark(sdest(splitsh)), + pointmark(sapex(splitsh))); + } + + // Add the Steiner point at the barycenter of the face. + pa = sorg(splitsh); + pb = sdest(splitsh); + // Create a new point. + makepoint(&steinpt, FREEFACETVERTEX); + for (i = 0; i < 3; i++) { + steinpt[i] = 0.5 * (pa[i] + pb[i]); + } + + ivf.bowywat = 1; // Use the Bowyer-Watson algorrithm. + ivf.cdtflag = 1; // Only create the initial cavity. + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; + ivf.assignmeshsize = b->metric; + ivf.smlenflag = useinsertradius; // Return the closet mesh vertex. + + point2tetorg(pa, searchtet); // Start location from it. + ivf.iloc = (int) OUTSIDE; + + ivf.rejflag = 1; // Reject it if it encroaches upon any segment. + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, NULL, &ivf, cavpoints, + cavfaces, cavshells, newtets, crosstets, misfaces)) { + if (ivf.iloc == (int) ENCSEGMENT) { + pointdealloc(steinpt); + // Split an encroached segment. + i = randomnation(encseglist->objects); + paryseg = (face *) fastlookup(encseglist, i); + splitseg = *paryseg; + encseglist->restart(); + + // Split the segment. + pa = sorg(splitseg); + pb = sdest(splitseg); + // Create a new point. + makepoint(&steinpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) { + steinpt[i] = 0.5 * (pa[i] + pb[i]); + } + point2tetorg(pa, searchtet); + ivf.iloc = (int) OUTSIDE; + ivf.rejflag = 0; + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, &splitseg, &ivf, + cavpoints, cavfaces, cavshells, newtets, + crosstets, misfaces)) { + terminatetetgen(this, 2); + } + if (useinsertradius) { + //save_segmentpoint_insradius(steinpt, ivf.parentpt, ivf.smlen); + } + st_segref_count++; + if (steinerleft > 0) steinerleft--; + } else { + terminatetetgen(this, 2); // assert(0); + } + } else { + if (useinsertradius) { + //save_facetpoint_insradius(steinpt, ivf.parentpt, ivf.smlen); + } + st_facref_count++; + if (steinerleft > 0) steinerleft--; + } + + while (subsegstack->objects > 0l) { + // seglist is used as a stack. + subsegstack->objects--; + paryseg = (face *) fastlookup(subsegstack, subsegstack->objects); + splitseg = *paryseg; + + // Check if this segment has been recovered. + sstpivot1(splitseg, searchtet); + if (searchtet.tet != NULL) continue; + + // Search the segment. + dir = scoutsegment(sorg(splitseg), sdest(splitseg), &splitseg, &searchtet, + &refpt, NULL); + if (dir == SHAREEDGE) { + // Found this segment, insert it. + // Let the segment remember an adjacent tet. + sstbond1(splitseg, searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, splitseg); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + } else { + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + // Split the segment. + makepoint(&steinpt, FREESEGVERTEX); + getsteinerptonsegment(&splitseg, refpt, steinpt); + ivf.iloc = (int) OUTSIDE; + ivf.rejflag = 0; + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, &splitseg, &ivf, + cavpoints, cavfaces, cavshells, newtets, + crosstets, misfaces)) { + terminatetetgen(this, 2); + } + if (useinsertradius) { + //save_segmentpoint_insradius(steinpt, ivf.parentpt, ivf.smlen); + } + st_segref_count++; + if (steinerleft > 0) steinerleft--; + } else { + terminatetetgen(this, 2); + } + } + } // while + + if (b->verbose > 2) { + printf(" Added %ld Steiner points.\n", points->items - baknum); + } +} + +//============================================================================// +// // +// constrainedfacets() Recover constrained facets in a CDT. // +// // +// All unrecovered subfaces are queued in 'subfacestack'. // +// // +//============================================================================// + +void tetgenmesh::constrainedfacets() +{ + arraypool *tg_crosstets, *tg_topnewtets, *tg_botnewtets; + arraypool *tg_topfaces, *tg_botfaces, *tg_midfaces; + arraypool *tg_topshells, *tg_botshells, *tg_facfaces; + arraypool *tg_toppoints, *tg_botpoints; + arraypool *tg_missingshs, *tg_missingshbds, *tg_missingshverts; + triface searchtet, neightet, crossedge; + face searchsh, *parysh, *parysh1; + face *paryseg; + point *parypt; + enum interresult dir; + int facetcount; + int success; + int t1ver; + int i, j; + + // Initialize arrays. + tg_crosstets = new arraypool(sizeof(triface), 10); + tg_topnewtets = new arraypool(sizeof(triface), 10); + tg_botnewtets = new arraypool(sizeof(triface), 10); + tg_topfaces = new arraypool(sizeof(triface), 10); + tg_botfaces = new arraypool(sizeof(triface), 10); + tg_midfaces = new arraypool(sizeof(triface), 10); + tg_toppoints = new arraypool(sizeof(point), 8); + tg_botpoints = new arraypool(sizeof(point), 8); + tg_facfaces = new arraypool(sizeof(face), 10); + tg_topshells = new arraypool(sizeof(face), 10); + tg_botshells = new arraypool(sizeof(face), 10); + tg_missingshs = new arraypool(sizeof(face), 10); + tg_missingshbds = new arraypool(sizeof(face), 10); + tg_missingshverts = new arraypool(sizeof(point), 8); + // This is a global array used by refineregion(). + encseglist = new arraypool(sizeof(face), 4); + + facetcount = 0; + + while (subfacstack->objects > 0l) { + + subfacstack->objects--; + parysh = (face *) fastlookup(subfacstack, subfacstack->objects); + searchsh = *parysh; + + if (searchsh.sh[3] == NULL) continue; // It is dead. + if (isshtet(searchsh)) continue; // It is recovered. + + // Collect all unrecovered subfaces which are co-facet. + smarktest(searchsh); + tg_facfaces->newindex((void **) &parysh); + *parysh = searchsh; + for (i = 0; i < tg_facfaces->objects; i++) { + parysh = (face *) fastlookup(tg_facfaces, i); + for (j = 0; j < 3; j++) { + if (!isshsubseg(*parysh)) { + spivot(*parysh, searchsh); + if (!smarktested(searchsh)) { + if (!isshtet(searchsh)) { + smarktest(searchsh); + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = searchsh; + } + } + } + senextself(*parysh); + } // j + } // i + // Have found all facet subfaces. Unmark them. + for (i = 0; i < tg_facfaces->objects; i++) { + parysh = (face *) fastlookup(tg_facfaces, i); + sunmarktest(*parysh); + } + + + if (b->verbose > 1) { + printf(" Recovering facet #%d: %ld subfaces.\n", facetcount + 1, + tg_facfaces->objects); + } + facetcount++; + + while (tg_facfaces->objects > 0l) { + + tg_facfaces->objects--; + parysh = (face *) fastlookup(tg_facfaces, tg_facfaces->objects); + searchsh = *parysh; + + if (searchsh.sh[3] == NULL) continue; // It is dead. + if (isshtet(searchsh)) continue; // It is recovered. + + searchtet.tet = NULL; + if (scoutsubface(&searchsh, &searchtet, 1)) continue; + + // The subface is missing. Form the missing region. + // Re-use 'tg_crosstets' for 'adjtets'. + formregion(&searchsh, tg_missingshs, tg_missingshbds, tg_missingshverts); + + int searchflag = scoutcrossedge(searchtet, tg_missingshbds, tg_missingshs); + if (searchflag > 0) { + // Save this crossing edge, will be used by fillcavity(). + crossedge = searchtet; + // Form a cavity of crossing tets. + success = formcavity(&searchtet, tg_missingshs, tg_crosstets, + tg_topfaces, tg_botfaces, tg_toppoints, + tg_botpoints); + if (success) { + if (!b->flipinsert) { + // Tetrahedralize the top part. Re-use 'tg_midfaces'. + delaunizecavity(tg_toppoints, tg_topfaces, tg_topshells, + tg_topnewtets, tg_crosstets, tg_midfaces); + // Tetrahedralize the bottom part. Re-use 'tg_midfaces'. + delaunizecavity(tg_botpoints, tg_botfaces, tg_botshells, + tg_botnewtets, tg_crosstets, tg_midfaces); + // Fill the cavity with new tets. + success = fillcavity(tg_topshells, tg_botshells, tg_midfaces, + tg_missingshs, tg_topnewtets, tg_botnewtets, + &crossedge); + if (success) { + // Cavity is remeshed. Delete old tets and outer new tets. + carvecavity(tg_crosstets, tg_topnewtets, tg_botnewtets); + } else { + restorecavity(tg_crosstets, tg_topnewtets, tg_botnewtets, + tg_missingshbds); + } + } else { + // Use the flip algorithm of Shewchuk to recover the subfaces. + flipinsertfacet(tg_crosstets, tg_toppoints, tg_botpoints, + tg_missingshverts); + // Put all subfaces in R back to tg_facfaces. + for (i = 0; i < tg_missingshs->objects; i++) { + parysh = (face *) fastlookup(tg_missingshs, i); + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + success = 1; + // Clear working lists. + tg_crosstets->restart(); + tg_topfaces->restart(); + tg_botfaces->restart(); + tg_toppoints->restart(); + tg_botpoints->restart(); + } // b->flipinsert + + if (success) { + // Recover interior subfaces. + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + if (!scoutsubface(parysh, &searchtet, 1)) { + // Add this face at the end of the list, so it will be + // processed immediately. + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + } + caveencshlist->restart(); + // Recover interior segments. This should always be recovered. + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + dir = scoutsegment(sorg(*paryseg), sdest(*paryseg), paryseg, + &searchtet, NULL, NULL); + if (dir != SHAREEDGE) { + terminatetetgen(this, 2); + } + // Insert this segment. + // Let the segment remember an adjacent tet. + sstbond1(*paryseg, searchtet); + // Bond the segment to all tets containing it. + neightet = searchtet; + do { + tssbond1(neightet, *paryseg); + fnextself(neightet); + } while (neightet.tet != searchtet.tet); + } + caveencseglist->restart(); + } // success - remesh cavity + } // success - form cavity + else { + terminatetetgen(this, 2); // Report a bug. + } // Not success - form cavity + } else { + // Put all subfaces in R back to tg_facfaces. + for (i = 0; i < tg_missingshs->objects; i++) { + parysh = (face *) fastlookup(tg_missingshs, i); + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + if (searchflag != -1) { + // Some edge(s) in the missing regions were flipped. + success = 1; + } else { + restorecavity(tg_crosstets, tg_topnewtets, tg_botnewtets, + tg_missingshbds); // Only remove fake segments. + // Choose an edge to split (set in recentsh) + recentsh = searchsh; + success = 0; // Do refineregion(); + } + } // if (scoutcrossedge) + + // Unmarktest all points of the missing region. + for (i = 0; i < tg_missingshverts->objects; i++) { + parypt = (point *) fastlookup(tg_missingshverts, i); + punmarktest(*parypt); + } + tg_missingshverts->restart(); + tg_missingshbds->restart(); + tg_missingshs->restart(); + + if (!success) { + // The missing region can not be recovered. Refine it. + refineregion(recentsh, tg_toppoints, tg_topfaces, tg_topshells, + tg_topnewtets, tg_crosstets, tg_midfaces); + } + } // while (tg_facfaces->objects) + + } // while ((subfacstack->objects) + + // Accumulate the dynamic memory. + totalworkmemory += (tg_crosstets->totalmemory + tg_topnewtets->totalmemory + + tg_botnewtets->totalmemory + tg_topfaces->totalmemory + + tg_botfaces->totalmemory + tg_midfaces->totalmemory + + tg_toppoints->totalmemory + tg_botpoints->totalmemory + + tg_facfaces->totalmemory + tg_topshells->totalmemory + + tg_botshells->totalmemory + tg_missingshs->totalmemory + + tg_missingshbds->totalmemory + + tg_missingshverts->totalmemory + + encseglist->totalmemory); + + // Delete arrays. + delete tg_crosstets; + delete tg_topnewtets; + delete tg_botnewtets; + delete tg_topfaces; + delete tg_botfaces; + delete tg_midfaces; + delete tg_toppoints; + delete tg_botpoints; + delete tg_facfaces; + delete tg_topshells; + delete tg_botshells; + delete tg_missingshs; + delete tg_missingshbds; + delete tg_missingshverts; + delete encseglist; + encseglist = NULL; +} + +//============================================================================// +// // +// constraineddelaunay() Create a constrained Delaunay tetrahedralization. // +// // +//============================================================================// + +void tetgenmesh::constraineddelaunay(clock_t& tv) +{ + face searchsh, *parysh; + face searchseg, *paryseg; + int s, i; + + // Statistics. + long bakfillregioncount; + long bakcavitycount, bakcavityexpcount; + long bakseg_ref_count; + + if (!b->quiet) { + printf("Constrained Delaunay...\n"); + } + + makesegmentendpointsmap(); + makefacetverticesmap(); + + if (b->verbose) { + printf(" Delaunizing segments.\n"); + } + + checksubsegflag = 1; + + // Put all segments into the list (in random order). + subsegs->traversalinit(); + for (i = 0; i < subsegs->items; i++) { + s = randomnation(i + 1); + // Move the s-th seg to the i-th. + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + // Put i-th seg to be the s-th. + searchseg.sh = shellfacetraverse(subsegs); + //sinfect(searchseg); // Only save it once. + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = searchseg; + } + + // Recover non-Delaunay segments. + delaunizesegments(); + + if (b->verbose) { + printf(" Inserted %ld Steiner points.\n", st_segref_count); + } + + tv = clock(); + + if (b->verbose) { + printf(" Constraining facets.\n"); + } + + // Subfaces will be introduced. + checksubfaceflag = 1; + + bakfillregioncount = fillregioncount; + bakcavitycount = cavitycount; + bakcavityexpcount = cavityexpcount; + bakseg_ref_count = st_segref_count; + + // Randomly order the subfaces. + subfaces->traversalinit(); + for (i = 0; i < subfaces->items; i++) { + s = randomnation(i + 1); + // Move the s-th subface to the i-th. + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(subfacstack, s); + // Put i-th subface to be the s-th. + searchsh.sh = shellfacetraverse(subfaces); + parysh = (face *) fastlookup(subfacstack, s); + *parysh = searchsh; + } + + // Recover facets. + constrainedfacets(); + + if (b->verbose) { + if (fillregioncount > bakfillregioncount) { + printf(" Remeshed %ld regions.\n", fillregioncount-bakfillregioncount); + } + if (cavitycount > bakcavitycount) { + printf(" Remeshed %ld cavities", cavitycount - bakcavitycount); + if (cavityexpcount - bakcavityexpcount) { + printf(" (%ld enlarged)", cavityexpcount - bakcavityexpcount); + } + printf(".\n"); + } + if (st_segref_count + st_facref_count - bakseg_ref_count > 0) { + printf(" Inserted %ld (%ld, %ld) refine points.\n", + st_segref_count + st_facref_count - bakseg_ref_count, + st_segref_count - bakseg_ref_count, st_facref_count); + } + } +} + +// // +// // +//== constrained_cxx =========================================================// + +//== steiner_cxx =============================================================// +// // +// // + +void tetgenmesh::sort_2pts(point p1, point p2, point ppt[2]) +{ + if (pointmark(p1) < pointmark(p2)) { + ppt[0] = p1; + ppt[1] = p2; + } else { + ppt[0] = p2; + ppt[1] = p1; + } +} + +void tetgenmesh::sort_3pts(point p1, point p2, point p3, point ppt[3]) +{ + int i1 = pointmark(p1); + int i2 = pointmark(p2); + int i3 = pointmark(p3); + + if (i1 < i2) { + if (i1 < i3) { + ppt[0] = p1; + if (i2 < i3) { + ppt[1] = p2; + ppt[2] = p3; + } else { + ppt[1] = p3; + ppt[2] = p2; + } + } else { + ppt[0] = p3; + ppt[1] = p1; + ppt[2] = p2; + } + } else { // i1 > i2 + if (i2 < i3) { + ppt[0] = p2; + if (i1 < i3) { + ppt[1] = p1; + ppt[2] = p3; + } else { + ppt[1] = p3; + ppt[2] = p1; + } + } else { + ppt[0] = p3; + ppt[1] = p2; + ppt[2] = p1; + } + } +} + + +//============================================================================// +// // +// is_collinear_at() Check if three vertices (from left to right): left, // +// mid, and right are collinear. // +// // +//============================================================================// + +bool tetgenmesh::is_collinear_at(point mid, point left, point right) +{ + REAL v1[3], v2[3]; + + v1[0] = left[0] - mid[0]; + v1[1] = left[1] - mid[1]; + v1[2] = left[2] - mid[2]; + + v2[0] = right[0] - mid[0]; + v2[1] = right[1] - mid[1]; + v2[2] = right[2] - mid[2]; + + REAL L1 = sqrt(v1[0]*v1[0]+v1[1]*v1[1]+v1[2]*v1[2]); + REAL L2 = sqrt(v2[0]*v2[0]+v2[1]*v2[1]+v2[2]*v2[2]); + REAL D = (v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]); + + REAL cos_ang = D / (L1 * L2); + return cos_ang < cos_collinear_ang_tol; +} + +//============================================================================// +// // +// is_segment() Check if the two vertices are endpoints of a segment. // +// // +//============================================================================// + +bool tetgenmesh::is_segment(point p1, point p2) +{ + if (pointtype(p1) == RIDGEVERTEX) { + if (pointtype(p2) == RIDGEVERTEX) { + // Check if p2 is connect to p1. + int idx = pointmark(p1); + for (int i = idx_segment_ridge_vertex_list[idx]; + i < idx_segment_ridge_vertex_list[idx+1]; i++) { + if (segment_ridge_vertex_list[i] == p2) { + return true; + } + } + } else if (pointtype(p2) == FREESEGVERTEX) { + // Check if the segment contains p2 has one if its endpoints be p1. + face parsentseg; + sdecode(point2sh(p2), parsentseg); + int segidx = getfacetindex(parsentseg); + if ((segmentendpointslist[segidx*2] == p1) || + (segmentendpointslist[segidx*2+1] == p1)) { + return true; + } + } + } else { + if (pointtype(p1) == FREESEGVERTEX) { + if (pointtype(p2) == RIDGEVERTEX) { + face parsentseg; + sdecode(point2sh(p1), parsentseg); + int segidx = getfacetindex(parsentseg); + if ((segmentendpointslist[segidx*2] == p2) || + (segmentendpointslist[segidx*2+1] == p2)) { + return true; + } + } else if (pointtype(p2) == FREESEGVERTEX) { + face parsentseg1, parsentseg2; + sdecode(point2sh(p1), parsentseg1); + sdecode(point2sh(p2), parsentseg2); + int segidx1 = getfacetindex(parsentseg1); + int segidx2 = getfacetindex(parsentseg2); + if (segidx1 == segidx2) { + return true; + } + } + } + } + + return false; +} + +//============================================================================// +// // +// valid_constrained_f23() Validate a 2-3 flip. // +// // +// The purpose of the following check is to avoid creating a degenrated face // +// (and subface) whose three vertices are nearly on one segment or on two // +// nearly collinear segments. // +// // +// "checktet" is a face (a,b,c) which is 2-3 flippable, and (d,e) will be // +// the new edge after this flip. // +// // +// return true if this 2-3 flip is good, otherwise, return false. // +// // +//============================================================================// + +bool tetgenmesh::valid_constrained_f23(triface& checktet, point pd, point pe) +{ + bool validflag = true; + + triface spintet; + face checkseg1, checkseg2; + point checkpt; + + for (int k = 0; k < 3; k++) { + checkpt = org(checktet); + esym(checktet, spintet); + enextself(spintet); // [x, d], x = a,b,c + tsspivot1(spintet, checkseg1); + bool isseg = (checkseg1.sh != NULL); + if (!isseg && boundary_recovery_flag) { + isseg = is_segment(checkpt, pd); + } + if (isseg) { + fsym(checktet, spintet); + esymself(spintet); + eprevself(spintet); + tsspivot1(spintet, checkseg2); + isseg = (checkseg2.sh != NULL); + if (!isseg && boundary_recovery_flag) { + isseg = is_segment(checkpt, pe); + } + if (isseg) { + if (pointtype(checkpt) == FREESEGVERTEX) { + // In this case, the two subsegments (checkseg1, checkseg2) + // must belong to the same segment, do not flip. + validflag = false; + break; + } else { + // Check if three vertices are nearly collinear. The middle + // vertex is checkpt. + if ((checkpt != dummypoint) && + (pe != dummypoint) && + (pd != dummypoint)) { + if (is_collinear_at(checkpt, pe, pd)) { + validflag = false; + break; + } + } + } + } // if (isseg) + } // if (isseg) + enextself(checktet); + } // k + + return validflag; +} + +//============================================================================// +// // +// valid_constrained_f32() Validate a 3-2 flip. // +// // +// Avoid creating a degenerated tetrahedral face whose three vertices are on // +// one (sub)segment. abtets[0], abdtets[1], abtets[2] are three tets // +// at the flipping edge (a,b), the new face will be (c, d, e). // +// The only new face we will create is (c,d,e), make sure that it is not // +// a (nearly) degenerated face. If the vertex c is RIDGEVEETEX or // +// FREESEGVERTEX, then the edges (c, d) and (c, e) should not on one segment.// +// The same for the vertex d and e. // +// // +// return true if this 3-2 flip is good, otherwise, return false. // +// // +//============================================================================// + +bool tetgenmesh::valid_constrained_f32(triface* abtets, point pa, point pb) +{ + bool validflag = true; // default. + + triface spintet; + face checksegs[3]; // edges: [c,d], [d,e], and [e,c] + point chkpt, leftpt, rightpt; + + // Check edges [c,d], [d,e], and [e,c] + for (int k = 0; k < 3; k++) { // [a,b,c], [a,b,d], [a,b,e] + enext(abtets[k], spintet); + esymself(spintet); + eprevself(spintet); // [c,d], [d,e], and [e,c] + tsspivot1(spintet, checksegs[k]); + // Ignore a temporaray segment (used in recoversubfaces()). + if (checksegs[k].sh != NULL) { + if (smarktest2ed(checksegs[k])) { + checksegs[k].sh = NULL; + } + } + } // k + + for (int k = 0; k < 3; k++) { + chkpt = apex(abtets[k]); // pc + leftpt = apex(abtets[(k+2)%3]); // pe + rightpt = apex(abtets[(k+1)%3]); // pd + bool isseg = (checksegs[k].sh != NULL); // [c,d] + if (!isseg && boundary_recovery_flag) { + isseg = is_segment(chkpt, rightpt); + } + if (isseg) { + isseg = (checksegs[(k+2)%3].sh != NULL); // [e,c] + if (!isseg && boundary_recovery_flag) { + isseg = is_segment(chkpt, leftpt); + } + if (isseg) { + if (pointtype(chkpt) == FREESEGVERTEX) { + validflag = false; + break; + } else { + if ((chkpt != dummypoint) && + (leftpt != dummypoint) && + (rightpt != dummypoint)) { + if (is_collinear_at(chkpt, leftpt, rightpt)) { + validflag = false; + break; + } + } + } + } + } + } // k + + return validflag; +} + +//============================================================================// +// // +// checkflipeligibility() A call back function for boundary recovery. // +// // +// 'fliptype' indicates which elementary flip will be performed: 1 : 2-to-3, // +// and 2 : 3-to-2, respectively. // +// // +// 'pa, ..., pe' are the vertices involved in this flip, where [a,b,c] is // +// the flip face, and [d,e] is the flip edge. NOTE: 'pc' may be 'dummypoint', // +// other points must not be 'dummypoint'. // +// // +//============================================================================// + +int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, + point pc, point pd, point pe, + int level, int edgepivot, + flipconstraints* fc) +{ + point tmppts[3]; + enum interresult dir; + int types[2], poss[4]; + int intflag; + int rejflag = 0; + int i; + + if (fc->seg[0] != NULL) { + // A constraining edge is given (e.g., for edge recovery). + if (fliptype == 1) { + // A 2-to-3 flip: [a,b,c] => [e,d,a], [e,d,b], [e,d,c]. + tmppts[0] = pa; + tmppts[1] = pb; + tmppts[2] = pc; + for (i = 0; i < 3 && !rejflag; i++) { + if (tmppts[i] != dummypoint) { + // Test if the face [e,d,#] intersects the edge. + intflag = tri_edge_test(pe, pd, tmppts[i], fc->seg[0], fc->seg[1], + NULL, 1, types, poss); + if (intflag == 2) { + // They intersect at a single point. + dir = (enum interresult) types[0]; + if (dir == ACROSSFACE) { + // The interior of [e,d,#] intersect the segment. + rejflag = 1; + } else if (dir == ACROSSEDGE) { + if (poss[0] == 0) { + // The interior of [e,d] intersect the segment. + // Since [e,d] is the newly created edge. Reject this flip. + rejflag = 1; + } + } + else { + if ((dir == ACROSSVERT) || (dir == TOUCHEDGE) || + (dir == TOUCHFACE)) { + // should be a self-intersection. + rejflag = 1; + } + } // dir + } else if (intflag == 4) { + // They may intersect at either a point or a line segment. + dir = (enum interresult) types[0]; + if (dir == ACROSSEDGE) { + if (poss[0] == 0) { + // The interior of [e,d] intersect the segment. + // Since [e,d] is the newly created edge. Reject this flip. + rejflag = 1; + } + } + else if (dir == ACROSSFACE) { + //assert(0); // This should be not possible. + terminatetetgen(this, 2); + } + else { + if ((dir == ACROSSVERT) || (dir == TOUCHEDGE) || + (dir == TOUCHFACE)) { + // This should be caused by a self-intersection. + rejflag = 1; // Do not flip. + } + } + } + } // if (tmppts[0] != dummypoint) + } // i + } else if (fliptype == 2) { + // A 3-to-2 flip: [e,d,a], [e,d,b], [e,d,c] => [a,b,c] + if (pc != dummypoint) { + // Check if the new face [a,b,c] intersect the edge in its interior. + intflag = tri_edge_test(pa, pb, pc, fc->seg[0], fc->seg[1], NULL, + 1, types, poss); + if (intflag == 2) { + // They intersect at a single point. + dir = (enum interresult) types[0]; + if (dir == ACROSSFACE) { + // The interior of [a,b,c] intersect the segment. + rejflag = 1; // Do not flip. + } + } else if (intflag == 4) { + // [a,b,c] is coplanar with the edge. + dir = (enum interresult) types[0]; + if (dir == ACROSSEDGE) { + // The boundary of [a,b,c] intersect the segment. + rejflag = 1; // Do not flip. + } + } + } // if (pc != dummypoint) + } + } // if (fc->seg[0] != NULL) + + if ((fc->fac[0] != NULL) && !rejflag) { + // A constraining face is given (e.g., for face recovery). + if (fliptype == 1) { + // A 2-to-3 flip. + // Test if the new edge [e,d] intersects the face. + intflag = tri_edge_test(fc->fac[0], fc->fac[1], fc->fac[2], pe, pd, + NULL, 1, types, poss); + if (intflag == 2) { + // They intersect at a single point. + dir = (enum interresult) types[0]; + if (dir == ACROSSFACE) { + rejflag = 1; + } else if (dir == ACROSSEDGE) { + rejflag = 1; + } + } else if (intflag == 4) { + // The edge [e,d] is coplanar with the face. + // There may be two intersections. + for (i = 0; i < 2 && !rejflag; i++) { + dir = (enum interresult) types[i]; + if (dir == ACROSSFACE) { + rejflag = 1; + } else if (dir == ACROSSEDGE) { + rejflag = 1; + } + } + } + } // if (fliptype == 1) + } // if (fc->fac[0] != NULL) + + if ((fc->remvert != NULL) && !rejflag) { + // The vertex is going to be removed. Do not create a new edge which + // contains this vertex. + if (fliptype == 1) { + // A 2-to-3 flip. + if ((pd == fc->remvert) || (pe == fc->remvert)) { + rejflag = 1; + } + } + } + + if (fc->remove_large_angle && !rejflag) { + // Remove a large dihedral angle. Do not create a new small angle. + badface bf; // used by get_tetqual(...) + REAL cosmaxd = 0, diff; + if (fliptype == 1) { + // We assume that neither 'a' nor 'b' is dummypoint. + // A 2-to-3 flip: [a,b,c] => [e,d,a], [e,d,b], [e,d,c]. + // The new tet [e,d,a,b] will be flipped later. Only two new tets: + // [e,d,b,c] and [e,d,c,a] need to be checked. + if ((pc != dummypoint) && (pe != dummypoint) && (pd != dummypoint)) { + REAL min_cosmaxd = 1.0, max_asp = 0; // record the worst quality. + // Get the largest dihedral angle of [e,d,b,c]. + if (get_tetqual(pe, pd, pb, pc, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); + max_asp = (max_asp > bf.key ? max_asp : bf.key); + // Get the largest dihedral angle of [e,d,c,a]. + if (get_tetqual(pe, pd, pc, pa, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); + max_asp = (max_asp > bf.key ? max_asp : bf.key); + // save the worst quality. + fc->cosdihed_out = (fc->cosdihed_out < min_cosmaxd ? fc->cosdihed_out : min_cosmaxd); + fc->max_asp_out = (fc->max_asp_out > max_asp ? fc->max_asp_out : max_asp); + } + } + } // if (pc != dummypoint && ...) + } else if (fliptype == 2) { + // A 3-to-2 flip: [e,d,a], [e,d,b], [e,d,c] => [a,b,c] + // We assume that neither 'e' nor 'd' is dummypoint. + if (level == 0) { + // Both new tets [a,b,c,d] and [b,a,c,e] are new tets. + if ((pa != dummypoint) && (pb != dummypoint) && (pc != dummypoint)) { + REAL min_cosmaxd = 1.0, max_asp = 0; // record the worst quality. + // Get the largest dihedral angle of [a,b,c,d]. + if (get_tetqual(pa, pb, pc, pd, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); + max_asp = (max_asp > bf.key ? max_asp : bf.key); + // Get the largest dihedral angle of [b,a,c,e]. + if (get_tetqual(pb, pa, pc, pe, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + min_cosmaxd = (min_cosmaxd < cosmaxd ? min_cosmaxd : cosmaxd); + max_asp = (max_asp > bf.key ? max_asp : bf.key); + // save the worst quality. + fc->cosdihed_out = (fc->cosdihed_out < min_cosmaxd ? fc->cosdihed_out : min_cosmaxd); + fc->max_asp_out = (fc->max_asp_out > max_asp ? fc->max_asp_out : max_asp); + } + } + } + } else { // level > 0 + if (edgepivot == 1) { + // The new tet [a,b,c,d] will be flipped. Only check [b,a,c,e]. + if ((pa != dummypoint) && (pb != dummypoint) && (pc != dummypoint)) { + // Get the largest dihedral angle of [b,a,c,e]. + if (get_tetqual(pb, pa, pc, pe, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + // save the worst quality. + fc->cosdihed_out = (fc->cosdihed_out < cosmaxd ? fc->cosdihed_out : cosmaxd); + fc->max_asp_out = (fc->max_asp_out > bf.key ? fc->max_asp_out : bf.key); + } + } + } else { + // The new tet [b,a,c,e] will be flipped. Only check [a,b,c,d]. + if ((pa != dummypoint) && (pb != dummypoint) && (pc != dummypoint)) { + // Get the largest dihedral angle of [b,a,c,e]. + if (get_tetqual(pa, pb, pc, pd, &bf)) { + cosmaxd = bf.cent[0]; + diff = cosmaxd - fc->cosdihed_in; + if (fabs(diff/fc->cosdihed_in) < b->epsilon) diff = 0.0; // Rounding. + } else { + diff = 0.0; // no improve. + } + if (diff <= 0) { //if (cosmaxd <= fc->cosdihed_in) { + rejflag = 1; + } else { + // Record the largest new angle. + // save the worst quality. + fc->cosdihed_out = (fc->cosdihed_out < cosmaxd ? fc->cosdihed_out : cosmaxd); + fc->max_asp_out = (fc->max_asp_out > bf.key ? fc->max_asp_out : bf.key); + } + } + } // edgepivot + } // level + } + } // if (fc->remove_large_angle && !rejflag) + + return rejflag; +} + +//============================================================================// +// // +// removeedgebyflips() Attempt to remove an edge by flips. // +// // +// 'flipedge' is a non-convex or flat edge [a,b,#,#] to be removed. // +// // +// The return value is a positive integer, it indicates whether the edge is // +// removed or not. A value "2" means the edge is removed, otherwise, the // +// edge is not removed and the value (must >= 3) is the current number of // +// tets in the edge star. // +// // +//============================================================================// + +int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) +{ + triface *abtets, spintet; + int t1ver; + int n, nn, i; + + + if (checksubsegflag) { + // Do not flip a segment. + if (issubseg(*flipedge)) { + if (fc->collectencsegflag) { + face checkseg, *paryseg; + tsspivot1(*flipedge, checkseg); + if (!sinfected(checkseg)) { + // Queue this segment in list. + sinfect(checkseg); + caveencseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } + return 0; + } + } + + // Count the number of tets at edge [a,b]. + int subface_count = 0; // count the # of subfaces at this edge. + n = 0; + spintet = *flipedge; + while (1) { + if (issubface(spintet)) subface_count++; + n++; + fnextself(spintet); + if (spintet.tet == flipedge->tet) break; + } + if (n < 3) { + // It is only possible when the mesh contains inverted tetrahedra. + terminatetetgen(this, 2); // Report a bug + } + + if (fc->noflip_in_surface) { + if (subface_count > 0) { + return 0; + } + } + + //if ((b->flipstarsize > 0) && (n > (b->flipstarsize+4))) { + if ((b->flipstarsize > 0) && (n > b->flipstarsize)) { + // The star size exceeds the limit. + return 0; // Do not flip it. + } + + // Allocate spaces. + abtets = new triface[n]; + // Collect the tets at edge [a,b]. + spintet = *flipedge; + for (i = 0; i < n; i++) { + abtets[i] = spintet; + setelemcounter(abtets[i], 1); + fnextself(spintet); + } + + + // Try to flip the edge (level = 0, edgepivot = 0). + nn = flipnm(abtets, n, 0, 0, fc); + + + if (nn > 2) { + // Edge is not flipped. Unmarktest the remaining tets in Star(ab). + for (i = 0; i < nn; i++) { + setelemcounter(abtets[i], 0); + } + // Restore the input edge (needed by Lawson's flip). + *flipedge = abtets[0]; + } + + // Release the temporary allocated spaces. + // NOTE: fc->unflip must be 0. + int bakunflip = fc->unflip; + fc->unflip = 0; + flipnm_post(abtets, n, nn, 0, fc); + fc->unflip = bakunflip; + + delete [] abtets; + + return nn; +} + +//============================================================================// +// // +// removefacebyflips() Remove a face by flips. // +// // +// Return 1 if the face is removed. Otherwise, return 0. // +// // +// ASSUMPTION: 'flipface' must not be a subface or a hull face. // +// // +//============================================================================// + +int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) +{ + triface fliptets[3], flipedge; + point pa, pb, pc, pd, pe; + REAL ori; + int reducflag = 0; + + fliptets[0] = *flipface; + fsym(*flipface, fliptets[1]); + pa = org(fliptets[0]); + pb = dest(fliptets[0]); + pc = apex(fliptets[0]); + pd = oppo(fliptets[0]); + pe = oppo(fliptets[1]); + + ori = orient3d(pa, pb, pd, pe); + if (ori > 0) { + ori = orient3d(pb, pc, pd, pe); + if (ori > 0) { + ori = orient3d(pc, pa, pd, pe); + if (ori > 0) { + // Found a 2-to-3 flip. + reducflag = 1; + } else { + eprev(*flipface, flipedge); // [c,a] + } + } else { + enext(*flipface, flipedge); // [b,c] + } + } else { + flipedge = *flipface; // [a,b] + } + + if (reducflag) { + triface checkface = fliptets[0]; + if (!valid_constrained_f23(checkface, pd, pe)) { + return 0; //reducflag = 0; + } + } + + if (reducflag) { + // A 2-to-3 flip is found. + flip23(fliptets, 0, fc); + return 1; + } else { + // Try to flip the selected edge of this face. + if (removeedgebyflips(&flipedge, fc) == 2) { + if (b->verbose > 3) { + printf(" Face is removed by removing an edge.\n"); + } + return 1; + } + } + + // Face is not removed. + return 0; +} + +//============================================================================// +// // +// recoveredgebyflips() Recover an edge in current tetrahedralization. // +// // +// If the edge is recovered, 'searchtet' returns a tet containing the edge. // +// // +// If the parameter 'fullsearch' is set, it tries to flip any face or edge // +// that intersects the recovering edge. Otherwise, only the face or edge // +// which is visible by 'startpt' is tried. // +// // +// The parameter 'sedge' is used to report self-intersection. If it is not // +// a NULL, it is EITHER a segment OR a subface that contains this edge. // +// // +// This routine assumes that the tetrahedralization is convex. // +// // +//============================================================================// + +int tetgenmesh::recoveredgebyflips(point startpt, point endpt, face *sedge, + triface* searchtet, int fullsearch, int& idir) +{ + flipconstraints fc; + enum interresult dir; + + idir = (int) DISJOINT; // init. + + fc.seg[0] = startpt; + fc.seg[1] = endpt; + fc.checkflipeligibility = 1; + + // The mainloop of the edge reocvery. + while (1) { // Loop I + + // Search the edge from 'startpt'. + point2tetorg(startpt, *searchtet); + dir = finddirection(searchtet, endpt); + + if (dir == ACROSSVERT) { + if (dest(*searchtet) == endpt) { + return 1; // Edge is recovered. + } else { + if (sedge != NULL) { + // It is a segment or a subedge (an edge of a facet). + // Check and report if there exists a self-intersection. + insertvertexflags ivf; + bool intersect_flag = false; + point nearpt = dest(*searchtet); + ivf.iloc = ONVERTEX; + + if (sedge->sh[5] == NULL) { + // It is a segment. + if (!issteinerpoint(nearpt)) { + // It is an input point. + if (!b->quiet && !b->nowarning) { + int segidx = getfacetindex(*sedge); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + point tmppt = NULL; + if (is_segment(p1, nearpt)) tmppt = p1; + else if (is_segment(p2, nearpt)) tmppt = p2; + if (tmppt != NULL) { + printf("Warning: Two line segments are %s overlapping.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); + } else { + printf("Warning: A vertex lies %s on a line segment.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d].\n", pointmark(nearpt)); + } + } + intersect_flag = true; + } else { + if (pointtype(nearpt) == FREESEGVERTEX) { + // Check if two segments are (nearly) intersecting. + int segidx = getfacetindex(*sedge); + face parsentseg; + sdecode(point2sh(nearpt), parsentseg); + int segidx2 = getfacetindex(parsentseg); + if (segidx2 != segidx) { + if (!b->quiet && !b->nowarning) { // -no -Q no -W + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are %s crossing.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + } + intersect_flag = true; + } else { + //if (ivf.iloc == ONVERTEX) { + terminatetetgen(this, 2); // This should not be possible. + //} + } + } else if (pointtype(nearpt) == FREEFACETVERTEX) { + // This case is very unlikely. + terminatetetgen(this, 2); // to debug... + if (!b->quiet && !b->nowarning) { // -no -Q no -W + //face parsentsh; + //sdecode(point2sh(nearpt), parsentsh); + printf("Warning: A segment and a facet intersect.\n"); + } + intersect_flag = true; + } else { + // other cases... + terminatetetgen(this, 2); // to be checked. + } + } + } else { + // It is an edge of a facet. + if (!issteinerpoint(nearpt)) { + if (!b->quiet && !b->nowarning) { // no "-Q -W" + point p1 = sorg(*sedge); + point p2 = sdest(*sedge); + point p3 = sapex(*sedge); + printf("Warning: A vertex lies on a facet.\n"); + printf(" vertex: [%d]\n", pointmark(nearpt)); + printf(" facet triangle: [%d,%d,%d], tag(%d).\n", + pointmark(p1), pointmark(p2), pointmark(p3), + shellmark(*sedge)); + } + intersect_flag = true; + } else { + // A Steiner point. + if (pointtype(nearpt) == FREESEGVERTEX) { + // A facet and a segment is intersecting. + if (!b->quiet && !b->nowarning) { + printf("Warning: A facet and a segment intersect.\n"); + printf(" ...\n"); + } + intersect_flag = true; + } else if (pointtype(nearpt) == FREEFACETVERTEX) { + // Check if two facets are intersecting. + if (!b->quiet && !b->nowarning) { + printf("Warning: Two facets intersect.\n"); + printf(" ...\n"); + } + intersect_flag = true; + } else { + // A FREEVOLVERTEX. + // This is not a real self-intersection. + terminatetetgen(this, 2); // check this case. + } + } + } + + if (intersect_flag) { + idir = (int) SELF_INTERSECT; + } + } // if (sedge != NULL) + return 0; + } + } // if (dir == ACROSSVERT) + + // The edge is missing. + + // Try to remove the first intersecting face/edge. + enextesymself(*searchtet); // Go to the opposite face. + + if (dir == ACROSSFACE) { + if (checksubfaceflag) { + if (issubface(*searchtet)) { + if (sedge) { + // A self-intersection is detected. + if (!b->quiet && !b->nowarning) { + bool is_seg = (sedge->sh[5] == NULL); + if (is_seg) { + face fac; tspivot(*searchtet, fac); + int segidx = getfacetindex(*sedge); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + printf("Warning: A segment and a facet exactly intersect.\n"); + printf(" seg : [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" facet triangle: [%d,%d,%d] tag(%d).\n", + pointmark(sorg(fac)), pointmark(sdest(fac)), + pointmark(sapex(fac)), shellmark(fac)); + } else { + // It is a subedge of a facet. + point *ppt = (point *) &(sedge->sh[3]); + printf("Warning: Two facets exactly intersect.\n"); + printf(" 1st facet triangle: [%d,%d,%d] tag(%d).\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*sedge)); + face fac; tspivot(*searchtet, fac); + ppt = (point *) &(fac.sh[3]); + printf(" 2nd facet triangle: [%d,%d,%d] tag(%d).\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(fac)); + } + } + idir = (int) SELF_INTERSECT; + } + return 0; + } // if (issubface(*searchtet)) + } + // Try to flip a crossing face. + if (removefacebyflips(searchtet, &fc)) { + continue; + } + } else if (dir == ACROSSEDGE) { + if (checksubsegflag) { + if (issubseg(*searchtet)) { + if (sedge) { + // A self-intersection is detected. + if (!b->quiet && !b->nowarning) { // no -Q, -W + bool is_seg = (sedge->sh[5] == NULL); + if (is_seg) { + face seg; tsspivot1(*searchtet, seg); + int segidx = getfacetindex(*sedge); + int segidx2 = getfacetindex(seg); + if (segidx != segidx2) { + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two segments exactly intersect.\n"); + printf(" 1st seg [%d,%d] tag(%d).\n", + pointmark(p1), pointmark(p2), shellmark(*sedge)); + printf(" 2nd seg: [%d,%d] tag(%d).\n", + pointmark(p3), pointmark(p4), shellmark(seg)); + } else { + terminatetetgen(this, 2); + } + } else { + // It is a subedge of a facet. + point *ppt = (point *) &(sedge->sh[3]); + printf("Warning: A facet and a segment exactly intersect.\n"); + printf(" facet triangle: [%d,%d,%d] tag(%d).\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*sedge)); + face seg; tsspivot1(*searchtet, seg); + ppt = (point *) &(seg.sh[3]); + printf(" seg: [%d,%d] tag(%d).\n", + pointmark(ppt[0]), pointmark(ppt[1]), shellmark(seg)); + } + } + idir = (int) SELF_INTERSECT; + } + return 0; + } + } + // Try to flip an intersecting edge. + if (removeedgebyflips(searchtet, &fc) == 2) { + continue; + } + } else { + terminatetetgen(this, 2); // report a bug + } + + // The edge is missing. + + if (fullsearch) { + // Try to flip one of the faces/edges which intersects the edge. + triface neightet, spintet; + point pa, pb, pc, pd; + badface bakface; + enum interresult dir1; + int types[2], poss[4], pos = 0; + int success = 0; + int t1ver; + int i, j; + + // Loop through the sequence of intersecting faces/edges from + // 'startpt' to 'endpt'. + point2tetorg(startpt, *searchtet); + dir = finddirection(searchtet, endpt); + + // Go to the face/edge intersecting the searching edge. + enextesymself(*searchtet); // Go to the opposite face. + // This face/edge has been tried in previous step. + + while (1) { // Loop I-I + + // Find the next intersecting face/edge. + fsymself(*searchtet); + if (dir == ACROSSFACE) { + neightet = *searchtet; + j = (neightet.ver & 3); // j is the current face number. + for (i = j + 1; i < j + 4; i++) { + neightet.ver = (i % 4); + pa = org(neightet); + pb = dest(neightet); + pc = apex(neightet); + pd = oppo(neightet); // The above point. + if (tri_edge_test(pa,pb,pc,startpt,endpt, pd, 1, types, poss)) { + dir = (enum interresult) types[0]; + pos = poss[0]; + break; + } else { + dir = DISJOINT; + pos = 0; + } + } // i + // There must be an intersection face/edge. + if (dir == DISJOINT) { + terminatetetgen(this, 2); + } + } else if (dir == ACROSSEDGE) { + while (1) { // Loop I-I-I + // Check the two opposite faces (of the edge) in 'searchtet'. + for (i = 0; i < 2; i++) { + if (i == 0) { + enextesym(*searchtet, neightet); + } else { + eprevesym(*searchtet, neightet); + } + pa = org(neightet); + pb = dest(neightet); + pc = apex(neightet); + pd = oppo(neightet); // The above point. + if (tri_edge_test(pa,pb,pc,startpt,endpt,pd,1, types, poss)) { + dir = (enum interresult) types[0]; + pos = poss[0]; + break; // for loop + } else { + dir = DISJOINT; + pos = 0; + } + } // i + if (dir != DISJOINT) { + // Find an intersection face/edge. + break; // Loop I-I-I + } + // No intersection. Rotate to the next tet at the edge. + fnextself(*searchtet); + } // while (1) // Loop I-I-I + } else { + terminatetetgen(this, 2); // Report a bug + } + + // Adjust to the intersecting edge/vertex. + for (i = 0; i < pos; i++) { + enextself(neightet); + } + + if (dir == SHAREVERT) { + // Check if we have reached the 'endpt'. + pd = org(neightet); + if (pd == endpt) { + // Failed to recover the edge. + break; // Loop I-I + } else { + return 0; + } + } + + // The next to be flipped face/edge. + *searchtet = neightet; + + // Bakup this face (tetrahedron). + bakface.forg = org(*searchtet); + bakface.fdest = dest(*searchtet); + bakface.fapex = apex(*searchtet); + bakface.foppo = oppo(*searchtet); + + // Try to flip this intersecting face/edge. + if (dir == ACROSSFACE) { + if (checksubfaceflag) { + if (issubface(*searchtet)) { + return 0; + } + } + if (removefacebyflips(searchtet, &fc)) { + success = 1; + break; // Loop I-I + } + } else if (dir == ACROSSEDGE) { + if (checksubsegflag) { + if (issubseg(*searchtet)) { + return 0; + } + } + if (removeedgebyflips(searchtet, &fc) == 2) { + success = 1; + break; // Loop I-I + } + } else if (dir == ACROSSVERT) { + return 0; + } else { + terminatetetgen(this, 2); + } + + // The face/edge is not flipped. + if ((searchtet->tet == NULL) || + (org(*searchtet) != bakface.forg) || + (dest(*searchtet) != bakface.fdest) || + (apex(*searchtet) != bakface.fapex) || + (oppo(*searchtet) != bakface.foppo)) { + // 'searchtet' was flipped. We must restore it. + point2tetorg(bakface.forg, *searchtet); + dir1 = finddirection(searchtet, bakface.fdest); + if (dir1 == ACROSSVERT) { + if (dest(*searchtet) == bakface.fdest) { + spintet = *searchtet; + while (1) { + if (apex(spintet) == bakface.fapex) { + // Found the face. + *searchtet = spintet; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) { + searchtet->tet = NULL; + break; // Not find. + } + } // while (1) + if (searchtet->tet != NULL) { + if (oppo(*searchtet) != bakface.foppo) { + fsymself(*searchtet); + if (oppo(*searchtet) != bakface.foppo) { + // The original (intersecting) tet has been flipped. + searchtet->tet = NULL; + break; // Not find. + } + } + } + } else { + searchtet->tet = NULL; // Not find. + } + } else { + searchtet->tet = NULL; // Not find. + } + if (searchtet->tet == NULL) { + success = 0; // This face/edge has been destroyed. + break; // Loop I-I + } + } + } // while (1) // Loop I-I + + if (success) { + // One of intersecting faces/edges is flipped. + continue; + } + + } // if (fullsearch) + + // The edge is missing. + break; // Loop I + + } // while (1) // Loop I + + return 0; +} + +//============================================================================// +// // +// add_steinerpt_in_schoenhardtpoly() Insert a Steiner point in a Schoen- // +// hardt polyhedron. // +// // +// 'abtets' is an array of n tets which all share at the edge [a,b]. Let the // +// tets are [a,b,p0,p1], [a,b,p1,p2], ..., [a,b,p_(n-2),p_(n-1)]. Moreover, // +// the edge [p0,p_(n-1)] intersects all of the tets in 'abtets'. A special // +// case is that the edge [p0,p_(n-1)] is coplanar with the edge [a,b]. // +// Such set of tets arises when we want to recover an edge from 'p0' to 'p_ // +// (n-1)', and the number of tets at [a,b] can not be reduced by any flip. // +// // +//============================================================================// + +int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, + int splitsliverflag, int chkencflag) +{ + triface worktet, *parytet; + triface faketet1, faketet2; + point pc, pd, steinerpt; + insertvertexflags ivf; + optparameters opm; + REAL vcd[3], sampt[3], smtpt[3]; + REAL maxminvol = 0.0, minvol = 0.0, ori; + int success, maxidx = 0; + int it, i; + + + if (splitsliverflag) { + // randomly pick a tet. + int idx = rand() % n; + + // Calulcate the barycenter of this tet. + point pa = org(abtets[idx]); + point pb = dest(abtets[idx]); + pc = apex(abtets[idx]); + pd = oppo(abtets[idx]); + + makepoint(&steinerpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = (pa[i] + pb[i] + pc[i] + pd[i]) / 4.; + } + + + worktet = abtets[idx]; + ivf.iloc = (int) OUTSIDE; // need point location. + ivf.bowywat = 1; + //ivf.lawson = 0; + ivf.lawson = 2; // Do flips to recover Delaunayness. + ivf.rejflag = 0; + ivf.chkencflag = chkencflag; + ivf.sloc = 0; + ivf.sbowywat = 0; + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (insertpoint(steinerpt, &worktet, NULL, NULL, &ivf)) { + // The vertex has been inserted. + if (flipstack != NULL) { + recoverdelaunay(); + } + st_volref_count++; + if (steinerleft > 0) steinerleft--; + return 1; + } else { + // Not inserted. + pointdealloc(steinerpt); + return 0; + } + } // if (splitsliverflag) + + pc = apex(abtets[0]); // pc = p0 + pd = oppo(abtets[n-1]); // pd = p_(n-1) + + // Find an optimial point in edge [c,d]. It is visible by all outer faces + // of 'abtets', and it maxmizes the min volume. + + // initialize the list of 2n boundary faces. + for (i = 0; i < n; i++) { + edestoppo(abtets[i], worktet); // [p_i,p_i+1,a] + cavetetlist->newindex((void **) &parytet); + *parytet = worktet; + eorgoppo(abtets[i], worktet); // [p_i+1,p_i,b] + cavetetlist->newindex((void **) &parytet); + *parytet = worktet; + } + + int N = 100; + REAL stepi = 0.01; + + // Search the point along the edge [c,d]. + for (i = 0; i < 3; i++) vcd[i] = pd[i] - pc[i]; + + // Sample N points in edge [c,d]. + for (it = 1; it < N; it++) { + for (i = 0; i < 3; i++) { + sampt[i] = pc[i] + (stepi * (double) it) * vcd[i]; + } + for (i = 0; i < cavetetlist->objects; i++) { + parytet = (triface *) fastlookup(cavetetlist, i); + ori = orient3d(dest(*parytet), org(*parytet), apex(*parytet), sampt); + if (i == 0) { + minvol = ori; + } else { + if (minvol > ori) minvol = ori; + } + } // i + if (it == 1) { + maxminvol = minvol; + maxidx = it; + } else { + if (maxminvol < minvol) { + maxminvol = minvol; + maxidx = it; + } + } + } // it + + if (maxminvol <= 0) { + cavetetlist->restart(); + return 0; + } + + for (i = 0; i < 3; i++) { + smtpt[i] = pc[i] + (stepi * (double) maxidx) * vcd[i]; + } + + // Create two faked tets to hold the two non-existing boundary faces: + // [d,c,a] and [c,d,b]. + maketetrahedron(&faketet1); + setvertices(faketet1, pd, pc, org(abtets[0]), dummypoint); + cavetetlist->newindex((void **) &parytet); + *parytet = faketet1; + maketetrahedron(&faketet2); + setvertices(faketet2, pc, pd, dest(abtets[0]), dummypoint); + cavetetlist->newindex((void **) &parytet); + *parytet = faketet2; + + // Point smooth options. + opm.max_min_volume = 1; + opm.numofsearchdirs = 20; + opm.searchstep = 0.001; + opm.maxiter = 100; // Limit the maximum iterations. + opm.initval = 0.0; // Initial volume is zero. + + // Try to relocate the point into the inside of the polyhedron. + success = smoothpoint(smtpt, cavetetlist, 1, &opm); + + if (success) { + while (opm.smthiter == 100) { + // It was relocated and the prescribed maximum iteration reached. + // Try to increase the search stepsize. + opm.searchstep *= 10.0; + //opm.maxiter = 100; // Limit the maximum iterations. + opm.initval = opm.imprval; + opm.smthiter = 0; // Init. + smoothpoint(smtpt, cavetetlist, 1, &opm); + } + } // if (success) + + // Delete the two faked tets. + tetrahedrondealloc(faketet1.tet); + tetrahedrondealloc(faketet2.tet); + + cavetetlist->restart(); + + if (success) { + // Insert this Steiner point. + + // Insert the Steiner point. + makepoint(&steinerpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) steinerpt[i] = smtpt[i]; + + // Insert the created Steiner point. + for (i = 0; i < n; i++) { + infect(abtets[i]); + caveoldtetlist->newindex((void **) &parytet); + *parytet = abtets[i]; + } + worktet = abtets[0]; // No need point location. + ivf.iloc = (int) INSTAR; + ivf.chkencflag = chkencflag; + ivf.assignmeshsize = b->metric; + if (ivf.assignmeshsize) { + // Search the tet containing 'steinerpt' for size interpolation. + locate(steinerpt, &(abtets[0])); + worktet = abtets[0]; + } + + // Insert the new point into the tetrahedralization T. + if (insertpoint(steinerpt, &worktet, NULL, NULL, &ivf)) { + // The vertex has been inserted. + st_volref_count++; + if (steinerleft > 0) steinerleft--; + return 1; + } else { + // Not inserted. + pointdealloc(steinerpt); + return 0; + } + } + + //if (!success) { + return 0; + //} +} + +//============================================================================// +// // +// add_steinerpt_in_segment() Add a Steiner point inside a segment. // +// // +//============================================================================// + +int tetgenmesh::add_steinerpt_in_segment(face* misseg, int searchlevel, int& idir) +{ + triface searchtet; + face *paryseg, candseg; + point startpt, endpt, pc, pd; + flipconstraints fc; + enum interresult dir; + REAL P[3], Q[3], tp, tq; + REAL len, smlen = 0, split = 0, split_q = 0; + int success; + int i; + + startpt = sorg(*misseg); + endpt = sdest(*misseg); + + idir = DISJOINT; // init. + + // sort the vertices + //if (pointmark(startpt) > pointmark(endpt)) { + // endpt = sorg(*misseg); + // startpt = sdest(*misseg); + //} + + + fc.seg[0] = startpt; + fc.seg[1] = endpt; + fc.checkflipeligibility = 1; + fc.collectencsegflag = 1; + + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + if (dir == ACROSSVERT) { + return 0; + } + + // Try to flip the first intersecting face/edge. + enextesymself(searchtet); // Go to the opposite face. + + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = searchlevel; + + if (dir == ACROSSFACE) { + // A face is intersected with the segment. Try to flip it. + success = removefacebyflips(&searchtet, &fc); + } else if (dir == ACROSSEDGE) { + // An edge is intersected with the segment. Try to flip it. + success = removeedgebyflips(&searchtet, &fc); + } + + split = 0; + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + suninfect(*paryseg); + // Calculate the shortest edge between the two lines. + pc = sorg(*paryseg); + pd = sdest(*paryseg); + + // sort the vertices + //if (pointmark(pc) > pointmark(pd)) { + // pd = sorg(*paryseg); + // pc = sdest(*paryseg); + //} + + tp = tq = 0; + if (linelineint(startpt, endpt, pc, pd, P, Q, &tp, &tq)) { + // Does the shortest edge lie between the two segments? + // Round tp and tq. + if ((tp > 0) && (tq < 1)) { + if (tp < 0.5) { + if (tp < (b->epsilon * 1e+3)) tp = 0.0; + } else { + if ((1.0 - tp) < (b->epsilon * 1e+3)) tp = 1.0; + } + } + if ((tp <= 0) || (tp >= 1)) continue; + if ((tq > 0) && (tq < 1)) { + if (tq < 0.5) { + if (tq < (b->epsilon * 1e+3)) tq = 0.0; + } else { + if ((1.0 - tq) < (b->epsilon * 1e+3)) tq = 1.0; + } + } + if ((tq <= 0) || (tq >= 1)) continue; + // It is a valid shortest edge. Calculate its length. + len = distance(P, Q); + if (split == 0) { + smlen = len; + split = tp; + split_q = tq; + candseg = *paryseg; + } else { + if (len < smlen) { + smlen = len; + split = tp; + split_q = tq; + candseg = *paryseg; + } + } + } + } + + caveencseglist->restart(); + b->fliplinklevel = bak_fliplinklevel; + + if (split == 0) { + // Found no crossing segment. + return 0; + } + + face splitsh; + face splitseg; + point steinerpt, *parypt; + insertvertexflags ivf; + + if (b->addsteiner_algo == 1) { + // Split the segment at the closest point to a near segment. + makepoint(&steinerpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = startpt[i] + split * (endpt[i] - startpt[i]); + } + } else { // b->addsteiner_algo == 2 + for (i = 0; i < 3; i++) { + P[i] = startpt[i] + split * (endpt[i] - startpt[i]); + } + pc = sorg(candseg); + pd = sdest(candseg); + for (i = 0; i < 3; i++) { + Q[i] = pc[i] + split_q * (pd[i] - pc[i]); + } + makepoint(&steinerpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = 0.5 * (P[i] + Q[i]); + } + } + + // Check if the two segments are nearly crossing each other. + pc = sorg(candseg); + pd = sdest(candseg); + if (is_collinear_at(steinerpt, pc, pd)) { // -p///#, default 179.9 degree + if (!b->quiet && !b->nowarning) { // no -Q, -W + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + int segidx2 = getfacetindex(candseg); + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are almost crossing.\n"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + } + + // calculate a new angle tolerance. + REAL collinear_ang = interiorangle(steinerpt, pc, pd, NULL) / PI * 180.; + double ang_diff = collinear_ang - b->collinear_ang_tol; + double new_ang_tol = collinear_ang + ang_diff / 180.; + + if (new_ang_tol < 180.0) { // no -Q, -W + // Reduce the angle tolerance to detect collinear event. + if (!b->quiet && !b->nowarning) { + printf(" Reducing collinear tolerance from %g to %g degree.\n", + b->collinear_ang_tol, new_ang_tol); + } + b->collinear_ang_tol = new_ang_tol; + cos_collinear_ang_tol = cos(b->collinear_ang_tol / 180.0 * PI); + } else { + // Report a self-intersection event due to epsilon. + if (!b->quiet && !b->nowarning) { // no -Q, -W + printf(" Cannot reduce the current collinear tolerance (=%g degree).\n", + b->collinear_ang_tol); + } + idir = SELF_INTERSECT; + pointdealloc(steinerpt); + return 0; + } + } + + // We need to locate the point. Start searching from 'searchtet'. + if (split < 0.5) { + point2tetorg(startpt, searchtet); + } else { + point2tetorg(endpt, searchtet); + } + if (b->addsteiner_algo == 1) { + splitseg = *misseg; + spivot(*misseg, splitsh); + // for create_a_shorter_edge(). + setpoint2sh(steinerpt, sencode(*misseg)); + } else { + splitsh.sh = NULL; + splitseg.sh = NULL; + } + ivf.iloc = (int) OUTSIDE; + ivf.bowywat = 1; + //ivf.lawson = 0; + ivf.lawson = 2; // Do flips to recover Delaunayness. + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; // split surface mesh separately, new subsegments are + // pushed into "subsegstack". + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (insertpoint(steinerpt, &searchtet, &splitsh, &splitseg, &ivf)) { + if (flipstack != NULL) { + recoverdelaunay(); + } + } else { + pointdealloc(steinerpt); + return 0; + } + + if (b->addsteiner_algo == 1) { + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + st_segref_count++; + } else { // b->addsteiner_algo == 2 + // Queue the segment for recovery. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + st_volref_count++; + } + if (steinerleft > 0) steinerleft--; + + return 1; +} + +//============================================================================// +// // +// addsteiner4recoversegment() Add a Steiner point for recovering a seg. // +// // +// Tries to add a Steiner point in the volume (near this segment) which will // +// help to recover this segment. This segment itself is not split. // +// // +// 'splitsliverflag' is a parameter used in the subroutine add_steiner_in_ // +// schonhardpoly(). // +// // +//============================================================================// + +int tetgenmesh::add_steinerpt_to_recover_edge(point startpt, point endpt, + face* misseg, int splitsegflag, int splitsliverflag, int& idir) +{ + triface *abtets, searchtet, spintet; + face splitsh; + face *paryseg; + point pa, pb, pd, steinerpt, *parypt; + enum interresult dir; + insertvertexflags ivf; + int types[2], poss[4]; + int n, endi, success; + int t1ver; + int i; + + idir = (int) DISJOINT; + + if (misseg != NULL) { + startpt = sorg(*misseg); + if (pointtype(startpt) == FREESEGVERTEX) { + sesymself(*misseg); + startpt = sorg(*misseg); + } + endpt = sdest(*misseg); + } + + + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + + + if (dir == ACROSSVERT) { + if (dest(searchtet) == endpt) { + // This edge exists. + if ((misseg != NULL) && (subsegstack != NULL)) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + } + return 1; + } else { + // This edge crosses a vertex (not endpt). + bool intersect_flag = false; // return + if (misseg != NULL) { + // Check whether there exists a self-intersection. + point nearpt = dest(searchtet); + ivf.iloc = ONVERTEX; + // report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + + if (!issteinerpoint(nearpt)) { + // It is an input point. + if (!b->quiet && !b->nowarning) { + point tmppt = NULL; + if (is_segment(p1, nearpt)) tmppt = p1; + else if (is_segment(p2, nearpt)) tmppt = p2; + if (tmppt != NULL) { + printf("Warning: Two line segments are %s overlapping.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); + } else { + printf("Warning: A vertex lies %s on a line segment.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" segment: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" vertex : [%d].\n", pointmark(nearpt)); + } + } + intersect_flag = true; + } else { + if (pointtype(nearpt) == FREESEGVERTEX) { + // Check if two segments are exactly intersecting. + face parsentseg; + sdecode(point2sh(nearpt), parsentseg); + int segidx2 = getfacetindex(parsentseg); + if (segidx2 != segidx) { + if (!b->quiet && !b->nowarning) { + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are %s crossing.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + } + intersect_flag = true; + } else { + if (ivf.iloc == ONVERTEX) { + terminatetetgen(this, 2); // This should not be possible. + } + } + } else { + // other cases... + terminatetetgen(this, 2); + } + } + } // if (misseg != NULL) + if (intersect_flag) { + idir = (int) SELF_INTERSECT; + } + return 0; + } + } // if (dir == ACROSSVERT) { + + enextself(searchtet); + + if (dir == ACROSSFACE) { + // The segment is crossing at least 3 faces. Find the common edge of + // the first 3 crossing faces. + esymself(searchtet); + fsym(searchtet, spintet); + pd = oppo(spintet); + + if (pd == endpt) { + if (misseg != NULL) { + // Calclate the smallest angle between (a,b,c) and (startpt, endpt). + triface tmptet; + REAL ang, collinear_ang = 0.; + for (int k = 0; k < 3; k++) { + ang = interiorangle(org(searchtet), startpt, endpt, NULL); // in [0, PI] + if (ang > collinear_ang) { + collinear_ang = ang; + tmptet = searchtet; // org(tmptet) + } + enextself(searchtet); + } + collinear_ang = collinear_ang / PI * 180.; // in degree + + if (collinear_ang > b->collinear_ang_tol) { // -p///#, default 179.9 degree + // Report a self-intersection event due to epsilon. + if (!b->quiet && !b->nowarning) { // no -Q, -W + point nearpt = org(tmptet); + ivf.iloc = NEARVERTEX; + // report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + + if (!issteinerpoint(nearpt)) { + point tmppt = NULL; + if (is_segment(p1, nearpt)) tmppt = p1; + else if (is_segment(p2, nearpt)) tmppt = p2; + if (tmppt != NULL) { + printf("Warning: Two line segments are %s overlapping.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); + } else { + printf("Warning: A vertex lies %s on a line segment.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" segment: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" vertex : [%d].\n", pointmark(nearpt)); + } + } else { + if (pointtype(nearpt) == FREESEGVERTEX) { + // Check if two segments are nearly intersecting. + face parsentseg; + sdecode(point2sh(nearpt), parsentseg); + int segidx2 = getfacetindex(parsentseg); + if (segidx2 != segidx) { + //if (!b->quiet && !b->nowarning) { + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are %s crossing.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + //} + //intersect_flag = true; + } else { + //if (ivf.iloc == ONVERTEX) { + terminatetetgen(this, 2); // This should not be possible. + //} + } + } else { + // Other case to report. + // assert(0); // to do... + terminatetetgen(this, 2); + } + } + } + + // calculate a new angle tolerance. + double ang_diff = collinear_ang - b->collinear_ang_tol; + double new_ang_tol = collinear_ang + ang_diff / 180.; + + if (new_ang_tol < 180.) { + // Reduce the angle tolerance to detect collinear event. + if (!b->quiet && !b->nowarning) { + printf(" Reducing collinear tolerance from %g to %g degree.\n", + b->collinear_ang_tol, new_ang_tol); + } + b->collinear_ang_tol = new_ang_tol; + cos_collinear_ang_tol = cos(b->collinear_ang_tol / 180. * PI); + + // This segment can be recovered by a 2-3 flip. + if (subsegstack != NULL) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + } + return 1; + } else { + if (!b->quiet && !b->nowarning) { + printf(" Cannot reduce the current collinear tolerance (=%g degree).\n", + b->collinear_ang_tol); + } + idir = (int) SELF_INTERSECT; + return 0; + } + } else { + // This segment can be recovered by a 2-3 flip. + if (subsegstack != NULL) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + } + return 1; + } + } else { + // This edge (not a segment) can be recovered by a 2-3 flip. + return 1; + } + } // if (pd == endpt) + + if (issubface(searchtet)) { + if (misseg != NULL) { + terminatetetgen(this, 2); + // Report a segment and a facet intersect. + if (!b->quiet && !b->nowarning) { + face fac; tspivot(searchtet, fac); + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + printf("Warning: A segment and a facet exactly intersect.\n"); + printf(" segment : [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" facet triangle: [%d,%d,%d] tag(%d).\n", + pointmark(org(searchtet)), pointmark(dest(searchtet)), + pointmark(apex(searchtet)), shellmark(fac)); + } + idir = (int) SELF_INTERSECT; + } + return 0; + } // if (issubface(searchtet)) + + for (i = 0; i < 3; i++) { + pa = org(spintet); + pb = dest(spintet); + if (tri_edge_test(pa, pb, pd, startpt, endpt, NULL, 1, types, poss)) { + break; // Found the edge. + } + enextself(spintet); + eprevself(searchtet); + } + esymself(searchtet); + } + else { // dir == ACROSSEDGE; + if (issubseg(searchtet)) { + terminatetetgen(this, 2); + if (misseg != NULL) { + // Report a self_intersection. + //bool intersect_flag = false; + //point nearpt = dest(searchtet); + ivf.iloc = ONVERTEX; + // report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + face parsentseg; + //sdecode(point2sh(nearpt), parsentseg); + tsspivot1(searchtet, parsentseg); + int segidx2 = getfacetindex(parsentseg); + if (segidx2 != segidx) { + if (!b->quiet && !b->nowarning) { + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are %s crossing.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + } + //intersect_flag = true; + } else { + if (ivf.iloc == ONVERTEX) { + terminatetetgen(this, 2); // This should not be possible. + } + } + idir = (int) SELF_INTERSECT; + } // if (misseg != NULL) + return 0; + } + } + + if (!splitsegflag) { + // Try to recover this segment by adding Steiner points near it. + + spintet = searchtet; + n = 0; endi = -1; + while (1) { + // Check if the endpt appears in the star. + if (apex(spintet) == endpt) { + endi = n; // Remember the position of endpt. + } + n++; // Count a tet in the star. + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + + if (endi > 0) { + // endpt is also in the edge star + // Get all tets in the edge star. + abtets = new triface[n]; + spintet = searchtet; + for (i = 0; i < n; i++) { + abtets[i] = spintet; + fnextself(spintet); + } + + success = 0; + + if (dir == ACROSSFACE) { + // Find a Steiner points inside the polyhedron. + if (add_steinerpt_in_schoenhardtpoly(abtets, endi, splitsliverflag, 0)) { + success = 1; + } + } else if (dir == ACROSSEDGE) { + // PLC check. + if (issubseg(searchtet)) { + terminatetetgen(this, 2); + } + if (n > 4) { + // In this case, 'abtets' is separated by the plane (containing the + // two intersecting edges) into two parts, P1 and P2, where P1 + // consists of 'endi' tets: abtets[0], abtets[1], ..., + // abtets[endi-1], and P2 consists of 'n - endi' tets: + // abtets[endi], abtets[endi+1], abtets[n-1]. + if (endi > 2) { // P1 + // There are at least 3 tets in the first part. + if (add_steinerpt_in_schoenhardtpoly(abtets, endi, splitsliverflag, 0)) { + success++; + } + } + if ((n - endi) > 2) { // P2 + // There are at least 3 tets in the first part. + if (add_steinerpt_in_schoenhardtpoly(&(abtets[endi]), n - endi, splitsliverflag, 0)) { + success++; + } + } + } else { + // In this case, a 4-to-4 flip should be re-cover the edge [c,d]. + // However, there will be invalid tets (either zero or negtive + // volume). Otherwise, [c,d] should already be recovered by the + // recoveredge() function. + } + } else { + terminatetetgen(this, 2); + } + + delete [] abtets; + + if (success && (misseg != NULL)) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + } + + if (success) { + return 1; + } + } // if (endi > 0) + + return 0; + } // if (!splitsegflag) + + if (b->verbose > 3) { + printf(" Recover segment (%d, %d) by splitting it.\n", + pointmark(startpt), pointmark(endpt)); + } + steinerpt = NULL; + + if (b->addsteiner_algo > 0) { // -Y/1 or -Y/2 + if (add_steinerpt_in_segment(misseg, 3, idir)) { + return 1; + } + if (idir == SELF_INTERSECT) { + return 0; + } + sesymself(*misseg); + if (add_steinerpt_in_segment(misseg, 3, idir)) { + return 1; + } + sesymself(*misseg); + if (idir == SELF_INTERSECT) { + return 0; + } + } + + + // Let the face [a,b,d] be the first intersecting face of the segment + // [startpt, endpt]. We add the interseting point. + REAL ip[3], u; + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + if (dir == ACROSSVERT) { + if (dest(searchtet) == endpt) { + // This edge exists. + if (misseg != NULL) { + // Add the missing segment back to the recovering list. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + } + return 1; + } else { + // This should be a self-intersection. + if (misseg != NULL) { + terminatetetgen(this, 2); + // report_seg_vertex_intersect(misseg, dest(searchtet), ONVERTEX); + idir = (int) SELF_INTERSECT; + } + return 0; + } + } + + enextself(searchtet); + pa = org(searchtet); + pb = dest(searchtet); + pd = oppo(searchtet); + + // Calculate the intersection of the face [a,b,d] and the segment. + //planelineint(pa, pb, pd, startpt, endpt, ip, &u); + + point fpt[3], ept[2]; + sort_3pts(pa, pb, pd, fpt); + sort_2pts(startpt, endpt, ept); + planelineint(fpt[0], fpt[1], fpt[2], ept[0], ept[1], ip, &u); + + if ((u > 0) && (u < 1)) { + // Create a Steiner point. + makepoint(&steinerpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) steinerpt[i] = ip[i]; + + // for create_a_shorter_edge(). + setpoint2sh(steinerpt, sencode(*misseg)); + + esymself(searchtet); // The crossing face/edge. + spivot(*misseg, splitsh); + if (dir == ACROSSFACE) { + //ivf.iloc = (int) ONFACE; + ivf.refineflag = 4; // Check if the crossing face is removed. + } else { + //ivf.iloc = (int) ONEDGE; + ivf.refineflag = 8; // Check if the crossing edge is removed. + } + ivf.iloc = (int) OUTSIDE; // do point location. + ivf.refinetet = searchtet; // The crossing face/edge. + ivf.bowywat = 1; + // ivf.lawson = 0; + ivf.lawson = 2; // Recover Delaunay after inserting this vertex. + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; // split surface mesh separately, new subsegments are + // pushed into "subsegstack". + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (insertpoint(steinerpt, &searchtet, &splitsh, misseg, &ivf)) { + if (flipstack != NULL) { + recoverdelaunay(); + } + + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + + st_segref_count++; + if (steinerleft > 0) steinerleft--; + + return 1; + } else { + // Check if this failure is due to a self-intersection. + if ((ivf.iloc == ONVERTEX) || (ivf.iloc == NEARVERTEX)) { + if (misseg != NULL) { + // report_seg_vertex_intersect(misseg, nearpt, ivf.iloc); + int segidx = getfacetindex(*misseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + bool intersect_flag = false; + point nearpt = org(searchtet); + if (!issteinerpoint(nearpt)) { + // 'nearpt' is an input vertex. + if (!b->quiet && !b->nowarning) { + point tmppt = NULL; + if (is_segment(p1, nearpt)) tmppt = p1; + else if (is_segment(p2, nearpt)) tmppt = p2; + if (tmppt != NULL) { + // Two input segments are nearly overlapping. + printf("Warning: Two line segments are %s overlapping.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(tmppt), pointmark(nearpt)); + } else { + // An input vertex is very close to a segment. + printf("Warning: A vertex lies %s on a line segment.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" segment: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" vertex : [%d].\n", pointmark(nearpt)); + } + } // if (!b->quiet && !b->nowarning) + intersect_flag = true; + } else { + if (pointtype(nearpt) == FREESEGVERTEX) { + // Check if two segments are nearly intersecting. + face parsentseg; + sdecode(point2sh(nearpt), parsentseg); + int segidx2 = getfacetindex(parsentseg); + if (segidx2 != segidx) { + point p3 = segmentendpointslist[segidx2*2]; + point p4 = segmentendpointslist[segidx2*2+1]; + printf("Warning: Two line segments are %s crossing.\n", + ivf.iloc == NEARVERTEX ? "nearly" : "exactly"); + printf(" 1st: [%d,%d].\n", pointmark(p1), pointmark(p2)); + printf(" 2nd: [%d,%d].\n", pointmark(p3), pointmark(p4)); + intersect_flag = true; + } + } else { + // report other cases. + // to do... + terminatetetgen(this, 2); + } + } + if (intersect_flag) { + if (!b->quiet && !b->nowarning) { + if (ivf.iloc == NEARVERTEX) { + double dd = distance(steinerpt, nearpt); + double new_dd = minedgelength - dd / longest; + double new_eps = new_dd / longest; + printf("You can ignore this warning by using -T%e (default is %e) option.\n", + new_eps, b->epsilon); + printf(" This will allow a short edge (len = %.17g) (default limit is %g).\n", + dd, minedgelength); + } + } + // A self-intersection is detected. + idir = (int) SELF_INTERSECT; + } // if (intersect_flag) + } // if (misseg != NULL) + } // if ((ivf.iloc == ONVERTEX) || (ivf.iloc == NEARVERTEX)) + + // The vertex is not inserted. + pointdealloc(steinerpt); + steinerpt = NULL; + } + } // if ((u > 0) && (u < 1)) + + return 0; // Failed to reocver this segment. + + // [2020-05-02] The following code is skipped. + if (steinerpt == NULL) { + // Split the segment at its midpoint. + makepoint(&steinerpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = 0.5 * (startpt[i] + endpt[i]); + } + + // We need to locate the point. + spivot(*misseg, splitsh); + ivf.iloc = (int) OUTSIDE; + ivf.bowywat = 1; + //ivf.lawson = 0; + ivf.lawson = 2; // do flip to recover locally Delaunay faces. + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; // mesh surface separately + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + if (insertpoint(steinerpt, &searchtet, &splitsh, misseg, &ivf)) { + if (flipstack != NULL) { + recoverdelaunay(); + } + } else { + terminatetetgen(this, 2); + } + } // if (endi > 0) + + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + + st_segref_count++; + if (steinerleft > 0) steinerleft--; + + return 1; +} + +//============================================================================// +// // +// recoversegments() Recover all segments. // +// // +// All segments need to be recovered are in 'subsegstack'. // +// // +// This routine first tries to recover each segment by only using flips. If // +// no flip is possible, and the flag 'steinerflag' is set, it then tries to // +// insert Steiner points near or in the segment. // +// // +//============================================================================// + +int tetgenmesh::recoversegments(arraypool *misseglist, int fullsearch, + int steinerflag) +{ + triface searchtet, spintet; + face sseg, *paryseg; + point startpt, endpt; + int success, idir; + int t1ver; + + long bak_inpoly_count = st_volref_count; + long bak_segref_count = st_segref_count; + + if (b->verbose > 1) { + printf(" Recover segments [%s level = %2d] #: %ld.\n", + (b->fliplinklevel > 0) ? "fixed" : "auto", + (b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, + subsegstack->objects); + } + + // Loop until 'subsegstack' is empty. + while (subsegstack->objects > 0l) { + // seglist is used as a stack. + subsegstack->objects--; + paryseg = (face *) fastlookup(subsegstack, subsegstack->objects); + sseg = *paryseg; + + // Check if this segment has been recovered. + sstpivot1(sseg, searchtet); + if (searchtet.tet != NULL) { + continue; // Not a missing segment. + } + + startpt = sorg(sseg); + endpt = sdest(sseg); + + if (b->verbose > 2) { + printf(" Recover segment (%d, %d).\n", pointmark(startpt), + pointmark(endpt)); + } + + success = 0; + + if (recoveredgebyflips(startpt, endpt, &sseg, &searchtet, 0, idir)) { + success = 1; + } else { + // Try to recover it from the other direction. + if ((idir != (int) SELF_INTERSECT) && + recoveredgebyflips(endpt, startpt, &sseg, &searchtet, 0, idir)) { + success = 1; + } + } + + + if (!success && fullsearch) { + if ((idir != (int) SELF_INTERSECT) && + recoveredgebyflips(startpt, endpt, &sseg, &searchtet, fullsearch, idir)) { + success = 1; + } + } + + if (success) { + // Segment is recovered. Insert it. + // Let the segment remember an adjacent tet. + sstbond1(sseg, searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, sseg); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + } else { + if ((idir != (int) SELF_INTERSECT) && (steinerflag > 0)) { + // Try to recover the segment but do not split it. + if (add_steinerpt_to_recover_edge(startpt, endpt, &sseg, 0, 0, idir)) { + success = 1; + } + if (!success && (idir != (int) SELF_INTERSECT) && (steinerflag > 1)) { + // Split the segment. + if (add_steinerpt_to_recover_edge(startpt, endpt, &sseg, 1, 0, idir)) { + success = 1; + } + } + } + + if (!success) { + if (idir != (int) SELF_INTERSECT) { + if (misseglist != NULL) { + // Save this segment (recover it later). + misseglist->newindex((void **) &paryseg); + *paryseg = sseg; + } + } else { + // Save this segment (do not recover it again). + if (skipped_segment_list == NULL) { + skipped_segment_list = new arraypool(sizeof(badface), 10); + } + badface *bf; + skipped_segment_list->newindex((void **) &bf); + bf->init(); + bf->ss = sseg; + bf->forg = sorg(sseg); + bf->fdest = sdest(sseg); + bf->key = (double) shellmark(sseg); + smarktest3(sseg); + // Save all subfaces at this segment, do not recover them later. + if (skipped_facet_list == NULL) { + skipped_facet_list = new arraypool(sizeof(badface), 10); + } + face neighsh, spinsh; + bf->ss.shver = 0; + spivot(bf->ss, neighsh); + spinsh = neighsh; + while (spinsh.sh != NULL) { + skipped_facet_list->newindex((void **) &bf); + bf->init(); + bf->ss = spinsh; + bf->forg = (point) spinsh.sh[3]; + bf->fdest = (point) spinsh.sh[4]; + bf->fapex = (point) spinsh.sh[5]; + bf->key = (double) shellmark(spinsh); + smarktest3(spinsh); // do not recover it. + spivotself(spinsh); + if (spinsh.sh == neighsh.sh) break; + } + } + } // if (!success) + } + + } // while (subsegstack->objects > 0l) + + if (steinerflag) { + if (b->verbose > 1) { + // Report the number of added Steiner points. + if (st_volref_count > bak_inpoly_count) { + printf(" Add %ld Steiner points in volume.\n", + st_volref_count - bak_inpoly_count); + } + if (st_segref_count > bak_segref_count) { + printf(" Add %ld Steiner points in segments.\n", + st_segref_count - bak_segref_count); + } + } + } + + return 0; +} + +//============================================================================// +// // +// recoverfacebyflips() Recover a face by flips. // +// // +// 'pa', 'pb', and 'pc' are the three vertices of this face. This routine // +// tries to recover it in the tetrahedral mesh. It is assumed that the three // +// edges, i.e., pa->pb, pb->pc, and pc->pa all exist. // +// // +// If the face is recovered, it is returned by 'searchtet'. // +// // +// If 'searchsh' is not NULL, it is a subface to be recovered. Its vertices // +// must be pa, pb, and pc. It is mainly used to check self-intersections. // +// Another use of this subface is to split it when a Steiner point is found // +// inside this subface. // +// // +//============================================================================// + +int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, + face *searchsh, triface* searchtet, + int &dir, point *p1, point *p2) +{ + triface spintet, flipedge; + point pd, pe; + flipconstraints fc; + int types[2], poss[4], intflag; + int success; + int t1ver; + int i, j; + + + fc.fac[0] = pa; + fc.fac[1] = pb; + fc.fac[2] = pc; + fc.checkflipeligibility = 1; + + dir = (int) DISJOINT; + success = 0; + + for (i = 0; i < 3 && !success; i++) { + while (1) { + // Get a tet containing the edge [a,b]. + point2tetorg(fc.fac[i], *searchtet); + finddirection(searchtet, fc.fac[(i+1)%3]); + // Search the face [a,b,c] + spintet = *searchtet; + while (1) { + if (apex(spintet) == fc.fac[(i+2)%3]) { + // Found the face. + *searchtet = spintet; + // Return the face [a,b,c]. + for (j = i; j > 0; j--) { + eprevself(*searchtet); + } + dir = (int) SHAREFACE; + success = 1; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } // while (1) + + if (success) break; + + // The face is missing. Try to recover it. + flipedge.tet = NULL; + // Find a crossing edge of this face. + spintet = *searchtet; + while (1) { + pd = apex(spintet); + pe = oppo(spintet); + if ((pd != dummypoint) && (pe != dummypoint)) { + // Check if [d,e] intersects [a,b,c] + intflag = tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss); + if (intflag > 0) { + // By the assumption that all edges of the face exist, they can + // only intersect at a single point. + if (intflag == 2) { + // Go to the edge [d,e]. + edestoppo(spintet, flipedge); // [d,e,a,b] + if (searchsh != NULL) { + // Check the intersection type. + dir = types[0]; // return this value. + if ((types[0] == (int) ACROSSFACE) || + (types[0] == (int) ACROSSEDGE)) { + // Check if [e,d] is a segment. + if (issubseg(flipedge)) { + // This subface intersects with a segment. + if (!b->quiet && !b->nowarning) { + if (!b->quiet && !b->nowarning) { + printf("Warning: A segment and a facet intersect.\n"); + face sseg; tsspivot1(flipedge, sseg); + int segidx = getfacetindex(sseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + printf(" segment: [%d,%d] tag(%d).\n", + pointmark(p1), pointmark(p2), shellmark(sseg)); + point *ppt = (point *) &(searchsh->sh[3]); + printf(" facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*searchsh)); + } + } + dir = (int) SELF_INTERSECT; + return 0; // Found a self-intersection. + } else { + // Check if [e,d] is an edge of a subface. + triface chkface = flipedge; + while (1) { + if (issubface(chkface)) break; + fsymself(chkface); + if (chkface.tet == flipedge.tet) break; + } + if (issubface(chkface)) { + if (searchsh != NULL) { + // Two subfaces are intersecting. + if (!b->quiet && !b->nowarning) { + printf("Warning: Found two facets intersect.\n"); + point *ppt = (point *) &(searchsh->sh[3]); + printf(" 1st facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*searchsh)); + face fa; tspivot(chkface, fa); + ppt = (point *) &(fa.sh[3]); + printf(" 2nd facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(fa)); + } + dir = (int) SELF_INTERSECT; + } + return 0; // Found a self-intersection. + } + } + } else if (types[0] == TOUCHFACE) { + // This is possible when a Steiner point was added on it. + point touchpt, *parypt; + if (poss[1] == 0) { + touchpt = pd; // pd is a coplanar vertex. + } else { + touchpt = pe; // pe is a coplanar vertex. + } + if (!issteinerpoint(touchpt)) { + if (!b->quiet && !b->nowarning) { + printf("Warning: A vertex lies on a facet.\n"); + printf(" vertex : [%d]\n", pointmark(touchpt)); + point *ppt = (point *) &(searchsh->sh[3]); + printf(" facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*searchsh)); + } + dir = (int) SELF_INTERSECT; + return 0; + } else if (pointtype(touchpt) == FREESEGVERTEX) { + if (!b->quiet && !b->nowarning) { + printf("Warning: A segment and a facet intersect.\n"); + face sseg; + sdecode(point2sh(touchpt), sseg); + int segidx = getfacetindex(sseg); + point p1 = segmentendpointslist[segidx*2]; + point p2 = segmentendpointslist[segidx*2+1]; + printf(" segment: [%d,%d] tag(%d).\n", + pointmark(p1), pointmark(p2), shellmark(sseg)); + point *ppt = (point *) &(searchsh->sh[3]); + printf(" facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*searchsh)); + } + dir = (int) SELF_INTERSECT; + return 0; + } else if (pointtype(touchpt) == FREEFACETVERTEX) { + if (!b->quiet && !b->nowarning) { + printf("Warning: Found two facets intersect.\n"); + point *ppt = (point *) &(searchsh->sh[3]); + printf(" 1st facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(*searchsh)); + face fa; + sdecode(point2sh(touchpt), fa); + ppt = (point *) &(fa.sh[3]); + printf(" 2nd facet triangle: [%d,%d,%d] tag(%d)\n", + pointmark(ppt[0]), pointmark(ppt[1]), + pointmark(ppt[2]), shellmark(fa)); + } + dir = (int) SELF_INTERSECT; + return 0; + } else if (pointtype(touchpt) == FREEVOLVERTEX) { + // A volume Steiner point was added in this subface. + // Split this subface by this point. + face checksh, *parysh; + int siloc = (int) ONFACE; + int sbowat = 0; // Only split this subface. A 1-to-3 flip. + setpointtype(touchpt, FREEFACETVERTEX); + sinsertvertex(touchpt, searchsh, NULL, siloc, sbowat, 0); + st_volref_count--; + st_facref_count++; + // Queue this vertex for removal. + subvertstack->newindex((void **) &parypt); + *parypt = touchpt; + // Queue new subfaces for recovery. + // Put all new subfaces into stack for recovery. + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // The new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + // Delete the old subfaces in sC(p). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + shellfacedealloc(subfaces, parysh->sh); + } + // Clear working lists. + caveshlist->restart(); + caveshbdlist->restart(); + cavesegshlist->restart(); + // We can return this function. + searchsh->sh = NULL; // It has been split. + return 1; + } else { + // Other cases may be due to a bug or a PLC error. + //return report_selfint_face(pa, pb, pc, searchsh, &flipedge, + // intflag, types, poss); + terminatetetgen(this, 2); // to debug... + dir = (int) SELF_INTERSECT; + return 0; // Found a self-intersection. + } + } else { + // The other intersection types: ACROSSVERT, TOUCHEDGE, + // SHAREVERTEX should not be possible or due to a PLC error. + //return report_selfint_face(pa, pb, pc, searchsh, &flipedge, + // intflag, types, poss); + terminatetetgen(this, 2); // to report + dir = (int) SELF_INTERSECT; + return 0; + } + } // if (searchsh != NULL) + } else { // intflag == 4. Coplanar case. + // Found a mesh edge is coplanar with this subface. + // It migh be caused by a self-intersection. + terminatetetgen(this, 2); // report this bug + } + break; + } // if (intflag > 0) + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) { + terminatetetgen(this, 2); + } + } // while (1) + // Try to flip the edge [d,e]. + // Remember a crossing edge. + *p1 = org(flipedge); + *p2 = dest(flipedge); + + if (removeedgebyflips(&flipedge, &fc) == 2) { + // A crossing edge is removed. + continue; + } + + // Unable to remove a crossing edge of this face. + break; + } // while (1) + } // i + + return success; +} + + +//============================================================================// +// // +// recoversubfaces() Recover all subfaces. // +// // +//============================================================================// + +int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) +{ + triface searchtet, neightet, spintet; + face searchsh, neighsh, neineish, *parysh; + face bdsegs[3]; + point startpt, endpt, apexpt, *parypt; + point cross_e1 = NULL, cross_e2 = NULL; // endpoints of a crossing edge. + point steinerpt; + insertvertexflags ivf; + int success, dir; + int t1ver; + int i, j; + + if (b->verbose > 1) { + printf(" Recover subfaces [%s level = %2d] #: %ld.\n", + (b->fliplinklevel > 0) ? "fixed" : "auto", + (b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, + subfacstack->objects); + } + + // Loop until 'subfacstack' is empty. + while (subfacstack->objects > 0l) { + + subfacstack->objects--; + parysh = (face *) fastlookup(subfacstack, subfacstack->objects); + searchsh = *parysh; + + if (searchsh.sh[3] == NULL) continue; // Skip a dead subface. + if (smarktest3ed(searchsh)) continue; // Skip a self-intersected subface. + + stpivot(searchsh, neightet); + if (neightet.tet != NULL) continue; // Skip a recovered subface. + + if (b->verbose > 2) { + printf(" Recover subface (%d, %d, %d).\n",pointmark(sorg(searchsh)), + pointmark(sdest(searchsh)), pointmark(sapex(searchsh))); + } + dir = (int) DISJOINT; // No self intersection is detected. + + // The three edges of the face need to be existed first. + for (i = 0; i < 3; i++) { + sspivot(searchsh, bdsegs[i]); + if (bdsegs[i].sh != NULL) { + // Check if this segment exist. + sstpivot1(bdsegs[i], searchtet); + if (searchtet.tet == NULL) { + // This segment is not recovered yet. Try to recover it. + success = 0; + startpt = sorg(searchsh); + endpt = sdest(searchsh); + if (recoveredgebyflips(startpt, endpt, &bdsegs[i], &searchtet, 0, dir)) { + success = 1; + } else { + if ((dir != (int) SELF_INTERSECT) && + recoveredgebyflips(endpt, startpt, &bdsegs[i], &searchtet, 0, dir)) { + success = 1; + } + } + if (success) { + // Segment is recovered. Insert it. + // Let the segment remember an adjacent tet. + sstbond1(bdsegs[i], searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, bdsegs[i]); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + } else { + // An edge of this subface is missing. Can't recover this subface. + // Delete any temporary segment that has been created. + for (j = (i - 1); j >= 0; j--) { + if (smarktest2ed(bdsegs[j])) { + spivot(bdsegs[j], neineish); + ssdissolve(neineish); + spivot(neineish, neighsh); + if (neighsh.sh != NULL) { + ssdissolve(neighsh); + } + sstpivot1(bdsegs[j], searchtet); + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, bdsegs[j].sh); + } + } // j + break; // i + } // if (success) else + } // if (searchtet.tet == NULL) + } else { + // This edge is not a segment. + // Check whether it exists or not. + success = 0; + startpt = sorg(searchsh); + endpt = sdest(searchsh); + point2tetorg(startpt, searchtet); + finddirection(&searchtet, endpt); + if (dest(searchtet) == endpt) { + success = 1; // Found this edge. + } else { + // The edge is missing. Try to recover it. + if (recoveredgebyflips(startpt, endpt, &searchsh, &searchtet, 0, dir)) { + success = 1; + } else { + if ((dir != (int) SELF_INTERSECT) && + recoveredgebyflips(endpt, startpt, &searchsh, &searchtet, 0, dir)) { + success = 1; + } + } + } + + if (success) { + // This edge exists. + if (issubseg(searchtet)) { + // A segment already exists at this edge! + //terminatetetgen(this, 2); // to debug + //dir = SELF_INTERSECT; + // We contnue to recover this subface instead of reporting a + // SELF_INTERSECT event. + // Eventually, we will find "a duplicated triangle" event. + } + } + + if (success && (dir != SELF_INTERSECT)) { + // This edge exists. + //if (!issubseg(searchtet)) { + // Insert a temporary segment to protect this edge. + makeshellface(subsegs, &(bdsegs[i])); + setshvertices(bdsegs[i], startpt, endpt, NULL); + smarktest2(bdsegs[i]); // It's a temporary segment. + // Insert this segment into surface mesh. + ssbond(searchsh, bdsegs[i]); + spivot(searchsh, neighsh); + if (neighsh.sh != NULL) { + ssbond(neighsh, bdsegs[i]); + } + // Insert this segment into tetrahedralization. + sstbond1(bdsegs[i], searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, bdsegs[i]); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + //} + } else { + // An edge of this subface is missing. Can't recover this subface. + // Delete any temporary segment that has been created. + for (j = (i - 1); j >= 0; j--) { + if (smarktest2ed(bdsegs[j])) { + spivot(bdsegs[j], neineish); + ssdissolve(neineish); + spivot(neineish, neighsh); + if (neighsh.sh != NULL) { + ssdissolve(neighsh); + } + sstpivot1(bdsegs[j], searchtet); + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, bdsegs[j].sh); + } + } // j + + break; + } + } + senextself(searchsh); + } // i + + if (i == 3) { + // All edges of this subface exist (or have been recovered). + // Recover the subface. + startpt = sorg(searchsh); + endpt = sdest(searchsh); + apexpt = sapex(searchsh); + + success = recoverfacebyflips(startpt, endpt, apexpt,&searchsh, &searchtet, + dir, &cross_e1, &cross_e2); + + // Delete any temporary segment that has been created. + for (j = 0; j < 3; j++) { + if (smarktest2ed(bdsegs[j])) { + spivot(bdsegs[j], neineish); + ssdissolve(neineish); + spivot(neineish, neighsh); + if (neighsh.sh != NULL) { + ssdissolve(neighsh); + } + sstpivot1(bdsegs[j], neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + shellfacedealloc(subsegs, bdsegs[j].sh); + } + } // j + + if (success) { + if (searchsh.sh != NULL) { + // Face is recovered. Insert it. + face chkface; + tspivot(searchtet, chkface); + if (chkface.sh == NULL) { + tsbond(searchtet, searchsh); + fsymself(searchtet); + sesymself(searchsh); + tsbond(searchtet, searchsh); + } else { + // A duplicated facet is found. + if (shellmark(chkface) == shellmark(searchsh)) { + if (!b->quiet && !b->nowarning) { + point *ppt = (point *) &(searchsh.sh[3]); + printf("Warning: A duplicated triangle (%d,%d,%d) tag(%d) is ignored.\n", + pointmark(ppt[0]), pointmark(ppt[1]), pointmark(ppt[2]), + shellmark(searchsh)); + } + duplicated_facets_count++; + smarktest3(searchsh); // do not recover it. + sinfect(searchsh); // it is an igonred duplicated facet. + } else { + if (!b->quiet && !b->nowarning) { + point *ppt = (point *) &(chkface.sh[3]); + printf("Warning: Two facets are overlapping at triangle (%d,%d,%d).\n", + pointmark(ppt[0]), pointmark(ppt[1]), pointmark(ppt[2])); + printf(" 1st facet tag(%d).\n", shellmark(chkface)); + printf(" 2nd facet tag(%d).\n", shellmark(searchsh)); + } + dir = SELF_INTERSECT; + success = 0; + } + } + } + } else { + if ((dir != (int) SELF_INTERSECT) && steinerflag) { + // Add a Steiner point at the barycenter of this subface. + REAL ip[3], u; + + //planelineint(startpt, endpt, apexpt, cross_e1, cross_e2, ip, &u); + + point fpt[3], ept[2]; + sort_3pts(startpt, endpt, apexpt, fpt); + sort_2pts(cross_e1, cross_e2, ept); + planelineint(fpt[0], fpt[1], fpt[2], ept[0], ept[1], ip, &u); + + makepoint(&steinerpt, FREEFACETVERTEX); + if ((u > 0.) && (u < 1.)) { + for (j = 0; j < 3; j++) steinerpt[j] = ip[j]; + // Make sure that this Steiner point is inside the subface. + if (is_collinear_at(steinerpt, startpt, endpt) || + is_collinear_at(steinerpt, endpt, apexpt) || + is_collinear_at(steinerpt, apexpt, startpt)) { + // Add the barycenter of this missing subface. + for (j = 0; j < 3; j++) { + steinerpt[j] = (startpt[j] + endpt[j] + apexpt[j]) / 3.0; + } + // Avoid creating a very skinny triangle + if (is_collinear_at(steinerpt, startpt, endpt) || + is_collinear_at(steinerpt, endpt, apexpt) || + is_collinear_at(steinerpt, apexpt, startpt)) { + terminatetetgen(this, 2); + } + } + } else { + // Add the barycenter of this missing subface. + for (j = 0; j < 3; j++) { + steinerpt[j] = (startpt[j] + endpt[j] + apexpt[j]) / 3.0; + } + // Avoid creating a very skinny triangle + if (is_collinear_at(steinerpt, startpt, endpt) || + is_collinear_at(steinerpt, endpt, apexpt) || + is_collinear_at(steinerpt, apexpt, startpt)) { + //assert(0); // to debug... + terminatetetgen(this, 2); + } + } + + // for create_a_shorter_edge(). + setpoint2sh(steinerpt, sencode(searchsh)); + + ivf.init(); + point2tetorg(startpt, searchtet); // Start from 'searchtet'. + ivf.iloc = (int) OUTSIDE; // Need point location. + ivf.bowywat = 1; + ivf.lawson = 2; // do recover delaunay. + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONFACE; // "searchsh" must be the subface. + ivf.sbowywat = 1; // split subface mesh separately, new subfaces + // are pushed into "subfacestack". + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf)) { + if (flipstack != NULL) { + recoverdelaunay(); + } + + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + + st_facref_count++; + if (steinerleft > 0) steinerleft--; + + success = 1; // This subface has been split. + } else { + // Failed to insert this point. + if (ivf.iloc == NEARVERTEX) { + // Check if this subface is nearly "touched" by an existing + // vertex. If so, report an event. + point chkpt = org(searchtet); + REAL dist = distance(steinerpt, chkpt); + if (dist < minedgelength) { + if (!issteinerpoint(chkpt)) { + if (!b->quiet && !b->nowarning) { // -no -Q -W + printf("Warning: A facet (%d,%d,%d) and a vertex %d are very close.\n", + pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), + pointmark(sapex(searchsh)), pointmark(chkpt)); + double dd = dist; // distance(steinerpt, nearpt); + //assert(dd > 0.); + //minedgelength = longest * b->epsilon; + //assert(dd < minedgelength); + double new_dd = minedgelength - dd / longest; + double new_eps = new_dd / longest; + printf("You can ignore this warning by using -T%e (default is %e) option.\n", + new_eps, b->epsilon); + printf(" This will allow a short edge (len = %.17g) (default limit is %g).\n", + dd, minedgelength); + } + dir = SELF_INTERSECT; + } + } else { + // Report other types of possible (nearly) self-intersection. + terminatetetgen(this, 2); + dir = SELF_INTERSECT; + } + } + + if ((dir != SELF_INTERSECT) && (steinerflag >= 2)) { + if (ivf.iloc == NULLCAVITY) { + // Collect a list of bad quality tets which prevent the + // insertion of this Steiner point. + terminatetetgen(this, 2); + point2tetorg(startpt, searchtet); + ivf.iloc = (int) OUTSIDE; // re-do point location. + ivf.collect_inial_cavity_flag = 1; + insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf); + } else { + terminatetetgen(this, 2); // report a bug. + } + } // if (steinerflag >= 2) + + pointdealloc(steinerpt); + steinerpt = NULL; + success = 0; // queue this subface. + } + } // if (steinerflag) + } + } else { // when i < 3 + // An edge (startpt, endpt) of this subface is missing. + if ((dir != (int) SELF_INTERSECT) && (steinerflag > 0)) { + // Split this edge by adding a Steiner point. + // Find the first face/edge crossed by the edge (startpt, endpt). + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + + + if (dir != (int) SELF_INTERSECT) { + // Insert a Steiner point. + REAL ip[3], u; + + enextself(searchtet); + point pa = org(searchtet); + point pb = dest(searchtet); + point pd = oppo(searchtet); + + //planelineint(pa, pb, pd, startpt, endpt, ip, &u); + + point fpt[3], ept[2]; + sort_3pts(pa, pb, pd, fpt); + sort_2pts(startpt, endpt, ept); + planelineint(fpt[0], fpt[1], fpt[2], ept[0], ept[1], ip, &u); + + makepoint(&steinerpt, FREEFACETVERTEX); + for (j = 0; j < 3; j++) steinerpt[j] = ip[j]; + + ivf.init(); + + ivf.refinetet = searchtet; // bakup the crossing face/edge. + + triface tmptet = searchtet; + ivf.iloc = locate(steinerpt, &tmptet); + + if (ivf.iloc == ONVERTEX) { + // the origin of tmptet is co-incident with this Steiner point. + searchtet = tmptet; + } + //else if (ivf.iloc == ONFACE) { + // searchtet = tmptet; + //} else if (ivf.iloc == ONEDGE) { + // searchtet = tmptet; + //} + else { + //assert(0); // to debug... + // Make sure that we can split the crossing edge/face (a,b,d). + if (dir == ACROSSFACE) { + ivf.iloc = (int) ONFACE; + //ivf.refineflag = 4; // Check if the crossing face is removed. + } else if (dir == ACROSSEDGE) { + ivf.iloc = (int) ONEDGE; + //ivf.refineflag = 8; // Check if the crossing edge is removed. + } else { + terminatetetgen(this, 2); + } + //ivf.iloc = (int) OUTSIDE; // do point location. + //ivf.refinetet = searchtet; // The crossing face/edge. + } + + ivf.bowywat = 1; + ivf.lawson = 2; // do recover delaunay. + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; // "searchsh" must be the subedge. + ivf.sbowywat = 1; // split subface mesh separately, new subfaces + // are pushed into "subfacestack". + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + //if (steinerflag >= 2) { + // Skip NEARVERTEX. This may create a very short edge. + //ivf.ignore_near_vertex = 1; + //} + + // searchsh may contain a missing segment. + // After splitting this subface, this segment must also be split. + // the two missing subsegments are stored in "subsegstack". + face misseg, *splitseg = NULL; + sspivot(searchsh, misseg); + if (misseg.sh != NULL) { + splitseg = &misseg; + setpointtype(steinerpt, FREESEGVERTEX); // default is FREEFACETVERTEX. + // for create_a_shorter_edge() + setpoint2sh(steinerpt, sencode(misseg)); + } else { + // for create_a_shorter_edge() + setpoint2sh(steinerpt, sencode(searchsh)); + } + + bool splitseg_flag = (splitseg != NULL); + int bak_iloc = ivf.iloc; // for collect_initial_cavity + + if (insertpoint(steinerpt, &searchtet, &searchsh, splitseg, &ivf)) { + if (flipstack != NULL) { + recoverdelaunay(); + } + + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + + if (splitseg_flag) { + st_segref_count++; + } else { + st_facref_count++; + } + if (steinerleft > 0) steinerleft--; + + success = 1; // This subface has been split. + } else { + // Failed to insert this point. + if (ivf.iloc == NEARVERTEX) { + // Check if this subface is nearly "touched" by an existing + // vertex. If so, report an event. + point chkpt = org(searchtet); + REAL dist = distance(steinerpt, chkpt); // for reporting. + if (!issteinerpoint(chkpt)) { + if (!b->quiet && !b->nowarning) { + if (splitseg_flag) { + printf("Warning: A segment (%d,%d) and a vertex %d are very close.\n", + pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), + pointmark(chkpt)); + } else { + printf("Warning: A facet (%d,%d,%d) and a vertex %d are very close.\n", + pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), + pointmark(sapex(searchsh)), pointmark(chkpt)); + } + //printf(" Will result a vert short edge (len=%.17g) (< %.17g)\n", + // dist, minedgelength); + double dd = dist; // distance(steinerpt, nearpt); + //assert(dd > 0.); + //minedgelength = longest * b->epsilon; + //assert(dd < minedgelength); + double new_dd = minedgelength - dd / longest; + double new_eps = new_dd / longest; + printf("You can ignore this warning by using -T%e (default is %e) option.\n", + new_eps, b->epsilon); + printf(" This will allow a short edge (len = %.17g) (default limit is %g).\n", + dd, minedgelength); + } + dir = SELF_INTERSECT; + } + } + + if ((dir != SELF_INTERSECT) && (steinerflag >= 2)) { + success = 0; // queue this subface. + // Failed to split a crossing edge/face. + if ((ivf.iloc == ONVERTEX) || (ivf.iloc == NEARVERTEX)) { + // Get the existing vertex (must be a Steiner point). + if (dir == ACROSSEDGE) { + int idir; + if (add_steinerpt_to_recover_edge(startpt, endpt, NULL, 0, 1, idir)) { + // A Steiner point is inserted. + // Push this subface back to stack, to recover it again. + subfacstack->newindex((void **) &parysh); + *parysh = searchsh; + success = 1; + } + } else if (dir == ACROSSFACE) { + // to do... + terminatetetgen(this, 2); + } else { + terminatetetgen(this, 2); // not possible. + } + } else if (ivf.iloc == NULLCAVITY) { + // Collect a list of bad quality tets which prevent the + // insertion of this Steiner point. + terminatetetgen(this, 2); + } else { + terminatetetgen(this, 2); // report a bug. + } + } // if (steinerflag >= 2) + + pointdealloc(steinerpt); + steinerpt = NULL; + //success = 0; // queue this subface. + } + } // if (dir != SELF_INTERSECT) + } // if ((dir != SELF_INTERSECT) && steinerflag > 0) + } // if (i == 2) else + + if (success) continue; // recover the next subface. + + if (dir == (int) SELF_INTERSECT) { + // Found a self-intersection. This subface cannot be recovered. + // Save it in a separate list, and remove it from the subface pool. + if (skipped_facet_list == NULL) { + skipped_facet_list = new arraypool(sizeof(badface), 10); + } + badface *bf; + skipped_facet_list->newindex((void **) &bf); + bf->init(); + bf->ss = searchsh; + bf->forg = (point) searchsh.sh[3]; + bf->fdest = (point) searchsh.sh[4]; + bf->fapex = (point) searchsh.sh[5]; + bf->key = (double) shellmark(searchsh); + smarktest3(searchsh); // do not recover it later. + continue; // recover the next subface. + } + + // This subface is missing. + if (steinerflag >= 2) { + terminatetetgen(this, 2); + } // if (steinerflag >= 2) + + // Save this subface to recover it later. + misshlist->newindex((void **) &parysh); + *parysh = searchsh; + } // while (subfacstack->objects > 0l) + + return 0; +} + +//============================================================================// +// // +// getvertexstar() Return the star of a vertex. // +// // +// If the flag 'fullstar' is set, return the complete star of this vertex. // +// Otherwise, only a part of the star which is bounded by facets is returned. // +// // +// 'tetlist' returns the list of tets in the star of the vertex 'searchpt'. // +// Every tet in 'tetlist' is at the face opposing to 'searchpt'. // +// // +// 'vertlist' returns the list of vertices in the star (exclude 'searchpt'). // +// // +// 'shlist' returns the list of subfaces in the star. Each subface must face // +// to the interior of this star. // +// // +//============================================================================// + +int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, + arraypool* vertlist, arraypool* shlist) +{ + triface searchtet, neightet, *parytet; + face checksh, *parysh; + point pt, *parypt; + int collectflag; + int t1ver; + int i, j; + + point2tetorg(searchpt, searchtet); + + // Go to the opposite face (the link face) of the vertex. + enextesymself(searchtet); + //assert(oppo(searchtet) == searchpt); + infect(searchtet); // Collect this tet (link face). + tetlist->newindex((void **) &parytet); + *parytet = searchtet; + if (vertlist != NULL) { + // Collect three (link) vertices. + j = (searchtet.ver & 3); // The current vertex index. + for (i = 1; i < 4; i++) { + pt = (point) searchtet.tet[4 + ((j + i) % 4)]; + pinfect(pt); + vertlist->newindex((void **) &parypt); + *parypt = pt; + } + } + + collectflag = 1; + esym(searchtet, neightet); + if (issubface(neightet)) { + if (shlist != NULL) { + tspivot(neightet, checksh); + if (!sinfected(checksh)) { + // Collect this subface (link edge). + sinfect(checksh); + shlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + if (!fullstar) { + collectflag = 0; + } + } + if (collectflag) { + fsymself(neightet); // Goto the adj tet of this face. + esymself(neightet); // Goto the oppo face of this vertex. + // assert(oppo(neightet) == searchpt); + infect(neightet); // Collect this tet (link face). + tetlist->newindex((void **) &parytet); + *parytet = neightet; + if (vertlist != NULL) { + // Collect its apex. + pt = apex(neightet); + pinfect(pt); + vertlist->newindex((void **) &parypt); + *parypt = pt; + } + } // if (collectflag) + + // Continue to collect all tets in the star. + for (i = 0; i < tetlist->objects; i++) { + searchtet = * (triface *) fastlookup(tetlist, i); + // Note that 'searchtet' is a face opposite to 'searchpt', and the neighbor + // tet at the current edge is already collected. + // Check the neighbors at the other two edges of this face. + for (j = 0; j < 2; j++) { + collectflag = 1; + enextself(searchtet); + esym(searchtet, neightet); + if (issubface(neightet)) { + if (shlist != NULL) { + tspivot(neightet, checksh); + if (!sinfected(checksh)) { + // Collect this subface (link edge). + sinfect(checksh); + shlist->newindex((void **) &parysh); + *parysh = checksh; + } + } + if (!fullstar) { + collectflag = 0; + } + } + if (collectflag) { + fsymself(neightet); + if (!infected(neightet)) { + esymself(neightet); // Go to the face opposite to 'searchpt'. + infect(neightet); + tetlist->newindex((void **) &parytet); + *parytet = neightet; + if (vertlist != NULL) { + // Check if a vertex is collected. + pt = apex(neightet); + if (!pinfected(pt)) { + pinfect(pt); + vertlist->newindex((void **) &parypt); + *parypt = pt; + } + } + } // if (!infected(neightet)) + } // if (collectflag) + } // j + } // i + + + // Uninfect the list of tets and vertices. + for (i = 0; i < tetlist->objects; i++) { + parytet = (triface *) fastlookup(tetlist, i); + uninfect(*parytet); + } + + if (vertlist != NULL) { + for (i = 0; i < vertlist->objects; i++) { + parypt = (point *) fastlookup(vertlist, i); + puninfect(*parypt); + } + } + + if (shlist != NULL) { + for (i = 0; i < shlist->objects; i++) { + parysh = (face *) fastlookup(shlist, i); + suninfect(*parysh); + } + } + + return (int) tetlist->objects; +} + +//============================================================================// +// // +// getedge() Get a tetrahedron having the two endpoints. // +// // +// The method here is to search the second vertex in the link faces of the // +// first vertex. The global array 'cavetetlist' is re-used for searching. // +// // +// This function is used for the case when the mesh is non-convex. Otherwise, // +// the function finddirection() should be faster than this. // +// // +//============================================================================// + +int tetgenmesh::getedge(point e1, point e2, triface *tedge) +{ + triface searchtet, neightet, *parytet; + point pt; + int done; + int i, j; + + if (e1 == NULL || e2 == NULL) { + return 0; + } + if ((pointtype(e1) == UNUSEDVERTEX) || + (pointtype(e2) == UNUSEDVERTEX)) { + return 0; + } + + // Quickly check if 'tedge' is just this edge. + if (!isdeadtet(*tedge)) { + if (org(*tedge) == e1) { + if (dest(*tedge) == e2) { + return 1; + } + } else if (org(*tedge) == e2) { + if (dest(*tedge) == e1) { + esymself(*tedge); + return 1; + } + } + } + + // Search for the edge [e1, e2]. + point2tetorg(e1, *tedge); + finddirection(tedge, e2); + if (dest(*tedge) == e2) { + return 1; + } else { + // Search for the edge [e2, e1]. + point2tetorg(e2, *tedge); + finddirection(tedge, e1); + if (dest(*tedge) == e1) { + esymself(*tedge); + return 1; + } + } + + + // Go to the link face of e1. + point2tetorg(e1, searchtet); + enextesymself(searchtet); + arraypool *tetlist = cavebdrylist; + + // Search e2. + for (i = 0; i < 3; i++) { + pt = apex(searchtet); + if (pt == e2) { + // Found. 'searchtet' is [#,#,e2,e1]. + eorgoppo(searchtet, *tedge); // [e1,e2,#,#]. + return 1; + } + enextself(searchtet); + } + + // Get the adjacent link face at 'searchtet'. + fnext(searchtet, neightet); + esymself(neightet); + // assert(oppo(neightet) == e1); + pt = apex(neightet); + if (pt == e2) { + // Found. 'neightet' is [#,#,e2,e1]. + eorgoppo(neightet, *tedge); // [e1,e2,#,#]. + return 1; + } + + // Continue searching in the link face of e1. + infect(searchtet); + tetlist->newindex((void **) &parytet); + *parytet = searchtet; + infect(neightet); + tetlist->newindex((void **) &parytet); + *parytet = neightet; + + done = 0; + + for (i = 0; (i < tetlist->objects) && !done; i++) { + parytet = (triface *) fastlookup(tetlist, i); + searchtet = *parytet; + for (j = 0; (j < 2) && !done; j++) { + enextself(searchtet); + fnext(searchtet, neightet); + if (!infected(neightet)) { + esymself(neightet); + pt = apex(neightet); + if (pt == e2) { + // Found. 'neightet' is [#,#,e2,e1]. + eorgoppo(neightet, *tedge); + done = 1; + } else { + infect(neightet); + tetlist->newindex((void **) &parytet); + *parytet = neightet; + } + } + } // j + } // i + + // Uninfect the list of visited tets. + for (i = 0; i < tetlist->objects; i++) { + parytet = (triface *) fastlookup(tetlist, i); + uninfect(*parytet); + } + tetlist->restart(); + + return done; +} + +//============================================================================// +// // +// reduceedgesatvertex() Reduce the number of edges at a given vertex. // +// // +// 'endptlist' contains the endpoints of edges connecting at the vertex. // +// // +//============================================================================// + +int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) +{ + triface searchtet; + point *pendpt, *parypt; + enum interresult dir; + flipconstraints fc; + int reduceflag; + int count; + int n, i, j; + + + fc.remvert = startpt; + fc.checkflipeligibility = 1; + + while (1) { + + count = 0; + + for (i = 0; i < endptlist->objects; i++) { + pendpt = (point *) fastlookup(endptlist, i); + if (*pendpt == dummypoint) { + continue; // Do not reduce a virtual edge. + } + reduceflag = 0; + // Find the edge. + if (nonconvex) { + if (getedge(startpt, *pendpt, &searchtet)) { + dir = ACROSSVERT; + } else { + // The edge does not exist (was flipped). + dir = INTERSECT; + } + } else { + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, *pendpt); + } + if (dir == ACROSSVERT) { + if (dest(searchtet) == *pendpt) { + // Do not flip a segment. + if (!issubseg(searchtet)) { + n = removeedgebyflips(&searchtet, &fc); + if (n == 2) { + reduceflag = 1; + } + } + } + else { + terminatetetgen(this, 2); + } + } else { + // The edge has been flipped. + reduceflag = 1; + } + if (reduceflag) { + count++; + // Move the last vertex into this slot. + j = endptlist->objects - 1; + parypt = (point *) fastlookup(endptlist, j); + *pendpt = *parypt; + endptlist->objects--; + i--; + } + } // i + + if (count == 0) { + // No edge is reduced. + break; + } + + } // while (1) + + return (int) endptlist->objects; +} + +//============================================================================// +// // +// removevertexbyflips() Remove a vertex by flips. // +// // +// This routine attempts to remove the given vertex 'rempt' (p) from the // +// tetrahedralization (T) by a sequence of flips. // +// // +// The algorithm used here is a simple edge reduce method. Suppose there are // +// n edges connected at p. We try to reduce the number of edges by flipping // +// any edge (not a segment) that is connecting at p. // +// // +// Unless T is a Delaunay tetrahedralization, there is no guarantee that 'p' // +// can be successfully removed. // +// // +//============================================================================// + +int tetgenmesh::removevertexbyflips(point steinerpt) +{ + triface *fliptets = NULL, wrktets[4]; + triface searchtet, spintet, neightet; + face parentsh, spinsh, checksh; + face leftseg, rightseg, checkseg; + point lpt = NULL, rpt = NULL, apexpt; //, *parypt; + flipconstraints fc; + enum verttype vt; + enum locateresult loc; + int valence, removeflag; + int slawson; + int t1ver; + int n, i; + + vt = pointtype(steinerpt); + + + if (vt == FREESEGVERTEX) { + sdecode(point2sh(steinerpt), leftseg); + leftseg.shver = 0; + if (sdest(leftseg) == steinerpt) { + senext(leftseg, rightseg); + spivotself(rightseg); + rightseg.shver = 0; + } else { + rightseg = leftseg; + senext2(rightseg, leftseg); + spivotself(leftseg); + leftseg.shver = 0; + } + lpt = sorg(leftseg); + rpt = sdest(rightseg); + + // Check if both leftseg and rightseg are recovered in tet mesh. + sstpivot1(leftseg, neightet); + if (neightet.tet == NULL) { + return 0; // Do not remove this Steiner point. + } + sstpivot1(rightseg, neightet); + if (neightet.tet == NULL) { + return 0; // Do not remove this Steiner point. + } + + if (b->verbose > 2) { + printf(" Removing Steiner point %d in segment (%d, %d).\n", + pointmark(steinerpt), pointmark(lpt), pointmark(rpt)); + + } + } else if (vt == FREEFACETVERTEX) { + if (b->verbose > 2) { + printf(" Removing Steiner point %d in facet.\n", + pointmark(steinerpt)); + } + } else if (vt == FREEVOLVERTEX) { + if (b->verbose > 2) { + printf(" Removing Steiner point %d in volume.\n", + pointmark(steinerpt)); + } + } else if (vt == VOLVERTEX) { + if (b->verbose > 2) { + printf(" Removing a point %d in volume.\n", + pointmark(steinerpt)); + } + } else { + // It is not a Steiner point. + return 0; + } + + // Try to reduce the number of edges at 'p' by flips. + getvertexstar(1, steinerpt, cavetetlist, cavetetvertlist, NULL); + cavetetlist->restart(); // This list may be re-used. + if (cavetetvertlist->objects > 3l) { + valence = reduceedgesatvertex(steinerpt, cavetetvertlist); + } else { + valence = cavetetvertlist->objects; + } + cavetetvertlist->restart(); + + removeflag = 0; + + if (valence == 4) { + // Only 4 vertices (4 tets) left! 'p' is inside the convex hull of the 4 + // vertices. This case is due to that 'p' is not exactly on the segment. + point2tetorg(steinerpt, searchtet); + loc = INTETRAHEDRON; + removeflag = 1; + } else if (valence == 5) { + // There are 5 edges. + if (vt == FREESEGVERTEX) { + sstpivot1(leftseg, searchtet); + if (org(searchtet) != steinerpt) { + esymself(searchtet); + } + i = 0; // Count the numbe of tet at the edge [p,lpt]. + neightet.tet = NULL; // Init the face. + spintet = searchtet; + while (1) { + i++; + if (apex(spintet) == rpt) { + // Remember the face containing the edge [lpt, rpt]. + neightet = spintet; + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + if (i == 3) { + // This case has been checked below. + } else if (i == 4) { + // There are 4 tets sharing at [p,lpt]. There must be 4 tets sharing + // at [p,rpt]. There must be a face [p, lpt, rpt]. + if (apex(neightet) == rpt) { + // The edge (segment) has been already recovered! + // Check if a 6-to-2 flip is possible (to remove 'p'). + // Let 'searchtet' be [p,d,a,b] + esym(neightet, searchtet); + enextself(searchtet); + // Check if there are exactly three tets at edge [p,d]. + wrktets[0] = searchtet; // [p,d,a,b] + for (i = 0; i < 2; i++) { + fnext(wrktets[i], wrktets[i+1]); // [p,d,b,c], [p,d,c,a] + } + if (apex(wrktets[0]) == oppo(wrktets[2])) { + loc = ONFACE; + removeflag = 1; + } + } + } + } else if (vt == FREEFACETVERTEX) { + // It is possible to do a 6-to-2 flip to remove the vertex. + point2tetorg(steinerpt, searchtet); + // Get the three faces of 'searchtet' which share at p. + // All faces has p as origin. + wrktets[0] = searchtet; + wrktets[1] = searchtet; + esymself(wrktets[1]); + enextself(wrktets[1]); + wrktets[2] = searchtet; + eprevself(wrktets[2]); + esymself(wrktets[2]); + // All internal edges of the six tets have valance either 3 or 4. + // Get one edge which has valance 3. + searchtet.tet = NULL; + for (i = 0; i < 3; i++) { + spintet = wrktets[i]; + valence = 0; + while (1) { + valence++; + fnextself(spintet); + if (spintet.tet == wrktets[i].tet) break; + } + if (valence == 3) { + // Found the edge. + searchtet = wrktets[i]; + break; + } + } + // Note, we do not detach the three subfaces at p. + // They will be removed within a 4-to-1 flip. + loc = ONFACE; + removeflag = 1; + } + //removeflag = 1; + } + + if (!removeflag) { + if (vt == FREESEGVERTEX) { + // Check is it possible to recover the edge [lpt,rpt]. + // The condition to check is: Whether each tet containing 'leftseg' is + // adjacent to a tet containing 'rightseg'. + sstpivot1(leftseg, searchtet); + if (org(searchtet) != steinerpt) { + esymself(searchtet); + } + spintet = searchtet; + while (1) { + // Go to the bottom face of this tet. + eprev(spintet, neightet); + esymself(neightet); // [steinerpt, p1, p2, lpt] + // Get the adjacent tet. + fsymself(neightet); // [p1, steinerpt, p2, rpt] + if (oppo(neightet) != rpt) { + // Found a non-matching adjacent tet. + break; + } + { + // [2017-10-15] Check if the tet is inverted? + point chkp1 = org(neightet); + point chkp2 = apex(neightet); + REAL chkori = orient3d(rpt, lpt, chkp1, chkp2); + if (chkori >= 0.0) { + // Either inverted or degenerated. + break; + } + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) { + // 'searchtet' is [p,d,p1,p2]. + loc = ONEDGE; + removeflag = 1; + break; + } + } + } // if (vt == FREESEGVERTEX) + } + + if (!removeflag) { + if (vt == FREESEGVERTEX) { + // Check if the edge [lpt, rpt] exists. + if (getedge(lpt, rpt, &searchtet)) { + // We have recovered this edge. Shift the vertex into the volume. + // We can recover this edge if the subfaces are not recovered yet. + if (!checksubfaceflag) { + // Remove the vertex from the surface mesh. + // This will re-create the segment [lpt, rpt] and re-triangulate + // all the facets at the segment. + // Detach the subsegments from their surrounding tets. + for (i = 0; i < 2; i++) { + checkseg = (i == 0) ? leftseg : rightseg; + sstpivot1(checkseg, neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + sstdissolve1(checkseg); + } // i + slawson = 1; // Do lawson flip after removal. + spivot(rightseg, parentsh); // 'rightseg' has p as its origin. + sremovevertex(steinerpt, &parentsh, &rightseg, slawson); + // Clear the list for new subfaces. + caveshbdlist->restart(); + // Insert the new segment. + sstbond1(rightseg, searchtet); + spintet = searchtet; + while (1) { + tssbond1(spintet, rightseg); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + // The Steiner point has been shifted into the volume. + setpointtype(steinerpt, FREEVOLVERTEX); + st_segref_count--; + st_volref_count++; + return 1; + } // if (!checksubfaceflag) + } // if (getedge(...)) + } // if (vt == FREESEGVERTEX) + } // if (!removeflag) + + if (!removeflag) { + return 0; + } + + if (vt == FREESEGVERTEX) { + // Detach the subsegments from their surronding tets. + for (i = 0; i < 2; i++) { + checkseg = (i == 0) ? leftseg : rightseg; + sstpivot1(checkseg, neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + sstdissolve1(checkseg); + } // i + if (checksubfaceflag) { + // Detach the subfaces at the subsegments from their attached tets. + for (i = 0; i < 2; i++) { + checkseg = (i == 0) ? leftseg : rightseg; + spivot(checkseg, parentsh); + if (parentsh.sh != NULL) { + spinsh = parentsh; + while (1) { + stpivot(spinsh, neightet); + if (neightet.tet != NULL) { + tsdissolve(neightet); + } + sesymself(spinsh); + stpivot(spinsh, neightet); + if (neightet.tet != NULL) { + tsdissolve(neightet); + } + stdissolve(spinsh); + spivotself(spinsh); // Go to the next subface. + if (spinsh.sh == parentsh.sh) break; + } + } + } // i + } // if (checksubfaceflag) + } + + if (loc == INTETRAHEDRON) { + // Collect the four tets containing 'p'. + fliptets = new triface[4]; + fliptets[0] = searchtet; // [p,d,a,b] + for (i = 0; i < 2; i++) { + fnext(fliptets[i], fliptets[i+1]); // [p,d,b,c], [p,d,c,a] + } + eprev(fliptets[0], fliptets[3]); + fnextself(fliptets[3]); // it is [a,p,b,c] + eprevself(fliptets[3]); + esymself(fliptets[3]); // [a,b,c,p]. + if (vt == FREEFACETVERTEX) { + // [2018-03-08] Check if the last 4-to-1 flip is valid. + // fliptets[0],[1],[2] are [p,d,a,b],[p,d,b,c],[p,d,c,a] + triface checktet, chkface; + for (i = 0; i < 3; i++) { + enext(fliptets[i], checktet); + esymself(checktet); // [a,d,b,p],[b,d,c,p],[c,d,a,p] + int scount = 0; int k; + for (k = 0; k < 3; k++) { + esym(checktet, chkface); + if (issubface(chkface)) scount++; + enextself(checktet); + } + if (scount == 3) { + break; // Found a tet which support a 3-to-1 flip. + } else if (scount == 2) { + // This is a strange configuration. Debug it. + // Do not do this flip. + delete [] fliptets; + return 0; + } + } + if (i == 3) { + // No tet in [p,d,a,b],[p,d,b,c],[p,d,c,a] support it. + int scount = 0; + for (i = 0; i < 3; i++) { + eprev(fliptets[i], checktet); + esymself(checktet); // [p,a,b,d],[p,b,c,d],[p,c,a,d] + if (issubface(chkface)) scount++; + } + if (scount != 3) { + // Do not do this flip. + delete [] fliptets; + return 0; + } + } + } // if (vt == FREEFACETVERTEX) + flip41(fliptets, 1, &fc); + //recenttet = fliptets[0]; + } else if (loc == ONFACE) { + // Let the original two tets be [a,b,c,d] and [b,a,c,e]. And p is in + // face [a,b,c]. Let 'searchtet' be the tet [p,d,a,b]. + // Collect the six tets containing 'p'. + fliptets = new triface[6]; + fliptets[0] = searchtet; // [p,d,a,b] + for (i = 0; i < 2; i++) { + fnext(fliptets[i], fliptets[i+1]); // [p,d,b,c], [p,d,c,a] + } + eprev(fliptets[0], fliptets[3]); + fnextself(fliptets[3]); // [a,p,b,e] + esymself(fliptets[3]); // [p,a,e,b] + eprevself(fliptets[3]); // [e,p,a,b] + for (i = 3; i < 5; i++) { + fnext(fliptets[i], fliptets[i+1]); // [e,p,b,c], [e,p,c,a] + } + if (vt == FREEFACETVERTEX) { + // We need to determine the location of three subfaces at p. + valence = 0; // Re-use it. + for (i = 3; i < 6; i++) { + if (issubface(fliptets[i])) valence++; + } + if (valence > 0) { + // We must do 3-to-2 flip in the upper part. We simply re-arrange + // the six tets. + for (i = 0; i < 3; i++) { + esym(fliptets[i+3], wrktets[i]); + esym(fliptets[i], fliptets[i+3]); + fliptets[i] = wrktets[i]; + } + // Swap the last two pairs, i.e., [1]<->[[2], and [4]<->[5] + wrktets[1] = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = wrktets[1]; + wrktets[1] = fliptets[4]; + fliptets[4] = fliptets[5]; + fliptets[5] = wrktets[1]; + } + // [2018-03-08] Check if the last 4-to-1 flip is valid. + // fliptets[0],[1],[2] are [p,d,a,b],[p,d,b,c],[p,d,c,a] + triface checktet, chkface; + for (i = 0; i < 3; i++) { + enext(fliptets[i], checktet); + esymself(checktet); // [a,d,b,p],[b,d,c,p],[c,d,a,p] + int scount = 0; int k; + for (k = 0; k < 3; k++) { + esym(checktet, chkface); + if (issubface(chkface)) scount++; + enextself(checktet); + } + if (scount == 3) { + break; // Found a tet which support a 3-to-1 flip. + } else if (scount == 2) { + // This is a strange configuration. Debug it. + // Do not do this flip. + delete [] fliptets; + return 0; + } + } + if (i == 3) { + // No tet in [p,d,a,b],[p,d,b,c],[p,d,c,a] support it. + int scount = 0; + for (i = 0; i < 3; i++) { + eprev(fliptets[i], checktet); + esymself(checktet); // [p,a,b,d],[p,b,c,d],[p,c,a,d] + if (issubface(chkface)) scount++; + } + if (scount != 3) { + // Do not do this flip. + delete [] fliptets; + return 0; + } + } + } // vt == FREEFACETVERTEX + // Remove p by a 6-to-2 flip, which is a combination of two flips: + // a 3-to-2 (deletes the edge [e,p]), and + // a 4-to-1 (deletes the vertex p). + // First do a 3-to-2 flip on [e,p,a,b],[e,p,b,c],[e,p,c,a]. It creates + // two new tets: [a,b,c,p] and [b,a,c,e]. The new tet [a,b,c,p] is + // degenerate (has zero volume). It will be deleted in the followed + // 4-to-1 flip. + //flip32(&(fliptets[3]), 1, 0, 0); + flip32(&(fliptets[3]), 1, &fc); + // Second do a 4-to-1 flip on [p,d,a,b],[p,d,b,c],[p,d,c,a],[a,b,c,p]. + // This creates a new tet [a,b,c,d]. + //flip41(fliptets, 1, 0, 0); + flip41(fliptets, 1, &fc); + //recenttet = fliptets[0]; + } else if (loc == ONEDGE) { + // Let the original edge be [e,d] and p is in [e,d]. Assume there are n + // tets sharing at edge [e,d] originally. We number the link vertices + // of [e,d]: p_0, p_1, ..., p_n-1. 'searchtet' is [p,d,p_0,p_1]. + // Count the number of tets at edge [e,p] and [p,d] (this is n). + n = 0; + spintet = searchtet; + while (1) { + n++; + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + // Collect the 2n tets containing 'p'. + fliptets = new triface[2 * n]; + fliptets[0] = searchtet; // [p,b,p_0,p_1] + for (i = 0; i < (n - 1); i++) { + fnext(fliptets[i], fliptets[i+1]); // [p,d,p_i,p_i+1]. + } + eprev(fliptets[0], fliptets[n]); + fnextself(fliptets[n]); // [p_0,p,p_1,e] + esymself(fliptets[n]); // [p,p_0,e,p_1] + eprevself(fliptets[n]); // [e,p,p_0,p_1] + for (i = n; i < (2 * n - 1); i++) { + fnext(fliptets[i], fliptets[i+1]); // [e,p,p_i,p_i+1]. + } + // Remove p by a 2n-to-n flip, it is a sequence of n flips: + // - Do a 2-to-3 flip on + // [p_0,p_1,p,d] and + // [p,p_1,p_0,e]. + // This produces: + // [e,d,p_0,p_1], + // [e,d,p_1,p] (degenerated), and + // [e,d,p,p_0] (degenerated). + wrktets[0] = fliptets[0]; // [p,d,p_0,p_1] + eprevself(wrktets[0]); // [p_0,p,d,p_1] + esymself(wrktets[0]); // [p,p_0,p_1,d] + enextself(wrktets[0]); // [p_0,p_1,p,d] [0] + wrktets[1] = fliptets[n]; // [e,p,p_0,p_1] + enextself(wrktets[1]); // [p,p_0,e,p_1] + esymself(wrktets[1]); // [p_0,p,p_1,e] + eprevself(wrktets[1]); // [p_1,p_0,p,e] [1] + //flip23(wrktets, 1, 0, 0); + flip23(wrktets, 1, &fc); + // Save the new tet [e,d,p,p_0] (degenerated). + fliptets[n] = wrktets[2]; + // Save the new tet [e,d,p_0,p_1]. + fliptets[0] = wrktets[0]; + // - Repeat from i = 1 to n-2: (n - 2) flips + // - Do a 3-to-2 flip on + // [p,p_i,d,e], + // [p,p_i,e,p_i+1], and + // [p,p_i,p_i+1,d]. + // This produces: + // [d,e,p_i+1,p_i], and + // [e,d,p_i+1,p] (degenerated). + for (i = 1; i < (n - 1); i++) { + wrktets[0] = wrktets[1]; // [e,d,p_i,p] (degenerated). + enextself(wrktets[0]); // [d,p_i,e,p] (...) + esymself(wrktets[0]); // [p_i,d,p,e] (...) + eprevself(wrktets[0]); // [p,p_i,d,e] (degenerated) [0]. + wrktets[1] = fliptets[n+i]; // [e,p,p_i,p_i+1] + enextself(wrktets[1]); // [p,p_i,e,p_i+1] [1] + wrktets[2] = fliptets[i]; // [p,d,p_i,p_i+1] + eprevself(wrktets[2]); // [p_i,p,d,p_i+1] + esymself(wrktets[2]); // [p,p_i,p_i+1,d] [2] + //flip32(wrktets, 1, 0, 0); + flip32(wrktets, 1, &fc); + // Save the new tet [e,d,p_i,p_i+1]. // FOR DEBUG ONLY + fliptets[i] = wrktets[0]; // [d,e,p_i+1,p_i] // FOR DEBUG ONLY + esymself(fliptets[i]); // [e,d,p_i,p_i+1] // FOR DEBUG ONLY + } + // - Do a 4-to-1 flip on + // [p,p_0,e,d], [d,e,p_0,p], + // [p,p_0,d,p_n-1], [e,p_n-1,p_0,p], + // [p,p_0,p_n-1,e], [p_0,p_n-1,d,p], and + // [e,d,p_n-1,p]. + // This produces + // [e,d,p_n-1,p_0] and + // deletes p. + wrktets[3] = wrktets[1]; // [e,d,p_n-1,p] (degenerated) [3] + wrktets[0] = fliptets[n]; // [e,d,p,p_0] (degenerated) + eprevself(wrktets[0]); // [p,e,d,p_0] (...) + esymself(wrktets[0]); // [e,p,p_0,d] (...) + enextself(wrktets[0]); // [p,p_0,e,d] (degenerated) [0] + wrktets[1] = fliptets[n-1]; // [p,d,p_n-1,p_0] + esymself(wrktets[1]); // [d,p,p_0,p_n-1] + enextself(wrktets[1]); // [p,p_0,d,p_n-1] [1] + wrktets[2] = fliptets[2*n-1]; // [e,p,p_n-1,p_0] + enextself(wrktets[2]); // [p_p_n-1,e,p_0] + esymself(wrktets[2]); // [p_n-1,p,p_0,e] + enextself(wrktets[2]); // [p,p_0,p_n-1,e] [2] + //flip41(wrktets, 1, 0, 0); + flip41(wrktets, 1, &fc); + // Save the new tet [e,d,p_n-1,p_0] // FOR DEBUG ONLY + fliptets[n-1] = wrktets[0]; // [e,d,p_n-1,p_0] // FOR DEBUG ONLY + //recenttet = fliptets[0]; + } + + delete [] fliptets; + + if (vt == FREESEGVERTEX) { + // Remove the vertex from the surface mesh. + // This will re-create the segment [lpt, rpt] and re-triangulate + // all the facets at the segment. + // Only do lawson flip when subfaces are not recovery yet. + slawson = (checksubfaceflag ? 0 : 1); + spivot(rightseg, parentsh); // 'rightseg' has p as its origin. + sremovevertex(steinerpt, &parentsh, &rightseg, slawson); + + // The original segment is returned in 'rightseg'. + rightseg.shver = 0; + // Insert the new segment. + point2tetorg(lpt, searchtet); + finddirection(&searchtet, rpt); + if (dest(searchtet) != rpt) { + terminatetetgen(this, 2); + } + sstbond1(rightseg, searchtet); + spintet = searchtet; + while (1) { + tssbond1(spintet, rightseg); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + + if (checksubfaceflag) { + // Insert subfaces at segment [lpt,rpt] into the tetrahedralization. + spivot(rightseg, parentsh); + if (parentsh.sh != NULL) { + spinsh = parentsh; + while (1) { + if (sorg(spinsh) != lpt) { + sesymself(spinsh); + } + apexpt = sapex(spinsh); + // Find the adjacent tet of [lpt,rpt,apexpt]; + spintet = searchtet; + while (1) { + if (apex(spintet) == apexpt) { + tsbond(spintet, spinsh); + sesymself(spinsh); // Get to another side of this face. + fsym(spintet, neightet); + tsbond(neightet, spinsh); + sesymself(spinsh); // Get back to the original side. + break; + } + fnextself(spintet); + } + spivotself(spinsh); + if (spinsh.sh == parentsh.sh) break; + } + } + } // if (checksubfaceflag) + + // Clear the set of new subfaces. + caveshbdlist->restart(); + } // if (vt == FREESEGVERTEX) + + // The point has been removed. + if (pointtype(steinerpt) != UNUSEDVERTEX) { + setpointtype(steinerpt, UNUSEDVERTEX); + unuverts++; + } + if (vt != VOLVERTEX) { + // Update the correspinding counters. + if (vt == FREESEGVERTEX) { + st_segref_count--; + } else if (vt == FREEFACETVERTEX) { + st_facref_count--; + } else if (vt == FREEVOLVERTEX) { + st_volref_count--; + } + if (steinerleft > 0) steinerleft++; + } + + return 1; +} + +//============================================================================// +// // +// smoothpoint() Moving a vertex to improve the mesh quality. // +// // +// 'smtpt' (p) is a point to be smoothed. Generally, it is a Steiner point. // +// It may be not a vertex of the mesh. // +// // +// This routine tries to move 'p' inside its star until a selected objective // +// function over all tetrahedra in the star is improved. The function may be // +// the some quality measures, i.e., aspect ratio, maximum dihedral angel, or // +// simply the volume of the tetrahedra. // +// // +// 'linkfacelist' contains the list of link faces of 'p'. Since a link face // +// has two orientations, ccw or cw, with respect to 'p'. 'ccw' indicates // +// the orientation is ccw (1) or not (0). // +// // +// 'opm' is a structure contains the parameters of the objective function. // +// It is needed by the evaluation of the function value. // +// // +// The return value indicates weather the point is smoothed or not. // +// // +// ASSUMPTION: This routine assumes that all link faces are true faces, i.e, // +// no face has 'dummypoint' as its vertex. // +// // +//============================================================================// + +int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, + optparameters *opm) +{ + triface *parytet, *parytet1, swaptet; + badface bf; + point pa, pb, pc; + REAL fcent[3], startpt[3], nextpt[3], bestpt[3]; + REAL oldval, minval = 0.0, val; + REAL maxcosd; // oldang, newang; + REAL ori, diff; + int numdirs, iter; + int i, j, k; + + // Decide the number of moving directions. + numdirs = (int) linkfacelist->objects; + if (numdirs > opm->numofsearchdirs) { + numdirs = opm->numofsearchdirs; // Maximum search directions. + } + + // Set the initial value. + opm->imprval = opm->initval; + iter = 0; + + for (i = 0; i < 3; i++) { + bestpt[i] = startpt[i] = smtpt[i]; + } + + // Iterate until the obj function is not improved. + while (1) { + + // Find the best next location. + oldval = opm->imprval; + + for (i = 0; i < numdirs; i++) { + // Randomly pick a link face (0 <= k <= objects - i - 1). + k = (int) randomnation(linkfacelist->objects - i); + parytet = (triface *) fastlookup(linkfacelist, k); + // Calculate a new position from 'p' to the center of this face. + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + for (j = 0; j < 3; j++) { + fcent[j] = (pa[j] + pb[j] + pc[j]) / 3.0; + } + for (j = 0; j < 3; j++) { + nextpt[j] = startpt[j] + opm->searchstep * (fcent[j] - startpt[j]); + } + // Calculate the largest minimum function value for the new location. + for (j = 0; j < linkfacelist->objects; j++) { + parytet = (triface *) fastlookup(linkfacelist, j); + if (ccw) { + pa = org(*parytet); + pb = dest(*parytet); + } else { + pb = org(*parytet); + pa = dest(*parytet); + } + pc = apex(*parytet); + ori = orient3d(pa, pb, pc, nextpt); + if (ori < 0.0) { + // Calcuate the objective function value. + if (opm->max_min_volume) { + //val = -ori; + val = - orient3dfast(pa, pb, pc, nextpt); + } else if (opm->min_max_aspectratio) { + get_tetqual(pa, pb, pc, nextpt, &bf); + val = 1.0 / bf.key; + } else if (opm->min_max_dihedangle) { + get_tetqual(pa, pb, pc, nextpt, &bf); + maxcosd = bf.cent[0]; + if (maxcosd < -1) maxcosd = -1.0; // Rounding. + val = maxcosd + 1.0; // Make it be positive. + } else { + // Unknown objective function. + val = 0.0; + } + } else { // ori >= 0.0; + // An invalid new tet. + // This may happen if the mesh contains inverted elements. + if (opm->max_min_volume) { + //val = -ori; + val = - orient3dfast(pa, pb, pc, nextpt); + } else { + // Discard this point. + break; // j + } + } // if (ori >= 0.0) + // Stop looping when the object value is not improved. + if (val <= opm->imprval) { + break; // j + } else { + // Remember the smallest improved value. + if (j == 0) { + minval = val; + } else { + minval = (val < minval) ? val : minval; + } + } + } // j + if (j == linkfacelist->objects) { + // The function value has been improved. + opm->imprval = minval; + // Save the new location of the point. + for (j = 0; j < 3; j++) bestpt[j] = nextpt[j]; + } + // Swap k-th and (object-i-1)-th entries. + j = linkfacelist->objects - i - 1; + parytet = (triface *) fastlookup(linkfacelist, k); + parytet1 = (triface *) fastlookup(linkfacelist, j); + swaptet = *parytet1; + *parytet1 = *parytet; + *parytet = swaptet; + } // i + + diff = opm->imprval - oldval; + if (diff > 0.0) { + // Is the function value improved effectively? + if (opm->max_min_volume) { + //if ((diff / oldval) < b->epsilon) diff = 0.0; + } else if (opm->min_max_aspectratio) { + if ((diff / oldval) < 1e-3) diff = 0.0; + } else if (opm->min_max_dihedangle) { + //oldang = acos(oldval - 1.0); + //newang = acos(opm->imprval - 1.0); + //if ((oldang - newang) < 0.00174) diff = 0.0; // about 0.1 degree. + } else { + // Unknown objective function. + terminatetetgen(this, 2); + } + } + + if (diff > 0.0) { + // Yes, move p to the new location and continue. + for (j = 0; j < 3; j++) startpt[j] = bestpt[j]; + iter++; + if ((opm->maxiter > 0) && (iter >= opm->maxiter)) { + // Maximum smoothing iterations reached. + break; + } + } else { + break; + } + + } // while (1) + + if (iter > 0) { + // The point has been smoothed. + opm->smthiter = iter; // Remember the number of iterations. + // The point has been smoothed. Update it to its new position. + for (i = 0; i < 3; i++) smtpt[i] = startpt[i]; + } + + return iter; +} + +//============================================================================// +// // +// suppressbdrysteinerpoint() Suppress a boundary Steiner point // +// // +//============================================================================// + +int tetgenmesh::suppressbdrysteinerpoint(point steinerpt) +{ + face parentsh, spinsh, *parysh; + face leftseg, rightseg; + point lpt = NULL, rpt = NULL; + int i; + + verttype vt = pointtype(steinerpt); + + if (vt == FREESEGVERTEX) { + sdecode(point2sh(steinerpt), leftseg); + leftseg.shver = 0; + if (sdest(leftseg) == steinerpt) { + senext(leftseg, rightseg); + spivotself(rightseg); + rightseg.shver = 0; + } else { + rightseg = leftseg; + senext2(rightseg, leftseg); + spivotself(leftseg); + leftseg.shver = 0; + } + lpt = sorg(leftseg); + rpt = sdest(rightseg); + if (b->verbose > 2) { + printf(" Suppressing Steiner point %d in segment (%d, %d).\n", + pointmark(steinerpt), pointmark(lpt), pointmark(rpt)); + } + // Get all subfaces at the left segment [lpt, steinerpt]. + spivot(leftseg, parentsh); + if (parentsh.sh != NULL) { + // It is not a dangling segment. + spinsh = parentsh; + while (1) { + cavesegshlist->newindex((void **) &parysh); + *parysh = spinsh; + // Orient the face consistently. + if (sorg(*parysh)!= sorg(parentsh)) sesymself(*parysh); + spivotself(spinsh); + if (spinsh.sh == NULL) break; + if (spinsh.sh == parentsh.sh) break; + } + } + if (cavesegshlist->objects < 2) { + // It is a single segment. Not handle it yet. + cavesegshlist->restart(); + return 0; + } + } else if (vt == FREEFACETVERTEX) { + if (b->verbose > 2) { + printf(" Suppressing Steiner point %d from facet.\n", + pointmark(steinerpt)); + } + sdecode(point2sh(steinerpt), parentsh); + // A facet Steiner point. There are exactly two sectors. + for (i = 0; i < 2; i++) { + cavesegshlist->newindex((void **) &parysh); + *parysh = parentsh; + sesymself(parentsh); + } + } else { + return 0; // no need to suppress it. + } + + triface searchtet, neightet, *parytet; + point pa, pb, pc, pd; + REAL v1[3], v2[3], len, u; + + REAL startpt[3] = {0,}, samplept[3] = {0,}, candpt[3] = {0,}; + REAL ori, minvol, smallvol; + int samplesize; + int it, j, k; + + int n = (int) cavesegshlist->objects; + point *newsteiners = new point[n]; + for (i = 0; i < n; i++) newsteiners[i] = NULL; + + // Search for each sector an interior vertex. + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + stpivot(*parysh, searchtet); + // Skip it if it is outside. + if (ishulltet(searchtet)) continue; + // Get the "half-ball". Tets in 'cavetetlist' all contain 'steinerpt' as + // opposite. Subfaces in 'caveshlist' all contain 'steinerpt' as apex. + // Moreover, subfaces are oriented towards the interior of the ball. + setpoint2tet(steinerpt, encode(searchtet)); + getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); + // Calculate the searching vector. + pa = sorg(*parysh); + pb = sdest(*parysh); + pc = sapex(*parysh); + facenormal(pa, pb, pc, v1, 1, NULL); + len = sqrt(dot(v1, v1)); + v1[0] /= len; + v1[1] /= len; + v1[2] /= len; + if (vt == FREESEGVERTEX) { + parysh = (face *) fastlookup(cavesegshlist, (i + 1) % n); + pd = sapex(*parysh); + facenormal(pb, pa, pd, v2, 1, NULL); + len = sqrt(dot(v2, v2)); + v2[0] /= len; + v2[1] /= len; + v2[2] /= len; + // Average the two vectors. + v1[0] = 0.5 * (v1[0] + v2[0]); + v1[1] = 0.5 * (v1[1] + v2[1]); + v1[2] = 0.5 * (v1[2] + v2[2]); + } + // Search the intersection of the ray starting from 'steinerpt' to + // the search direction 'v1' and the shell of the half-ball. + // - Construct an endpoint. + len = distance(pa, pb); + v2[0] = steinerpt[0] + len * v1[0]; + v2[1] = steinerpt[1] + len * v1[1]; + v2[2] = steinerpt[2] + len * v1[2]; + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + // Test if the ray startpt->v2 lies in the cone: where 'steinerpt' + // is the apex, and three sides are defined by the triangle + // [pa, pb, pc]. + ori = orient3d(steinerpt, pa, pb, v2); + if (ori >= 0) { + ori = orient3d(steinerpt, pb, pc, v2); + if (ori >= 0) { + ori = orient3d(steinerpt, pc, pa, v2); + if (ori >= 0) { + // Found! Calculate the intersection. + planelineint(pa, pb, pc, steinerpt, v2, startpt, &u); + break; + } + } + } + } // j + if (j == cavetetlist->objects) { + break; // There is no intersection!! Debug is needed. + } + // Close the ball by adding the subfaces. + for (j = 0; j < caveshlist->objects; j++) { + parysh = (face *) fastlookup(caveshlist, j); + stpivot(*parysh, neightet); + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; + } + // Search a best point inside the segment [startpt, steinerpt]. + it = 0; + samplesize = 100; + v1[0] = steinerpt[0] - startpt[0]; + v1[1] = steinerpt[1] - startpt[1]; + v1[2] = steinerpt[2] - startpt[2]; + minvol = -1.0; + while (it < 3) { + for (j = 1; j < samplesize - 1; j++) { + samplept[0] = startpt[0] + ((REAL) j / (REAL) samplesize) * v1[0]; + samplept[1] = startpt[1] + ((REAL) j / (REAL) samplesize) * v1[1]; + samplept[2] = startpt[2] + ((REAL) j / (REAL) samplesize) * v1[2]; + // Find the minimum volume for 'samplept'. + smallvol = -1; + for (k = 0; k < cavetetlist->objects; k++) { + parytet = (triface *) fastlookup(cavetetlist, k); + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + ori = orient3d(pb, pa, pc, samplept); + { + // [2017-10-15] Rounding + REAL lab = distance(pa, pb); + REAL lbc = distance(pb, pc); + REAL lca = distance(pc, pa); + REAL lv = (lab + lbc + lca) / 3.0; + REAL l3 = lv*lv*lv; + if (fabs(ori) / l3 < 1e-8) ori = 0.0; + } + if (ori <= 0) { + break; // An invalid tet. + } + if (smallvol == -1) { + smallvol = ori; + } else { + if (ori < smallvol) smallvol = ori; + } + } // k + if (k == cavetetlist->objects) { + // Found a valid point. Remember it. + if (minvol == -1.0) { + candpt[0] = samplept[0]; + candpt[1] = samplept[1]; + candpt[2] = samplept[2]; + minvol = smallvol; + } else { + if (minvol < smallvol) { + // It is a better location. Remember it. + candpt[0] = samplept[0]; + candpt[1] = samplept[1]; + candpt[2] = samplept[2]; + minvol = smallvol; + } else { + // No improvement of smallest volume. + // Since we are searching along the line [startpt, steinerpy], + // The smallest volume can only be decreased later. + break; + } + } + } + } // j + if (minvol > 0) break; + samplesize *= 10; + it++; + } // while (it < 3) + if (minvol == -1.0) { + // Failed to find a valid point. + cavetetlist->restart(); + caveshlist->restart(); + break; + } + // Create a new Steiner point inside this section. + makepoint(&(newsteiners[i]), FREEVOLVERTEX); + newsteiners[i][0] = candpt[0]; + newsteiners[i][1] = candpt[1]; + newsteiners[i][2] = candpt[2]; + cavetetlist->restart(); + caveshlist->restart(); + } // i + + if (i < cavesegshlist->objects) { + // Failed to suppress the vertex. + for (; i > 0; i--) { + if (newsteiners[i - 1] != NULL) { + pointdealloc(newsteiners[i - 1]); + } + } + delete [] newsteiners; + cavesegshlist->restart(); + return 0; + } + + // First insert Steiner points into the mesh. + // 'cavesegshlist' will be used by insertpoint(). + //int nfaces = cavesegshlist->objects; + face *segshlist = new face[n]; + for (i = 0; i < cavesegshlist->objects; i++) { + segshlist[i] = * (face *) fastlookup(cavesegshlist, i); + } + cavesegshlist->restart(); + + for (i = 0; i < n; i++) { + //assert(caveoldtetlist->objects == 0); + //assert(cavetetlist->objects == 0); + parysh = &(segshlist[i]); + // 'parysh' is the face [lpt, steinerpt, #]. + stpivot(*parysh, searchtet); + // Skip it if it is outside. + if (ishulltet(searchtet)) continue; + + // Get the "half-ball". Tets in 'cavetetlist' all contain 'steinerpt' as + // opposite. Subfaces in 'caveshlist' all contain 'steinerpt' as apex. + // Moreover, subfaces are oriented towards the interior of the ball. + setpoint2tet(steinerpt, encode(searchtet)); + getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); + + // Get all tets in this sector. + for (int j = 0; j < cavetetlist->objects; j++) { + neightet = * (triface *) fastlookup(cavetetlist, j); + infect(neightet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = neightet; + } + cavetetlist->restart(); + caveshlist->restart(); + + insertvertexflags ivf; + searchtet = neightet; // No need point location. + ivf.iloc = (int) INSTAR; // No need point location. + // The following are default options. + //ivf.bowywat = 0; + //ivf.lawson = 0; + //ivf.validflag = 0; // no need to validate cavity. + //ivf.chkencflag = 0; //chkencflag; + ivf.assignmeshsize = b->metric; + if (ivf.assignmeshsize) { + // Search the tet containing 'steinerpt' for size interpolation. + locate(newsteiners[i], &searchtet); + } + + // Insert the new point into the tetrahedralization T. + // Note that T is convex (nonconvex = 0). + if (insertpoint(newsteiners[i], &searchtet, NULL, NULL, &ivf)) { + // The vertex has been inserted. + st_volref_count++; + if (steinerleft > 0) steinerleft--; + //return 1; + } else { + // Not inserted. + //assert(0); + pointdealloc(newsteiners[i]); + newsteiners[i] = NULL; + break; //return 0; + } + } // i + + delete [] segshlist; + + if (i < n) { + //assert(0); + delete [] newsteiners; + return 0; + } + + // Now remove the Steiner point from the segment. + if (!removevertexbyflips(steinerpt)) { + //assert(0); + delete [] newsteiners; + return 0; + } + + // We've removed a Steiner points. + setpointtype(steinerpt, UNUSEDVERTEX); + unuverts++; + + int steinercount = 0; + + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 100000; // Unlimited flip level. + + // Try to remove newly added Steiner points. + for (i = 0; i < n; i++) { + if (newsteiners[i] != NULL) { + if (!removevertexbyflips(newsteiners[i])) { + if (b->supsteiner_level > 0) { // Not -Y/0 + // Save it in subvertstack for removal. + point *parypt; + subvertstack->newindex((void **) &parypt); + *parypt = newsteiners[i]; + } + steinercount++; + } + } + } + + b->fliplinklevel = bak_fliplinklevel; + + if (steinercount > 0) { + if (b->verbose > 3) { + printf(" Added %d interior Steiner points.\n", steinercount); + } + } + + delete [] newsteiners; + + return 1; +} + + +//============================================================================// +// // +// suppresssteinerpoints() Suppress Steiner points. // +// // +// All Steiner points have been saved in 'subvertstack' in the routines // +// carveholes() and suppresssteinerpoint(). // +// Each Steiner point is either removed or shifted into the interior. // +// // +//============================================================================// + +int tetgenmesh::suppresssteinerpoints() +{ + + if (!b->quiet) { + printf("Suppressing Steiner points ...\n"); + } + + point rempt, *parypt; + + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 100000; // Unlimited flip level. + int suppcount = 0, remcount = 0; + int i; + + // Try to suppress boundary Steiner points. + for (i = 0; i < subvertstack->objects; i++) { + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (pointtype(rempt) != UNUSEDVERTEX) { + if ((pointtype(rempt) == FREESEGVERTEX) || + (pointtype(rempt) == FREEFACETVERTEX)) { + if (suppressbdrysteinerpoint(rempt)) { + suppcount++; + } + } + } + } // i + + if (suppcount > 0) { + if (b->verbose) { + printf(" Suppressed %d boundary Steiner points.\n", suppcount); + } + } + + if (b->supsteiner_level > 0) { // -Y/1 + for (i = 0; i < subvertstack->objects; i++) { + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (pointtype(rempt) != UNUSEDVERTEX) { + if (pointtype(rempt) == FREEVOLVERTEX) { + if (removevertexbyflips(rempt)) { + remcount++; + } + } + } + } + } + + if (remcount > 0) { + if (b->verbose) { + printf(" Removed %d interior Steiner points.\n", remcount); + } + } + + b->fliplinklevel = bak_fliplinklevel; + + if (b->supsteiner_level > 1) { // -Y/2 + // Smooth interior Steiner points. + optparameters opm; + triface *parytet; + point *ppt; + REAL ori; + int smtcount, count, ivcount; + int nt, j; + + // Point smooth options. + opm.max_min_volume = 1; + opm.numofsearchdirs = 20; + opm.searchstep = 0.001; + opm.maxiter = 30; // Limit the maximum iterations. + + smtcount = 0; + + do { + + nt = 0; + + while (1) { + count = 0; + ivcount = 0; // Clear the inverted count. + + for (i = 0; i < subvertstack->objects; i++) { + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (pointtype(rempt) == FREEVOLVERTEX) { + getvertexstar(1, rempt, cavetetlist, NULL, NULL); + // Calculate the initial smallest volume (maybe zero or negative). + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + ppt = (point *) &(parytet->tet[4]); + ori = orient3dfast(ppt[1], ppt[0], ppt[2], ppt[3]); + if (j == 0) { + opm.initval = ori; + } else { + if (opm.initval > ori) opm.initval = ori; + } + } + if (smoothpoint(rempt, cavetetlist, 1, &opm)) { + count++; + } + if (opm.imprval <= 0.0) { + ivcount++; // The mesh contains inverted elements. + } + cavetetlist->restart(); + } + } // i + + smtcount += count; + + if (count == 0) { + // No point has been smoothed. + break; + } + + nt++; + if (nt > 2) { + break; // Already three iterations. + } + } // while + + if (ivcount > 0) { + // There are inverted elements! + if (opm.maxiter > 0) { + // Set unlimited smoothing steps. Try again. + opm.numofsearchdirs = 30; + opm.searchstep = 0.0001; + opm.maxiter = -1; + continue; + } + } + + break; + } while (1); // Additional loop for (ivcount > 0) + + if (ivcount > 0) { + printf("BUG Report! The mesh contain inverted elements.\n"); + } + + if (b->verbose) { + if (smtcount > 0) { + printf(" Smoothed %d Steiner points.\n", smtcount); + } + } + } // -Y2 + + subvertstack->restart(); + + return 1; +} + +//============================================================================// +// // +// recoverboundary() Recover segments and facets. // +// // +//============================================================================// + +void tetgenmesh::recoverboundary(clock_t& tv) +{ + arraypool *misseglist, *misshlist; + arraypool *bdrysteinerptlist; + face searchsh, *parysh; + face searchseg, *paryseg; + point rempt, *parypt; + long ms; // The number of missing segments/subfaces. + int nit; // The number of iterations. + int s, i; + + // Counters. + long bak_segref_count, bak_facref_count, bak_volref_count; + + if (!b->quiet) { + printf("Recovering boundaries...\n"); + } + + boundary_recovery_flag = 1; + cos_collinear_ang_tol = cos(b->collinear_ang_tol / 180. * PI); + + if (segmentendpointslist == NULL) { + // We need segment adjacent information during flips. + makesegmentendpointsmap(); + } + + + if (b->verbose) { + printf(" Recovering segments.\n"); + } + + // Segments will be introduced. + checksubsegflag = 1; + + misseglist = new arraypool(sizeof(face), 8); + bdrysteinerptlist = new arraypool(sizeof(point), 8); + + // In random order. + subsegs->traversalinit(); + for (i = 0; i < subsegs->items; i++) { + s = randomnation(i + 1); + // Move the s-th seg to the i-th. + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + // Put i-th seg to be the s-th. + searchseg.sh = shellfacetraverse(subsegs); + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = searchseg; + } + + // The init number of missing segments. + ms = subsegs->items; + nit = 0; + if (b->fliplinklevel < 0) { + autofliplinklevel = 1; // Init value. + } + + // First, trying to recover segments by only doing flips. + while (1) { + recoversegments(misseglist, 0, 0); + + if (misseglist->objects > 0) { + if (b->fliplinklevel >= 0) { + break; + } else { + if (misseglist->objects >= ms) { + nit++; + if (nit >= 3) { + //break; + // Do the last round with unbounded flip link level. + b->fliplinklevel = 100000; + } + } else { + ms = misseglist->objects; + if (nit > 0) { + nit--; + } + } + for (i = 0; i < misseglist->objects; i++) { + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(misseglist, i); + } + misseglist->restart(); + autofliplinklevel+=b->fliplinklevelinc; + } + } else { + // All segments are recovered. + break; + } + } // while (1) + + if (b->verbose) { + printf(" %ld (%ld) segments are recovered (missing).\n", + subsegs->items - misseglist->objects, misseglist->objects); + } + + if (misseglist->objects > 0) { + // Second, trying to recover segments by doing more flips (fullsearch). + while (misseglist->objects > 0) { + ms = misseglist->objects; + for (i = 0; i < misseglist->objects; i++) { + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(misseglist, i); + } + misseglist->restart(); + + recoversegments(misseglist, 1, 0); + + if (misseglist->objects < ms) { + // The number of missing segments is reduced. + continue; + } else { + break; + } + } + if (b->verbose) { + printf(" %ld (%ld) segments are recovered (missing).\n", + subsegs->items - misseglist->objects, misseglist->objects); + } + } + + //int bak_verbose = b->verbose; + //if (b->verbose < 3) { + // b->verbose = 3; // debug... + //} + + if (misseglist->objects > 0) { + // Third, trying to recover segments by doing more flips (fullsearch) + // and adding Steiner points in the volume. + + if (b->verbose) { + printf(" Recovering Delaunay.\n"); + } + + recoverdelaunay(); + + if (b->verbose) { + printf(" Recovering segments with Steiner points.\n"); + } + + while (misseglist->objects > 0) { + ms = misseglist->objects; + for (i = 0; i < misseglist->objects; i++) { + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(misseglist, i); + } + misseglist->restart(); + + //recoversegments(misseglist, 1, 1); + recoversegments(misseglist, 0, 1); // no full search + + if (misseglist->objects < ms) { + // The number of missing segments is reduced. + continue; + } else { + break; + } + } + if (b->verbose) { + printf(" Added %ld Steiner points in volume.\n", st_volref_count); + } + } + + if (misseglist->objects > 0) { + // Last, trying to recover segments by doing more flips (fullsearch), + // and adding Steiner points in the volume, and splitting segments. + long bak_inpoly_count = st_volref_count; //st_inpoly_count; + + if (b->verbose) { + printf(" Recovering Delaunay.\n"); + } + + recoverdelaunay(); + + if (b->verbose) { + printf(" Recovering segments with Steiner points.\n"); + } + + while (misseglist->objects > 0) { + ms = misseglist->objects; + for (i = 0; i < misseglist->objects; i++) { + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(misseglist, i); + } + misseglist->restart(); + + //recoversegments(misseglist, 1, 2); + recoversegments(misseglist, 0, 2); // no full search + + if (misseglist->objects < ms) { + // The number of missing segments is reduced. + continue; + } else { + break; + } + } // while (misseglist->objects > 0) + + if (b->verbose) { + printf(" Added %ld Steiner points in segments.\n", st_segref_count); + if (st_volref_count > bak_inpoly_count) { + printf(" Added another %ld Steiner points in volume.\n", + st_volref_count - bak_inpoly_count); + } + } + + // There may be un-recovered subsegments. + if (misseglist->objects > 0l) { + if (b->verbose) { + printf(" !! %ld subsegments are missing.\n", misseglist->objects); + } + } + } + + if (skipped_segment_list != NULL) { + if (!b->quiet) { + printf(" Skipped %ld segments due to intersections.\n", + skipped_segment_list->objects); + } + delete skipped_segment_list; + } + + + //b->verbose = bak_verbose; // debug... + + if (st_segref_count > 0) { + // Try to remove the Steiner points added in segments. + if (b->verbose) { + printf(" Suppressing %ld Steiner points in segments.\n", st_segref_count); + } + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 20; // limit this value + + bak_segref_count = st_segref_count; + bak_volref_count = st_volref_count; + for (i = 0; i < subvertstack->objects; i++) { + // Get the Steiner point. + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (!removevertexbyflips(rempt)) { + // Save it in list. + bdrysteinerptlist->newindex((void **) &parypt); + *parypt = rempt; + } + } + if (b->verbose) { + if (st_segref_count < bak_segref_count) { + if (bak_volref_count < st_volref_count) { + printf(" Suppressed %ld Steiner points in segments.\n", + st_volref_count - bak_volref_count); + } + if ((st_segref_count + (st_volref_count - bak_volref_count)) < + bak_segref_count) { + printf(" Removed %ld Steiner points in segments.\n", + bak_segref_count - + (st_segref_count + (st_volref_count - bak_volref_count))); + } + } + } + + b->fliplinklevel = bak_fliplinklevel; // restore it. + subvertstack->restart(); + } + + + tv = clock(); + + if (b->verbose) { + printf(" Recovering facets.\n"); + } + + // Subfaces will be introduced. + checksubfaceflag = 1; + + misshlist = new arraypool(sizeof(face), 8); + + // Randomly order the subfaces. + subfaces->traversalinit(); + for (i = 0; i < subfaces->items; i++) { + s = randomnation(i + 1); + // Move the s-th subface to the i-th. + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(subfacstack, s); + // Put i-th subface to be the s-th. + searchsh.sh = shellfacetraverse(subfaces); + parysh = (face *) fastlookup(subfacstack, s); + *parysh = searchsh; + } + + ms = subfaces->items; + nit = 0; + b->fliplinklevel = -1; // Init. + if (b->fliplinklevel < 0) { + autofliplinklevel = 1; // Init value. + } + + while (1) { + recoversubfaces(misshlist, 0); + + if (misshlist->objects > 0) { + if (b->fliplinklevel >= 0) { + break; + } else { + if (misshlist->objects >= ms) { + nit++; + if (nit >= 3) { + //break; + // Do the last round with unbounded flip link level. + //b->fliplinklevel = 100000; // this can be very slow. + if (autofliplinklevel < 30) { + b->fliplinklevel = 30; + } else { + b->fliplinklevel = autofliplinklevel + 30; + } + } + } else { + ms = misshlist->objects; + if (nit > 0) { + nit--; + } + } + for (i = 0; i < misshlist->objects; i++) { + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(misshlist, i); + } + misshlist->restart(); + autofliplinklevel+=b->fliplinklevelinc; + } + } else { + // All subfaces are recovered. + break; + } + } // while (1) + + if (b->verbose) { + printf(" %ld (%ld) subfaces are recovered (missing).\n", + subfaces->items - misshlist->objects, misshlist->objects); + } + + if (misshlist->objects > 0) { + // There are missing subfaces. Add Steiner points. + + if (b->verbose) { + printf(" Recovering Delaunay.\n"); + } + + recoverdelaunay(); + + if (b->verbose) { + printf(" Recovering facets with Steiner points.\n"); + } + + while (misshlist->objects > 0) { + ms = misshlist->objects; + for (i = 0; i < misshlist->objects; i++) { + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(misshlist, i); + } + misshlist->restart(); + + recoversubfaces(misshlist, 1); + + if (misshlist->objects < ms) { + continue; + } else { + break; + } + } + + if (b->verbose) { + printf(" %ld (%ld) subfaces are recovered (missing).\n", + subfaces->items - misshlist->objects, misshlist->objects); + printf(" Added %ld Steiner points in facets.\n", st_facref_count); + } + } + + if (misshlist->objects > 0) { + long bak_steiner = st_facref_count; + + if (b->verbose) { + printf(" Recovering Delaunay.\n"); + } + + recoverdelaunay(); + + if (b->verbose) { + printf(" Recovering facets with Steiner points.\n"); + } + + while (misshlist->objects > 0) { + ms = misshlist->objects; + for (i = 0; i < misshlist->objects; i++) { + subfacstack->newindex((void **) &parysh); + *parysh = * (face *) fastlookup(misshlist, i); + } + misshlist->restart(); + + recoversubfaces(misshlist, 2); // steinerflag = 2; + + if (misshlist->objects < ms) { + continue; + } else { + break; + } + } + + if (subsegstack->objects > 0) { + // Save unrecovered subsegments. + triface neightet; + face checkseg; + for (i = 0; i < subsegstack->objects; i++) { + checkseg = * (face *) fastlookup(subsegstack, i); + if ((checkseg.sh == NULL) || + (checkseg.sh[3] == NULL)) continue; + // Check if this subsegment is missing. + sstpivot1(checkseg, neightet); + if (neightet.tet != NULL) continue; + // Save a missing subsegment. + misseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + subsegstack->restart(); + } // if (subsegstack->objects > 0) + + if (b->verbose) { + printf(" %ld (%ld) subfaces are recovered (missing).\n", + subfaces->items - misshlist->objects, misshlist->objects); + printf(" Added %ld Steiner points in facets.\n", + st_facref_count - bak_steiner); + } + } + + // There may be un-recovered subsegments. + if (misshlist->objects > 0l) { + if (b->verbose) { + printf(" !! %ld subfaces are missing.\n", misshlist->objects); + } + terminatetetgen(this, 2); + // Save the list of missing subface. + //missing_tri_list = new arraypool(sizeof(face), 8); + //for (i = 0; i < misshlist->objects; i++) { + // missing_tri_list->newindex((void **) &parysh); + // *parysh = * (face *) fastlookup(misshlist, i); + //} + //misshlist->restart(); + } + + if (duplicated_facets_count > 0l) { + if (b->verbose) { + printf(" Deleting %ld duplicated facets.\n", duplicated_facets_count); + } + triface neightet, spintet; + face faceloop, sfaces[256]; // *tmp_sfaces = NULL; + face sseg; + int snum, snum_limit = 256; + int t1ver; + subfaces->traversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + while (faceloop.sh != NULL) { + if (sinfected(faceloop)) { + // Delete an ignored duplicated subface. + shellfacedealloc(subfaces, faceloop.sh); + } + if (!smarktest3ed(faceloop)) { + faceloop.shver = 0; + stpivot(faceloop, neightet); + if (neightet.tet == NULL) { + terminatetetgen(this, 2); + } + // Update the subface connections at its three edges. + for (int k= 0; k < 3; k++) { + sspivot(faceloop, sseg); + if (sseg.sh != NULL) { + ssbond(faceloop, sseg); // Update segment connection. + } + // Get all subfaces at this edge. + snum = 0; + spintet = neightet; + do { + if (issubface(spintet)) { + tspivot(spintet, sfaces[snum++]); + if (snum > snum_limit) { + // Unlikely to happen. + terminatetetgen(this, 2); + //tmp_sfaces = new face[snum_limit * 2]; + } + } + fnextself(spintet); + } while (spintet.tet != neightet.tet); + // Re-create the face ring. + for (int j = 0; j < snum - 1; j++) { + sbond1(sfaces[j], sfaces[j+1]); + } + sbond1(sfaces[snum - 1], sfaces[0]); + enextself(neightet); + senextself(faceloop); + } // k + } + faceloop.sh = shellfacetraverse(subfaces); + } + } // if (duplicated_facets_count > 0l) + + + if (st_facref_count > 0) { + // Try to remove the Steiner points added in facets. + if (b->verbose) { + printf(" Suppressing %ld Steiner points in facets.\n", st_facref_count); + } + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 30; // limit this value + + bak_facref_count = st_facref_count; + for (i = 0; i < subvertstack->objects; i++) { + // Get the Steiner point. + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (!removevertexbyflips(*parypt)) { + // Save it in list. + bdrysteinerptlist->newindex((void **) &parypt); + *parypt = rempt; + } + } + if (b->verbose) { + if (st_facref_count < bak_facref_count) { + printf(" Removed %ld Steiner points in facets.\n", + bak_facref_count - st_facref_count); + } + } + + b->fliplinklevel = bak_fliplinklevel; + subvertstack->restart(); + } + + + // There may be missing segments and subfaces. + if (misseglist->objects > 0) { + triface adjtet; + face checkseg; + for (i = 0; i < misseglist->objects; i++) { + checkseg = * (face *) fastlookup(misseglist, i); + // A saved missing segment might be split or recovered. + if ((checkseg.sh == NULL) || (checkseg.sh[3] == NULL)) { + continue; // it is split. + } + sstpivot1(checkseg, adjtet); + if (adjtet.tet != NULL) { + continue; // it is recovered. + } + // This is a missing segmemt. + subsegstack->newindex((void **) &paryseg); + *paryseg = checkseg; + } + if (subsegstack->objects > 0) { + if (!b->quiet && !b->nowarning) { + printf("Warning: %ld segments are not recovered.\n", subsegstack->objects); + } + //assert(0); // to do... + subsegstack->restart(); + } + } + + + if (bdrysteinerptlist->objects > 0) { + if (b->verbose) { + printf(" %ld Steiner points remained in boundary.\n", + bdrysteinerptlist->objects); + } + } // if + + + boundary_recovery_flag = 0; + + // Accumulate the dynamic memory. + totalworkmemory += (misseglist->totalmemory + misshlist->totalmemory + + bdrysteinerptlist->totalmemory); + + delete bdrysteinerptlist; + delete misseglist; + delete misshlist; +} + +// // +// // +//== steiner_cxx =============================================================// + + +//== reconstruct_cxx =========================================================// +// // +// // + +//============================================================================// +// // +// carveholes() Remove tetrahedra not in the mesh domain. // +// // +//============================================================================// + + +void tetgenmesh::carveholes() +{ + arraypool *tetarray, *hullarray; + triface tetloop, neightet, *parytet, *parytet1; + triface *regiontets = NULL; + face checksh, *parysh; + face checkseg; + point ptloop, *parypt; + int t1ver; + int i, j, k; + + if (!b->quiet) { + if (b->convex) { + printf("Marking exterior tetrahedra ...\n"); + } else { + printf("Removing exterior tetrahedra ...\n"); + } + } + + // Initialize the pool of exterior tets. + tetarray = new arraypool(sizeof(triface), 10); + hullarray = new arraypool(sizeof(triface), 10); + + // Collect unprotected tets and hull tets. + tetrahedrons->traversalinit(); + tetloop.ver = 11; // The face opposite to dummypoint. + tetloop.tet = alltetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + if (ishulltet(tetloop)) { + // Is this side protected by a subface? + if (!issubface(tetloop)) { + // Collect an unprotected hull tet and tet. + infect(tetloop); + hullarray->newindex((void **) &parytet); + *parytet = tetloop; + // tetloop's face number is 11 & 3 = 3. + decode(tetloop.tet[3], neightet); + if (!infected(neightet)) { + infect(neightet); + tetarray->newindex((void **) &parytet); + *parytet = neightet; + } + } + } + tetloop.tet = alltetrahedrontraverse(); + } + + if (in->numberofholes > 0) { + // Mark as infected any tets inside volume holes. + for (i = 0; i < 3 * in->numberofholes; i += 3) { + // Search a tet containing the i-th hole point. + neightet.tet = NULL; + randomsample(&(in->holelist[i]), &neightet); + if (locate(&(in->holelist[i]), &neightet) != OUTSIDE) { + // The tet 'neightet' contain this point. + if (!infected(neightet)) { + infect(neightet); + tetarray->newindex((void **) &parytet); + *parytet = neightet; + // Add its adjacent tet if it is not protected. + if (!issubface(neightet)) { + decode(neightet.tet[neightet.ver & 3], tetloop); + if (!infected(tetloop)) { + infect(tetloop); + if (ishulltet(tetloop)) { + hullarray->newindex((void **) &parytet); + } else { + tetarray->newindex((void **) &parytet); + } + *parytet = tetloop; + } + } + else { + // It is protected. Check if its adjacent tet is a hull tet. + decode(neightet.tet[neightet.ver & 3], tetloop); + if (ishulltet(tetloop)) { + // It is hull tet, add it into the list. Moreover, the subface + // is dead, i.e., both sides are in exterior. + if (!infected(tetloop)) { + infect(tetloop); + hullarray->newindex((void **) &parytet); + *parytet = tetloop; + } + } + if (infected(tetloop)) { + // Both sides of this subface are in exterior. + tspivot(neightet, checksh); + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + } // if (!infected(neightet)) + } else { + // A hole point locates outside of the convex hull. + if (!b->quiet) { + printf("Warning: The %d-th hole point ", i/3 + 1); + printf("lies outside the convex hull.\n"); + } + } + } // i + } // if (in->numberofholes > 0) + + if (b->hole_mesh && (b->hole_mesh_filename[0] != 0)) { + // A hole mesh (***.ele) is given. + //enum tetgenbehavior::objecttype object; + char filebasename[256]; + strcpy(filebasename, b->hole_mesh_filename); + //object = tetgenbehavior::MESH; + if (!strcmp(&filebasename[strlen(filebasename) - 4], ".ele")) { + filebasename[strlen(filebasename) - 4] = '\0'; + //object = tetgenbehavior::MESH; + } + bool hole_mesh_loaded = false; + tetgenio io; + if (io.load_node(filebasename)) { + if (io.load_tet(filebasename)) { + hole_mesh_loaded = true; + } + } + if (hole_mesh_loaded) { + if (b->verbose) { + printf(" Adding hole tets from the mesh %s\n", b->hole_mesh_filename); + } + int count = 0, hcount = 0, scount = 0; + int shift = io.firstnumber > 0 ? -1 : 0; + double *p1, *p2, *p3, *p4; + double searchpt[3]; + // Randomly select a tet. + i = randomnation(io.numberoftetrahedra); + //for (i = 0; i < io.numberoftetrahedra; i++) { + int *idx = &(io.tetrahedronlist[i * 4]); + p1 = &(io.pointlist[(idx[0]+shift)*3]); + p2 = &(io.pointlist[(idx[1]+shift)*3]); + p3 = &(io.pointlist[(idx[2]+shift)*3]); + p4 = &(io.pointlist[(idx[3]+shift)*3]); + for (j = 0; j < 3; j++) { + searchpt[j] = (p1[j]+p2[j]+p3[j]+p4[j])/4.; + } + // Search the point. + neightet.tet = NULL; + if (locate(searchpt, &neightet) != OUTSIDE) { + // The tet 'neightet' contain this point. + if (!infected(neightet)) { + infect(neightet); + tetarray->newindex((void **) &parytet); + *parytet = neightet; + count++; + // Add its adjacent tet if it is not protected. + if (!issubface(neightet)) { + decode(neightet.tet[neightet.ver & 3], tetloop); + if (!infected(tetloop)) { + infect(tetloop); + if (ishulltet(tetloop)) { + hullarray->newindex((void **) &parytet); + hcount++; + } else { + tetarray->newindex((void **) &parytet); + count++; + } + *parytet = tetloop; + } + } + else { + // It is protected. Check if its adjacent tet is a hull tet. + decode(neightet.tet[neightet.ver & 3], tetloop); + if (ishulltet(tetloop)) { + // It is hull tet, add it into the list. Moreover, the subface + // is dead, i.e., both sides are in exterior. + if (!infected(tetloop)) { + infect(tetloop); + hullarray->newindex((void **) &parytet); + *parytet = tetloop; + hcount++; + } + } + if (infected(tetloop)) { + // Both sides of this subface are in exterior. + tspivot(neightet, checksh); + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + scount++; + } + } + } + } + //} // i + if (b->verbose) { + printf(" Added %d hole tets, %d hull tet, %d hole subfaces\n", + count, hcount, scount); + } + } // if (hole_mesh_loaded) + } + + if (b->regionattrib && (in->numberofregions > 0)) { // -A option. + // Record the tetrahedra that contains the region points for assigning + // region attributes after the holes have been carved. + regiontets = new triface[in->numberofregions]; + // Mark as marktested any tetrahedra inside volume regions. + for (i = 0; i < 5 * in->numberofregions; i += 5) { + // Search a tet containing the i-th region point. + neightet.tet = NULL; + randomsample(&(in->regionlist[i]), &neightet); + if (locate(&(in->regionlist[i]), &neightet) != OUTSIDE) { + regiontets[i/5] = neightet; + } else { + if (!b->quiet) { + printf("Warning: The %d-th region point ", i/5+1); + printf("lies outside the convex hull.\n"); + } + regiontets[i/5].tet = NULL; + } + } + } + + // Collect all exterior tets (in concave place and in holes). + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + j = (parytet->ver & 3); // j is the current face number. + // Check the other three adjacent tets. + for (k = 1; k < 4; k++) { + decode(parytet->tet[(j + k) % 4], neightet); + // neightet may be a hull tet. + if (!infected(neightet)) { + // Is neightet protected by a subface. + if (!issubface(neightet)) { + // Not proected. Collect it. (It must not be a hull tet). + infect(neightet); + tetarray->newindex((void **) &parytet1); + *parytet1 = neightet; + } else { + // Protected. Check if it is a hull tet. + if (ishulltet(neightet)) { + // A hull tet. Collect it. + infect(neightet); + hullarray->newindex((void **) &parytet1); + *parytet1 = neightet; + // Both sides of this subface are exterior. + tspivot(neightet, checksh); + // Queue this subface (to be deleted later). + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + } else { + // Both sides of this face are in exterior. + // If there is a subface. It should be collected. + if (issubface(neightet)) { + tspivot(neightet, checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + } + } // j, k + } // i + + if (b->regionattrib && (in->numberofregions > 0)) { + // Re-check saved region tets to see if they lie outside. + for (i = 0; i < in->numberofregions; i++) { + if ((regiontets[i].tet != NULL) && infected(regiontets[i])) { + if (b->verbose) { + printf("Warning: The %d-th region point ", i+1); + printf("lies in the exterior of the domain.\n"); + } + regiontets[i].tet = NULL; + } + } + } + + // Collect vertices which point to infected tets. These vertices + // may get deleted after the removal of exterior tets. + // If -Y1 option is used, collect all Steiner points for removal. + // The lists 'cavetetvertlist' and 'subvertstack' are re-used. + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + if ((pointtype(ptloop) != UNUSEDVERTEX) && + (pointtype(ptloop) != DUPLICATEDVERTEX)) { + decode(point2tet(ptloop), neightet); + if (infected(neightet)) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = ptloop; + } + if ((!b->cdt || b->nobisect) && (b->supsteiner_level > 0)) { // -Y/1 + // Queue it if it is a Steiner point. + if (pointmark(ptloop) > + (in->numberofpoints - (in->firstnumber ? 0 : 1))) { + subvertstack->newindex((void **) &parypt); + *parypt = ptloop; + } + } + } + ptloop = pointtraverse(); + } + + if (!b->convex && (tetarray->objects > 0l)) { // No -c option. + // Remove exterior tets. Hull tets are updated. + arraypool *newhullfacearray; + triface hulltet, casface; + face segloop, *paryseg; + point pa, pb, pc; + long delsegcount = 0l; + + // Collect segments which point to infected tets. Some segments + // may get deleted after the removal of exterior tets. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + sstpivot1(segloop, neightet); + if (infected(neightet)) { + subsegstack->newindex((void **) &paryseg); + *paryseg = segloop; + } + segloop.sh = shellfacetraverse(subsegs); + } + + newhullfacearray = new arraypool(sizeof(triface), 10); + + // Create and save new hull tets. + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + for (j = 0; j < 4; j++) { + decode(parytet->tet[j], tetloop); + if (!infected(tetloop)) { + // Found a new hull face (must be a subface). + tspivot(tetloop, checksh); + maketetrahedron(&hulltet); + pa = org(tetloop); + pb = dest(tetloop); + pc = apex(tetloop); + setvertices(hulltet, pb, pa, pc, dummypoint); + bond(tetloop, hulltet); + // Update the subface-to-tet map. + sesymself(checksh); + tsbond(hulltet, checksh); + // Update the segment-to-tet map. + for (k = 0; k < 3; k++) { + if (issubseg(tetloop)) { + tsspivot1(tetloop, checkseg); + tssbond1(hulltet, checkseg); + sstbond1(checkseg, hulltet); + } + enextself(tetloop); + eprevself(hulltet); + } + // Update the point-to-tet map. + setpoint2tet(pa, (tetrahedron) tetloop.tet); + setpoint2tet(pb, (tetrahedron) tetloop.tet); + setpoint2tet(pc, (tetrahedron) tetloop.tet); + // Save the exterior tet at this hull face. It still holds pointer + // to the adjacent interior tet. Use it to connect new hull tets. + newhullfacearray->newindex((void **) &parytet1); + parytet1->tet = parytet->tet; + parytet1->ver = j; + } // if (!infected(tetloop)) + } // j + } // i + + // Connect new hull tets. + for (i = 0; i < newhullfacearray->objects; i++) { + parytet = (triface *) fastlookup(newhullfacearray, i); + fsym(*parytet, neightet); + // Get the new hull tet. + fsym(neightet, hulltet); + for (j = 0; j < 3; j++) { + esym(hulltet, casface); + if (casface.tet[casface.ver & 3] == NULL) { + // Since the boundary of the domain may not be a manifold, we + // find the adjacent hull face by traversing the tets in the + // exterior (which are all infected tets). + neightet = *parytet; + while (1) { + fnextself(neightet); + if (!infected(neightet)) break; + } + if (!ishulltet(neightet)) { + // An interior tet. Get the new hull tet. + fsymself(neightet); + esymself(neightet); + } + // Bond them together. + bond(casface, neightet); + } + enextself(hulltet); + enextself(*parytet); + } // j + } // i + + if (subfacstack->objects > 0l) { + // Remove all subfaces which do not attach to any tetrahedron. + // Segments which are not attached to any subfaces and tets + // are deleted too. + face casingout, casingin; + + for (i = 0; i < subfacstack->objects; i++) { + parysh = (face *) fastlookup(subfacstack, i); + if (i == 0) { + if (b->verbose) { + printf("Warning: Removed an exterior face (%d, %d, %d) #%d\n", + pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), + pointmark(sapex(*parysh)), shellmark(*parysh)); + } + } + // Dissolve this subface from face links. + for (j = 0; j < 3; j++) { + spivot(*parysh, casingout); + sspivot(*parysh, checkseg); + if (casingout.sh != NULL) { + casingin = casingout; + while (1) { + spivot(casingin, checksh); + if (checksh.sh == parysh->sh) break; + casingin = checksh; + } + if (casingin.sh != casingout.sh) { + // Update the link: ... -> casingin -> casingout ->... + sbond1(casingin, casingout); + } else { + // Only one subface at this edge is left. + sdissolve(casingout); + } + if (checkseg.sh != NULL) { + // Make sure the segment does not connect to a dead one. + ssbond(casingout, checkseg); + } + } else { + if (checkseg.sh != NULL) { + //if (checkseg.sh[3] != NULL) { + if (delsegcount == 0) { + if (b->verbose) { + printf("Warning: Removed an exterior segment (%d, %d) #%d\n", + pointmark(sorg(checkseg)), pointmark(sdest(checkseg)), + shellmark(checkseg)); + } + } + shellfacedealloc(subsegs, checkseg.sh); + delsegcount++; + } + } + senextself(*parysh); + } // j + // Delete this subface. + shellfacedealloc(subfaces, parysh->sh); + } // i + if (b->verbose) { + printf(" Deleted %ld subfaces.\n", subfacstack->objects); + } + subfacstack->restart(); + } // if (subfacstack->objects > 0l) + + if (subsegstack->objects > 0l) { + for (i = 0; i < subsegstack->objects; i++) { + paryseg = (face *) fastlookup(subsegstack, i); + if (paryseg->sh && (paryseg->sh[3] != NULL)) { + sstpivot1(*paryseg, neightet); + if (infected(neightet)) { + if (b->verbose) { + printf("Warning: Removed an exterior segment (%d, %d) #%d\n", + pointmark(sorg(*paryseg)), pointmark(sdest(*paryseg)), + shellmark(*paryseg)); + } + shellfacedealloc(subsegs, paryseg->sh); + delsegcount++; + } + } + } + subsegstack->restart(); + } // if (subsegstack->objects > 0l) + + if (delsegcount > 0) { + if (b->verbose) { + printf(" Deleted %ld segments.\n", delsegcount); + } + } + + if (cavetetvertlist->objects > 0l) { + // Some vertices may lie in exterior. Marke them as UNUSEDVERTEX. + long delvertcount = unuverts; + long delsteinercount = 0l; + + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + decode(point2tet(*parypt), neightet); + if (infected(neightet)) { + // Found an exterior vertex. + if (pointmark(*parypt) > + (in->numberofpoints - (in->firstnumber ? 0 : 1))) { + // A Steiner point. + if (pointtype(*parypt) == FREESEGVERTEX) { + st_segref_count--; + } else if (pointtype(*parypt) == FREEFACETVERTEX) { + st_facref_count--; + } else { + st_volref_count--; + } + delsteinercount++; + if (steinerleft > 0) steinerleft++; + } + setpointtype(*parypt, UNUSEDVERTEX); + unuverts++; + } + } + + if (b->verbose) { + if (unuverts > delvertcount) { + if (delsteinercount > 0l) { + if (unuverts > (delvertcount + delsteinercount)) { + printf(" Removed %ld exterior input vertices.\n", + unuverts - delvertcount - delsteinercount); + } + printf(" Removed %ld exterior Steiner vertices.\n", + delsteinercount); + } else { + printf(" Removed %ld exterior input vertices.\n", + unuverts - delvertcount); + } + } + } + cavetetvertlist->restart(); + // Comment: 'subvertstack' will be cleaned in routine + // suppresssteinerpoints(). + } // if (cavetetvertlist->objects > 0l) + + // Update the hull size. + hullsize += (newhullfacearray->objects - hullarray->objects); + + // Delete all exterior tets and old hull tets. + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + tetrahedrondealloc(parytet->tet); + } + tetarray->restart(); + + for (i = 0; i < hullarray->objects; i++) { + parytet = (triface *) fastlookup(hullarray, i); + tetrahedrondealloc(parytet->tet); + } + hullarray->restart(); + + delete newhullfacearray; + } // if (!b->convex && (tetarray->objects > 0l)) + + if (b->convex && (tetarray->objects > 0l)) { // With -c option + // In this case, all exterior tets get a region marker '-1'. + int attrnum = numelemattrib - 1; + + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + setelemattribute(parytet->tet, attrnum, -1); + } + tetarray->restart(); + + for (i = 0; i < hullarray->objects; i++) { + parytet = (triface *) fastlookup(hullarray, i); + uninfect(*parytet); + } + hullarray->restart(); + + if (subfacstack->objects > 0l) { + for (i = 0; i < subfacstack->objects; i++) { + parysh = (face *) fastlookup(subfacstack, i); + suninfect(*parysh); + } + subfacstack->restart(); + } + + if (cavetetvertlist->objects > 0l) { + cavetetvertlist->restart(); + } + } // if (b->convex && (tetarray->objects > 0l)) + + if (b->regionattrib) { // With -A option. + if (!b->quiet) { + printf("Spreading region attributes.\n"); + } + REAL volume; + int attr, maxattr = 0; // Choose a small number here. + int attrnum = numelemattrib - 1; + // Comment: The element region marker is at the end of the list of + // the element attributes. + int regioncount = 0; + + arraypool *tmpary = new arraypool(sizeof(int), 8); + int *paryint; + + // If has user-defined region attributes. + if (in->numberofregions > 0) { + // Spread region attributes. + for (i = 0; i < 5 * in->numberofregions; i += 5) { + if (regiontets[i/5].tet != NULL) { + attr = (int) in->regionlist[i + 3]; + if (attr > maxattr) { + maxattr = attr; + } + volume = in->regionlist[i + 4]; + tetarray->restart(); // Re-use this array. + infect(regiontets[i/5]); + tetarray->newindex((void **) &parytet); + *parytet = regiontets[i/5]; + // Collect and set attrs for all tets of this region. + for (j = 0; j < tetarray->objects; j++) { + parytet = (triface *) fastlookup(tetarray, j); + tetloop = *parytet; + setelemattribute(tetloop.tet, attrnum, attr); + if (b->varvolume) { // If has -a option. + setvolumebound(tetloop.tet, volume); + } + for (k = 0; k < 4; k++) { + decode(tetloop.tet[k], neightet); + // Is the adjacent already checked? + if (!infected(neightet)) { + // Is this side protected by a subface? + if (!issubface(neightet)) { + infect(neightet); + tetarray->newindex((void **) &parytet); + *parytet = neightet; + } + } + } // k + } // j + tmpary->newindex((void **) &paryint); + *paryint = attr; + regioncount++; + } // if (regiontets[i/5].tet != NULL) + } // i + } + + // Set attributes for all tetrahedra. + attr = maxattr + 1; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + if (!infected(tetloop)) { + // An unmarked region. + tetarray->restart(); // Re-use this array. + infect(tetloop); + tetarray->newindex((void **) &parytet); + *parytet = tetloop; + // Find and mark all tets. + for (j = 0; j < tetarray->objects; j++) { + parytet = (triface *) fastlookup(tetarray, j); + tetloop = *parytet; + setelemattribute(tetloop.tet, attrnum, attr); + for (k = 0; k < 4; k++) { + decode(tetloop.tet[k], neightet); + // Is the adjacent tet already checked? + if (!infected(neightet)) { + // Is this side protected by a subface? + if (!issubface(neightet)) { + infect(neightet); + tetarray->newindex((void **) &parytet); + *parytet = neightet; + } + } + } // k + } // j + tmpary->newindex((void **) &paryint); + *paryint = attr; + attr++; // Increase the attribute. + regioncount++; + } + tetloop.tet = tetrahedrontraverse(); + } + + // Uninfect processed tets. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + uninfect(tetloop); + tetloop.tet = tetrahedrontraverse(); + } + + // Until here, every tet has a region attribute. + subdomains = regioncount; // Remember it for output. + subdomain_markers = new int[subdomains]; + for (i = 0; i < subdomains; i++) { + paryint = (int *) fastlookup(tmpary, i); + subdomain_markers[i] = *paryint; + } + delete tmpary; + + if (b->verbose) { + //assert(regioncount > 0); + if (regioncount > 1) { + printf(" Found %d subdomains.\n", regioncount); + } else { + printf(" Found %d domain.\n", regioncount); + } + } + } // if (b->regionattrib) + + if (regiontets != NULL) { + delete [] regiontets; + } + delete tetarray; + delete hullarray; + + if (!b->convex) { // No -c option + // The mesh is non-convex now. + nonconvex = 1; + + // Push all hull tets into 'flipstack'. + tetrahedrons->traversalinit(); + tetloop.ver = 11; // The face opposite to dummypoint. + tetloop.tet = alltetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + if ((point) tetloop.tet[7] == dummypoint) { + fsym(tetloop, neightet); + flippush(flipstack, &neightet); + } + tetloop.tet = alltetrahedrontraverse(); + } + + flipconstraints fc; + fc.enqflag = 2; + long sliver_peel_count = lawsonflip3d(&fc); + + if (sliver_peel_count > 0l) { + if (b->verbose) { + printf(" Removed %ld hull slivers.\n", sliver_peel_count); + } + } + unflipqueue->restart(); + } // if (!b->convex) +} + +// [2018-07-30] +// Search a face with given indices (i,j,k). +// This function is only called when the default fast search fails. +// It is possible when there are non-manifold edges on the hull. +// On finish, tetloop return this face if it exists, otherwise, return 0. +int tetgenmesh::search_face(point pi, point pj, point pk, triface &tetloop) +{ + pinfect(pi); + pinfect(pj); + pinfect(pk); + + int t1ver; + triface t, t1; + point *pts, toppo; + int pcount = 0; + + t.ver = t1.ver = 0; + tetrahedrons->traversalinit(); + t.tet = tetrahedrontraverse(); + while (t.tet != NULL) { + pts = (point *) t.tet; + pcount = 0; + if (pinfected(pts[4])) pcount++; + if (pinfected(pts[5])) pcount++; + if (pinfected(pts[6])) pcount++; + if (pinfected(pts[7])) pcount++; + + if (pcount == 3) { + // Found a tet containing this face. + for (t.ver = 0; t.ver < 4; t.ver++) { + toppo = oppo(t); + if (!pinfected(toppo)) break; + } + int ii; + for (ii = 0; ii < 3; ii++) { + if (org(t) == pi) break; + enextself(t); + } + if (dest(t) == pj) { + } else { + eprevself(t); + fsymself(t); + } + break; + } + t.tet = tetrahedrontraverse(); + } + + puninfect(pi); + puninfect(pj); + puninfect(pk); + + if (t.tet != NULL) { + tetloop = t; + return 1; + } else { + return 0; + } +} + +int tetgenmesh::search_edge(point p0, point p1, triface &tetloop) +{ + triface t; + int ii; + + tetrahedrons->traversalinit(); + t.tet = tetrahedrontraverse(); + while (t.tet != NULL) { + for (ii = 0; ii < 6; ii++) { + t.ver = edge2ver[ii]; + if (((org(t) == p0) && (dest(t) == p1)) || + ((org(t) == p1) && (dest(t) == p0))) { + // Found the tet. + tetloop = t; + return 1; + } + } + t.tet = tetrahedrontraverse(); + } + + tetloop.tet = NULL; + return 0; +} + +//============================================================================// +// // +// reconstructmesh() Reconstruct a tetrahedral mesh. // +// // +//============================================================================// + +void tetgenmesh::reconstructmesh() +{ + tetrahedron *ver2tetarray; + point *idx2verlist; + triface tetloop, checktet, prevchktet; + triface hulltet, face1, face2; + tetrahedron tptr; + face subloop, neighsh, nextsh; + face segloop; + shellface sptr; + point p[4], q[3]; + REAL ori, attrib, volume; + REAL cosang_tol, cosang; + REAL n1[3], n2[3]; + int eextras, marker = 0; + int bondflag; + int t1ver; + int idx, i, j, k; + + if (!b->quiet) { + printf("Reconstructing mesh ...\n"); + } + + if (b->convex) { // -c option. + // Assume the mesh is convex. Exterior tets have region attribute -1. + if (!(in->numberoftetrahedronattributes > 0)) { + terminatetetgen(this, 2); + } + } else { + // Assume the mesh is non-convex. + nonconvex = 1; + } + + // Create a map from indices to vertices. + makeindex2pointmap(idx2verlist); + // 'idx2verlist' has length 'in->numberofpoints + 1'. + if (in->firstnumber == 1) { + idx2verlist[0] = dummypoint; // Let 0th-entry be dummypoint. + } + + // Allocate an array that maps each vertex to its adjacent tets. + ver2tetarray = new tetrahedron[in->numberofpoints + 1]; + unuverts = in->numberofpoints; // All vertices are unused yet. + //for (i = 0; i < in->numberofpoints + 1; i++) { + for (i = in->firstnumber; i < in->numberofpoints + in->firstnumber; i++) { + ver2tetarray[i] = NULL; + } + + // Create the tetrahedra and connect those that share a common face. + for (i = 0; i < in->numberoftetrahedra; i++) { + // Get the four vertices. + idx = i * in->numberofcorners; + for (j = 0; j < 4; j++) { + p[j] = idx2verlist[in->tetrahedronlist[idx++]]; + if (pointtype(p[j]) == UNUSEDVERTEX) { + setpointtype(p[j], VOLVERTEX); // initial type. + unuverts--; + } + } + // Check the orientation. + ori = orient3d(p[0], p[1], p[2], p[3]); + if (ori > 0.0) { + // Swap the first two vertices. + q[0] = p[0]; p[0] = p[1]; p[1] = q[0]; + } else if (ori == 0.0) { + if (!b->quiet) { + printf("Warning: Tet #%d is degenerate.\n", i + in->firstnumber); + } + } + // Create a new tetrahedron. + maketetrahedron(&tetloop); // tetloop.ver = 11. + setvertices(tetloop, p[0], p[1], p[2], p[3]); + // Set element attributes if they exist. + for (j = 0; j < in->numberoftetrahedronattributes; j++) { + idx = i * in->numberoftetrahedronattributes; + attrib = in->tetrahedronattributelist[idx + j]; + setelemattribute(tetloop.tet, j, attrib); + } + // If -a switch is used (with no number follows) Set a volume + // constraint if it exists. + if (b->varvolume) { + if (in->tetrahedronvolumelist != (REAL *) NULL) { + volume = in->tetrahedronvolumelist[i]; + } else { + volume = -1.0; + } + setvolumebound(tetloop.tet, volume); + } + // Try connecting this tet to others that share the common faces. + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + p[3] = oppo(tetloop); + // Look for other tets having this vertex. + idx = pointmark(p[3]); + tptr = ver2tetarray[idx]; + // Link the current tet to the next one in the stack. + tetloop.tet[8 + tetloop.ver] = tptr; + // Push the current tet onto the stack. + ver2tetarray[idx] = encode(tetloop); + decode(tptr, checktet); + if (checktet.tet != NULL) { + p[0] = org(tetloop); // a + p[1] = dest(tetloop); // b + p[2] = apex(tetloop); // c + prevchktet = tetloop; + do { + q[0] = org(checktet); // a' + q[1] = dest(checktet); // b' + q[2] = apex(checktet); // c' + // Check the three faces at 'd' in 'checktet'. + bondflag = 0; + for (j = 0; j < 3; j++) { + // Go to the face [b',a',d], or [c',b',d], or [a',c',d]. + esym(checktet, face2); + if (face2.tet[face2.ver & 3] == NULL) { + k = ((j + 1) % 3); + if (q[k] == p[0]) { // b', c', a' = a + if (q[j] == p[1]) { // a', b', c' = b + // [#,#,d] is matched to [b,a,d]. + esym(tetloop, face1); + bond(face1, face2); + bondflag++; + } + } + if (q[k] == p[1]) { // b',c',a' = b + if (q[j] == p[2]) { // a',b',c' = c + // [#,#,d] is matched to [c,b,d]. + enext(tetloop, face1); + esymself(face1); + bond(face1, face2); + bondflag++; + } + } + if (q[k] == p[2]) { // b',c',a' = c + if (q[j] == p[0]) { // a',b',c' = a + // [#,#,d] is matched to [a,c,d]. + eprev(tetloop, face1); + esymself(face1); + bond(face1, face2); + bondflag++; + } + } + } else { + bondflag++; + } + enextself(checktet); + } // j + // Go to the next tet in the link. + tptr = checktet.tet[8 + checktet.ver]; + if (bondflag == 3) { + // All three faces at d in 'checktet' have been connected. + // It can be removed from the link. + prevchktet.tet[8 + prevchktet.ver] = tptr; + } else { + // Bakup the previous tet in the link. + prevchktet = checktet; + } + decode(tptr, checktet); + } while (checktet.tet != NULL); + } // if (checktet.tet != NULL) + } // for (tetloop.ver = 0; ... + } // i + + // Remember a tet of the mesh. + recenttet = tetloop; + + // Create hull tets, create the point-to-tet map, and clean up the + // temporary spaces used in each tet. + hullsize = tetrahedrons->items; + + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + tptr = encode(tetloop); + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + if (tetloop.tet[tetloop.ver] == NULL) { + // Create a hull tet. + maketetrahedron(&hulltet); + p[0] = org(tetloop); + p[1] = dest(tetloop); + p[2] = apex(tetloop); + setvertices(hulltet, p[1], p[0], p[2], dummypoint); + bond(tetloop, hulltet); + // Try connecting this to others that share common hull edges. + for (j = 0; j < 3; j++) { + fsym(hulltet, face2); + while (1) { + if (face2.tet == NULL) break; + esymself(face2); + if (apex(face2) == dummypoint) break; + fsymself(face2); + } + if (face2.tet != NULL) { + // Found an adjacent hull tet. + esym(hulltet, face1); + bond(face1, face2); + } + enextself(hulltet); + } + } + // Create the point-to-tet map. + setpoint2tet((point) (tetloop.tet[4 + tetloop.ver]), tptr); + // Clean the temporary used space. + tetloop.tet[8 + tetloop.ver] = NULL; + } + tetloop.tet = tetrahedrontraverse(); + } + + hullsize = tetrahedrons->items - hullsize; + + // Subfaces will be inserted into the mesh. + if (in->trifacelist != NULL) { + // A .face file is given. It may contain boundary faces. Insert them. + for (i = 0; i < in->numberoftrifaces; i++) { + // Is it a subface? + if (in->trifacemarkerlist != NULL) { + marker = in->trifacemarkerlist[i]; + } else { + // Face markers are not available. Assume all of them are subfaces. + marker = -1; // The default marker. + } + if (marker != 0) { + idx = i * 3; + for (j = 0; j < 3; j++) { + p[j] = idx2verlist[in->trifacelist[idx++]]; + } + // Search the subface. + bondflag = 0; + neighsh.sh = NULL; + // Make sure all vertices are in the mesh. Avoid crash. + for (j = 0; j < 3; j++) { + decode(point2tet(p[j]), checktet); + if (checktet.tet == NULL) break; + } + if ((j == 3) && getedge(p[0], p[1], &checktet)) { + tetloop = checktet; + q[2] = apex(checktet); + while (1) { + if (apex(tetloop) == p[2]) { + // Found the face. + // Check if there exist a subface already? + tspivot(tetloop, neighsh); + if (neighsh.sh != NULL) { + // Found a duplicated subface. + // This happens when the mesh was generated by other mesher. + bondflag = 0; + } else { + bondflag = 1; + } + break; + } + fnextself(tetloop); + if (apex(tetloop) == q[2]) break; + } + } + if (!bondflag) { + if (neighsh.sh == NULL) { + if (b->verbose > 1) { + printf("Warning: Searching subface #%d [%d,%d,%d] mark=%d.\n", + i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), + pointmark(p[2]), marker); + } + // Search it globally. + if (search_face(p[0], p[1], p[2], tetloop)) { + bondflag = 1; + } + } + } + if (bondflag) { + // Create a new subface. + makeshellface(subfaces, &subloop); + setshvertices(subloop, p[0], p[1], p[2]); + // Create the point-to-subface map. + sptr = sencode(subloop); + for (j = 0; j < 3; j++) { + setpointtype(p[j], FACETVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(subloop, marker); + // Insert the subface into the mesh. + tsbond(tetloop, subloop); + fsymself(tetloop); + sesymself(subloop); + tsbond(tetloop, subloop); + } else { + if (neighsh.sh != NULL) { + // The subface already exists. Only set its mark. + setshellmark(neighsh, marker); + } else { + if (!b->quiet) { + printf("Warning: Subface #%d [%d,%d,%d] mark=%d is not found.\n", + i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), + pointmark(p[2]), marker); + } + } + } // if (bondflag) + } // if (marker != 0) + } // i + } // if (in->trifacelist) + + // Indentify subfaces from the mesh. + // Create subfaces for hull faces (if they're not subface yet) and + // interior faces which separate two different materials. + eextras = in->numberoftetrahedronattributes; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + tspivot(tetloop, neighsh); + if (neighsh.sh == NULL) { + bondflag = 0; + fsym(tetloop, checktet); + if (ishulltet(checktet)) { + // A hull face. + if (!b->convex) { + bondflag = 1; // Insert a hull subface. + } + } else { + if (eextras > 0) { + if (elemattribute(tetloop.tet, eextras - 1) != + elemattribute(checktet.tet, eextras - 1)) { + bondflag = 1; // Insert an interior interface. + } + } + } + if (bondflag) { + // Create a new subface. + makeshellface(subfaces, &subloop); + p[0] = org(tetloop); + p[1] = dest(tetloop); + p[2] = apex(tetloop); + setshvertices(subloop, p[0], p[1], p[2]); + // Create the point-to-subface map. + sptr = sencode(subloop); + for (j = 0; j < 3; j++) { + setpointtype(p[j], FACETVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(subloop, -1); // Default marker. + // Insert the subface into the mesh. + tsbond(tetloop, subloop); + sesymself(subloop); + tsbond(checktet, subloop); + } // if (bondflag) + } // if (neighsh.sh == NULL) + } + tetloop.tet = tetrahedrontraverse(); + } + + // Connect subfaces together. + subfaces->traversalinit(); + subloop.shver = 0; + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != (shellface *) NULL) { + for (i = 0; i < 3; i++) { + spivot(subloop, neighsh); + if (neighsh.sh == NULL) { + // Form a subface ring by linking all subfaces at this edge. + // Traversing all faces of the tets at this edge. + stpivot(subloop, tetloop); + q[2] = apex(tetloop); + neighsh = subloop; + while (1) { + fnextself(tetloop); + tspivot(tetloop, nextsh); + if (nextsh.sh != NULL) { + // Do not connect itself. + if (nextsh.sh != neighsh.sh) { + // Link neighsh <= nextsh. + sbond1(neighsh, nextsh); + neighsh = nextsh; + } + } + if (apex(tetloop) == q[2]) { + break; + } + } // while (1) + } // if (neighsh.sh == NULL) + senextself(subloop); + } + subloop.sh = shellfacetraverse(subfaces); + } + + + // Segments will be introduced. + if (in->edgelist != NULL) { + // A .edge file is given. It may contain boundary edges. Insert them. + for (i = 0; i < in->numberofedges; i++) { + // Is it a segment? + if (in->edgemarkerlist != NULL) { + marker = in->edgemarkerlist[i]; + } else { + // Edge markers are not available. Assume all of them are segments. + marker = -1; // Default marker. + } + if (marker != 0) { + // Insert a segment. + idx = i * 2; + for (j = 0; j < 2; j++) { + p[j] = idx2verlist[in->edgelist[idx++]]; + } + // Make sure all vertices are in the mesh. Avoid crash. + for (j = 0; j < 2; j++) { + decode(point2tet(p[j]), checktet); + if (checktet.tet == NULL) break; + } + // Search the segment. + bondflag = 0; + if (j == 2) { + if (getedge(p[0], p[1], &checktet)) { + bondflag = 1; + } else { + if (b->verbose > 1) { + printf("Warning: Searching segment #%d [%d,%d] mark=%d.\n", + i + in->firstnumber, pointmark(p[0]), pointmark(p[1]), marker); + } + // Search it globally. + if (search_edge(p[0], p[1], checktet)) { + bondflag = 1; + } + } + } + if (bondflag > 0) { + // Create a new segment. + makeshellface(subsegs, &segloop); + setshvertices(segloop, p[0], p[1], NULL); + // Create the point-to-segment map. + sptr = sencode(segloop); + for (j = 0; j < 2; j++) { + setpointtype(p[j], RIDGEVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(segloop, marker); + // Insert the segment into the mesh. + tetloop = checktet; + q[2] = apex(checktet); + subloop.sh = NULL; + while (1) { + tssbond1(tetloop, segloop); + tspivot(tetloop, subloop); + if (subloop.sh != NULL) { + ssbond1(subloop, segloop); + sbond1(segloop, subloop); + } + fnextself(tetloop); + if (apex(tetloop) == q[2]) break; + } // while (1) + // Remember an adjacent tet for this segment. + sstbond1(segloop, tetloop); + } else { + if (!b->quiet) { + printf("Warning: Segment #%d [%d,%d] is missing.\n", + i + in->firstnumber, pointmark(p[0]), pointmark(p[1])); + } + } + } // if (marker != 0) + } // i + } // if (in->edgelist) + + // Identify segments from the mesh. + // Create segments for non-manifold edges (which are shared by more + // than two subfaces), and for non-coplanar edges, i.e., two subfaces + // form an dihedral angle > 'b->facet_separate_ang_tol' (degree). + cosang_tol = cos(b->facet_separate_ang_tol / 180.0 * PI); + subfaces->traversalinit(); + subloop.shver = 0; + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != (shellface *) NULL) { + for (i = 0; i < 3; i++) { + sspivot(subloop, segloop); + if (segloop.sh == NULL) { + // Check if this edge is a segment. + bondflag = 0; + // Counter the number of subfaces at this edge. + idx = 0; + spivot(subloop, nextsh); + if (nextsh.sh != NULL) { + nextsh = subloop; + while (1) { + idx++; + spivotself(nextsh); + if (nextsh.sh == subloop.sh) break; + } + } else { + // There is only one subface at this edge. + idx = 1; + } + if (idx != 2) { + // It's a non-manifold edge. Insert a segment. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + bondflag = 1; + } else { + // There are two subfaces at this edge. + spivot(subloop, neighsh); + if (shellmark(subloop) != shellmark(neighsh)) { + // It's an interior interface. Insert a segment. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + bondflag = 1; + } else { + if (!b->convex) { + // Check the dihedral angle formed by the two subfaces. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + p[2] = sapex(subloop); + p[3] = sapex(neighsh); + facenormal(p[0], p[1], p[2], n1, 1, NULL); + facenormal(p[0], p[1], p[3], n2, 1, NULL); + cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); + // Rounding. + if (cosang > 1.0) cosang = 1.0; + else if (cosang < -1.0) cosang = -1.0; + if (cosang > cosang_tol) { + bondflag = 1; + } + } + } + } + if (bondflag) { + // Create a new segment. + makeshellface(subsegs, &segloop); + setshvertices(segloop, p[0], p[1], NULL); + // Create the point-to-segment map. + sptr = sencode(segloop); + for (j = 0; j < 2; j++) { + setpointtype(p[j], RIDGEVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(segloop, -1); // Default marker. + // Insert the subface into the mesh. + stpivot(subloop, tetloop); + q[2] = apex(tetloop); + while (1) { + tssbond1(tetloop, segloop); + tspivot(tetloop, neighsh); + if (neighsh.sh != NULL) { + ssbond1(neighsh, segloop); + } + fnextself(tetloop); + if (apex(tetloop) == q[2]) break; + } // while (1) + // Remember an adjacent tet for this segment. + sstbond1(segloop, tetloop); + sbond1(segloop, subloop); + } // if (bondflag) + } // if (neighsh.sh == NULL) + senextself(subloop); + } // i + subloop.sh = shellfacetraverse(subfaces); + } + + // Remember the number of input segments. + insegments = subsegs->items; + + if (!b->nobisect || checkconstraints) { + // Mark Steiner points on segments and facets. + // - all vertices which remaining type FEACTVERTEX become + // Steiner points in facets (= FREEFACERVERTEX). + // - vertices on segment need to be checked. + face* segperverlist; + int* idx2seglist; + face parentseg, nextseg; + verttype vt; + REAL area, len; // l1, l2; + int fmarker; + + makepoint2submap(subsegs, idx2seglist, segperverlist); + + points->traversalinit(); + point ptloop = pointtraverse(); + while (ptloop != NULL) { + vt = pointtype(ptloop); + if (vt == VOLVERTEX) { + setpointtype(ptloop, FREEVOLVERTEX); + st_volref_count++; + } else if (vt == FACETVERTEX) { + setpointtype(ptloop, FREEFACETVERTEX); + st_facref_count++; + } else if (vt == RIDGEVERTEX) { + idx = pointmark(ptloop) - in->firstnumber; + if ((idx2seglist[idx + 1] - idx2seglist[idx]) == 2) { + i = idx2seglist[idx]; + parentseg = segperverlist[i]; + nextseg = segperverlist[i + 1]; + sesymself(nextseg); + p[0] = sorg(nextseg); + p[1] = sdest(parentseg); + // Check if three points p[0], ptloop, p[2] are (nearly) collinear. + if (is_collinear_at(ptloop, p[0], p[1])) { + // They are (nearly) collinear. + setpointtype(ptloop, FREESEGVERTEX); + // Connect nextseg and parentseg together at ptloop. + senextself(nextseg); + senext2self(parentseg); + sbond(nextseg, parentseg); + st_segref_count++; + } + } + } + ptloop = pointtraverse(); + } + + // Are there area constraints? + if (b->quality && (in->facetconstraintlist != (REAL *) NULL)) { + // Set maximum area constraints on facets. + for (i = 0; i < in->numberoffacetconstraints; i++) { + fmarker = (int) in->facetconstraintlist[i * 2]; + area = in->facetconstraintlist[i * 2 + 1]; + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + if (shellmark(subloop) == fmarker) { + setareabound(subloop, area); + } + subloop.sh = shellfacetraverse(subfaces); + } + } + } + + // Are there length constraints? + if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + // Set maximum length constraints on segments. + int e1, e2; + for (i = 0; i < in->numberofsegmentconstraints; i++) { + e1 = (int) in->segmentconstraintlist[i * 3]; + e2 = (int) in->segmentconstraintlist[i * 3 + 1]; + len = in->segmentconstraintlist[i * 3 + 2]; + // Search for edge [e1, e2]. + idx = e1 - in->firstnumber; + for (j = idx2seglist[idx]; j < idx2seglist[idx + 1]; j++) { + parentseg = segperverlist[j]; + if (pointmark(sdest(parentseg)) == e2) { + setareabound(parentseg, len); + break; + } + } + } + } + + delete [] idx2seglist; + delete [] segperverlist; + } + + + // Set global flags. + checksubsegflag = 1; + checksubfaceflag = 1; + + delete [] idx2verlist; + delete [] ver2tetarray; +} + +//============================================================================// +// // +// scoutpoint() Search a point in mesh. // +// // +// This function searches the point in a mesh whose domain may be not convex. // +// In case of a convex domain, the locate() function is sufficient. // +// // +// If 'randflag' is used, randomly select a start searching tet. Otherwise, // +// start searching directly from 'searchtet'. // +// // +//============================================================================// + +int tetgenmesh::scout_point(point searchpt, triface *searchtet, int randflag) +{ + if (b->verbose > 3) { + printf(" Scout point %d.\n", pointmark(searchpt)); + } + // randflag is not used. + enum locateresult loc = OUTSIDE; + int maxiter = 100, iter = 0; + + do { + // 'searchtet' must be a valid tetrahedron. + if (searchtet->tet == NULL) { + // Randomly select a good starting tet. + randomsample(searchpt, searchtet); + } + + if (ishulltet(*searchtet)) { + if ((recenttet.tet != NULL) && !ishulltet(recenttet)) { + *searchtet = recenttet; + } + } + + if (ishulltet(*searchtet)) { + int t1ver; + searchtet->ver = 11; + fsymself(*searchtet); + } + + loc = locate_point_walk(searchpt, searchtet, 0); // encflg = 0. + + if (loc == OUTSIDE) { + //randomsample(searchpt, searchtet); + searchtet->tet = NULL; + } + + iter++; + if (iter < maxiter) break; + } while (loc != OUTSIDE); + + if (loc == INTETRAHEDRON) { + // Check if this vertex is nearly on subfacet. + triface chktet = *searchtet; + for (chktet.ver = 0; chktet.ver < 4; chktet.ver++) { + if (issubface(chktet)) { + point pa = org(chktet); + point pb = org(chktet); + point pc = org(chktet); + REAL ori = orient3d(pa, pb, pc, searchpt); + REAL averlen = (distance(pa, pb) + + distance(pb, pc) + + distance(pc, pa)) / 3.; + REAL len3 = averlen * averlen * averlen; + REAL ratio = (-ori) / len3; + if (ratio < b->epsilon) { + *searchtet = chktet; + loc = ONFACE; + break; + } + } + } + } // if (loc == INTETRAHEDRON) + + if (loc == ONFACE) { + // Check if this vertex is nearly on a subsegment. + triface chkface = *searchtet; + for (int i = 0; i < 3; i++) { + if (issubseg(chkface)) { + REAL cosang = cos_interiorangle(searchpt, org(chkface), dest(chkface)); + if (cosang < cos_collinear_ang_tol) { // -p////179.9 + *searchtet = chkface; + loc = ONEDGE; + break; + } + } + enextself(chkface); + } + } // if (loc == ONFACE) + + if (loc == ONEDGE) { + // Check if this vertex is nearly on a vertex. + triface chkedge = *searchtet; + for (int i = 0; i < 2; i++) { + REAL dd = distance(searchpt, org(chkedge)); + if (dd < minedgelength) { + *searchtet = chkedge; + loc = ONVERTEX; + break; + } + esymself(chkedge); + } + } + + return (int) loc; +} + +//============================================================================// +// // +// getpointmeshsize() Interpolate the mesh size at given point. // +// // +// 'iloc' indicates the location of the point w.r.t. 'searchtet'. The size // +// is obtained by linear interpolation on the vertices of the tet. // +// // +//============================================================================// + +REAL tetgenmesh::getpointmeshsize(point searchpt, triface *searchtet, int iloc) +{ + point *pts, pa, pb, pc; + REAL volume, vol[4], wei[4]; + REAL size; + int i; + + size = 0; + + if (iloc == (int) INTETRAHEDRON) { + pts = (point *) &(searchtet->tet[4]); + // Only do interpolation if all vertices have non-zero sizes. + if ((pts[0][pointmtrindex] > 0) && (pts[1][pointmtrindex] > 0) && + (pts[2][pointmtrindex] > 0) && (pts[3][pointmtrindex] > 0)) { + // P1 interpolation. + volume = orient3dfast(pts[0], pts[1], pts[2], pts[3]); + vol[0] = orient3dfast(searchpt, pts[1], pts[2], pts[3]); + vol[1] = orient3dfast(pts[0], searchpt, pts[2], pts[3]); + vol[2] = orient3dfast(pts[0], pts[1], searchpt, pts[3]); + vol[3] = orient3dfast(pts[0], pts[1], pts[2], searchpt); + for (i = 0; i < 4; i++) { + wei[i] = fabs(vol[i] / volume); + size += (wei[i] * pts[i][pointmtrindex]); + } + } + } else if (iloc == (int) ONFACE) { + pa = org(*searchtet); + pb = dest(*searchtet); + pc = apex(*searchtet); + if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0) && + (pc[pointmtrindex] > 0)) { + volume = triarea(pa, pb, pc); + vol[0] = triarea(searchpt, pb, pc); + vol[1] = triarea(pa, searchpt, pc); + vol[2] = triarea(pa, pb, searchpt); + size = (vol[0] / volume) * pa[pointmtrindex] + + (vol[1] / volume) * pb[pointmtrindex] + + (vol[2] / volume) * pc[pointmtrindex]; + } + } else if (iloc == (int) ONEDGE) { + pa = org(*searchtet); + pb = dest(*searchtet); + if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0)) { + volume = distance(pa, pb); + vol[0] = distance(searchpt, pb); + vol[1] = distance(pa, searchpt); + size = (vol[0] / volume) * pa[pointmtrindex] + + (vol[1] / volume) * pb[pointmtrindex]; + } + } else if (iloc == (int) ONVERTEX) { + pa = org(*searchtet); + if (pa[pointmtrindex] > 0) { + size = pa[pointmtrindex]; + } + } + + return size; +} + +//============================================================================// +// // +// interpolatemeshsize() Interpolate the mesh size from a background mesh // +// (source) to the current mesh (destination). // +// // +//============================================================================// + +void tetgenmesh::interpolatemeshsize() +{ + triface searchtet; + point ploop; + REAL minval = 0.0, maxval = 0.0; + int iloc; + int count; + + if (!b->quiet) { + printf("Interpolating mesh size ...\n"); + } + + long bak_nonregularcount = nonregularcount; + nonregularcount = 0l; // Count the number of (slow) global searches. + long baksmaples = bgm->samples; + bgm->samples = 3l; + count = 0; // Count the number of interpolated points. + + // Interpolate sizes for all points in the current mesh. + points->traversalinit(); + ploop = pointtraverse(); + while (ploop != NULL) { + // Search a tet in bgm which containing this point. + searchtet.tet = NULL; + iloc = bgm->scout_point(ploop, &searchtet, 1); // randflag = 1 + if (iloc != (int) OUTSIDE) { + // Interpolate the mesh size. + ploop[pointmtrindex] = bgm->getpointmeshsize(ploop, &searchtet, iloc); + setpoint2bgmtet(ploop, bgm->encode(searchtet)); + if (count == 0) { + // This is the first interpolated point. + minval = maxval = ploop[pointmtrindex]; + } else { + if (ploop[pointmtrindex] < minval) { + minval = ploop[pointmtrindex]; + } + if (ploop[pointmtrindex] > maxval) { + maxval = ploop[pointmtrindex]; + } + } + count++; + } else { + if (!b->quiet) { + printf("Warnning: Failed to locate point %d in source mesh.\n", + pointmark(ploop)); + } + } + ploop = pointtraverse(); + } + + if (b->verbose) { + printf(" Interoplated %d points.\n", count); + if (nonregularcount > 0l) { + printf(" Performed %ld brute-force searches.\n", nonregularcount); + } + printf(" Size rangle [%.17g, %.17g].\n", minval, maxval); + } + + bgm->samples = baksmaples; + nonregularcount = bak_nonregularcount; +} + +//============================================================================// +// // +// insertconstrainedpoints() Insert a list of points into the mesh. // +// // +// Assumption: The bounding box of the insert point set should be no larger // +// than the bounding box of the mesh. (Required by point sorting). // +// // +//============================================================================// + +void tetgenmesh::insertconstrainedpoints(point *insertarray, int arylen, + int rejflag) +{ + triface searchtet, spintet; + face splitsh; + face splitseg; + insertvertexflags ivf; + //flipconstraints fc; + int randflag = 0; + int t1ver; + int i; + + if (b->verbose) { + printf(" Inserting %d constrained points\n", arylen); + } + + if (b->no_sort) { // -b/1 option. + if (b->verbose) { + printf(" Using the input order.\n"); + } + } else { + if (b->verbose) { + printf(" Permuting vertices.\n"); + } + point swappoint; + int randindex; + srand(arylen); + for (i = 0; i < arylen; i++) { + randindex = rand() % (i + 1); + swappoint = insertarray[i]; + insertarray[i] = insertarray[randindex]; + insertarray[randindex] = swappoint; + } + if (b->brio_hilbert) { // -b1 option + if (b->verbose) { + printf(" Sorting vertices.\n"); + } + hilbert_init(in->mesh_dim); + int ngroup = 0; + brio_multiscale_sort(insertarray, arylen, b->brio_threshold, + b->brio_ratio, &ngroup); + } else { // -b0 option. + randflag = 1; + } // if (!b->brio_hilbert) + } // if (!b->no_sort) + + long bak_nonregularcount = nonregularcount; + nonregularcount = 0l; + long baksmaples = samples; + samples = 3l; // Use at least 3 samples. Updated in randomsample(). + + long bak_seg_count = st_segref_count; + long bak_fac_count = st_facref_count; + long bak_vol_count = st_volref_count; + + // Initialize the insertion parameters. + // Use Bowyer-Watson algorithm. + ivf.bowywat = 1; + ivf.lawson = 2; // do flip to recover Delaunay + ivf.validflag = 1; // Validate the B-W cavity. + ivf.rejflag = rejflag; + ivf.chkencflag = 0; + ivf.sloc = (int) INSTAR; + ivf.sbowywat = 3; + ivf.splitbdflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + encseglist = new arraypool(sizeof(face), 8); + encshlist = new arraypool(sizeof(badface), 8); + searchtet.tet = NULL; + + // Insert the points. + for (i = 0; i < arylen; i++) { + // Find the location of the inserted point. + // Do not use 'recenttet', since the mesh may be non-convex. + ivf.iloc = scout_point(insertarray[i], &searchtet, randflag); + + // Decide the right type for this point. + setpointtype(insertarray[i], FREEVOLVERTEX); // Default. + splitsh.sh = NULL; + splitseg.sh = NULL; + if (ivf.iloc == (int) ONEDGE) { + if (issubseg(searchtet)) { + tsspivot1(searchtet, splitseg); + setpointtype(insertarray[i], FREESEGVERTEX); + //ivf.rejflag = 0; + } else { + // Check if it is a subface edge. + spintet = searchtet; + while (1) { + if (issubface(spintet)) { + tspivot(spintet, splitsh); + setpointtype(insertarray[i], FREEFACETVERTEX); + //ivf.rejflag |= 1; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + } + } else if (ivf.iloc == (int) ONFACE) { + if (issubface(searchtet)) { + tspivot(searchtet, splitsh); + setpointtype(insertarray[i], FREEFACETVERTEX); + //ivf.rejflag |= 1; + } + } + + // Now insert the point. + if (insertpoint(insertarray[i], &searchtet, &splitsh, &splitseg, &ivf)) { + if (flipstack != NULL) { + flipconstraints fc; + //fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + //unflipqueue->restart(); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + + // Update the Steiner counters. + if (pointtype(insertarray[i]) == FREESEGVERTEX) { + st_segref_count++; + } else if (pointtype(insertarray[i]) == FREEFACETVERTEX) { + st_facref_count++; + } else { + st_volref_count++; + } + } else { + // Point is not inserted. + if (pointtype(insertarray[i]) != UNUSEDVERTEX) { + setpointtype(insertarray[i], UNUSEDVERTEX); + } + unuverts++; + + encseglist->restart(); + encshlist->restart(); + } + } // i + + if (later_unflip_queue->objects > 0) { + recoverdelaunay(); + } + + delete encseglist; + delete encshlist; + encseglist = NULL; + encshlist = NULL; + + if (b->verbose) { + printf(" Inserted %ld (%ld, %ld, %ld) vertices.\n", + st_segref_count + st_facref_count + st_volref_count - + (bak_seg_count + bak_fac_count + bak_vol_count), + st_segref_count - bak_seg_count, st_facref_count - bak_fac_count, + st_volref_count - bak_vol_count); + if (nonregularcount > 0l) { + printf(" Performed %ld brute-force searches.\n", nonregularcount); + } + } + + nonregularcount = bak_nonregularcount; + samples = baksmaples; +} + +void tetgenmesh::insertconstrainedpoints(tetgenio *addio) +{ + point *insertarray, newpt; + REAL x, y, z, w; + int index, attribindex, mtrindex; + int arylen, i, j; + + if (!b->quiet) { + printf("Inserting constrained points ...\n"); + } + + insertarray = new point[addio->numberofpoints]; + arylen = 0; + index = 0; + attribindex = 0; + mtrindex = 0; + + for (i = 0; i < addio->numberofpoints; i++) { + x = addio->pointlist[index++]; + y = addio->pointlist[index++]; + z = addio->pointlist[index++]; + // Test if this point lies inside the bounding box. + if ((x < xmin) || (x > xmax) || (y < ymin) || (y > ymax) || + (z < zmin) || (z > zmax)) { + if (b->verbose) { + printf("Warning: Point #%d lies outside the bounding box. Ignored\n", + i + in->firstnumber); + } + continue; + } + makepoint(&newpt, UNUSEDVERTEX); + newpt[0] = x; + newpt[1] = y; + newpt[2] = z; + // Read the point attributes. (Including point weights.) + for (j = 0; j < addio->numberofpointattributes; j++) { + newpt[3 + j] = addio->pointattributelist[attribindex++]; + } + // Read the point metric tensor. + for (j = 0; j < addio->numberofpointmtrs; j++) { + newpt[pointmtrindex + j] = addio->pointmtrlist[mtrindex++]; + } + if (b->weighted) { // -w option + if (addio->numberofpointattributes > 0) { + // The first point attribute is its weight. + w = newpt[3]; + } else { + // No given weight available. Default choose the maximum + // absolute value among its coordinates. + w = fabs(x); + if (w < fabs(y)) w = fabs(y); + if (w < fabs(z)) w = fabs(z); + } + if (b->weighted_param == 0) { + newpt[3] = x * x + y * y + z * z - w; // Weighted DT. + } else { // -w1 option + newpt[3] = w; // Regular tetrahedralization. + } + } + insertarray[arylen] = newpt; + arylen++; + } // i + + // Insert the points. + int rejflag = 0; // Do not check encroachment. + if (b->metric) { // -m option. + rejflag |= 4; // Reject it if it lies in some protecting balls. + } + + insertconstrainedpoints(insertarray, arylen, rejflag); + + delete [] insertarray; +} + +//============================================================================// +// // +// meshcoarsening() Deleting (selected) vertices. // +// // +//============================================================================// + +void tetgenmesh::collectremovepoints(arraypool *remptlist) +{ + point ptloop, *parypt; + verttype vt; + + // If a mesh sizing function is given. Collect vertices whose mesh size + // is greater than its smallest edge length. + if (b->metric) { // -m option + REAL len, smlen; + int i; + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + // Do not remove a boundary vertex + vt = pointtype(ptloop); + if ((vt == RIDGEVERTEX) || /*(vt == ACUTEVERTEX) ||*/ (vt == FACETVERTEX) || + (vt == FREEFACETVERTEX) || (vt == FREESEGVERTEX) || (vt == UNUSEDVERTEX)) { + ptloop = pointtraverse(); + continue; + } + if (ptloop[pointmtrindex] > 0) { + // Get the smallest edge length at this vertex. + getvertexstar(1, ptloop, cavetetlist, cavetetvertlist, NULL); + parypt = (point *) fastlookup(cavetetvertlist, 0); + smlen = distance(ptloop, *parypt); + for (i = 1; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + len = distance(ptloop, *parypt); + if (len < smlen) { + smlen = len; + } + } + cavetetvertlist->restart(); + cavetetlist->restart(); + if (smlen < ptloop[pointmtrindex]) { + pinfect(ptloop); + remptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + } + ptloop = pointtraverse(); + } + if (b->verbose > 1) { + printf(" Coarsen %ld oversized points.\n", remptlist->objects); + } + } + + // If 'in->pointmarkerlist' exists, Collect vertices with markers '-1'. + if (in->pointmarkerlist != NULL) { + long bak_count = remptlist->objects; + points->traversalinit(); + ptloop = pointtraverse(); + int index = 0; + while (ptloop != NULL) { + if (index < in->numberofpoints) { + if (in->pointmarkerlist[index] == -1) { + pinfect(ptloop); + remptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + } else { + // Remaining are not input points. Stop here. + break; + } + index++; + ptloop = pointtraverse(); + } + if (b->verbose > 1) { + printf(" Coarsen %ld marked points.\n", remptlist->objects - bak_count); + } + } // if (in->pointmarkerlist != NULL) + + if (b->coarsen_param > 0) { // -R1/# + // Remove a coarsen_percent number of interior points. + if (b->verbose > 1) { + printf(" Coarsen %g percent of interior points.\n", + b->coarsen_percent * 100.0); + } + arraypool *intptlist = new arraypool(sizeof(point *), 10); + // Count the total number of interior points. + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + vt = pointtype(ptloop); + if ((vt == VOLVERTEX) || (vt == FREEVOLVERTEX) || + (vt == FREEFACETVERTEX) || (vt == FREESEGVERTEX)) { + intptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + ptloop = pointtraverse(); + } + if (intptlist->objects > 0l) { + // Sort the list of points randomly. + point *parypt_i, swappt; + int randindex, i; + srand(intptlist->objects); + for (i = 0; i < intptlist->objects; i++) { + randindex = rand() % (i + 1); // randomnation(i + 1); + parypt_i = (point *) fastlookup(intptlist, i); + parypt = (point *) fastlookup(intptlist, randindex); + // Swap this two points. + swappt = *parypt_i; + *parypt_i = *parypt; + *parypt = swappt; + } + int remcount = (int) ((REAL) intptlist->objects * b->coarsen_percent); + // Return the first remcount points. + for (i = 0; i < remcount; i++) { + parypt_i = (point *) fastlookup(intptlist, i); + if (!pinfected(*parypt_i)) { + pinfected(*parypt_i); + remptlist->newindex((void **) &parypt); + *parypt = *parypt_i; + } + } + } + delete intptlist; + } + + // Unmark all collected vertices. + for (int i = 0; i < remptlist->objects; i++) { + parypt = (point *) fastlookup(remptlist, i); + puninfect(*parypt); + } +} + +void tetgenmesh::meshcoarsening() +{ + arraypool *remptlist; + + if (!b->quiet) { + printf("Mesh coarsening ...\n"); + } + + // Collect the set of points to be removed + remptlist = new arraypool(sizeof(point *), 10); + collectremovepoints(remptlist); + + if (remptlist->objects == 0l) { + delete remptlist; + return; + } + + if (b->verbose) { + if (remptlist->objects > 0l) { + printf(" Removing %ld points...\n", remptlist->objects); + } + } + + point *parypt, *plastpt; + long ms = remptlist->objects; + int nit = 0; + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = -1; + autofliplinklevel = 1; // Init value. + int i; + + while (1) { + + if (b->verbose > 1) { + printf(" Removing points [%s level = %2d] #: %ld.\n", + (b->fliplinklevel > 0) ? "fixed" : "auto", + (b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, + remptlist->objects); + } + + // Remove the list of points. + for (i = 0; i < remptlist->objects; i++) { + parypt = (point *) fastlookup(remptlist, i); + if (removevertexbyflips(*parypt)) { + // Move the last entry to the current place. + plastpt = (point *) fastlookup(remptlist, remptlist->objects - 1); + *parypt = *plastpt; + remptlist->objects--; + i--; + } + } + + if (remptlist->objects > 0l) { + if (b->fliplinklevel >= 0) { + break; // We have tried all levels. + } + if (remptlist->objects == ms) { + nit++; + if (nit >= 3) { + // Do the last round with unbounded flip link level. + b->fliplinklevel = 100000; + } + } else { + ms = remptlist->objects; + if (nit > 0) { + nit--; + } + } + autofliplinklevel+=b->fliplinklevelinc; + } else { + // All points are removed. + break; + } + } // while (1) + + if (remptlist->objects > 0l) { + if (b->verbose) { + printf(" %ld points are not removed !\n", remptlist->objects); + } + } + + b->fliplinklevel = bak_fliplinklevel; + delete remptlist; +} + +// // +// // +//== reconstruct_cxx =========================================================// + +//== refine_cxx ==============================================================// +// // +// // + +//============================================================================// +// // +// makesegmentendpointsmap() Create a map from a segment to its endpoints. // +// // +// The map is saved in the array 'segmentendpointslist'. The length of this // +// array is twice the number of segments. Each segment is assigned a unique // +// index (starting from 0). // +// // +//============================================================================// + +void tetgenmesh::makesegmentendpointsmap() +{ + arraypool *segptlist; + face segloop, prevseg, nextseg; + point eorg, edest, *parypt; + int segindex = 0, idx = 0; + int i; + + if (b->verbose > 0) { + printf(" Creating the segment-endpoints map.\n"); + } + segptlist = new arraypool(2 * sizeof(point), 10); + + // for creating ridge_vertex-to-segment map; + // The index might start from 0 or 1. + idx_segment_ridge_vertex_list = new int[points->items + 2]; + for (i = 0; i < points->items + 2; i++) { + idx_segment_ridge_vertex_list[i] = 0; + } + + // A segment s may have been split into many subsegments. Operate the one + // which contains the origin of s. Then mark the rest of subsegments. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + segloop.shver = 0; + while (segloop.sh != NULL) { + senext2(segloop, prevseg); + spivotself(prevseg); + if (prevseg.sh == NULL) { + eorg = sorg(segloop); + edest = sdest(segloop); + setfacetindex(segloop, segindex); + senext(segloop, nextseg); + spivotself(nextseg); + while (nextseg.sh != NULL) { + setfacetindex(nextseg, segindex); + nextseg.shver = 0; + if (sorg(nextseg) != edest) sesymself(nextseg); + edest = sdest(nextseg); + // Go the next connected subsegment at edest. + senextself(nextseg); + spivotself(nextseg); + } + segptlist->newindex((void **) &parypt); + parypt[0] = eorg; + parypt[1] = edest; + segindex++; + // for creating adj_ridge_vertex_list; + idx_segment_ridge_vertex_list[pointmark(eorg)]++; + idx_segment_ridge_vertex_list[pointmark(edest)]++; + } + segloop.sh = shellfacetraverse(subsegs); + } + + if (b->verbose) { + printf(" Found %ld segments.\n", segptlist->objects); + } + + segmentendpointslist_length = segptlist->objects; + segmentendpointslist = new point[segptlist->objects * 2]; + + totalworkmemory += (segptlist->objects * 2) * sizeof(point *); + + for (i = 0; i < segptlist->objects; i++) { + parypt = (point *) fastlookup(segptlist, i); + segmentendpointslist[idx++] = parypt[0]; + segmentendpointslist[idx++] = parypt[1]; + } + + // Create the adj_ridge_vertex_list. + int j = idx_segment_ridge_vertex_list[0], k; + idx_segment_ridge_vertex_list[0] = 0; + for (i = 0; i < points->items + 1; i++) { + k = idx_segment_ridge_vertex_list[i+1]; + idx_segment_ridge_vertex_list[i+1] = idx_segment_ridge_vertex_list[i] + j; + j = k; + } + + //assert(i == points->items+1); + int total_count = idx_segment_ridge_vertex_list[i] + 1; + segment_ridge_vertex_list = new point[total_count]; + for (i = 0; i < segptlist->objects; i++) { + eorg = segmentendpointslist[i*2]; + edest = segmentendpointslist[i*2+1]; + j = pointmark(eorg); + k = pointmark(edest); + segment_ridge_vertex_list[idx_segment_ridge_vertex_list[j]] = edest; //eorg; + segment_ridge_vertex_list[idx_segment_ridge_vertex_list[k]] = eorg; //edest; + idx_segment_ridge_vertex_list[j]++; + idx_segment_ridge_vertex_list[k]++; + } + + // Counters in idx_adj_ridge_vertex_list[] are shifted by 1. + for (i = points->items; i >= 0; i--) { + idx_segment_ridge_vertex_list[i+1] = idx_segment_ridge_vertex_list[i]; + } + idx_segment_ridge_vertex_list[0] = 0; + + + delete segptlist; +} + +//============================================================================// +// // +// set_ridge_vertex_protecting_ball() Calculate the protecting ball for a // +// given ridge vertex. // +// // +//============================================================================// + +REAL tetgenmesh::set_ridge_vertex_protecting_ball(point ridge_pt) +{ + REAL rv = getpointinsradius(ridge_pt); + if (rv == 0.) { + REAL mindist = 1.e+30, dist; + int idx = pointmark(ridge_pt); + for (int i = idx_segment_ridge_vertex_list[idx]; + i < idx_segment_ridge_vertex_list[idx+1]; i++) { + dist = distance(ridge_pt, segment_ridge_vertex_list[i]); + if (mindist > dist) mindist = dist; + } + rv = mindist * 0.95; // mindist / 3.0; // refer to J. Shewchuk + setpointinsradius(ridge_pt, rv); + } + return rv; +} + +//============================================================================// +// // +// get_min_diahedral_angle() Calculate the minimum (interior) dihedral // +// angle a given segment. // +// // +//============================================================================// + +REAL tetgenmesh::get_min_diahedral_angle(face* seg) +{ + triface adjtet, spintet; + face startsh, neighsh; + point pa, pb, pc1, pc2; + REAL n1[3], n2[3]; + REAL n1len, n2len; + REAL costheta; //, ori; + REAL theta, sum_theta, minang = 2.0 * PI; + int t1ver; + + pa = sorg(*seg); + pb = sdest(*seg); + spivot(*seg, startsh); + if (startsh.sh == NULL) { + // This segment is not connected by any facet. + sstpivot1(*seg, adjtet); + if (adjtet.tet != NULL) { + // This segment is completely inside the volume. + return 360.; // 2*pi. + } + } else { + if (sorg(startsh) != pa) sesymself(startsh); + stpivot(startsh, adjtet); + } + if (adjtet.tet == NULL) { + // This segment is not inserted (recovered) yet. + return 0.; + } + + + sum_theta = 0.; + spintet = adjtet; + while (true) { + if (!ishulltet(spintet)) { + // Increase the interior dihedral angle (sum_theta). + pc1 = apex(spintet); + pc2 = oppo(spintet); + facenormal(pa, pb, pc1, n1, 1, NULL); + facenormal(pa, pb, pc2, n2, 1, NULL); + n1len = sqrt(dot(n1, n1)); + n2len = sqrt(dot(n2, n2)); + costheta = dot(n1, n2) / (n1len * n2len); + // Be careful rounding error! + if (costheta > 1.0) { + costheta = 1.0; + } else if (costheta < -1.0) { + costheta = -1.0; + } + theta = acos(costheta); + sum_theta += theta; + } + // Go to the next adjacent tetrahedron at this segment. + fnextself(spintet); + // Check if we meet a subface. + tspivot(spintet, neighsh); + if ((neighsh.sh != NULL) && (sum_theta > 0.)) { + // Update the smallest dihedral angle. + if (sum_theta < minang) minang = sum_theta; + sum_theta = 0.; // clear it + } + if (spintet.tet == adjtet.tet) break; + } + + double mindihedang = minang / PI * 180.; + return mindihedang; +} + +//============================================================================// +// // +// get_min_angle_at_ridge_vertex() Calculate the minimum face angle at a // +// given ridge vertex. // +// // +//============================================================================// + +REAL tetgenmesh::get_min_angle_at_ridge_vertex(face* seg) +{ + face startsh, spinsh, neighsh; + point pa, pb, pc; + REAL theta, sum_theta, minang = 2.0 * PI; + + pa = sorg(*seg); + spivot(*seg, startsh); + if (startsh.sh == NULL) { + // This segment does not belong to any facet. + return 360.; // 2*pi. + } else { + if (sorg(startsh) != pa) sesymself(startsh); + } + + spinsh = startsh; + while (spinsh.sh != NULL) { + sum_theta = 0.; + neighsh = spinsh; + while (true) { + pb = sdest(neighsh); + pc = sapex(neighsh); + theta = interiorangle(pa, pb, pc, NULL); + sum_theta += theta; + senext2self(neighsh); + if (isshsubseg(neighsh)) break; + spivotself(neighsh); + if (sorg(neighsh) != pa) sesymself(neighsh); + } + if (sum_theta < minang) { + minang = sum_theta; + } + // Go to the next facet at this segment. + spivotself(spinsh); + if (spinsh.sh == startsh.sh) break; + if (spinsh.sh == NULL) break; // A single facet may happen. + if (sorg(spinsh) != pa) sesymself(spinsh); + } + + return minang / PI * 180.; +} + +//============================================================================// +// // +// create_segment_info_list() Calculate the minimum dihedral angle at a // +// a given segment. // +// // +// segment_info_list = new double[segmentendpointslist_length * 4]; // +// - [0] min_dihedral_angle (degree) at this segment, // +// - [1] min_protect_cylinder_radius at this segment (for bookkeeping only), // +// - [2] min_seg_seg_angle (degree) at its endpoint [0], // +// - [3] min_seg_seg_angle (degree) at its endpoint [1]. // +// // +// This function must be called after makesegmentendpointsmap(). The number // +// of unique segments (segmentendpointslist_length) is calculated. // +// // +//============================================================================// + +void tetgenmesh::create_segment_info_list() +{ + face min_dihedral_ang_seg; + point min_face_ang_vertex; + REAL min_dihedral_ang = 360.; + REAL min_face_ang = 360.; + + if (b->verbose > 0) { + printf(" Creating the segment_info_list.\n"); + } + if (segment_info_list != NULL) { + delete [] segment_info_list; + } + + if (subsegs->items == 0) { + return; // There is no segments. + } + + int count = (segmentendpointslist_length + 1) * 4; + segment_info_list = new double[count]; + for (int i = 0; i < count; i++) { + segment_info_list[i] = 0.; + } + + // Loop through the list of segments. + face segloop; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + int segidx = getfacetindex(segloop); + // Check if this segment has been already calulcated. + double *values = &(segment_info_list[segidx * 4]); + + // The min_diahedral_angle at this segment is in (0, 2pi]. + if (values[0] == 0.) { + // Get the smallest dihedral angle at this segment. + values[0] = get_min_diahedral_angle(&segloop); + if (values[0] < min_dihedral_ang) { + min_dihedral_ang = values[0]; + min_dihedral_ang_seg = segloop; + } + } + + point *endpts = &(segmentendpointslist[segidx * 2]); + + for (int k = 0; k < 2; k++) { + segloop.shver = 0; + if (values[2+k] == 0.) { + if (sorg(segloop) != endpts[k]) { + sesymself(segloop); + } + if (sorg(segloop) == endpts[k]) { + // Get the min face angle at vertex endpts[0]. + values[2+k] = get_min_angle_at_ridge_vertex(&segloop); + if (values[2+k] < min_face_ang) { + min_face_ang = values[2+k]; + min_face_ang_vertex = endpts[k]; + } + } + } + } + + segloop.sh = shellfacetraverse(subsegs); + } + + if (b->verbose) { + printf(" min_dihedral angle = %g degree, at segment [%d,%d]\n", + min_dihedral_ang, pointmark(sorg(min_dihedral_ang_seg)), + pointmark(sdest(min_dihedral_ang_seg))); + printf(" min face angle = %g degree, at vertex %d\n", + min_face_ang, pointmark(min_face_ang_vertex)); + } + +} + +//============================================================================// +// // +// makefacetverticesmap() Create a map from facet to its vertices. // +// // +// All facets will be indexed (starting from 0). The map is saved in two // +// global arrays: 'idx2facetlist' and 'facetverticeslist'. // +// // +//============================================================================// + +void tetgenmesh::makefacetverticesmap() +{ + arraypool *facetvertexlist, *vertlist, **paryvertlist; + face subloop, neighsh, *parysh, *parysh1; + point pa, *ppt, *parypt; + verttype vt; + int facetindex, totalvertices; + unsigned long max_facet_size = 0l; + int max_facet_idx = 0; + int i, j, k; + + if (b->verbose) { + printf(" Creating the facet vertices map.\n"); + } + + facetvertexlist = new arraypool(sizeof(arraypool *), 10); + facetindex = totalvertices = 0; + + // The index might start from 0 or 1. + idx_ridge_vertex_facet_list = new int[points->items + 2]; + for (i = 0; i < points->items + 2; i++) { + idx_ridge_vertex_facet_list[i] = 0; + } + + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + if (!sinfected(subloop)) { + // A new facet. Create its vertices list. + vertlist = new arraypool(sizeof(point *), 8); + ppt = (point *) &(subloop.sh[3]); + for (k = 0; k < 3; k++) { + vt = pointtype(ppt[k]); + //if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { + if (vt == RIDGEVERTEX) { + pinfect(ppt[k]); + vertlist->newindex((void **) &parypt); + *parypt = ppt[k]; + // for creating ridge_vertex-to-facet map. + idx_ridge_vertex_facet_list[pointmark(ppt[k])]++; + } + } + sinfect(subloop); + caveshlist->newindex((void **) &parysh); + *parysh = subloop; + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + setfacetindex(*parysh, facetindex); + for (j = 0; j < 3; j++) { + if (!isshsubseg(*parysh)) { + spivot(*parysh, neighsh); + if (!sinfected(neighsh)) { + pa = sapex(neighsh); + if (!pinfected(pa)) { + vt = pointtype(pa); + //if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { + if (vt == RIDGEVERTEX) { + pinfect(pa); + vertlist->newindex((void **) &parypt); + *parypt = pa; + // for creating ridge_vertex-to-facet map. + idx_ridge_vertex_facet_list[pointmark(pa)]++; + } + } + sinfect(neighsh); + caveshlist->newindex((void **) &parysh1); + *parysh1 = neighsh; + } + } + senextself(*parysh); + } + } // i + totalvertices += (int) vertlist->objects; + if (max_facet_size < vertlist->objects) { + max_facet_size = vertlist->objects; + max_facet_idx = facetindex; + } + // Uninfect facet vertices. + for (k = 0; k < vertlist->objects; k++) { + parypt = (point *) fastlookup(vertlist, k); + puninfect(*parypt); + } + caveshlist->restart(); + // Save this vertex list. + facetvertexlist->newindex((void **) &paryvertlist); + *paryvertlist = vertlist; + facetindex++; + } + subloop.sh = shellfacetraverse(subfaces); + } + + // All subfaces are infected. Uninfect them. + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + suninfect(subloop); + subloop.sh = shellfacetraverse(subfaces); + } + + if (b->verbose) { + printf(" Found %ld facets. Max facet idx(%d), size(%ld)\n", + facetvertexlist->objects, max_facet_idx, max_facet_size); + } + + number_of_facets = facetindex; + idx2facetlist = new int[facetindex + 1]; + facetverticeslist = new point[totalvertices]; + + // create ridge_vertex-to-facet map. + j = idx_ridge_vertex_facet_list[0]; //k; + idx_ridge_vertex_facet_list[0] = 0; + for (i = 0; i < points->items + 1; i++) { + k = idx_ridge_vertex_facet_list[i+1]; + idx_ridge_vertex_facet_list[i+1] = idx_ridge_vertex_facet_list[i] + j; + j = k; + } + + int total_count = idx_ridge_vertex_facet_list[i] + 1; + ridge_vertex_facet_list = new int[total_count]; + + // Bookkeeping + totalworkmemory += ((facetindex + 1) * sizeof(int) + + totalvertices * sizeof(point *)); + + idx2facetlist[0] = 0; + for (i = 0, k = 0; i < facetindex; i++) { + paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); + vertlist = *paryvertlist; + idx2facetlist[i + 1] = (idx2facetlist[i] + (int) vertlist->objects); + for (j = 0; j < vertlist->objects; j++) { + parypt = (point *) fastlookup(vertlist, j); + facetverticeslist[k] = *parypt; + k++; + // create ridge_vertex-to-facet map. + int ridge_idx = pointmark(*parypt); // index of this ridge vertex + // 'i' is the current facet index. + ridge_vertex_facet_list[idx_ridge_vertex_facet_list[ridge_idx]] = i; + // for the next facet index of this ridge vertex. + idx_ridge_vertex_facet_list[ridge_idx]++; + } + } + + // Counters in idx_ridge_vertex_facet_list[] are shifted by 1. + for (i = points->items; i >= 0; i--) { + idx_ridge_vertex_facet_list[i+1] = idx_ridge_vertex_facet_list[i]; + } + idx_ridge_vertex_facet_list[0] = 0; + + + // Free the lists. + for (i = 0; i < facetvertexlist->objects; i++) { + paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); + vertlist = *paryvertlist; + delete vertlist; + } + delete facetvertexlist; +} + +//============================================================================// +// // +// create_segment_facet_map() Create the map from segments to adjacent // +// facets. // +// // +//============================================================================// + +void tetgenmesh::create_segment_facet_map() +{ + if (b->verbose > 0) { + printf(" Creating the segment-to-facets map.\n"); + } + if (idx_segment_facet_list != NULL) { + delete [] idx_segment_facet_list; + delete [] segment_facet_list; + } + + face startsh, spinsh; + face segloop; + int segindex, facetidx; + int totalcount = 0; + int i; + + // both segment-index and facet-index start from zero. + idx_segment_facet_list = new int[segmentendpointslist_length + 1]; + for (i = 0; i < segmentendpointslist_length + 1; i++) { + idx_segment_facet_list[i] = 0; + } + + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + segindex = getfacetindex(segloop); + if (idx_segment_facet_list[segindex] == 0) { + // Count the number of facets at this segment. + spivot(segloop, startsh); + spinsh = startsh; + while (spinsh.sh != NULL) { + idx_segment_facet_list[segindex]++; + spivotself(spinsh); + if (spinsh.sh == startsh.sh) break; + } + totalcount += idx_segment_facet_list[segindex]; + } + segloop.sh = shellfacetraverse(subsegs); + } + + // A working list. + bool *bflags = new bool[segmentendpointslist_length + 1]; + + // Have got the totalcount, fill the starting indices into the list. + int j = idx_segment_facet_list[0], k; + idx_segment_facet_list[0] = 0; + //for (i = 0; i < segmentendpointslist_length + 1; i++) { + for (i = 0; i < segmentendpointslist_length; i++) { + k = idx_segment_facet_list[i+1]; + idx_segment_facet_list[i+1] = idx_segment_facet_list[i] + j; + j = k; + bflags[i] = false; + } + + segment_facet_list = new int[totalcount + 1]; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + segindex = getfacetindex(segloop); + if (!bflags[segindex]) { + spivot(segloop, startsh); + spinsh = startsh; + while (spinsh.sh != NULL) { + facetidx = getfacetindex(spinsh); + segment_facet_list[idx_segment_facet_list[segindex]] = facetidx; + idx_segment_facet_list[segindex]++; // for the next one + spivotself(spinsh); + if (spinsh.sh == startsh.sh) break; + } + bflags[segindex] = true; + } + segloop.sh = shellfacetraverse(subsegs); + } + + // Counters in idx_segment_facet_list[] are shifted by 1. + for (i = segmentendpointslist_length - 1; i >= 0; i--) { + idx_segment_facet_list[i+1] = idx_segment_facet_list[i]; + } + idx_segment_facet_list[0] = 0; + + + delete [] bflags; +} + +//============================================================================// +// // +// ridge_vertices_adjacent() Check if two ridge vertices are connected by // +// an input segment. // +// // +//============================================================================// + +int tetgenmesh::ridge_vertices_adjacent(point e1, point e2) +{ + int idx = pointmark(e1); + int acount = idx_segment_ridge_vertex_list[idx+1]-idx_segment_ridge_vertex_list[idx]; + for (int i = 0; i < acount; i++) { + if (segment_ridge_vertex_list[idx_segment_ridge_vertex_list[idx]+i] == e2) { + return 1; // adjacent. + } + } + return 0; // not adjacent. +} + +//============================================================================// +// // +// facet_ridge_vertex_adjacent() Check if a facet and a ridge vertex is // +// adjacent by an input segment. // +// // +//============================================================================// + +int tetgenmesh::facet_ridge_vertex_adjacent(face *chkfac, point chkpt) +{ + int ridge_idx = pointmark(chkpt); + int facet_idx = getfacetindex(*chkfac); + for (int i = idx_ridge_vertex_facet_list[ridge_idx]; + i < idx_ridge_vertex_facet_list[ridge_idx+1]; i++) { + if (ridge_vertex_facet_list[i] == facet_idx) { + return 1; // They are adjacent. + } + } + return 0; +} + +//============================================================================// +// // +// segsegadjacent() Check whether two segments, or a segment and a facet, // +// or two facets are adjacent to each other. // +// // +//============================================================================// + +int tetgenmesh::segsegadjacent(face *seg1, face *seg2) +{ + int segidx1 = getfacetindex(*seg1); + int segidx2 = getfacetindex(*seg2); + + if (segidx1 == segidx2) { + return 2; // Adjacent. They are the same segment. + } + + point pa1 = segmentendpointslist[segidx1 * 2]; + point pb1 = segmentendpointslist[segidx1 * 2 + 1]; + point pa2 = segmentendpointslist[segidx2 * 2]; + point pb2 = segmentendpointslist[segidx2 * 2 + 1]; + + if ((pa1 == pa2) || (pa1 == pb2) || (pb1 == pa2) || (pb1 == pb2)) { + return 1; // Adjacent. + } + return 0; // not adjacent +} + +//============================================================================// +// // +// segfacetadjacent() Check whether a segment and a facet are adjacent or // +// not. // +// // +//============================================================================// + +int tetgenmesh::segfacetadjacent(face *subseg, face *subsh) +{ + int seg_idx = getfacetindex(*subseg); + int facet_idx = getfacetindex(*subsh); + for (int i = idx_segment_facet_list[seg_idx]; + i < idx_segment_facet_list[seg_idx+1]; i++) { + if (segment_facet_list[i] == facet_idx) { + return 1; // They are adjacent. + } + } + return 0; +} + +//============================================================================// +// // +// facetfacetadjacent() Check whether two facets are adjacent or not. // +// // +//============================================================================// + +int tetgenmesh::facetfacetadjacent(face *subsh1, face *subsh2) +{ + int count = 0, i; + + int fidx1 = getfacetindex(*subsh1); + int fidx2 = getfacetindex(*subsh2); + + if (fidx1 == fidx2) { + return 2; // Adjacent. They are the same facet. + } + + for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { + pinfect(facetverticeslist[i]); + } + + for (i = idx2facetlist[fidx2]; i < idx2facetlist[fidx2+1]; i++) { + if (pinfected(facetverticeslist[i])) count++; + } + + // Uninfect the vertices. + for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { + puninfect(facetverticeslist[i]); + } + + if (count > 0) { + return 1; + } else { + return 0; + } +} + +//============================================================================// +// // +// is_sharp_segment() Check whether a given segment is sharp or not. // +// // +//============================================================================// + +bool tetgenmesh::is_sharp_segment(face *seg) +{ + int segidx = getfacetindex(*seg); + double mindihedang = segment_info_list[segidx*4]; + return mindihedang < 72.; // (in theory) < 72 degree is sufficient. +} + +//============================================================================// +// // +// does_seg_contain_acute_vertex() Check whether one of the endpoints of a // +// given segment is a sharp corner. // +// // +//============================================================================// + +bool tetgenmesh::does_seg_contain_acute_vertex(face* seg) +{ + int segidx = getfacetindex(*seg); + point *ppt = &(segmentendpointslist[segidx * 2]); + REAL ang = 180.; + // Get the smallest angle at its endpoints. + for (int i = 0; i < 2; i++) { + if ((ppt[i] == sorg(*seg)) || (ppt[i] == sdest(*seg))) { + if (segment_info_list[segidx * 4 + 2 + i] < ang) { + ang = segment_info_list[segidx * 4 + 2 + i]; + } + } + } + return ang < 60.; +} + +//============================================================================// +// // +// create_a_shorter_edge() Can we create an edge (which is shorter than // +// minedgelength) between the two given vertices? // +// // +//============================================================================// + +bool tetgenmesh::create_a_shorter_edge(point steinerpt, point nearpt) +{ + bool createflag = false; // default, do not create a shorter edge. + + enum verttype nearpt_type = pointtype(nearpt); + enum verttype steiner_type = pointtype(steinerpt); + + if (nearpt_type == RIDGEVERTEX) { + if (steiner_type == FREESEGVERTEX) { + // Create a shorter edge if the Steiner point does not on an adjacent + // segment of this ridge vertex. + face parentseg; + sdecode(point2sh(steinerpt), parentseg); + int segidx = getfacetindex(parentseg); + point pa = segmentendpointslist[segidx * 2]; + point pb = segmentendpointslist[segidx * 2 + 1]; + if ((pa != nearpt) && (pb != nearpt)) { + createflag = true; // create a shorter edge. + } + } else if (steiner_type == FREEFACETVERTEX) { + // Create a shorter edge if the Steiner point does not on an adjacent + // facet of this ridge vertex. + face parentsh; + sdecode(point2sh(steinerpt), parentsh); + if (!facet_ridge_vertex_adjacent(&parentsh, nearpt)) { + createflag = true; // create a shorter edge. + } + } + } else if (nearpt_type == FREESEGVERTEX) { + if (steiner_type == FREESEGVERTEX) { + // Check if they are on the same segment. + face seg1, seg2; + sdecode(point2sh(steinerpt), seg1); + sdecode(point2sh(nearpt), seg2); + int sidx1 = getfacetindex(seg1); + int sidx2 = getfacetindex(seg2); + if (sidx1 != sidx2) { + createflag = true; // create a shorter edge. + } + } else if (steiner_type == FREEFACETVERTEX) { + face parentseg, paresntsh; + sdecode(point2sh(steinerpt), paresntsh); + sdecode(point2sh(nearpt), parentseg); + if (!segfacetadjacent(&parentseg, &paresntsh)) { + createflag = true; // create a shorter edge. + } + } + } else if (nearpt_type == FREEFACETVERTEX) { + if (steiner_type == FREESEGVERTEX) { + //assert(0); // to debug... + face parentseg, paresntsh; + sdecode(point2sh(nearpt), paresntsh); + sdecode(point2sh(steinerpt), parentseg); + if (!segfacetadjacent(&parentseg, &paresntsh)) { + createflag = true; // create a shorter edge. + } + } else if (steiner_type == FREEFACETVERTEX) { + // Create a short edge if they are on two different facets. + face paresntsh1, paresntsh2; + sdecode(point2sh(nearpt), paresntsh1); + sdecode(point2sh(steinerpt), paresntsh2); + int sidx1 = getfacetindex(paresntsh1); + int sidx2 = getfacetindex(paresntsh2); + if (sidx1 != sidx2) { + createflag = true; // create a shorter edge. + } + } + } + + return createflag; +} + +//============================================================================// +// // +// enqueuesubface() Queue a subface or a subsegment for encroachment check.// +// // +//============================================================================// + +void tetgenmesh::enqueuesubface(memorypool *pool, face *chkface) +{ + if (!smarktest2ed(*chkface)) { + smarktest2(*chkface); // Only queue it once. + face *queface = (face *) pool->alloc(); + *queface = *chkface; + } +} + +//============================================================================// +// // +// enqueuetetrahedron() Queue a tetrahedron for quality check. // +// // +//============================================================================// + +void tetgenmesh::enqueuetetrahedron(triface *chktet) +{ + if (!marktest2ed(*chktet)) { + marktest2(*chktet); // Only queue it once. + triface *quetet = (triface *) badtetrahedrons->alloc(); + *quetet = *chktet; + } +} + +//============================================================================// +// // +// check_encroachment() Check whether a given point encroaches upon a line // +// segment or not. // +// // +// 'checkpt' should not be dummypoint. // +// // +//============================================================================// + +bool tetgenmesh::check_encroachment(point pa, point pb, point checkpt) +{ + // dot = (pa->checkpt) * (pb->checkpt) + REAL d = (pa[0] - checkpt[0]) * (pb[0] - checkpt[0]) + + (pa[1] - checkpt[1]) * (pb[1] - checkpt[1]) + + (pa[2] - checkpt[2]) * (pb[2] - checkpt[2]); + return d < 0.; // cos\theta < 0. ==> 90 < theta <= 180 degree. +} + +//============================================================================// +// // +// check_enc_segment() Is a given segment encroached? // +// // +//============================================================================// + +bool tetgenmesh::check_enc_segment(face *chkseg, point *pencpt) +{ + point *ppt = (point *) &(chkseg->sh[3]); + + if (*pencpt != NULL) { + return check_encroachment(ppt[0], ppt[1], *pencpt); + } + + triface searchtet, spintet; + point encpt = NULL, tapex; + REAL prjpt[3]; // The projection point from encpt to segment. + REAL minprjdist = 0., prjdist; + int t1ver; + + sstpivot1(*chkseg, searchtet); + spintet = searchtet; + while (1) { + tapex = apex(spintet); + if (tapex != dummypoint) { + if (check_encroachment(ppt[0], ppt[1], tapex)) { + // Find one encroaching vertex. Calculate its projection distance + projpt2edge(tapex, ppt[0], ppt[1], prjpt); + prjdist = distance(tapex, prjpt); + if (encpt == NULL) { + encpt = tapex; + minprjdist = prjdist; + } else { + if (prjdist < minprjdist) { + encpt = tapex; + minprjdist = prjdist; + } + } + } + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + + if (encpt != NULL) { + *pencpt = encpt; // Return this enc point. + return true; + } + + return false; // do not split it. +} + +//============================================================================// +// // +// get_steiner_on_segment() Get the Steiner point to split a given segment.// +// // +//============================================================================// + +bool tetgenmesh::get_steiner_on_segment(face* seg, point refpt, point steinpt) +{ + point ei = sorg(*seg); + point ej = sdest(*seg); + //if (*prefpt == NULL) { + // // Check if this segment is encroached by some existing vertices. + // assert(0); // to do ... + //} + // Is this segment contains an acute seg-seg angle? + bool acute_flag = false; + int i; + + if ((refpt) != NULL) { + // This segment is encroched by an existing vertex. + REAL L, L1, t; + + if (pointtype(refpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(refpt), parentseg); + int sidx1 = getfacetindex(parentseg); + point far_pi = segmentendpointslist[sidx1 * 2]; + point far_pj = segmentendpointslist[sidx1 * 2 + 1]; + int sidx2 = getfacetindex(*seg); + point far_ei = segmentendpointslist[sidx2 * 2]; + point far_ej = segmentendpointslist[sidx2 * 2 + 1]; + if ((far_pi == far_ei) || (far_pj == far_ei)) { + // Two segments are adjacent at far_ei! + // Create a Steiner point at the intersection of the segment + // [far_ei, far_ej] and the sphere centered at far_ei with + // radius |far_ei - refpt|. + L = distance(far_ei, far_ej); + L1 = distance(far_ei, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); + } + REAL lfs_at_steiner = distance(refpt, steinpt); + //REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if (/*(dist_to_ei < lfs_at_steiner) ||*/ + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + set_ridge_vertex_protecting_ball(far_ei); + acute_flag = true; + } else if ((far_pi == far_ej) || (far_pj == far_ej)) { + // Two segments are adjacent at far_ej! + L = distance(far_ei, far_ej); + L1 = distance(far_ej, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); + } + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + //REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) /*|| + (dist_to_ej < lfs_at_steiner)*/) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + set_ridge_vertex_protecting_ball(far_ej); + acute_flag = true; + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) || + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + } + } else if (pointtype(refpt) == RIDGEVERTEX) { + int sidx2 = getfacetindex(*seg); + point far_ei = segmentendpointslist[sidx2 * 2]; + point far_ej = segmentendpointslist[sidx2 * 2 + 1]; + if (ridge_vertices_adjacent(far_ei, refpt)) { + // Thjey are adjacent at far_ei. + // Create a Steiner point at the intersection of the segment + // [far_ei, far_ej] and the sphere centered at far_ei with + // radius |far_ei - refpt|. + L = distance(far_ei, far_ej); + L1 = distance(far_ei, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); + } + REAL lfs_at_steiner = distance(refpt, steinpt); + //REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if (/*(dist_to_ei < lfs_at_steiner) ||*/ + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + set_ridge_vertex_protecting_ball(far_ei); + acute_flag = true; + } else if (ridge_vertices_adjacent(far_ej, refpt)) { + // Calulate a new point. + L = distance(far_ei, far_ej); + L1 = distance(far_ej, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); + } + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + //REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) /*|| + (dist_to_ej < lfs_at_steiner)*/) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + set_ridge_vertex_protecting_ball(far_ej); + acute_flag = true; + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) || + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + } + } else if (pointtype(refpt) == FREEFACETVERTEX) { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) || + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + // Make sure that steinpt is not too close to ei and ej. + REAL lfs_at_steiner = distance(refpt, steinpt); + REAL dist_to_ei = distance(steinpt, ei); + REAL dist_to_ej = distance(steinpt, ej); + if ((dist_to_ei < lfs_at_steiner) || + (dist_to_ej < lfs_at_steiner)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + } + + // Make sure that steinpt is not too close to ei and ej. + } else { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } + } + + + return acute_flag; +} + +//============================================================================// +// // +// split_segment() Split a given segment. // +// // +// If param != NULL, it contains the circumcenter and its insertion radius // +// of a bad quality tetrahedron. Tghis circumcenter is rejected since it // +// encroaches upon this segment. // +// // +//============================================================================// + +bool tetgenmesh::split_segment(face *splitseg, point encpt, REAL *param, + int qflag, int chkencflag, int *iloc) +{ + triface searchtet; + face searchsh; + point newpt; + insertvertexflags ivf; + + + insert_point_count++; + if (!b->quiet && (b->refine_progress_ratio > 0)) { + if (insert_point_count >= report_refine_progress) { + printf(" %ld insertions, added %ld points", + insert_point_count - last_insertion_count, + points->items - last_point_count); + last_point_count = points->items; // update it. + last_insertion_count = insert_point_count; + if (check_tets_list->objects > 0l) { + printf(", %ld tetrahedra in queue.\n", check_tets_list->objects); + } else if (split_subfaces_pool->items > 0l) { + printf(", %ld subfaces in queue.\n", split_subfaces_pool->items); + } else { + printf(", %ld segments in queue.\n", split_segments_pool->items); + } + // The next report event + report_refine_progress *= (1. + b->refine_progress_ratio); + } + } + // Is this segment shared by two facets form an acute dihedral angle? + int segidx = getfacetindex(*splitseg); + bool is_sharp = is_sharp_segment(splitseg); + + if (!qflag && (encpt == NULL)) { + // The split of this segment is due to a rejected ccent of a bad quality + // subface or a tetrahedron. + if (is_sharp) { + // Do not split a sharp segment. + *iloc = (int) SHARPCORNER; + return false; + } + // Do not split this segment if one of its endpoints is a sharp corner. + if (does_seg_contain_acute_vertex(splitseg)) { + *iloc = (int) SHARPCORNER; + return false; + } + } + + // We need to know whether the segment of the new point is adjacent + // to another segment which contains the encroached point (encpt). + makepoint(&newpt, FREESEGVERTEX); + get_steiner_on_segment(splitseg, encpt, newpt); + + // For create_a_shorter_edge() called in insertpoint(). + setpoint2sh(newpt, sencode(*splitseg)); + + // Split the segment by the Bowyer-Watson algorithm. + sstpivot1(*splitseg, searchtet); + ivf.iloc = (int) ONEDGE; + ivf.bowywat = 3; // Use Bowyer-Watson, preserve subsegments and subfaces; + ivf.validflag = 1; // Validate the B-W cavity. + ivf.lawson = 2; // Do flips to recover Delaunayness. + ivf.rejflag = 0; // Do not check encroachment of new segments/facets. + if (b->metric) { + ivf.rejflag |= 4; // Do check encroachment of protecting balls. + } + ivf.chkencflag = chkencflag; + ivf.sloc = (int) INSTAR; // ivf.iloc; + ivf.sbowywat = 3; // ivf.bowywat; // Surface mesh options. + ivf.splitbdflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + ivf.smlenflag = useinsertradius; // Return the distance to its nearest vertex. + // Reject a near Steiner point on this segment when: + // - it is only encroached by a rejected circumcenter, or + // - the insertion of the reject ccent is not due to mesh size (qflag). + if (!qflag) { //if (!is_adjacent || !qflag) { + ivf.check_insert_radius = useinsertradius; + } + ivf.parentpt = NULL; + + if (insertpoint(newpt, &searchtet, &searchsh, splitseg, &ivf)) { + st_segref_count++; + if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + REAL rv = 0.0; // param[3]; // emin, maybe zero. + + if (is_sharp) { + // A Steiner point on a sharp segment needs insertion radius. + // Default use the distance to its neartest vertex. + double L = ivf.smlen * 0.95; // (ivf.smlen / 3.); + // Choose the larger one between param[3] and L + rv = (param[3] > L ? param[3] : L); + // Record the minimum insertion radius for this segment. + double minradius = segment_info_list[segidx*4+1]; + if (minradius == 0.) { + minradius = rv; + } else { + if (rv < minradius) minradius = rv; + } + segment_info_list[segidx*4+1] = minradius; + } + + setpointinsradius(newpt, rv); // ivf.smlen + setpoint2ppt(newpt, ivf.parentpt); + if (ivf.smlen < smallest_insradius) { // rv? + smallest_insradius = ivf.smlen; + } + } + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + //unflipqueue->restart(); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + + *iloc = ivf.iloc; + return true; + } else { + // Point is not inserted. + if (ivf.iloc == (int) NEARVERTEX) { + terminatetetgen(this, 2); // report a bug. + } + + + pointdealloc(newpt); + + *iloc = ivf.iloc; + return false; + } +} + +//============================================================================// +// // +// repairencsegs() Repair encroached (sub) segments. // +// // +//============================================================================// + +void tetgenmesh::repairencsegs(REAL *param, int qflag, int chkencflag) +{ + int split_count = 0, rej_count = 0; + bool ref_segment = ((b->cdtrefine & 1) > 0); // -D1, -D3, -D5, -D7 + + while (ref_segment && + ((badsubsegs->items > 0) || (split_segments_pool->items > 0))) { + + if (badsubsegs->items > 0) { + badsubsegs->traversalinit(); + face *bface = (face *) badsubsegs->traverse(); + while (bface != NULL) { + // A queued segment may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued segment may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + point encpt = NULL; + if (check_enc_segment(bface, &encpt)) { + badface *bf = (badface *) split_segments_pool->alloc(); + bf->init(); + bf->ss = *bface; + bf->forg = sorg(*bface); + bf->fdest = sdest(*bface); + bf->noppo = encpt; + // Push it onto stack. + bf->nextitem = stack_enc_segments; + stack_enc_segments = bf; + } + } + } + bface = (face *) badsubsegs->traverse(); + } // while (bface != NULL) + badsubsegs->restart(); + } // if (badsubsegs->items > 0) + + if (split_segments_pool->items == 0) break; + + // Stop if we have used the desried number of Steiner points. + if (steinerleft == 0) break; + // Stop if the desried number of tetrahedra is reached. + if ((elem_limit > 0) && + ((tetrahedrons->items - hullsize) > elem_limit)) break; + + // Pop up an encroached segment. + badface *bf = stack_enc_segments; + stack_enc_segments = bf->nextitem; + if ((bf->ss.sh != NULL) && + (sorg(bf->ss) == bf->forg) && + (sdest(bf->ss) == bf->fdest)) { + int iloc = (int) UNKNOWN; + split_count++; + if (!split_segment(&(bf->ss), bf->noppo, param, qflag, chkencflag, &iloc)) { + rej_count++; + } + } + // Return this badface to the pool. + split_segments_pool->dealloc((void *) bf); + } + + if (b->verbose > 2) { + printf(" Trying to split %d segments, %d were rejected.\n", + split_count, rej_count); + } + + if (badsubsegs->items > 0) { + // Clean this list (due to ref_segment). + badsubsegs->traversalinit(); + face *bface = (face *) badsubsegs->traverse(); + while (bface != NULL) { + // A queued segment may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued segment may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } + } + bface = (face *) badsubsegs->traverse(); + } // while (bface != NULL) + badsubsegs->restart(); + } // if (badsubsegs->items > 0) + + if (split_segments_pool->items > 0) { + if (steinerleft == 0) { + if (b->verbose) { + printf("The desired number of Steiner points is reached.\n"); + } + } else if (elem_limit > 0) { + if (b->verbose) { + printf("The desired number %ld of elements is reached.\n", elem_limit); + } + } + split_segments_pool->restart(); + stack_enc_segments = NULL; + } +} + +//============================================================================// +// // +// get_subface_ccent() Calculate the circumcenter of the diametrical circ- // +// umsphere of a given subface. // +// // +//============================================================================// + +bool tetgenmesh::get_subface_ccent(face *chkfac, REAL *pos) +{ + point P = (point) chkfac->sh[3]; + point Q = (point) chkfac->sh[4]; + point R = (point) chkfac->sh[5]; + + if (circumsphere(P, Q, R, NULL, pos, NULL)) { + return true; + } else { + terminatetetgen(this, 2); + return false; + } + +} + +//============================================================================// +// // +// check_enc_subface() Check if a given subface is encroached or not. // +// // +//============================================================================// + +bool tetgenmesh::check_enc_subface(face *chkfac, point *pencpt, REAL *ccent, + REAL *radius) +{ + triface adjtet; + point encpt = NULL, pa, pb, pc, toppo; + REAL prjpt[3], minprjdist = 0., prjdist; + REAL ori; + int t1ver; + + //get_subface_ccent(chkfac, ccent); + REAL rd = distance(ccent, sorg(*chkfac)); + *radius = rd; + + if (*pencpt != NULL) { + // This is only used during the insertion of a Steiner point. + REAL len = distance(ccent, *pencpt); + if ((fabs(len - rd) / rd) < 1e-3) len = rd; // Rounding. + if (len < rd) { + return true; + } + return false; + } + + stpivot(*chkfac, adjtet); + if (adjtet.tet == NULL) { + // This subface is not attached to any tet. + return false; + } + for (int i = 0; i < 2; i++) { + toppo = oppo(adjtet); + if (toppo != dummypoint) { + REAL len = distance(ccent, toppo); + //if ((fabs(len - rd) / rd) < b->epsilon) len = rd; // Rounding. + if ((fabs(len - rd) / rd) < 1e-3) len = rd; // Rounding. + if (len < rd) { + int adjacent = 0; // not adjacent + if (pointtype(toppo) == RIDGEVERTEX) { + adjacent = facet_ridge_vertex_adjacent(chkfac, toppo); + } else if (pointtype(toppo) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(toppo), parentseg); + adjacent = segfacetadjacent(&parentseg, chkfac); + } else if (pointtype(toppo) == FREEFACETVERTEX) { + face parentsh; + sdecode(point2sh(toppo), parentsh); + int facidx1 = getfacetindex(parentsh); + int facidx2 = getfacetindex(*chkfac); + if (facidx1 == facidx2) { + adjacent = 1; // They are on the same facet. + } + } + if (adjacent) { + // They are adjacent and they are on the same facet. + flippush(flipstack, &adjtet); + return false; + } + pa = org(adjtet); + pb = dest(adjtet); + pc = apex(adjtet); + projpt2face(toppo, pa, pb, pc, prjpt); + ori = orient3d(pa, pb, toppo, prjpt); + if (ori >= 0) { + ori = orient3d(pb, pc, toppo, prjpt); + if (ori >= 0) { + ori = orient3d(pc, pa, toppo, prjpt); + if (ori >= 0) { + prjdist = distance(toppo, prjpt); + if (encpt == NULL) { + encpt = toppo; + minprjdist = prjdist; + } else { + if (prjdist < minprjdist) { + encpt = toppo; + minprjdist = prjdist; + } + } + } // if (ori >= 0) + } // if (ori >= 0) + } // if (ori >= 0) + } // if (len < rd) + } + fsymself(adjtet); + } + + if (encpt != NULL) { + *pencpt = encpt; + return true; + } + + return false; // this subface is not encroached. +} + +//============================================================================// +// // +// check_subface() Is a given subface in a bad shape (radius-edge ratio)? // +// // +//============================================================================// + +bool tetgenmesh::check_subface(face *chkfac, REAL *ccent, REAL radius, REAL *param) +{ + + // Get the shortest edge length. + REAL emin = 1.e+30, dist; + int shver = 0; + for (chkfac->shver = 0; chkfac->shver < 3; chkfac->shver++) { + dist = distance(sorg(*chkfac), sdest(*chkfac)); + if (dist < emin) { + emin = dist; + shver = chkfac->shver; + } + } + chkfac->shver = shver; + + REAL ratio = radius / emin; + if (ratio > b->minratio) { + // Set a small value to protect this vertex (refer to J. Shewchuk). + // Enlarge the insertion radius (due to small angle) + point pa = sorg(*chkfac); + point pb = sdest(*chkfac); + REAL ra = getpointinsradius(pa); + REAL rb = getpointinsradius(pb); + if (ra > 0.) { + if (ra > emin) { + emin = ra; + } + } + if (rb > 0.) { + if (rb > emin) { + emin = rb; + } + } + + param[3] = emin; // emin / 3.; // (emin * b->minratio); + param[4] = ratio; + param[5] = 0.; // not used. + return true; // need to split it. + } + + return false; +} + +//============================================================================// +// // +// enqueue_subface() Push a badly-shaped subface into the priority queue. // +// // +//============================================================================// + +void tetgenmesh::enqueue_subface(face *bface, point encpt, REAL *ccent, REAL *param) +{ + badface *bf = (badface *) split_subfaces_pool->alloc(); + bf->init(); + bf->ss = *bface; + bf->forg = sorg(*bface); + bf->fdest = sdest(*bface); + bf->fapex = sapex(*bface); + bf->noppo = encpt; + int i; + for (i = 0; i < 3; i++) bf->cent[i] = ccent[i]; + for (i = 3; i < 6; i++) bf->cent[i] = param[i]; + + if (encpt != NULL) { + // Push it into the encroaching stack. + bf->nextitem = stack_enc_subfaces; + stack_enc_subfaces = bf; + } else { + // Push it into the priority queue. + REAL qual = 1.0; + if (param[4] > 1.) { + qual = 1.0 / param[4]; // 1 / radius_edge_ratio. + } + // Determine the appropriate queue to put the bad subface into. + int queuenumber = 0; + if (qual < 1) { + queuenumber = (int) (64.0 * (1 - qual)); + if (queuenumber > 63) { + queuenumber = 63; + } + } else { + // It's not a bad shape; put the subface in the lowest-priority queue. + queuenumber = 0; + } + + // Are we inserting into an empty queue? + if (queuefront[queuenumber] == (badface *) NULL) { + // Yes, we are inserting into an empty queue. + // Will this become the highest-priority queue? + if (queuenumber > firstnonemptyq) { + // Yes, this is the highest-priority queue. + nextnonemptyq[queuenumber] = firstnonemptyq; + firstnonemptyq = queuenumber; + } else { + // No, this is not the highest-priority queue. + // Find the queue with next higher priority. + int i = queuenumber + 1; + while (queuefront[i] == (badface *) NULL) { + i++; + } + // Mark the newly nonempty queue as following a higher-priority queue. + nextnonemptyq[queuenumber] = nextnonemptyq[i]; + nextnonemptyq[i] = queuenumber; + } + // Put the bad subface at the beginning of the (empty) queue. + queuefront[queuenumber] = bf; + } else { + // Add the bad tetrahedron to the end of an already nonempty queue. + queuetail[queuenumber]->nextitem = bf; + } + // Maintain a pointer to the last subface of the queue. + queuetail[queuenumber] = bf; + } +} + +// Return the subface at the front of the queue. +tetgenmesh::badface* tetgenmesh::top_subface() +{ + if (stack_enc_subfaces != NULL) { + return stack_enc_subfaces; + } else { + // Keep a record of which queue was accessed in case dequeuebadtetra() + // is called later. + recentq = firstnonemptyq; + // If no queues are nonempty, return NULL. + if (firstnonemptyq < 0) { + return (badface *) NULL; + } else { + // Return the first tetrahedron of the highest-priority queue. + return queuefront[firstnonemptyq]; + } + } +} + +//============================================================================// +// // +// dequeue_subface() Popup a badly-shaped subface from the priority queue. // +// // +//============================================================================// + +void tetgenmesh::dequeue_subface() +{ + badface *bf; + int i; + + if (stack_enc_subfaces != NULL) { + bf = stack_enc_subfaces; + stack_enc_subfaces = bf->nextitem; + // Return the bad subface to the pool. + split_subfaces_pool->dealloc((void *) bf); + } else { + // If queues were empty last time topbadtetra() was called, do nothing. + if (recentq >= 0) { + // Find the tetrahedron last returned by topbadtetra(). + bf = queuefront[recentq]; + // Remove the tetrahedron from the queue. + queuefront[recentq] = bf->nextitem; + // If this queue is now empty, update the list of nonempty queues. + if (bf == queuetail[recentq]) { + // Was this the highest-priority queue? + if (firstnonemptyq == recentq) { + // Yes; find the queue with next lower priority. + firstnonemptyq = nextnonemptyq[firstnonemptyq]; + } else { + // No; find the queue with next higher priority. + i = recentq + 1; + while (queuefront[i] == (badface *) NULL) { + i++; + } + nextnonemptyq[i] = nextnonemptyq[recentq]; + } + } + // Return the bad subface to the pool. + split_subfaces_pool->dealloc((void *) bf); + } + } +} + +//============================================================================// +// // +// parallel_shift() Parallel shift a triangle along its normal. // +// // +// Given a triangle (a, b, c), create a parallel triangle (pa, pb, pc) at a // +// distance above (a, b, c). // +// // +//============================================================================// + +void tetgenmesh::parallel_shift(point pa, point pb, point pc, + point pt, REAL* ppt) +{ + // Get the normal and the average edge length of this triangle. + REAL N[3], Lav; + facenormal(pa, pb, pc, N, 1, &Lav); + + // Normalize the normal. + REAL L = sqrt(N[0]*N[0]+N[1]*N[1]+N[2]*N[2]); + N[0] /= L; + N[1] /= L; + N[2] /= L; + + // Calculate the shifted vertices. + for (int i = 0; i < 3; i++) { + ppt[0] = pt[0] + Lav * N[0]; + ppt[1] = pt[1] + Lav * N[1]; + ppt[2] = pt[2] + Lav * N[2]; + } + +} + +//============================================================================// +// // +// locate_on_surface() Locate a vertex in a facet. // +// // +//============================================================================// + +enum tetgenmesh::locateresult +tetgenmesh::locate_on_surface(point searchpt, face* searchsh) +{ + enum locateresult loc = OUTSIDE; + + triface searchtet; + stpivot(*searchsh, searchtet); + if (ishulltet(searchtet)) { + sesymself(*searchsh); + stpivot(*searchsh, searchtet); + } + + // Select an edge such that pt lies to CCW of it. + point pa, pb, pc; + REAL toppo[3]; // a parallel-shifted point + REAL n1[3], n2[3], cosang; + int t1ver; // used by fnextself() + int i; + + for (i = 0; i < 3; i++) { + pa = org(searchtet); + pb = dest(searchtet); + pc = apex(searchtet); + parallel_shift(pa, pb, pc, pa, toppo); + if (orient3d(pa, pb, toppo, searchpt) > 0) { + break; + } + enextself(searchtet); + } + if (i == 3) { + terminatetetgen(this, 2); + } + + while (true) { + + // Let E = [a,b,c] and p lies to the CCW of [a->b]. + // Make sure that the searching vertex and the current subface (a,b,c) are + // (nearly) coplanar. We check the dihedral angle between (a,b,c) and + // (a,b,searchpt). If it is within the tolerance of co-planar facets, + // then we continue the search, otherwise, the search is stopped. + facenormal(pa, pb, pc, n1, 1, NULL); + facenormal(pb, pa, searchpt, n2, 1, NULL); + cosang = dot(n1, n2) / (sqrt(dot(n1, n1)) * sqrt(dot(n2, n2))); + if (cosang > cos_facet_separate_ang_tol) { + // The searching vertex is not coplanar with this subface. + loc = NONCOPLANAR; + break; + } + + parallel_shift(pa, pb, pc, pc, toppo); + REAL ori1 = orient3d(pb, pc, toppo, searchpt); + REAL ori2 = orient3d(pc, pa, toppo, searchpt); + + if (ori1 > 0) { + if (ori2 > 0) { + //break; // Found. + loc = ONFACE; break; + } else if (ori2 < 0) { + //E.ver = _eprev_tbl[E.ver]; + eprevself(searchtet); + } else { // ori2 == 0 + //E.ver = _eprev_tbl[E.ver]; + //return LOC_ON_EDGE; // ONEDGE p lies on edge [c,a] + eprevself(searchtet); + loc = ONEDGE; break; + } + } else if (ori1 < 0) { + if (ori2 > 0) { + //E.ver = _enext_tbl[E.ver]; + enextself(searchtet); + } else if (ori2 < 0) { + // Randomly choose one. + if (rand() % 2) { // flipping a coin. + //E.ver = _enext_tbl[E.ver]; + enextself(searchtet); + } else { + //E.ver = _eprev_tbl[E.ver]; + eprevself(searchtet); + } + } else { // ori2 == 0 + //E.ver = _enext_tbl[E.ver]; + enextself(searchtet); + } + } else { // ori1 == 0 + if (ori2 > 0) { + //E.ver = _enext_tbl[E.ver]; // p lies on edge [b,c]. + //return LOC_ON_EDGE; // ONEDGE + enextself(searchtet); + loc = ONEDGE; break; + } else if (ori2 < 0) { + //E.ver = _eprev_tbl[E.ver]; + eprevself(searchtet); + } else { // ori2 == 0 + //E.ver = _eprev_tbl[E.ver]; // p is coincident with apex. + //return LOC_ON_VERT; // ONVERTEX Org(E) + eprevself(searchtet); + loc = ONVERTEX; break; + } + } + + // Check if we want to cross a segment. + if (issubseg(searchtet)) { + loc = ENCSEGMENT; break; + } + + // Goto the adjacent subface at this subedge. + int fcount = 0; + while (fcount < 100000) { + esymself(searchtet); + if (issubface(searchtet)) break; + fsymself(searchtet); + fcount++; + } + if (!issubface(searchtet)) { + terminatetetgen(this, 2); // report a bug + } + + // Update the vertices. + pa = org(searchtet); + pb = dest(searchtet); + pc = apex(searchtet); + //toppo = oppo(searchtet); + } // while (true) + + tspivot(searchtet, *searchsh); + + return loc; +} + +//============================================================================// +// // +// split_subface() Split a subface. // +// // +// param[6], it contains the following data: // +// [0],[1],[2] - the location of a rejected circumcent, // +// [3] - the samllest edge length ( = insertion radius) // +// [4] - ratio-edge ratio (of this subface). // +// If it is zero, it is an encroached subface. // +// [5] - no used. // +/// // +//============================================================================// + +bool tetgenmesh::split_subface(face *splitfac, point encpt, REAL *ccent, + REAL *param, int qflag, int chkencflag, int *iloc) +{ + triface searchtet; + face searchsh; + insertvertexflags ivf; + point newpt, bak_pts[3], *ppt; + bool is_adjacent = false; + bool splitflag = false; // Indicate if any Steiner point is added. + int i; + + insert_point_count++; + if (!b->quiet && (b->refine_progress_ratio > 0.)) { + if (insert_point_count >= report_refine_progress) { + printf(" %ld insertions, added %ld points", + insert_point_count - last_insertion_count, + points->items - last_point_count); + last_point_count = points->items; // update it. + last_insertion_count = insert_point_count; + if (check_tets_list->objects > 0l) { + printf(", %ld tetrahedra in queue.\n", check_tets_list->objects); + } else { + printf(", %ld subfaces in queue.\n", split_subfaces_pool->items); + } + // The next report event + report_refine_progress *= (1. + b->refine_progress_ratio); + } + } + + // Check if this subface is adjacent to a sharp segment, i.e., it is incident + // by two facets which form an acute dihedral angle. + face checkface = *splitfac; + face checkseg; + for (i = 0; i < 3; i++) { + sspivot(checkface, checkseg); + if (checkseg.sh != NULL) { + if (is_sharp_segment(&checkseg)) { + is_adjacent = true; + break; + } + } + senext2self(checkface); + } + + if (is_adjacent) { + // Only split it either it is a bad quality triangle, or due to the + // qflag, i.e., mesh size requirement. + if (!qflag) { + if (encpt != NULL) { + *iloc = (int) SHARPCORNER; + return false; // reject splitting this subface. + } else { + if (param[4] == 0.0) { + // It is not a bad quality subface. + *iloc = (int) SHARPCORNER; + return false; // reject splitting this subface. + } + } + } + } // if (is_adjacent) + + + // Deciding the inserting point. + if (encpt != NULL) { + // Insert at the projection of the encpt on the facet. + REAL pos[3]; + ppt = (point *) &(splitfac->sh[3]); + projpt2face(encpt, ppt[0], ppt[1], ppt[2], pos); + makepoint(&newpt, FREEFACETVERTEX); + for (i = 0; i < 3; i++) newpt[i] = pos[i]; + + //if (is_adjacent) { + // Check whether this new position is too close to an existing vertex. + REAL prjdist = distance(encpt, newpt); + REAL dist, mindist = 1.e+30; + for (i = 0; i < 3; i++) { + dist = distance(ppt[i], newpt); + if (dist < mindist) mindist = dist; + } + if (mindist < prjdist) { + // Use the circumcenter of this triange instead of the proj of encpt. + for (i = 0; i < 3; i++) newpt[i] = ccent[i]; + } + //} + } else { + // Split the subface at its circumcenter. + makepoint(&newpt, FREEFACETVERTEX); + for (i = 0; i < 3; i++) newpt[i] = ccent[i]; + } + + // This info is needed by create_a_shorter_edge() (called in insertpoint()). + setpoint2sh(newpt, sencode(*splitfac)); + + + searchsh = *splitfac; + ivf.iloc = (int) locate_on_surface(newpt, &searchsh); + + if (ivf.iloc == (int) ENCSEGMENT) { + // Point lies in the outside of the facet. + pointdealloc(newpt); + *iloc = FENSEDIN; // it is a fested in vertex. + return splitflag; + } else if (ivf.iloc == (int) ONVERTEX) { + pointdealloc(newpt); + *iloc = ONVERTEX; + return splitflag; + } else if (ivf.iloc == (int) NONCOPLANAR) { + pointdealloc(newpt); + *iloc = NONCOPLANAR; + return splitflag; + } + + if ((ivf.iloc != (int) ONFACE) && (ivf.iloc != (int) ONEDGE)) { + terminatetetgen(this, 2); // report a bug + } + + // Insert the point. + stpivot(searchsh, searchtet); + ivf.bowywat = 3; // Use Bowyer-Watson. Preserve subsegments and subfaces; + ivf.lawson = 2; + ivf.rejflag = 1; // Do check the encroachment of segments. + if (b->metric) { + ivf.rejflag |= 4; // Do check encroachment of protecting balls. + } + ivf.chkencflag = (chkencflag & (~1)); + ivf.sloc = (int) INSTAR; // ivf.iloc; + ivf.sbowywat = 3; // ivf.bowywat; + ivf.splitbdflag = 1; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + ivf.refineflag = 2; + ivf.refinesh = *splitfac; + + ivf.smlenflag = useinsertradius; // Update the insertion radius. + + // Reject a near Steiner point on this subface when: + // - the insertion of the reject ccent is not due to mesh size (qflag). + if (!qflag) { + ivf.check_insert_radius = useinsertradius; + } + //if (is_adjacent) { + // ivf.parentpt = encpt; // This allows to insert a shorter edge. + //} else { + ivf.parentpt = NULL; // init + //} + + if (insertpoint(newpt, &searchtet, &searchsh, NULL, &ivf)) { + st_facref_count++; + if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + REAL rv = 0.0; // param[3]; // emin, maybe zero. + + if (is_adjacent) { // if (encpt != NULL) { + // A sharp (dihedral) angle is involved. + // Insertion radius must be > 0. + double L = (ivf.smlen / 3.); + // Choose the larger one between param[3] and L + rv = (param[3] > L ? param[3] : L); + } + + setpointinsradius(newpt, rv); + setpoint2ppt(newpt, ivf.parentpt); + if (smallest_insradius > ivf.smlen) { + smallest_insradius = ivf.smlen; + } + } + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = (chkencflag & (~1)); //chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + //unflipqueue->restart(); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + + *iloc = ivf.iloc; + return true; + } + + // Point is not inserted. + pointdealloc(newpt); + + if (ivf.iloc == (int) ENCSEGMENT) { + // Bakup the split subface. + ppt = (point *) &(splitfac->sh[3]); + for (i = 0; i < 3; i++) bak_pts[i] = ppt[i]; + + bool ref_segment = ((b->cdtrefine & 1) > 0); // -D1, -D3, -D5, or -D7 + + if (ref_segment || qflag) { + // Select an encroached segment and split it. + for (i = 0; i < encseglist->objects; i++) { + //face *paryseg = (face *) fastlookup(encseglist, i); + badface *bf = (badface *) fastlookup(encseglist, i); + if ((bf->ss.sh == NULL) || + (sorg(bf->ss) != bf->forg) || + (sdest(bf->ss) != bf->fdest)) continue; // Skip this segment. + int tmp_iloc; + if (split_segment(&(bf->ss), NULL, param, qflag, (chkencflag | 1), &tmp_iloc)) { + // A Steiner point is inserted on an encroached segment. + // Check if this subface is split as well. + if ((splitfac->sh == NULL) || (splitfac->sh[3] == NULL)) { + splitflag = true; break; + } else { + ppt = (point *) &(splitfac->sh[3]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2])) { + splitflag = true; break; + } + } + } + } + } // if (ref_segment) + encseglist->restart(); + // Some segments may be encroached. + if (badsubsegs->items > 0) { + //repairencsegs(param, qflag, (chkencflag | 1)); + repairencsegs(param, 0, (chkencflag | 1)); // qflag = 0 + } + // Check if this subface is split as well. + if ((splitfac->sh == NULL) || (splitfac->sh[3] == NULL)) { + splitflag = true; + } else { + ppt = (point *) &(splitfac->sh[3]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2])) { + splitflag = true; + } + } + } else if (ivf.iloc == (int) NEARVERTEX) { + terminatetetgen(this, 2); // report a bug + } + + *iloc = ivf.iloc; + return splitflag; +} + +//============================================================================// +// // +// repairencfacs() Repair encroached subfaces. // +// // +//============================================================================// + +void tetgenmesh::repairencfacs(REAL *param, int qflag, int chkencflag) +{ + point encpt = NULL; + REAL ccent[3], radius; //, param[6] = {0.,}; + int split_count = 0, rej_count = 0; + //int qflag = 0; + int i; + + bool ref_subface = ((b->cdtrefine & 2) > 0); // -D2, -D3, -D6, -D7 + + // This function may be called from split_tetrahedron(). In this case, the + // insertion radius of the rejected circumcenter is stored in param[3]. + // The check_subface() will return the insertion radius of the circumcenter + // of a bad quality subface also in param[3]. + REAL tet_emin = param[3]; + + while (ref_subface && + ((badsubfacs->items > 0) || (split_subfaces_pool->items > 0))) { + + if (badsubfacs->items > 0) { + badsubfacs->traversalinit(); + face *bface = (face *) badsubfacs->traverse(); + while (bface != NULL) { + // A queued subface may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued subface may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + for (i = 3; i < 6; i++) param[i] = 0.; // Clear previous values. + if (get_subface_ccent(bface, ccent)) { + encpt = NULL; + if (check_enc_subface(bface, &encpt, ccent, &radius)) { + param[3] = tet_emin; // maybe zero. + enqueue_subface(bface, encpt, ccent, param); + } else { + if (check_subface(bface, ccent, radius, param)) { + if (tet_emin > 0) { + // Use the larger one. + param[3] = (param[3] > tet_emin ? param[3] : tet_emin); + } + enqueue_subface(bface, NULL, ccent, param); + } + } + } else { + // report a bug. + terminatetetgen(this, 2); + } + } + } + bface = (face *) badsubfacs->traverse(); + } // while (bface != NULL) + + badsubfacs->restart(); // clear this pool + + // check_enc_subface() may find some non-Delaunay subfaces. + if (flippool->items > 0) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + } + } // if (badsubfacs->items > 0) + + if (split_subfaces_pool->items == 0) break; + + // Stop if we have used the desried number of Steiner points. + if (steinerleft == 0) break; + // Stop if the desried number of tetrahedra is reached. + if ((elem_limit > 0) && + ((tetrahedrons->items - hullsize) > elem_limit)) break; + + + badface *bf = top_subface(); + + if ((bf->ss.sh != NULL) && + ( sorg(bf->ss) == bf->forg) && + (sdest(bf->ss) == bf->fdest) && + (sapex(bf->ss) == bf->fapex)) { + // Try to split this subface. + encpt = bf->noppo; // The encroaching vertex. + for (i = 0; i < 3; i++) ccent[i] = bf->cent[i]; + for (i = 3; i < 6; i++) param[i] = bf->cent[i]; + split_count++; + + int iloc = (int) UNKNOWN; + if (!split_subface(&bf->ss, encpt, ccent, param, qflag, chkencflag, &iloc)) { + rej_count++; + if (qflag || ((param[4] > (3. * b->minratio)) && (iloc != SHARPCORNER))) { + // Queue a unsplit (bad quality) subface. + badface *bt = NULL; + unsplit_subfaces->newindex((void **) &bt); + //bt->init(); + *bt = *bf; + } + } + } + dequeue_subface(); + } // while ((badsubfacs->items > 0) || (split_subfaces_pool->items > 0)) + + if (b->verbose > 3) { + printf(" Tried to split %d subfaces, %d were rejected.\n", + split_count, rej_count); + } + param[3] = tet_emin; // Restore this value. + + if (badsubfacs->items > 0) { + // Clean this list (due to the ref_subface flag) + badsubfacs->traversalinit(); + face *bface = (face *) badsubfacs->traverse(); + while (bface != NULL) { + // A queued subface may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued subface may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } + } + bface = (face *) badsubfacs->traverse(); + } // while (bface != NULL) + badsubfacs->restart(); // clear this pool + } // if (badsubfacs->items > 0) + + if (split_subfaces_pool->items > 0) { + if (steinerleft == 0) { + if (b->verbose) { + printf("The desired number of Steiner points is reached.\n"); + } + } else if (elem_limit > 0) { + if (b->verbose) { + printf("The desired number %ld of elements is reached.\n", elem_limit); + } + } + split_subfaces_pool->restart(); // Clear this pool. + unsplit_subfaces->restart(); + stack_enc_subfaces = NULL; + } +} + +//============================================================================// +// // +// check_tetrahedron() Check if the tet needs to be split. // +// // +// "param[6]" returns the following data: // +// [0],[1],[2] - the location of the new point // +// [3] - the samllest edge length ( = insertion radius) // +// [4] - the radius-edge ratio // +// [5] - (optional) edge ratio // +// // +// "chktet" returns the shortest edge of this tet. // +// // +//============================================================================// + +bool tetgenmesh::check_tetrahedron(triface *chktet, REAL* param, int &qflag) +{ + point pd = (point) chktet->tet[7]; + if (pd == dummypoint) { + return false; // Do not split a hull tet. + } + + point pa = (point) chktet->tet[4]; + point pb = (point) chktet->tet[5]; + point pc = (point) chktet->tet[6]; + + + REAL D = orient3dexact(pa, pb, pc, pd); // =6*vol + + if (D >= 0.0) { + // A degenerated tetrahedron. + terminatetetgen(this, 2); + } + + qflag = 0; // default + + REAL elen[6]; + REAL vol = -D / 6.0; + REAL emin = 0., ratio = 0.; + + // Calculate the circumcenter of this tet. + point P = pa, Q = pb, R = pc, S = pd; + + REAL U[3], V[3], W[3], Z[3]; // variables. + + REAL hp = P[0]*P[0] + P[1]*P[1] + P[2]*P[2]; // - wp + REAL hq = Q[0]*Q[0] + Q[1]*Q[1] + Q[2]*Q[2]; // - wq + REAL hr = R[0]*R[0] + R[1]*R[1] + R[2]*R[2]; // - wr + REAL hs = S[0]*S[0] + S[1]*S[1] + S[2]*S[2]; // - wr + + U[0] = hp; U[1] = P[1]; U[2] = P[2]; + V[0] = hq; V[1] = Q[1]; V[2] = Q[2]; + W[0] = hr; W[1] = R[1]; W[2] = R[2]; + Z[0] = hs; Z[1] = S[1]; Z[2] = S[2]; + + REAL D1 = orient3d(U, V, W, Z); + + U[0] = P[0]; U[1] = hp; //U[2] = P[2]; + V[0] = Q[0]; V[1] = hq; //V[2] = Q[2]; + W[0] = R[0]; W[1] = hr; //W[2] = R[2]; + Z[0] = S[0]; Z[1] = hs; //Z[2] = S[2]; + + REAL D2 = orient3d(U, V, W, Z); + + /*U[0] = P[0];*/ U[1] = P[1]; U[2] = hp; + /*V[0] = Q[0];*/ V[1] = Q[1]; V[2] = hq; + /*W[0] = R[0];*/ W[1] = R[1]; W[2] = hr; + /*Z[0] = S[0];*/ Z[1] = S[1]; Z[2] = hs; + + REAL D3 = orient3d(U, V, W, Z); + + REAL DD = D * 2.; + + param[0] = D1 / DD; + param[1] = D2 / DD; + param[2] = D3 / DD; + + + param[4] = 1.0; // default a good ratio. + param[5] = vol; + + elen[0] = distance2(pc, pd); + elen[1] = distance2(pd, pa); + elen[2] = distance2(pa, pb); + elen[3] = distance2(pb, pc); + elen[4] = distance2(pb, pd); + elen[5] = distance2(pa, pc); + + // Find the shortest edge. + emin = elen[0]; + int eidx = 0; + for (int i = 1; i < 6; i++) { + if (emin > elen[i]) { + emin = elen[i]; eidx = i; + } + } + emin = sqrt(emin); + // Let chktet be the shortest edge in this tet. + chktet->ver = edge2ver[eidx]; + + // check mesh size (qflag). + if (b->varvolume || b->fixedvolume) { // -a# + if (b->fixedvolume) { + if (vol > b->maxvolume) { + // set the insertion radius, use the smaller one between the + // smallest edge length of this tet and mesh size; + emin = (emin < b->maxvolume_length ? emin : b->maxvolume_length); + qflag = 1; + } + } + if (!qflag && b->varvolume) { + REAL volbnd = volumebound(chktet->tet); + if ((volbnd > 0.0) && (vol > volbnd)) { + // set the insertion radius; + REAL msize = pow(volbnd, 1./3.) / 3.; + emin = (emin < msize ? emin : msize); + qflag = 1; + } + } + } // -a# + + if (!qflag && b->metric) { // -m + //int eidx = 0; + for (int i = 0; i < 6; i++) { + elen[i] = sqrt(elen[i]); + } + if (pa[pointmtrindex] > 0) { + // Get the longest edge {pa, pd}, {pa, pb}, {pa, pc} + REAL maxelen = elen[1]; //eidx = 1; + if (maxelen < elen[2]) {maxelen = elen[2]; /*eidx = 2;*/} + if (maxelen < elen[5]) {maxelen = elen[5]; /*eidx = 5;*/} + maxelen /= 2.0; + if (maxelen > pa[pointmtrindex]) { + emin = (emin < pa[pointmtrindex] ? emin : pa[pointmtrindex]); + //emax = maxelen; + qflag = 1; + } + } + if (!qflag && (pb[pointmtrindex] > 0)) { + // Get the longest edge at pb. + REAL maxelen = elen[2]; //eidx = 2; + if (maxelen < elen[3]) {maxelen = elen[3]; /*eidx = 3;*/} + if (maxelen < elen[4]) {maxelen = elen[4]; /*eidx = 4;*/} + maxelen /= 2.0; + if (maxelen > pb[pointmtrindex]) { + emin = (emin < pb[pointmtrindex] ? emin : pb[pointmtrindex]); + //emax = maxelen; + qflag = 1; + } + } + if (!qflag && (pc[pointmtrindex] > 0)) { + // Get the longest edge at pc. + REAL maxelen = elen[0]; //eidx = 0; + if (maxelen < elen[3]) {maxelen = elen[3]; /*eidx = 3;*/} + if (maxelen < elen[5]) {maxelen = elen[5]; /*eidx = 5;*/} + maxelen /= 2.0; + if (maxelen > pc[pointmtrindex]) { + emin = (emin < pc[pointmtrindex] ? emin : pc[pointmtrindex]); + //emax = maxelen; + qflag = 1; + } + } + if (!qflag && (pd[pointmtrindex] > 0)) { + // Get the longest edge at pd. + REAL maxelen = elen[0]; //eidx = 0; + if (maxelen < elen[1]) {maxelen = elen[1]; /*eidx = 1;*/} + if (maxelen < elen[4]) {maxelen = elen[4]; /*eidx = 4;*/} + maxelen /= 2.0; + if (maxelen > pd[pointmtrindex]) { + emin = (emin < pd[pointmtrindex] ? emin : pd[pointmtrindex]); + //emax = maxelen; + qflag = 1; + } + } + } // if (!qflag && b->metric) // -m + + if (qflag) { + param[3] = emin; // The desired mesh size. + //param[4] = 1.0; // ratio; // = 0. + //param[5] = vol; + return true; + } + + if (b->minratio > 1.0) { + REAL radius = distance(param, pa); + + ratio = radius / emin; + + + if (ratio > b->minratio) { + //qflag = 0; + // The smallest insertion radius should be at least larger than + // the smallest edge length (==> graded mesh size). + point pa = org(*chktet); + point pb = dest(*chktet); + REAL ra = getpointinsradius(pa); + REAL rb = getpointinsradius(pb); + if ((ra > 0.) && (ra > emin)) { + emin = ra; // the relaxed (enlarged) insertion radius. + } + if ((rb > 0.) && (rb > emin)) { + emin = rb; // the relaxed (enlarged) insertion radius. + } + + param[3] = emin; // (emin * b->minratio); + param[4] = ratio; + //param[5] = vol; + return true; + } + } + + return false; // no need to split this tetrahedron. +} + +//============================================================================// +// // +// checktet4split() Check if a given tet has a bad shape. // +// // +//============================================================================// + +bool tetgenmesh::checktet4split(triface *chktet, REAL* param, int& qflag) +{ + point pa, pb, pc, pd, *ppt; + REAL vda[3], vdb[3], vdc[3]; + REAL vab[3], vbc[3], vca[3]; + REAL N[4][3], L[4], cosd[6], elen[6]; + REAL maxcosd, vol, volbnd, rd, Lmax, Lmin; + REAL A[4][4], rhs[4], D; + int indx[4]; + int i, j; + + if (b->convex) { // -c + // Skip this tet if it lies in the exterior. + if (elemattribute(chktet->tet, numelemattrib - 1) == -1.0) { + return 0; + } + } + + qflag = 0; + for (i = 0; i < 6; i++) param[i] = 0.; + + pd = (point) chktet->tet[7]; + if (pd == dummypoint) { + return 0; // Do not split a hull tet. + } + + pa = (point) chktet->tet[4]; + pb = (point) chktet->tet[5]; + pc = (point) chktet->tet[6]; + + + // Get the edge vectors vda: d->a, vdb: d->b, vdc: d->c. + // Set the matrix A = [vda, vdb, vdc]^T. + for (i = 0; i < 3; i++) A[0][i] = vda[i] = pa[i] - pd[i]; + for (i = 0; i < 3; i++) A[1][i] = vdb[i] = pb[i] - pd[i]; + for (i = 0; i < 3; i++) A[2][i] = vdc[i] = pc[i] - pd[i]; + + // Get the other edge vectors. + for (i = 0; i < 3; i++) vab[i] = pb[i] - pa[i]; + for (i = 0; i < 3; i++) vbc[i] = pc[i] - pb[i]; + for (i = 0; i < 3; i++) vca[i] = pa[i] - pc[i]; + + if (!lu_decmp(A, 3, indx, &D, 0)) { + // Is it a degenerated tet (vol = 0). + REAL D = orient3dexact(pa, pb, pc, pd); // =6*vol + if (D >= 0.0) { + // A degenerated tetrahedron. + terminatetetgen(this, 2); + } + // We temporarily leave this tet. It should be fixed by mesh improvement. + return false; + } + + // Calculate the circumcenter and radius of this tet. + rhs[0] = 0.5 * dot(vda, vda); + rhs[1] = 0.5 * dot(vdb, vdb); + rhs[2] = 0.5 * dot(vdc, vdc); + lu_solve(A, 3, indx, rhs, 0); + + for (i = 0; i < 3; i++) param[i] = pd[i] + rhs[i]; + rd = sqrt(dot(rhs, rhs)); + + // Check volume if '-a#' and '-a' options are used. + if (b->varvolume || b->fixedvolume) { + vol = fabs(A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2]) / 6.0; + if (b->fixedvolume) { + if (vol > b->maxvolume) { + qflag = 1; + } + } + if (!qflag && b->varvolume) { + volbnd = volumebound(chktet->tet); + if ((volbnd > 0.0) && (vol > volbnd)) { + qflag = 1; + } + } + if (qflag == 1) { + return true; + } + } + + if (b->metric) { // -m option. Check mesh size. + // Check if the ccent lies outside one of the prot.balls at vertices. + ppt = (point *) &(chktet->tet[4]); + for (i = 0; i < 4; i++) { + if (ppt[i][pointmtrindex] > 0) { + if (rd > ppt[i][pointmtrindex]) { + qflag = 1; // Enforce mesh size. + return true; + } + } + } + } + + if (in->tetunsuitable != NULL) { + // Execute the user-defined meshing sizing evaluation. + if ((*(in->tetunsuitable))(pa, pb, pc, pd, NULL, 0)) { + return true; + } + } + + + // Check the radius-edge ratio. Set by -q#. + if (b->minratio > 0) { + elen[0] = dot(vdc, vdc); + elen[1] = dot(vda, vda); + elen[2] = dot(vab, vab); + elen[3] = dot(vbc, vbc); + elen[4] = dot(vdb, vdb); + elen[5] = dot(vca, vca); + + Lmax = Lmin = elen[0]; + int eidx = 0; + for (i = 1; i < 6; i++) { + Lmax = (Lmax < elen[i] ? elen[i] : Lmax); + //Lmin = (Lmin > elen[i] ? elen[i] : Lmin); + if (Lmin > elen[i]) { + Lmin = elen[i]; eidx = i; + } + } + // Let chktet be the shortest edge in this tet. + chktet->ver = edge2ver[eidx]; + + //Lmax = sqrt(Lmax); + Lmin = sqrt(Lmin); + D = rd / Lmin; + if (D > b->minratio) { + // A bad radius-edge ratio. + param[3] = Lmin; + param[4] = D; + param[5] = sqrt(Lmax) / Lmin; // edge ratio. + return true; + } + } // if (b->minratio > 0) + + // Check the minimum dihedral angle. Set by -q/#. + if (b->mindihedral > 0) { + // Compute the 4 face normals (N[0], ..., N[3]). + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) N[j][i] = 0.0; + N[j][j] = 1.0; // Positive means the inside direction + lu_solve(A, 3, indx, N[j], 0); + } + for (i = 0; i < 3; i++) N[3][i] = - N[0][i] - N[1][i] - N[2][i]; + // Normalize the normals. + for (i = 0; i < 4; i++) { + L[i] = sqrt(dot(N[i], N[i])); + if (L[i] == 0) { + terminatetetgen(this, 2); + } + for (j = 0; j < 3; j++) N[i][j] /= L[i]; + } + // Calculate the six dihedral angles. + cosd[0] = -dot(N[0], N[1]); // Edge cd, bd, bc. + cosd[1] = -dot(N[0], N[2]); + cosd[2] = -dot(N[0], N[3]); + cosd[3] = -dot(N[1], N[2]); // Edge ad, ac + cosd[4] = -dot(N[1], N[3]); + cosd[5] = -dot(N[2], N[3]); // Edge ab + // Get the smallest dihedral angle. + //maxcosd = mincosd = cosd[0]; + maxcosd = cosd[0]; + for (i = 1; i < 6; i++) { + //if (cosd[i] > maxcosd) maxcosd = cosd[i]; + maxcosd = (cosd[i] > maxcosd ? cosd[i] : maxcosd); + //mincosd = (cosd[i] < mincosd ? cosd[i] : maxcosd); + } + if (maxcosd > cosmindihed) { + // A bad dihedral angle. + return true; + } + } // if (b->mindihedral > 0) + + return 0; +} + +//============================================================================// +// // +// locate_point_walk() Locate a point by line searching. // +// // +//============================================================================// + +enum tetgenmesh::locateresult + tetgenmesh::locate_point_walk(point searchpt, triface* searchtet, int chkencflag) +{ + // Construct the starting point to be the barycenter of 'searchtet'. + REAL startpt[3]; + point *ppt = (point *) &(searchtet->tet[4]); + for (int i = 0; i < 3; i++) { + startpt[i] = (ppt[0][i] + ppt[1][i] + ppt[2][i] + ppt[3][i]) / 4.; + } + + point torg, tdest, tapex, toppo; + REAL ori, oriorg, oridest, oriapex; + enum locateresult loc = OUTSIDE; + enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; + + for (searchtet->ver = 0; searchtet->ver < 4; searchtet->ver++) { + torg = org(*searchtet); + tdest = dest(*searchtet); + tapex = apex(*searchtet); + ori = orient3d(torg, tdest, tapex, searchpt); + if (ori < 0) break; + } + + if (searchtet->ver == 4) { + terminatetetgen(this, 2); + } + int max_visited_tets = 10000; // tetrahedrons->items; + + // Walk through tetrahedra to locate the point. + while (max_visited_tets > 0) { + toppo = oppo(*searchtet); + + // Check if the vertex is we seek. + if (toppo == searchpt) { + // Adjust the origin of searchtet to be searchpt. + esymself(*searchtet); + eprevself(*searchtet); + loc = ONVERTEX; // return ONVERTEX; + break; + } + + // We enter from the crruent face of `serarchtet', which face do we exit? + // Find the next face which is intersect with the line (startpt->searchpt). + oriorg = orient3d(tdest, tapex, toppo, searchpt); + oridest = orient3d(tapex, torg, toppo, searchpt); + oriapex = orient3d( torg, tdest, toppo, searchpt); + + if (oriorg < 0) { + if (oridest < 0) { + if (oriapex < 0) { + // All three faces are possible. + if (tri_edge_test(tdest,tapex,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = ORGMOVE; + } else if (tri_edge_test(tapex,torg,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = DESTMOVE; + } else if (tri_edge_test(torg,tdest,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = APEXMOVE; + } else { + int s = randomnation(3); // 's' is in {0,1,2}. + if (s == 0) { + nextmove = ORGMOVE; + } else if (s == 1) { + nextmove = DESTMOVE; + } else { + nextmove = APEXMOVE; + } + } + } else { + // Two faces, opposite to origin and destination, are viable. + if (tri_edge_test(tdest,tapex,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = ORGMOVE; + } else if (tri_edge_test(tapex,torg,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = DESTMOVE; + } else { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = ORGMOVE; + } else { + nextmove = DESTMOVE; + } + } + } + } else { + if (oriapex < 0) { + // Two faces, opposite to origin and apex, are viable. + if (tri_edge_test(tdest,tapex,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = ORGMOVE; + } else if (tri_edge_test(torg,tdest,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = APEXMOVE; + } else { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = ORGMOVE; + } else { + nextmove = APEXMOVE; + } + } + } else { + // Only the face opposite to origin is viable. + nextmove = ORGMOVE; + } + } + } else { + if (oridest < 0) { + if (oriapex < 0) { + // Two faces, opposite to destination and apex, are viable. + if (tri_edge_test(tapex,torg,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = DESTMOVE; + } else if (tri_edge_test(torg,tdest,toppo,startpt,searchpt,NULL,0,NULL,NULL)) { + nextmove = APEXMOVE; + } else { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { + nextmove = DESTMOVE; + } else { + nextmove = APEXMOVE; + } + } + } else { + // Only the face opposite to destination is viable. + nextmove = DESTMOVE; + } + } else { + if (oriapex < 0) { + // Only the face opposite to apex is viable. + nextmove = APEXMOVE; + } else { + // The point we seek must be on the boundary of or inside this + // tetrahedron. Check for boundary cases. + if (oriorg == 0) { + // Go to the face opposite to origin. + enextesymself(*searchtet); + if (oridest == 0) { + eprevself(*searchtet); // edge oppo->apex + if (oriapex == 0) { + // oppo is duplicated with p. + loc = ONVERTEX; // return ONVERTEX; + break; + } + loc = ONEDGE; // return ONEDGE; + break; + } + if (oriapex == 0) { + enextself(*searchtet); // edge dest->oppo + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oridest == 0) { + // Go to the face opposite to destination. + eprevesymself(*searchtet); + if (oriapex == 0) { + eprevself(*searchtet); // edge oppo->org + loc = ONEDGE; // return ONEDGE; + break; + } + loc = ONFACE; // return ONFACE; + break; + } + if (oriapex == 0) { + // Go to the face opposite to apex + esymself(*searchtet); + loc = ONFACE; // return ONFACE; + break; + } + loc = INTETRAHEDRON; // return INTETRAHEDRON; + break; + } + } + } + + // Move to the selected face. + if (nextmove == ORGMOVE) { + enextesymself(*searchtet); + } else if (nextmove == DESTMOVE) { + eprevesymself(*searchtet); + } else { + esymself(*searchtet); + } + if (chkencflag) { + // Check if we are walking across a subface. + if (issubface(*searchtet)) { + loc = ENCSUBFACE; + break; + } + } + // Move to the adjacent tetrahedron (maybe a hull tetrahedron). + //fsymself(*searchtet); + //if (oppo(*searchtet) == dummypoint) { + // loc = OUTSIDE; // return OUTSIDE; + // break; + //} + decode(searchtet->tet[searchtet->ver & 3], *searchtet); // fsymself + if (ishulltet(*searchtet)) { + loc = OUTSIDE; // return OUTSIDE; + break; + } + max_visited_tets--; + + // Retreat the three vertices of the base face. + torg = org(*searchtet); + tdest = dest(*searchtet); + tapex = apex(*searchtet); + } // while (true) + + return loc; +} + +//============================================================================// +// // +// splittetrahedron() Split a tetrahedron. // +// // +//============================================================================// + +bool tetgenmesh::split_tetrahedron(triface* splittet, // the tet to be split. + REAL *param, // param[6], it contains the following data + // [0],[1],[2] - the location of the new point + // [3] - the samllest edge length ( = insertion radius) + // [4] - radius-edge ratio + // [5] - its volume + int qflag, // split due to mesh size enforcement. + int chkencflag, + insertvertexflags &ivf) +{ + triface searchtet; + point newpt, bak_pts[4], *ppt; + bool splitflag = false; + int i; + + + insert_point_count++; + if (!b->quiet && (b->refine_progress_ratio > 0.)) { + if (insert_point_count >= report_refine_progress) { + printf(" %ld insertions, added %ld points, %ld tetrahedra in queue.\n", + insert_point_count - last_insertion_count, + points->items - last_point_count, + check_tets_list->objects); + last_point_count = points->items; // update it. + last_insertion_count = insert_point_count; + // The next report event + report_refine_progress *= (1. + b->refine_progress_ratio); + } + } + + makepoint(&newpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) newpt[i] = param[i]; + + // Locate the new point. Starting from an interior point 'q' of the + // splittet. We perform a walk from q to the 'newpt', stop walking + // either we hit a subface or enter OUTSIDE. + searchtet = *splittet; + ivf.iloc = (int) OUTSIDE; + //ivf.iloc = locate(newpt, &searchtet, 1); // 'chkencflag' = 1. + ivf.iloc = locate_point_walk(newpt, &searchtet, 1); // 'chkencflag' = 1. + + + if ((ivf.iloc == (int) ENCSUBFACE) || (ivf.iloc == (int) OUTSIDE)) { + // The circumcenter 'c' is not visible from 'q' (the interior of the tet). + pointdealloc(newpt); // Do not insert this vertex. + + + ivf.iloc = (int) FENSEDIN; + return splitflag; + } // if (ivf.iloc == (int) ENCSUBFACE) + + // Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; + ivf.bowywat = 3; + ivf.lawson = 2; + ivf.rejflag = 3; // Do check for encroached segments and subfaces. + if (b->metric) { + ivf.rejflag |= 4; // Reject it if it lies in some protecting balls. + } + ivf.chkencflag = (chkencflag & (~3)); // chkencflag; + ivf.sloc = ivf.sbowywat = 0; // No use. + ivf.splitbdflag = 0; // No use (its an interior vertex). + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + // Mesh refinement options. + ivf.refineflag = 1; + ivf.refinetet = *splittet; + // get the shortest edge length to the new point. + ivf.smlenflag = useinsertradius; + if (!qflag) { + // Avoid creating an unnecessarily short edge. + ivf.check_insert_radius = useinsertradius; + } else { + ivf.check_insert_radius = 0; + } + ivf.parentpt = NULL; // init. + + if (insertpoint(newpt, &searchtet, NULL, NULL, &ivf)) { + // Vertex is inserted. + st_volref_count++; + if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + // Save the shortest edge between: emin and ivf.smlen + REAL rv = 0.0; // ivf.smlen; + if (param[3] > 0.0) { // The smallest edge length of this tet. + rv = (param[3] < ivf.smlen ? param[3] : ivf.smlen); + } + setpointinsradius(newpt, rv); // ivf.smlen + setpoint2ppt(newpt, ivf.parentpt); + if (ivf.smlen < smallest_insradius) { // ivf.smlen + smallest_insradius = ivf.smlen; + } + } + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = (chkencflag & (~3)); //chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + //unflipqueue->restart(); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + + return true; + } + + // Point is not inserted. + pointdealloc(newpt); + + if (ivf.iloc == (int) ENCSEGMENT) { + if (!b->nobisect) { //if (!b->nobisect && qflag) { // no -Y + // bakup the vertices of this tet. + ppt = (point *) &(splittet->tet[4]); + for (i = 0; i < 4; i++) bak_pts[i] = ppt[i]; + + bool ref_segment = ((b->cdtrefine & 1) > 0); + + if (ref_segment || qflag) { + for (i = 0; i < encseglist->objects; i++) { + //face *paryseg = (face *) fastlookup(encseglist, i); + badface *bf = (badface *) fastlookup(encseglist, i); + if ((bf->ss.sh == NULL) || + (sorg(bf->ss) != bf->forg) || + (sdest(bf->ss) != bf->fdest)) { + continue; // Skip this segment. + } + int tmp_iloc; + if (split_segment(&(bf->ss), NULL, param, qflag, (chkencflag | 3), &tmp_iloc)) { + // A Steienr point is inserted on a segment. + // Check if this tet is split as well. + if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { + splitflag = true; // The tet is split as well. + } else { + ppt = (point *) &(splittet->tet[4]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2]) || + (ppt[3] != bak_pts[3])) { + splitflag = true; // The tet is split as well. + } + } + if (splitflag) { + break; // This tetrahedron is split. + } + } + } // i + } // if (ref_segment ||qflag) + encseglist->restart(); + // Some segments may need to be repaired. + if (badsubsegs->items > 0) { + //repairencsegs(param, qflag, (chkencflag | 3)); // Queue new enroached subsegments and subfaces. + repairencsegs(param, 0, (chkencflag | 3)); // qflag = 0 + } + // Some subfaces may need to be repaired. + if (badsubfacs->items > 0) { + //repairencfacs(param, qflag, (chkencflag | 2)); // Queue new encroached subfaces. + repairencfacs(param, 0, (chkencflag | 2)); // qflag = 0 + if (unsplit_subfaces->objects > 0) { + unsplit_subfaces->restart(); // clear this list; + } + } + // Check if this tet is split as well. + if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { + splitflag = true; // The tet is split as well. + } else { + ppt = (point *) &(splittet->tet[4]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2]) || + (ppt[3] != bak_pts[3])) { + splitflag = true; // The tet is split as well. + } + } + } else { // if (!b->nobisect) { // no -Y + encseglist->restart(); + } + } else if (ivf.iloc == (int) ENCSUBFACE) { + if (!b->nobisect) { //if (!b->nobisect && qflag) { // no -Y + // bakup the vertices of this tet. + ppt = (point *) &(splittet->tet[4]); + for (i = 0; i < 4; i++) bak_pts[i] = ppt[i]; + + bool ref_subface = ((b->cdtrefine & 2) > 0); + + if (ref_subface || qflag) { + // This rejected Steiner point may encroach upon more than one subfaces. + // We split the one which contains the projection of this rejected + // Steiner point. Moreover, there may be many subfaces. + triface adjtet; + point pa, pb, pc, toppo; + REAL prjpt[3], ori; + int scount = 0; + int t1ver; + + // Clean the bad radius-edge ratio, so split_subface() knows that + // the split of this subface is due to a rejected tet ccenter. + param[4] = 0.0; + + for (i = 0; i < encshlist->objects; i++) { + badface *bface = (badface *) fastlookup(encshlist, i); + // This subface may be split. + if ((bface->ss.sh == NULL) || + (sorg(bface->ss) != bface->forg) || + (sdest(bface->ss) != bface->fdest) || + (sapex(bface->ss) != bface->fapex)) { + continue; + } + stpivot(bface->ss, adjtet); + if (ishulltet(adjtet)) { + fsymself(adjtet); + } + toppo = oppo(adjtet); // used by orient3d() + //assert(toppo != dummypoint); + pa = org(adjtet); + pb = dest(adjtet); + pc = apex(adjtet); + projpt2face(param, pa, pb, pc, prjpt); + ori = orient3d(pa, pb, toppo, prjpt); + if (ori >= 0) { + ori = orient3d(pb, pc, toppo, prjpt); + if (ori >= 0) { + ori = orient3d(pc, pa, toppo, prjpt); + if (ori >= 0) { + scount++; + // Found such a subface, try to split it. + int tmp_iloc; + split_subface(&(bface->ss), NULL, bface->cent, param, qflag, + chkencflag | 2, &tmp_iloc); + // This subface may not be split while some encroached subsegments + // might be split. + // Check if this tet is split as well. + if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { + splitflag = true; // The tet is split as well. + } else { + ppt = (point *) &(splittet->tet[4]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2]) || + (ppt[3] != bak_pts[3])) { + splitflag = true; // The tet is split as well. + } + } + if (splitflag) { + break; + } + } // if (ori >= 0) + } + } + } // i + if (scount == 0) { + // Not such subface is found! This can happen due to the existence + // of small angles and non-Delaunay elements. + // Select an encroached subface and split it. + for (i = 0; i < encshlist->objects; i++) { + badface *bface = (badface *) fastlookup(encshlist, i); + if ((bface->ss.sh == NULL) || + (sorg(bface->ss) != bface->forg) || + (sdest(bface->ss) != bface->fdest) || + (sapex(bface->ss) != bface->fapex)) { + continue; + } + //if (get_subface_ccent(&(bface->ss), ccent)) { + int tmp_iloc; + split_subface(&(bface->ss), NULL, bface->cent, param, qflag, + chkencflag | 2, &tmp_iloc); + // Check if this tet is split as well. + if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { + splitflag = true; // The tet is split as well. + } else { + ppt = (point *) &(splittet->tet[4]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2]) || + (ppt[3] != bak_pts[3])) { + splitflag = true; // The tet is split as well. + } + } + if (splitflag) { + break; // This tetrahedron is split. + } + } + } // if (scount == 0) + } // if (ref_subface) + encshlist->restart(); // Clear the list. + // Some subfaces may need to be repaired. + if (badsubfacs->items > 0) { + //repairencfacs(param, qflag, (chkencflag | 2)); // Queue new encroached subfaces. + repairencfacs(param, 0, (chkencflag | 2)); // qflag = 0 + if (unsplit_subfaces->objects > 0) { + unsplit_subfaces->restart(); // clear this list. + } + } + // Check if this tet is split as well. + if ((splittet->tet == NULL) || (splittet->tet[4] == NULL)) { + splitflag = true; // The tet is split as well. + } else { + ppt = (point *) &(splittet->tet[4]); + if ((ppt[0] != bak_pts[0]) || + (ppt[1] != bak_pts[1]) || + (ppt[2] != bak_pts[2]) || + (ppt[3] != bak_pts[3])) { + splitflag = true; // The tet is split as well. + } + } + } else { // if (!b->nobisect) + encshlist->restart(); + } + } + + return splitflag; +} + +//============================================================================// +// // +// repairbadtets() Repair bad quality tetrahedra. // +// // +//============================================================================// + +void tetgenmesh::repairbadtets(REAL queratio, int chkencflag) +{ + triface *bface, *quetet, *last_quetet; + triface checktet; + REAL param[6] = {0.,}; + int qflag = 0; + int i; + + while ((badtetrahedrons->items > 0) || (check_tets_list->objects > 0)) { + + if (badtetrahedrons->items > 0) { + badtetrahedrons->traversalinit(); + bface = (triface *) badtetrahedrons->traverse(); + while (bface != NULL) { + check_tets_list->newindex((void **) &quetet); + *quetet = *bface; + bface = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } + + // Stop if we have used the desried number of Steiner points. + if (steinerleft == 0) break; + // Stop if the desried number of tetrahedra is reached. + if ((elem_limit > 0) && + ((tetrahedrons->items - hullsize) > elem_limit)) break; + + + // Randomly select a tet to split. + i = rand() % check_tets_list->objects; + quetet = (triface *) fastlookup(check_tets_list, i); + checktet = *quetet; + + // Fill the current position by the last tet in the list. + i = check_tets_list->objects - 1; + last_quetet = (triface *) fastlookup(check_tets_list, i); + *quetet = *last_quetet; + check_tets_list->objects--; + + if (!isdeadtet(checktet)) { + if (marktest2ed(checktet)) { + unmarktest2(checktet); + //if (check_tetrahedron(&checktet, param, qflag)) { + if (checktet4split(&checktet, param, qflag)) { + bool splitflag = false; + insertvertexflags ivf; + splitflag = split_tetrahedron(&checktet, param, qflag, chkencflag, ivf); + if (!splitflag) { + if (qflag || (param[4] > queratio)) { // radius-edge ratio + badface *bt = NULL; + unsplit_badtets->newindex((void **) &bt); + bt->init(); + bt->tt = checktet; + bt->forg = org(checktet); + bt->fdest = dest(checktet); + bt->fapex = apex(checktet); + bt->foppo = oppo(checktet); + for (i = 0; i < 6; i++) bt->cent[i] = param[i]; + bt->key = (double) qflag; + } + } + } + } + } // if (!isdeadtet(checktet)) { + + } // while ((badtetrahedrons->items > 0) || (check_tets_list->objects > 0)) + + if (check_tets_list->objects > 0) { + if (steinerleft == 0) { + if (b->verbose) { + printf("The desired number of Steiner points is reached.\n"); + } + } else if (elem_limit > 0) { + if (b->verbose) { + printf("The desired number %ld of elements is reached.\n", elem_limit); + } + } + //split_tets_pool->restart(); // Clear this pool. + // Unmark all unchecked tetrahedra. + for (i = 0; i < check_tets_list->objects; i++) { + quetet = (triface *) fastlookup(check_tets_list, i); + if (!isdeadtet(*quetet)) { + unmarktest2(*quetet); + } + } + check_tets_list->restart(); + } +} + +//============================================================================// +// // +// delaunayrefinement() Refine the mesh by Delaunay refinement. // +// // +//============================================================================// + +void tetgenmesh::delaunayrefinement() +{ + triface checktet; + face checksh; + face checkseg; + long steinercount; + REAL param[6] = {0., 0., 0., 0., 0., 0.}; + int qflag = 0; + int chkencflag = 0; + int i; + + long bak_segref_count, bak_facref_count, bak_volref_count; + + if (!b->quiet) { + printf("Refining mesh...\n"); + } + + if (b->verbose) { + printf(" Min radius-edge ratio = %g.\n", b->minratio); + if (b->mindihedral > 0.) { + printf(" Min dihedral angle = %g.\n", b->mindihedral); + } + if (b->fixedvolume) { + printf(" Max tet volume = %g.\n", b->maxvolume); + } + //printf(" Min Edge length = %g.\n", b->minedgelength); + } + // Used in locate_point_on_surface(); + cos_facet_separate_ang_tol = cos(b->facet_separate_ang_tol/180.*PI); // -p/# + // Used in function is_collinear_at(mid, left, right); + cos_collinear_ang_tol = cos(b->collinear_ang_tol/180.*PI); // -p///# + + // The cosine value of the min dihedral angle (-q/#) for tetrahedra. + cosmindihed = cos(b->mindihedral / 180.0 * PI); + + steinerleft = b->steinerleft; // Upperbound of # Steiner points (by -S#). + if (steinerleft > 0) { + // Check if we've already used up the given number of Steiner points. + steinercount = st_segref_count + st_facref_count + st_volref_count; + if (steinercount < steinerleft) { + steinerleft -= steinercount; + } else { + if (!b->quiet) { + printf("\nWarning: "); + printf("The desired number of Steiner points (%d) has reached.\n\n", + b->steinerleft); + } + return; // No more Steiner points. + } + } + + if (b->refine && (b->elem_growth_ratio > 0.0)) { // -r# + int ntet = in->numberoftetrahedra; // tetrahedrons->items - hullsize; + elem_limit = ntet * (1.0 + b->elem_growth_ratio); + } + + if (b->refine_progress_ratio > 0) { // -r/# default is 0.333 + insert_point_count = 0l; + last_insertion_count = 0l; + last_point_count = points->items; + report_refine_progress = points->items * (1. + b->refine_progress_ratio); + } + + if (!b->nobisect) { // no -Y. + if (segmentendpointslist == NULL) { + makesegmentendpointsmap(); // create ridge_vertex-to-segment map. + } + create_segment_info_list(); + makefacetverticesmap(); // create ridge_vertex-to-facet map. + create_segment_facet_map(); // vreate segment-to-facet map. + } + + + // Begin of memory allocation =============================================== + // Initialize the pools and priority queues. + long bls = b->shellfaceperblock; + long blt = b->tetrahedraperblock; + + badsubsegs = new memorypool(sizeof(face), 256, sizeof(void *), 0); + badsubfacs = new memorypool(sizeof(face), 256, sizeof(void *), 0); + badtetrahedrons = new memorypool(sizeof(triface), blt, sizeof(void *), 0); + + split_segments_pool = new memorypool(sizeof(badface), bls, sizeof(void *), 0); + split_subfaces_pool = new memorypool(sizeof(badface), bls, sizeof(void *), 0); + + long est_size = blt; + int log2objperblk = 0; + while (est_size >>= 1) log2objperblk++; + if (log2objperblk < 10) log2objperblk = 10; // At least 1024. + + check_tets_list = new arraypool(sizeof(triface), log2objperblk); + + unsplit_segments = new arraypool(sizeof(badface), 10); + unsplit_subfaces = new arraypool(sizeof(badface), 10); + unsplit_badtets = new arraypool(sizeof(badface), 10); + + stack_enc_segments = stack_enc_subfaces = NULL; + + for (i = 0; i < 64; i++) { + queuefront[i] = NULL; + } + firstnonemptyq = -1; + recentq = -1; + + encseglist = new arraypool(sizeof(badface), 8); + encshlist = new arraypool(sizeof(badface), 8); + // End of memory allocation ================================================= + + + // with -r and an .elem file ================================================ + if (b->refine && (in->refine_elem_list != NULL)) { + if (b->verbose) { + printf(" Refining a list of given elements.\n"); + } + //assert(b->varvolume > 0); // -a option must be used. + chkencflag = 4; // Check bad tetrahedra. + steinercount = points->items; + + REAL queratio = b->minratio > 2. ? b->minratio : 2.0; + queratio *= 2.0; // queratio; // increase this value. + + // Create a map from indices to points. + point *idx2verlist; + makeindex2pointmap(idx2verlist); + + int *elelist = in->refine_elem_list; + int elem; + + for (elem = 0; elem < in->numberofrefineelems; elem++) { + point p1 = idx2verlist[elelist[elem*4]]; + point p2 = idx2verlist[elelist[elem*4+1]]; + point p3 = idx2verlist[elelist[elem*4+2]]; + point p4 = idx2verlist[elelist[elem*4+3]]; + + if (!get_tet(p1, p2, p3, p4, &checktet)) { + continue; + } + + REAL volume_limit; + if (in->refine_elem_vol_list != NULL) { + volume_limit = in->refine_elem_vol_list[i]; + } else { + point *ppt = (point *) &(checktet.tet[4]); + REAL volume = orient3dfast(ppt[1], ppt[0], ppt[2], ppt[3]) / 6.; + volume_limit = volume / 3.; + } + setvolumebound(checktet.tet, volume_limit); + + //assert(check_tets_list->objects == 0l); + triface *quetet; + marktest2(checktet); + check_tets_list->newindex((void **) &quetet); + *quetet = checktet; + + int maxiter = 2, iter; + + for (iter = 0; iter < maxiter; iter++) { + repairbadtets(queratio, chkencflag); + + if (later_unflip_queue->objects > 0l) { + recoverdelaunay(); + } + + // Split unsplit tetrahedra + long badtetcount = 0, splitcount = 0; + int j; + + for (i = 0; i < unsplit_badtets->objects; i++) { + badface *bt = (badface *) fastlookup(unsplit_badtets, i); + if ((bt->tt.tet != NULL) && + ( org(bt->tt) == bt->forg ) && + (dest(bt->tt) == bt->fdest) && + (apex(bt->tt) == bt->fapex) && + (oppo(bt->tt) == bt->foppo)) { + + if (steinerleft == 0) break; + if (elem_limit > 0) { + if ((tetrahedrons->items - hullsize) > elem_limit) { + break; + } + } + + // Count a live tet. + badtetcount++; + insertvertexflags ivf; + qflag = (int) bt->key; + point *ppt = (point *) &(bt->tt.tet[4]); + for (j = 0; j < 3; j++) { + param[j] = (ppt[0][j]+ppt[1][j]+ppt[2][j]+ppt[3][j]) / 4.0; + } + for (; j < 6; j++) { + param[j] = bt->cent[j]; + } + if (split_tetrahedron(&bt->tt, param, qflag, chkencflag, ivf)) { + splitcount++; + } + + if (badtetrahedrons->items > 0) { + // Push new bad quality tetrahedron into queue. + badtetrahedrons->traversalinit(); + triface *bface = (triface *) badtetrahedrons->traverse(); + while (bface != NULL) { + check_tets_list->newindex((void **) &quetet); + *quetet = *bface; + bface = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } + } + } // i + + unsplit_badtets->restart(); + + if (splitcount == 0) break; + } // iter + + if (check_tets_list->objects > 0) { + // Clean the list. + for (i = 0; i < check_tets_list->objects; i++) { + quetet = (triface *) fastlookup(check_tets_list, i); + if (!isdeadtet(*quetet)) { + unmarktest2(*quetet); + } + } + check_tets_list->restart(); + } + + if (steinerleft == 0) break; + if (elem_limit > 0) { + if ((tetrahedrons->items - hullsize) > elem_limit) { + break; + } + } + + } // elem + + if (b->verbose) { + printf(" Added %ld Steiner points.\n", points->items - steinercount); + } + delete [] idx2verlist; + } // if (b->refine && (in->refine_elem_list != NULL)) + // with -r and an .elem file ================================================ + + bool force_quit_refinement = false; + + if (steinerleft == 0) { + force_quit_refinement = true; + } else if (elem_limit > 0) { + if ((tetrahedrons->items - hullsize) > elem_limit) { + force_quit_refinement = true; + } + } + + if (!b->nobisect) { // no -Y + bool ref_segment = ((b->cdtrefine & 1) > 0); // -D1, -D3, -D5, or -D7 + + if (ref_segment && !force_quit_refinement) { + if (b->verbose) { + printf(" Splitting encroached subsegments.\n"); + } + + chkencflag = 1; // Only check encroaching subsegments. + steinercount = points->items; + + // Add all segments into the pool. + subsegs->traversalinit(); + checkseg.sh = shellfacetraverse(subsegs); + while (checkseg.sh != (shellface *) NULL) { + //enqueuesubface(badsubsegs, &checkseg); + point encpt = NULL; + if (check_enc_segment(&checkseg, &encpt)) { + badface *bf = (badface *) split_segments_pool->alloc(); + bf->init(); + bf->ss = checkseg; + bf->forg = sorg(checkseg); + bf->fdest = sdest(checkseg); + bf->noppo = encpt; + // Push it into stack. + bf->nextitem = stack_enc_segments; + stack_enc_segments = bf; + } + checkseg.sh = shellfacetraverse(subsegs); + } + + // Split all encroached segments. + for (i = 0; i < 6; i++) param[i] = 0.0; + qflag = 0; + + repairencsegs(param, qflag, chkencflag); + + if (b->verbose) { + printf(" Added %ld Steiner points.\n", points->items - steinercount); + } + + if (later_unflip_queue->objects > 0l) { + recoverdelaunay(); + } + } // if (ref_segment) + + bool ref_surface = ((b->cdtrefine & 2) > 0); // -D2, -D3, or -D7 + + if (ref_surface && !force_quit_refinement) { + if (b->verbose) { + printf(" Splitting encroached and bad quality subfaces.\n"); + } + + chkencflag = 2; // only check encroaching subfaces. + steinercount = points->items; + bak_segref_count = st_segref_count; + bak_facref_count = st_facref_count; + + // Add all subfaces into the pool. + REAL ccent[3], radius; + point encpt = NULL; + + subfaces->traversalinit(); + checksh.sh = shellfacetraverse(subfaces); + while (checksh.sh != (shellface *) NULL) { + //enqueuesubface(badsubfacs, &checksh); + if (get_subface_ccent(&checksh, ccent)) { + encpt = NULL; + for (i = 3; i < 6; i++) param[i] = 0.0; + if (check_enc_subface(&checksh, &encpt, ccent, &radius)) { + enqueue_subface(&checksh, encpt, ccent, param); + } else { + if (check_subface(&checksh, ccent, radius, param)) { + enqueue_subface(&checksh, NULL, ccent, param); + } + } + } else { + terminatetetgen(this, 2); // report a bug. + } + checksh.sh = shellfacetraverse(subfaces); + } + + // check_enc_subface() may find some non-Delaunay faces. + if (flippool->items > 0) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + } + + // Split all encroached subfaces. + for (i = 0; i < 6; i++) param[i] = 0.0; + qflag = 0; + + int maxiter = 3, iter; + + for (iter = 0; iter < maxiter; iter++) { + + if (b->verbose > 1) { + printf(" iter = %d\n", iter+1); + } + long iter_steinercount = points->items; + long iter_segref_count = st_segref_count; + long iter_facref_count = st_facref_count; + + repairencfacs(param, qflag, chkencflag); + + if (b->verbose > 1) { + printf(" Added %ld (%ld,%ld) Steiner points.\n", + points->items-iter_steinercount, + st_segref_count-iter_segref_count, + st_facref_count-iter_facref_count); + } + + if (unsplit_subfaces->objects > 0) { + if (b->verbose > 1) { + printf(" splitting %ld unsplit subfaces\n", unsplit_subfaces->objects); + } + int scount = 0; // Count the split subfaces. + + for (i = 0; i < unsplit_subfaces->objects; i++) { + badface *bf = (badface *) fastlookup(unsplit_subfaces, i); + if ((bf->ss.sh != NULL) && + ( sorg(bf->ss) == bf->forg) && + (sdest(bf->ss) == bf->fdest) && + (sapex(bf->ss) == bf->fapex)) { + // Try to split it in its barycenter. + int iloc, j; + for (j = 0; j < 3; j++) { + ccent[j] = (bf->forg[j] + bf->fdest[j] + bf->fapex[j]) / 3.; + } + for (j = 3; j < 6; j++) param[j] = bf->cent[j]; + encpt = bf->noppo; // The encroaching vertex. + if (split_subface(&bf->ss, encpt, ccent, param, qflag, chkencflag, &iloc)) { + scount++; + } + } + } // i + + unsplit_subfaces->restart(); + + if (b->verbose > 1) { + printf(" Split %d subfaces.\n", scount); + } + } else { + break; // no unsplit subfaces. + } // if (unsplit_subfaces->objects > 0) + } // iter + + if (b->verbose) { + printf(" Added %ld (%ld,%ld) Steiner points.\n", + points->items-steinercount, st_segref_count-bak_segref_count, + st_facref_count-bak_facref_count); + } + + if (badsubfacs->items > 0) { + // Clean this pool. + badsubfacs->traversalinit(); + face *bface = (face *) badsubfacs->traverse(); + while (bface != NULL) { + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } + } + bface = (face *) badsubfacs->traverse(); + } + badsubfacs->restart(); + } + + if (later_unflip_queue->objects > 0l) { + recoverdelaunay(); + } + } // if (ref_subface) + } // if (!b->nobisect) + + if (((b->cdtrefine & 4) > 0) && !force_quit_refinement) { // -D4, -D5, or -D7 + // Begin of adding Steiner points in volume =============================== + + // A Steiner point can be added only if it does not encroach upon any + // boundary segment or subface. + if (b->verbose) { + printf(" Splitting bad quality tets.\n"); + } + + for (i = 0; i < 6; i++) param[i] = 0.0; + qflag = 0; + + // Add all tetrahedra (no hull tets) into the pool. + triface *quetet; + tetrahedrons->traversalinit(); + checktet.tet = tetrahedrontraverse(); + while (checktet.tet != NULL) { + marktest2(checktet); + check_tets_list->newindex((void **) &quetet); + *quetet = checktet; + checktet.tet = tetrahedrontraverse(); + } + + + chkencflag = 4; // Check bad tetrahedra. + + REAL queratio = b->minratio > 2. ? b->minratio : 2.0; + queratio *= 2.0; // queratio; // increase this value. + + int maxiter = 3, iter; + + for (iter = 0; iter < maxiter; iter++) { + steinercount = points->items; + bak_segref_count = st_segref_count; + bak_facref_count = st_facref_count; + bak_volref_count = st_volref_count; + + if (b->verbose > 1) { + printf(" iter = %d: queratio = %g\n", iter + 1, queratio); + } + + // Split all bad quality tetrahedra. + repairbadtets(queratio, chkencflag); + + if (b->verbose) { + printf(" Added %ld (%ld,%ld,%ld) Steiner points.\n", + points->items - steinercount, + st_segref_count - bak_segref_count, + st_facref_count - bak_facref_count, + st_volref_count - bak_volref_count); + } + + + if (later_unflip_queue->objects > 0l) { + recoverdelaunay(); + } + + if (unsplit_badtets->objects == 0) break; + + //queratio *= 2.0; // queratio; // increase this value. + + // Split unsplit tetrahedra + long badtetcount = 0, splitcount = 0; + int j; + + for (i = 0; i < unsplit_badtets->objects; i++) { + badface *bt = (badface *) fastlookup(unsplit_badtets, i); + if ((bt->tt.tet != NULL) && + ( org(bt->tt) == bt->forg ) && + (dest(bt->tt) == bt->fdest) && + (apex(bt->tt) == bt->fapex) && + (oppo(bt->tt) == bt->foppo)) { + if (steinerleft == 0) break; + if (elem_limit > 0) { + if ((tetrahedrons->items - hullsize) > elem_limit) { + break; + } + } + + // Count a live tet. + badtetcount++; + insertvertexflags ivf; + qflag = (int) bt->key; + point *ppt = (point *) &(bt->tt.tet[4]); + for (j = 0; j < 3; j++) { + param[j] = (ppt[0][j]+ppt[1][j]+ppt[2][j]+ppt[3][j]) / 4.0; + } + for (; j < 6; j++) { + param[j] = bt->cent[j]; + } + if (split_tetrahedron(&bt->tt, param, qflag, chkencflag, ivf)) { + splitcount++; + } + if (badtetrahedrons->items > 0) { + // Push new bad quality tetrahedron into queue. + badtetrahedrons->traversalinit(); + triface *bface = (triface *) badtetrahedrons->traverse(); + while (bface != NULL) { + check_tets_list->newindex((void **) &quetet); + *quetet = *bface; + bface = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } + } + } // i + unsplit_badtets->restart(); + + + if (splitcount == 0) break; + } // iter + + if (check_tets_list->objects > 0) { + // Unmark all unchecked tetrahedra. + for (i = 0; i < check_tets_list->objects; i++) { + quetet = (triface *) fastlookup(check_tets_list, i); + if (!isdeadtet(*quetet)) { + unmarktest2(*quetet); + } + } + check_tets_list->restart(); + } + + // End of adding Steiner points in volume ================================= + } // if ((b->cdtrefine & 4) > 0) { + + + delete encseglist; + delete encshlist; + encseglist = NULL; + encshlist = NULL; + + totalworkmemory += (badsubsegs->maxitems * badsubsegs->itembytes); + delete badsubsegs; + badsubsegs = NULL; + totalworkmemory += (badsubfacs->maxitems * badsubfacs->itembytes); + delete badsubfacs; + badsubfacs = NULL; + totalworkmemory += (split_subfaces_pool->maxitems * split_subfaces_pool->itembytes); + delete split_subfaces_pool; + split_subfaces_pool = NULL; + delete split_segments_pool; + split_segments_pool = NULL; + delete unsplit_segments; + delete unsplit_subfaces; + unsplit_segments = unsplit_subfaces = NULL; + + totalworkmemory += (badtetrahedrons->maxitems*badtetrahedrons->itembytes); + delete badtetrahedrons; + badtetrahedrons = NULL; + totalworkmemory += (check_tets_list->totalmemory); + delete check_tets_list; + check_tets_list = NULL; + delete unsplit_badtets; + unsplit_badtets = NULL; +} + +// // +// // +//== refine_cxx ==============================================================// + +//== optimize_cxx ============================================================// +// // +// // + +//============================================================================// +// // +// lawsonflip3d() A three-dimensional Lawson's algorithm. // +// // +//============================================================================// + +long tetgenmesh::lawsonflip3d(flipconstraints *fc) +{ + triface fliptets[5], neightet, hulltet; + face checksh, casingout; + badface *popface, *bface; + point pd, pe, *pts; + REAL sign, ori; + REAL vol, len3; + long flipcount, totalcount = 0l; + long sliver_peels = 0l; + int t1ver; + int i; + + + while (flippool->items != 0l) { + if (b->verbose > 2) { + printf(" Lawson flip %ld faces.\n", flippool->items); + } + flipcount = 0l; + + while (flipstack != (badface *) NULL) { + // Pop a face from the stack. + popface = flipstack; + fliptets[0] = popface->tt; + flipstack = flipstack->nextitem; // The next top item in stack. + flippool->dealloc((void *) popface); + + // Skip it if it is a dead tet (destroyed by previous flips). + if (isdeadtet(fliptets[0])) continue; + // Skip it if it is not the same tet as we saved. + if (!facemarked(fliptets[0])) continue; + + unmarkface(fliptets[0]); + + + if (ishulltet(fliptets[0])) continue; + + fsym(fliptets[0], fliptets[1]); + if (ishulltet(fliptets[1])) { + if (nonconvex) { + // Check if 'fliptets[0]' it is a hull sliver. + tspivot(fliptets[0], checksh); + for (i = 0; i < 3; i++) { + if (!isshsubseg(checksh)) { + spivot(checksh, casingout); + //assert(casingout.sh != NULL); + if (sorg(checksh) != sdest(casingout)) sesymself(casingout); + stpivot(casingout, neightet); + if (neightet.tet == fliptets[0].tet) { + // Found a hull sliver 'neightet'. Let it be [e,d,a,b], where + // [e,d,a] and [d,e,b] are hull faces. + edestoppo(neightet, hulltet); // [a,b,e,d] + fsymself(hulltet); // [b,a,e,#] + if (oppo(hulltet) == dummypoint) { + pe = org(neightet); + if ((pointtype(pe) == FREEFACETVERTEX) || + (pointtype(pe) == FREESEGVERTEX)) { + removevertexbyflips(pe); + } + } else { + eorgoppo(neightet, hulltet); // [b,a,d,e] + fsymself(hulltet); // [a,b,d,#] + if (oppo(hulltet) == dummypoint) { + pd = dest(neightet); + if ((pointtype(pd) == FREEFACETVERTEX) || + (pointtype(pd) == FREESEGVERTEX)) { + removevertexbyflips(pd); + } + } else { + // Perform a 3-to-2 flip to remove the sliver. + // To avoid creating an "inverted" subface in the surface + // Check the normals of the two new subfaces, they must + // not be opposite. + point chk_pe = org(neightet); + point chk_pd = dest(neightet); + point chk_pa = apex(neightet); + point chk_pb = oppo(neightet); + REAL n1[3], n2[3]; + facenormal(chk_pa, chk_pb, chk_pe, n1, 1, NULL); + facenormal(chk_pb, chk_pa, chk_pd, n2, 1, NULL); + double dot = n1[0]*n2[0]+n1[1]*n2[1]+n1[2]*n2[2]; + if (dot > 0.) { + fliptets[0] = neightet; // [e,d,a,b] + fnext(fliptets[0], fliptets[1]); // [e,d,b,c] + fnext(fliptets[1], fliptets[2]); // [e,d,c,a] + flip32(fliptets, 1, fc); + // Update counters. + flip32count--; + flip22count--; + sliver_peels++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + } // if (dot. > 0) + } + } + break; + } // if (neightet.tet == fliptets[0].tet) + } // if (!isshsubseg(checksh)) + senextself(checksh); + } // i + } // if (nonconvex) + continue; + } + + if (checksubfaceflag) { + // Do not flip if it is a subface. + if (issubface(fliptets[0])) continue; + } + + // Test whether the face is locally Delaunay or not. + pts = (point *) fliptets[1].tet; + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], oppo(fliptets[0])); + + if (sign < 0) { + // A non-Delaunay face. Try to flip it. + pd = oppo(fliptets[0]); + pe = oppo(fliptets[1]); + + // Use the length of the edge [d,e] as a reference to determine + // a nearly degenerated new tet. + len3 = distance(pd, pe); + len3 = (len3 * len3 * len3); + int round_flag = 0; // [2017-10-20] + // Check the convexity of its three edges. Stop checking either a + // locally non-convex edge (ori < 0) or a flat edge (ori = 0) is + // encountered, and 'fliptet' represents that edge. + for (i = 0; i < 3; i++) { + ori = orient3d(org(fliptets[0]), dest(fliptets[0]), pd, pe); + if (ori > 0) { + // Avoid creating a nearly degenerated new tet at boundary. + // Re-use fliptets[2], fliptets[3]; + esym(fliptets[0], fliptets[2]); + esym(fliptets[1], fliptets[3]); + if (issubface(fliptets[2]) || issubface(fliptets[3])) { + vol = orient3dfast(org(fliptets[0]), dest(fliptets[0]), pd, pe); + if ((fabs(vol) / len3) < b->epsilon) { + ori = 0.0; // Do rounding. + round_flag = 1; // [2017-10-20] + } + } + } // Rounding check + if (ori <= 0) break; + enextself(fliptets[0]); + eprevself(fliptets[1]); + } + + if (ori > 0) { + // A 2-to-3 flip is found. + // [0] [a,b,c,d], + // [1] [b,a,c,e]. no dummypoint. + flip23(fliptets, 0, fc); + flipcount++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } else { // ori <= 0 + // The edge ('fliptets[0]' = [a',b',c',d]) is non-convex or flat, + // where the edge [a',b'] is one of [a,b], [b,c], and [c,a]. + if (checksubsegflag) { + // Do not flip if it is a segment. + if (issubseg(fliptets[0])) continue; + } + // Count the number of interior subfaces for a valid 2-2 flip. + int scount = 0; + // Check if there are three or four tets sharing at this edge. + esymself(fliptets[0]); // [b,a,d,c] + for (i = 0; i < 3; i++) { + if (issubface(fliptets[i])) scount++; + fnext(fliptets[i], fliptets[i+1]); + } + if (fliptets[3].tet == fliptets[0].tet) { + // A 3-2 flip is found. "scount" must be either 0 or 2. + if (scount == 1) { + // This can happen during the boundary recovery. The adjacent + // subface is either missing or not recovered yet. + continue; + } else if (scount == 2) { + // Valid if a 2-2 flip is possible. + for (i = 0; i < 3; i++) { + if (!issubface(fliptets[i])) break; + } + // Assume fliptets[i] is the tet (b,a,c,e). The two subfaces are + // fliptets[(i+1)%3] (b,a,e,d) and fliptets[(i+2)%3] (b,a,d,c). + // A 2-2 flip is possible if the two faces (d,e,a) and (e,d,b) + // are not subfaces. + triface face1, face2; + neightet = fliptets[(i+1)%3]; // (b,a,e,d) + enext(neightet, face1); + esymself(face1); // (e,a,d) + eprev(neightet, face2); + esymself(face2); // (b,e,d) + if (issubface(face1) || issubface(face2)) { + continue; + } + } + // A 3-to-2 flip is found. (No hull tet.) + flip32(fliptets, 0, fc); + flipcount++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } else { + // There are more than 3 tets at this edge. + fnext(fliptets[3], fliptets[4]); + if (fliptets[4].tet == fliptets[0].tet) { + if (ori != 0.) { + if (nonconvex) { + if (apex(fliptets[3]) == dummypoint) { + // This edge is locally non-convex on the hull. + // It can be removed by a 4-to-4 flip. + ori = 0; + round_flag = 1; + } + } // if (nonconvex) + } + if (ori == 0) { + // A 4-to-4 flip is found. (Two hull tets may be involved.) + // Current tets in 'fliptets': + // [0] [b,a,d,c] (d may be newpt) + // [1] [b,a,c,e] + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + // There are exactly 4 tets at this edge. + // Moreover, a,b,e,d are coplanar. This 4-4 flip will replace + // edge (a,b) to edge (d,e). + + // A valid 2-2 flip is when both faces (a,b,d) and (a,b,e) are + // subfaces, and (a,b,c) and (a,b,f) are not subfaces. + if (issubface(fliptets[0])) { // (a,b,d) + if (!issubface(fliptets[2])) { // (a,b,e) + continue; // not valid 2-2 flip. + } + if (issubface(fliptets[1]) || + issubface(fliptets[3])) { + continue; // The surface mesh is degnerated. + } + } else { + if (issubface(fliptets[1]) || + issubface(fliptets[2]) || + issubface(fliptets[3])) { + continue; // not valid 2-2 flip. + } + } + + if (round_flag == 1) { + //continue; // [2017-10-20] + // We want to flip (nearly coplanar) edges [a,b] to [d,e]. + // Only allow this flip if all new faces are locally Delaunay. + // Otherwise, this routine may not terminate. + point pb = org(fliptets[0]); + point pa = dest(fliptets[0]); + point pc = apex(fliptets[1]); + point pf = apex(fliptets[3]); // pf may be dummypoint + + if (is_collinear_at(pa, pd, pe) || + is_collinear_at(pb, pd, pe)) { + continue; // avoid creating a degenerated (sub)face. + } + + // Validate the four new tets (not inverted) + REAL o1, o2; + o1 = orient3d(pe, pd, pc, pa); + o2 = orient3d(pe, pd, pb, pc); + if ((o1 >= 0.) || (o2 >= 0.)) { + //assert(0); // to debug... + continue; // inverted new tets + } + if (pf != dummypoint) { + REAL o3, o4; + o3 = orient3d(pe, pd, pa, pf); + o4 = orient3d(pe, pd, pf, pb); + if ((o3 >= 0.) || (o4 >= 0.)) { + continue; // inverted new tets + } + } + // Validate locally Delaunay properties of new faces. + REAL test_sign = insphere_s(pe, pd, pc, pa, pb); + if (test_sign < 0) { + // Locally non-Delaunay. Do not perform the 4-4 flip. + continue; + } + if (pf != dummypoint) { + test_sign = insphere_s(pe, pd, pf, pb, pa); + if (test_sign < 0) { + // Locally non-Delaunay. Do not perform the 4-4 flip. + continue; + } + } + } // if (round_flag == 1) + esymself(fliptets[0]); // [a,b,c,d] + // A 2-to-3 flip replaces face [a,b,c] by edge [e,d]. + // This creates a degenerate tet [e,d,a,b] (tmpfliptets[0]). + // It will be removed by the followed 3-to-2 flip. + flip23(fliptets, 0, fc); // No hull tet. + fnext(fliptets[3], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Current tets in 'fliptets': + // [0] [...] + // [1] [b,a,d,e] (degenerated, d may be new point). + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + // A 3-to-2 flip replaces edge [b,a] by face [d,e,f]. + // Hull tets may be involved (f may be dummypoint). + flip32(&(fliptets[1]), (apex(fliptets[3]) == dummypoint), fc); + flipcount++; + flip23count--; + flip32count--; + flip44count++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } // if (ori == 0) + } + } + // This non-Delaunay face is unflippable. Save it. + // unflipqueue->newindex((void **) &bface); + bface = (badface *) flippool->alloc(); + bface->init(); + esymself(fliptets[0]); // *** The original non-Delaunay face **** + bface->tt = fliptets[0]; + bface->forg = org(fliptets[0]); + bface->fdest = dest(fliptets[0]); + bface->fapex = apex(fliptets[0]); + // Add it into the unflip queue. + if (unflip_queue_front == NULL) { + unflip_queue_front = bface; + } else { + unflip_queue_tail->nextitem = bface; + } + unflip_queue_tail = bface; + } // if (ori <= 0) + } // if (sign < 0) + } // while (flipstack) + + if (b->verbose > 2) { + if (flipcount > 0) { + printf(" Performed %ld flips.\n", flipcount); + } + if (flippool->items > 0) { + printf(" Saved %ld unflippbale faces.\n", flippool->items); + } + } + // Accumulate the counter of flips. + totalcount += flipcount; + + // Return if no unflippable faces left. + //if (unflipqueue->objects == 0l) break; + if (flippool->items == 0l) break; + // Return if no flip has been performed. + if (flipcount == 0l) break; + + // Try to flip the unflippable faces. + while (unflip_queue_front != NULL) { + bface = unflip_queue_front; + if (!isdeadtet(bface->tt) && + (org(bface->tt) == bface->forg) && + (dest(bface->tt) == bface->fdest) && + (apex(bface->tt) == bface->fapex)) { + flippush(flipstack, &(bface->tt)); + } + unflip_queue_front = bface->nextitem; + flippool->dealloc((void *) bface); + } + unflip_queue_tail = NULL; + + } // while (flippool->items != 0l) + + if (flippool->items > 0l) { + // Save the unflippable faces to flip them later. + badface *bf; + while (unflip_queue_front != NULL) { + bface = unflip_queue_front; + if (!isdeadtet(bface->tt) && + (org(bface->tt) == bface->forg) && + (dest(bface->tt) == bface->fdest) && + (apex(bface->tt) == bface->fapex)) { + //flippush(flipstack, &(bface->tt)); + later_unflip_queue->newindex((void **) &bf); + *bf = *bface; + } + unflip_queue_front = bface->nextitem; + //flippool->dealloc((void *) bface); + } + //unflip_queue_tail = NULL; + flippool->restart(); // Clear the pool. + } + + if (b->verbose > 2) { + if (totalcount > 0) { + printf(" Performed %ld flips.\n", totalcount); + } + if (sliver_peels > 0) { + printf(" Removed %ld hull slivers.\n", sliver_peels); + } + //if (unflipqueue->objects > 0l) { + // printf(" %ld unflippable edges remained.\n", unflipqueue->objects); + //} + } + + return totalcount + sliver_peels; +} + +//============================================================================// +// // +// recoverdelaunay() Recovery the locally Delaunay property. // +// // +//============================================================================// + +void tetgenmesh::recoverdelaunay() +{ + badface *bface, *parybface; + flipconstraints fc; + int i, j; + + if (b->verbose > 2) { + printf(" Recovering Delaunayness...\n"); + } + tetprism_vol_sum = 0.0; // Initialize it. + + if (later_unflip_queue->objects > 0) { + // Flip the saved unflippable faces. + for (i = 0; i < later_unflip_queue->objects; i++) { + bface = (badface *) fastlookup(later_unflip_queue, i); + if (!isdeadtet(bface->tt) && + (org(bface->tt) == bface->forg) && + (dest(bface->tt) == bface->fdest) && + (apex(bface->tt) == bface->fapex)) { + flippush(flipstack, &(bface->tt)); + } + } + later_unflip_queue->restart(); // clean it. + if (flippool->items == 0l) { + return; + } + } else { + if (flippool->items == 0l) { + // Flip all locally non-Delaunay faces of the tetrahedralisation. + triface tetloop, neightet; //, *parytet; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != NULL) { + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + decode(tetloop.tet[tetloop.ver], neightet); + if (!facemarked(neightet)) { + flippush(flipstack, &tetloop); + } + } + point *ppt = (point *) &(tetloop.tet[4]); + tetprism_vol_sum += tetprismvol(ppt[0], ppt[1], ppt[2], ppt[3]); + tetloop.tet = tetrahedrontraverse(); + } + } + } + + recover_delaunay_count++; + + // Calulate a relatively lower bound for small improvement. + // Used to avoid rounding error in volume calculation. + fc.bak_tetprism_vol = tetprism_vol_sum * b->epsilon * 1e-3; + + if (b->verbose > 2) { + printf(" Initial obj = %.17g\n", tetprism_vol_sum); + } + + if (b->verbose > 2) { + printf(" Recover Delaunay [Lawson] : %ld\n", flippool->items); + } + + // First only use the basic Lawson's flip. + fc.remove_ndelaunay_edge = 1; + fc.enqflag = 2; + + lawsonflip3d(&fc); + + if (b->verbose > 2) { + printf(" obj (after Lawson) = %.17g\n", tetprism_vol_sum); + } + + if (later_unflip_queue->objects == 0l) { + return; + } + + fc.unflip = 0; // fc.unflip = 1; // Unflip if the edge is not flipped. + fc.collectnewtets = 1; // new tets are returned in 'cavetetlist'. + fc.enqflag = 0; + + int bak_autofliplinklevel = autofliplinklevel; + int bak_fliplinklevel = b->fliplinklevel; + autofliplinklevel = 1; // Init level. + b->fliplinklevel = -1; // No fixed level. + + badface *bfarray = new badface[later_unflip_queue->objects]; + + while ((later_unflip_queue->objects > 0) && + (autofliplinklevel < 4)) { // level = 1,2,3 //< 10 + + int nbf = later_unflip_queue->objects; + for (i = 0; i < nbf; i++) { + bfarray[i] = * (badface *) fastlookup(later_unflip_queue, i); + } + later_unflip_queue->restart(); // clean it. + + if (b->verbose > 2) { + printf(" Recover Delaunay [level = %2d] #: %d.\n", + autofliplinklevel, nbf); + } + + for (i = 0; i < nbf; i++) { + bface = &(bfarray[i]); + if (getedge(bface->forg, bface->fdest, &bface->tt)) { + if (removeedgebyflips(&(bface->tt), &fc) == 2) { + tetprism_vol_sum += fc.tetprism_vol_sum; + } else { + // This edge is not removed. Save it in later_flip_queue. + later_unflip_queue->newindex((void **) &parybface); + *parybface = bfarray[i]; // *bface; + } + fc.tetprism_vol_sum = 0.0; // Clear it. + if (cavetetlist->objects > 0) { + // Queue new faces for flips. + triface neightet, *parytet; + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + // A queued new tet may be dead. + if (!isdeadtet(*parytet)) { + for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { + // Avoid queue a face twice. + decode(parytet->tet[parytet->ver], neightet); + if (!facemarked(neightet)) { + flippush(flipstack, parytet); + } + } // parytet->ver + } + } // j + cavetetlist->restart(); + } // if (cavetetlist->objects > 0) + } + } // i + + autofliplinklevel++; // =b->fliplinklevelinc; + } // while (later_unflip_queue->objects > 0) + + delete [] bfarray; + + if (b->verbose > 2) { + if (later_unflip_queue->objects > 0l) { + printf(" %ld non-Delaunay edges remained.\n", later_unflip_queue->objects); + } + } + + if (flippool->items > 0l) { + // Flip locally non-Delaunay faces. Unflippable faces are queued + // in later_flip_queue. + fc.remove_ndelaunay_edge = 1; + fc.enqflag = 2; // queue exteior faces of a flip. + lawsonflip3d(&fc); + //fc.enqflag = 0; // for removedgebyflips(). + } + + if (b->verbose > 2) { + printf(" Final obj = %.17g\n", tetprism_vol_sum); + } + + if (later_unflip_queue->objects > 0l) { + if (b->verbose > 2) { + printf(" %ld non-Delaunay edges remained.\n", later_unflip_queue->objects); + } + later_unflip_queue->restart(); + } + + autofliplinklevel = bak_autofliplinklevel; // Restore this value. + b->fliplinklevel = bak_fliplinklevel; +} + +//============================================================================// +// // +// get_seg_laplacian_center() Get the Laplcian center of a mesh vertex. // +// // +// "mesh_vert" must be a Steiner vertex (FREESEGVERTEX) in a segment. // +// // +//============================================================================// + +int tetgenmesh::get_seg_laplacian_center(point mesh_vert, REAL target[3]) +{ + if (pointtype(mesh_vert) == UNUSEDVERTEX) { + return 0; + } + + face leftseg, rightseg; + + sdecode(point2sh(mesh_vert), leftseg); + leftseg.shver = 0; + if (sdest(leftseg) == mesh_vert) { + senext(leftseg, rightseg); + spivotself(rightseg); + rightseg.shver = 0; + if (sorg(rightseg) != mesh_vert) { + sesymself(rightseg); + } + if (sorg(rightseg) != mesh_vert) { + terminatetetgen(this, 2); + } + } else { + rightseg = leftseg; + senext2(rightseg, leftseg); + spivotself(leftseg); + leftseg.shver = 0; + if (sdest(leftseg) != mesh_vert) { + sesymself(leftseg); + } + if (sdest(leftseg) != mesh_vert) { + terminatetetgen(this, 2); + } + } + point lpt = sorg(leftseg); + point rpt = sdest(rightseg); + + int j; + + for (j = 0; j < 3; j++) { + target[j] = 0.5 * (lpt[j] + rpt[j]); + } + + return 1; +} + +//============================================================================// +// // +// get_surf_laplacian_center() Get the Laplcian center of a mesh vertex. // +// // +// "mesh_vert" must be a Steiner vertex (FREEFACETVERTEX) in a facet. // +// // +//============================================================================// + +int tetgenmesh::get_surf_laplacian_center(point mesh_vert, REAL target[3]) +{ + if (pointtype(mesh_vert) == UNUSEDVERTEX) { + return 0; + } + + getvertexstar(1, mesh_vert, caveoldtetlist, NULL, caveshlist); + + // The number of vertices is the same as the number of edges. + int npt = (int) caveshlist->objects; + int i, j; + + for (j = 0; j < 3; j++) { + target[j] = 0.; + } + + for (i = 0; i < npt; i++) { + face *cavesh = (face *) fastlookup(caveshlist, i); + point e1 = sorg(*cavesh); + point e2 = sdest(*cavesh); + for (j = 0; j < 3; j++) { + target[j] += e1[j]; + } + for (j = 0; j < 3; j++) { + target[j] += e2[j]; + } + } + + // We added every link vertex twice. + int npt2 = npt * 2; + + for (j = 0; j < 3; j++) { + target[j] /= (double) npt2; + } + + caveoldtetlist->restart(); + caveshlist->restart(); + return 1; +} + +//============================================================================// +// // +// get_laplacian_center() Get the Laplcian center of a mesh vertex. // +// // +// "mesh_vert" must be a Steiner vertex (FREEVOLVERTEX) in volume. // +// // +//============================================================================// + +int tetgenmesh::get_laplacian_center(point mesh_vert, REAL target[3]) +{ + if (pointtype(mesh_vert) == UNUSEDVERTEX) { + return 0; + } + getvertexstar(1, mesh_vert, caveoldtetlist, cavetetvertlist, NULL); + + // Calculate the laplacian center. + int npt = (int) cavetetvertlist->objects; + int i, j; + + for (j = 0; j < 3; j++) { + target[j] = 0.; + } + + for (i = 0; i < npt; i++) { + point *pt = (point *) fastlookup(cavetetvertlist, i); + for (j = 0; j < 3; j++) { + target[j] += (*pt)[j]; + } + } + + for (j = 0; j < 3; j++) { + target[j] /= (double) npt; + } + + cavetetvertlist->restart(); + return 1; +} + +//============================================================================// +// // +// move_vertex() Try to move a given vertex towards the target position. // +// // +//============================================================================// + +bool tetgenmesh::move_vertex(point mesh_vert, REAL target[3]) +{ + if (pointtype(mesh_vert) == UNUSEDVERTEX) { + if (caveoldtetlist->objects > 0l) { + caveoldtetlist->restart(); + } + return 0; + } + // Do not move if the target is already very close the vertex. + if (distance(mesh_vert, target) < minedgelength) { + if (caveoldtetlist->objects > 0l) { + caveoldtetlist->restart(); + } + return 0; + } + triface* cavetet; + point pa, pb, pc; + REAL ori; + int i, j; + + REAL dir[3], newpos[3]; + REAL alpha = b->smooth_alpha; // 0.3; + + for (j = 0; j < 3; j++) { + dir[j] = target[j] - mesh_vert[j]; + newpos[j] = mesh_vert[j] + alpha * dir[j]; + } + + if (caveoldtetlist->objects == 0l) { + getvertexstar(1, mesh_vert, caveoldtetlist, NULL, NULL); + } + + bool moveflag = true; + int iter = 0; + + while (iter < 3) { + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if (ishulltet(*cavetet)) continue; // Skip a hull face. + + pa = org(*cavetet); + pb = dest(*cavetet); + pc = apex(*cavetet); + ori = orient3d(pa, pb, pc, newpos); + if (ori >= 0) { + moveflag = false; + break; // This tet becomes invalid. + } + } + if (moveflag) { + break; + } else { + alpha = (alpha / 2.); + for (j = 0; j < 3; j++) { + newpos[j] = mesh_vert[j] + alpha * dir[j]; + } + iter++; + } + } // while (iter < 3) + + if (moveflag) { + for (j = 0; j < 3; j++) { + mesh_vert[j] = newpos[j]; + } + + triface checkface, neightet; + //int j; + + // Push all faces of this vertex star and link into queue. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if (ishulltet(*cavetet)) continue; // Skip a hull face. + flippush(flipstack, cavetet); + for (j = 0; j < 3; j++) { + esym(*cavetet, checkface); + fsym(checkface, neightet); + if (!facemarked(neightet)) { + flippush(flipstack, &checkface); + } + enextself(*cavetet); + } + } + + if (badtetrahedrons != NULL) { + // queue all cavity tets for quality check. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + if (ishulltet(*cavetet)) continue; // Skip a hull face. + enqueuetetrahedron(cavetet); + } + } + + flipconstraints fc; + fc.enqflag = 2; // queue all exterior faces of a flip. + if (badtetrahedrons != NULL) { + fc.chkencflag = 4; // queue new tets for quality check. + } + lawsonflip3d(&fc); + + + } // if (moveflag) + + caveoldtetlist->restart(); + return moveflag; +} + +//============================================================================// +// // +// smooth_vertices() Smooth vertices. // +// // +//============================================================================// + +void tetgenmesh::smooth_vertices() +{ + if (!b->quiet) { + printf("Smoothing vertices...\n"); + } + + if (b->verbose) { + printf(" Smooth criterion = %d\n", b->smooth_cirterion); + printf(" Smooth iterations = %d\n", b->smooth_maxiter); + printf(" Smooth relax-alpha = %g\n", b->smooth_alpha); + } + + point *smpt_list = NULL; + point *surf_smpt_list = NULL; + point *seg_smpt_list = NULL; + int volcount = 0, faccount = 0, segcount = 0; + + // Only use it when we have Steiner points. + if (st_segref_count > 0) { + seg_smpt_list = new point[st_segref_count]; + } + if (st_volref_count > 0) { + smpt_list = new point[st_volref_count]; + } + if (st_facref_count > 0) { + surf_smpt_list = new point[st_facref_count]; + } + + points->traversalinit(); + point ptloop = pointtraverse(); + while (ptloop != NULL) { + enum verttype vt = pointtype(ptloop); + if (vt == FREEVOLVERTEX) { + smpt_list[volcount++] = ptloop; + } else if (vt == FREEFACETVERTEX) { + surf_smpt_list[faccount++] = ptloop; + } else if (vt == FREESEGVERTEX) { + seg_smpt_list[segcount++] = ptloop; + } + ptloop = pointtraverse(); + } + + if ((volcount != st_volref_count) || + (faccount != st_facref_count) || + (segcount != st_segref_count)) { + terminatetetgen(this, 2); + } + + if (b->verbose > 1) { + printf(" Smoothing (%ld, %ld) %ld vertices.\n", + st_segref_count, st_facref_count, st_volref_count); + } + + // Allocate a list of target points. + REAL *target_list = NULL; + REAL *surf_target_list = NULL; + REAL *seg_target_list = NULL; + + if (st_volref_count > 0) { + target_list = new REAL[st_volref_count * 3]; + } + if (st_facref_count > 0) { + surf_target_list = new REAL[st_facref_count * 3]; + } + if (st_segref_count > 0) { + seg_target_list = new REAL[st_segref_count * 3]; + } + + long bak_flipcount = flip23count + flip32count + flip44count; + int movedcount = 0, total_movecount = 0; + int unmovedcount = 0, total_unmovedcount = 0; + int iter = 0, maxiter = b->smooth_maxiter; + int i; + + for (iter = 0; iter < maxiter; iter++) { + + movedcount = unmovedcount = 0; + + if (((b->smooth_cirterion & 4) > 0)) { // -s4, -s5, -s6, -s7, default -s3 + //if (st_segref_count > 0) { + for (i = 0; i < st_segref_count; i++) { + get_seg_laplacian_center(seg_smpt_list[i], &(seg_target_list[i*3])); + } + for (i = 0; i < st_segref_count; i++) { + if (move_vertex(seg_smpt_list[i], &(seg_target_list[i*3]))) { + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + movedcount++; + } else { + unmovedcount++; + } + } + //} // if (st_segref_count > 0) + } + + if (((b->smooth_cirterion & 2) > 0)) { // default -s3 + //if (st_facref_count > 0) { + for (i = 0; i < st_facref_count; i++) { + get_surf_laplacian_center(surf_smpt_list[i], &(surf_target_list[i*3])); + } + for (i = 0; i < st_facref_count; i++) { + if (move_vertex(surf_smpt_list[i], &(surf_target_list[i*3]))) { + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + movedcount++; + } else { + unmovedcount++; + } + } + //} // if (st_facref_count > 0) + } + + if (((b->smooth_cirterion & 1) > 0)) { // default -s3 + //if (st_volref_count > 0) { + for (i = 0; i < st_volref_count; i++) { + get_laplacian_center(smpt_list[i], &(target_list[i*3])); + caveoldtetlist->restart(); + } + for (i = 0; i < st_volref_count; i++) { + if (move_vertex(smpt_list[i], &(target_list[i*3]))) { + if (later_unflip_queue->objects > b->unflip_queue_limit) { + recoverdelaunay(); + } + movedcount++; + } else { + unmovedcount++; + } + } + //} // if (st_volref_count > 0) + } + + if (movedcount == 0) break; + + if (b->verbose > 1) { + printf(" iter=%d, smoothed %d vertices, %d unsmoothed\n", iter, + movedcount, unmovedcount); + } + + + total_movecount += movedcount; + total_unmovedcount += unmovedcount; + + if (later_unflip_queue->objects > 0) { + recoverdelaunay(); + } + } // iter + + if (b->verbose > 1) { + printf(" Smoothed %d (%d) times, flipped %ld faces.\n", + total_movecount, total_unmovedcount, + flip23count + flip32count + flip44count - bak_flipcount); + } + + if (st_segref_count > 0) { + delete [] seg_smpt_list; + delete [] seg_target_list; + } + if (st_facref_count > 0) { + delete [] surf_target_list; + delete [] surf_smpt_list; + } + if (st_volref_count > 0) { + delete [] target_list; + delete [] smpt_list; + } +} + +//============================================================================// +// // +// get_tet() Get the tetrahedron with the given vertices. // +// // +//============================================================================// + +bool tetgenmesh::get_tet(point pa, point pb, point pc, point pd, triface *searchtet) +{ + if (getedge(pa, pb, searchtet)) { + int t1ver; + triface spintet = *searchtet; + while (1) { + if (apex(spintet) == pc) { + *searchtet = spintet; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } + if (apex(*searchtet) == pc) { + if (oppo(*searchtet) == pd) { + return true; + } else { + fsymself(*searchtet); + if (oppo(*searchtet) == pd) { + return true; + } + } + } + } + + return false; +} + +//============================================================================// +// // +// get_tetqual() Calculate various quality measures of a given tetrahedron.// +// // +// Calculate the aspect ratio (Lmax / hmin), edge ratio (Lmax/Lmin), maximal // +// and minimal dihedral angles of this tetrahedron. // +// // +// These values are returned by: // +// bf->key, aspect ratio // +// bf->cent[0], cosine of maximal dihedral angle // +// bf->cent[1], cosine of minimal dihedral angle // +// bf->cent[2], edge ratio // +// bf->cent[3], minimal edge length // +// bf->cent[4], volume (used to validate whether it is modified or not). // +// bf->cent[5], (no use). // +// bf->tet, the edge with maximal dihedral angle. // +// bf->ss.shver, (re-used) count the number of dihedrals > 165 degree. // +// // +//============================================================================// + +bool tetgenmesh::get_tetqual(triface *chktet, point oppo_pt, badface *bf) +{ + if (chktet != NULL) { + bf->init(); + if (oppo_pt == NULL) { + point *ppt = (point *) &(chktet->tet[4]); + bf->forg = ppt[0]; // pa + bf->fdest = ppt[1]; // pb + bf->fapex = ppt[2]; // pc + bf->foppo = ppt[3]; // pd + } else { + bf->forg = org(*chktet); + bf->fdest = dest(*chktet); + bf->fapex = apex(*chktet); + bf->foppo = oppo_pt; + } + } + + REAL A[4][4], rhs[4], D; + int indx[4]; + int i, j; + + // get the entries of A[3][3]. + for (i = 0; i < 3; i++) A[0][i] = bf->forg[i] - bf->foppo[i]; // d->a vec + for (i = 0; i < 3; i++) A[1][i] = bf->fdest[i] - bf->foppo[i]; // d->b vec + for (i = 0; i < 3; i++) A[2][i] = bf->fapex[i] - bf->foppo[i]; // d->c vec + + // Get the max-min edge length + REAL L[6], Lmax, Lmin; + REAL Vab[3], Vbc[3], Vca[3]; + + for (i = 0; i < 3; i++) Vab[i] = bf->fdest[i] - bf->forg[i]; // a->b vec + for (i = 0; i < 3; i++) Vbc[i] = bf->fapex[i] - bf->fdest[i]; // b->c vec + for (i = 0; i < 3; i++) Vca[i] = bf->forg[i] - bf->fapex[i]; // c->a vec + + // Use the idx2edge + L[0] = dot(A[2], A[2]); // edge c,d + L[1] = dot(A[0], A[0]); // edge a,d + L[2] = dot(Vab, Vab); // edge a,b + L[3] = dot(Vbc, Vbc); // edge b,c + L[4] = dot(A[1], A[1]); // edge b,d + L[5] = dot(Vca, Vca); // edge a,c + + Lmax = Lmin = L[0]; + //int idx = 0; + for (i = 1; i < 6; i++) { + Lmax = (Lmax < L[i] ? L[i] : Lmax); + Lmin = (Lmin > L[i] ? L[i] : Lmin); + //if (Lmin > L[i]) { + // Lmin = L[i]; idx = i; + //} + } + + Lmax = sqrt(Lmax); + Lmin = sqrt(Lmin); + + // Caluclate the Lmax / Lmin edge ratio (to detect very short edge). + bf->cent[2] = Lmax / Lmin; + bf->cent[3] = Lmin; + + // Calculate the normals and heights. + REAL N[4][3]; // The normals of the four faces. + REAL H[4]; // H[i] is the inverse of the height of its corresponding face. + bool flat_flag = false; + + if (lu_decmp(A, 3, indx, &D, 0)) { + // Get the volume of this tet. + bf->cent[4] = fabs((A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2])); + if (bf->cent[4] > 0.0) { + // Compute the inverse of matrix A, to get 3 normals of the 4 faces. + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) rhs[i] = 0.0; + rhs[j] = 1.0; // Positive means the inside direction + lu_solve(A, 3, indx, rhs, 0); + for (i = 0; i < 3; i++) N[j][i] = rhs[i]; + } + // Get the fourth normal by summing up the first three. + for (i = 0; i < 3; i++) N[3][i] = - N[0][i] - N[1][i] - N[2][i]; + } else { + // This is a very flat tet. + flat_flag = true; + } + } else { + flat_flag = true; + } + + if (flat_flag) { + // This tet is nearly degenerate. + bf->cent[4] = orient3d(bf->fdest, bf->forg, bf->fapex, bf->foppo); + if (bf->cent[4] <= 0.0) { + return false; // degenerated or inverted. + } + // Calculate the normals of the four faces. + facenormal(bf->fapex, bf->fdest, bf->foppo, N[0], 1, NULL); // face [c,b,d] + facenormal(bf->forg, bf->fapex, bf->foppo, N[1], 1, NULL); // face [a,c,d] + facenormal(bf->fdest, bf->forg, bf->foppo, N[2], 1, NULL); // face [b,a,d] + facenormal(bf->forg, bf->fdest, bf->fapex, N[3], 1, NULL); // face [a,b,c] + } // if (!success) + + // Normalized the normals. + for (i = 0; i < 4; i++) { + H[i] = sqrt(dot(N[i], N[i])); + if (H[i] > 0.0) { + for (j = 0; j < 3; j++) N[i][j] /= H[i]; + } else { + return false; // H[i] == 0.0; + } + } + + if (!flat_flag) { + // Get the biggest H[i] (corresponding to the smallest height). + REAL minheightinv = H[0]; + for (i = 1; i < 4; i++) { + if (H[i] > minheightinv) minheightinv = H[i]; + } + // Calulcate the aspect ratio = L_max / h_min. + bf->key = Lmax * minheightinv; + } else { + // A very flat tet. + //if (bf->key <= 0.0) { + bf->key = 1.e+30; // infinity. + //} + } + + // Calculate the cosine of the dihedral angles of the edges. + REAL cosmaxd = 1.0, cosmind = -1.0, cosd; + int f1, f2, idx = 0; + bf->ss.shver = 0; // // Count the number of large dihedrals. + for (i = 0; i < 6; i++) { + switch (i) { + case 0: f1 = 0; f2 = 1; break; // [c,d]. + case 1: f1 = 1; f2 = 2; break; // [a,d]. + case 2: f1 = 2; f2 = 3; break; // [a,b]. + case 3: f1 = 0; f2 = 3; break; // [b,c]. + case 4: f1 = 2; f2 = 0; break; // [b,d]. + case 5: f1 = 1; f2 = 3; break; // [a,c]. + } + cosd = -dot(N[f1], N[f2]); + if (cosd < -1.0) cosd = -1.0; // Rounding. + if (cosd > 1.0) cosd = 1.0; // Rounding. + // cosmaxd = cosd < cosmaxd ? cosd : cosmaxd; + if (cosd < cosmaxd) {cosmaxd = cosd; idx = i;} + cosmind = (cosd > cosmind ? cosd : cosmind); + // Count the number of large dihedrals. + if (cosd < cos_large_dihed) bf->ss.shver++; + } // i + + bf->cent[0] = cosmaxd; + bf->cent[1] = cosmind; + + // Remember the edge with largest dihedral angle. + if (chktet) bf->tt.tet = chktet->tet; + bf->tt.ver = edge2ver[idx]; + + bf->cent[5] = 0.0; + + return true; +} + +bool tetgenmesh::get_tetqual(point pa, point pb, point pc, point pd, badface *bf) +{ + bf->init(); + + bf->forg = pa; + bf->fdest = pb; + bf->fapex = pc; + bf->foppo = pd; + + return get_tetqual(NULL, NULL, bf); +} + +//============================================================================// +// // +// enqueue_badtet() Push a bad-quality tet into the proority queue. // +// // +//============================================================================// + +void tetgenmesh:: enqueue_badtet(badface *bf) +{ + badface *bt = (badface *) badqual_tets_pool->alloc(); + + *bt = *bf; + + // The following vertices are used by get_tet(...) to identify whether + // the saved tet is still alive or not. + //bt->forg = org(bf->tt); + //bt->fdest = dest(bf->tt); + //bt->fapex = apex(bf->tt); + //bt->foppo = oppo(bf->tt); + + bt->nextitem = NULL; // important, this pointer is used to recongise the last + // item in each queue. + + // Push it into the priority queue. + REAL qual = 1.0 / log(bf->key); + + // Determine the appropriate queue to put the bad subface into. + int queuenumber = 0; + if (qual < 1.0) { + queuenumber = (int) (64.0 * (1.0 - qual)); + if (queuenumber > 63) { + queuenumber = 63; + } + } else { + // It's not a bad shape; put the subface in the lowest-priority queue. + queuenumber = 0; + } + + // Are we inserting into an empty queue? + if (bt_queuefront[queuenumber] == (badface *) NULL) { + // Yes, we are inserting into an empty queue. + // Will this become the highest-priority queue? + if (queuenumber > bt_firstnonemptyq) { + // Yes, this is the highest-priority queue. + bt_nextnonemptyq[queuenumber] = bt_firstnonemptyq; + bt_firstnonemptyq = queuenumber; + } else { + // No, this is not the highest-priority queue. + // Find the queue with next higher priority. + int i = queuenumber + 1; + while (bt_queuefront[i] == (badface *) NULL) { + i++; + } + // Mark the newly nonempty queue as following a higher-priority queue. + bt_nextnonemptyq[queuenumber] = bt_nextnonemptyq[i]; + bt_nextnonemptyq[i] = queuenumber; + } + // Put the bad subface at the beginning of the (empty) queue. + bt_queuefront[queuenumber] = bt; + } else { + // Add the bad tetrahedron to the end of an already nonempty queue. + bt_queuetail[queuenumber]->nextitem = bt; + } + // Maintain a pointer to the last subface of the queue. + bt_queuetail[queuenumber] = bt; +} + +//============================================================================// +// // +// top_badtet() Get a bad-quality tet from the priority queue. // +// // +//============================================================================// + +tetgenmesh::badface* tetgenmesh::top_badtet() +{ + // Keep a record of which queue was accessed in case dequeuebadtetra() + // is called later. + bt_recentq = bt_firstnonemptyq; + // If no queues are nonempty, return NULL. + if (bt_firstnonemptyq < 0) { + return (badface *) NULL; + } else { + // Return the first tetrahedron of the highest-priority queue. + return bt_queuefront[bt_firstnonemptyq]; + } +} + +//============================================================================// +// // +// dequeue_badtet() Popup a bad-quality tet from the priority queue. // +// // +//============================================================================// + +void tetgenmesh::dequeue_badtet() +{ + badface *bt; + int i; + + // If queues were empty last time topbadtetra() was called, do nothing. + if (bt_recentq >= 0) { + // Find the tetrahedron last returned by topbadtetra(). + bt = bt_queuefront[bt_recentq]; + // Remove the tetrahedron from the queue. + bt_queuefront[bt_recentq] = bt->nextitem; + // If this queue is now empty, update the list of nonempty queues. + if (bt == bt_queuetail[bt_recentq]) { + // Was this the highest-priority queue? + if (bt_firstnonemptyq == bt_recentq) { + // Yes; find the queue with next lower priority. + bt_firstnonemptyq = bt_nextnonemptyq[bt_firstnonemptyq]; + } else { + // No; find the queue with next higher priority. + i = bt_recentq + 1; + while (bt_queuefront[i] == (badface *) NULL) { + i++; + } + bt_nextnonemptyq[i] = bt_nextnonemptyq[bt_recentq]; + } + } + // Return the badface to the pool. + badqual_tets_pool->dealloc((void *) bt); + } +} + + +//============================================================================// +// // +// add_steinerpt_to_repair() Add Steiner to repair a bad-qaulity tet. // +// // +//============================================================================// + +bool tetgenmesh::add_steinerpt_to_repair(badface *bf, bool bSmooth) +{ + REAL cosmaxd = bf->cent[0]; + REAL eta = bf->cent[2]; + int lcount = bf->ss.shver; // the number of large dihedrals. + + triface splittet; + splittet.tet = NULL; + + if (cosmaxd < cosslidihed) { // cossmtdihed + // It is a sliver (flat) (might contain a short edge -- skinny). + triface sliver_edge; + char shape = 0; + + // Determine the outer shape of this sliver, i.e., a square of a triangle? + if (lcount == 2) { + // It is a square. Try to remove the edge [a,b] + shape = 'S'; + sliver_edge = bf->tt; + } else if (lcount == 3) { + // It is a triangle. Try to remove the edge [c,d] + shape = 'T'; + edestoppo(bf->tt, sliver_edge); // face [c,d,a] + } + + // Determine a Steiner point according to the shape of this sliver. + if (shape == 'S') { + REAL vol, max_vol = 0.0; + + triface check_sliver = sliver_edge; + for (int i = 0; i < 2; i++) { + bool is_bdry = false; + if (issubseg(check_sliver)) { + is_bdry = true; + } else { + triface spintet = check_sliver; + int t1ver; + do { + if (issubface(spintet)) { + is_bdry = true; break; + } + fnextself(spintet); + } while (spintet.tet != check_sliver.tet); + } + + if (!is_bdry) { + triface spintet = check_sliver; + int t1ver; + do { + point *ppt = (point *) &(spintet.tet[4]); + vol = orient3d(ppt[1], ppt[0], ppt[2], ppt[3]); + if (vol > max_vol) { + max_vol = vol; + splittet = spintet; + } + fnextself(spintet); + } while (spintet.tet != check_sliver.tet); + } + + // Check the opposite edge. + edestoppoself(check_sliver); + } // i + } else if (shape == 'T') { + } + } else if (eta > b->opt_max_edge_ratio) { + // It is a skinny tet. + // This tet contains a relatively short edge. Check if it can be collapsed. + REAL Lmin = bf->cent[3]; + + // Get the shortest edge of this tet. + triface short_edge = bf->tt; + int i; + for (i = 0; i < 6; i++) { + short_edge.ver = edge2ver[i]; + REAL dd = distance(org(short_edge), dest(short_edge)); + if ((fabs(Lmin - dd) / Lmin) < 1e-4) break; + } + if (i == 6) { + terminatetetgen(this, 2); + } + + if (Lmin <= minedgelength) { + // A very short edge. Check if it was correctly created. + point e1 = org(short_edge); + point e2 = dest(short_edge); + if (issteinerpoint(e1)) { + if (!create_a_shorter_edge(e1, e2)) { + terminatetetgen(this, 2); + } + } else if (issteinerpoint(e2)) { + if (!create_a_shorter_edge(e2, e1)) { + terminatetetgen(this, 2); + } + } + } + } + + if (splittet.tet == NULL) { + return false; // not added. + } + + // Do not add if the splittet is also a bad qual tet. + badface tmpbf; + if (get_tetqual(&splittet, NULL, &tmpbf)) { + if (tmpbf.cent[0] < cosslidihed) { + return false; + } + } else { + return false; + } + + point steinerpt; + makepoint(&steinerpt, FREEVOLVERTEX); + point *ppt = (point *) &(splittet.tet[4]); + for (int j = 0; j < 3; j++) { + steinerpt[j] = (ppt[0][j]+ppt[1][j]+ppt[2][j]+ppt[3][j]) / 4.0; + } + + insertvertexflags ivf; + + //triface searchtet = splittet; + ivf.iloc = (int) OUTSIDE; + ivf.bowywat = 3; + ivf.lawson = 2; + ivf.rejflag = 0; + if (badtetrahedrons != NULL) { + ivf.chkencflag = 4; // queue new tets. + } + ivf.sloc = ivf.sbowywat = 0; // No use. + ivf.splitbdflag = 0; // No use (its an interior vertex). + ivf.validflag = 1; + ivf.respectbdflag = 1; + + ivf.smlenflag = 1; // avoid creating very short edges + ivf.parentpt = NULL; // init. + + if (insertpoint(steinerpt, &splittet, NULL, NULL, &ivf)) { + st_volref_count++; + //if (steinerleft > 0) steinerleft--; + + if (flipstack != NULL) { + flipconstraints fc; + fc.enqflag = 2; + if (badtetrahedrons != NULL) { + fc.chkencflag = 4; + } + lawsonflip3d(&fc); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + //recoverdelaunay(); + later_unflip_queue->restart(); // clean it. + } + } else { + // Point is not inserted. + pointdealloc(steinerpt); + return false; + } + + if (bSmooth) { + REAL ccent[3]; + get_laplacian_center(steinerpt, ccent); + if (move_vertex(steinerpt, ccent)) { + opt_smooth_count++; + } + } // if (bSmooth) + + if (badtetrahedrons->items > 0) { + // Push new bad quality tetrahedron into queue. + badface bf; + REAL max_asp = 0., cosmaxd = 1.; + badtetrahedrons->traversalinit(); + triface *bface = (triface *) badtetrahedrons->traverse(); + while (bface != NULL) { + if (!isdeadtet(*bface)) { + // A queued tet may have been processed. + if (marktest2ed(*bface)) { + unmarktest2(*bface); + if (!ishulltet(*bface)) { + get_tetqual(bface, NULL, &bf); + // Save the worst quality. + max_asp = (max_asp > bf.key ? max_asp : bf.key); + cosmaxd = (cosmaxd < bf.cent[0] ? cosmaxd : bf.cent[0]); + if ((bf.key > b->opt_max_asp_ratio) || (bf.cent[0] < cosmaxdihed)) { + bf.forg = org(bf.tt); + bf.fdest = dest(bf.tt); + bf.fapex = apex(bf.tt); + bf.foppo = oppo(bf.tt); + enqueue_badtet(&bf); + } + } // if (!ishulltet(*bface)) + } + } + bface = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } + + // Check if the bad quality tet is removed or not. + if (get_tet(bf->forg, bf->fdest, bf->fapex, bf->foppo, &(bf->tt))) { + // Try to remove it. + if (repair_tet(bf, true, false, false)) { + return true; + } + } else { + // This tet is removed. + return true; + } + + return false; // not added. +} + + + + + + +//============================================================================// +// // +// flip_edge_to_improve() Flip an edge of a bad-quality tet. // +// // +//============================================================================// + +bool tetgenmesh::flip_edge_to_improve(triface *sliver_edge, REAL& improved_cosmaxd) +{ + if (issubseg(*sliver_edge)) { + return false; + } + + flipconstraints fc; + + //fc.noflip_in_surface = 1; // do not flip in surface. + fc.noflip_in_surface = ((b->nobisect > 0) || ((b->cdtrefine & 2) == 0)); + fc.remove_large_angle = 1; + fc.unflip = 1; + fc.collectnewtets = 1; + fc.checkflipeligibility = 1; + fc.cosdihed_in = improved_cosmaxd; // cosmaxd; + fc.cosdihed_out = 0.0; // 90 degree. + fc.max_asp_out = 0.0; + + if (removeedgebyflips(sliver_edge, &fc) == 2) { + // This sliver is removed by flips. + if ((fc.cosdihed_out < cosmaxdihed) || (fc.max_asp_out > b->opt_max_asp_ratio)) { + // Queue new bad tets for further improvements. + badface bf; + for (int j = 0; j < cavetetlist->objects; j++) { + triface *parytet = (triface *) fastlookup(cavetetlist, j); + if (!isdeadtet(*parytet) && !ishulltet(*parytet)) { + if (get_tetqual(parytet, NULL, &bf)) { + if ((bf.key > b->opt_max_asp_ratio) || (bf.cent[0] < cosmaxdihed)) { + bf.forg = org(bf.tt); + bf.fdest = dest(bf.tt); + bf.fapex = apex(bf.tt); + bf.foppo = oppo(bf.tt); + enqueue_badtet(&bf); + } + } else { + terminatetetgen(this, 2); + } + } + } // j + } + cavetetlist->restart(); + return true; + } + + return false; +} + +//============================================================================// +// // +// repair_tet() Repair a bad-qaulity tet. // +// // +//============================================================================// + +bool tetgenmesh::repair_tet(badface *bf, bool bFlips, bool bSmooth, bool bSteiners) +{ + REAL cosmaxd = bf->cent[0]; + REAL eta = bf->cent[2]; + int lcount = bf->ss.shver; // the number of large dihedrals. + + if (cosmaxd < cossmtdihed) { + // It is a sliver (flat) (it might contain a short edge -- skinny). + //triface sliver_edge; + char shape = '0'; + + // Determine the outer shape of this sliver, i.e., a square of a triangle? + if (lcount == 2) { + // It is a square. Try to remove the edge [a,b] + shape = 'S'; + } else if (lcount == 3) { + // It is a triangle. Try to remove the edge [c,d] + shape = 'T'; + //edestoppo(bf->tt, sliver_edge); // face [c,d,a] + } + + if (bFlips) { + if (shape == 'S') { + triface sliver_edge = bf->tt; + if (flip_edge_to_improve(&sliver_edge, cosmaxd)) { + opt_flips_count++; + return true; + } + // Due to 'unflip', the flip function may modify the sliver. + if (get_tet(bf->forg, bf->fdest, bf->fapex, bf->foppo, &(bf->tt))) { + // Try to flip the opposite edge of this sliver. + edestoppo(bf->tt, sliver_edge); // face [c,d,a] + if (flip_edge_to_improve(&sliver_edge, cosmaxd)) { + opt_flips_count++; + return true; + } + } + } else if (shape == 'T') { + triface sliver_edge; + // flip_face_to_improve(...) + edestoppo(bf->tt, sliver_edge); // face [c,d,a] + if (flip_edge_to_improve(&sliver_edge, cosmaxd)) { + opt_flips_count++; + return true; + } + } + } + } else if (eta > b->opt_max_edge_ratio) { + // It is a skinny tet. + // This tet contains a relatively short edge. Check if it can be collapsed. + REAL Lmin = bf->cent[3]; + + // Get the shortest edge of this tet. + triface short_edge = bf->tt; + int i; + for (i = 0; i < 6; i++) { + short_edge.ver = edge2ver[i]; + REAL dd = distance(org(short_edge), dest(short_edge)); + //if (fabs(Lmin - dd) < 1e-8) break; + if ((fabs(Lmin - dd) / Lmin) < 1e-4) break; + } + if (i == 6) { + terminatetetgen(this, 2); + } + + + if (Lmin <= minedgelength) { + // A very short edge. Check if it was correctly created. + point e1 = org(short_edge); + point e2 = dest(short_edge); + if (issteinerpoint(e1)) { + if (!create_a_shorter_edge(e1, e2)) { + terminatetetgen(this, 2); + } + } else if (issteinerpoint(e2)) { + if (!create_a_shorter_edge(e2, e1)) { + terminatetetgen(this, 2); + } + } + } + + } else { + // It is neither a flat nor skinny tet. While it has a large asp. + + } + + + if (bSteiners && + ((bf->key > opt_max_sliver_asp_ratio) || (cosmaxd < cosslidihed))) { + // This sliver is not removed. Due to 'unflip', the flip function may + // modify the sliver. + if (get_tet(bf->forg, bf->fdest, bf->fapex, bf->foppo, &(bf->tt))) { + if (add_steinerpt_to_repair(bf, bSmooth)) { + return true; + } + } + } // if (bSteiners) + + return false; // not repaired +} + +//============================================================================// +// // +// repair_badqual_tets() Repair all queued bad quality tet. // +// // +//============================================================================// + +long tetgenmesh::repair_badqual_tets(bool bFlips, bool bSmooth, bool bSteiners) +{ + if (b->verbose > 1) { + printf(" Repairing %ld bad quality tets.\n", badqual_tets_pool->items); + } + long repaired_count = 0l; + + while (badqual_tets_pool->items > 0) { + + // Get a badtet of highest priority. + badface *bt = top_badtet(); + + if (get_tet(bt->forg, bt->fdest, bt->fapex, bt->foppo, &(bt->tt))) { + if (repair_tet(bt, bFlips, bSmooth, bSteiners)) { + repaired_count++; + } else { + // Failed to repair this tet. Save it. + badface *bf = NULL; + unsplit_badtets->newindex((void **) &bf); + *bf = *bt; + } + } // if (get_tet(...)) + + // Return the badtet to the pool. + dequeue_badtet(); + } // while (badqual_tets_pool->items > 0) + + if (unsplit_badtets->objects > 0l) { + // Re-initialise the priority queue + for (int i = 0; i < 64; i++) { + bt_queuefront[i] = bt_queuetail[i] = NULL; + } + bt_firstnonemptyq = -1; + bt_recentq = -1; + + for (int i = 0; i < unsplit_badtets->objects; i++) { + badface *bt = (badface *) fastlookup(unsplit_badtets, i); + enqueue_badtet(bt); + } + unsplit_badtets->restart(); + } // if (unsplit_badtets->objects > 0l) + + return repaired_count; +} + +//============================================================================// +// // +// improve_mesh() Mesh improvement. // +// // +//============================================================================// + +void tetgenmesh::improve_mesh() +{ + if (!b->quiet) { + printf("Improving mesh...\n"); + } + + if (b->verbose) { + printf(" Target maximum aspect ratio = %g.\n", b->opt_max_asp_ratio); + printf(" Target maximum dihedral angle = %g.\n", b->optmaxdihedral); + printf(" Maximum flip level = %d.\n", b->opt_max_flip_level); // -O# + printf(" Number of iterations = %d.\n", b->opt_iterations); // -O///# + } + + long blt = b->tetrahedraperblock; + badqual_tets_pool = new memorypool(sizeof(badface), blt, sizeof(void *), 0); + badtetrahedrons = new memorypool(sizeof(triface), blt, sizeof(void *), 0); + unsplit_badtets = new arraypool(sizeof(badface), 10); + + for (int i = 0; i < 64; i++) { + bt_queuefront[i] = NULL; + } + bt_firstnonemptyq = -1; + bt_recentq = -1; + + cos_large_dihed = cos(135. / 180. * PI); // used in get_tetqual + + cosmaxdihed = cos(b->optmaxdihedral / 180.0 * PI); // set by -o/# + + // The smallest dihedral angle to identify slivers. + REAL sliver_ang_tol = b->optmaxdihedral - 5.0; + if (sliver_ang_tol < 172.0) { + sliver_ang_tol = 172.; + } + cossmtdihed = cos(sliver_ang_tol / 180.0 * PI); + + // The smallest dihedral angle to split slivers. + REAL split_sliver_ang_tol = b->optmaxdihedral + 10.0; + if (split_sliver_ang_tol < 179.0) { + split_sliver_ang_tol = 179.0; + } else if (split_sliver_ang_tol > 180.0) { + split_sliver_ang_tol = 179.9; + } + cosslidihed = cos(split_sliver_ang_tol / 180.0 * PI); + + opt_max_sliver_asp_ratio = b->opt_max_asp_ratio * 10.; // set by -o//# + + int attrnum = numelemattrib - 1; + triface checktet; badface bf; + + // Put all bad tetrahedra into array. + tetrahedrons->traversalinit(); + checktet.tet = tetrahedrontraverse(); + while (checktet.tet != NULL) { + if (b->convex) { // -c + // Skip this tet if it lies in the exterior. + if (elemattribute(checktet.tet, attrnum) == -1.0) { + checktet.tet = tetrahedrontraverse(); + continue; + } + } + if (get_tetqual(&checktet, NULL, &bf)) { + if ((bf.key > b->opt_max_asp_ratio) || (bf.cent[0] < cosmaxdihed)) { + bf.forg = org(bf.tt); + bf.fdest = dest(bf.tt); + bf.fapex = apex(bf.tt); + bf.foppo = oppo(bf.tt); + enqueue_badtet(&bf); + } + } else { + terminatetetgen(this, 2); // a degenerated tet. + } + checktet.tet = tetrahedrontraverse(); + } + + // Backup flip edge options. + int bakautofliplinklevel = autofliplinklevel; + int bakfliplinklevel = b->fliplinklevel; + int bakmaxflipstarsize = b->flipstarsize; + + b->fliplinklevel = 1; // initial (<= b->opt_max_flip_level, -O#) + b->flipstarsize = 10; // b->optmaxflipstarsize; + + long total_repaired_count = 0l; + long bak_pt_count = points->items; + + // Only using flips. + while (badqual_tets_pool->items > 0) { + long repaired_count = repair_badqual_tets(true, false, false); + total_repaired_count += repaired_count; + if (b->fliplinklevel < b->opt_max_flip_level) { + b->fliplinklevel++; + } else { + break; // maximal flip level is reached. + } + } // while (badqual_tets_pool->items > 0) + + if (b->verbose > 1) { + printf(" Repaired %ld tetrahedra by flips.\n", total_repaired_count); + printf(" %ld badqual tets remained.\n", badqual_tets_pool->items); + } + + int iter = 0; + long bak_st_count = st_volref_count; + while ((badqual_tets_pool->items > 0) && (iter < b->opt_iterations)) { + //b->fliplinklevel++; + long repaired_count = repair_badqual_tets(true, true, true); + // Break if no repair and no new Steiner point. + if ((repaired_count == 0l) && (bak_st_count == st_volref_count)) { + break; + } + total_repaired_count += repaired_count; + bak_st_count = st_volref_count; + iter++; + } // while (badqual_tets_pool->items > 0) + + // Do last flips. + if (badqual_tets_pool->items > 0) { + long repaired_count = repair_badqual_tets(true, false, false); + total_repaired_count += repaired_count; + } + + if (b->verbose > 1) { + printf(" Repaired %ld tetrahedra.\n", total_repaired_count); + printf(" %ld badqual tets remained.\n", badqual_tets_pool->items); + } + + if (later_unflip_queue->objects > b->unflip_queue_limit) { + //recoverdelaunay(); + later_unflip_queue->restart(); // clean it. + } + + if (b->verbose) { + if (opt_flips_count > 0l) { + printf(" Removed %ld edges/faces.\n", opt_flips_count); + } + if (opt_collapse_count > 0l) { + printf(" Collapsed %ld edges/faces.\n", opt_collapse_count); + } + if (opt_smooth_count > 0l) { + printf(" Smoothed %ld vertices.\n", opt_smooth_count); + } + if ((points->items - bak_pt_count) > 0l) { + printf(" Added %ld Steiner points.\n", points->items - bak_pt_count); + } + } + + + // Restore original flip edge options. + autofliplinklevel = bakautofliplinklevel; + b->fliplinklevel = bakfliplinklevel; + b->flipstarsize = bakmaxflipstarsize; + + delete badtetrahedrons; + badtetrahedrons = NULL; + delete badqual_tets_pool; + badqual_tets_pool = NULL; + delete unsplit_badtets; + unsplit_badtets = NULL; +} + +// // +// // +//== optimize_cxx ============================================================// + +//== meshstat_cxx ============================================================// +// // +// // + +//============================================================================// +// // +// printfcomma() Print a (large) number with the 'thousands separator'. // +// // +// The following code was simply copied from "stackoverflow". // +// // +//============================================================================// + +void tetgenmesh::printfcomma(unsigned long n) +{ + unsigned long n2 = 0; + int scale = 1; + while (n >= 1000) { + n2 = n2 + scale * (n % 1000); + n /= 1000; + scale *= 1000; + } + printf ("%ld", n); + while (scale != 1) { + scale /= 1000; + n = n2 / scale; + n2 = n2 % scale; + printf (",%03ld", n); + } +} + +//============================================================================// +// // +// checkmesh() Test the mesh for topological consistency. // +// // +// If 'topoflag' is set, only check the topological connection of the mesh, // +// i.e., do not report degenerated or inverted elements. // +// // +//============================================================================// + +int tetgenmesh::check_mesh(int topoflag) +{ + triface tetloop, neightet, symtet; + point pa, pb, pc, pd; + REAL ori; + int horrors, i; + + if (!b->quiet) { + printf(" Checking consistency of mesh...\n"); + } + + horrors = 0; + tetloop.ver = 0; + // Run through the list of tetrahedra, checking each one. + tetrahedrons->traversalinit(); + tetloop.tet = alltetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + // Check all four faces of the tetrahedron. + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + pa = org(tetloop); + pb = dest(tetloop); + pc = apex(tetloop); + pd = oppo(tetloop); + if (tetloop.ver == 0) { // Only test for inversion once. + if (!ishulltet(tetloop)) { // Only do test if it is not a hull tet. + if (!topoflag) { + ori = orient3d(pa, pb, pc, pd); + if (ori >= 0.0) { + printf(" !! !! %s ", ori > 0.0 ? "Inverted" : "Degenerated"); + printf(" (%d, %d, %d, %d) (ori = %.17g)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd), ori); + horrors++; + } + } + } + if (infected(tetloop)) { + // This may be a bug. Report it. + printf(" !! (%d, %d, %d, %d) is infected.\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + horrors++; + } + if (marktested(tetloop)) { + // This may be a bug. Report it. + printf(" !! (%d, %d, %d, %d) is marked.\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + horrors++; + } + } + if (tetloop.tet[tetloop.ver] == NULL) { + printf(" !! !! No neighbor at face (%d, %d, %d).\n", pointmark(pa), + pointmark(pb), pointmark(pc)); + horrors++; + } else { + // Find the neighboring tetrahedron on this face. + fsym(tetloop, neightet); + if (neightet.tet != NULL) { + // Check that the tetrahedron's neighbor knows it's a neighbor. + fsym(neightet, symtet); + if ((tetloop.tet != symtet.tet) || (tetloop.ver != symtet.ver)) { + printf(" !! !! Asymmetric tetra-tetra bond:\n"); + if (tetloop.tet == symtet.tet) { + printf(" (Right tetrahedron, wrong orientation)\n"); + } + printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + // Check if they have the same edge (the bond() operation). + if ((org(neightet) != pb) || (dest(neightet) != pa)) { + printf(" !! !! Wrong edge-edge bond:\n"); + printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + // Check if they have the same apex. + if (apex(neightet) != pc) { + printf(" !! !! Wrong face-face bond:\n"); + printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + // Check if they have the same opposite. + if (oppo(neightet) == pd) { + printf(" !! !! Two identical tetra:\n"); + printf(" First: (%d, %d, %d, %d)\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + printf(" Second: (%d, %d, %d, %d)\n", pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + } else { + printf(" !! !! Tet-face has no neighbor (%d, %d, %d) - %d:\n", + pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd)); + horrors++; + } + } + if (facemarked(tetloop)) { + // This may be a bug. Report it. + printf(" !! tetface (%d, %d, %d) %d is marked.\n", pointmark(pa), + pointmark(pb), pointmark(pc), pointmark(pd)); + } + } + // Check the six edges of this tet. + for (i = 0; i < 6; i++) { + tetloop.ver = edge2ver[i]; + if (edgemarked(tetloop)) { + // This may be a bug. Report it. + printf(" !! tetedge (%d, %d) %d, %d is marked.\n", + pointmark(org(tetloop)), pointmark(dest(tetloop)), + pointmark(apex(tetloop)), pointmark(oppo(tetloop))); + } + } + tetloop.tet = alltetrahedrontraverse(); + } + if (horrors == 0) { + if (!b->quiet) { + printf(" In my studied opinion, the mesh appears to be consistent.\n"); + } + } else { + printf(" !! !! !! !! %d %s witnessed.\n", horrors, + horrors > 1 ? "abnormity" : "abnormities"); + } + + return horrors; +} + +//============================================================================// +// // +// checkshells() Test the boundary mesh for topological consistency. // +// // +//============================================================================// + +int tetgenmesh::check_shells() +{ + triface neightet, symtet; + face shloop, spinsh, nextsh; + face checkseg; + point pa, pb; + int bakcount; + int horrors, i; + + if (!b->quiet) { + printf(" Checking consistency of the mesh boundary...\n"); + } + horrors = 0; + + void **bakpathblock = subfaces->pathblock; + void *bakpathitem = subfaces->pathitem; + int bakpathitemsleft = subfaces->pathitemsleft; + int bakalignbytes = subfaces->alignbytes; + + subfaces->traversalinit(); + shloop.sh = shellfacetraverse(subfaces); + while (shloop.sh != NULL) { + shloop.shver = 0; + for (i = 0; i < 3; i++) { + // Check the face ring at this edge. + pa = sorg(shloop); + pb = sdest(shloop); + spinsh = shloop; + spivot(spinsh, nextsh); + bakcount = horrors; + while ((nextsh.sh != NULL) && (nextsh.sh != shloop.sh)) { + if (nextsh.sh[3] == NULL) { + printf(" !! !! Wrong subface-subface connection (Dead subface).\n"); + printf(" First: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Second: x%lu (DEAD)\n", (unsigned long)(uintptr_t) nextsh.sh); + horrors++; + break; + } + // check if they have the same edge. + if (!(((sorg(nextsh) == pa) && (sdest(nextsh) == pb)) || + ((sorg(nextsh) == pb) && (sdest(nextsh) == pa)))) { + printf(" !! !! Wrong subface-subface connection.\n"); + printf(" First: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Scond: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) nextsh.sh, + pointmark(sorg(nextsh)), pointmark(sdest(nextsh)), + pointmark(sapex(nextsh))); + horrors++; + break; + } + // Check they should not have the same apex. + if (sapex(nextsh) == sapex(spinsh)) { + printf(" !! !! Existing two duplicated subfaces.\n"); + printf(" First: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Scond: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) nextsh.sh, + pointmark(sorg(nextsh)), pointmark(sdest(nextsh)), + pointmark(sapex(nextsh))); + horrors++; + break; + } + spinsh = nextsh; + spivot(spinsh, nextsh); + } + // Check subface-subseg bond. + sspivot(shloop, checkseg); + if (checkseg.sh != NULL) { + if (checkseg.sh[3] == NULL) { + printf(" !! !! Wrong subface-subseg connection (Dead subseg).\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Sub: x%lu (Dead)\n", (unsigned long)(uintptr_t) checkseg.sh); + horrors++; + } else { + if (!(((sorg(checkseg) == pa) && (sdest(checkseg) == pb)) || + ((sorg(checkseg) == pb) && (sdest(checkseg) == pa)))) { + printf(" !! !! Wrong subface-subseg connection.\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Seg: x%lu (%d, %d).\n", (unsigned long)(uintptr_t) checkseg.sh, + pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); + horrors++; + } + } + } + if (horrors > bakcount) break; // An error detected. + senextself(shloop); + } + // Check tet-subface connection. + stpivot(shloop, neightet); + if (neightet.tet != NULL) { + if (neightet.tet[4] == NULL) { + printf(" !! !! Wrong sub-to-tet connection (Dead tet)\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Tet: x%lu (DEAD)\n", (unsigned long)(uintptr_t) neightet.tet); + horrors++; + } else { + if (!((sorg(shloop) == org(neightet)) && + (sdest(shloop) == dest(neightet)))) { + printf(" !! !! Wrong sub-to-tet connection\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) shloop.sh, + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + printf(" Tet: x%lu (%d, %d, %d, %d).\n", + (unsigned long)(uintptr_t) neightet.tet, pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + tspivot(neightet, spinsh); + if (!((sorg(spinsh) == org(neightet)) && + (sdest(spinsh) == dest(neightet)))) { + printf(" !! !! Wrong tet-sub connection.\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Tet: x%lu (%d, %d, %d, %d).\n", + (unsigned long)(uintptr_t) neightet.tet, pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + horrors++; + } + fsym(neightet, symtet); + tspivot(symtet, spinsh); + if (spinsh.sh != NULL) { + if (!((sorg(spinsh) == org(symtet)) && + (sdest(spinsh) == dest(symtet)))) { + printf(" !! !! Wrong tet-sub connection.\n"); + printf(" Sub: x%lu (%d, %d, %d).\n", (unsigned long)(uintptr_t) spinsh.sh, + pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + pointmark(sapex(spinsh))); + printf(" Tet: x%lu (%d, %d, %d, %d).\n", + (unsigned long)(uintptr_t) symtet.tet, pointmark(org(symtet)), + pointmark(dest(symtet)), pointmark(apex(symtet)), + pointmark(oppo(symtet))); + horrors++; + } + } else { + printf(" Warning: Broken tet-sub-tet connection.\n"); + } + } + } + if (sinfected(shloop)) { + // This may be a bug. report it. + printf(" !! A infected subface: (%d, %d, %d).\n", + pointmark(sorg(shloop)), pointmark(sdest(shloop)), + pointmark(sapex(shloop))); + } + if (smarktested(shloop)) { + // This may be a bug. report it. + printf(" !! A marked subface: (%d, %d, %d).\n", pointmark(sorg(shloop)), + pointmark(sdest(shloop)), pointmark(sapex(shloop))); + } + shloop.sh = shellfacetraverse(subfaces); + } + + if (horrors == 0) { + if (!b->quiet) { + printf(" Mesh boundaries connected correctly.\n"); + } + } else { + printf(" !! !! !! !! %d boundary connection viewed with horror.\n", + horrors); + } + + subfaces->pathblock = bakpathblock; + subfaces->pathitem = bakpathitem; + subfaces->pathitemsleft = bakpathitemsleft; + subfaces->alignbytes = bakalignbytes; + + return horrors; +} + +//============================================================================// +// // +// checksegments() Check the connections between tetrahedra and segments. // +// // +//============================================================================// + +int tetgenmesh::check_segments() +{ + triface tetloop, neightet, spintet; + shellface *segs; + face neighsh, spinsh, checksh; + face sseg, checkseg; + point pa, pb; + int miscount; + int t1ver; + int horrors, i; + + + if (!b->quiet) { + printf(" Checking tet->seg connections...\n"); + } + + horrors = 0; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != NULL) { + // Loop the six edges of the tet. + if (tetloop.tet[8] != NULL) { + segs = (shellface *) tetloop.tet[8]; + for (i = 0; i < 6; i++) { + sdecode(segs[i], sseg); + if (sseg.sh != NULL) { + // Get the edge of the tet. + tetloop.ver = edge2ver[i]; + // Check if they are the same edge. + pa = (point) sseg.sh[3]; + pb = (point) sseg.sh[4]; + if (!(((org(tetloop) == pa) && (dest(tetloop) == pb)) || + ((org(tetloop) == pb) && (dest(tetloop) == pa)))) { + printf(" !! Wrong tet-seg connection.\n"); + printf(" Tet: x%lu (%d, %d, %d, %d) - Seg: x%lu (%d, %d).\n", + (unsigned long)(uintptr_t) tetloop.tet, pointmark(org(tetloop)), + pointmark(dest(tetloop)), pointmark(apex(tetloop)), + pointmark(oppo(tetloop)), (unsigned long)(uintptr_t) sseg.sh, + pointmark(pa), pointmark(pb)); + horrors++; + } else { + // Loop all tets sharing at this edge. + neightet = tetloop; + do { + tsspivot1(neightet, checkseg); + if (checkseg.sh != sseg.sh) { + printf(" !! Wrong tet->seg connection.\n"); + printf(" Tet: x%lu (%d, %d, %d, %d) - ", + (unsigned long)(uintptr_t) neightet.tet, pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet))); + if (checkseg.sh != NULL) { + printf("Seg x%lu (%d, %d).\n", (unsigned long)(uintptr_t) checkseg.sh, + pointmark(sorg(checkseg)),pointmark(sdest(checkseg))); + } else { + printf("Seg: NULL.\n"); + } + horrors++; + } + fnextself(neightet); + } while (neightet.tet != tetloop.tet); + } + // Check the seg->tet pointer. + sstpivot1(sseg, neightet); + if (neightet.tet == NULL) { + printf(" !! Wrong seg->tet connection (A NULL tet).\n"); + horrors++; + } else { + if (!(((org(neightet) == pa) && (dest(neightet) == pb)) || + ((org(neightet) == pb) && (dest(neightet) == pa)))) { + printf(" !! Wrong seg->tet connection (Wrong edge).\n"); + printf(" Tet: x%lu (%d, %d, %d, %d) - Seg: x%lu (%d, %d).\n", + (unsigned long)(uintptr_t) neightet.tet, pointmark(org(neightet)), + pointmark(dest(neightet)), pointmark(apex(neightet)), + pointmark(oppo(neightet)), (unsigned long)(uintptr_t) sseg.sh, + pointmark(pa), pointmark(pb)); + horrors++; + } + } + } + } + } + // Loop the six edge of this tet. + neightet.tet = tetloop.tet; + for (i = 0; i < 6; i++) { + neightet.ver = edge2ver[i]; + if (edgemarked(neightet)) { + // A possible bug. Report it. + printf(" !! A marked edge: (%d, %d, %d, %d) -- x%lu %d.\n", + pointmark(org(neightet)), pointmark(dest(neightet)), + pointmark(apex(neightet)), pointmark(oppo(neightet)), + (unsigned long)(uintptr_t) neightet.tet, neightet.ver); + // Check if all tets at the edge are marked. + spintet = neightet; + while (1) { + fnextself(spintet); + if (!edgemarked(spintet)) { + printf(" !! !! An unmarked edge (%d, %d, %d, %d) -- x%lu %d.\n", + pointmark(org(spintet)), pointmark(dest(spintet)), + pointmark(apex(spintet)), pointmark(oppo(spintet)), + (unsigned long)(uintptr_t) spintet.tet, spintet.ver); + horrors++; + } + if (spintet.tet == neightet.tet) break; + } + } + } + tetloop.tet = tetrahedrontraverse(); + } + + if (!b->quiet) { + printf(" Checking seg->tet connections...\n"); + } + + miscount = 0; // Count the number of unrecovered segments. + subsegs->traversalinit(); + sseg.shver = 0; + sseg.sh = shellfacetraverse(subsegs); + while (sseg.sh != NULL) { + pa = sorg(sseg); + pb = sdest(sseg); + spivot(sseg, neighsh); + if (neighsh.sh != NULL) { + spinsh = neighsh; + while (1) { + // Check seg-subface bond. + point e1 = sorg(spinsh); + point e2 = sdest(spinsh); + if (((e1 == pa) && (e2 == pb)) || + ((e1 == pb) && (e2 == pa))) { + // Keep the same rotate direction. + //if (sorg(spinsh) != pa) { + // sesymself(spinsh); + // printf(" !! Wrong ori at subface (%d, %d, %d) -- x%lu %d\n", + // pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), + // pointmark(sapex(spinsh)), (uintptr_t) spinsh.sh, + // spinsh.shver); + // horrors++; + //} + stpivot(spinsh, spintet); + if (spintet.tet != NULL) { + // Check if all tets at this segment. + while (1) { + tsspivot1(spintet, checkseg); + if (checkseg.sh == NULL) { + printf(" !! !! No seg at tet (%d, %d, %d, %d) -- x%lu %d\n", + pointmark(org(spintet)), pointmark(dest(spintet)), + pointmark(apex(spintet)), pointmark(oppo(spintet)), + (unsigned long)(uintptr_t) spintet.tet, spintet.ver); + horrors++; + } + if (checkseg.sh != sseg.sh) { + printf(" !! !! Wrong seg (%d, %d) at tet (%d, %d, %d, %d)\n", + pointmark(sorg(checkseg)), pointmark(sdest(checkseg)), + pointmark(org(spintet)), pointmark(dest(spintet)), + pointmark(apex(spintet)), pointmark(oppo(spintet))); + horrors++; + } + fnextself(spintet); + // Stop at the next subface. + tspivot(spintet, checksh); + if (checksh.sh != NULL) break; + } // while (1) + } + } else { + point e3 = sapex(spinsh); + printf(" !! Wrong seg-subface (%d, %d) - (%d, %d, %d) connect\n", + pointmark(pa), pointmark(pb), + (e1 != NULL ? pointmark(e1) : -1), + (e2 != NULL ? pointmark(e2) : -1), + (e3 != NULL ? pointmark(e3) : -1) + //(uintptr_t) spinsh.sh, + //spinsh.shver + ); + horrors++; + break; + } // if pa, pb + spivotself(spinsh); + if (spinsh.sh == NULL) break; // A dangling segment. + if (spinsh.sh == neighsh.sh) break; + } // while (1) + } // if (neighsh.sh != NULL) + // Count the number of "un-recovered" segments. + sstpivot1(sseg, neightet); + if (neightet.tet == NULL) { + miscount++; + } + sseg.sh = shellfacetraverse(subsegs); + } + + if (!b->quiet) { + printf(" Checking seg->seg connections...\n"); + } + + points->traversalinit(); + pa = pointtraverse(); + while (pa != NULL) { + if (pointtype(pa) == FREESEGVERTEX) { + // There should be two subsegments connected at 'pa'. + // Get a subsegment containing 'pa'. + sdecode(point2sh(pa), sseg); + if ((sseg.sh == NULL) || sseg.sh[3] == NULL) { + printf(" !! Dead point-to-seg pointer at point %d.\n", + pointmark(pa)); + horrors++; + } else { + sseg.shver = 0; + if (sorg(sseg) != pa) { + if (sdest(sseg) != pa) { + printf(" !! Wrong point-to-seg pointer at point %d.\n", + pointmark(pa)); + horrors++; + } else { + // Find the next subsegment at 'pa'. + senext(sseg, checkseg); + if ((checkseg.sh == NULL) || (checkseg.sh[3] == NULL)) { + printf(" !! Dead seg-seg connection at point %d.\n", + pointmark(pa)); + horrors++; + } else { + spivotself(checkseg); + checkseg.shver = 0; + if ((sorg(checkseg) != pa) && (sdest(checkseg) != pa)) { + printf(" !! Wrong seg-seg connection at point %d.\n", + pointmark(pa)); + horrors++; + } + } + } + } else { + // Find the previous subsegment at 'pa'. + senext2(sseg, checkseg); + if ((checkseg.sh == NULL) || (checkseg.sh[3] == NULL)) { + printf(" !! Dead seg-seg connection at point %d.\n", + pointmark(pa)); + horrors++; + } else { + spivotself(checkseg); + checkseg.shver = 0; + if ((sorg(checkseg) != pa) && (sdest(checkseg) != pa)) { + printf(" !! Wrong seg-seg connection at point %d.\n", + pointmark(pa)); + horrors++; + } + } + } + } + } + pa = pointtraverse(); + } + + if (horrors == 0) { + printf(" Segments are connected properly.\n"); + } else { + printf(" !! !! !! !! Found %d missing connections.\n", horrors); + } + if (miscount > 0) { + printf(" !! !! Found %d missing segments.\n", miscount); + } + + return horrors; +} + +//============================================================================// +// // +// checkdelaunay() Ensure that the mesh is (constrained) Delaunay. // +// // +//============================================================================// + +int tetgenmesh::check_delaunay(int perturb) +{ + triface tetloop; + triface symtet; + face checksh; + point pa, pb, pc, pd, pe; + REAL sign; + int ndcount; // Count the non-locally Delaunay faces. + int horrors; + + if (!b->quiet) { + printf(" Checking Delaunay property of the mesh...\n"); + } + + ndcount = 0; + horrors = 0; + tetloop.ver = 0; + // Run through the list of triangles, checking each one. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + // Check all four faces of the tetrahedron. + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, symtet); + // Only do test if its adjoining tet is not a hull tet or its pointer + // is larger (to ensure that each pair isn't tested twice). + if (((point) symtet.tet[7] != dummypoint)&&(tetloop.tet < symtet.tet)) { + pa = org(tetloop); + pb = dest(tetloop); + pc = apex(tetloop); + pd = oppo(tetloop); + pe = oppo(symtet); + if (perturb) { + sign = insphere_s(pa, pb, pc, pd, pe); + } else { + sign = insphere(pa, pb, pc, pd, pe); + } + if (sign < 0.0) { + ndcount++; + if (checksubfaceflag) { + tspivot(tetloop, checksh); + } + if (checksh.sh == NULL) { + printf(" !! Non-locally Delaunay (%d, %d, %d) - %d, %d\n", + pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd), + pointmark(pe)); + horrors++; + } + } + } + } + tetloop.tet = tetrahedrontraverse(); + } + + if (horrors == 0) { + if (!b->quiet) { + if (ndcount > 0) { + printf(" The mesh is constrained Delaunay.\n"); + } else { + printf(" The mesh is Delaunay.\n"); + } + } + } else { + printf(" !! !! !! !! Found %d non-Delaunay faces.\n", horrors); + } + + return horrors; +} + +//============================================================================// +// // +// Check if the current tetrahedralization is (constrained) regular. // +// // +// The parameter 'type' determines which regularity should be checked: // +// - 0: check the Delaunay property. // +// - 1: check the Delaunay property with symbolic perturbation. // +// - 2: check the regular property, the weights are stored in p[3]. // +// - 3: check the regular property with symbolic perturbation. // +// // +//============================================================================// + +int tetgenmesh::check_regular(int type) +{ + triface tetloop; + triface symtet; + face checksh; + point p[5]; + REAL sign; + int ndcount; // Count the non-locally Delaunay faces. + int horrors; + + if (!b->quiet) { + printf(" Checking %s %s property of the mesh...\n", + (type & 2) == 0 ? "Delaunay" : "regular", + (type & 1) == 0 ? " " : "(s)"); + } + + // Make sure orient3d(p[1], p[0], p[2], p[3]) > 0; + // Hence if (insphere(p[1], p[0], p[2], p[3], p[4]) > 0) means that + // p[4] lies inside the circumsphere of p[1], p[0], p[2], p[3]. + // The same if orient4d(p[1], p[0], p[2], p[3], p[4]) > 0 means that + // p[4] lies below the oriented hyperplane passing through + // p[1], p[0], p[2], p[3]. + + ndcount = 0; + horrors = 0; + tetloop.ver = 0; + // Run through the list of triangles, checking each one. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + // Check all four faces of the tetrahedron. + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, symtet); + // Only do test if its adjoining tet is not a hull tet or its pointer + // is larger (to ensure that each pair isn't tested twice). + if (((point) symtet.tet[7] != dummypoint)&&(tetloop.tet < symtet.tet)) { + p[0] = org(tetloop); // pa + p[1] = dest(tetloop); // pb + p[2] = apex(tetloop); // pc + p[3] = oppo(tetloop); // pd + p[4] = oppo(symtet); // pe + + if (type == 0) { + sign = insphere(p[1], p[0], p[2], p[3], p[4]); + } else if (type == 1) { + sign = insphere_s(p[1], p[0], p[2], p[3], p[4]); + } else if (type == 2) { + sign = orient4d(p[1], p[0], p[2], p[3], p[4], + p[1][3], p[0][3], p[2][3], p[3][3], p[4][3]); + } else { // type == 3 + sign = orient4d_s(p[1], p[0], p[2], p[3], p[4], + p[1][3], p[0][3], p[2][3], p[3][3], p[4][3]); + } + + if (sign > 0.0) { + ndcount++; + if (checksubfaceflag) { + tspivot(tetloop, checksh); + } + if (checksh.sh == NULL) { + printf(" !! Non-locally %s (%d, %d, %d) - %d, %d\n", + (type & 2) == 0 ? "Delaunay" : "regular", + pointmark(p[0]), pointmark(p[1]), pointmark(p[2]), + pointmark(p[3]), pointmark(p[4])); + horrors++; + } + } + } + } + tetloop.tet = tetrahedrontraverse(); + } + + if (horrors == 0) { + if (!b->quiet) { + if (ndcount > 0) { + printf(" The mesh is constrained %s.\n", + (type & 2) == 0 ? "Delaunay" : "regular"); + } else { + printf(" The mesh is %s.\n", (type & 2) == 0 ? "Delaunay" : "regular"); + } + } + } else { + printf(" !! !! !! !! Found %d non-%s faces.\n", horrors, + (type & 2) == 0 ? "Delaunay" : "regular"); + } + + return horrors; +} + +//============================================================================// +// // +// checkconforming() Ensure that the mesh is conforming Delaunay. // +// // +// If 'flag' is 1, only check subsegments. If 'flag' is 2, check subfaces. // +// If 'flag' is 3, check both subsegments and subfaces. // +// // +//============================================================================// + +int tetgenmesh::check_conforming(int flag) +{ + triface searchtet, neightet, spintet; + face shloop; + face segloop; + point eorg, edest, eapex, pa, pb, pc; + REAL cent[3], radius, dist, diff, rd, len; + bool enq; + int encsubsegs, encsubfaces; + int t1ver; + int i; + + REAL A[4][4], rhs[4], D; + int indx[4]; + REAL elen[3]; + + encsubsegs = 0; + + if (flag & 1) { + if (!b->quiet) { + printf(" Checking conforming property of segments...\n"); + } + encsubsegs = 0; + + // Run through the list of subsegments, check each one. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != (shellface *) NULL) { + eorg = (point) segloop.sh[3]; + edest = (point) segloop.sh[4]; + radius = 0.5 * distance(eorg, edest); + for (i = 0; i < 3; i++) cent[i] = 0.5 * (eorg[i] + edest[i]); + + enq = false; + sstpivot1(segloop, neightet); + if (neightet.tet != NULL) { + spintet = neightet; + while (1) { + eapex= apex(spintet); + if (eapex != dummypoint) { + dist = distance(eapex, cent); + diff = dist - radius; + if (fabs(diff) / radius <= b->epsilon) diff = 0.0; // Rounding. + if (diff < 0) { + enq = true; break; + } + } + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + } + if (enq) { + printf(" !! !! Non-conforming segment: (%d, %d)\n", + pointmark(eorg), pointmark(edest)); + encsubsegs++; + } + segloop.sh = shellfacetraverse(subsegs); + } + + if (encsubsegs == 0) { + if (!b->quiet) { + printf(" The segments are conforming Delaunay.\n"); + } + } else { + printf(" !! !! %d subsegments are non-conforming.\n", encsubsegs); + } + } // if (flag & 1) + + encsubfaces = 0; + + if (flag & 2) { + if (!b->quiet) { + printf(" Checking conforming property of subfaces...\n"); + } + + // Run through the list of subfaces, check each one. + subfaces->traversalinit(); + shloop.sh = shellfacetraverse(subfaces); + while (shloop.sh != (shellface *) NULL) { + pa = (point) shloop.sh[3]; + pb = (point) shloop.sh[4]; + pc = (point) shloop.sh[5]; + + // Compute the coefficient matrix A (3x3). + A[0][0] = pb[0] - pa[0]; + A[0][1] = pb[1] - pa[1]; + A[0][2] = pb[2] - pa[2]; // vector V1 (pa->pb) + A[1][0] = pc[0] - pa[0]; + A[1][1] = pc[1] - pa[1]; + A[1][2] = pc[2] - pa[2]; // vector V2 (pa->pc) + cross(A[0], A[1], A[2]); // vector V3 (V1 X V2) + + // Compute the right hand side vector b (3x1). + elen[0] = dot(A[0], A[0]); + elen[1] = dot(A[1], A[1]); + rhs[0] = 0.5 * elen[0]; + rhs[1] = 0.5 * elen[1]; + rhs[2] = 0.0; + + if (lu_decmp(A, 3, indx, &D, 0)) { + lu_solve(A, 3, indx, rhs, 0); + cent[0] = pa[0] + rhs[0]; + cent[1] = pa[1] + rhs[1]; + cent[2] = pa[2] + rhs[2]; + rd = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); + + // Check if this subface is encroached. + for (i = 0; i < 2; i++) { + stpivot(shloop, searchtet); + if (!ishulltet(searchtet)) { + len = distance(oppo(searchtet), cent); + if ((fabs(len - rd) / rd) < b->epsilon) len = rd; // Rounding. + if (len < rd) { + printf(" !! !! Non-conforming subface: (%d, %d, %d)\n", + pointmark(pa), pointmark(pb), pointmark(pc)); + encsubfaces++; + enq = true; break; + } + } + sesymself(shloop); + } + } + shloop.sh = shellfacetraverse(subfaces); + } + + if (encsubfaces == 0) { + if (!b->quiet) { + printf(" The subfaces are conforming Delaunay.\n"); + } + } else { + printf(" !! !! %d subfaces are non-conforming.\n", encsubfaces); + } + } // if (flag & 2) + + return encsubsegs + encsubfaces; +} + +//============================================================================// +// // +// qualitystatistics() Print statistics about the quality of the mesh. // +// // +//============================================================================// + +void tetgenmesh::qualitystatistics() +{ + triface tetloop, neightet; + point p[4]; + char sbuf[128]; + REAL radiusratiotable[12]; + REAL aspectratiotable[12]; + REAL A[4][4], rhs[4], D; + REAL V[6][3], N[4][3], H[4]; // edge-vectors, face-normals, face-heights. + REAL edgelength[6], alldihed[6], faceangle[3]; + REAL shortest, longest; + REAL smallestvolume, biggestvolume; + REAL smallestratio, biggestratio; + REAL smallestradiusratio, biggestradiusratio; // radius-edge ratio. + REAL smallestdiangle, biggestdiangle; + REAL smallestfaangle, biggestfaangle; + REAL total_tet_vol, total_tetprism_vol; + REAL tetvol, minaltitude; + REAL cirradius, minheightinv; // insradius; + REAL shortlen, longlen; + REAL tetaspect, tetradius; + REAL smalldiangle, bigdiangle; + REAL smallfaangle, bigfaangle; + unsigned long radiustable[12]; + unsigned long aspecttable[16]; + unsigned long dihedangletable[18]; + unsigned long faceangletable[18]; + int indx[4]; + int radiusindex; + int aspectindex; + int tendegree; + int i, j; + // Report the tet which has the biggest radius-edge ratio. + triface biggestradiusratiotet; + // Report the tet which has the biggest volume. + triface biggestvolumetet; + triface longestedgetet; + + printf("Mesh quality statistics:\n\n"); + + shortlen = longlen = 0.0; + smalldiangle = bigdiangle = 0.0; + total_tet_vol = 0.0; + total_tetprism_vol = 0.0; + + radiusratiotable[0] = 0.707; radiusratiotable[1] = 1.0; + radiusratiotable[2] = 1.1; radiusratiotable[3] = 1.2; + radiusratiotable[4] = 1.4; radiusratiotable[5] = 1.6; + radiusratiotable[6] = 1.8; radiusratiotable[7] = 2.0; + radiusratiotable[8] = 2.5; radiusratiotable[9] = 3.0; + radiusratiotable[10] = 10.0; radiusratiotable[11] = 0.0; + + aspectratiotable[0] = 1.5; aspectratiotable[1] = 2.0; + aspectratiotable[2] = 2.5; aspectratiotable[3] = 3.0; + aspectratiotable[4] = 4.0; aspectratiotable[5] = 6.0; + aspectratiotable[6] = 10.0; aspectratiotable[7] = 15.0; + aspectratiotable[8] = 25.0; aspectratiotable[9] = 50.0; + aspectratiotable[10] = 100.0; aspectratiotable[11] = 0.0; + + for (i = 0; i < 12; i++) radiustable[i] = 0l; + for (i = 0; i < 12; i++) aspecttable[i] = 0l; + for (i = 0; i < 18; i++) dihedangletable[i] = 0l; + for (i = 0; i < 18; i++) faceangletable[i] = 0l; + + minaltitude = xmax - xmin + ymax - ymin + zmax - zmin; + minaltitude = minaltitude * minaltitude; + shortest = minaltitude; + longest = 0.0; + smallestvolume = minaltitude; + biggestvolume = 0.0; + smallestratio = smallestradiusratio = 1e+16; // minaltitude; + biggestratio = biggestradiusratio = 0.0; + smallestdiangle = smallestfaangle = 180.0; + biggestdiangle = biggestfaangle = 0.0; + + + int attrnum = numelemattrib - 1; + + // Loop all elements, calculate quality parameters for each element. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + + //int tidx = 1; + + while (tetloop.tet != (tetrahedron *) NULL) { + + if (b->convex) { + // Skip tets in the exterior. + if (elemattribute(tetloop.tet, attrnum) == -1.0) { + tetloop.tet = tetrahedrontraverse(); + continue; + } + } + + // Get four vertices: p0, p1, p2, p3. + for (i = 0; i < 4; i++) p[i] = (point) tetloop.tet[4 + i]; + + // Get the tet volume. + tetvol = orient3dfast(p[1], p[0], p[2], p[3]) / 6.0; + total_tet_vol += tetvol; + total_tetprism_vol += tetprismvol(p[0], p[1], p[2], p[3]); + + // Calculate the largest and smallest volume. + if (tetvol < smallestvolume) { + smallestvolume = tetvol; + } + if (tetvol > biggestvolume) { + biggestvolume = tetvol; + biggestvolumetet.tet = tetloop.tet; + } + + // Set the edge vectors: V[0], ..., V[5] + for (i = 0; i < 3; i++) V[0][i] = p[0][i] - p[3][i]; // V[0]: p3->p0. + for (i = 0; i < 3; i++) V[1][i] = p[1][i] - p[3][i]; // V[1]: p3->p1. + for (i = 0; i < 3; i++) V[2][i] = p[2][i] - p[3][i]; // V[2]: p3->p2. + for (i = 0; i < 3; i++) V[3][i] = p[1][i] - p[0][i]; // V[3]: p0->p1. + for (i = 0; i < 3; i++) V[4][i] = p[2][i] - p[1][i]; // V[4]: p1->p2. + for (i = 0; i < 3; i++) V[5][i] = p[0][i] - p[2][i]; // V[5]: p2->p0. + + // Get the squares of the edge lengths. + for (i = 0; i < 6; i++) edgelength[i] = dot(V[i], V[i]); + + // Calculate the longest and shortest edge length. + for (i = 0; i < 6; i++) { + if (i == 0) { + shortlen = longlen = edgelength[i]; + } else { + shortlen = edgelength[i] < shortlen ? edgelength[i] : shortlen; + longlen = edgelength[i] > longlen ? edgelength[i] : longlen; + } + if (edgelength[i] > longest) { + longest = edgelength[i]; + longestedgetet.tet = tetloop.tet; + } + if (edgelength[i] < shortest) { + shortest = edgelength[i]; + } + } + + // Set the matrix A = [V[0], V[1], V[2]]^T. + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) A[j][i] = V[j][i]; + } + + // Decompose A just once. + if (lu_decmp(A, 3, indx, &D, 0)) { + // Get the three faces normals. + for (j = 0; j < 3; j++) { + for (i = 0; i < 3; i++) rhs[i] = 0.0; + rhs[j] = 1.0; // Positive means the inside direction + lu_solve(A, 3, indx, rhs, 0); + for (i = 0; i < 3; i++) N[j][i] = rhs[i]; + } + // Get the fourth face normal by summing up the first three. + for (i = 0; i < 3; i++) N[3][i] = - N[0][i] - N[1][i] - N[2][i]; + // Get the radius of the circumsphere. + for (i = 0; i < 3; i++) rhs[i] = 0.5 * dot(V[i], V[i]); + lu_solve(A, 3, indx, rhs, 0); + cirradius = sqrt(dot(rhs, rhs)); + // Normalize the face normals. + for (i = 0; i < 4; i++) { + // H[i] is the inverse of height of its corresponding face. + H[i] = sqrt(dot(N[i], N[i])); + for (j = 0; j < 3; j++) N[i][j] /= H[i]; + } + // Get the radius of the inscribed sphere. + // insradius = 1.0 / (H[0] + H[1] + H[2] + H[3]); + // Get the biggest H[i] (corresponding to the smallest height). + minheightinv = H[0]; + for (i = 1; i < 4; i++) { + if (H[i] > minheightinv) minheightinv = H[i]; + } + } else { + // A nearly degenerated tet. + if (tetvol <= 0.0) { + printf(" !! Warning: A %s tet (%d,%d,%d,%d).\n", + tetvol < 0 ? "inverted" : "degenerated", pointmark(p[0]), + pointmark(p[1]), pointmark(p[2]), pointmark(p[3])); + // Skip it. + tetloop.tet = tetrahedrontraverse(); + continue; + } + // Calculate the four face normals. + facenormal(p[2], p[1], p[3], N[0], 1, NULL); + facenormal(p[0], p[2], p[3], N[1], 1, NULL); + facenormal(p[1], p[0], p[3], N[2], 1, NULL); + facenormal(p[0], p[1], p[2], N[3], 1, NULL); + // Normalize the face normals. + for (i = 0; i < 4; i++) { + // H[i] is the twice of the area of the face. + H[i] = sqrt(dot(N[i], N[i])); + for (j = 0; j < 3; j++) N[i][j] /= H[i]; + } + // Get the biggest H[i] / tetvol (corresponding to the smallest height). + minheightinv = (H[0] / tetvol); + for (i = 1; i < 4; i++) { + if ((H[i] / tetvol) > minheightinv) minheightinv = (H[i] / tetvol); + } + // Let the circumradius to be the half of its longest edge length. + cirradius = 0.5 * sqrt(longlen); + } + + // Get the dihedrals (in degree) at each edges. + j = 0; + for (i = 1; i < 4; i++) { + alldihed[j] = -dot(N[0], N[i]); // Edge cd, bd, bc. + if (alldihed[j] < -1.0) alldihed[j] = -1; // Rounding. + else if (alldihed[j] > 1.0) alldihed[j] = 1; + alldihed[j] = acos(alldihed[j]) / PI * 180.0; + j++; + } + for (i = 2; i < 4; i++) { + alldihed[j] = -dot(N[1], N[i]); // Edge ad, ac. + if (alldihed[j] < -1.0) alldihed[j] = -1; // Rounding. + else if (alldihed[j] > 1.0) alldihed[j] = 1; + alldihed[j] = acos(alldihed[j]) / PI * 180.0; + j++; + } + alldihed[j] = -dot(N[2], N[3]); // Edge ab. + if (alldihed[j] < -1.0) alldihed[j] = -1; // Rounding. + else if (alldihed[j] > 1.0) alldihed[j] = 1; + alldihed[j] = acos(alldihed[j]) / PI * 180.0; + + // Calculate the largest and smallest dihedral angles. + for (i = 0; i < 6; i++) { + if (i == 0) { + smalldiangle = bigdiangle = alldihed[i]; + } else { + smalldiangle = alldihed[i] < smalldiangle ? alldihed[i] : smalldiangle; + bigdiangle = alldihed[i] > bigdiangle ? alldihed[i] : bigdiangle; + } + if (alldihed[i] < smallestdiangle) { + smallestdiangle = alldihed[i]; + } + if (alldihed[i] > biggestdiangle) { + biggestdiangle = alldihed[i]; + } + // Accumulate the corresponding number in the dihedral angle histogram. + if (alldihed[i] < 5.0) { + tendegree = 0; + } else if (alldihed[i] >= 5.0 && alldihed[i] < 10.0) { + tendegree = 1; + } else if (alldihed[i] >= 80.0 && alldihed[i] < 110.0) { + tendegree = 9; // Angles between 80 to 110 degree are in one entry. + } else if (alldihed[i] >= 170.0 && alldihed[i] < 175.0) { + tendegree = 16; + } else if (alldihed[i] >= 175.0) { + tendegree = 17; + } else { + tendegree = (int) (alldihed[i] / 10.); + if (alldihed[i] < 80.0) { + tendegree++; // In the left column. + } else { + tendegree--; // In the right column. + } + } + dihedangletable[tendegree]++; + } + + + + // Calculate the largest and smallest face angles. + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, neightet); + // Only do the calulation once for a face. + if (((point) neightet.tet[7] == dummypoint) || + (tetloop.tet < neightet.tet)) { + p[0] = org(tetloop); + p[1] = dest(tetloop); + p[2] = apex(tetloop); + faceangle[0] = interiorangle(p[0], p[1], p[2], NULL); + faceangle[1] = interiorangle(p[1], p[2], p[0], NULL); + faceangle[2] = PI - (faceangle[0] + faceangle[1]); + // Translate angles into degrees. + for (i = 0; i < 3; i++) { + faceangle[i] = (faceangle[i] * 180.0) / PI; + } + // Calculate the largest and smallest face angles. + for (i = 0; i < 3; i++) { + if (i == 0) { + smallfaangle = bigfaangle = faceangle[i]; + } else { + smallfaangle = faceangle[i] < smallfaangle ? + faceangle[i] : smallfaangle; + bigfaangle = faceangle[i] > bigfaangle ? faceangle[i] : bigfaangle; + } + if (faceangle[i] < smallestfaangle) { + smallestfaangle = faceangle[i]; + } + if (faceangle[i] > biggestfaangle) { + biggestfaangle = faceangle[i]; + } + tendegree = (int) (faceangle[i] / 10.); + faceangletable[tendegree]++; + } + } + } + + // Calculate aspect ratio and radius-edge ratio for this element. + tetradius = cirradius / sqrt(shortlen); + if (tetradius < smallestradiusratio) { + smallestradiusratio = tetradius; + } + if (tetradius > biggestradiusratio) { + biggestradiusratio = tetradius; + biggestradiusratiotet.tet = tetloop.tet; + } + // tetaspect = sqrt(longlen) / (2.0 * insradius); + tetaspect = sqrt(longlen) * minheightinv; + // Remember the largest and smallest aspect ratio. + if (tetaspect < smallestratio) { + smallestratio = tetaspect; + } + if (tetaspect > biggestratio) { + biggestratio = tetaspect; + } + // Accumulate the corresponding number in the aspect ratio histogram. + aspectindex = 0; + while ((tetaspect > aspectratiotable[aspectindex]) && (aspectindex < 11)) { + aspectindex++; + } + aspecttable[aspectindex]++; + radiusindex = 0; + while ((tetradius > radiusratiotable[radiusindex]) && (radiusindex < 11)) { + radiusindex++; + } + radiustable[radiusindex]++; + + tetloop.tet = tetrahedrontraverse(); + } + + shortest = sqrt(shortest); + longest = sqrt(longest); + minaltitude = sqrt(minaltitude); + + printf(" Smallest volume: %16.5g | Largest volume: %16.5g\n", + smallestvolume, biggestvolume); + printf(" Shortest edge: %16.5g | Longest edge: %16.5g\n", + shortest, longest); + printf(" Smallest asp.ratio: %13.5g | Largest asp.ratio: %13.5g\n", + smallestratio, biggestratio); + sprintf(sbuf, "%.17g", biggestfaangle); + if (strlen(sbuf) > 8) { + sbuf[8] = '\0'; + } + printf(" Smallest facangle: %14.5g | Largest facangle: %s\n", + smallestfaangle, sbuf); + sprintf(sbuf, "%.17g", biggestdiangle); + if (strlen(sbuf) > 8) { + sbuf[8] = '\0'; + } + printf(" Smallest dihedral: %14.5g | Largest dihedral: %s\n\n", + smallestdiangle, sbuf); + + printf(" Aspect ratio histogram:\n"); + printf(" < %-6.6g : %8ld | %6.6g - %-6.6g : %8ld\n", + aspectratiotable[0], aspecttable[0], aspectratiotable[5], + aspectratiotable[6], aspecttable[6]); + for (i = 1; i < 5; i++) { + printf(" %6.6g - %-6.6g : %8ld | %6.6g - %-6.6g : %8ld\n", + aspectratiotable[i - 1], aspectratiotable[i], aspecttable[i], + aspectratiotable[i + 5], aspectratiotable[i + 6], + aspecttable[i + 6]); + } + printf(" %6.6g - %-6.6g : %8ld | %6.6g - : %8ld\n", + aspectratiotable[4], aspectratiotable[5], aspecttable[5], + aspectratiotable[10], aspecttable[11]); + printf(" (A tetrahedron's aspect ratio is its longest edge length"); + printf(" divided by its\n"); + printf(" smallest side height)\n\n"); + + printf(" Face angle histogram:\n"); + for (i = 0; i < 9; i++) { + printf(" %3d - %3d degrees: %8ld | %3d - %3d degrees: %8ld\n", + i * 10, i * 10 + 10, faceangletable[i], + i * 10 + 90, i * 10 + 100, faceangletable[i + 9]); + } + if (minfaceang != PI) { + printf(" Minimum input face angle is %g (degree).\n", + minfaceang / PI * 180.0); + } + printf("\n"); + + printf(" Dihedral angle histogram:\n"); + // Print the three two rows: + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", + 0, 5, dihedangletable[0], 80, 110, dihedangletable[9]); + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", + 5, 10, dihedangletable[1], 110, 120, dihedangletable[10]); + // Print the third to seventh rows. + for (i = 2; i < 7; i++) { + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", + (i - 1) * 10, (i - 1) * 10 + 10, dihedangletable[i], + (i - 1) * 10 + 110, (i - 1) * 10 + 120, dihedangletable[i + 9]); + } + // Print the last two rows. + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", + 60, 70, dihedangletable[7], 170, 175, dihedangletable[16]); + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", + 70, 80, dihedangletable[8], 175, 180, dihedangletable[17]); + if (minfacetdihed != PI) { + printf(" Minimum input dihedral angle is %g (degree).\n", + minfacetdihed / PI * 180.0); + } + printf("\n"); + printf("\n"); +} + + +//============================================================================// +// // +// memorystatistics() Report the memory usage. // +// // +//============================================================================// + +void tetgenmesh::memorystatistics() +{ + printf("Memory usage statistics:\n\n"); + + // Count the number of blocks of tetrahedra. + int tetblocks = 0; + tetrahedrons->pathblock = tetrahedrons->firstblock; + while (tetrahedrons->pathblock != NULL) { + tetblocks++; + tetrahedrons->pathblock = (void **) *(tetrahedrons->pathblock); + } + + // Calculate the total memory (in bytes) used by storing meshes. + unsigned long totalmeshmemory = 0l, totalt2shmemory = 0l; + totalmeshmemory = points->maxitems * points->itembytes + + tetrahedrons->maxitems * tetrahedrons->itembytes; + if (b->plc || b->refine) { + totalmeshmemory += (subfaces->maxitems * subfaces->itembytes + + subsegs->maxitems * subsegs->itembytes); + totalt2shmemory = (tet2subpool->maxitems * tet2subpool->itembytes + + tet2segpool->maxitems * tet2segpool->itembytes); + } + + unsigned long totalalgomemory = 0l; + totalalgomemory = cavetetlist->totalmemory + cavebdrylist->totalmemory + + caveoldtetlist->totalmemory + + flippool->maxitems * flippool->itembytes; + if (b->plc || b->refine) { + totalalgomemory += (subsegstack->totalmemory + subfacstack->totalmemory + + subvertstack->totalmemory + + caveshlist->totalmemory + caveshbdlist->totalmemory + + cavesegshlist->totalmemory + + cavetetshlist->totalmemory + + cavetetseglist->totalmemory + + caveencshlist->totalmemory + + caveencseglist->totalmemory + + cavetetvertlist->totalmemory + + unflipqueue->totalmemory); + } + + printf(" Maximum number of tetrahedra: %ld\n", tetrahedrons->maxitems); + printf(" Maximum number of tet blocks (blocksize = %d): %d\n", + b->tetrahedraperblock, tetblocks); + /* + if (b->plc || b->refine) { + printf(" Approximate memory for tetrahedral mesh (bytes): %ld\n", + totalmeshmemory); + + printf(" Approximate memory for extra pointers (bytes): %ld\n", + totalt2shmemory); + } else { + printf(" Approximate memory for tetrahedralization (bytes): %ld\n", + totalmeshmemory); + } + printf(" Approximate memory for algorithms (bytes): %ld\n", + totalalgomemory); + printf(" Approximate memory for working arrays (bytes): %ld\n", + totalworkmemory); + printf(" Approximate total used memory (bytes): %ld\n", + totalmeshmemory + totalt2shmemory + totalalgomemory + + totalworkmemory); + */ + if (b->plc || b->refine) { + printf(" Approximate memory for tetrahedral mesh (bytes): "); + printfcomma(totalmeshmemory); printf("\n"); + + printf(" Approximate memory for extra pointers (bytes): "); + printfcomma(totalt2shmemory); printf("\n"); + } else { + printf(" Approximate memory for tetrahedralization (bytes): "); + printfcomma(totalmeshmemory); printf("\n"); + } + printf(" Approximate memory for algorithms (bytes): "); + printfcomma(totalalgomemory); printf("\n"); + printf(" Approximate memory for working arrays (bytes): "); + printfcomma(totalworkmemory); printf("\n"); + printf(" Approximate total used memory (bytes): "); + printfcomma(totalmeshmemory + totalt2shmemory + totalalgomemory + + totalworkmemory); + printf("\n"); + + printf("\n"); +} + +//============================================================================// +// // +// statistics() Print all sorts of cool facts. // +// // +//============================================================================// + +void tetgenmesh::statistics() +{ + long tetnumber, facenumber; + + printf("\nStatistics:\n\n"); + printf(" Input points: %d\n", in->numberofpoints); + if (b->refine) { + printf(" Input tetrahedra: %d\n", in->numberoftetrahedra); + if (in->numberoftrifaces > 0) { + printf(" Input triangles: %d\n", in->numberoftrifaces); + } + if (in->numberofedges > 0) { + printf(" Input edges: %d\n", in->numberofedges); + } + } else if (b->plc) { + printf(" Input facets: %d\n", in->numberoffacets); + printf(" Input segments: %ld\n", insegments); + if (in->numberofedges > 0) { + printf(" Input edges: %d\n", in->numberofedges); + } + printf(" Input holes: %d\n", in->numberofholes); + printf(" Input regions: %d\n", in->numberofregions); + } + + tetnumber = tetrahedrons->items - hullsize; + facenumber = (tetnumber * 4l + hullsize) / 2l; + + if (b->weighted) { // -w option + printf("\n Mesh points: %ld\n", points->items - nonregularcount); + } else { + printf("\n Mesh points: %ld\n", points->items); + } + printf(" Mesh tetrahedra: %ld\n", tetnumber); + printf(" Mesh faces: %ld\n", facenumber); + if (meshedges > 0l) { + printf(" Mesh edges: %ld\n", meshedges); + } else { + if (!nonconvex) { + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + meshedges = vsize + facenumber - tetnumber - 1; + printf(" Mesh edges: %ld\n", meshedges); + } + } + + if (b->plc || b->refine) { + printf(" Mesh faces on exterior boundary: %ld\n", hullsize); + if (meshhulledges > 0l) { + printf(" Mesh edges on exterior boundary: %ld\n", meshhulledges); + } + printf(" Mesh faces on input facets: %ld\n", subfaces->items); + printf(" Mesh edges on input segments: %ld\n", subsegs->items); + if (st_facref_count > 0l) { + printf(" Steiner points on input facets: %ld\n", st_facref_count); + } + if (st_segref_count > 0l) { + printf(" Steiner points on input segments: %ld\n", st_segref_count); + } + if (st_volref_count > 0l) { + printf(" Steiner points inside domain: %ld\n", st_volref_count); + } + } else { + printf(" Convex hull faces: %ld\n", hullsize); + if (meshhulledges > 0l) { + printf(" Convex hull edges: %ld\n", meshhulledges); + } + } + if (b->weighted) { // -w option + printf(" Skipped non-regular points: %ld\n", nonregularcount); + } + printf("\n"); + + + if (b->verbose > 0) { + if (b->plc || b->refine) { // -p or -r + if (tetrahedrons->items > 0l) { + qualitystatistics(); + } + } + if (tetrahedrons->items > 0l) { + memorystatistics(); + } + } +} + +// // +// // +//== meshstat_cxx ============================================================// + +//== output_cxx ==============================================================// +// // +// // + +//============================================================================// +// // +// jettisonnodes() Jettison unused or duplicated vertices. // +// // +// Unused points are those input points which are outside the mesh domain or // +// have no connection (isolated) to the mesh. Duplicated points exist for // +// example if the input PLC is read from a .stl mesh file (marked during the // +// Delaunay tetrahedralization step. This routine remove these points from // +// points list. All existing points are reindexed. // +// // +//============================================================================// + +void tetgenmesh::jettisonnodes() +{ + point pointloop; + bool jetflag; + int oldidx, newidx; + int remcount; + + if (!b->quiet) { + printf("Jettisoning redundant points.\n"); + } + + points->traversalinit(); + pointloop = pointtraverse(); + oldidx = newidx = 0; // in->firstnumber; + remcount = 0; + while (pointloop != (point) NULL) { + jetflag = (pointtype(pointloop) == DUPLICATEDVERTEX) || + (pointtype(pointloop) == UNUSEDVERTEX); + if (jetflag) { + // It is a duplicated or unused point, delete it. + pointdealloc(pointloop); + remcount++; + } else { + // Re-index it. + setpointmark(pointloop, newidx + in->firstnumber); + if (in->pointmarkerlist != (int *) NULL) { + if (oldidx < in->numberofpoints) { + // Re-index the point marker as well. + in->pointmarkerlist[newidx] = in->pointmarkerlist[oldidx]; + } + } + newidx++; + } + oldidx++; + pointloop = pointtraverse(); + } + if (b->verbose) { + printf(" %ld duplicated vertices are removed.\n", dupverts); + printf(" %ld unused vertices are removed.\n", unuverts); + } + dupverts = 0l; + unuverts = 0l; + + // The following line ensures that dead items in the pool of nodes cannot + // be allocated for the new created nodes. This ensures that the input + // nodes will occur earlier in the output files, and have lower indices. + points->deaditemstack = (void *) NULL; +} + +//============================================================================// +// // +// highorder() Create extra nodes for quadratic subparametric elements. // +// // +// 'highordertable' is an array (size = numberoftetrahedra * 6) for storing // +// high-order nodes of each tetrahedron. This routine is used only when -o2 // +// switch is used. // +// // +//============================================================================// + +void tetgenmesh::highorder() +{ + triface tetloop, worktet, spintet; + point *extralist, *adjextralist; + point torg, tdest, newpoint; + int highorderindex; + int t1ver; + int i, j; + + if (!b->quiet) { + printf("Adding vertices for second-order tetrahedra.\n"); + } + + // Initialize the 'highordertable'. + point *highordertable = new point[tetrahedrons->items * 6]; + if (highordertable == (point *) NULL) { + terminatetetgen(this, 1); + } + + // This will overwrite the slot for element markers. + highorderindex = 11; + + // The following line ensures that dead items in the pool of nodes cannot + // be allocated for the extra nodes associated with high order elements. + // This ensures that the primary nodes (at the corners of elements) will + // occur earlier in the output files, and have lower indices, than the + // extra nodes. + points->deaditemstack = (void *) NULL; + + // Assign an entry for each tetrahedron to find its extra nodes. At the + // mean while, initialize all extra nodes be NULL. + i = 0; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + tetloop.tet[highorderindex] = (tetrahedron) &highordertable[i]; + for (j = 0; j < 6; j++) { + highordertable[i + j] = (point) NULL; + } + i += 6; + tetloop.tet = tetrahedrontraverse(); + } + + // To create a unique node on each edge. Loop over all tetrahedra, and + // look at the six edges of each tetrahedron. If the extra node in + // the tetrahedron corresponding to this edge is NULL, create a node + // for this edge, at the same time, set the new node into the extra + // node lists of all other tetrahedra sharing this edge. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + // Get the list of extra nodes. + extralist = (point *) tetloop.tet[highorderindex]; + worktet.tet = tetloop.tet; + for (i = 0; i < 6; i++) { + if (extralist[i] == (point) NULL) { + // Go to the ith-edge. + worktet.ver = edge2ver[i]; + // Create a new point in the middle of this edge. + torg = org(worktet); + tdest = dest(worktet); + makepoint(&newpoint, FREEVOLVERTEX); + for (j = 0; j < 3 + numpointattrib; j++) { + newpoint[j] = 0.5 * (torg[j] + tdest[j]); + } + // Interpolate its metrics. + for (j = 0; j < in->numberofpointmtrs; j++) { + newpoint[pointmtrindex + j] = + 0.5 * (torg[pointmtrindex + j] + tdest[pointmtrindex + j]); + } + // Set this point into all extra node lists at this edge. + spintet = worktet; + while (1) { + if (!ishulltet(spintet)) { + adjextralist = (point *) spintet.tet[highorderindex]; + adjextralist[ver2edge[spintet.ver]] = newpoint; + } + fnextself(spintet); + if (spintet.tet == worktet.tet) break; + } + } // if (!extralist[i]) + } // i + tetloop.tet = tetrahedrontraverse(); + } + + delete [] highordertable; +} + +//============================================================================// +// // +// indexelements() Index all tetrahedra. // +// // +// Many output functions require that the tetrahedra are indexed. This // +// routine is called when -E option is used. // +// // +//============================================================================// + +void tetgenmesh::indexelements() +{ + triface worktet; + int eindex = b->zeroindex ? 0 : in->firstnumber; // firstindex; + tetrahedrons->traversalinit(); + worktet.tet = tetrahedrontraverse(); + while (worktet.tet != NULL) { + setelemindex(worktet.tet, eindex); + eindex++; + if (b->metric) { // -m option + // Update the point-to-tet map, so that every point is pointing + // to a real tet, not a fictious one. Used by .p2t file. + tetrahedron tptr = encode(worktet); + for (int i = 0; i < 4; i++) { + setpoint2tet((point) (worktet.tet[4 + i]), tptr); + } + } + worktet.tet = tetrahedrontraverse(); + } +} + +//============================================================================// +// // +// numberedges() Count the number of edges, save in "meshedges". // +// // +// This routine is called when '-p' or '-r', and '-E' options are used. The // +// total number of edges depends on the genus of the input surface mesh. // +// // +// NOTE: This routine must be called after outelements(). So all elements // +// have been indexed. // +// // +//============================================================================// + +void tetgenmesh::numberedges() +{ + triface worktet, spintet; + int ishulledge; + int t1ver; + int i; + + meshedges = meshhulledges = 0l; + + tetrahedrons->traversalinit(); + worktet.tet = tetrahedrontraverse(); + while (worktet.tet != NULL) { + for (i = 0; i < 6; i++) { + worktet.ver = edge2ver[i]; + ishulledge = 0; + fnext(worktet, spintet); + do { + if (!ishulltet(spintet)) { + if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; + } else { + ishulledge = 1; + } + fnextself(spintet); + } while (spintet.tet != worktet.tet); + if (spintet.tet == worktet.tet) { + meshedges++; + if (ishulledge) meshhulledges++; + } + } + infect(worktet); + worktet.tet = tetrahedrontraverse(); + } +} + +//============================================================================// +// // +// outnodes() Output the points to a .node file or a tetgenio structure. // +// // +// Note: each point has already been numbered on input (the first index is // +// 'in->firstnumber'). // +// // +//============================================================================// + +void tetgenmesh::outnodes(tetgenio* out) +{ + FILE *outfile = NULL; + char outnodefilename[FILENAMESIZE]; + face parentsh; + point pointloop; + int nextras, bmark, marker = 0, weightDT = 0; + int coordindex, attribindex; + int pointnumber, firstindex; + int index, i; + + if (out == (tetgenio *) NULL) { + strcpy(outnodefilename, b->outfilename); + strcat(outnodefilename, ".node"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outnodefilename); + } else { + printf("Writing nodes.\n"); + } + } + + nextras = numpointattrib; + if (b->weighted) { // -w + if (b->weighted_param == 0) weightDT = 1; // Weighted DT. + } + + bmark = !b->nobound && in->pointmarkerlist; + + if (out == (tetgenio *) NULL) { + outfile = fopen(outnodefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outnodefilename); + terminatetetgen(this, 1); + } + // Number of points, number of dimensions, number of point attributes, + // and number of boundary markers (zero or one). + //fprintf(outfile, "%ld %d %d %d\n", points->items, 3, nextras, bmark); + // [2020-01-16] added save flag (for viewing Steiner points). + //fprintf(outfile, "%ld %d %d %d 1\n", points->items, 3, nextras, bmark); + fprintf(outfile, "%ld %d %d %d\n", points->items, 3, nextras, bmark); + } else { + // Allocate space for 'pointlist'; + out->pointlist = new REAL[points->items * 3]; + if (out->pointlist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + // Allocate space for 'pointattributelist' if necessary; + if (nextras > 0) { + out->pointattributelist = new REAL[points->items * nextras]; + if (out->pointattributelist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + // Allocate space for 'pointmarkerlist' if necessary; + if (bmark) { + out->pointmarkerlist = new int[points->items]; + if (out->pointmarkerlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + if (b->psc) { + out->pointparamlist = new tetgenio::pointparam[points->items]; + if (out->pointparamlist == NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + out->numberofpoints = points->items; + out->numberofpointattributes = nextras; + coordindex = 0; + attribindex = 0; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + + points->traversalinit(); + pointloop = pointtraverse(); + pointnumber = firstindex; // in->firstnumber; + index = 0; + while (pointloop != (point) NULL) { + if (bmark) { + // Default the vertex has a zero marker. + marker = 0; + // Is it an input vertex? + if (index < in->numberofpoints) { + // Input point's marker is directly copied to output. + marker = in->pointmarkerlist[index]; + } else { + if ((pointtype(pointloop) == FREESEGVERTEX) || + (pointtype(pointloop) == FREEFACETVERTEX)) { + sdecode(point2sh(pointloop), parentsh); + if (parentsh.sh != NULL) { + marker = shellmark(parentsh); + } + } // if (pointtype(...)) + } + } + if (out == (tetgenio *) NULL) { + // Point number, x, y and z coordinates. + fprintf(outfile, "%4d %.17g %.17g %.17g", pointnumber, + pointloop[0], pointloop[1], pointloop[2]); + for (i = 0; i < nextras; i++) { + // Write an attribute. + if ((i == 0) && weightDT) { + fprintf(outfile, " %.17g", pointloop[0] * pointloop[0] + + pointloop[1] * pointloop[1] + pointloop[2] * pointloop[2] + - pointloop[3 + i]); + } else { + fprintf(outfile, " %.17g", pointloop[3 + i]); + } + } + if (bmark) { + // Write the boundary marker. + fprintf(outfile, " %d", marker); + } + if (b->psc) { + fprintf(outfile, " %.8g %.8g %d", pointgeomuv(pointloop, 0), + pointgeomuv(pointloop, 1), pointgeomtag(pointloop)); + if (pointtype(pointloop) == RIDGEVERTEX) { + fprintf(outfile, " 0"); + //} else if (pointtype(pointloop) == ACUTEVERTEX) { + // fprintf(outfile, " 0"); + } else if (pointtype(pointloop) == FREESEGVERTEX) { + fprintf(outfile, " 1"); + } else if (pointtype(pointloop) == FREEFACETVERTEX) { + fprintf(outfile, " 2"); + } else if (pointtype(pointloop) == FREEVOLVERTEX) { + fprintf(outfile, " 3"); + } else { + fprintf(outfile, " -1"); // Unknown type. + } + } + // // [2020-01-16] Write vertex flags + // if (pointnumber > in->numberofpoints) { + // fprintf(outfile, " 16"); // A Steiner point. + // } else { + // fprintf(outfile, " 0"); + // } + fprintf(outfile, "\n"); + } else { + // X, y, and z coordinates. + out->pointlist[coordindex++] = pointloop[0]; + out->pointlist[coordindex++] = pointloop[1]; + out->pointlist[coordindex++] = pointloop[2]; + // Point attributes. + for (i = 0; i < nextras; i++) { + // Output an attribute. + if ((i == 0) && weightDT) { + out->pointattributelist[attribindex++] = + pointloop[0] * pointloop[0] + pointloop[1] * pointloop[1] + + pointloop[2] * pointloop[2] - pointloop[3 + i]; + } else { + out->pointattributelist[attribindex++] = pointloop[3 + i]; + } + } + if (bmark) { + // Output the boundary marker. + out->pointmarkerlist[index] = marker; + } + if (b->psc) { + out->pointparamlist[index].uv[0] = pointgeomuv(pointloop, 0); + out->pointparamlist[index].uv[1] = pointgeomuv(pointloop, 1); + out->pointparamlist[index].tag = pointgeomtag(pointloop); + if (pointtype(pointloop) == RIDGEVERTEX) { + out->pointparamlist[index].type = 0; + //} else if (pointtype(pointloop) == ACUTEVERTEX) { + // out->pointparamlist[index].type = 0; + } else if (pointtype(pointloop) == FREESEGVERTEX) { + out->pointparamlist[index].type = 1; + } else if (pointtype(pointloop) == FREEFACETVERTEX) { + out->pointparamlist[index].type = 2; + } else if (pointtype(pointloop) == FREEVOLVERTEX) { + out->pointparamlist[index].type = 3; + } else { + out->pointparamlist[index].type = -1; // Unknown type. + } + } + } + pointloop = pointtraverse(); + pointnumber++; + index++; + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outmetrics() Output the metric to a file (*.mtr) or a tetgenio obj. // +// // +//============================================================================// + +void tetgenmesh::outmetrics(tetgenio* out) +{ + FILE *outfile = NULL; + char outmtrfilename[FILENAMESIZE]; + point ptloop; + int mtrindex = 0; + int i; + int msize = (sizeoftensor - useinsertradius); + if (msize == 0) { + return; + } + + if (out == (tetgenio *) NULL) { + strcpy(outmtrfilename, b->outfilename); + strcat(outmtrfilename, ".mtr"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outmtrfilename); + } else { + printf("Writing metrics.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outmtrfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outmtrfilename); + terminatetetgen(this, 3); + } + // Number of points, number of point metrices, + fprintf(outfile, "%ld %d\n", points->items, msize); + } else { + // Allocate space for 'pointmtrlist'. + out->numberofpointmtrs = msize; + out->pointmtrlist = new REAL[points->items * msize]; + if (out->pointmtrlist == (REAL *) NULL) { + terminatetetgen(this, 1); + } + } + + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != (point) NULL) { + if (out == (tetgenio *) NULL) { + for (i = 0; i < msize; i++) { + fprintf(outfile, " %-16.8e", ptloop[pointmtrindex + i]); + } + fprintf(outfile, "\n"); + } else { + for (i = 0; i < msize; i++) { + out->pointmtrlist[mtrindex++] = ptloop[pointmtrindex + i]; + } + } + ptloop = pointtraverse(); + } + + // Output the point-to-tet map. + if (out == (tetgenio *) NULL) { + strcpy(outmtrfilename, b->outfilename); + strcat(outmtrfilename, ".p2t"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outmtrfilename); + } else { + printf("Writing point-to-tet map.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outmtrfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outmtrfilename); + terminatetetgen(this, 3); + } + // Number of points, + //fprintf(outfile, "%ld\n", points->items); + } else { + // Allocate space for 'point2tetlist'. + out->point2tetlist = new int[points->items]; + if (out->point2tetlist == (int *) NULL) { + terminatetetgen(this, 1); + } + } + + // The list of tetrahedra must be indexed. + if (bgm != NULL) { + bgm->indexelements(); + } + // Determine the first index (0 or 1). + int firstindex = b->zeroindex ? 0 : in->firstnumber; + int pointindex = firstindex; + i = 0; + + triface parenttet; + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != (point) NULL) { + if (bgm != NULL) { + bgm->decode(point2bgmtet(ptloop), parenttet); + } else { + decode(point2tet(ptloop), parenttet); + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%d %d\n", pointindex, elemindex(parenttet.tet)); + } else { + out->point2tetlist[i] = elemindex(parenttet.tet); + } + pointindex++; + i++; + ptloop = pointtraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outelements() Output the tetrahedra to an .ele file or a tetgenio // +// structure. // +// // +// This routine also indexes all tetrahedra (exclusing hull tets) (from in-> // +// firstnumber). The total number of mesh edges is counted in 'meshedges'. // +// // +//============================================================================// + +void tetgenmesh::outelements(tetgenio* out) +{ + FILE *outfile = NULL; + char outelefilename[FILENAMESIZE]; + tetrahedron* tptr; + point p1, p2, p3, p4; + point *extralist; + REAL *talist = NULL; + int *tlist = NULL; + long ntets; + int firstindex, shift; + int pointindex, attribindex; + int highorderindex = 11; + int elementnumber; + int eextras; + int i; + + if (out == (tetgenio *) NULL) { + strcpy(outelefilename, b->outfilename); + strcat(outelefilename, ".ele"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outelefilename); + } else { + printf("Writing elements.\n"); + } + } + + // The number of tets excluding hull tets. + ntets = tetrahedrons->items - hullsize; + + eextras = numelemattrib; + if (out == (tetgenio *) NULL) { + outfile = fopen(outelefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outelefilename); + terminatetetgen(this, 1); + } + // Number of tetras, points per tetra, attributes per tetra. + fprintf(outfile, "%ld %d %d\n", ntets, b->order == 1 ? 4 : 10, eextras); + } else { + // Allocate memory for output tetrahedra. + out->tetrahedronlist = new int[ntets * (b->order == 1 ? 4 : 10)]; + if (out->tetrahedronlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + // Allocate memory for output tetrahedron attributes if necessary. + if (eextras > 0) { + out->tetrahedronattributelist = new REAL[ntets * eextras]; + if (out->tetrahedronattributelist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + out->numberoftetrahedra = ntets; + out->numberofcorners = b->order == 1 ? 4 : 10; + out->numberoftetrahedronattributes = eextras; + tlist = out->tetrahedronlist; + talist = out->tetrahedronattributelist; + pointindex = 0; + attribindex = 0; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shift. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + tetrahedrons->traversalinit(); + tptr = tetrahedrontraverse(); + elementnumber = firstindex; // in->firstnumber; + while (tptr != (tetrahedron *) NULL) { + if (!b->reversetetori) { + p1 = (point) tptr[4]; + p2 = (point) tptr[5]; + } else { + p1 = (point) tptr[5]; + p2 = (point) tptr[4]; + } + p3 = (point) tptr[6]; + p4 = (point) tptr[7]; + if (out == (tetgenio *) NULL) { + // Tetrahedron number, indices for four points. + fprintf(outfile, "%5d %5d %5d %5d %5d", elementnumber, + pointmark(p1) - shift, pointmark(p2) - shift, + pointmark(p3) - shift, pointmark(p4) - shift); + if (b->order == 2) { + extralist = (point *) tptr[highorderindex]; + // indices for six extra points. + fprintf(outfile, " %5d %5d %5d %5d %5d %5d", + pointmark(extralist[0]) - shift, pointmark(extralist[1]) - shift, + pointmark(extralist[2]) - shift, pointmark(extralist[3]) - shift, + pointmark(extralist[4]) - shift, pointmark(extralist[5]) - shift); + } + for (i = 0; i < eextras; i++) { + fprintf(outfile, " %.17g", elemattribute(tptr, i)); + } + fprintf(outfile, "\n"); + } else { + tlist[pointindex++] = pointmark(p1) - shift; + tlist[pointindex++] = pointmark(p2) - shift; + tlist[pointindex++] = pointmark(p3) - shift; + tlist[pointindex++] = pointmark(p4) - shift; + if (b->order == 2) { + extralist = (point *) tptr[highorderindex]; + tlist[pointindex++] = pointmark(extralist[0]) - shift; + tlist[pointindex++] = pointmark(extralist[1]) - shift; + tlist[pointindex++] = pointmark(extralist[2]) - shift; + tlist[pointindex++] = pointmark(extralist[3]) - shift; + tlist[pointindex++] = pointmark(extralist[4]) - shift; + tlist[pointindex++] = pointmark(extralist[5]) - shift; + } + for (i = 0; i < eextras; i++) { + talist[attribindex++] = elemattribute(tptr, i); + } + } + // Remember the index of this element (for counting edges). + setelemindex(tptr, elementnumber); + if (b->metric) { // -m option + // Update the point-to-tet map, so that every point is pointing + // to a real tet, not a fictious one. Used by .p2t file. + for (int i = 0; i < 4; i++) { + setpoint2tet((point) (tptr[4 + i]), (tetrahedron) tptr); + } + } + tptr = tetrahedrontraverse(); + elementnumber++; + } + + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outfaces() Output all faces to a .face file or a tetgenio object. // +// // +// The total number of faces f can be calculated as following: Let t be the // +// total number of tets. Since each tet has 4 faces, the number t * 4 counts // +// each interior face twice and each hull face once. So f = (t * 4 + h) / 2, // +// where h is the total number of hull faces (which is known). // +// // +//============================================================================// + +void tetgenmesh::outfaces(tetgenio* out) +{ + FILE *outfile = NULL; + char facefilename[FILENAMESIZE]; + triface tface, tsymface; + face checkmark; + point torg, tdest, tapex; + long ntets, faces; + int *elist = NULL, *emlist = NULL; + int neigh1 = 0, neigh2 = 0; + int marker = 0; + int firstindex, shift; + int facenumber; + int index = 0; + + // For -o2 option. + triface workface; + point *extralist, pp[3] = {0,0,0}; + int highorderindex = 11; + int o2index = 0, i; + + // For -nn option. + int *tet2facelist = NULL; + int tidx; + + if (out == (tetgenio *) NULL) { + strcpy(facefilename, b->outfilename); + strcat(facefilename, ".face"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", facefilename); + } else { + printf("Writing faces.\n"); + } + } + + ntets = tetrahedrons->items - hullsize; + faces = (ntets * 4l + hullsize) / 2l; + + if (out == (tetgenio *) NULL) { + outfile = fopen(facefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", facefilename); + terminatetetgen(this, 1); + } + fprintf(outfile, "%ld %d\n", faces, !b->nobound); + } else { + // Allocate memory for 'trifacelist'. + out->trifacelist = new int[faces * 3]; + if (out->trifacelist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2facelist = new int[faces * 3]; + } + // Allocate memory for 'trifacemarkerlist' if necessary. + if (!b->nobound) { + out->trifacemarkerlist = new int[faces]; + if (out->trifacemarkerlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + if (b->neighout > 1) { + // '-nn' switch. + out->face2tetlist = new int[faces * 2]; + if (out->face2tetlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + } + out->numberoftrifaces = faces; + elist = out->trifacelist; + emlist = out->trifacemarkerlist; + } + + if (b->neighout > 1) { // -nn option + // Output the tetrahedron-to-face map. + tet2facelist = new int[ntets * 4]; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + tetrahedrons->traversalinit(); + tface.tet = tetrahedrontraverse(); + facenumber = firstindex; // in->firstnumber; + // To loop over the set of faces, loop over all tetrahedra, and look at + // the four faces of each one. If its adjacent tet is a hull tet, + // operate on the face, otherwise, operate on the face only if the + // current tet has a smaller index than its neighbor. + while (tface.tet != (tetrahedron *) NULL) { + for (tface.ver = 0; tface.ver < 4; tface.ver ++) { + fsym(tface, tsymface); + if (ishulltet(tsymface) || + (elemindex(tface.tet) < elemindex(tsymface.tet))) { + torg = org(tface); + tdest = dest(tface); + tapex = apex(tface); + if (b->order == 2) { // -o2 + // Get the three extra vertices on edges. + extralist = (point *) (tface.tet[highorderindex]); + // The extra vertices are on edges opposite the corners. + enext(tface, workface); + for (i = 0; i < 3; i++) { + pp[i] = extralist[ver2edge[workface.ver]]; + enextself(workface); + } + } + if (!b->nobound) { + // Get the boundary marker of this face. + if (b->plc || b->refine) { + // Shell face is used. + tspivot(tface, checkmark); + if (checkmark.sh == NULL) { + marker = 0; // It is an inner face. It's marker is 0. + } else { + marker = shellmark(checkmark); + } + } else { + // Shell face is not used, only distinguish outer and inner face. + marker = (int) ishulltet(tsymface); + } + } + if (b->neighout > 1) { + // '-nn' switch. Output adjacent tets indices. + if (!ishulltet(tface)) { + neigh1 = elemindex(tface.tet); + } else { + neigh1 = -1; + } + if (!ishulltet(tsymface)) { + neigh2 = elemindex(tsymface.tet); + } else { + neigh2 = -1; + } + // Fill the tetrahedron-to-face map. + tidx = elemindex(tface.tet) - firstindex; + tet2facelist[tidx * 4 + tface.ver] = facenumber; + if (!ishulltet(tsymface)) { + tidx = elemindex(tsymface.tet) - firstindex; + tet2facelist[tidx * 4 + (tsymface.ver & 3)] = facenumber; + } + } + if (out == (tetgenio *) NULL) { + // Face number, indices of three vertices. + fprintf(outfile, "%5d %4d %4d %4d", facenumber, + pointmark(torg) - shift, pointmark(tdest) - shift, + pointmark(tapex) - shift); + if (b->order == 2) { // -o2 + fprintf(outfile, " %4d %4d %4d", pointmark(pp[0]) - shift, + pointmark(pp[1]) - shift, pointmark(pp[2]) - shift); + } + if (!b->nobound) { + // Output a boundary marker. + fprintf(outfile, " %d", marker); + } + if (b->neighout > 1) { + fprintf(outfile, " %5d %5d", neigh1, neigh2); + } + fprintf(outfile, "\n"); + } else { + // Output indices of three vertices. + elist[index++] = pointmark(torg) - shift; + elist[index++] = pointmark(tdest) - shift; + elist[index++] = pointmark(tapex) - shift; + if (b->order == 2) { // -o2 + out->o2facelist[o2index++] = pointmark(pp[0]) - shift; + out->o2facelist[o2index++] = pointmark(pp[1]) - shift; + out->o2facelist[o2index++] = pointmark(pp[2]) - shift; + } + if (!b->nobound) { + emlist[facenumber - in->firstnumber] = marker; + } + if (b->neighout > 1) { + out->face2tetlist[(facenumber - in->firstnumber) * 2] = neigh1; + out->face2tetlist[(facenumber - in->firstnumber) * 2 + 1] = neigh2; + } + } + facenumber++; + } + } + tface.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } + + if (b->neighout > 1) { // -nn option + // Output the tetrahedron-to-face map. + if (out == (tetgenio *) NULL) { + strcpy(facefilename, b->outfilename); + strcat(facefilename, ".t2f"); + } + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", facefilename); + } else { + printf("Writing tetrahedron-to-face map.\n"); + } + } + if (out == (tetgenio *) NULL) { + outfile = fopen(facefilename, "w"); + for (tidx = 0; tidx < ntets; tidx++) { + index = tidx * 4; + fprintf(outfile, "%4d %d %d %d %d\n", tidx + in->firstnumber, + tet2facelist[index], tet2facelist[index+1], + tet2facelist[index+2], tet2facelist[index+3]); + } + fclose(outfile); + delete [] tet2facelist; + } else { + // Simply copy the address of the list to the output. + out->tet2facelist = tet2facelist; + } + } +} + +//============================================================================// +// // +// outhullfaces() Output hull faces to a .face file or a tetgenio object. // +// // +// The normal of each face is pointing to the outside of the domain. // +// // +//============================================================================// + +void tetgenmesh::outhullfaces(tetgenio* out) +{ + FILE *outfile = NULL; + char facefilename[FILENAMESIZE]; + triface hulltet; + point torg, tdest, tapex; + int *elist = NULL; + int firstindex, shift; + int facenumber; + int index; + + if (out == (tetgenio *) NULL) { + strcpy(facefilename, b->outfilename); + strcat(facefilename, ".face"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", facefilename); + } else { + printf("Writing faces.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(facefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", facefilename); + terminatetetgen(this, 1); + } + fprintf(outfile, "%ld 0\n", hullsize); + } else { + // Allocate memory for 'trifacelist'. + out->trifacelist = new int[hullsize * 3]; + if (out->trifacelist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + out->numberoftrifaces = hullsize; + elist = out->trifacelist; + index = 0; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + tetrahedrons->traversalinit(); + hulltet.tet = alltetrahedrontraverse(); + facenumber = firstindex; + while (hulltet.tet != (tetrahedron *) NULL) { + if (ishulltet(hulltet)) { + torg = (point) hulltet.tet[4]; + tdest = (point) hulltet.tet[5]; + tapex = (point) hulltet.tet[6]; + if (out == (tetgenio *) NULL) { + // Face number, indices of three vertices. + fprintf(outfile, "%5d %4d %4d %4d", facenumber, + pointmark(torg) - shift, pointmark(tdest) - shift, + pointmark(tapex) - shift); + fprintf(outfile, "\n"); + } else { + // Output indices of three vertices. + elist[index++] = pointmark(torg) - shift; + elist[index++] = pointmark(tdest) - shift; + elist[index++] = pointmark(tapex) - shift; + } + facenumber++; + } + hulltet.tet = alltetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outsubfaces() Output subfaces (i.e. boundary faces) to a .face file or // +// a tetgenio structure. // +// // +// The boundary faces are found in 'subfaces'. For listing triangle vertices // +// in the same sense for all triangles in the mesh, the direction determined // +// by right-hand rule is pointer to the inside of the volume. // +// // +//============================================================================// + +void tetgenmesh::outsubfaces(tetgenio* out) +{ + FILE *outfile = NULL; + char facefilename[FILENAMESIZE]; + int *elist = NULL; + int *emlist = NULL; + int index = 0, index1 = 0, index2 = 0; + triface abuttingtet; + face faceloop; + point torg, tdest, tapex; + int marker = 0; + int firstindex, shift; + int neigh1 = 0, neigh2 = 0; + int facenumber; + + // For -o2 option. + triface workface; + point *extralist, pp[3] = {0,0,0}; + int highorderindex = 11; + int o2index = 0, i; + + int t1ver; // used by fsymself() + + if (out == (tetgenio *) NULL) { + strcpy(facefilename, b->outfilename); + strcat(facefilename, ".face"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", facefilename); + } else { + printf("Writing faces.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(facefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", facefilename); + terminatetetgen(this, 3); + } + // Number of subfaces. + fprintf(outfile, "%ld %d\n", subfaces->items, !b->nobound); + } else { + // Allocate memory for 'trifacelist'. + out->trifacelist = new int[subfaces->items * 3]; + if (out->trifacelist == (int *) NULL) { + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2facelist = new int[subfaces->items * 3]; + } + if (!b->nobound) { + // Allocate memory for 'trifacemarkerlist'. + out->trifacemarkerlist = new int[subfaces->items]; + if (out->trifacemarkerlist == (int *) NULL) { + terminatetetgen(this, 1); + } + } + if (b->neighout > 1) { + // '-nn' switch. + out->face2tetlist = new int[subfaces->items * 2]; + if (out->face2tetlist == (int *) NULL) { + terminatetetgen(this, 1); + } + } + out->numberoftrifaces = subfaces->items; + elist = out->trifacelist; + emlist = out->trifacemarkerlist; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + subfaces->traversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + facenumber = firstindex; // in->firstnumber; + while (faceloop.sh != (shellface *) NULL) { + stpivot(faceloop, abuttingtet); + // If there is a tetrahedron containing this subface, orient it so + // that the normal of this face points to inside of the volume by + // right-hand rule. + if (abuttingtet.tet != NULL) { + if (ishulltet(abuttingtet)) { + fsymself(abuttingtet); + } + } + if (abuttingtet.tet != NULL) { + torg = org(abuttingtet); + tdest = dest(abuttingtet); + tapex = apex(abuttingtet); + if (b->order == 2) { // -o2 + // Get the three extra vertices on edges. + extralist = (point *) (abuttingtet.tet[highorderindex]); + workface = abuttingtet; + for (i = 0; i < 3; i++) { + pp[i] = extralist[ver2edge[workface.ver]]; + enextself(workface); + } + } + } else { + // This may happen when only a surface mesh be generated. + torg = sorg(faceloop); + tdest = sdest(faceloop); + tapex = sapex(faceloop); + if (b->order == 2) { // -o2 + // There is no extra node list available. + pp[0] = torg; + pp[1] = tdest; + pp[2] = tapex; + } + } + if (!b->nobound) { + marker = shellmark(faceloop); + } + if (b->neighout > 1) { + // '-nn' switch. Output adjacent tets indices. + neigh1 = -1; + neigh2 = -1; + stpivot(faceloop, abuttingtet); + if (abuttingtet.tet != NULL) { + if (!ishulltet(abuttingtet)) { + neigh1 = elemindex(abuttingtet.tet); + } + fsymself(abuttingtet); + if (!ishulltet(abuttingtet)) { + neigh2 = elemindex(abuttingtet.tet); + } + } + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%5d %4d %4d %4d", facenumber, + pointmark(torg) - shift, pointmark(tdest) - shift, + pointmark(tapex) - shift); + if (b->order == 2) { // -o2 + fprintf(outfile, " %4d %4d %4d", pointmark(pp[0]) - shift, + pointmark(pp[1]) - shift, pointmark(pp[2]) - shift); + } + if (!b->nobound) { + fprintf(outfile, " %d", marker); + } + if (b->neighout > 1) { + fprintf(outfile, " %5d %5d", neigh1, neigh2); + } + fprintf(outfile, "\n"); + } else { + // Output three vertices of this face; + elist[index++] = pointmark(torg) - shift; + elist[index++] = pointmark(tdest) - shift; + elist[index++] = pointmark(tapex) - shift; + if (b->order == 2) { // -o2 + out->o2facelist[o2index++] = pointmark(pp[0]) - shift; + out->o2facelist[o2index++] = pointmark(pp[1]) - shift; + out->o2facelist[o2index++] = pointmark(pp[2]) - shift; + } + if (!b->nobound) { + emlist[index1++] = marker; + } + if (b->neighout > 1) { + out->face2tetlist[index2++] = neigh1; + out->face2tetlist[index2++] = neigh2; + } + } + facenumber++; + faceloop.sh = shellfacetraverse(subfaces); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outedges() Output all edges to a .edge file or a tetgenio object. // +// // +// Note: This routine must be called after outelements(), so that the total // +// number of edges 'meshedges' has been counted. // +// // +//============================================================================// + +void tetgenmesh::outedges(tetgenio* out) +{ + FILE *outfile = NULL; + char edgefilename[FILENAMESIZE]; + triface tetloop, worktet, spintet; + face checkseg; + point torg, tdest; + int ishulledge; + int firstindex, shift; + int edgenumber, marker; + int index = 0, index1 = 0, index2 = 0; + int t1ver; + int i; + + // For -o2 option. + point *extralist, pp = NULL; + int highorderindex = 11; + int o2index = 0; + + // For -nn option. + int *tet2edgelist = NULL; + int tidx; + + if (out == (tetgenio *) NULL) { + strcpy(edgefilename, b->outfilename); + strcat(edgefilename, ".edge"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", edgefilename); + } else { + printf("Writing edges.\n"); + } + } + + if (meshedges == 0l) { + if (nonconvex) { + numberedges(); // Count the edges. + } else { + // Use Euler's characteristic to get the numbe of edges. + // It states V - E + F - C = 1, hence E = V + F - C - 1. + long tsize = tetrahedrons->items - hullsize; + long fsize = (tsize * 4l + hullsize) / 2l; + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + meshedges = vsize + fsize - tsize - 1; + } + } + meshhulledges = 0l; // It will be counted. + + if (out == (tetgenio *) NULL) { + outfile = fopen(edgefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", edgefilename); + terminatetetgen(this, 1); + } + // Write the number of edges, boundary markers (0 or 1). + fprintf(outfile, "%ld %d\n", meshedges, !b->nobound); + } else { + // Allocate memory for 'edgelist'. + out->numberofedges = meshedges; + out->edgelist = new int[meshedges * 2]; + if (out->edgelist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + if (b->order == 2) { // -o2 switch + out->o2edgelist = new int[meshedges]; + } + if (!b->nobound) { + out->edgemarkerlist = new int[meshedges]; + } + if (b->neighout > 1) { // '-nn' switch. + out->edge2tetlist = new int[meshedges]; + } + } + + if (b->neighout > 1) { // -nn option + // Output the tetrahedron-to-edge map. + long tsize = tetrahedrons->items - hullsize; + tet2edgelist = new int[tsize * 6]; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift (reduce) the output indices by 1. + } + + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + edgenumber = firstindex; // in->firstnumber; + while (tetloop.tet != (tetrahedron *) NULL) { + // Count the number of Voronoi faces. + worktet.tet = tetloop.tet; + for (i = 0; i < 6; i++) { + worktet.ver = edge2ver[i]; + ishulledge = 0; + fnext(worktet, spintet); + do { + if (!ishulltet(spintet)) { + if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; + } else { + ishulledge = 1; + } + fnextself(spintet); + } while (spintet.tet != worktet.tet); + if (spintet.tet == worktet.tet) { + // Found a new edge. + if (ishulledge) meshhulledges++; + torg = org(worktet); + tdest = dest(worktet); + if (b->order == 2) { // -o2 + // Get the extra vertex on this edge. + extralist = (point *) worktet.tet[highorderindex]; + pp = extralist[ver2edge[worktet.ver]]; + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%5d %4d %4d", edgenumber, + pointmark(torg) - shift, pointmark(tdest) - shift); + if (b->order == 2) { // -o2 + fprintf(outfile, " %4d", pointmark(pp) - shift); + } + } else { + // Output three vertices of this face; + out->edgelist[index++] = pointmark(torg) - shift; + out->edgelist[index++] = pointmark(tdest) - shift; + if (b->order == 2) { // -o2 + out->o2edgelist[o2index++] = pointmark(pp) - shift; + } + } + if (!b->nobound) { + if (b->plc || b->refine) { + // Check if the edge is a segment. + tsspivot1(worktet, checkseg); + if (checkseg.sh != NULL) { + marker = shellmark(checkseg); + } else { + marker = 0; // It's not a segment. + } + } else { + // Mark it if it is a hull edge. + marker = ishulledge ? 1 : 0; + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", marker); + } else { + out->edgemarkerlist[index1++] = marker; + } + } + if (b->neighout > 1) { // '-nn' switch. + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", elemindex(tetloop.tet)); + } else { + out->edge2tetlist[index2++] = elemindex(tetloop.tet); + } + // Fill the tetrahedron-to-edge map. + spintet = worktet; + while (1) { + if (!ishulltet(spintet)) { + tidx = elemindex(spintet.tet) - firstindex; + tet2edgelist[tidx * 6 + ver2edge[spintet.ver]] = edgenumber; + } + fnextself(spintet); + if (spintet.tet == worktet.tet) break; + } + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "\n"); + } + edgenumber++; + } + } + tetloop.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } + + if (b->neighout > 1) { // -nn option + long tsize = tetrahedrons->items - hullsize; + + if (b->facesout) { // -f option + // Build the face-to-edge map (use the tet-to-edge map). + long fsize = (tsize * 4l + hullsize) / 2l; + int *face2edgelist = new int[fsize * 3]; + + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + int facenumber = 0; // firstindex; // in->firstnumber; + while (tetloop.tet != (tetrahedron *) NULL) { + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, spintet); + if (ishulltet(spintet) || + (elemindex(tetloop.tet) < elemindex(spintet.tet))) { + // The three edges of this face are ordered such that the + // first edge is opposite to the first vertex of this face + // that appears in the .face file, and so on. + tidx = elemindex(tetloop.tet) - firstindex; + worktet = tetloop; + for (i = 0; i < 3; i++) { + enextself(worktet); // The edge opposite to vertex i. + int eidx = tet2edgelist[tidx * 6 + ver2edge[worktet.ver]]; + face2edgelist[facenumber * 3 + i] = eidx; + } + facenumber++; + } + } + tetloop.tet = tetrahedrontraverse(); + } + + // Output the face-to-edge map. + if (out == (tetgenio *) NULL) { + strcpy(edgefilename, b->outfilename); + strcat(edgefilename, ".f2e"); + } + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", edgefilename); + } else { + printf("Writing face-to-edge map.\n"); + } + } + if (out == (tetgenio *) NULL) { + outfile = fopen(edgefilename, "w"); + for (tidx = 0; tidx < fsize; tidx++) { // Re-use `tidx' + i = tidx * 3; + fprintf(outfile, "%4d %d %d %d\n", tidx + in->firstnumber, + face2edgelist[i], face2edgelist[i+1], face2edgelist[i+2]); + } + fclose(outfile); + delete [] face2edgelist; + } else { + // Simply copy the address of the list to the output. + out->face2edgelist = face2edgelist; + } + } // if (b->facesout) + + // Output the tetrahedron-to-edge map. + if (out == (tetgenio *) NULL) { + strcpy(edgefilename, b->outfilename); + strcat(edgefilename, ".t2e"); + } + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", edgefilename); + } else { + printf("Writing tetrahedron-to-edge map.\n"); + } + } + if (out == (tetgenio *) NULL) { + outfile = fopen(edgefilename, "w"); + for (tidx = 0; tidx < tsize; tidx++) { + i = tidx * 6; + fprintf(outfile, "%4d %d %d %d %d %d %d\n", tidx + in->firstnumber, + tet2edgelist[i], tet2edgelist[i+1], tet2edgelist[i+2], + tet2edgelist[i+3], tet2edgelist[i+4], tet2edgelist[i+5]); + } + fclose(outfile); + delete [] tet2edgelist; + } else { + // Simply copy the address of the list to the output. + out->tet2edgelist = tet2edgelist; + } + } +} + +//============================================================================// +// // +// outsubsegments() Output segments to a .edge file or a structure. // +// // +//============================================================================// + +void tetgenmesh::outsubsegments(tetgenio* out) +{ + FILE *outfile = NULL; + char edgefilename[FILENAMESIZE]; + int *elist = NULL; + int index, i; + face edgeloop; + point torg, tdest; + int firstindex, shift; + int marker; + int edgenumber; + + // For -o2 option. + triface workface, spintet; + point *extralist, pp = NULL; + int highorderindex = 11; + int o2index = 0; + + // For -nn option. + int neigh = -1; + int index2 = 0; + + int t1ver; // used by fsymself() + + if (out == (tetgenio *) NULL) { + strcpy(edgefilename, b->outfilename); + strcat(edgefilename, ".edge"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", edgefilename); + } else { + printf("Writing edges.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(edgefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", edgefilename); + terminatetetgen(this, 3); + } + // Number of subsegments. + fprintf(outfile, "%ld 1\n", subsegs->items); + } else { + // Allocate memory for 'edgelist'. + out->edgelist = new int[subsegs->items * (b->order == 1 ? 2 : 3)]; + if (out->edgelist == (int *) NULL) { + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2edgelist = new int[subsegs->items]; + } + out->edgemarkerlist = new int[subsegs->items]; + if (out->edgemarkerlist == (int *) NULL) { + terminatetetgen(this, 1); + } + if (b->neighout > 1) { + out->edge2tetlist = new int[subsegs->items]; + } + out->numberofedges = subsegs->items; + elist = out->edgelist; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + index = 0; + i = 0; + + subsegs->traversalinit(); + edgeloop.sh = shellfacetraverse(subsegs); + edgenumber = firstindex; // in->firstnumber; + while (edgeloop.sh != (shellface *) NULL) { + torg = sorg(edgeloop); + tdest = sdest(edgeloop); + if ((b->order == 2) || (b->neighout > 1)) { + sstpivot1(edgeloop, workface); + if (workface.tet != NULL) { + // We must find a non-hull tet. + if (ishulltet(workface)) { + spintet = workface; + while (1) { + fnextself(spintet); + if (!ishulltet(spintet)) break; + if (spintet.tet == workface.tet) break; + } + workface = spintet; + } + } + } + if (b->order == 2) { // -o2 + // Get the extra vertex on this edge. + if (workface.tet != NULL) { + extralist = (point *) workface.tet[highorderindex]; + pp = extralist[ver2edge[workface.ver]]; + } else { + pp = torg; // There is no extra node available. + } + } + if (b->neighout > 1) { // -nn + if (workface.tet != NULL) { + neigh = elemindex(workface.tet); + } else { + neigh = -1; + } + } + marker = shellmark(edgeloop); + if (marker == 0) { + marker = 1; // Default marker of a boundary edge is 1. + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%5d %4d %4d", edgenumber, + pointmark(torg) - shift, pointmark(tdest) - shift); + if (b->order == 2) { // -o2 + fprintf(outfile, " %4d", pointmark(pp) - shift); + } + fprintf(outfile, " %d", marker); + if (b->neighout > 1) { // -nn + fprintf(outfile, " %4d", neigh); + } + fprintf(outfile, "\n"); + } else { + // Output three vertices of this face; + elist[index++] = pointmark(torg) - shift; + elist[index++] = pointmark(tdest) - shift; + if (b->order == 2) { // -o2 + out->o2edgelist[o2index++] = pointmark(pp) - shift; + } + out->edgemarkerlist[i++] = marker; + if (b->neighout > 1) { // -nn + out->edge2tetlist[index2++] = neigh; + } + } + edgenumber++; + edgeloop.sh = shellfacetraverse(subsegs); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outneighbors() Output tet neighbors to a .neigh file or a structure. // +// // +//============================================================================// + +void tetgenmesh::outneighbors(tetgenio* out) +{ + FILE *outfile = NULL; + char neighborfilename[FILENAMESIZE]; + int *nlist = NULL; + int index = 0; + triface tetloop, tetsym; + int neighbori[4]; + int firstindex; + int elementnumber; + long ntets; + + if (out == (tetgenio *) NULL) { + strcpy(neighborfilename, b->outfilename); + strcat(neighborfilename, ".neigh"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", neighborfilename); + } else { + printf("Writing neighbors.\n"); + } + } + + ntets = tetrahedrons->items - hullsize; + + if (out == (tetgenio *) NULL) { + outfile = fopen(neighborfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", neighborfilename); + terminatetetgen(this, 1); + } + // Number of tetrahedra, four faces per tetrahedron. + fprintf(outfile, "%ld %d\n", ntets, 4); + } else { + // Allocate memory for 'neighborlist'. + out->neighborlist = new int[ntets * 4]; + if (out->neighborlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + terminatetetgen(this, 1); + } + nlist = out->neighborlist; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + elementnumber = firstindex; // in->firstnumber; + while (tetloop.tet != (tetrahedron *) NULL) { + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, tetsym); + if (!ishulltet(tetsym)) { + neighbori[tetloop.ver] = elemindex(tetsym.tet); + } else { + neighbori[tetloop.ver] = -1; + } + } + if (out == (tetgenio *) NULL) { + // Tetrahedra number, neighboring tetrahedron numbers. + fprintf(outfile, "%4d %4d %4d %4d %4d\n", elementnumber, + neighbori[0], neighbori[1], neighbori[2], neighbori[3]); + } else { + nlist[index++] = neighbori[0]; + nlist[index++] = neighbori[1]; + nlist[index++] = neighbori[2]; + nlist[index++] = neighbori[3]; + } + tetloop.tet = tetrahedrontraverse(); + elementnumber++; + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outvoronoi() Output the Voronoi diagram to .v.node, .v.edge, v.face, // +// and .v.cell. // +// // +// The Voronoi diagram is the geometric dual of the Delaunay triangulation. // +// The Voronoi vertices are the circumcenters of Delaunay tetrahedra. Each // +// Voronoi edge connects two Voronoi vertices at two sides of a common Dela- // +// unay face. At a face of convex hull, it becomes a ray (goto the infinity). // +// A Voronoi face is the convex hull of all Voronoi vertices around a common // +// Delaunay edge. It is a closed polygon for any internal Delaunay edge. At a // +// ridge, it is unbounded. Each Voronoi cell is the convex hull of all Vor- // +// onoi vertices around a common Delaunay vertex. It is a polytope for any // +// internal Delaunay vertex. It is an unbounded polyhedron for a Delaunay // +// vertex belonging to the convex hull. // +// // +// NOTE: This routine is only used when the input is only a set of point. // +// Comment: Special thanks to Victor Liu for finding and fixing few bugs. // +// // +//============================================================================// + +void tetgenmesh::outvoronoi(tetgenio* out) +{ + FILE *outfile = NULL; + char outfilename[FILENAMESIZE]; + tetgenio::voroedge *vedge = NULL; + tetgenio::vorofacet *vfacet = NULL; + arraypool *tetlist, *ptlist; + triface tetloop, worktet, spintet, firsttet; + point pt[4], ploop, neipt; + REAL ccent[3], infvec[3], vec1[3], vec2[3], L; + long ntets, faces, edges; + int *indexarray, *fidxs, *eidxs; + int arraysize, *vertarray = NULL; + int vpointcount, vedgecount, vfacecount, tcount; + int ishullvert, ishullface; + int index, shift, end1, end2; + int i, j; + + int t1ver; // used by fsymself() + + // Output Voronoi vertices to .v.node file. + if (out == (tetgenio *) NULL) { + strcpy(outfilename, b->outfilename); + strcat(outfilename, ".v.node"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outfilename); + } else { + printf("Writing Voronoi vertices.\n"); + } + } + + // Determine the first index (0 or 1). + shift = (b->zeroindex ? 0 : in->firstnumber); + + // Each face and edge of the tetrahedral mesh will be indexed for indexing + // the Voronoi edges and facets. Indices of faces and edges are saved in + // each tetrahedron (including hull tets). + + // Allocate the total space once. + indexarray = new int[tetrahedrons->items * 10]; + + // Allocate space (10 integers) into each tetrahedron. It re-uses the slot + // for element markers, flags. + i = 0; + tetrahedrons->traversalinit(); + tetloop.tet = alltetrahedrontraverse(); + while (tetloop.tet != NULL) { + tetloop.tet[11] = (tetrahedron) &(indexarray[i * 10]); + i++; + tetloop.tet = alltetrahedrontraverse(); + } + + // The number of tetrahedra (excluding hull tets) (Voronoi vertices). + ntets = tetrahedrons->items - hullsize; + // The number of Delaunay faces (Voronoi edges). + faces = (4l * ntets + hullsize) / 2l; + // The number of Delaunay edges (Voronoi faces). + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + if (!nonconvex) { + edges = vsize + faces - ntets - 1; + } else { + if (meshedges == 0l) { + numberedges(); // Count edges. + } + edges = meshedges; + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outfilename); + terminatetetgen(this, 3); + } + // Number of voronoi points, 3 dim, no attributes, no marker. + fprintf(outfile, "%ld 3 0 0\n", ntets); + } else { + // Allocate space for 'vpointlist'. + out->numberofvpoints = (int) ntets; + out->vpointlist = new REAL[out->numberofvpoints * 3]; + if (out->vpointlist == (REAL *) NULL) { + terminatetetgen(this, 1); + } + } + + // Output Voronoi vertices (the circumcenters of tetrahedra). + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + vpointcount = 0; // The (internal) v-index always starts from 0. + index = 0; + while (tetloop.tet != (tetrahedron *) NULL) { + for (i = 0; i < 4; i++) { + pt[i] = (point) tetloop.tet[4 + i]; + setpoint2tet(pt[i], encode(tetloop)); + } + if (b->weighted) { + orthosphere(pt[0], pt[1], pt[2], pt[3], pt[0][3], pt[1][3], pt[2][3], + pt[3][3], ccent, NULL); + } else { + circumsphere(pt[0], pt[1], pt[2], pt[3], ccent, NULL); + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%4d %16.8e %16.8e %16.8e\n", vpointcount + shift, + ccent[0], ccent[1], ccent[2]); + } else { + out->vpointlist[index++] = ccent[0]; + out->vpointlist[index++] = ccent[1]; + out->vpointlist[index++] = ccent[2]; + } + setelemindex(tetloop.tet, vpointcount); + vpointcount++; + tetloop.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } + + // Output Voronoi edges to .v.edge file. + if (out == (tetgenio *) NULL) { + strcpy(outfilename, b->outfilename); + strcat(outfilename, ".v.edge"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outfilename); + } else { + printf("Writing Voronoi edges.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outfilename); + terminatetetgen(this, 3); + } + // Number of Voronoi edges, no marker. + fprintf(outfile, "%ld 0\n", faces); + } else { + // Allocate space for 'vpointlist'. + out->numberofvedges = (int) faces; + out->vedgelist = new tetgenio::voroedge[out->numberofvedges]; + } + + // Output the Voronoi edges. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + vedgecount = 0; // D-Face (V-edge) index (from zero). + index = 0; // The Delaunay-face index. + while (tetloop.tet != (tetrahedron *) NULL) { + // Count the number of Voronoi edges. Look at the four faces of each + // tetrahedron. Count the face if the tetrahedron's index is + // smaller than its neighbor's or the neighbor is outside. + end1 = elemindex(tetloop.tet); + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + fsym(tetloop, worktet); + if (ishulltet(worktet) || + (elemindex(tetloop.tet) < elemindex(worktet.tet))) { + // Found a Voronoi edge. Operate on it. + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%4d %4d", vedgecount + shift, end1 + shift); + } else { + vedge = &(out->vedgelist[index++]); + vedge->v1 = end1 + shift; + } + if (!ishulltet(worktet)) { + end2 = elemindex(worktet.tet); + } else { + end2 = -1; + } + // Note that end2 may be -1 (worktet.tet is outside). + if (end2 == -1) { + // Calculate the out normal of this hull face. + pt[0] = dest(worktet); + pt[1] = org(worktet); + pt[2] = apex(worktet); + for (j = 0; j < 3; j++) vec1[j] = pt[1][j] - pt[0][j]; + for (j = 0; j < 3; j++) vec2[j] = pt[2][j] - pt[0][j]; + cross(vec1, vec2, infvec); + // Normalize it. + L = sqrt(infvec[0] * infvec[0] + infvec[1] * infvec[1] + + infvec[2] * infvec[2]); + if (L > 0) for (j = 0; j < 3; j++) infvec[j] /= L; + if (out == (tetgenio *) NULL) { + fprintf(outfile, " -1"); + fprintf(outfile, " %g %g %g\n", infvec[0], infvec[1], infvec[2]); + } else { + vedge->v2 = -1; + vedge->vnormal[0] = infvec[0]; + vedge->vnormal[1] = infvec[1]; + vedge->vnormal[2] = infvec[2]; + } + } else { + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %4d\n", end2 + shift); + } else { + vedge->v2 = end2 + shift; + vedge->vnormal[0] = 0.0; + vedge->vnormal[1] = 0.0; + vedge->vnormal[2] = 0.0; + } + } + // Save the V-edge index in this tet and its neighbor. + fidxs = (int *) (tetloop.tet[11]); + fidxs[tetloop.ver] = vedgecount; + fidxs = (int *) (worktet.tet[11]); + fidxs[worktet.ver & 3] = vedgecount; + vedgecount++; + } + } // tetloop.ver + tetloop.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } + + // Output Voronoi faces to .v.face file. + if (out == (tetgenio *) NULL) { + strcpy(outfilename, b->outfilename); + strcat(outfilename, ".v.face"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outfilename); + } else { + printf("Writing Voronoi faces.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outfilename); + terminatetetgen(this, 3); + } + // Number of Voronoi faces. + fprintf(outfile, "%ld 0\n", edges); + } else { + out->numberofvfacets = edges; + out->vfacetlist = new tetgenio::vorofacet[out->numberofvfacets]; + if (out->vfacetlist == (tetgenio::vorofacet *) NULL) { + terminatetetgen(this, 1); + } + } + + // Output the Voronoi facets. + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + vfacecount = 0; // D-edge (V-facet) index (from zero). + while (tetloop.tet != (tetrahedron *) NULL) { + // Count the number of Voronoi faces. Look at the six edges of each + // tetrahedron. Count the edge only if the tetrahedron's index is + // smaller than those of all other tetrahedra that share the edge. + worktet.tet = tetloop.tet; + for (i = 0; i < 6; i++) { + worktet.ver = edge2ver[i]; + // Count the number of faces at this edge. If the edge is a hull edge, + // the face containing dummypoint is also counted. + //ishulledge = 0; // Is it a hull edge. + tcount = 0; + firsttet = worktet; + spintet = worktet; + while (1) { + tcount++; + fnextself(spintet); + if (spintet.tet == worktet.tet) break; + if (!ishulltet(spintet)) { + if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; + } else { + //ishulledge = 1; + if (apex(spintet) == dummypoint) { + // We make this V-edge appear in the end of the edge list. + fnext(spintet, firsttet); + } + } + } // while (1) + if (spintet.tet == worktet.tet) { + // Found a Voronoi facet. Operate on it. + pt[0] = org(worktet); + pt[1] = dest(worktet); + end1 = pointmark(pt[0]) - in->firstnumber; // V-cell index + end2 = pointmark(pt[1]) - in->firstnumber; + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%4d %4d %4d %-2d ", vfacecount + shift, + end1 + shift, end2 + shift, tcount); + } else { + vfacet = &(out->vfacetlist[vfacecount]); + vfacet->c1 = end1 + shift; + vfacet->c2 = end2 + shift; + vfacet->elist = new int[tcount + 1]; + vfacet->elist[0] = tcount; + index = 1; + } + // Output V-edges of this V-facet. + spintet = firsttet; //worktet; + while (1) { + fidxs = (int *) (spintet.tet[11]); + if (apex(spintet) != dummypoint) { + vedgecount = fidxs[spintet.ver & 3]; + ishullface = 0; + } else { + ishullface = 1; // It's not a real face. + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", !ishullface ? (vedgecount + shift) : -1); + } else { + vfacet->elist[index++] = !ishullface ? (vedgecount + shift) : -1; + } + // Save the V-facet index in this tet at this edge. + eidxs = &(fidxs[4]); + eidxs[ver2edge[spintet.ver]] = vfacecount; + // Go to the next face. + fnextself(spintet); + if (spintet.tet == firsttet.tet) break; + } // while (1) + if (out == (tetgenio *) NULL) { + fprintf(outfile, "\n"); + } + vfacecount++; + } // if (spintet.tet == worktet.tet) + } // if (i = 0; i < 6; i++) + tetloop.tet = tetrahedrontraverse(); + } + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } + + // Output Voronoi cells to .v.cell file. + if (out == (tetgenio *) NULL) { + strcpy(outfilename, b->outfilename); + strcat(outfilename, ".v.cell"); + } + + if (!b->quiet) { + if (out == (tetgenio *) NULL) { + printf("Writing %s.\n", outfilename); + } else { + printf("Writing Voronoi cells.\n"); + } + } + + if (out == (tetgenio *) NULL) { + outfile = fopen(outfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", outfilename); + terminatetetgen(this, 3); + } + // Number of Voronoi cells. + fprintf(outfile, "%ld\n", points->items - unuverts - dupverts); + } else { + out->numberofvcells = points->items - unuverts - dupverts; + out->vcelllist = new int*[out->numberofvcells]; + if (out->vcelllist == (int **) NULL) { + terminatetetgen(this, 1); + } + } + + // Output Voronoi cells. + tetlist = cavetetlist; + ptlist = cavetetvertlist; + points->traversalinit(); + ploop = pointtraverse(); + vpointcount = 0; + while (ploop != (point) NULL) { + if ((pointtype(ploop) != UNUSEDVERTEX) && + (pointtype(ploop) != DUPLICATEDVERTEX) && + (pointtype(ploop) != NREGULARVERTEX)) { + getvertexstar(1, ploop, tetlist, ptlist, NULL); + // Mark all vertices. Check if it is a hull vertex. + ishullvert = 0; + for (i = 0; i < ptlist->objects; i++) { + neipt = * (point *) fastlookup(ptlist, i); + if (neipt != dummypoint) { + pinfect(neipt); + } else { + ishullvert = 1; + } + } + tcount = (int) ptlist->objects; + if (out == (tetgenio *) NULL) { + fprintf(outfile, "%4d %-2d ", vpointcount + shift, tcount); + } else { + arraysize = tcount; + vertarray = new int[arraysize + 1]; + out->vcelllist[vpointcount] = vertarray; + vertarray[0] = tcount; + index = 1; + } + // List Voronoi facets bounding this cell. + for (i = 0; i < tetlist->objects; i++) { + worktet = * (triface *) fastlookup(tetlist, i); + // Let 'worktet' be [a,b,c,d] where d = ploop. + for (j = 0; j < 3; j++) { + neipt = org(worktet); // neipt is a, or b, or c + // Skip the dummypoint. + if (neipt != dummypoint) { + if (pinfected(neipt)) { + // It's not processed yet. + puninfect(neipt); + // Go to the DT edge [a,d], or [b,d], or [c,d]. + esym(worktet, spintet); + enextself(spintet); + // Get the V-face dual to this edge. + eidxs = (int *) spintet.tet[11]; + vfacecount = eidxs[4 + ver2edge[spintet.ver]]; + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", vfacecount + shift); + } else { + vertarray[index++] = vfacecount + shift; + } + } + } + enextself(worktet); + } // j + } // i + if (ishullvert) { + // Add a hull facet (-1) to the facet list. + if (out == (tetgenio *) NULL) { + fprintf(outfile, " -1"); + } else { + vertarray[index++] = -1; + } + } + if (out == (tetgenio *) NULL) { + fprintf(outfile, "\n"); + } + tetlist->restart(); + ptlist->restart(); + vpointcount++; + } + ploop = pointtraverse(); + } + + // Delete the space for face/edge indices. + delete [] indexarray; + + if (out == (tetgenio *) NULL) { + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); + } +} + +//============================================================================// +// // +// outsmesh() Write surface mesh to a .smesh file, which can be read and // +// tetrahedralized by TetGen. // +// // +// You can specify a filename (without suffix) in 'smfilename'. If you don't // +// supply a filename (let smfilename be NULL), the default name stored in // +// 'tetgenbehavior' will be used. // +// // +//============================================================================// + +void tetgenmesh::outsmesh(char* smfilename) +{ + FILE *outfile; + char nodfilename[FILENAMESIZE]; + char smefilename[FILENAMESIZE]; + face faceloop; + point p1, p2, p3; + int firstindex, shift; + int bmark; + int marker; + int i; + + if (smfilename != (char *) NULL && smfilename[0] != '\0') { + strcpy(smefilename, smfilename); + } else if (b->outfilename[0] != '\0') { + strcpy(smefilename, b->outfilename); + } else { + strcpy(smefilename, "unnamed"); + } + strcpy(nodfilename, smefilename); + strcat(smefilename, ".smesh"); + strcat(nodfilename, ".node"); + + if (!b->quiet) { + printf("Writing %s.\n", smefilename); + } + outfile = fopen(smefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", smefilename); + return; + } + + // Determine the first index (0 or 1). + firstindex = b->zeroindex ? 0 : in->firstnumber; + shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + fprintf(outfile, "# %s. TetGen's input file.\n", smefilename); + fprintf(outfile, "\n# part 1: node list.\n"); + fprintf(outfile, "0 3 0 0 # nodes are found in %s.\n", nodfilename); + + marker = 0; // avoid compile warning. + bmark = !b->nobound && (in->facetmarkerlist || in->trifacemarkerlist); + + fprintf(outfile, "\n# part 2: facet list.\n"); + // Number of facets, boundary marker. + fprintf(outfile, "%ld %d\n", subfaces->items, bmark); + + subfaces->traversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + while (faceloop.sh != (shellface *) NULL) { + p1 = sorg(faceloop); + p2 = sdest(faceloop); + p3 = sapex(faceloop); + if (bmark) { + marker = shellmark(faceloop); + } + fprintf(outfile, "3 %4d %4d %4d", pointmark(p1) - shift, + pointmark(p2) - shift, pointmark(p3) - shift); + if (bmark) { + fprintf(outfile, " %d", marker); + } + fprintf(outfile, "\n"); + faceloop.sh = shellfacetraverse(subfaces); + } + + // Copy input holelist. + fprintf(outfile, "\n# part 3: hole list.\n"); + fprintf(outfile, "%d\n", in->numberofholes); + for (i = 0; i < in->numberofholes; i++) { + fprintf(outfile, "%d %g %g %g\n", i + in->firstnumber, + in->holelist[i * 3], in->holelist[i * 3 + 1], + in->holelist[i * 3 + 2]); + } + + // Copy input regionlist. + fprintf(outfile, "\n# part 4: region list.\n"); + fprintf(outfile, "%d\n", in->numberofregions); + for (i = 0; i < in->numberofregions; i++) { + fprintf(outfile, "%d %g %g %g %d %g\n", i + in->firstnumber, + in->regionlist[i * 5], in->regionlist[i * 5 + 1], + in->regionlist[i * 5 + 2], (int) in->regionlist[i * 5 + 3], + in->regionlist[i * 5 + 4]); + } + + fprintf(outfile, "# Generated by %s\n", b->commandline); + fclose(outfile); +} + +//============================================================================// +// // +// outmesh2medit() Write mesh to a .mesh file, which can be read and // +// rendered by Medit (a free mesh viewer from INRIA). // +// // +// You can specify a filename (without suffix) in 'mfilename'. If you don't // +// supply a filename (let mfilename be NULL), the default name stored in // +// 'tetgenbehavior' will be used. The output file will have the suffix .mesh. // +// // +//============================================================================// + +void tetgenmesh::outmesh2medit(char* mfilename) +{ + FILE *outfile; + char mefilename[FILENAMESIZE]; + tetrahedron* tetptr; + triface tface, tsymface; + face faceloop, segloop, checkmark; + point ptloop, p1, p2, p3, p4; + long ntets, faces; + int pointnumber; + int marker; + int i; + + if (mfilename != (char *) NULL && mfilename[0] != '\0') { + strcpy(mefilename, mfilename); + } else if (b->outfilename[0] != '\0') { + strcpy(mefilename, b->outfilename); + } else { + strcpy(mefilename, "unnamed"); + } + strcat(mefilename, ".mesh"); + + int *subdomains_facets = NULL; + int *subdomains_facets_ori = NULL; + int sub_count = 0; // Count the number of indexed subdomains. + if (subdomains > 0) { + subdomains_facets = new int[subdomains]; + subdomains_facets_ori = new int[subdomains]; + for (i = 0; i < subdomains; i++) { + subdomains_facets_ori[i] = 0; // initialise + } + } + + if (!b->quiet) { + printf("Writing %s.\n", mefilename); + } + outfile = fopen(mefilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", mefilename); + return; + } + + fprintf(outfile, "MeshVersionFormatted 1\n"); + fprintf(outfile, "\n"); + fprintf(outfile, "Dimension\n"); + fprintf(outfile, "3\n"); + fprintf(outfile, "\n"); + + fprintf(outfile, "\n# Set of mesh vertices\n"); + fprintf(outfile, "Vertices\n"); + fprintf(outfile, "%ld\n", points->items); + + points->traversalinit(); + ptloop = pointtraverse(); + pointnumber = 1; // Medit need start number form 1. + while (ptloop != (point) NULL) { + // Point coordinates. + fprintf(outfile, "%.17g %.17g %.17g", ptloop[0], ptloop[1], ptloop[2]); + if (in->numberofpointattributes > 0) { + // Write an attribute, ignore others if more than one. + fprintf(outfile, " %.17g\n", ptloop[3]); + } else { + fprintf(outfile, " 0\n"); + } + setpointmark(ptloop, pointnumber); + ptloop = pointtraverse(); + pointnumber++; + } + + if (b->plc || b->refine) { + fprintf(outfile, "\nEdges\n"); + fprintf(outfile, "%ld\n", subsegs->items); + + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != (shellface *) NULL) { + p1 = sorg(segloop); + p2 = sdest(segloop); + fprintf(outfile, "%5d %5d", pointmark(p1), pointmark(p2)); + marker = shellmark(segloop); + fprintf(outfile, " %d\n", marker); + segloop.sh = shellfacetraverse(subsegs); + } + } + + ntets = tetrahedrons->items - hullsize; + + faces = subfaces->items; + triface abuttingtet; + int t1ver; + + fprintf(outfile, "\n# Set of Triangles\n"); + fprintf(outfile, "Triangles\n"); + fprintf(outfile, "%ld\n", faces); + + subfaces->traversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + int facidx = 1; // Index facets for subdomains. + while (faceloop.sh != (shellface *) NULL) { + stpivot(faceloop, abuttingtet); + if (abuttingtet.tet != NULL) { + if (ishulltet(abuttingtet)) { + fsymself(abuttingtet); + } + } + if (abuttingtet.tet != NULL) { + p1 = org (abuttingtet); + p2 = dest(abuttingtet); + p3 = apex(abuttingtet); + if (subdomains) { + int attr = elemattribute(abuttingtet.tet, 0); + int idx = attr - 1; + if (subdomain_markers[idx] != attr) { + // search it. + } + if (subdomains_facets_ori[idx] == 0) { + subdomains_facets[idx] = facidx; + subdomains_facets_ori[idx] = 1; + sub_count++; + fsym(abuttingtet, tsymface); + if ((tsymface.tet != NULL) && !ishulltet(tsymface)) { + attr = elemattribute(tsymface.tet, 0); + idx = attr - 1; + if (subdomain_markers[idx] != attr) { + // search it. + } + if (subdomains_facets_ori[idx] == 0) { + subdomains_facets[idx] = facidx; + subdomains_facets_ori[idx] = -1; + sub_count++; + } + } + } + } + } else { + // A dangling subfacet. + p1 = sorg(faceloop); + p2 = sdest(faceloop); + p3 = sapex(faceloop); + } + marker = shellmark(faceloop); + fprintf(outfile, "%5d %5d %5d %d\n", + pointmark(p1), pointmark(p2), pointmark(p3), marker); + //setelemindex(faceloop.sh, facidx); + facidx++; + faceloop.sh = shellfacetraverse(subfaces); + } + + + fprintf(outfile, "\n# Set of Tetrahedra\n"); + fprintf(outfile, "Tetrahedra\n"); + fprintf(outfile, "%ld\n", ntets); + + tetrahedrons->traversalinit(); + tetptr = tetrahedrontraverse(); + while (tetptr != (tetrahedron *) NULL) { + if (!b->reversetetori) { + p1 = (point) tetptr[4]; + p2 = (point) tetptr[5]; + } else { + p1 = (point) tetptr[5]; + p2 = (point) tetptr[4]; + } + p3 = (point) tetptr[6]; + p4 = (point) tetptr[7]; + fprintf(outfile, "%5d %5d %5d %5d", + pointmark(p1), pointmark(p2), pointmark(p3), pointmark(p4)); + if (numelemattrib > 0) { + fprintf(outfile, " %.17g", elemattribute(tetptr, 0)); + } else { + fprintf(outfile, " 0"); + } + fprintf(outfile, "\n"); + tetptr = tetrahedrontraverse(); + } + + //fprintf(outfile, "\nCorners\n"); + //fprintf(outfile, "%d\n", in->numberofpoints); + //for (i = 0; i < in->numberofpoints; i++) { + // fprintf(outfile, "%4d\n", i + 1); + //} + + if (subdomains > 0) { + fprintf(outfile, "\nSubDomainFromGeom\n"); + fprintf(outfile, "%d\n", subdomains); + for (i = 0; i < subdomains; i++) { + fprintf(outfile, "3 %d %d %d\n", + subdomains_facets[i], + subdomains_facets_ori[i], + subdomain_markers[i]); + } + delete [] subdomains_facets; + delete [] subdomains_facets_ori; + } + + fprintf(outfile, "\nEnd\n"); + fclose(outfile); +} + + + + + +//============================================================================// +// // +// outmesh2vtk() Save mesh to file in VTK Legacy format. // +// // +// This function was contributed by Bryn Llyod from ETH, 2007. // +// // +//============================================================================// + +void tetgenmesh::outmesh2vtk(char* ofilename, int mesh_idx) +{ + FILE *outfile; + char vtkfilename[FILENAMESIZE]; + point pointloop, p1, p2, p3, p4; + tetrahedron* tptr; + double x, y, z; + int n1, n2, n3, n4; + int nnodes = 4; + int celltype = 10; + + if (b->order == 2) { + printf(" Write VTK not implemented for order 2 elements \n"); + return; + } + + int NEL = tetrahedrons->items - hullsize; + int NN = points->items; + + if (ofilename != (char *) NULL && ofilename[0] != '\0') { + //strcpy(vtkfilename, ofilename); + sprintf(vtkfilename, "%s.%d.vtk", ofilename, mesh_idx); + } else if (b->outfilename[0] != '\0') { + strcpy(vtkfilename, b->outfilename); + strcat(vtkfilename, ".vtk"); + } else { + strcpy(vtkfilename, "noname.vtk"); + } + + if (!b->quiet) { + printf("Writing %s.\n", vtkfilename); + } + outfile = fopen(vtkfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", vtkfilename); + return; + } + + //always write big endian + //bool ImALittleEndian = !testIsBigEndian(); + + fprintf(outfile, "# vtk DataFile Version 2.0\n"); + fprintf(outfile, "Unstructured Grid\n"); + fprintf(outfile, "ASCII\n"); // BINARY + fprintf(outfile, "DATASET UNSTRUCTURED_GRID\n"); + fprintf(outfile, "POINTS %d double\n", NN); + + points->traversalinit(); + pointloop = pointtraverse(); + for(int id=0; idtraversalinit(); + tptr = tetrahedrontraverse(); + //elementnumber = firstindex; // in->firstnumber; + while (tptr != (tetrahedron *) NULL) { + if (!b->reversetetori) { + p1 = (point) tptr[4]; + p2 = (point) tptr[5]; + } else { + p1 = (point) tptr[5]; + p2 = (point) tptr[4]; + } + p3 = (point) tptr[6]; + p4 = (point) tptr[7]; + n1 = pointmark(p1) - in->firstnumber; + n2 = pointmark(p2) - in->firstnumber; + n3 = pointmark(p3) - in->firstnumber; + n4 = pointmark(p4) - in->firstnumber; + fprintf(outfile, "%d %4d %4d %4d %4d\n", nnodes, n1, n2, n3, n4); + tptr = tetrahedrontraverse(); + } + fprintf(outfile, "\n"); + + fprintf(outfile, "CELL_TYPES %d\n", NEL); + for(int tid=0; tid 0) { + // Output tetrahedra region attributes. + fprintf(outfile, "CELL_DATA %d\n", NEL); + fprintf(outfile, "SCALARS cell_scalars int 1\n"); + fprintf(outfile, "LOOKUP_TABLE default\n"); + tetrahedrons->traversalinit(); + tptr = tetrahedrontraverse(); + while (tptr != (tetrahedron *) NULL) { + fprintf(outfile, "%d\n", (int) elemattribute(tptr, numelemattrib - 1)); + tptr = tetrahedrontraverse(); + } + fprintf(outfile, "\n"); + } + + fclose(outfile); +} + +void tetgenmesh::out_surfmesh_vtk(char* ofilename, int mesh_idx) +{ + FILE *outfile; + char vtkfilename[FILENAMESIZE]; + triface abuttingtet; + face faceloop; + point pointloop, torg, tdest, tapex; + double x, y, z; + int n1, n2, n3; + int nnodes = 3; + int celltype = 5; // triangle + + int t1ver; + + if (b->order == 2) { + printf(" Write VTK not implemented for order 2 elements \n"); + return; + } + + int NEL = subfaces->items; //tetrahedrons->items - hullsize; + int NN = points->items; + + if (ofilename != (char *) NULL && ofilename[0] != '\0') { + //strcpy(vtkfilename, ofilename); + sprintf(vtkfilename, "%s.%d.vtk", ofilename, mesh_idx); + } else if (b->outfilename[0] != '\0') { + strcpy(vtkfilename, b->outfilename); + strcat(vtkfilename, ".surf.vtk"); + } else { + strcpy(vtkfilename, "noname.surf.vtk"); + } + + if (!b->quiet) { + printf("Writing %s.\n", vtkfilename); + } + outfile = fopen(vtkfilename, "w"); + if (outfile == (FILE *) NULL) { + printf("File I/O Error: Cannot create file %s.\n", vtkfilename); + return; + } + + //always write big endian + //bool ImALittleEndian = !testIsBigEndian(); + + fprintf(outfile, "# vtk DataFile Version 2.0\n"); + fprintf(outfile, "Unstructured Grid\n"); + fprintf(outfile, "ASCII\n"); // BINARY + fprintf(outfile, "DATASET UNSTRUCTURED_GRID\n"); + fprintf(outfile, "POINTS %d double\n", NN); + + points->traversalinit(); + pointloop = pointtraverse(); + for(int id=0; idtraversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + //facenumber = firstindex; // in->firstnumber; + while (faceloop.sh != (shellface *) NULL) { + stpivot(faceloop, abuttingtet); + // If there is a tetrahedron containing this subface, orient it so + // that the normal of this face points to inside of the volume by + // right-hand rule. + if (abuttingtet.tet != NULL) { + if (ishulltet(abuttingtet)) { + fsymself(abuttingtet); + } + } + if (abuttingtet.tet != NULL) { + torg = org(abuttingtet); + tdest = dest(abuttingtet); + tapex = apex(abuttingtet); + } else { + // This may happen when only a surface mesh be generated. + torg = sorg(faceloop); + tdest = sdest(faceloop); + tapex = sapex(faceloop); + } + + n1 = pointmark(torg) - in->firstnumber; + n2 = pointmark(tdest) - in->firstnumber; + n3 = pointmark(tapex) - in->firstnumber; + + fprintf(outfile, "%d %4d %4d %4d\n", nnodes, n1, n2, n3); + //facenumber++; + faceloop.sh = shellfacetraverse(subfaces); + } + fprintf(outfile, "\n"); + + fprintf(outfile, "CELL_TYPES %d\n", NEL); + for(int tid=0; tidfacetmarkerlist != NULL) { //if (numelemattrib > 0) { + // Output tetrahedra region attributes. + fprintf(outfile, "CELL_DATA %d\n", NEL); + fprintf(outfile, "SCALARS cell_scalars int 1\n"); + fprintf(outfile, "LOOKUP_TABLE default\n"); + + subfaces->traversalinit(); + faceloop.sh = shellfacetraverse(subfaces); + //facenumber = firstindex; // in->firstnumber; + while (faceloop.sh != (shellface *) NULL) { + fprintf(outfile, "%d\n", (int) shellmark(faceloop)); + //facenumber++; + faceloop.sh = shellfacetraverse(subfaces); + } + fprintf(outfile, "\n"); + } + + fclose(outfile); +} + +//============================================================================// +// // +// out_intersected_facets() Save skipped subfaces. // +// // +//============================================================================// + +void tetgenmesh::out_intersected_facets() +{ + char FileName[1024], *sptr; + + strcpy(FileName, b->outfilename); + sptr = strrchr(b->outfilename, '.'); + if (sptr != NULL) *sptr = '\0'; + strcat(b->outfilename, "_skipped"); + + outnodes(NULL); + + strcpy(b->outfilename, FileName); // Restore the original file name. + + strcpy(FileName, b->outfilename); + sptr = strrchr(FileName, '.'); + if (sptr != NULL) *sptr = '\0'; + strcat(FileName, "_skipped.face"); + FILE *fout = fopen(FileName, "w"); + + if (!b->quiet) { + printf("Writing %s\n", FileName); + } + + // Determine the first index (0 or 1). + int firstindex = b->zeroindex ? 0 : in->firstnumber; + int shift = 0; // Default no shiftment. + if ((in->firstnumber == 1) && (firstindex == 0)) { + shift = 1; // Shift the output indices by 1. + } + + int facenumber = firstindex; // in->firstnumber; + + fprintf(fout, "%ld 1\n", skipped_facet_list->objects); + + for (int i = 0; i < (int) skipped_facet_list->objects; i++) { + badface *bf = (badface *) fastlookup(skipped_facet_list, i); + fprintf(fout, "%d %d %d %d %d\n", facenumber, + pointmark(bf->forg) - shift, + pointmark(bf->fdest) - shift, + pointmark(bf->fapex) - shift, + (int) bf->key); + // remove it from the pool of subfaces (do not output them to .face). + shellfacedealloc(subfaces, bf->ss.sh); + facenumber++; + } + + fclose(fout); +} + + +// // +// // +//== output_cxx ==============================================================// + +//== main_cxx ================================================================// +// // +// // + +//============================================================================// +// // +// tetrahedralize() The interface for users using TetGen library to // +// generate tetrahedral meshes with all features. // +// // +// The sequence is roughly as follows. Many of these steps can be skipped, // +// depending on the command line switches. // +// // +// - Initialize constants and parse the command line. // +// - Read the vertices from a file and either // +// - tetrahedralize them (no -r), or // +// - read an old mesh from files and reconstruct it (-r). // +// - Insert the boundary segments and facets (-p or -Y). // +// - Read the holes (-p), regional attributes (-pA), and regional volume // +// constraints (-pa). Carve the holes and concavities, and spread the // +// regional attributes and volume constraints. // +// - Enforce the constraints on minimum quality bound (-q) and maximum // +// volume (-a), and a mesh size function (-m). // +// - Optimize the mesh wrt. specified quality measures (-O and -o). // +// - Write the output files and print the statistics. // +// - Check the consistency of the mesh (-C). // +// // +//============================================================================// + +void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, + tetgenio *addin, tetgenio *bgmin) +{ + tetgenmesh m; + clock_t tv[13], ts[6]; // Timing informations (defined in time.h) + REAL cps = (REAL) CLOCKS_PER_SEC; + + tv[0] = clock(); + + m.b = b; + m.in = in; + m.addin = addin; + + if (b->metric && bgmin && (bgmin->numberofpoints > 0)) { + m.bgm = new tetgenmesh(); // Create an empty background mesh. + m.bgm->b = b; + m.bgm->in = bgmin; + } + + m.initializepools(); + m.transfernodes(); + + + tv[1] = clock(); + + if (b->refine) { // -r + m.reconstructmesh(); + } else { // -p + m.incrementaldelaunay(ts[0]); + } + + tv[2] = clock(); + + if (!b->quiet) { + if (b->refine) { + printf("Mesh reconstruction seconds: %g\n", ((REAL)(tv[2]-tv[1])) / cps); + } else { + printf("Delaunay seconds: %g\n", ((REAL)(tv[2]-tv[1])) / cps); + if (b->verbose) { + printf(" Point sorting seconds: %g\n", ((REAL)(ts[0]-tv[1])) / cps); + + } + } + } + + if (b->plc && !b->refine) { // -p + m.meshsurface(); + + ts[0] = clock(); + + if (!b->quiet) { + printf("Surface mesh seconds: %g\n", ((REAL)(ts[0]-tv[2])) / cps); + } + } + + + tv[3] = clock(); + + if ((b->metric) && (m.bgm != NULL)) { // -m + m.bgm->initializepools(); + m.bgm->transfernodes(); + m.bgm->reconstructmesh(); + + ts[0] = clock(); + + if (!b->quiet) { + printf("Background mesh reconstruct seconds: %g\n", + ((REAL)(ts[0] - tv[3])) / cps); + } + + if (b->metric) { // -m + m.interpolatemeshsize(); + + ts[1] = clock(); + + if (!b->quiet) { + printf("Size interpolating seconds: %g\n",((REAL)(ts[1]-ts[0])) / cps); + } + } + } + + tv[4] = clock(); + + if (b->plc && !b->refine) { // -p + if (!b->cdt) { // no -D + m.recoverboundary(ts[0]); + } else { + m.constraineddelaunay(ts[0]); + } + + ts[1] = clock(); + + if (!b->quiet) { + if (!b->cdt) { // no -D + printf("Boundary recovery "); + } else { + printf("Constrained Delaunay "); + } + printf("seconds: %g\n", ((REAL)(ts[1] - tv[4])) / cps); + if (b->verbose) { + printf(" Segment recovery seconds: %g\n",((REAL)(ts[0]-tv[4]))/ cps); + printf(" Facet recovery seconds: %g\n", ((REAL)(ts[1]-ts[0])) / cps); + } + } + + if (m.skipped_facet_list != NULL) { + if (!b->quiet) { + printf("\n!!! %ld input triangles are skipped due to self-intersections.\n", + m.skipped_facet_list->objects); + } + + if (!b->nofacewritten) m.out_intersected_facets(); + delete m.skipped_facet_list; + m.skipped_facet_list = NULL; + + if (!b->nonodewritten) m.outnodes(out); + if (!b->noelewritten) m.outelements(out); + if (!b->nofacewritten) m.outsubfaces(out); + if (!b->nofacewritten) m.outsubsegments(out); + + terminatetetgen(NULL, 3); // This is not a normal exit. + } + + if (b->diagnose) { // -d + if (!b->quiet) { + printf("\nThe input surface mesh is correct.\n"); + } + return; + } + + m.carveholes(); + + ts[2] = clock(); + + if (!b->quiet) { + printf("Exterior tets removal seconds: %g\n",((REAL)(ts[2]-ts[1]))/cps); + } + + ts[3] = clock(); + + if ((!b->cdt || b->nobisect) && (b->supsteiner_level > 0)) { // no -D, -Y/1 + if (m.subvertstack->objects > 0l) { + m.suppresssteinerpoints(); + if (!b->quiet) { + printf("Steiner suppression seconds: %g\n", ((REAL)(ts[3]-ts[2]))/cps); + } + } + } + + if ((b->nobisect > 1)) { // -YY + if ((m.st_segref_count > 0) || (m.st_facref_count > 0)) { + if (!b->nonodewritten) m.outnodes(out); + if (!b->noelewritten) m.outelements(out); + if (!b->nofacewritten) m.outsubfaces(out); + if (!b->nofacewritten) m.outsubsegments(out); + printf("!! Boundary contains Steiner points (-YY option). Program stopped.\n"); + terminatetetgen(&m, 200); + } + } + } + + tv[5] = clock(); + + if (b->metric || b->coarsen) { // -m or -R + m.meshcoarsening(); + } + + tv[6] = clock(); + + if (!b->quiet) { + if (b->metric || b->coarsen) { + printf("Mesh coarsening seconds: %g\n", ((REAL)(tv[6] - tv[5])) / cps); + } + } + + if (b->plc || (b->refine && b->quality && (in->refine_elem_list == NULL))) { + if (!b->quiet) { + printf("Recovering Delaunayness...\n"); + } + m.recoverdelaunay(); + } + + tv[7] = clock(); + + if (b->plc || (b->refine && b->quality && (in->refine_elem_list == NULL))) { + if (!b->quiet) { + printf("Delaunay recovery seconds: %g\n", ((REAL)(tv[7] - tv[6]))/cps); + } + } + + if ((b->plc || b->refine) && b->insertaddpoints) { // -i + if ((addin != NULL) && (addin->numberofpoints > 0)) { + m.insertconstrainedpoints(addin); + } + } + + tv[8] = clock(); + + if (!b->quiet) { + if ((b->plc || b->refine) && b->insertaddpoints) { // -i + if ((addin != NULL) && (addin->numberofpoints > 0)) { + printf("Constrained points seconds: %g\n", ((REAL)(tv[8]-tv[7]))/cps); + } + } + } + if (b->quality) { // -q + m.delaunayrefinement(); + } + + tv[9] = clock(); + + if (!b->quiet) { + if (b->quality) { + printf("Refinement seconds: %g\n", ((REAL)(tv[9] - tv[8])) / cps); + } + } + + if ((b->plc || b->quality) && + (b->smooth_maxiter > 0) && + ((m.st_volref_count > 0) || (m.st_facref_count > 0))) { + m.smooth_vertices(); // m.optimizemesh(ts[0]); + } + + tv[10] = clock(); + + if (!b->quiet) { + if ((b->plc || b->quality) && + (b->smooth_maxiter > 0) && + ((m.st_volref_count > 0) || (m.st_facref_count > 0))) { + printf("Mesh smoothing seconds: %g\n", ((REAL)(tv[10] - tv[9])) / cps); + } + } + + if (b->plc || b->quality) { + m.improve_mesh(); + } + + tv[11] = clock(); + + if (!b->quiet) { + if (b->plc || b->quality) { + printf("Mesh improvement seconds: %g\n", ((REAL)(tv[11] - tv[10])) / cps); + } + } + + if (!b->nojettison && ((m.dupverts > 0) || (m.unuverts > 0) + || (b->refine && (in->numberofcorners == 10)))) { + m.jettisonnodes(); + } + + + if ((b->order == 2) && !b->convex) { + m.highorder(); + } + + if (!b->quiet) { + printf("\n"); + } + + if (out != (tetgenio *) NULL) { + out->firstnumber = in->firstnumber; + out->mesh_dim = in->mesh_dim; + } + + if (b->nonodewritten || b->noiterationnum) { + if (!b->quiet) { + printf("NOT writing a .node file.\n"); + } + } else { + m.outnodes(out); + } + + if (b->noelewritten) { + if (!b->quiet) { + printf("NOT writing an .ele file.\n"); + } + m.indexelements(); + } else { + if (m.tetrahedrons->items > 0l) { + m.outelements(out); + } + } + + if (b->nofacewritten) { + if (!b->quiet) { + printf("NOT writing an .face file.\n"); + } + } else { + if (b->facesout) { + if (m.tetrahedrons->items > 0l) { + m.outfaces(out); // Output all faces. + } + } else { + if (b->plc || b->refine) { + if (m.subfaces->items > 0l) { + m.outsubfaces(out); // Output boundary faces. + } + } else { + if (m.tetrahedrons->items > 0l) { + m.outhullfaces(out); // Output convex hull faces. + } + } + } + } + + + if (b->nofacewritten) { + if (!b->quiet) { + printf("NOT writing an .edge file.\n"); + } + } else { + if (b->edgesout) { // -e + m.outedges(out); // output all mesh edges. + } else { + if (b->plc || b->refine) { + m.outsubsegments(out); // output subsegments. + } + } + } + + if ((b->plc || b->refine) && b->metric) { // -m + m.outmetrics(out); + } + + if (!out && b->plc && + ((b->object == tetgenbehavior::OFF) || + (b->object == tetgenbehavior::PLY) || + (b->object == tetgenbehavior::STL))) { + m.outsmesh(b->outfilename); + } + + if (!out && b->meditview) { + m.outmesh2medit(b->outfilename); + } + + + if (!out && b->vtkview) { + m.outmesh2vtk(NULL, 0); // b->outfilename + } + + if (!out && b->vtksurfview) { + m.out_surfmesh_vtk(NULL, 0); + } + + if (b->neighout) { + m.outneighbors(out); + } + + if (b->voroout) { + m.outvoronoi(out); + } + + + tv[12] = clock(); + + if (!b->quiet) { + printf("\nOutput seconds: %g\n", ((REAL)(tv[12] - tv[11])) / cps); + printf("Total running seconds: %g\n", ((REAL)(tv[12] - tv[0])) / cps); + } + + if (b->docheck) { + m.check_mesh(0); + if (b->plc || b->refine) { + m.check_shells(); + m.check_segments(); + } + if (b->docheck > 1) { + m.check_delaunay(); + } + } + + if (!b->quiet) { + m.statistics(); + } +} + +#ifndef TETLIBRARY + +//============================================================================// +// // +// main() The command line interface of TetGen. // +// // +//============================================================================// + +//int tetgen_main(int argc, char *argv[]) +int main(int argc, char *argv[]) + +#else // with TETLIBRARY + +//============================================================================// +// // +// tetrahedralize() The library interface of TetGen. // +// // +//============================================================================// + +void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, + tetgenio *addin, tetgenio *bgmin) + +#endif // not TETLIBRARY + +{ + tetgenbehavior b; + +#ifndef TETLIBRARY + + tetgenio in, addin, bgmin; + + if (!b.parse_commandline(argc, argv)) { + terminatetetgen(NULL, 10); + } + + // Read input files. + if (b.refine) { // -r + if (!in.load_tetmesh(b.infilename, (int) b.object)) { + terminatetetgen(NULL, 10); + } + } else { // -p + if (!in.load_plc(b.infilename, (int) b.object)) { + terminatetetgen(NULL, 10); + } + } + if (b.insertaddpoints) { // -i + // Try to read a .a.node file. + addin.load_node(b.addinfilename); + } + if (b.metric) { // -m + // Try to read a background mesh in files .b.node, .b.ele. + bgmin.load_tetmesh(b.bgmeshfilename, (int) b.object); + } + + tetrahedralize(&b, &in, NULL, &addin, &bgmin); + + return 0; + +#else // with TETLIBRARY + + if (!b.parse_commandline(switches)) { + terminatetetgen(NULL, 10); + } + tetrahedralize(&b, in, out, addin, bgmin); + +#endif // not TETLIBRARY +} + +// // +// // +//== main_cxx ================================================================// + +#pragma warning(pop) diff --git a/cocos/gi/light-probe/tetgen.h b/cocos/gi/light-probe/tetgen.h new file mode 100644 index 0000000..0e34818 --- /dev/null +++ b/cocos/gi/light-probe/tetgen.h @@ -0,0 +1,3613 @@ +//============================================================================// +// // +// TetGen // +// // +// A Quality Tetrahedral Mesh Generator and A 3D Delaunay Triangulator // +// // +// Version 1.6.0 // +// August 31, 2020 // +// // +// Copyright (C) 2002--2020 // +// // +// Hang Si // +// Research Group: Numerical Mathematics and Scientific Computing // +// Weierstrass Institute for Applied Analysis and Stochastics (WIAS) // +// Mohrenstr. 39, 10117 Berlin, Germany // +// si@wias-berlin.de // +// // +// TetGen is a tetrahedral mesh generator. It creates 3d triangulations of // +// polyhedral domains. It generates meshes with well-shaped elements whose // +// sizes are adapted to the geometric features or user-provided sizing // +// functions. It has applications in various applications in scientific // +// computing, such as computer graphics (CG), computer-aided design (CAD), // +// geometry processing (parametrizations and computer animation), and // +// physical simulations (finite element analysis). // +// // +// TetGen computes (weighted) Delaunay triangulations for three-dimensional // +// (weighted) point sets, and constrained Delaunay triangulations for // +// three-dimensional polyhedral domains. In the latter case, input edges // +// and triangles can be completely preserved in the output meshes. TetGen // +// can refine or coarsen an existing mesh to result in good quality and // +// size-adapted mesh according to the geometric features and user-defined // +// mesh sizing functions. // +// // +// TetGen implements theoretically proven algorithms for computing the // +// Delaunay and constrained Delaunay tetrahedralizations. TetGen achieves // +// robustness and efficiency by using advanced techniques in computational // +// geometry. A technical paper describes the algorithms and methods // +// implemented in TetGen is available in ACM-TOMS, Hang Si ``TetGen, a // +// Delaunay-Based Quality Tetrahedral Mesh Generator", ACM Transactions on // +// Mathematical Software, February 2015, https://doi.org/10.1145/2629697. // +// // +// TetGen is freely available through the website: http://www.tetgen.org. // +// It may be copied, modified, and redistributed for non-commercial use. // +// Please consult the file LICENSE for the detailed copyright notices. // +// // +//============================================================================// + + +#ifndef tetgenH +#define tetgenH + +// To compile TetGen as a library instead of an executable program, define +// the TETLIBRARY symbol. + +#define TETLIBRARY + + +// TetGen default uses the double-precision (64 bit) for a real number. +// Alternatively, one can use the single-precision (32 bit) 'float' if the +// memory is limited. + +#define REAL double // #define REAL float + +// The maximum number of characters in a file name (including the null). + +#define FILENAMESIZE 1024 + +// The maximum number of chars in a line read from a file (including the null). + +#define INPUTLINESIZE 2048 + +// C standard libraries to perform Input/output operations, general utililities, +// manipulate strings and arrays, compute common mathematical operations, +// get date and time information. + +#include +#include +#include +#include +#include + +// The types 'intptr_t' and 'uintptr_t' are signed and unsigned integer types, +// respectively. They are guaranteed to be the same width as a pointer. +// They are defined in by the C99 Standard. + +#include + +//============================================================================// +// // +// tetgenio // +// // +// A structure for transferring input/output data between the user and // +// TetGen's internal data structure (class tetgenmesh). // +// // +// This data structure contains a collection of arrays, i.e., points, facets, // +// tetrahedra. It contains functions to read input data from files (.node, // +// .poly, .face, .edge, .ele) as well as write output data into files. // +// // +// Once an object of tetgenio is declared, no array is created. One has to // +// allocate enough memory for them. On the deletion of this object, the // +// memory occupied by these arrays needs to be freed. The routine // +// deinitialize() will be automatically called. It frees the memory for // +// an array if it is not a NULL. Note that it assumes that the memory is // +// allocated by the C++ "new" operator. Otherwise, the user is responsible // +// to free them and all pointers must be NULL. // +// // +//============================================================================// + +class tetgenio { + +public: + + // A "polygon" describes a simple polygon (no holes). It is not necessarily + // convex. Each polygon contains a number of corners (points) and the same + // number of sides (edges). The points of the polygon must be given in + // either counterclockwise or clockwise order and they form a ring, so + // every two consecutive points forms an edge of the polygon. + typedef struct { + int *vertexlist; + int numberofvertices; + } polygon; + + // A "facet" describes a polygonal region possibly with holes, edges, and + // points floating in it. Each facet consists of a list of polygons and + // a list of hole points (which lie strictly inside holes). + typedef struct { + polygon *polygonlist; + int numberofpolygons; + REAL *holelist; + int numberofholes; + } facet; + + // A "voroedge" is an edge of the Voronoi diagram. It corresponds to a + // Delaunay face. Each voroedge is either a line segment connecting + // two Voronoi vertices or a ray starting from a Voronoi vertex to an + // "infinite vertex". 'v1' and 'v2' are two indices pointing to the + // list of Voronoi vertices. 'v1' must be non-negative, while 'v2' may + // be -1 if it is a ray, in this case, the unit normal of this ray is + // given in 'vnormal'. + typedef struct { + int v1, v2; + REAL vnormal[3]; + } voroedge; + + // A "vorofacet" is an facet of the Voronoi diagram. It corresponds to a + // Delaunay edge. Each Voronoi facet is a convex polygon formed by a + // list of Voronoi edges, it may not be closed. 'c1' and 'c2' are two + // indices pointing into the list of Voronoi cells, i.e., the two cells + // share this facet. 'elist' is an array of indices pointing into the + // list of Voronoi edges, 'elist[0]' saves the number of Voronoi edges + // (including rays) of this facet. + typedef struct { + int c1, c2; + int *elist; + } vorofacet; + + + // Additional parameters associated with an input (or mesh) vertex. + // These informations are provided by CAD libraries. + typedef struct { + REAL uv[2]; + int tag; + int type; // 0, 1, or 2. + } pointparam; + + // Callback functions for meshing PSCs. + typedef REAL (* GetVertexParamOnEdge)(void*, int, int); + typedef void (* GetSteinerOnEdge)(void*, int, REAL, REAL*); + typedef void (* GetVertexParamOnFace)(void*, int, int, REAL*); + typedef void (* GetEdgeSteinerParamOnFace)(void*, int, REAL, int, REAL*); + typedef void (* GetSteinerOnFace)(void*, int, REAL*, REAL*); + + // A callback function for mesh refinement. + typedef bool (* TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); + + // Items are numbered starting from 'firstnumber' (0 or 1), default is 0. + int firstnumber; + + // Dimension of the mesh (2 or 3), default is 3. + int mesh_dim; + + // Does the lines in .node file contain index or not, default is 1. + int useindex; + + // 'pointlist': An array of point coordinates. The first point's x + // coordinate is at index [0] and its y coordinate at index [1], its + // z coordinate is at index [2], followed by the coordinates of the + // remaining points. Each point occupies three REALs. + // 'pointattributelist': An array of point attributes. Each point's + // attributes occupy 'numberofpointattributes' REALs. + // 'pointmtrlist': An array of metric tensors at points. Each point's + // tensor occupies 'numberofpointmtr' REALs. + // 'pointmarkerlist': An array of point markers; one integer per point. + // 'point2tetlist': An array of tetrahedra indices; one integer per point. + REAL *pointlist; + REAL *pointattributelist; + REAL *pointmtrlist; + int *pointmarkerlist; + int *point2tetlist; + pointparam *pointparamlist; + int numberofpoints; + int numberofpointattributes; + int numberofpointmtrs; + + // 'tetrahedronlist': An array of tetrahedron corners. The first + // tetrahedron's first corner is at index [0], followed by its other + // corners, followed by six nodes on the edges of the tetrahedron if the + // second order option (-o2) is applied. Each tetrahedron occupies + // 'numberofcorners' ints. The second order nodes are ouput only. + // 'tetrahedronattributelist': An array of tetrahedron attributes. Each + // tetrahedron's attributes occupy 'numberoftetrahedronattributes' REALs. + // 'tetrahedronvolumelist': An array of constraints, i.e. tetrahedron's + // volume; one REAL per element. Input only. + // 'neighborlist': An array of tetrahedron neighbors; 4 ints per element. + // 'tet2facelist': An array of tetrahedron face indices; 4 ints per element. + // 'tet2edgelist': An array of tetrahedron edge indices; 6 ints per element. + int *tetrahedronlist; + REAL *tetrahedronattributelist; + REAL *tetrahedronvolumelist; + int *neighborlist; + int *tet2facelist; + int *tet2edgelist; + int numberoftetrahedra; + int numberofcorners; + int numberoftetrahedronattributes; + + // 'facetlist': An array of facets. Each entry is a structure of facet. + // 'facetmarkerlist': An array of facet markers; one int per facet. + facet *facetlist; + int *facetmarkerlist; + int numberoffacets; + + // 'holelist': An array of holes (in volume). Each hole is given by a + // seed (point) which lies strictly inside it. The first seed's x, y and z + // coordinates are at indices [0], [1] and [2], followed by the + // remaining seeds. Three REALs per hole. + REAL *holelist; + int numberofholes; + + // 'regionlist': An array of regions (subdomains). Each region is given by + // a seed (point) which lies strictly inside it. The first seed's x, y and + // z coordinates are at indices [0], [1] and [2], followed by the regional + // attribute at index [3], followed by the maximum volume at index [4]. + // Five REALs per region. + // Note that each regional attribute is used only if you select the 'A' + // switch, and each volume constraint is used only if you select the + // 'a' switch (with no number following). + REAL *regionlist; + int numberofregions; + + // 'refine_elem_list': An array of tetrahedra to be refined. The first + // tetrahedron's first corner is at index [0], followed by its other + // corners. Four integers per element. + // 'refine_elem_vol_list': An array of constraints, i.e. tetrahedron's + // volume; one REAL per element. + int *refine_elem_list; + REAL *refine_elem_vol_list; + int numberofrefineelems; + + // 'facetconstraintlist': An array of facet constraints. Each constraint + // specifies a maximum area bound on the subfaces of that facet. The + // first facet constraint is given by a facet marker at index [0] and its + // maximum area bound at index [1], followed by the remaining facet con- + // straints. Two REALs per facet constraint. Note: the facet marker is + // actually an integer. + REAL *facetconstraintlist; + int numberoffacetconstraints; + + // 'segmentconstraintlist': An array of segment constraints. Each constraint + // specifies a maximum length bound on the subsegments of that segment. + // The first constraint is given by the two endpoints of the segment at + // index [0] and [1], and the maximum length bound at index [2], followed + // by the remaining segment constraints. Three REALs per constraint. + // Note the segment endpoints are actually integers. + REAL *segmentconstraintlist; + int numberofsegmentconstraints; + + + // 'trifacelist': An array of face (triangle) corners. The first face's + // three corners are at indices [0], [1] and [2], followed by the remaining + // faces. Three ints per face. + // 'trifacemarkerlist': An array of face markers; one int per face. + // 'o2facelist': An array of second order nodes (on the edges) of the face. + // It is output only if the second order option (-o2) is applied. The + // first face's three second order nodes are at [0], [1], and [2], + // followed by the remaining faces. Three ints per face. + // 'face2tetlist': An array of tetrahedra indices; 2 ints per face. + // 'face2edgelist': An array of edge indices; 3 ints per face. + int *trifacelist; + int *trifacemarkerlist; + int *o2facelist; + int *face2tetlist; + int *face2edgelist; + int numberoftrifaces; + + // 'edgelist': An array of edge endpoints. The first edge's endpoints + // are at indices [0] and [1], followed by the remaining edges. + // Two ints per edge. + // 'edgemarkerlist': An array of edge markers; one int per edge. + // 'o2edgelist': An array of midpoints of edges. It is output only if the + // second order option (-o2) is applied. One int per edge. + // 'edge2tetlist': An array of tetrahedra indices. One int per edge. + int *edgelist; + int *edgemarkerlist; + int *o2edgelist; + int *edge2tetlist; + int numberofedges; + + // 'vpointlist': An array of Voronoi vertex coordinates (like pointlist). + // 'vedgelist': An array of Voronoi edges. Each entry is a 'voroedge'. + // 'vfacetlist': An array of Voronoi facets. Each entry is a 'vorofacet'. + // 'vcelllist': An array of Voronoi cells. Each entry is an array of + // indices pointing into 'vfacetlist'. The 0th entry is used to store + // the length of this array. + REAL *vpointlist; + voroedge *vedgelist; + vorofacet *vfacetlist; + int **vcelllist; + int numberofvpoints; + int numberofvedges; + int numberofvfacets; + int numberofvcells; + + + // Variable (and callback functions) for meshing PSCs. + void *geomhandle; + GetVertexParamOnEdge getvertexparamonedge; + GetSteinerOnEdge getsteineronedge; + GetVertexParamOnFace getvertexparamonface; + GetEdgeSteinerParamOnFace getedgesteinerparamonface; + GetSteinerOnFace getsteineronface; + + // A callback function. + TetSizeFunc tetunsuitable; + + // Input & output routines. + bool load_node_call(FILE* infile, int markers, int uvflag, char*); + bool load_node(char*); + bool load_edge(char*); + bool load_face(char*); + bool load_tet(char*); + bool load_vol(char*); + bool load_var(char*); + bool load_mtr(char*); + bool load_elem(char*); + bool load_poly(char*); + bool load_off(char*); + bool load_ply(char*); + bool load_stl(char*); + bool load_vtk(char*); + bool load_medit(char*, int); + bool load_neumesh(char*, int); + bool load_plc(char*, int); + bool load_tetmesh(char*, int); + void save_nodes(const char*); + void save_elements(const char*); + void save_faces(const char*); + void save_edges(char*); + void save_neighbors(char*); + void save_poly(const char*); + void save_faces2smesh(char*); + + // Read line and parse string functions. + char *readline(char* string, FILE* infile, int *linenumber); + char *findnextfield(char* string); + char *readnumberline(char* string, FILE* infile, char* infilename); + char *findnextnumber(char* string); + + static void init(polygon* p) { + p->vertexlist = (int *) NULL; + p->numberofvertices = 0; + } + + static void init(facet* f) { + f->polygonlist = (polygon *) NULL; + f->numberofpolygons = 0; + f->holelist = (REAL *) NULL; + f->numberofholes = 0; + } + + // Initialize routine. + void initialize() + { + firstnumber = 0; + mesh_dim = 3; + useindex = 1; + + pointlist = (REAL *) NULL; + pointattributelist = (REAL *) NULL; + pointmtrlist = (REAL *) NULL; + pointmarkerlist = (int *) NULL; + point2tetlist = (int *) NULL; + pointparamlist = (pointparam *) NULL; + numberofpoints = 0; + numberofpointattributes = 0; + numberofpointmtrs = 0; + + tetrahedronlist = (int *) NULL; + tetrahedronattributelist = (REAL *) NULL; + tetrahedronvolumelist = (REAL *) NULL; + neighborlist = (int *) NULL; + tet2facelist = (int *) NULL; + tet2edgelist = (int *) NULL; + numberoftetrahedra = 0; + numberofcorners = 4; + numberoftetrahedronattributes = 0; + + trifacelist = (int *) NULL; + trifacemarkerlist = (int *) NULL; + o2facelist = (int *) NULL; + face2tetlist = (int *) NULL; + face2edgelist = (int *) NULL; + numberoftrifaces = 0; + + edgelist = (int *) NULL; + edgemarkerlist = (int *) NULL; + o2edgelist = (int *) NULL; + edge2tetlist = (int *) NULL; + numberofedges = 0; + + facetlist = (facet *) NULL; + facetmarkerlist = (int *) NULL; + numberoffacets = 0; + + holelist = (REAL *) NULL; + numberofholes = 0; + + regionlist = (REAL *) NULL; + numberofregions = 0; + + refine_elem_list = (int *) NULL; + refine_elem_vol_list = (REAL *) NULL; + numberofrefineelems = 0; + + facetconstraintlist = (REAL *) NULL; + numberoffacetconstraints = 0; + segmentconstraintlist = (REAL *) NULL; + numberofsegmentconstraints = 0; + + + vpointlist = (REAL *) NULL; + vedgelist = (voroedge *) NULL; + vfacetlist = (vorofacet *) NULL; + vcelllist = (int **) NULL; + numberofvpoints = 0; + numberofvedges = 0; + numberofvfacets = 0; + numberofvcells = 0; + + + tetunsuitable = NULL; + + geomhandle = NULL; + getvertexparamonedge = NULL; + getsteineronedge = NULL; + getvertexparamonface = NULL; + getedgesteinerparamonface = NULL; + getsteineronface = NULL; + } + + // Free the memory allocated in 'tetgenio'. Note that it assumes that the + // memory was allocated by the "new" operator (C++). + void clean_memory() + { + int i, j; + + if (pointlist != (REAL *) NULL) { + delete [] pointlist; + } + if (pointattributelist != (REAL *) NULL) { + delete [] pointattributelist; + } + if (pointmtrlist != (REAL *) NULL) { + delete [] pointmtrlist; + } + if (pointmarkerlist != (int *) NULL) { + delete [] pointmarkerlist; + } + if (point2tetlist != (int *) NULL) { + delete [] point2tetlist; + } + if (pointparamlist != (pointparam *) NULL) { + delete [] pointparamlist; + } + + if (tetrahedronlist != (int *) NULL) { + delete [] tetrahedronlist; + } + if (tetrahedronattributelist != (REAL *) NULL) { + delete [] tetrahedronattributelist; + } + if (tetrahedronvolumelist != (REAL *) NULL) { + delete [] tetrahedronvolumelist; + } + if (neighborlist != (int *) NULL) { + delete [] neighborlist; + } + if (tet2facelist != (int *) NULL) { + delete [] tet2facelist; + } + if (tet2edgelist != (int *) NULL) { + delete [] tet2edgelist; + } + + if (trifacelist != (int *) NULL) { + delete [] trifacelist; + } + if (trifacemarkerlist != (int *) NULL) { + delete [] trifacemarkerlist; + } + if (o2facelist != (int *) NULL) { + delete [] o2facelist; + } + if (face2tetlist != (int *) NULL) { + delete [] face2tetlist; + } + if (face2edgelist != (int *) NULL) { + delete [] face2edgelist; + } + + if (edgelist != (int *) NULL) { + delete [] edgelist; + } + if (edgemarkerlist != (int *) NULL) { + delete [] edgemarkerlist; + } + if (o2edgelist != (int *) NULL) { + delete [] o2edgelist; + } + if (edge2tetlist != (int *) NULL) { + delete [] edge2tetlist; + } + + if (facetlist != (facet *) NULL) { + facet *f; + polygon *p; + for (i = 0; i < numberoffacets; i++) { + f = &facetlist[i]; + for (j = 0; j < f->numberofpolygons; j++) { + p = &f->polygonlist[j]; + delete [] p->vertexlist; + } + delete [] f->polygonlist; + if (f->holelist != (REAL *) NULL) { + delete [] f->holelist; + } + } + delete [] facetlist; + } + if (facetmarkerlist != (int *) NULL) { + delete [] facetmarkerlist; + } + + if (holelist != (REAL *) NULL) { + delete [] holelist; + } + if (regionlist != (REAL *) NULL) { + delete [] regionlist; + } + if (refine_elem_list != (int *) NULL) { + delete [] refine_elem_list; + if (refine_elem_vol_list != (REAL *) NULL) { + delete [] refine_elem_vol_list; + } + } + if (facetconstraintlist != (REAL *) NULL) { + delete [] facetconstraintlist; + } + if (segmentconstraintlist != (REAL *) NULL) { + delete [] segmentconstraintlist; + } + if (vpointlist != (REAL *) NULL) { + delete [] vpointlist; + } + if (vedgelist != (voroedge *) NULL) { + delete [] vedgelist; + } + if (vfacetlist != (vorofacet *) NULL) { + for (i = 0; i < numberofvfacets; i++) { + delete [] vfacetlist[i].elist; + } + delete [] vfacetlist; + } + if (vcelllist != (int **) NULL) { + for (i = 0; i < numberofvcells; i++) { + delete [] vcelllist[i]; + } + delete [] vcelllist; + } + } + + // Constructor & destructor. + tetgenio() {initialize();} + ~tetgenio() {clean_memory();} + +}; // class tetgenio + +//============================================================================// +// // +// tetgenbehavior // +// // +// A structure for maintaining the switches and parameters used by TetGen's // +// internal data structure and algorithms. // +// // +// All switches and parameters are initialized with default values. They are // +// set by the command line arguments (argc, argv). // +// // +// NOTE: Some switches are incompatible with others. While some may depend // +// on other switches. The routine parse_commandline() sets the switches from // +// the command line (a list of strings) and checks the consistency of the // +// applied switches. // +// // +//============================================================================// + +class tetgenbehavior { + +public: + + // Switches of TetGen. + int plc; // '-p', 0. + int psc; // '-s', 0. + int refine; // '-r', 0. + int quality; // '-q', 0. + int nobisect; // '-Y', 0. + int cdt; // '-D', 0. + int cdtrefine; // '-D#', 7. + int coarsen; // '-R', 0. + int weighted; // '-w', 0. + int brio_hilbert; // '-b', 1. + int flipinsert; // '-L', 0. + int metric; // '-m', 0. + int varvolume; // '-a', 0. + int fixedvolume; // '-a', 0. + int regionattrib; // '-A', 0. + int insertaddpoints; // '-i', 0. + int diagnose; // '-d', 0. + int convex; // '-c', 0. + int nomergefacet; // '-M', 0. + int nomergevertex; // '-M', 0. + int noexact; // '-X', 0. + int nostaticfilter; // '-X', 0. + int zeroindex; // '-z', 0. + int facesout; // '-f', 0. + int edgesout; // '-e', 0. + int neighout; // '-n', 0. + int voroout; // '-v', 0. + int meditview; // '-g', 0. + int vtkview; // '-k', 0. + int vtksurfview; // '-k', 0. + int nobound; // '-B', 0. + int nonodewritten; // '-N', 0. + int noelewritten; // '-E', 0. + int nofacewritten; // '-F', 0. + int noiterationnum; // '-I', 0. + int nojettison; // '-J', 0. + int docheck; // '-C', 0. + int quiet; // '-Q', 0. + int nowarning; // '-W', 0. + int verbose; // '-V', 0. + + // Parameters of TetGen. + int vertexperblock; // '-x', 4092. + int tetrahedraperblock; // '-x', 8188. + int shellfaceperblock; // '-x', 2044. + int supsteiner_level; // '-Y/', 2. + int addsteiner_algo; // '-Y//', 1. + int coarsen_param; // '-R', 0. + int weighted_param; // '-w', 0. + int fliplinklevel; // -1. + int flipstarsize; // -1. + int fliplinklevelinc; // 1. + int opt_max_flip_level; // '-O', 3. + int opt_scheme; // '-O/#', 7. + int opt_iterations; // -O//#, 3. + int smooth_cirterion; // -s, 1. + int smooth_maxiter; // -s, 7. + int delmaxfliplevel; // 1. + int order; // '-o', 1. + int reversetetori; // '-o/', 0. + int steinerleft; // '-S', 0. + int unflip_queue_limit; // '-U#', 1000. + int no_sort; // 0. + int hilbert_order; // '-b///', 52. + int hilbert_limit; // '-b//' 8. + int brio_threshold; // '-b' 64. + REAL brio_ratio; // '-b/' 0.125. + REAL epsilon; // '-T', 1.0e-8. + REAL facet_separate_ang_tol; // '-p', 179.9. + REAL collinear_ang_tol; // '-p/', 179.9. + REAL facet_small_ang_tol; // '-p//', 15.0. + REAL maxvolume; // '-a', -1.0. + REAL maxvolume_length; // '-a', -1.0. + REAL minratio; // '-q', 0.0. + REAL opt_max_asp_ratio; // 1000.0. + REAL opt_max_edge_ratio; // 100.0. + REAL mindihedral; // '-q', 5.0. + REAL optmaxdihedral; // -o/# 177.0. + REAL metric_scale; // -m#, 1.0. + REAL smooth_alpha; // '-s', 0.3. + REAL coarsen_percent; // -R1/#, 1.0. + REAL elem_growth_ratio; // Growth ratio of # elements, -r#, 0.0. + REAL refine_progress_ratio; // -r/#, 0.333. + + // Strings of command line arguments and input/output file names. + char commandline[1024]; + char infilename[1024]; + char outfilename[1024]; + char addinfilename[1024]; + char bgmeshfilename[1024]; + + // Read an additional tetrahedral mesh and treat it as holes [2018-07-30]. + int hole_mesh; // '-H', 0. + char hole_mesh_filename[1024]; + + // The input object of TetGen. They are recognized by either the input + // file extensions or by the specified options. + // Currently the following objects are supported: + // - NODES, a list of nodes (.node); + // - POLY, a piecewise linear complex (.poly or .smesh); + // - OFF, a polyhedron (.off, Geomview's file format); + // - PLY, a polyhedron (.ply, file format from gatech, only ASCII); + // - STL, a surface mesh (.stl, stereolithography format); + // - MEDIT, a surface mesh (.mesh, Medit's file format); + // - MESH, a tetrahedral mesh (.ele). + // If no extension is available, the imposed command line switch + // (-p or -r) implies the object. + enum objecttype {NODES, POLY, OFF, PLY, STL, MEDIT, VTK, MESH, NEU_MESH} object; + + + void syntax(); + void usage(); + + // Command line parse routine. + bool parse_commandline(int argc, char **argv); + bool parse_commandline(char *switches) { + return parse_commandline(0, &switches); + } + + // Initialize all variables. + tetgenbehavior() + { + plc = 0; + psc = 0; + refine = 0; + quality = 0; + nobisect = 0; + cdt = 0; // set by -D (without a number following it) + cdtrefine = 7; // default, set by -D# + coarsen = 0; + metric = 0; + weighted = 0; + brio_hilbert = 1; + flipinsert = 0; + varvolume = 0; + fixedvolume = 0; + noexact = 0; + nostaticfilter = 0; + insertaddpoints = 0; + regionattrib = 0; + diagnose = 0; + convex = 0; + zeroindex = 0; + facesout = 0; + edgesout = 0; + neighout = 0; + voroout = 0; + meditview = 0; + vtkview = 0; + vtksurfview = 0; + nobound = 0; + nonodewritten = 0; + noelewritten = 0; + nofacewritten = 0; + noiterationnum = 0; + nomergefacet = 0; + nomergevertex = 0; + nojettison = 0; + docheck = 0; + quiet = 0; + nowarning = 0; + verbose = 0; + + vertexperblock = 4092; + tetrahedraperblock = 8188; + shellfaceperblock = 4092; + supsteiner_level = 2; + addsteiner_algo = 1; + coarsen_param = 0; + weighted_param = 0; + fliplinklevel = -1; + flipstarsize = -1; + fliplinklevelinc = 1; + opt_scheme = 7; + opt_max_flip_level = 3; + opt_iterations = 3; + delmaxfliplevel = 1; + order = 1; + reversetetori = 0; + steinerleft = -1; + unflip_queue_limit = 1000; + no_sort = 0; + hilbert_order = 52; //-1; + hilbert_limit = 8; + brio_threshold = 64; + brio_ratio = 0.125; + facet_separate_ang_tol = 179.9; + collinear_ang_tol = 179.9; + facet_small_ang_tol = 15.0; + maxvolume = -1.0; + maxvolume_length = -1.0; + minratio = 2.0; + opt_max_asp_ratio = 1000.; + opt_max_edge_ratio = 100.; + mindihedral = 3.5; + optmaxdihedral = 177.00; + epsilon = 1.0e-8; + coarsen_percent = 1.0; + metric_scale = 1.0; // -m# + elem_growth_ratio = 0.0; // -r# + refine_progress_ratio = 0.333; // -r/# + object = NODES; + + smooth_cirterion = 3; // -s# default smooth surface and volume vertices. + smooth_maxiter = 7; // set by -s#/7 + smooth_alpha = 0.3; // relax parameter, set by -s#/#/0.3 + + commandline[0] = '\0'; + infilename[0] = '\0'; + outfilename[0] = '\0'; + addinfilename[0] = '\0'; + bgmeshfilename[0] = '\0'; + + hole_mesh = 0; + hole_mesh_filename[0] = '\0'; + + } + +}; // class tetgenbehavior + +//============================================================================// +// // +// Robust Geometric predicates // +// // +// The following routines are the robust geometric predicates for orientation // +// test and point-in-sphere test implemented by Jonathan Shewchuk. // +// He generously provided the source code in the public domain, // +// http://www.cs.cmu.edu/~quake/robust.html. // +// predicates.cxx is a C++ version of the original C code. // +// // +// The original predicates of Shewchuk only use "dynamic filters", i.e., it // +// computes the error at runtime step by step. TetGen first uses a "static // +// filter" in each predicate. It estimates the maximal possible error in all // +// cases. It safely and quickly "filters" many easy cases. // +// // +//============================================================================// + +void exactinit(int, int, int, REAL, REAL, REAL); + +REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd); +REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe); +REAL orient4d(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, + REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); + +REAL orient2dexact(REAL *pa, REAL *pb, REAL *pc); +REAL orient3dexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd); +REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); + + +//============================================================================// +// // +// tetgenmesh TetGen's internal mesh data structure. // +// // +// It uses a tetrahedron-based mesh data structure. It implements elementary // +// flip operations to locally modify the mesh. It implements basic meshing // +// algorithms to create Delaunay tetrahedraliations, to perform boundary // +// recovery, to place Steiner points in the mesh domain, and to optimize the // +// quality of the mesh. // +// // +//============================================================================// + +class tetgenmesh { + +public: + +//============================================================================// +// // +// Mesh data structure // +// // +// A tetrahedral mesh T of a 3D piecewise linear complex (PLC) X is a 3D // +// simplicial complex whose underlying space is equal to the space of X. T // +// contains a 2D subcomplex S which is a triangular mesh of the boundary of // +// X. S contains a 1D subcomplex L which is a linear mesh of the boundary of // +// S. Faces and edges in S and L are respectively called subfaces and segme- // +// nts to distinguish them from others in T. // +// // +// TetGen uses a tetrahedron-based data structure. It stores tetrahedra and // +// vertices. This data structure is pointer-based. Each tetrahedron contains // +// pointers to its vertices and adjacent tetrahedra. Each vertex holds its x-,// +// y-, z-coordinates, and a pointer to one of the tetrahedra having it. Both // +// tetrahedra and vertices may contain user data. // +// // +// Let T be a tetrahedralization. Each triangular face of T belongs to either // +// two or one tetrahedron. In the latter case, it is an exterior boundary // +// face of T. TetGen attaches tetrahedra (one-to-one) to such faces. All such // +// tetrahedra contain an "infinite vertex" (which has no geometric coordinates// +// ). One can imagine such a vertex lies in 4D space and is visible by all // +// exterior boundary faces simultaneously. This extended set of tetrahedra // +// (including the infinite vertex) becomes a tetrahedralization of a 3-sphere // +// that has no boundary in 3d. It has the nice property that every triangular // +// face is shared by exactly two tetrahedra. // +// // +// The current version of TetGen stores explicitly the subfaces and segments // +// (which are in surface mesh S and the linear mesh L), respectively. Extra // +// pointers are allocated in tetrahedra and subfaces to point each other. // +// // +//============================================================================// + + // The tetrahedron data structure. It includes the following fields: + // - a list of four adjoining tetrahedra; + // - a list of four vertices; + // - a pointer to a list of four subfaces (optional, for -p switch); + // - a pointer to a list of six segments (optional, for -p switch); + // - a list of user-defined floating-point attributes (optional); + // - a volume constraint (optional, for -a switch); + // - an integer of element marker (and flags); + // The structure of a tetrahedron is an array of pointers. Its actual size + // (the length of the array) is determined at runtime. + + typedef REAL **tetrahedron; + + // The subface data structure. It includes the following fields: + // - a list of three adjoining subfaces; + // - a list of three vertices; + // - a list of three adjoining segments; + // - two adjoining tetrahedra; + // - an area constraint (optional, for -q switch); + // - an integer for boundary marker; + // - an integer for type, flags, etc. + + typedef REAL **shellface; + + // The point data structure. It includes the following fields: + // - x, y and z coordinates; + // - a list of user-defined point attributes (optional); + // - u, v coordinates (optional, for -s switch); + // - a metric tensor (optional, for -q or -m switch); + // - a pointer to an adjacent tetrahedron; + // - a pointer to a parent (or a duplicate) point; + // - a pointer to an adjacent subface or segment (optional, -p switch); + // - a pointer to a tet in background mesh (optional, for -m switch); + // - an integer for boundary marker (point index); + // - an integer for point type (and flags). + // - an integer for geometry tag (optional, for -s switch). + // The structure of a point is an array of REALs. Its acutal size is + // determined at the runtime. + + typedef REAL *point; + +//============================================================================// +// // +// Handles // +// // +// Navigation and manipulation in a tetrahedralization are accomplished by // +// operating on structures referred as ``handles". A handle is a pair (t,v), // +// where t is a pointer to a tetrahedron, and v is a 4-bit integer, in the // +// range from 0 to 11. v is called the ``version'' of a tetrahedron, it rep- // +// resents a directed edge of a specific face of the tetrahedron. // +// // +// There are 12 even permutations of the four vertices, each of them corres- // +// ponds to a directed edge (a version) of the tetrahedron. The 12 versions // +// can be grouped into 4 distinct ``edge rings'' in 4 ``oriented faces'' of // +// this tetrahedron. One can encode each version (a directed edge) into a // +// 4-bit integer such that the two upper bits encode the index (from 0 to 2) // +// of this edge in the edge ring, and the two lower bits encode the index ( // +// from 0 to 3) of the oriented face which contains this edge. // +// // +// The four vertices of a tetrahedron are indexed from 0 to 3 (according to // +// their storage in the data structure). Give each face the same index as // +// the node opposite it in the tetrahedron. Denote the edge connecting face // +// i to face j as i/j. We number the twelve versions as follows: // +// // +// | edge 0 edge 1 edge 2 // +// --------|-------------------------------- // +// face 0 | 0 (0/1) 4 (0/3) 8 (0/2) // +// face 1 | 1 (1/2) 5 (1/3) 9 (1/0) // +// face 2 | 2 (2/3) 6 (2/1) 10 (2/0) // +// face 3 | 3 (3/0) 7 (3/1) 11 (3/2) // +// // +// Similarly, navigation and manipulation in a (boundary) triangulation are // +// done by using handles of triangles. Each handle is a pair (s, v), where s // +// is a pointer to a triangle, and v is a version in the range from 0 to 5. // +// Each version corresponds to a directed edge of this triangle. // +// // +// Number the three vertices of a triangle from 0 to 2 (according to their // +// storage in the data structure). Give each edge the same index as the node // +// opposite it in the triangle. The six versions of a triangle are: // +// // +// | edge 0 edge 1 edge 2 // +// ---------------|-------------------------- // +// ccw orieation | 0 2 4 // +// cw orieation | 1 3 5 // +// // +// In the following, a 'triface' is a handle of tetrahedron, and a 'face' is // +// a handle of a triangle. // +// // +//============================================================================// + + class triface { + public: + tetrahedron *tet; + int ver; // Range from 0 to 11. + triface() : tet(0), ver(0) {} + triface& operator=(const triface& t) { + tet = t.tet; ver = t.ver; + return *this; + } + }; + + class face { + public: + shellface *sh; + int shver; // Range from 0 to 5. + face() : sh(0), shver(0) {} + face& operator=(const face& s) { + sh = s.sh; shver = s.shver; + return *this; + } + }; + +//============================================================================// +// // +// Arraypool // +// // +// A dynamic linear array. (It is written by J. Shewchuk) // +// // +// Each arraypool contains an array of pointers to a number of blocks. Each // +// block contains the same fixed number of objects. Each index of the array // +// addresses a particular object in the pool. The most significant bits add- // +// ress the index of the block containing the object. The less significant // +// bits address this object within the block. // +// // +// 'objectbytes' is the size of one object in blocks; 'log2objectsperblock' // +// is the base-2 logarithm of 'objectsperblock'; 'objects' counts the number // +// of allocated objects; 'totalmemory' is the total memory in bytes. // +// // +//============================================================================// + + class arraypool { + + public: + + int objectbytes; + int objectsperblock; + int log2objectsperblock; + int objectsperblockmark; + int toparraylen; + char **toparray; + long objects; + unsigned long totalmemory; + + void restart(); + void poolinit(int sizeofobject, int log2objperblk); + char* getblock(int objectindex); + void* lookup(int objectindex); + int newindex(void **newptr); + + arraypool(int sizeofobject, int log2objperblk); + ~arraypool(); + }; + +// fastlookup() -- A fast, unsafe operation. Return the pointer to the object +// with a given index. Note: The object's block must have been allocated, +// i.e., by the function newindex(). + +#define fastlookup(pool, index) \ + (void *) ((pool)->toparray[(index) >> (pool)->log2objectsperblock] + \ + ((index) & (pool)->objectsperblockmark) * (pool)->objectbytes) + +//============================================================================// +// // +// Memorypool // +// // +// A structure for memory allocation. (It is written by J. Shewchuk) // +// // +// firstblock is the first block of items. nowblock is the block from which // +// items are currently being allocated. nextitem points to the next slab // +// of free memory for an item. deaditemstack is the head of a linked list // +// (stack) of deallocated items that can be recycled. unallocateditems is // +// the number of items that remain to be allocated from nowblock. // +// // +// Traversal is the process of walking through the entire list of items, and // +// is separate from allocation. Note that a traversal will visit items on // +// the "deaditemstack" stack as well as live items. pathblock points to // +// the block currently being traversed. pathitem points to the next item // +// to be traversed. pathitemsleft is the number of items that remain to // +// be traversed in pathblock. // +// // +//============================================================================// + + class memorypool { + + public: + + void **firstblock, **nowblock; + void *nextitem; + void *deaditemstack; + void **pathblock; + void *pathitem; + int alignbytes; + int itembytes, itemwords; + int itemsperblock; + long items, maxitems; + int unallocateditems; + int pathitemsleft; + + memorypool(); + memorypool(int, int, int, int); + ~memorypool(); + + void poolinit(int, int, int, int); + void restart(); + void *alloc(); + void dealloc(void*); + void traversalinit(); + void *traverse(); + }; + +//============================================================================// +// // +// badface // +// // +// Despite of its name, a 'badface' can be used to represent one of the // +// following objects: // +// - a face of a tetrahedron which is (possibly) non-Delaunay; // +// - an encroached subsegment or subface; // +// - a bad-quality tetrahedron, i.e, has too large radius-edge ratio; // +// - a sliver, i.e., has good radius-edge ratio but nearly zero volume; // +// - a recently flipped face (saved for undoing the flip later). // +// // +//============================================================================// + + class badface { + public: + triface tt; + face ss; + REAL key, cent[6]; // circumcenter or cos(dihedral angles) at 6 edges. + point forg, fdest, fapex, foppo, noppo; + badface *nextitem; + badface() : key(0), forg(0), fdest(0), fapex(0), foppo(0), noppo(0), + nextitem(0) {} + void init() { + key = 0.; + for (int k = 0; k < 6; k++) cent[k] = 0.; + tt.tet = NULL; tt.ver = 0; + ss.sh = NULL; ss.shver = 0; + forg = fdest = fapex = foppo = noppo = NULL; + nextitem = NULL; + } + }; + +//============================================================================// +// // +// insertvertexflags // +// // +// A collection of flags that pass to the routine insertvertex(). // +// // +//============================================================================// + + class insertvertexflags { + + public: + + int iloc; // input/output. + int bowywat, lawson; + int splitbdflag, validflag, respectbdflag; + int rejflag, chkencflag, cdtflag; + int assignmeshsize; + int sloc, sbowywat; + + // Used by Delaunay refinement. + int collect_inial_cavity_flag; + int ignore_near_vertex; + int check_insert_radius; + int refineflag; // 0, 1, 2, 3 + triface refinetet; + face refinesh; + int smlenflag; // for useinsertradius. + REAL smlen; // for useinsertradius. + point parentpt; + + void init() { + iloc = bowywat = lawson = 0; + splitbdflag = validflag = respectbdflag = 0; + rejflag = chkencflag = cdtflag = 0; + assignmeshsize = 0; + sloc = sbowywat = 0; + + collect_inial_cavity_flag = 0; + ignore_near_vertex = 0; + check_insert_radius = 0; + refineflag = 0; + refinetet.tet = NULL; + refinesh.sh = NULL; + smlenflag = 0; + smlen = 0.0; + parentpt = NULL; + } + + insertvertexflags() { + init(); + } + }; + +//============================================================================// +// // +// flipconstraints // +// // +// A structure of a collection of data (options and parameters) which pass // +// to the edge flip function flipnm(). // +// // +//============================================================================// + + class flipconstraints { + + public: + + // Elementary flip flags. + int enqflag; // (= flipflag) + int chkencflag; + + // Control flags + int unflip; // Undo the performed flips. + int collectnewtets; // Collect the new tets created by flips. + int collectencsegflag; + + // Optimization flags. + int noflip_in_surface; // do not flip edges (not segment) in surface. + int remove_ndelaunay_edge; // Remove a non-Delaunay edge. + REAL bak_tetprism_vol; // The value to be minimized. + REAL tetprism_vol_sum; + int remove_large_angle; // Remove a large dihedral angle at edge. + REAL cosdihed_in; // The input cosine of the dihedral angle (> 0). + REAL cosdihed_out; // The improved cosine of the dihedral angle. + REAL max_asp_out; // Max asp ratio after the improvement of dihedral angle. + + // Boundary recovery flags. + int checkflipeligibility; + point seg[2]; // A constraining edge to be recovered. + point fac[3]; // A constraining face to be recovered. + point remvert; // A vertex to be removed. + + + flipconstraints() { + enqflag = 0; + chkencflag = 0; + + unflip = 0; + collectnewtets = 0; + collectencsegflag = 0; + + noflip_in_surface = 0; + remove_ndelaunay_edge = 0; + bak_tetprism_vol = 0.0; + tetprism_vol_sum = 0.0; + remove_large_angle = 0; + cosdihed_in = 0.0; + cosdihed_out = 0.0; + max_asp_out = 0.0; + + checkflipeligibility = 0; + seg[0] = NULL; + fac[0] = NULL; + remvert = NULL; + } + }; + +//============================================================================// +// // +// optparameters // +// // +// Optimization options and parameters. // +// // +//============================================================================// + + class optparameters { + + public: + + // The one of goals of optimization. + int max_min_volume; // Maximize the minimum volume. + int min_max_aspectratio; // Minimize the maximum aspect ratio. + int min_max_dihedangle; // Minimize the maximum dihedral angle. + + // The initial and improved value. + REAL initval, imprval; + + int numofsearchdirs; + REAL searchstep; + int maxiter; // Maximum smoothing iterations (disabled by -1). + int smthiter; // Performed iterations. + + + optparameters() { + max_min_volume = 0; + min_max_aspectratio = 0; + min_max_dihedangle = 0; + + initval = imprval = 0.0; + + numofsearchdirs = 10; + searchstep = 0.01; + maxiter = -1; // Unlimited smoothing iterations. + smthiter = 0; + + } + }; + + +//============================================================================// +// // +// Labels (enumeration declarations) used by TetGen. // +// // +//============================================================================// + + // Labels that signify the type of a vertex. + enum verttype {UNUSEDVERTEX, DUPLICATEDVERTEX, RIDGEVERTEX, /*ACUTEVERTEX,*/ + FACETVERTEX, VOLVERTEX, FREESEGVERTEX, FREEFACETVERTEX, + FREEVOLVERTEX, NREGULARVERTEX, DEADVERTEX}; + + // Labels that signify the result of triangle-triangle intersection test. + enum interresult {DISJOINT, INTERSECT, SHAREVERT, SHAREEDGE, SHAREFACE, + TOUCHEDGE, TOUCHFACE, ACROSSVERT, ACROSSEDGE, ACROSSFACE, + SELF_INTERSECT}; + + // Labels that signify the result of point location. + enum locateresult {UNKNOWN, OUTSIDE, INTETRAHEDRON, ONFACE, ONEDGE, ONVERTEX, + ENCVERTEX, ENCSEGMENT, ENCSUBFACE, NEARVERTEX, NONREGULAR, + INSTAR, BADELEMENT, NULLCAVITY, SHARPCORNER, FENSEDIN, + NONCOPLANAR, SELF_ENCROACH}; + +//============================================================================// +// // +// Variables of TetGen // +// // +//============================================================================// + + // Pointer to the input data (a set of nodes, a PLC, or a mesh). + tetgenio *in, *addin; + + // Pointer to the switches and parameters. + tetgenbehavior *b; + + // Pointer to a background mesh (contains size specification map). + tetgenmesh *bgm; + + // Memorypools to store mesh elements (points, tetrahedra, subfaces, and + // segments) and extra pointers between tetrahedra, subfaces, and segments. + memorypool *tetrahedrons, *subfaces, *subsegs, *points; + memorypool *tet2subpool, *tet2segpool; + + // Memorypools to store bad-quality (or encroached) elements. + memorypool *badtetrahedrons, *badsubfacs, *badsubsegs; + memorypool *split_subfaces_pool, *split_segments_pool; + arraypool *unsplit_badtets, *unsplit_subfaces, *unsplit_segments; + arraypool *check_tets_list; + + badface *stack_enc_segments, *stack_enc_subfaces; + + // Bad quality subfaces are ordered by priority queues. + badface *queuefront[64]; + badface *queuetail[64]; + int nextnonemptyq[64]; + int firstnonemptyq, recentq; + + // Bad quality tetrahedra are ordered by priority queues. + memorypool *badqual_tets_pool; + badface *bt_queuefront[64]; + badface *bt_queuetail[64]; + int bt_nextnonemptyq[64]; + int bt_firstnonemptyq, bt_recentq; + + // A memorypool to store faces to be flipped. + memorypool *flippool; + arraypool *later_unflip_queue, *unflipqueue; + badface *flipstack, *unflip_queue_front, *unflip_queue_tail; + + // Arrays used for point insertion (the Bowyer-Watson algorithm). + arraypool *cavetetlist, *cavebdrylist, *caveoldtetlist; + arraypool *cave_oldtet_list; // only tetrahedron's + arraypool *cavetetshlist, *cavetetseglist, *cavetetvertlist; + arraypool *caveencshlist, *caveencseglist; + arraypool *caveshlist, *caveshbdlist, *cavesegshlist; + triface _bw_faces[4096]; // _bw_faces[64][64]; + + // Stacks used for CDT construction and boundary recovery. + arraypool *subsegstack, *subfacstack, *subvertstack; + arraypool *skipped_segment_list, *skipped_facet_list; + + // Arrays of encroached segments and subfaces (for mesh refinement). + arraypool *encseglist, *encshlist; + + // The map between facets to their vertices (for mesh refinement). + int number_of_facets; + int *idx2facetlist; + point *facetverticeslist; + int *idx_segment_facet_list; // segment-to-facet map. + int *segment_facet_list; + int *idx_ridge_vertex_facet_list; // vertex-to-facet map. + int *ridge_vertex_facet_list; + + // The map between segments to their endpoints (for mesh refinement). + int segmentendpointslist_length; + point *segmentendpointslist; + double *segment_info_list; + int *idx_segment_ridge_vertex_list; // are two ridge vertices form a segment? + point *segment_ridge_vertex_list; + + // The infinite vertex. + point dummypoint; + // The recently visited tetrahedron, subface. + triface recenttet; + face recentsh; + + // PI is the ratio of a circle's circumference to its diameter. + static REAL PI; + + // The list of subdomains. (-A option). + int subdomains; // Number of subdomains. + int *subdomain_markers; + + // Various variables. + int numpointattrib; // Number of point attributes. + int numelemattrib; // Number of tetrahedron attributes. + int sizeoftensor; // Number of REALs per metric tensor. + int pointmtrindex; // Index to find the metric tensor of a point. + int pointparamindex; // Index to find the u,v coordinates of a point. + int point2simindex; // Index to find a simplex adjacent to a point. + int pointmarkindex; // Index to find boundary marker of a point. + int pointinsradiusindex; // Index to find the insertion radius of a point. + int elemattribindex; // Index to find attributes of a tetrahedron. + int polarindex; // Index to find the polar plane parameters. + int volumeboundindex; // Index to find volume bound of a tetrahedron. + int elemmarkerindex; // Index to find marker of a tetrahedron. + int shmarkindex; // Index to find boundary marker of a subface. + int areaboundindex; // Index to find area bound of a subface. + int checksubsegflag; // Are there segments in the tetrahedralization yet? + int checksubfaceflag; // Are there subfaces in the tetrahedralization yet? + int boundary_recovery_flag; + int checkconstraints; // Are there variant (node, seg, facet) constraints? + int nonconvex; // Is current mesh non-convex? + int autofliplinklevel; // The increase of link levels, default is 1. + int useinsertradius; // Save the insertion radius for Steiner points. + long samples; // Number of random samples for point location. + unsigned long randomseed; // Current random number seed. + REAL cosmaxdihed, cosmindihed; // The cosine values of max/min dihedral. + REAL cossmtdihed; // The cosine value of a bad dihedral to be smoothed. + REAL cosslidihed; // The cosine value of the max dihedral of a sliver. + REAL cos_large_dihed; // The cosine value of large dihedral (135 degree). + REAL opt_max_sliver_asp_ratio; // = 10 x b->opt_max_asp_ratio. + REAL minfaceang, minfacetdihed; // The minimum input (dihedral) angles. + REAL cos_facet_separate_ang_tol; + REAL cos_collinear_ang_tol; + REAL tetprism_vol_sum; // The total volume of tetrahedral-prisms (in 4D). + REAL longest; // The longest possible edge length. + REAL minedgelength; // = longest * b->epsion. + REAL xmax, xmin, ymax, ymin, zmax, zmin; // Bounding box of points. + + // Options for mesh refinement. + REAL big_radius_edge_ratio; // calculated by qualitystatistics(). + REAL smallest_insradius; // Save the smallest insertion radius. + long elem_limit; + long insert_point_count; // number of attempted insertions. + long report_refine_progress; // the next report event. + long last_point_count; // number of points after last report event. + long last_insertion_count; // number of insertions after last report event. + + // Counters. + long insegments; // Number of input segments. + long hullsize; // Number of exterior boundary faces. + long meshedges; // Number of mesh edges. + long meshhulledges; // Number of boundary mesh edges. + long steinerleft; // Number of Steiner points not yet used. + long dupverts; // Are there duplicated vertices? + long unuverts; // Are there unused vertices? + long duplicated_facets_count; // Are there duplicated facets.? + long nonregularcount; // Are there non-regular vertices? + long st_segref_count, st_facref_count, st_volref_count; // Steiner points. + long fillregioncount, cavitycount, cavityexpcount; + long flip14count, flip26count, flipn2ncount; + long flip23count, flip32count, flip44count, flip41count; + long flip31count, flip22count; + long opt_flips_count, opt_collapse_count, opt_smooth_count; + long recover_delaunay_count; + unsigned long totalworkmemory; // Total memory used by working arrays. + + +//============================================================================// +// // +// Mesh manipulation primitives // +// // +//============================================================================// + + // Fast lookup tables for mesh manipulation primitives. + static int bondtbl[12][12], fsymtbl[12][12]; + static int esymtbl[12], enexttbl[12], eprevtbl[12]; + static int enextesymtbl[12], eprevesymtbl[12]; + static int eorgoppotbl[12], edestoppotbl[12]; + static int facepivot1[12], facepivot2[12][12]; + static int orgpivot[12], destpivot[12], apexpivot[12], oppopivot[12]; + static int tsbondtbl[12][6], stbondtbl[12][6]; + static int tspivottbl[12][6], stpivottbl[12][6]; + static int ver2edge[12], edge2ver[6], epivot[12]; + static int sorgpivot [6], sdestpivot[6], sapexpivot[6]; + static int snextpivot[6]; + + void inittables(); + + // Primitives for tetrahedra. + inline tetrahedron encode(triface& t); + inline tetrahedron encode2(tetrahedron* ptr, int ver); + inline void decode(tetrahedron ptr, triface& t); + inline tetrahedron* decode_tet_only(tetrahedron ptr); + inline int decode_ver_only(tetrahedron ptr); + inline void bond(triface& t1, triface& t2); + inline void dissolve(triface& t); + inline void esym(triface& t1, triface& t2); + inline void esymself(triface& t); + inline void enext(triface& t1, triface& t2); + inline void enextself(triface& t); + inline void eprev(triface& t1, triface& t2); + inline void eprevself(triface& t); + inline void enextesym(triface& t1, triface& t2); + inline void enextesymself(triface& t); + inline void eprevesym(triface& t1, triface& t2); + inline void eprevesymself(triface& t); + inline void eorgoppo(triface& t1, triface& t2); + inline void eorgoppoself(triface& t); + inline void edestoppo(triface& t1, triface& t2); + inline void edestoppoself(triface& t); + inline void fsym(triface& t1, triface& t2); + inline void fsymself(triface& t); + inline void fnext(triface& t1, triface& t2); + inline void fnextself(triface& t); + inline point org (triface& t); + inline point dest(triface& t); + inline point apex(triface& t); + inline point oppo(triface& t); + inline void setorg (triface& t, point p); + inline void setdest(triface& t, point p); + inline void setapex(triface& t, point p); + inline void setoppo(triface& t, point p); + inline REAL elemattribute(tetrahedron* ptr, int attnum); + inline void setelemattribute(tetrahedron* ptr, int attnum, REAL value); + inline REAL* get_polar(tetrahedron* ptr); + inline REAL get_volume(tetrahedron* ptr); + inline REAL volumebound(tetrahedron* ptr); + inline void setvolumebound(tetrahedron* ptr, REAL value); + inline int elemindex(tetrahedron* ptr); + inline void setelemindex(tetrahedron* ptr, int value); + inline int elemmarker(tetrahedron* ptr); + inline void setelemmarker(tetrahedron* ptr, int value); + inline void infect(triface& t); + inline void uninfect(triface& t); + inline bool infected(triface& t); + inline void marktest(triface& t); + inline void unmarktest(triface& t); + inline bool marktested(triface& t); + inline void markface(triface& t); + inline void unmarkface(triface& t); + inline bool facemarked(triface& t); + inline void markedge(triface& t); + inline void unmarkedge(triface& t); + inline bool edgemarked(triface& t); + inline void marktest2(triface& t); + inline void unmarktest2(triface& t); + inline bool marktest2ed(triface& t); + inline int elemcounter(triface& t); + inline void setelemcounter(triface& t, int value); + inline void increaseelemcounter(triface& t); + inline void decreaseelemcounter(triface& t); + inline bool ishulltet(triface& t); + inline bool isdeadtet(triface& t); + + // Primitives for subfaces and subsegments. + inline void sdecode(shellface sptr, face& s); + inline shellface sencode(face& s); + inline shellface sencode2(shellface *sh, int shver); + inline void spivot(face& s1, face& s2); + inline void spivotself(face& s); + inline void sbond(face& s1, face& s2); + inline void sbond1(face& s1, face& s2); + inline void sdissolve(face& s); + inline point sorg(face& s); + inline point sdest(face& s); + inline point sapex(face& s); + inline void setsorg(face& s, point pointptr); + inline void setsdest(face& s, point pointptr); + inline void setsapex(face& s, point pointptr); + inline void sesym(face& s1, face& s2); + inline void sesymself(face& s); + inline void senext(face& s1, face& s2); + inline void senextself(face& s); + inline void senext2(face& s1, face& s2); + inline void senext2self(face& s); + inline REAL areabound(face& s); + inline void setareabound(face& s, REAL value); + inline int shellmark(face& s); + inline void setshellmark(face& s, int value); + inline void sinfect(face& s); + inline void suninfect(face& s); + inline bool sinfected(face& s); + inline void smarktest(face& s); + inline void sunmarktest(face& s); + inline bool smarktested(face& s); + inline void smarktest2(face& s); + inline void sunmarktest2(face& s); + inline bool smarktest2ed(face& s); + inline void smarktest3(face& s); + inline void sunmarktest3(face& s); + inline bool smarktest3ed(face& s); + inline void setfacetindex(face& f, int value); + inline int getfacetindex(face& f); + inline bool isdeadsh(face& s); + + // Primitives for interacting tetrahedra and subfaces. + inline void tsbond(triface& t, face& s); + inline void tsdissolve(triface& t); + inline void stdissolve(face& s); + inline void tspivot(triface& t, face& s); + inline void stpivot(face& s, triface& t); + + // Primitives for interacting tetrahedra and segments. + inline void tssbond1(triface& t, face& seg); + inline void sstbond1(face& s, triface& t); + inline void tssdissolve1(triface& t); + inline void sstdissolve1(face& s); + inline void tsspivot1(triface& t, face& s); + inline void sstpivot1(face& s, triface& t); + + // Primitives for interacting subfaces and segments. + inline void ssbond(face& s, face& edge); + inline void ssbond1(face& s, face& edge); + inline void ssdissolve(face& s); + inline void sspivot(face& s, face& edge); + + // Primitives for points. + inline int pointmark(point pt); + inline void setpointmark(point pt, int value); + inline enum verttype pointtype(point pt); + inline void setpointtype(point pt, enum verttype value); + inline int pointgeomtag(point pt); + inline void setpointgeomtag(point pt, int value); + inline REAL pointgeomuv(point pt, int i); + inline void setpointgeomuv(point pt, int i, REAL value); + inline void pinfect(point pt); + inline void puninfect(point pt); + inline bool pinfected(point pt); + inline void pmarktest(point pt); + inline void punmarktest(point pt); + inline bool pmarktested(point pt); + inline void pmarktest2(point pt); + inline void punmarktest2(point pt); + inline bool pmarktest2ed(point pt); + inline void pmarktest3(point pt); + inline void punmarktest3(point pt); + inline bool pmarktest3ed(point pt); + inline tetrahedron point2tet(point pt); + inline void setpoint2tet(point pt, tetrahedron value); + inline shellface point2sh(point pt); + inline void setpoint2sh(point pt, shellface value); + inline point point2ppt(point pt); + inline void setpoint2ppt(point pt, point value); + inline tetrahedron point2bgmtet(point pt); + inline void setpoint2bgmtet(point pt, tetrahedron value); + inline void setpointinsradius(point pt, REAL value); + inline REAL getpointinsradius(point pt); + inline bool issteinerpoint(point pt); + + // Advanced primitives. + inline void point2tetorg(point pt, triface& t); + inline void point2shorg(point pa, face& s); + inline point farsorg(face& seg); + inline point farsdest(face& seg); + +//============================================================================// +// // +// Memory managment // +// // +//============================================================================// + + void tetrahedrondealloc(tetrahedron*); + tetrahedron *tetrahedrontraverse(); + tetrahedron *alltetrahedrontraverse(); + void shellfacedealloc(memorypool*, shellface*); + shellface *shellfacetraverse(memorypool*); + void pointdealloc(point); + point pointtraverse(); + + void makeindex2pointmap(point*&); + void makepoint2submap(memorypool*, int*&, face*&); + void maketetrahedron(triface*); + void maketetrahedron2(triface*, point, point, point, point); + void makeshellface(memorypool*, face*); + void makepoint(point*, enum verttype); + + void initializepools(); + +//============================================================================// +// // +// Advanced geometric predicates and calculations // +// // +// the routine insphere_s() implements a simplified symbolic perturbation // +// scheme from Edelsbrunner, et al [*]. Hence the point-in-sphere test never // +// returns a zero. The idea is to perturb the weights of vertices in 4D. // +// // +// The routine tri_edge_test() determines whether or not a triangle and an // +// edge intersect in 3D. If they do cross, their intersection type is also // +// reported. This test is a combination of n 3D orientation tests (3 < n < 9).// +// It uses the robust orient3d() test to make the branch decisions. // +// // +// There are several routines to calculate geometrical quantities, e.g., // +// circumcenters, angles, dihedral angles, face normals, face areas, etc. // +// They are implemented using floating-point arithmetics. // +// // +//============================================================================// + + // Symbolic perturbations (robust) + REAL insphere_s(REAL*, REAL*, REAL*, REAL*, REAL*); + REAL orient4d_s(REAL*, REAL*, REAL*, REAL*, REAL*, + REAL, REAL, REAL, REAL, REAL); + + // An embedded 2-dimensional geometric predicate (non-robust) + REAL incircle3d(point pa, point pb, point pc, point pd); + + // Triangle-edge intersection test (robust) + int tri_edge_2d(point, point, point, point, point, point, int, int*, int*); + int tri_edge_tail(point,point,point,point,point,point,REAL,REAL,int,int*,int*); + int tri_edge_test(point, point, point, point, point, point, int, int*, int*); + + // Triangle-triangle intersection test (robust) + int tri_edge_inter_tail(point, point, point, point, point, REAL, REAL); + int tri_tri_inter(point, point, point, point, point, point); + + // Linear algebra functions + inline REAL dot(REAL* v1, REAL* v2); + inline void cross(REAL* v1, REAL* v2, REAL* n); + bool lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N); + void lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N); + + // Geometric calculations (non-robust) + REAL orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd); + inline REAL norm2(REAL x, REAL y, REAL z); + inline REAL distance(REAL* p1, REAL* p2); + inline REAL distance2(REAL* p1, REAL* p2); + void facenormal(point pa, point pb, point pc, REAL *n, int pivot, REAL *lav); + REAL facedihedral(REAL* pa, REAL* pb, REAL* pc1, REAL* pc2); + REAL triarea(REAL* pa, REAL* pb, REAL* pc); + REAL interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n); + REAL cos_interiorangle(REAL* o, REAL* p1, REAL* p2); + void projpt2edge(REAL* p, REAL* e1, REAL* e2, REAL* prj); + void projpt2face(REAL* p, REAL* f1, REAL* f2, REAL* f3, REAL* prj); + bool circumsphere(REAL*, REAL*, REAL*, REAL*, REAL* cent, REAL* radius); + bool orthosphere(REAL*,REAL*,REAL*,REAL*,REAL,REAL,REAL,REAL,REAL*,REAL*); + void planelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + int linelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + REAL tetprismvol(REAL* pa, REAL* pb, REAL* pc, REAL* pd); + bool calculateabovepoint(arraypool*, point*, point*, point*); + void calculateabovepoint4(point, point, point, point); + +//============================================================================// +// // +// Local mesh transformations // +// // +// A local transformation replaces a set of tetrahedra with another set that // +// partitions the same space and boundaries. // +// // +// In 3D, the most straightforward local transformations are the elementary // +// flips performed within the convex hull of five vertices: 2-to-3, 3-to-2, // +// 1-to-4, and 4-to-1 flips. The numbers indicate the number of tetrahedra // +// before and after each flip. The 1-to-4 and 4-to-1 flip involve inserting // +// or deleting a vertex, respectively. // +// // +// There are complex local transformations that are a combination of element- // +// ary flips. For example, a 4-to-4 flip, which replaces two coplanar edges, // +// combines a 2-to-3 flip and a 3-to-2 flip. Note that the first 2-to-3 flip // +// will temporarily create a degenerate tetrahedron removed immediately by // +// the followed 3-to-2 flip. More generally, an n-to-m flip, where n > 3, // +// m = (n - 2) * 2, which removes an edge, can be done by first performing a // +// sequence of (n - 3) 2-to-3 flips followed by a 3-to-2 flip. // +// // +// The routines flip23(), flip32(), and flip41() perform the three elementray // +// flips. The flip14() is available inside the routine insertpoint(). // +// // +// The routines flipnm() and flipnm_post() implement a generalized edge flip // +// algorithm that uses elementary flips. // +// // +// The routine insertpoint() implements the Bowyer-Watson's cavity algorithm // +// to insert a vertex. It works for arbitrary tetrahedralization, either // +// Delaunay, or constrained Delaunay, or non-Delaunay. // +// // +//============================================================================// + + void flippush(badface*&, triface*); + + // The elementary flips. + void flip23(triface*, int, flipconstraints* fc); + void flip32(triface*, int, flipconstraints* fc); + void flip41(triface*, int, flipconstraints* fc); + + // A generalized edge flip. + int flipnm(triface*, int n, int level, int, flipconstraints* fc); + int flipnm_post(triface*, int n, int nn, int, flipconstraints* fc); + + // Point insertion. + int insertpoint(point, triface*, face*, face*, insertvertexflags*); + void insertpoint_abort(face*, insertvertexflags*); + +//============================================================================// +// // +// Delaunay tetrahedralization // +// // +// The routine incrementaldelaunay() implemented two incremental algorithms // +// for constructing Delaunay tetrahedralizations (DTs): the Bowyer-Watson // +// (B-W) algorithm and the incremental flip algorithm of Edelsbrunner and // +// Shah, "Incremental topological flipping works for regular triangulation," // +// Algorithmica, 15:233-241, 1996. // +// // +// The routine incrementalflip() implements the flip algorithm of [Edelsbrun- // +// ner and Shah, 1996]. It flips a queue of locally non-Delaunay faces (in // +// arbitrary order). The success is guaranteed when the Delaunay tetrahedra- // +// lization is constructed incrementally by adding one vertex at a time. // +// // +// The routine locate() finds a tetrahedron contains a new point in current // +// DT. It uses a simple stochastic walk algorithm: starting from an arbitrary // +// tetrahedron in DT, it finds the destination by visit one tetrahedron at a // +// time, randomly chooses a tetrahedron if there are more than one choices. // +// This algorithm terminates due to Edelsbrunner's acyclic theorem. // +// // +// Choose a good starting tetrahedron is crucial to the speed of the walk. // +// TetGen initially uses the "jump-and-walk" algorithm of Muecke, E.P., Saias,// +// I., and Zhu, B. "Fast Randomized Point Location Without Preprocessing." In // +// Proceedings of the 12th ACM Symposium on Computational Geometry, 274-283, // +// 1996. It first randomly samples several tetrahedra in the DT and then // +// choosing the closet one to start walking. // +// // +// The above algorithm slows download dramatically as the number of points // +// grows -- reported in Amenta, N., Choi, S. and Rote, G., "Incremental // +// construction con {BRIO}," In Proceedings of 19th ACM Symposium on Computa- // +// tional Geometry, 211-219, 2003. On the other hand, Liu and Snoeyink showed // +// that the point location could be made in constant time if the points are // +// pre-sorted so that the nearby points in space have nearby indices, then // +// adding the points in this order. They sorted the points along the 3D // +// Hilbert curve. // +// // +// The routine hilbert_sort3() sorts a set of 3D points along the 3D Hilbert // +// curve. It recursively splits a point set according to the Hilbert indices // +// mapped to the subboxes of the bounding box of the point set. The Hilbert // +// indices is calculated by Butz's algorithm in 1971. An excellent exposition // +// of this algorithm can be found in the paper of Hamilton, C., "Compact // +// Hilbert Indices", Technical Report CS-2006-07, Computer Science, Dalhousie // +// University, 2006 (the Section 2). My implementation also referenced Steven // +// Witham's performance of "Hilbert walk" (hopefully, it is still available // +// at http://www.tiac.net/~sw/2008/10/Hilbert/). // +// // +// TetGen sorts the points using the method in the paper of Boissonnat,J.-D., // +// Devillers, O. and Hornus, S. "Incremental Construction of the Delaunay // +// Triangulation and the Delaunay Graph in Medium Dimension," In Proceedings // +// of the 25th ACM Symposium on Computational Geometry, 2009. It first // +// randomly sorts the points into subgroups using the Biased Randomized // +// Insertion Ordering (BRIO) of Amenta et al 2003, then sorts the points in // +// each subgroup along the 3D Hilbert curve. Inserting points in this order // +// ensure a randomized "sprinkling" of the points over the domain, while // +// sorting of each subset provides locality. // +// // +//============================================================================// + + void transfernodes(); + + // Point sorting. + int transgc[8][3][8], tsb1mod3[8]; + void hilbert_init(int n); + int hilbert_split(point* vertexarray, int arraysize, int gc0, int gc1, + REAL, REAL, REAL, REAL, REAL, REAL); + void hilbert_sort3(point* vertexarray, int arraysize, int e, int d, + REAL, REAL, REAL, REAL, REAL, REAL, int depth); + void brio_multiscale_sort(point*,int,int threshold,REAL ratio,int* depth); + + // Point location. + unsigned long randomnation(unsigned int choices); + void randomsample(point searchpt, triface *searchtet); + enum locateresult locate(point searchpt, triface *searchtet, int chkencflag = 0); + + // Incremental Delaunay construction. + enum locateresult locate_dt(point searchpt, triface *searchtet); + int insert_vertex_bw(point, triface*, insertvertexflags*); + void initialdelaunay(point pa, point pb, point pc, point pd); + void incrementaldelaunay(clock_t&); + +//============================================================================// +// // +// Surface triangulation // +// // +//============================================================================// + + void flipshpush(face*); + void flip22(face*, int, int); + void flip31(face*, int); + long lawsonflip(); + int sinsertvertex(point newpt, face*, face*, int iloc, int bowywat, int); + int sremovevertex(point delpt, face*, face*, int lawson); + + enum locateresult slocate(point, face*, int, int, int); + enum interresult sscoutsegment(face*, point, int, int, int); + void scarveholes(int, REAL*); + int triangulate(int, arraypool*, arraypool*, int, REAL*); + + void unifysegments(); + void identifyinputedges(point*); + void mergefacets(); + void meshsurface(); + + +//============================================================================// +// // +// Constrained Delaunay tetrahedralization // +// // +// A constrained Delaunay tetrahedralization (CDT) is a variation of a Delau- // +// nay tetrahedralization (DT) that respects the boundary of a 3D PLC (mesh // +// domain). A crucial difference between a CDT and a DT is that triangles in // +// the PLC's polygons are not required to be locally Delaunay, which frees // +// the CDT to respect the PLC's polygons better. CDTs have optimal properties // +// similar to those of DTs. // +// // +// Steiner Points and Steiner CDTs. It is well-known that even a simple 3D // +// polyhedron may not have a tetrahedralization which only uses its vertices. // +// Some extra points, so-called "Steiner points" are needed to form a tetrah- // +// edralization of such polyhedron. A Steiner CDT of a 3D PLC is a CDT // +// containing Steiner points. TetGen generates Steiner CDTs. // +// // +// The routine constraineddelaunay() creates a (Steiner) CDT of the PLC // +// (including Steiner points). It has two steps, (1) segment recovery and (2) // +// facet (polygon) recovery. // +// // +// The routine delaunizesegments() implements the segment recovery algorithm // +// of Si, H., and Gaertner, K. "Meshing Piecewise Linear Complexes by // +// Constrained Delaunay Tetrahedralizations," In Proceedings of the 14th // +// International Meshing Roundtable, 147--163, 2005. It adds Steiner points // +// into non-Delaunay segments until all subsegments appear together in a DT. // +// The running time of this algorithm is proportional to the number of // +// Steiner points. // +// // +// There are two incremental facet recovery algorithms: the cavity re- // +// triangulation algorithm of Si, H., and Gaertner, K. "3D Boundary Recovery // +// by Constrained Delaunay Tetrahedralization," International Journal for // +// Numerical Methods in Engineering, 85:1341-1364, 2011, and the flip // +// algorithm of Shewchuk, J. "Updating and Constructing Constrained Delaunay // +// and Constrained Regular Triangulations by Flips." In Proceedings of the // +// 19th ACM Symposium on Computational Geometry, 86-95, 2003. // +// // +// Although no Steiner point is needed in step (2), a facet with non-coplanar // +// vertices might need Steiner points. It is discussed in the paper of Si, H.,// +// and Shewchuk, J., "Incrementally Constructing and Updating Constrained // +// Delaunay Tetrahedralizations with Finite Precision Coordinates." In // +// Proceedings of the 21th International Meshing Roundtable, 2012. // +// // +// Our implementation of the facet recovery algorithms recovers a "missing // +// region" at a time. Each missing region is a subset of connected interiors // +// of a polygon. The routine formcavity() creates the cavity of crossing // +// tetrahedra of the missing region. The cavity re-triangulation algorithm is // +// implemented by three subroutines, delaunizecavity(), fillcavity(), and // +// carvecavity(). Since it may fail due to non-coplanar vertices, the // +// subroutine restorecavity() is used to restore the original cavity. // +// // +// The routine flipinsertfacet() implements the flip algorithm. The sub- // +// routine flipcertify() is used to maintain the priority queue of flips. // +// The routine refineregion() is called when the facet recovery algorithm // +// fails to recover a missing region. It inserts Steiner points to refine the // +// missing region. To avoid inserting Steiner points very close to existing // +// segments. The classical encroachment rules of the Delaunay refinement // +// algorithm are used to choose the Steiner points. The routine // +// constrainedfacets() does the facet recovery by using either the cavity re- // +// triangulation algorithm (default) or the flip algorithm. It results in a // +// CDT of the (modified) PLC (including Steiner points). // +// // +//============================================================================// + + enum interresult finddirection(triface* searchtet, point endpt); + enum interresult scoutsegment(point, point, face*, triface*, point*, + arraypool*); + int getsteinerptonsegment(face* seg, point refpt, point steinpt); + void delaunizesegments(); + + int scoutsubface(face* searchsh,triface* searchtet,int shflag); + void formregion(face*, arraypool*, arraypool*, arraypool*); + int scoutcrossedge(triface& crosstet, arraypool*, arraypool*); + bool formcavity(triface*, arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + // Facet recovery by cavity re-triangulation [Si and Gaertner 2011]. + void delaunizecavity(arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + bool fillcavity(arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*, triface* crossedge); + void carvecavity(arraypool*, arraypool*, arraypool*); + void restorecavity(arraypool*, arraypool*, arraypool*, arraypool*); + // Facet recovery by flips [Shewchuk 2003]. + void flipcertify(triface *chkface, badface **pqueue, point, point, point); + void flipinsertfacet(arraypool*, arraypool*, arraypool*, arraypool*); + + int insertpoint_cdt(point, triface*, face*, face*, insertvertexflags*, + arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + void refineregion(face&, arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + void constrainedfacets(); + + void constraineddelaunay(clock_t&); + +//============================================================================// +// // +// Constrained tetrahedralizations. // +// // +//============================================================================// + + void sort_2pts(point p1, point p2, point ppt[2]); + void sort_3pts(point p1, point p2, point p3, point ppt[3]); + + bool is_collinear_at(point mid, point left, point right); + bool is_segment(point p1, point p2); + bool valid_constrained_f23(triface&, point pd, point pe); + bool valid_constrained_f32(triface*, point pa, point pb); + + int checkflipeligibility(int fliptype, point, point, point, point, point, + int level, int edgepivot, flipconstraints* fc); + + int removeedgebyflips(triface*, flipconstraints*); + int removefacebyflips(triface*, flipconstraints*); + + int recoveredgebyflips(point, point, face*, triface*, int fullsearch, int& idir); + int add_steinerpt_in_schoenhardtpoly(triface*, int, int, int chkencflag); + int add_steinerpt_in_segment(face*, int searchlevel, int& idir); + int add_steinerpt_to_recover_edge(point, point, face*, int, int, int& idir); + int recoversegments(arraypool*, int fullsearch, int steinerflag); + + int recoverfacebyflips(point,point,point,face*,triface*,int&,point*,point*); + int recoversubfaces(arraypool*, int steinerflag); + + int getvertexstar(int, point searchpt, arraypool*, arraypool*, arraypool*); + int getedge(point, point, triface*); + int reduceedgesatvertex(point startpt, arraypool* endptlist); + int removevertexbyflips(point steinerpt); + + int smoothpoint(point smtpt, arraypool*, int ccw, optparameters *opm); + int suppressbdrysteinerpoint(point steinerpt); + int suppresssteinerpoints(); + + void recoverboundary(clock_t&); + +//============================================================================// +// // +// Mesh reconstruction // +// // +//============================================================================// + + void carveholes(); + + void reconstructmesh(); + + int search_face(point p0, point p1, point p2, triface &tetloop); + int search_edge(point p0, point p1, triface &tetloop); + int scout_point(point, triface*, int randflag); + REAL getpointmeshsize(point, triface*, int iloc); + void interpolatemeshsize(); + + void insertconstrainedpoints(point *insertarray, int arylen, int rejflag); + void insertconstrainedpoints(tetgenio *addio); + + void collectremovepoints(arraypool *remptlist); + void meshcoarsening(); + +//============================================================================// +// // +// Mesh refinement // +// // +// The purpose of mesh refinement is to obtain a tetrahedral mesh with well- // +// -shaped tetrahedra and appropriate mesh size. It is necessary to insert // +// new Steiner points to achieve this property. The questions are (1) how to // +// choose the Steiner points? and (2) how to insert them? // +// // +// Delaunay refinement is a technique first developed by Chew [1989] and // +// Ruppert [1993, 1995] to generate quality triangular meshes in the plane. // +// It provides guarantee on the smallest angle of the triangles. Rupper's // +// algorithm guarantees that the mesh is size-optimal (to within a constant // +// factor) among all meshes with the same quality. // +// Shewchuk generalized Ruppert's algorithm into 3D in his PhD thesis // +// [Shewchuk 1997]. A short version of his algorithm appears in "Tetrahedral // +// Mesh Generation by Delaunay Refinement," In Proceedings of the 14th ACM // +// Symposium on Computational Geometry, 86-95, 1998. It guarantees that all // +// tetrahedra of the output mesh have a "radius-edge ratio" (equivalent to // +// the minimal face angle) bounded. However, it does not remove slivers, a // +// type of very flat tetrahedra which can have no small face angles but have // +// very small (and large) dihedral angles. Moreover, it may not terminate if // +// the input PLC contains "sharp features", e.g., two edges (or two facets) // +// meet at an acute angle (or dihedral angle). // +// // +// TetGen uses the basic Delaunay refinement scheme to insert Steiner points. // +// While it always maintains a constrained Delaunay mesh. The algorithm is // +// described in Si, H., "Adaptive Constrained Delaunay Mesh Generation," // +// International Journal for Numerical Methods in Engineering, 75:856-880. // +// This algorithm always terminates and sharp features are easily preserved. // +// The mesh has good quality (same as Shewchuk's Delaunay refinement algori- // +// thm) in the bulk of the mesh domain. Moreover, it supports the generation // +// of adaptive mesh according to a (isotropic) mesh sizing function. // +// // +//============================================================================// + + void makesegmentendpointsmap(); + REAL set_ridge_vertex_protecting_ball(point); + REAL get_min_angle_at_ridge_vertex(face* seg); + REAL get_min_diahedral_angle(face* seg); + void create_segment_info_list(); + + void makefacetverticesmap(); + void create_segment_facet_map(); + + int ridge_vertices_adjacent(point, point); + int facet_ridge_vertex_adjacent(face *, point); + int segsegadjacent(face *, face *); + int segfacetadjacent(face *checkseg, face *checksh); + int facetfacetadjacent(face *, face *); + bool is_sharp_segment(face* seg); + bool does_seg_contain_acute_vertex(face* seg); + bool create_a_shorter_edge(point steinerpt, point nearpt); + + void enqueuesubface(memorypool*, face*); + void enqueuetetrahedron(triface*); + + bool check_encroachment(point pa, point pb, point checkpt); + bool check_enc_segment(face *chkseg, point *pencpt); + bool get_steiner_on_segment(face* seg, point encpt, point newpt); + bool split_segment(face *splitseg, point encpt, REAL *param, int qflag, int, int*); + void repairencsegs(REAL *param, int qflag, int chkencflag); + + bool get_subface_ccent(face *chkfac, REAL *ccent); + bool check_enc_subface(face *chkfac, point *pencpt, REAL *ccent, REAL *radius); + bool check_subface(face *chkfac, REAL *ccent, REAL radius, REAL *param); + void enqueue_subface(face *bface, point encpt, REAL *ccent, REAL *param); + badface* top_subface(); + void dequeue_subface(); + void parallel_shift(point pa, point pb, point pc, point pt, REAL* ppt); + enum locateresult locate_on_surface(point searchpt, face* searchsh); + bool split_subface(face *splitfac, point encpt, REAL *ccent, REAL*, int, int, int*); + void repairencfacs(REAL *param, int qflag, int chkencflag); + + bool check_tetrahedron(triface *chktet, REAL* param, int& qflag); + bool checktet4split(triface *chktet, REAL* param, int& qflag); + enum locateresult locate_point_walk(point searchpt, triface*, int chkencflag); + bool split_tetrahedron(triface*, REAL*, int, int, insertvertexflags &ivf); + void repairbadtets(REAL queratio, int chkencflag); + + void delaunayrefinement(); + +//============================================================================// +// // +// Mesh optimization // +// // +//============================================================================// + + long lawsonflip3d(flipconstraints *fc); + void recoverdelaunay(); + + int get_seg_laplacian_center(point mesh_vert, REAL target[3]); + int get_surf_laplacian_center(point mesh_vert, REAL target[3]); + int get_laplacian_center(point mesh_vert, REAL target[3]); + bool move_vertex(point mesh_vert, REAL target[3]); + void smooth_vertices(); + + bool get_tet(point, point, point, point, triface *); + bool get_tetqual(triface *chktet, point oppo_pt, badface *bf); + bool get_tetqual(point, point, point, point, badface *bf); + void enqueue_badtet(badface *bf); + badface* top_badtet(); + void dequeue_badtet(); + + bool add_steinerpt_to_repair(badface *bf, bool bSmooth); + bool flip_edge_to_improve(triface *sliver_edge, REAL& improved_cosmaxd); + bool repair_tet(badface *bf, bool bFlips, bool bSmooth, bool bSteiners); + long repair_badqual_tets(bool bFlips, bool bSmooth, bool bSteiners); + void improve_mesh(); + +//============================================================================// +// // +// Mesh check and statistics // +// // +//============================================================================// + + // Mesh validations. + int check_mesh(int topoflag); + int check_shells(); + int check_segments(); + int check_delaunay(int perturb = 1); + int check_regular(int); + int check_conforming(int); + + // Mesh statistics. + void printfcomma(unsigned long n); + void qualitystatistics(); + void memorystatistics(); + void statistics(); + +//============================================================================// +// // +// Mesh output // +// // +//============================================================================// + + void jettisonnodes(); + void highorder(); + void indexelements(); + void numberedges(); + void outnodes(tetgenio*); + void outmetrics(tetgenio*); + void outelements(tetgenio*); + void outfaces(tetgenio*); + void outhullfaces(tetgenio*); + void outsubfaces(tetgenio*); + void outedges(tetgenio*); + void outsubsegments(tetgenio*); + void outneighbors(tetgenio*); + void outvoronoi(tetgenio*); + void outsmesh(char*); + void outmesh2medit(char*); + void outmesh2vtk(char*, int); + void out_surfmesh_vtk(char*, int); + void out_intersected_facets(); + + + + +//============================================================================// +// // +// Constructor & destructor // +// // +//============================================================================// + + void initializetetgenmesh() + { + in = addin = NULL; + b = NULL; + bgm = NULL; + + tetrahedrons = subfaces = subsegs = points = NULL; + tet2segpool = tet2subpool = NULL; + dummypoint = NULL; + + badtetrahedrons = badsubfacs = badsubsegs = NULL; + split_segments_pool = split_subfaces_pool = NULL; + unsplit_badtets = unsplit_subfaces = unsplit_segments = NULL; + check_tets_list = NULL; + badqual_tets_pool = NULL; + + stack_enc_segments = stack_enc_subfaces = NULL; + + flippool = NULL; + flipstack = unflip_queue_front = unflip_queue_tail = NULL; + later_unflip_queue = unflipqueue = NULL; + + cavetetlist = cavebdrylist = caveoldtetlist = NULL; + cave_oldtet_list = NULL; + cavetetshlist = cavetetseglist = cavetetvertlist = NULL; + caveencshlist = caveencseglist = NULL; + caveshlist = caveshbdlist = cavesegshlist = NULL; + + subsegstack = subfacstack = subvertstack = NULL; + skipped_segment_list = skipped_facet_list = NULL; + + encseglist = encshlist = NULL; + + number_of_facets = 0; + idx2facetlist = NULL; + facetverticeslist = NULL; + idx_segment_facet_list = NULL; + segment_facet_list = NULL; + idx_ridge_vertex_facet_list = NULL; + ridge_vertex_facet_list = NULL; + + segmentendpointslist_length = 0; + segmentendpointslist = NULL; + segment_info_list = NULL; + idx_segment_ridge_vertex_list = NULL; + segment_ridge_vertex_list = NULL; + + subdomains = 0; + subdomain_markers = NULL; + + numpointattrib = numelemattrib = 0; + sizeoftensor = 0; + pointmtrindex = 0; + pointparamindex = 0; + pointmarkindex = 0; + point2simindex = 0; + pointinsradiusindex = 0; + elemattribindex = 0; + polarindex = 0; + volumeboundindex = 0; + shmarkindex = 0; + areaboundindex = 0; + checksubsegflag = 0; + checksubfaceflag = 0; + boundary_recovery_flag = 0; + checkconstraints = 0; + nonconvex = 0; + autofliplinklevel = 1; + useinsertradius = 0; + samples = 0l; + randomseed = 1l; + minfaceang = minfacetdihed = PI; + cos_facet_separate_ang_tol = cos(179.9/180.*PI); + cos_collinear_ang_tol = cos(179.9/180.*PI); + tetprism_vol_sum = 0.0; + longest = minedgelength = 0.0; + xmax = xmin = ymax = ymin = zmax = zmin = 0.0; + + smallest_insradius = 1.e+30; + big_radius_edge_ratio = 100.0; + elem_limit = 0; + insert_point_count = 0l; + report_refine_progress = 0l; + last_point_count = 0l; + last_insertion_count = 0l; + + insegments = 0l; + hullsize = 0l; + meshedges = meshhulledges = 0l; + steinerleft = -1; + dupverts = 0l; + unuverts = 0l; + duplicated_facets_count = 0l; + nonregularcount = 0l; + st_segref_count = st_facref_count = st_volref_count = 0l; + fillregioncount = cavitycount = cavityexpcount = 0l; + flip14count = flip26count = flipn2ncount = 0l; + flip23count = flip32count = flip44count = flip41count = 0l; + flip22count = flip31count = 0l; + recover_delaunay_count = 0l; + opt_flips_count = opt_collapse_count = opt_smooth_count = 0l; + totalworkmemory = 0l; + + } // tetgenmesh() + + void freememory() + { + if (bgm != NULL) { + delete bgm; + } + + if (points != (memorypool *) NULL) { + delete points; + delete [] dummypoint; + } + if (tetrahedrons != (memorypool *) NULL) { + delete tetrahedrons; + } + if (subfaces != (memorypool *) NULL) { + delete subfaces; + delete subsegs; + } + if (tet2segpool != NULL) { + delete tet2segpool; + delete tet2subpool; + } + + if (badtetrahedrons) { + delete badtetrahedrons; + } + if (badsubfacs) { + delete badsubfacs; + } + if (badsubsegs) { + delete badsubsegs; + } + if (unsplit_badtets) { + delete unsplit_badtets; + } + if (check_tets_list) { + delete check_tets_list; + } + + if (flippool != NULL) { + delete flippool; + delete later_unflip_queue; + delete unflipqueue; + } + + if (cavetetlist != NULL) { + delete cavetetlist; + delete cavebdrylist; + delete caveoldtetlist; + delete cavetetvertlist; + delete cave_oldtet_list; + } + + if (caveshlist != NULL) { + delete caveshlist; + delete caveshbdlist; + delete cavesegshlist; + delete cavetetshlist; + delete cavetetseglist; + delete caveencshlist; + delete caveencseglist; + } + + if (subsegstack != NULL) { + delete subsegstack; + delete subfacstack; + delete subvertstack; + } + + if (idx2facetlist != NULL) { + delete [] idx2facetlist; + delete [] facetverticeslist; + delete [] idx_segment_facet_list; + delete [] segment_facet_list; + delete [] idx_ridge_vertex_facet_list; + delete [] ridge_vertex_facet_list; + } + + if (segmentendpointslist != NULL) { + delete [] segmentendpointslist; + delete [] idx_segment_ridge_vertex_list; + delete [] segment_ridge_vertex_list; + } + + if (segment_info_list != NULL) { + delete [] segment_info_list; + } + + if (subdomain_markers != NULL) { + delete [] subdomain_markers; + } + + initializetetgenmesh(); + } + + tetgenmesh() + { + initializetetgenmesh(); + } + + ~tetgenmesh() + { + freememory(); + } // ~tetgenmesh() + +}; // End of class tetgenmesh. + +//============================================================================// +// // +// tetrahedralize() Interface for using TetGen's library to generate // +// Delaunay tetrahedralizations, constrained Delaunay // +// tetrahedralizations, quality tetrahedral meshes. // +// // +// 'in' is an object of 'tetgenio' containing a PLC or a previously generated // +// tetrahedral mesh you want to refine. 'out' is another object of 'tetgenio'// +// for returing the generated tetrahedral mesh. If it is a NULL pointer, the // +// output mesh is saved to file(s). If 'bgmin' != NULL, it contains a back- // +// ground mesh defining a mesh size function. // +// // +//============================================================================// + +void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, + tetgenio *addin = NULL, tetgenio *bgmin = NULL); + +#ifdef TETLIBRARY +void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, + tetgenio *addin = NULL, tetgenio *bgmin = NULL); + +#endif // #ifdef TETLIBRARY + +//============================================================================// +// // +// terminatetetgen() Terminate TetGen with a given exit code. // +// // +//============================================================================// + + +inline void terminatetetgen(tetgenmesh *m, int x) +{ +#ifdef TETLIBRARY + throw x; +#else + switch (x) { + case 1: // Out of memory. + printf("Error: Out of memory.\n"); + break; + case 2: // Encounter an internal error. + printf("Please report this bug to Hang.Si@wias-berlin.de. Include\n"); + printf(" the message above, your input data set, and the exact\n"); + printf(" command line you used to run this program, thank you.\n"); + break; + case 3: + printf("The input surface mesh contain self-intersections. Program stopped.\n"); + //printf("Hint: use -d option to detect all self-intersections.\n"); + break; + case 4: + printf("A very small input feature size was detected. Program stopped.\n"); + if (m) { + printf("Hint: use -T option to set a smaller tolerance. Current is %g\n", + m->b->epsilon); + } + break; + case 5: + printf("Two very close input facets were detected. Program stopped.\n"); + printf("Hint: use -Y option to avoid adding Steiner points in boundary.\n"); + break; + case 10: + printf("An input error was detected. Program stopped.\n"); + break; + case 200: + printf("Boundary contains Steiner points (-YY option). Program stopped.\n"); + break; + } // switch (x) + exit(x); +#endif // #ifdef TETLIBRARY +} + +//============================================================================// +// // +// Primitives for tetrahedra // +// // +//============================================================================// + +// encode() compress a handle into a single pointer. It relies on the +// assumption that all addresses of tetrahedra are aligned to sixteen- +// byte boundaries, so that the last four significant bits are zero. + +inline tetgenmesh::tetrahedron tetgenmesh::encode(triface& t) { + return (tetrahedron) ((uintptr_t) (t).tet | (uintptr_t) (t).ver); +} + +inline tetgenmesh::tetrahedron tetgenmesh::encode2(tetrahedron* ptr, int ver) { + return (tetrahedron) ((uintptr_t) (ptr) | (uintptr_t) (ver)); +} + +// decode() converts a pointer to a handle. The version is extracted from +// the four least significant bits of the pointer. + +inline void tetgenmesh::decode(tetrahedron ptr, triface& t) { + (t).ver = (int) ((uintptr_t) (ptr) & (uintptr_t) 15); + (t).tet = (tetrahedron *) ((uintptr_t) (ptr) ^ (uintptr_t) (t).ver); +} + +inline tetgenmesh::tetrahedron* tetgenmesh::decode_tet_only(tetrahedron ptr) +{ + return (tetrahedron *) ((((uintptr_t) ptr) >> 4) << 4); +} + +inline int tetgenmesh::decode_ver_only(tetrahedron ptr) +{ + return (int) ((uintptr_t) (ptr) & (uintptr_t) 15); +} + +// bond() connects two tetrahedra together. (t1,v1) and (t2,v2) must +// refer to the same face and the same edge. + +inline void tetgenmesh::bond(triface& t1, triface& t2) { + t1.tet[t1.ver & 3] = encode2(t2.tet, bondtbl[t1.ver][t2.ver]); + t2.tet[t2.ver & 3] = encode2(t1.tet, bondtbl[t2.ver][t1.ver]); +} + + +// dissolve() a bond (from one side). + +inline void tetgenmesh::dissolve(triface& t) { + t.tet[t.ver & 3] = NULL; +} + +// enext() finds the next edge (counterclockwise) in the same face. + +inline void tetgenmesh::enext(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = enexttbl[t1.ver]; // (t1.ver + 4) % 12; +} + +inline void tetgenmesh::enextself(triface& t) { + t.ver = enexttbl[t.ver]; // (t.ver + 4) % 12; +} + +// eprev() finds the next edge (clockwise) in the same face. + +inline void tetgenmesh::eprev(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eprevtbl[t1.ver]; // (t1.ver + 8) % 12; +} + +inline void tetgenmesh::eprevself(triface& t) { + t.ver = eprevtbl[t.ver]; // (t.ver + 8) % 12; +} + +// esym() finds the reversed edge. It is in the other face of the +// same tetrahedron. + +inline void tetgenmesh::esym(triface& t1, triface& t2) { + (t2).tet = (t1).tet; + (t2).ver = esymtbl[(t1).ver]; +} + +inline void tetgenmesh::esymself(triface& t) { + (t).ver = esymtbl[(t).ver]; +} + +// enextesym() finds the reversed edge of the next edge. It is in the other +// face of the same tetrahedron. It is the combination esym() * enext(). + +inline void tetgenmesh::enextesym(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = enextesymtbl[t1.ver]; +} + +inline void tetgenmesh::enextesymself(triface& t) { + t.ver = enextesymtbl[t.ver]; +} + +// eprevesym() finds the reversed edge of the previous edge. + +inline void tetgenmesh::eprevesym(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eprevesymtbl[t1.ver]; +} + +inline void tetgenmesh::eprevesymself(triface& t) { + t.ver = eprevesymtbl[t.ver]; +} + +// eorgoppo() Finds the opposite face of the origin of the current edge. +// Return the opposite edge of the current edge. + +inline void tetgenmesh::eorgoppo(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eorgoppotbl[t1.ver]; +} + +inline void tetgenmesh::eorgoppoself(triface& t) { + t.ver = eorgoppotbl[t.ver]; +} + +// edestoppo() Finds the opposite face of the destination of the current +// edge. Return the opposite edge of the current edge. + +inline void tetgenmesh::edestoppo(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = edestoppotbl[t1.ver]; +} + +inline void tetgenmesh::edestoppoself(triface& t) { + t.ver = edestoppotbl[t.ver]; +} + +// fsym() finds the adjacent tetrahedron at the same face and the same edge. + +inline void tetgenmesh::fsym(triface& t1, triface& t2) { + decode((t1).tet[(t1).ver & 3], t2); + t2.ver = fsymtbl[t1.ver][t2.ver]; +} + + +#define fsymself(t) \ + t1ver = (t).ver; \ + decode((t).tet[(t).ver & 3], (t));\ + (t).ver = fsymtbl[t1ver][(t).ver] + +// fnext() finds the next face while rotating about an edge according to +// a right-hand rule. The face is in the adjacent tetrahedron. It is +// the combination: fsym() * esym(). + +inline void tetgenmesh::fnext(triface& t1, triface& t2) { + decode(t1.tet[facepivot1[t1.ver]], t2); + t2.ver = facepivot2[t1.ver][t2.ver]; +} + + +#define fnextself(t) \ + t1ver = (t).ver; \ + decode((t).tet[facepivot1[(t).ver]], (t)); \ + (t).ver = facepivot2[t1ver][(t).ver] + + +// The following primtives get or set the origin, destination, face apex, +// or face opposite of an ordered tetrahedron. + +inline tetgenmesh::point tetgenmesh::org(triface& t) { + return (point) (t).tet[orgpivot[(t).ver]]; +} + +inline tetgenmesh::point tetgenmesh:: dest(triface& t) { + return (point) (t).tet[destpivot[(t).ver]]; +} + +inline tetgenmesh::point tetgenmesh:: apex(triface& t) { + return (point) (t).tet[apexpivot[(t).ver]]; +} + +inline tetgenmesh::point tetgenmesh:: oppo(triface& t) { + return (point) (t).tet[oppopivot[(t).ver]]; +} + +inline void tetgenmesh:: setorg(triface& t, point p) { + (t).tet[orgpivot[(t).ver]] = (tetrahedron) (p); +} + +inline void tetgenmesh:: setdest(triface& t, point p) { + (t).tet[destpivot[(t).ver]] = (tetrahedron) (p); +} + +inline void tetgenmesh:: setapex(triface& t, point p) { + (t).tet[apexpivot[(t).ver]] = (tetrahedron) (p); +} + +inline void tetgenmesh:: setoppo(triface& t, point p) { + (t).tet[oppopivot[(t).ver]] = (tetrahedron) (p); +} + +#define setvertices(t, torg, tdest, tapex, toppo) \ + (t).tet[orgpivot[(t).ver]] = (tetrahedron) (torg);\ + (t).tet[destpivot[(t).ver]] = (tetrahedron) (tdest); \ + (t).tet[apexpivot[(t).ver]] = (tetrahedron) (tapex); \ + (t).tet[oppopivot[(t).ver]] = (tetrahedron) (toppo) + + +inline REAL* tetgenmesh::get_polar(tetrahedron* ptr) +{ + return &(((REAL *) (ptr))[polarindex]); +} +inline REAL tetgenmesh::get_volume(tetrahedron* ptr) +{ + return ((REAL *) (ptr))[polarindex + 4]; +} + +// Check or set a tetrahedron's attributes. + +inline REAL tetgenmesh::elemattribute(tetrahedron* ptr, int attnum) { + return ((REAL *) (ptr))[elemattribindex + attnum]; +} + +inline void tetgenmesh::setelemattribute(tetrahedron* ptr, int attnum, + REAL value) { + ((REAL *) (ptr))[elemattribindex + attnum] = value; +} + +// Check or set a tetrahedron's maximum volume bound. + +inline REAL tetgenmesh::volumebound(tetrahedron* ptr) { + return ((REAL *) (ptr))[volumeboundindex]; +} + +inline void tetgenmesh::setvolumebound(tetrahedron* ptr, REAL value) { + ((REAL *) (ptr))[volumeboundindex] = value; +} + +// Get or set a tetrahedron's index (only used for output). +// These two routines use the reserved slot ptr[10]. + +inline int tetgenmesh::elemindex(tetrahedron* ptr) { + int *iptr = (int *) &(ptr[10]); + return iptr[0]; +} + +inline void tetgenmesh::setelemindex(tetrahedron* ptr, int value) { + int *iptr = (int *) &(ptr[10]); + iptr[0] = value; +} + +// Get or set a tetrahedron's marker. +// Set 'value = 0' cleans all the face/edge flags. + +inline int tetgenmesh::elemmarker(tetrahedron* ptr) { + return ((int *) (ptr))[elemmarkerindex]; +} + +inline void tetgenmesh::setelemmarker(tetrahedron* ptr, int value) { + ((int *) (ptr))[elemmarkerindex] = value; +} + +// infect(), infected(), uninfect() -- primitives to flag or unflag a +// tetrahedron. The last bit of the element marker is flagged (1) +// or unflagged (0). + +inline void tetgenmesh::infect(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= 1; +} + +inline void tetgenmesh::uninfect(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~1; +} + +inline bool tetgenmesh::infected(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & 1) != 0; +} + +// marktest(), marktested(), unmarktest() -- primitives to flag or unflag a +// tetrahedron. Use the second lowerest bit of the element marker. + +inline void tetgenmesh::marktest(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= 2; +} + +inline void tetgenmesh::unmarktest(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~2; +} + +inline bool tetgenmesh::marktested(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & 2) != 0; +} + +// markface(), unmarkface(), facemarked() -- primitives to flag or unflag a +// face of a tetrahedron. From the last 3rd to 6th bits are used for +// face markers, e.g., the last third bit corresponds to loc = 0. + +inline void tetgenmesh::markface(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (4 << (t.ver & 3)); +} + +inline void tetgenmesh::unmarkface(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(4 << (t.ver & 3)); +} + +inline bool tetgenmesh::facemarked(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & (4 << (t.ver & 3))) != 0; +} + +// markedge(), unmarkedge(), edgemarked() -- primitives to flag or unflag an +// edge of a tetrahedron. From the last 7th to 12th bits are used for +// edge markers, e.g., the last 7th bit corresponds to the 0th edge, etc. +// Remark: The last 7th bit is marked by 2^6 = 64. + +inline void tetgenmesh::markedge(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (int) (64 << ver2edge[(t).ver]); +} + +inline void tetgenmesh::unmarkedge(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(int) (64 << ver2edge[(t).ver]); +} + +inline bool tetgenmesh::edgemarked(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & + (int) (64 << ver2edge[(t).ver])) != 0; +} + +// marktest2(), unmarktest2(), marktest2ed() -- primitives to flag and unflag +// a tetrahedron. The 13th bit (2^12 = 4096) is used for this flag. + +inline void tetgenmesh::marktest2(triface& t) { + ((int *) (t.tet))[elemmarkerindex] |= (int) (4096); +} + +inline void tetgenmesh::unmarktest2(triface& t) { + ((int *) (t.tet))[elemmarkerindex] &= ~(int) (4096); +} + +inline bool tetgenmesh::marktest2ed(triface& t) { + return (((int *) (t.tet))[elemmarkerindex] & (int) (4096)) != 0; +} + +// elemcounter(), setelemcounter() -- primitives to read or ser a (small) +// integer counter in this tet. It is saved from the 16th bit. On 32 bit +// system, the range of the counter is [0, 2^15 = 32768]. + +inline int tetgenmesh::elemcounter(triface& t) { + return (((int *) (t.tet))[elemmarkerindex]) >> 16; +} + +inline void tetgenmesh::setelemcounter(triface& t, int value) { + int c = ((int *) (t.tet))[elemmarkerindex]; + // Clear the old counter while keep the other flags. + c &= 65535; // sum_{i=0^15} 2^i + c |= (value << 16); + ((int *) (t.tet))[elemmarkerindex] = c; +} + +inline void tetgenmesh::increaseelemcounter(triface& t) { + int c = elemcounter(t); + setelemcounter(t, c + 1); +} + +inline void tetgenmesh::decreaseelemcounter(triface& t) { + int c = elemcounter(t); + setelemcounter(t, c - 1); +} + +// ishulltet() tests if t is a hull tetrahedron. + +inline bool tetgenmesh::ishulltet(triface& t) { + return (point) (t).tet[7] == dummypoint; +} + +// isdeadtet() tests if t is a tetrahedron is dead. + +inline bool tetgenmesh::isdeadtet(triface& t) { + return ((t.tet == NULL) || (t.tet[4] == NULL)); +} + +//============================================================================// +// // +// Primitives for subfaces and subsegments // +// // +//============================================================================// + +// Each subface contains three pointers to its neighboring subfaces, with +// edge versions. To save memory, both information are kept in a single +// pointer. To make this possible, all subfaces are aligned to eight-byte +// boundaries, so that the last three bits of each pointer are zeros. An +// edge version (in the range 0 to 5) is compressed into the last three +// bits of each pointer by 'sencode()'. 'sdecode()' decodes a pointer, +// extracting an edge version and a pointer to the beginning of a subface. + +inline void tetgenmesh::sdecode(shellface sptr, face& s) { + s.shver = (int) ((uintptr_t) (sptr) & (uintptr_t) 7); + s.sh = (shellface *) ((uintptr_t) (sptr) ^ (uintptr_t) (s.shver)); +} + +inline tetgenmesh::shellface tetgenmesh::sencode(face& s) { + return (shellface) ((uintptr_t) s.sh | (uintptr_t) s.shver); +} + +inline tetgenmesh::shellface tetgenmesh::sencode2(shellface *sh, int shver) { + return (shellface) ((uintptr_t) sh | (uintptr_t) shver); +} + +// sbond() bonds two subfaces (s1) and (s2) together. s1 and s2 must refer +// to the same edge. No requirement is needed on their orientations. + +inline void tetgenmesh::sbond(face& s1, face& s2) +{ + s1.sh[s1.shver >> 1] = sencode(s2); + s2.sh[s2.shver >> 1] = sencode(s1); +} + +// sbond1() bonds s1 <== s2, i.e., after bonding, s1 is pointing to s2, +// but s2 is not pointing to s1. s1 and s2 must refer to the same edge. +// No requirement is needed on their orientations. + +inline void tetgenmesh::sbond1(face& s1, face& s2) +{ + s1.sh[s1.shver >> 1] = sencode(s2); +} + +// Dissolve a subface bond (from one side). Note that the other subface +// will still think it's connected to this subface. + +inline void tetgenmesh::sdissolve(face& s) +{ + s.sh[s.shver >> 1] = NULL; +} + +// spivot() finds the adjacent subface (s2) for a given subface (s1). +// s1 and s2 share at the same edge. + +inline void tetgenmesh::spivot(face& s1, face& s2) +{ + shellface sptr = s1.sh[s1.shver >> 1]; + sdecode(sptr, s2); +} + +inline void tetgenmesh::spivotself(face& s) +{ + shellface sptr = s.sh[s.shver >> 1]; + sdecode(sptr, s); +} + +// These primitives determine or set the origin, destination, or apex +// of a subface with respect to the edge version. + +inline tetgenmesh::point tetgenmesh::sorg(face& s) +{ + return (point) s.sh[sorgpivot[s.shver]]; +} + +inline tetgenmesh::point tetgenmesh::sdest(face& s) +{ + return (point) s.sh[sdestpivot[s.shver]]; +} + +inline tetgenmesh::point tetgenmesh::sapex(face& s) +{ + return (point) s.sh[sapexpivot[s.shver]]; +} + +inline void tetgenmesh::setsorg(face& s, point pointptr) +{ + s.sh[sorgpivot[s.shver]] = (shellface) pointptr; +} + +inline void tetgenmesh::setsdest(face& s, point pointptr) +{ + s.sh[sdestpivot[s.shver]] = (shellface) pointptr; +} + +inline void tetgenmesh::setsapex(face& s, point pointptr) +{ + s.sh[sapexpivot[s.shver]] = (shellface) pointptr; +} + +#define setshvertices(s, pa, pb, pc)\ + setsorg(s, pa);\ + setsdest(s, pb);\ + setsapex(s, pc) + +// sesym() reserves the direction of the lead edge. + +inline void tetgenmesh::sesym(face& s1, face& s2) +{ + s2.sh = s1.sh; + s2.shver = (s1.shver ^ 1); // Inverse the last bit. +} + +inline void tetgenmesh::sesymself(face& s) +{ + s.shver ^= 1; +} + +// senext() finds the next edge (counterclockwise) in the same orientation +// of this face. + +inline void tetgenmesh::senext(face& s1, face& s2) +{ + s2.sh = s1.sh; + s2.shver = snextpivot[s1.shver]; +} + +inline void tetgenmesh::senextself(face& s) +{ + s.shver = snextpivot[s.shver]; +} + +inline void tetgenmesh::senext2(face& s1, face& s2) +{ + s2.sh = s1.sh; + s2.shver = snextpivot[snextpivot[s1.shver]]; +} + +inline void tetgenmesh::senext2self(face& s) +{ + s.shver = snextpivot[snextpivot[s.shver]]; +} + + +// Check or set a subface's maximum area bound. + +inline REAL tetgenmesh::areabound(face& s) +{ + return ((REAL *) (s.sh))[areaboundindex]; +} + +inline void tetgenmesh::setareabound(face& s, REAL value) +{ + ((REAL *) (s.sh))[areaboundindex] = value; +} + +// These two primitives read or set a shell marker. Shell markers are used +// to hold user boundary information. + +inline int tetgenmesh::shellmark(face& s) +{ + return ((int *) (s.sh))[shmarkindex]; +} + +inline void tetgenmesh::setshellmark(face& s, int value) +{ + ((int *) (s.sh))[shmarkindex] = value; +} + + + +// sinfect(), sinfected(), suninfect() -- primitives to flag or unflag a +// subface. The last bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. + +inline void tetgenmesh::sinfect(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *) ((s).sh))[shmarkindex+1] | (int) 1); +} + +inline void tetgenmesh::suninfect(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *) ((s).sh))[shmarkindex+1] & ~(int) 1); +} + +// Test a subface for viral infection. + +inline bool tetgenmesh::sinfected(face& s) +{ + return (((int *) ((s).sh))[shmarkindex+1] & (int) 1) != 0; +} + +// smarktest(), smarktested(), sunmarktest() -- primitives to flag or unflag +// a subface. The last 2nd bit of the integer is flagged. + +inline void tetgenmesh::smarktest(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 2); +} + +inline void tetgenmesh::sunmarktest(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)2); +} + +inline bool tetgenmesh::smarktested(face& s) +{ + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 2) != 0); +} + +// smarktest2(), smarktest2ed(), sunmarktest2() -- primitives to flag or +// unflag a subface. The last 3rd bit of the integer is flagged. + +inline void tetgenmesh::smarktest2(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 4); +} + +inline void tetgenmesh::sunmarktest2(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)4); +} + +inline bool tetgenmesh::smarktest2ed(face& s) +{ + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 4) != 0); +} + +// The last 4th bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. + +inline void tetgenmesh::smarktest3(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] | (int) 8); +} + +inline void tetgenmesh::sunmarktest3(face& s) +{ + ((int *) ((s).sh))[shmarkindex+1] = + (((int *)((s).sh))[shmarkindex+1] & ~(int)8); +} + +inline bool tetgenmesh::smarktest3ed(face& s) +{ + return ((((int *) ((s).sh))[shmarkindex+1] & (int) 8) != 0); +} + + +// Each facet has a unique index (automatically indexed). Starting from '0'. +// We save this index in the same field of the shell type. + +inline void tetgenmesh::setfacetindex(face& s, int value) +{ + ((int *) (s.sh))[shmarkindex + 2] = value; +} + +inline int tetgenmesh::getfacetindex(face& s) +{ + return ((int *) (s.sh))[shmarkindex + 2]; +} + +// Tests if the subface (subsegment) s is dead. + +inline bool tetgenmesh::isdeadsh(face& s) { + return ((s.sh == NULL) || (s.sh[3] == NULL)); +} + +//============================================================================// +// // +// Primitives for interacting between tetrahedra and subfaces // +// // +//============================================================================// + +// tsbond() bond a tetrahedron (t) and a subface (s) together. +// Note that t and s must be the same face and the same edge. Moreover, +// t and s have the same orientation. +// Since the edge number in t and in s can be any number in {0,1,2}. We bond +// the edge in s which corresponds to t's 0th edge, and vice versa. + +inline void tetgenmesh::tsbond(triface& t, face& s) +{ + if ((t).tet[9] == NULL) { + // Allocate space for this tet. + (t).tet[9] = (tetrahedron) tet2subpool->alloc(); + // Initialize. + for (int i = 0; i < 4; i++) { + ((shellface *) (t).tet[9])[i] = NULL; + } + } + // Bond t <== s. + ((shellface *) (t).tet[9])[(t).ver & 3] = + sencode2((s).sh, tsbondtbl[t.ver][s.shver]); + // Bond s <== t. + s.sh[9 + ((s).shver & 1)] = + (shellface) encode2((t).tet, stbondtbl[t.ver][s.shver]); +} + +// tspivot() finds a subface (s) abutting on the given tetrahdera (t). +// Return s.sh = NULL if there is no subface at t. Otherwise, return +// the subface s, and s and t must be at the same edge wth the same +// orientation. + +inline void tetgenmesh::tspivot(triface& t, face& s) +{ + if ((t).tet[9] == NULL) { + (s).sh = NULL; + return; + } + // Get the attached subface s. + sdecode(((shellface *) (t).tet[9])[(t).ver & 3], (s)); + (s).shver = tspivottbl[t.ver][s.shver]; +} + +// Quickly check if the handle (t, v) is a subface. +#define issubface(t) \ + ((t).tet[9] && ((t).tet[9])[(t).ver & 3]) + +// stpivot() finds a tetrahedron (t) abutting a given subface (s). +// Return the t (if it exists) with the same edge and the same +// orientation of s. + +inline void tetgenmesh::stpivot(face& s, triface& t) +{ + decode((tetrahedron) s.sh[9 + (s.shver & 1)], t); + if ((t).tet == NULL) { + return; + } + (t).ver = stpivottbl[t.ver][s.shver]; +} + +// Quickly check if this subface is attached to a tetrahedron. + +#define isshtet(s) \ + ((s).sh[9 + ((s).shver & 1)]) + +// tsdissolve() dissolve a bond (from the tetrahedron side). + +inline void tetgenmesh::tsdissolve(triface& t) +{ + if ((t).tet[9] != NULL) { + ((shellface *) (t).tet[9])[(t).ver & 3] = NULL; + } +} + +// stdissolve() dissolve a bond (from the subface side). + +inline void tetgenmesh::stdissolve(face& s) +{ + (s).sh[9] = NULL; + (s).sh[10] = NULL; +} + +//============================================================================// +// // +// Primitives for interacting between subfaces and segments // +// // +//============================================================================// + +// ssbond() bond a subface to a subsegment. + +inline void tetgenmesh::ssbond(face& s, face& edge) +{ + s.sh[6 + (s.shver >> 1)] = sencode(edge); + edge.sh[0] = sencode(s); +} + +inline void tetgenmesh::ssbond1(face& s, face& edge) +{ + s.sh[6 + (s.shver >> 1)] = sencode(edge); + //edge.sh[0] = sencode(s); +} + +// ssdisolve() dissolve a bond (from the subface side) + +inline void tetgenmesh::ssdissolve(face& s) +{ + s.sh[6 + (s.shver >> 1)] = NULL; +} + +// sspivot() finds a subsegment abutting a subface. + +inline void tetgenmesh::sspivot(face& s, face& edge) +{ + sdecode((shellface) s.sh[6 + (s.shver >> 1)], edge); +} + +// Quickly check if the edge is a subsegment. + +#define isshsubseg(s) \ + ((s).sh[6 + ((s).shver >> 1)]) + +//============================================================================// +// // +// Primitives for interacting between tetrahedra and segments // +// // +//============================================================================// + +inline void tetgenmesh::tssbond1(triface& t, face& s) +{ + if ((t).tet[8] == NULL) { + // Allocate space for this tet. + (t).tet[8] = (tetrahedron) tet2segpool->alloc(); + // Initialization. + for (int i = 0; i < 6; i++) { + ((shellface *) (t).tet[8])[i] = NULL; + } + } + ((shellface *) (t).tet[8])[ver2edge[(t).ver]] = sencode((s)); +} + +inline void tetgenmesh::sstbond1(face& s, triface& t) +{ + ((tetrahedron *) (s).sh)[9] = encode(t); +} + +inline void tetgenmesh::tssdissolve1(triface& t) +{ + if ((t).tet[8] != NULL) { + ((shellface *) (t).tet[8])[ver2edge[(t).ver]] = NULL; + } +} + +inline void tetgenmesh::sstdissolve1(face& s) +{ + ((tetrahedron *) (s).sh)[9] = NULL; +} + +inline void tetgenmesh::tsspivot1(triface& t, face& s) +{ + if ((t).tet[8] != NULL) { + sdecode(((shellface *) (t).tet[8])[ver2edge[(t).ver]], s); + } else { + (s).sh = NULL; + } +} + +// Quickly check whether 't' is a segment or not. + +#define issubseg(t) \ + ((t).tet[8] && ((t).tet[8])[ver2edge[(t).ver]]) + +inline void tetgenmesh::sstpivot1(face& s, triface& t) +{ + decode((tetrahedron) s.sh[9], t); +} + +//============================================================================// +// // +// Primitives for points // +// // +//============================================================================// + +inline int tetgenmesh::pointmark(point pt) { + return ((int *) (pt))[pointmarkindex]; +} + +inline void tetgenmesh::setpointmark(point pt, int value) { + ((int *) (pt))[pointmarkindex] = value; +} + + +// These two primitives set and read the type of the point. + +inline enum tetgenmesh::verttype tetgenmesh::pointtype(point pt) { + return (enum verttype) (((int *) (pt))[pointmarkindex + 1] >> (int) 8); +} + +inline void tetgenmesh::setpointtype(point pt, enum verttype value) { + ((int *) (pt))[pointmarkindex + 1] = + ((int) value << 8) + (((int *) (pt))[pointmarkindex + 1] & (int) 255); +} + +// pinfect(), puninfect(), pinfected() -- primitives to flag or unflag +// a point. The last bit of the integer '[pointindex+1]' is flagged. + +inline void tetgenmesh::pinfect(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 1; +} + +inline void tetgenmesh::puninfect(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 1; +} + +inline bool tetgenmesh::pinfected(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 1) != 0; +} + +// pmarktest(), punmarktest(), pmarktested() -- more primitives to +// flag or unflag a point. + +inline void tetgenmesh::pmarktest(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 2; +} + +inline void tetgenmesh::punmarktest(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 2; +} + +inline bool tetgenmesh::pmarktested(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 2) != 0; +} + +inline void tetgenmesh::pmarktest2(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 4; +} + +inline void tetgenmesh::punmarktest2(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 4; +} + +inline bool tetgenmesh::pmarktest2ed(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 4) != 0; +} + +inline void tetgenmesh::pmarktest3(point pt) { + ((int *) (pt))[pointmarkindex + 1] |= (int) 8; +} + +inline void tetgenmesh::punmarktest3(point pt) { + ((int *) (pt))[pointmarkindex + 1] &= ~(int) 8; +} + +inline bool tetgenmesh::pmarktest3ed(point pt) { + return (((int *) (pt))[pointmarkindex + 1] & (int) 8) != 0; +} + +// Read and set the geometry tag of the point (used by -s option). + +inline int tetgenmesh::pointgeomtag(point pt) { + return ((int *) (pt))[pointmarkindex + 2]; +} + +inline void tetgenmesh::setpointgeomtag(point pt, int value) { + ((int *) (pt))[pointmarkindex + 2] = value; +} + +// Read and set the u,v coordinates of the point (used by -s option). + +inline REAL tetgenmesh::pointgeomuv(point pt, int i) { + return pt[pointparamindex + i]; +} + +inline void tetgenmesh::setpointgeomuv(point pt, int i, REAL value) { + pt[pointparamindex + i] = value; +} + + + +// These following primitives set and read a pointer to a tetrahedron +// a subface/subsegment, a point, or a tet of background mesh. + +inline tetgenmesh::tetrahedron tetgenmesh::point2tet(point pt) { + return ((tetrahedron *) (pt))[point2simindex]; +} + +inline void tetgenmesh::setpoint2tet(point pt, tetrahedron value) { + ((tetrahedron *) (pt))[point2simindex] = value; +} + +inline tetgenmesh::point tetgenmesh::point2ppt(point pt) { + return (point) ((tetrahedron *) (pt))[point2simindex + 1]; +} + +inline void tetgenmesh::setpoint2ppt(point pt, point value) { + ((tetrahedron *) (pt))[point2simindex + 1] = (tetrahedron) value; +} + +inline tetgenmesh::shellface tetgenmesh::point2sh(point pt) { + return (shellface) ((tetrahedron *) (pt))[point2simindex + 2]; +} + +inline void tetgenmesh::setpoint2sh(point pt, shellface value) { + ((tetrahedron *) (pt))[point2simindex + 2] = (tetrahedron) value; +} + + +inline tetgenmesh::tetrahedron tetgenmesh::point2bgmtet(point pt) { + return ((tetrahedron *) (pt))[point2simindex + 3]; +} + +inline void tetgenmesh::setpoint2bgmtet(point pt, tetrahedron value) { + ((tetrahedron *) (pt))[point2simindex + 3] = value; +} + + +// The primitives for saving and getting the insertion radius. +inline void tetgenmesh::setpointinsradius(point pt, REAL value) +{ + pt[pointinsradiusindex] = value; +} + +inline REAL tetgenmesh::getpointinsradius(point pt) +{ + return pt[pointinsradiusindex]; +} + +inline bool tetgenmesh::issteinerpoint(point pt) { + return (pointtype(pt) == FREESEGVERTEX) || (pointtype(pt) == FREEFACETVERTEX) + || (pointtype(pt) == FREEVOLVERTEX); +} + +// point2tetorg() Get the tetrahedron whose origin is the point. + +inline void tetgenmesh::point2tetorg(point pa, triface& searchtet) +{ + decode(point2tet(pa), searchtet); + if ((point) searchtet.tet[4] == pa) { + searchtet.ver = 11; + } else if ((point) searchtet.tet[5] == pa) { + searchtet.ver = 3; + } else if ((point) searchtet.tet[6] == pa) { + searchtet.ver = 7; + } else { + searchtet.ver = 0; + } +} + +// point2shorg() Get the subface/segment whose origin is the point. + +inline void tetgenmesh::point2shorg(point pa, face& searchsh) +{ + sdecode(point2sh(pa), searchsh); + if ((point) searchsh.sh[3] == pa) { + searchsh.shver = 0; + } else if ((point) searchsh.sh[4] == pa) { + searchsh.shver = (searchsh.sh[5] != NULL ? 2 : 1); + } else { + searchsh.shver = 4; + } +} + +// farsorg() Return the origin of the subsegment. +// farsdest() Return the destination of the subsegment. + +inline tetgenmesh::point tetgenmesh::farsorg(face& s) +{ + face travesh, neighsh; + + travesh = s; + while (1) { + senext2(travesh, neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (sorg(neighsh) != sorg(travesh)) sesymself(neighsh); + senext2(neighsh, travesh); + } + return sorg(travesh); +} + +inline tetgenmesh::point tetgenmesh::farsdest(face& s) +{ + face travesh, neighsh; + + travesh = s; + while (1) { + senext(travesh, neighsh); + spivotself(neighsh); + if (neighsh.sh == NULL) break; + if (sdest(neighsh) != sdest(travesh)) sesymself(neighsh); + senext(neighsh, travesh); + } + return sdest(travesh); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Linear algebra operators. // +// // +/////////////////////////////////////////////////////////////////////////////// + +// dot() returns the dot product: v1 dot v2. +inline REAL tetgenmesh::dot(REAL* v1, REAL* v2) +{ + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} + +// cross() computes the cross product: n = v1 cross v2. +inline void tetgenmesh::cross(REAL* v1, REAL* v2, REAL* n) +{ + n[0] = v1[1] * v2[2] - v2[1] * v1[2]; + n[1] = -(v1[0] * v2[2] - v2[0] * v1[2]); + n[2] = v1[0] * v2[1] - v2[0] * v1[1]; +} + +// distance() computes the Euclidean distance between two points. +inline REAL tetgenmesh::distance(REAL* p1, REAL* p2) +{ + return sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + + (p2[1] - p1[1]) * (p2[1] - p1[1]) + + (p2[2] - p1[2]) * (p2[2] - p1[2])); +} + +inline REAL tetgenmesh::distance2(REAL* p1, REAL* p2) +{ + return norm2(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]); +} + +inline REAL tetgenmesh::norm2(REAL x, REAL y, REAL z) +{ + return (x) * (x) + (y) * (y) + (z) * (z); +} + + + +#endif // #ifndef tetgenH + diff --git a/cocos/main/EmptyMain.cpp b/cocos/main/EmptyMain.cpp new file mode 100644 index 0000000..433b779 --- /dev/null +++ b/cocos/main/EmptyMain.cpp @@ -0,0 +1,30 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include + +#include "platform/BasePlatform.h" + +int main(int argc, char **argv) { + START_PLATFORM(argc, (const char **)argv); +} \ No newline at end of file diff --git a/cocos/math/Color.cpp b/cocos/math/Color.cpp new file mode 100644 index 0000000..6d97d68 --- /dev/null +++ b/cocos/math/Color.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + +http://www.cocos.com + +Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Color.h" + +namespace cc { + +Color::Color() +: r(0), + g(0), + b(0), + a(0) { +} + +Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +: r(r), + g(g), + b(b), + a(a) { +} + +Color::Color(const uint8_t *src) { + set(src); +} + +Color::Color(uint32_t val) { + set(val); +} + +Color::Color(const Color &p1, const Color &p2) { + set(p1, p2); +} + +Color::Color(const Color ©) { + set(copy); +} + +void Color::set(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + this->r = r; + this->g = g; + this->b = b; + this->a = a; +} + +void Color::set(const uint8_t *array) { + r = array[0]; + g = array[1]; + b = array[2]; + a = array[3]; +} + +void Color::set(uint32_t val) { + r = val & 0x000000FF; + g = (val & 0x0000FF00) >> 8; + b = (val & 0x00FF0000) >> 16; + a = (val & 0xFF000000) >> 24; +} + +void Color::set(const Color &c) { + this->r = c.r; + this->g = c.g; + this->b = c.b; + this->a = c.a; +} + +void Color::set(const Color &p1, const Color &p2) { + r = p2.r - p1.r; + g = p2.g - p1.g; + b = p2.b - p1.b; + a = p2.a - p1.a; +} + +Vec4 Color::toVec4() const { + return {static_cast(r) / 255.F, static_cast(g) / 255.F, static_cast(b) / 255.F, static_cast(a) / 255.F}; +} + +} // namespace cc diff --git a/cocos/math/Color.h b/cocos/math/Color.h new file mode 100644 index 0000000..2a1f5ec --- /dev/null +++ b/cocos/math/Color.h @@ -0,0 +1,118 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + +http://www.cocos.com + +Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +#include "math/Vec4.h" + +namespace cc { + +class Color { +public: + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + /** + * Constructs a new color initialized to all zeros. + */ + Color(); + + /** + * Constructs a new color initialized to the specified values. + * + * @param xx The x coordinate. + * @param yy The y coordinate. + * @param zz The z coordinate. + * @param ww The w coordinate. + */ + explicit Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + + /** + * Constructs a new color from the values in the specified array. + * + * @param array An array containing the elements of the color in the order r, g, b, a. + */ + explicit Color(const uint8_t *src); + + explicit Color(uint32_t val); + + /** + * Constructs a color that describes the direction between the specified points. + * + * @param p1 The first point. + * @param p2 The second point. + */ + Color(const Color &p1, const Color &p2); + + /** + * Constructor. + * + * Creates a new color that is a copy of the specified color. + * + * @param copy The color to copy. + */ + Color(const Color ©); + /** + * Sets the elements of this color to the specified values. + * + * @param xx The new x coordinate. + * @param yy The new y coordinate. + * @param zz The new z coordinate. + * @param ww The new w coordinate. + */ + void set(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + + /** + * Sets the elements of this color from the values in the specified array. + * + * @param array An array containing the elements of the color in the order r, g, b, a. + */ + void set(const uint8_t *array); + + void set(uint32_t val); + + /** + * Sets the elements of this color to those in the specified color. + * + * @param v The color to copy. + */ + void set(const Color &c); + + /** + * Sets this color to the directional color between the specified points. + * + * @param p1 The first point. + * @param p2 The second point. + */ + void set(const Color &p1, const Color &p2); + + Vec4 toVec4() const; +}; + +} // namespace cc diff --git a/cocos/math/Geometry.cpp b/cocos/math/Geometry.cpp new file mode 100644 index 0000000..e65bd18 --- /dev/null +++ b/cocos/math/Geometry.cpp @@ -0,0 +1,241 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "math/Geometry.h" + +#include +#include +#include "base/Macros.h" + +// implementation of Vec2 +namespace cc { + +// implementation of Size + +Size::Size() = default; + +Size::Size(float w, float h) : width(w), + height(h) { +} + +Size::Size(const Size &other) = default; + +Size::Size(const Vec2 &point) : width(point.x), + height(point.y) { +} + +Size &Size::operator=(const Size &other) { + setSize(other.width, other.height); + return *this; +} + +Size &Size::operator=(const Vec2 &point) { + setSize(point.x, point.y); + return *this; +} + +Size Size::operator+(const Size &right) const { + return Size(this->width + right.width, this->height + right.height); +} + +Size Size::operator-(const Size &right) const { + return Size(this->width - right.width, this->height - right.height); +} + +Size Size::operator*(float a) const { + return Size(this->width * a, this->height * a); +} + +Size Size::operator/(float a) const { + CC_ASSERT_NE(a, 0); + float inv = 1.0F / a; + return Size(this->width * inv, this->height * inv); +} + +void Size::setSize(float w, float h) { + this->width = w; + this->height = h; +} + +bool Size::equals(const Size &target) const { + return (std::abs(this->width - target.width) < FLT_EPSILON) && (std::abs(this->height - target.height) < FLT_EPSILON); +} + +const Size Size::ZERO = Size(0, 0); + +// implementation of Rect + +Rect::Rect() { + setRect(0.0F, 0.0F, 0.0F, 0.0F); +} + +Rect::Rect(float x, float y, float width, float height) { + setRect(x, y, width, height); +} +Rect::Rect(const Vec2 &pos, const Size &dimension) { + setRect(pos.x, pos.y, dimension.width, dimension.height); +} + +Rect::Rect(const Rect &other) { + setRect(other.x, other.y, other.width, other.height); +} + +Rect &Rect::operator=(const Rect &other) { + setRect(other.x, other.y, other.width, other.height); + return *this; +} + +void Rect::setRect(float x, float y, float width, float height) { + // CGRect can support width<0 or height<0 + // CC_ASSERT(width >= 0.0f && height >= 0.0f); + + this->x = x; + this->y = y; + this->width = width; + this->height = height; +} + +bool Rect::equals(const Rect &rect) const { + return (std::abs(this->x - rect.x) < FLT_EPSILON) && (std::abs(this->y - rect.y) < FLT_EPSILON) && (std::abs(this->width - rect.width) < FLT_EPSILON) && (std::abs(this->height - rect.height) < FLT_EPSILON); +} + +float Rect::getMaxX() const { + return this->x + this->width; +} + +float Rect::getMidX() const { + return this->x + this->width / 2.0F; +} + +float Rect::getMinX() const { + return this->x; +} + +float Rect::getMaxY() const { + return this->y + this->height; +} + +float Rect::getMidY() const { + return this->y + this->height / 2.0F; +} + +float Rect::getMinY() const { + return this->y; +} + +bool Rect::containsPoint(const Vec2 &point) const { + bool bRet = false; + + if (point.x >= getMinX() && point.x <= getMaxX() && point.y >= getMinY() && point.y <= getMaxY()) { + bRet = true; + } + + return bRet; +} + +bool Rect::intersectsRect(const Rect &rect) const { + return !(getMaxX() < rect.getMinX() || + rect.getMaxX() < getMinX() || + getMaxY() < rect.getMinY() || + rect.getMaxY() < getMinY()); +} + +bool Rect::intersectsCircle(const Vec2 ¢er, float radius) const { + Vec2 rectangleCenter((this->x + this->width / 2), + (this->y + this->height / 2)); + + float w = this->width / 2; + float h = this->height / 2; + + float dx = std::abs(center.x - rectangleCenter.x); + float dy = std::abs(center.y - rectangleCenter.y); + + if (dx > (radius + w) || dy > (radius + h)) { + return false; + } + + Vec2 circleDistance(std::abs(center.x - this->x - w), + std::abs(center.y - this->y - h)); + + if (circleDistance.x <= (w)) { + return true; + } + + if (circleDistance.y <= (h)) { + return true; + } + + float cornerDistanceSq = powf(circleDistance.x - w, 2) + powf(circleDistance.y - h, 2); + + return (cornerDistanceSq <= (powf(radius, 2))); +} + +void Rect::merge(const Rect &rect) { + float minX = std::min(getMinX(), rect.getMinX()); + float minY = std::min(getMinY(), rect.getMinY()); + float maxX = std::max(getMaxX(), rect.getMaxX()); + float maxY = std::max(getMaxY(), rect.getMaxY()); + setRect(minX, minY, maxX - minX, maxY - minY); +} + +Rect Rect::unionWithRect(const Rect &rect) const { + float thisLeftX = this->x; + float thisRightX = this->x + this->width; + float thisTopY = this->y + this->height; + float thisBottomY = this->y; + + if (thisRightX < thisLeftX) { + std::swap(thisRightX, thisLeftX); // This rect has negative width + } + + if (thisTopY < thisBottomY) { + std::swap(thisTopY, thisBottomY); // This rect has negative height + } + + float otherLeftX = rect.x; + float otherRightX = rect.x + rect.width; + float otherTopY = rect.y + rect.height; + float otherBottomY = rect.y; + + if (otherRightX < otherLeftX) { + std::swap(otherRightX, otherLeftX); // Other rect has negative width + } + + if (otherTopY < otherBottomY) { + std::swap(otherTopY, otherBottomY); // Other rect has negative height + } + + float combinedLeftX = std::min(thisLeftX, otherLeftX); + float combinedRightX = std::max(thisRightX, otherRightX); + float combinedTopY = std::max(thisTopY, otherTopY); + float combinedBottomY = std::min(thisBottomY, otherBottomY); + + return Rect(combinedLeftX, combinedBottomY, combinedRightX - combinedLeftX, combinedTopY - combinedBottomY); +} + +const Rect Rect::ZERO = Rect(0, 0, 0, 0); + +} // namespace cc diff --git a/cocos/math/Geometry.h b/cocos/math/Geometry.h new file mode 100644 index 0000000..3fde8a3 --- /dev/null +++ b/cocos/math/Geometry.h @@ -0,0 +1,140 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "math/Vec2.h" + +namespace cc { + +class CC_DLL Size { +public: + /**Width of the Size.*/ + float width{0.F}; + /**Height of the Size.*/ + float height{0.F}; + + /**Conversion from Vec2 to Size.*/ + operator Vec2() const { // NOLINT + return Vec2(width, height); + } + + /** + @{ + Constructor. + @param width Width of the size. + @param height Height of the size. + @param other Copy constructor. + @param point Conversion from a point. + */ + Size(); + Size(float width, float height); + Size(const Size &other); + explicit Size(const Vec2 &point); + /**@}*/ + + Size &operator=(const Size &other); + Size &operator=(const Vec2 &point); + Size operator+(const Size &right) const; + Size operator-(const Size &right) const; + Size operator*(float a) const; + Size operator/(float a) const; + /** + Set the width and height of Size. + */ + void setSize(float width, float height); + /** + Check if two size is the same. + */ + bool equals(const Size &target) const; + /**Size(0,0).*/ + static const Size ZERO; +}; + +/**Rectangle area.*/ +class CC_DLL Rect { +public: + float x{0.F}; + float y{0.F}; + float width{0.F}; + float height{0.F}; + + Rect(); + Rect(float x, float y, float width, float height); + Rect(const Vec2 &pos, const Size &dimension); + Rect(const Rect &other); + Rect &operator=(const Rect &other); + void setRect(float x, float y, float width, float height); + /** + Get the left of the rect. + */ + float getMinX() const; /// return the leftmost x-value of current rect + /** + Get the X coordinate of center point. + */ + float getMidX() const; /// return the midpoint x-value of current rect + /** + Get the right of rect. + */ + float getMaxX() const; /// return the rightmost x-value of current rect + /** + Get the bottom of rect. + */ + float getMinY() const; /// return the bottommost y-value of current rect + /** + Get the Y coordinate of center point. + */ + float getMidY() const; /// return the midpoint y-value of current rect + /** + Get top of rect. + */ + float getMaxY() const; /// return the topmost y-value of current rect + + bool equals(const Rect &rect) const; + /** + Check if the points is contained in the rect. + */ + bool containsPoint(const Vec2 &point) const; + /** + Check the intersect status of two rects. + */ + bool intersectsRect(const Rect &rect) const; + /** + Check the intersect status of the rect and a circle. + */ + bool intersectsCircle(const Vec2 ¢er, float radius) const; + /** + Get the min rect which can contain this and rect. + */ + Rect unionWithRect(const Rect &rect) const; + /**Compute the min rect which can contain this and rect, assign it to this.*/ + void merge(const Rect &rect); + /**An empty Rect.*/ + static const Rect ZERO; +}; + +} // namespace cc diff --git a/cocos/math/Mat3.cpp b/cocos/math/Mat3.cpp new file mode 100644 index 0000000..332e51b --- /dev/null +++ b/cocos/math/Mat3.cpp @@ -0,0 +1,448 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "math/Mat3.h" + +#include +#include +#include "base/Macros.h" +#include "math/Math.h" +#include "math/MathUtil.h" +#include "math/Quaternion.h" + +NS_CC_MATH_BEGIN + +Mat3::Mat3() { + *this = IDENTITY; +} + +Mat3::Mat3(float m00, float m01, float m02, + float m03, float m04, float m05, + float m06, float m07, float m08) { + set(m00, m01, m02, m03, m04, m05, m06, m07, m08); +} + +Mat3::Mat3(const float *mat) { + set(mat); +} + +Mat3::Mat3(const Mat3 ©) { + memcpy(m, copy.m, MATRIX3_SIZE); +} + +void Mat3::set(float m00, float m01, float m02, + float m03, float m04, float m05, + float m06, float m07, float m08) { + m[0] = m00; + m[1] = m01; + m[2] = m02; + m[3] = m03; + m[4] = m04; + m[5] = m05; + m[6] = m06; + m[7] = m07; + m[8] = m08; +} + +void Mat3::set(const float *mat) { + CC_ASSERT(mat); + memcpy(this->m, mat, MATRIX3_SIZE); +} + +void Mat3::set(const Mat3 &mat) { + memcpy(this->m, mat.m, MATRIX3_SIZE); +} + +void Mat3::identity(Mat3 &mat) { + mat.m[0] = 1; + mat.m[1] = 0; + mat.m[2] = 0; + mat.m[3] = 0; + mat.m[4] = 1; + mat.m[5] = 0; + mat.m[6] = 0; + mat.m[7] = 0; + mat.m[8] = 1; +} + +void Mat3::fromViewUp(const Vec3 &view, Mat3 *out) { + CC_ASSERT(out); + fromViewUp(view, Vec3(0, 1, 0), out); +} +void Mat3::fromViewUp(const Vec3 &view, const Vec3 &up, Mat3 *out) { + CC_ASSERT(out); + if (view.lengthSquared() < math::EPSILON * math::EPSILON) { + Mat3::identity(*out); + return; + } + Vec3 vTempA{Vec3::ZERO}; + Vec3 vTempB{Vec3::ZERO}; + + Vec3::cross(up, view, &vTempA); + vTempA.normalize(); + if (vTempA.lengthSquared() < math::EPSILON * math::EPSILON) { + Mat3::identity(*out); + return; + } + Vec3::cross(view, vTempA, &vTempB); + out->set(vTempA.x, vTempA.y, vTempA.z, + vTempB.x, vTempB.y, vTempB.z, + view.x, view.y, view.z); +} + +void Mat3::transpose() { + float a01 = m[1]; + float a02 = m[2]; + float a12 = m[5]; + m[1] = m[3]; + m[2] = m[6]; + m[3] = a01; + m[5] = m[7]; + m[6] = a02; + m[7] = a12; +} + +void Mat3::transpose(const Mat3 &mat, Mat3 *out) { + CC_ASSERT(out); + out->m[0] = mat.m[0]; + out->m[1] = mat.m[3]; + out->m[2] = mat.m[6]; + out->m[3] = mat.m[1]; + out->m[4] = mat.m[4]; + out->m[5] = mat.m[7]; + out->m[6] = mat.m[2]; + out->m[7] = mat.m[5]; + out->m[8] = mat.m[8]; +} + +void Mat3::inverse() { + float a00 = m[0]; + float a01 = m[1]; + float a02 = m[2]; + float a10 = m[3]; + float a11 = m[4]; + float a12 = m[5]; + float a20 = m[6]; + float a21 = m[7]; + float a22 = m[8]; + + float b01 = a22 * a11 - a12 * a21; + float b11 = -a22 * a10 + a12 * a20; + float b21 = a21 * a10 - a11 * a20; + + // Calculate the determinant + float det = a00 * b01 + a01 * b11 + a02 * b21; + if (det == 0.F) { + set(0.F, 0.F, 0.F, 0.F, 0.F, 0.F, 0.F, 0.F, 0.F); + return; + } + + det = 1.0F / det; + m[0] = b01 * det; + m[1] = (-a22 * a01 + a02 * a21) * det; + m[2] = (a12 * a01 - a02 * a11) * det; + m[3] = b11 * det; + m[4] = (a22 * a00 - a02 * a20) * det; + m[5] = (-a12 * a00 + a02 * a10) * det; + m[6] = b21 * det; + m[7] = (-a21 * a00 + a01 * a20) * det; + m[8] = (a11 * a00 - a01 * a10) * det; +} + +void Mat3::adjoint(const Mat3 &mat, Mat3 *out) { + CC_ASSERT(out); + float a00 = mat.m[0]; + float a01 = mat.m[1]; + float a02 = mat.m[2]; + float a10 = mat.m[3]; + float a11 = mat.m[4]; + float a12 = mat.m[5]; + float a20 = mat.m[6]; + float a21 = mat.m[7]; + float a22 = mat.m[8]; + + out->m[0] = (a11 * a22 - a12 * a21); + out->m[1] = (a02 * a21 - a01 * a22); + out->m[2] = (a01 * a12 - a02 * a11); + out->m[3] = (a12 * a20 - a10 * a22); + out->m[4] = (a00 * a22 - a02 * a20); + out->m[5] = (a02 * a10 - a00 * a12); + out->m[6] = (a10 * a21 - a11 * a20); + out->m[7] = (a01 * a20 - a00 * a21); + out->m[8] = (a00 * a11 - a01 * a10); +} + +float Mat3::determinant() { + return m[0] * (m[8] * m[4] - m[5] * m[7]) + m[1] * (-m[8] * m[3] + m[5] * m[6]) + m[2] * (m[7] * m[3] - m[4] * m[6]); +} + +void Mat3::multiply(const Mat3 &a, const Mat3 &b, Mat3 *out) { + CC_ASSERT(out); + float a00 = a.m[0]; + float a01 = a.m[1]; + float a02 = a.m[2]; + float a10 = a.m[3]; + float a11 = a.m[4]; + float a12 = a.m[5]; + float a20 = a.m[6]; + float a21 = a.m[7]; + float a22 = a.m[8]; + + float b00 = b.m[0]; + float b01 = b.m[1]; + float b02 = b.m[2]; + float b10 = b.m[3]; + float b11 = b.m[4]; + float b12 = b.m[5]; + float b20 = b.m[6]; + float b21 = b.m[7]; + float b22 = b.m[8]; + + out->m[0] = b00 * a00 + b01 * a10 + b02 * a20; + out->m[1] = b00 * a01 + b01 * a11 + b02 * a21; + out->m[2] = b00 * a02 + b01 * a12 + b02 * a22; + + out->m[3] = b10 * a00 + b11 * a10 + b12 * a20; + out->m[4] = b10 * a01 + b11 * a11 + b12 * a21; + out->m[5] = b10 * a02 + b11 * a12 + b12 * a22; + + out->m[6] = b20 * a00 + b21 * a10 + b22 * a20; + out->m[7] = b20 * a01 + b21 * a11 + b22 * a21; + out->m[8] = b20 * a02 + b21 * a12 + b22 * a22; +} + +void Mat3::translate(const Mat3 &mat, const Vec2 &vec, Mat3 *out) { + CC_ASSERT(out); + float a00 = mat.m[0]; + float a01 = mat.m[1]; + float a02 = mat.m[2]; + float a10 = mat.m[3]; + float a11 = mat.m[4]; + float a12 = mat.m[5]; + float a20 = mat.m[6]; + float a21 = mat.m[7]; + float a22 = mat.m[8]; + float x = vec.x; + float y = vec.y; + + out->m[0] = a00; + out->m[1] = a01; + out->m[2] = a02; + + out->m[3] = a10; + out->m[4] = a11; + out->m[5] = a12; + + out->m[6] = x * a00 + y * a10 + a20; + out->m[7] = x * a01 + y * a11 + a21; + out->m[8] = x * a02 + y * a12 + a22; +} + +void Mat3::rotate(const Mat3 &mat, float rad, Mat3 *out) { + CC_ASSERT(out); + float a00 = mat.m[0]; + float a01 = mat.m[1]; + float a02 = mat.m[2]; + float a10 = mat.m[3]; + float a11 = mat.m[4]; + float a12 = mat.m[5]; + float a20 = mat.m[6]; + float a21 = mat.m[7]; + float a22 = mat.m[8]; + + float s = sin(rad); + float c = cos(rad); + + out->m[0] = c * a00 + s * a10; + out->m[1] = c * a01 + s * a11; + out->m[2] = c * a02 + s * a12; + + out->m[3] = c * a10 - s * a00; + out->m[4] = c * a11 - s * a01; + out->m[5] = c * a12 - s * a02; + + out->m[6] = a20; + out->m[7] = a21; + out->m[8] = a22; +} + +void Mat3::scale(const Mat3 &mat, const Vec2 &vec, Mat3 *out) { + CC_ASSERT(out); + float x = vec.x; + float y = vec.y; + + out->m[0] = x * mat.m[0]; + out->m[1] = x * mat.m[1]; + out->m[2] = x * mat.m[2]; + + out->m[3] = y * mat.m[3]; + out->m[4] = y * mat.m[4]; + out->m[5] = y * mat.m[5]; + + out->m[6] = mat.m[6]; + out->m[7] = mat.m[7]; + out->m[8] = mat.m[8]; +} + +void Mat3::fromMat4(const Mat4 &mat, Mat3 *out) { + CC_ASSERT(out); + out->m[0] = mat.m[0]; + out->m[1] = mat.m[1]; + out->m[2] = mat.m[2]; + out->m[3] = mat.m[4]; + out->m[4] = mat.m[5]; + out->m[5] = mat.m[6]; + out->m[6] = mat.m[8]; + out->m[7] = mat.m[9]; + out->m[8] = mat.m[10]; +} + +void Mat3::fromTranslation(const Vec2 &vec, Mat3 *out) { + CC_ASSERT(out); + out->m[0] = 1; + out->m[1] = 0; + out->m[2] = 0; + out->m[3] = 0; + out->m[4] = 1; + out->m[5] = 0; + out->m[6] = vec.x; + out->m[7] = vec.y; + out->m[8] = 1; +} + +void Mat3::fromRotation(float rad, Mat3 *out) { + CC_ASSERT(out); + float s = sin(rad); + float c = cos(rad); + + out->m[0] = c; + out->m[1] = s; + out->m[2] = 0; + + out->m[3] = -s; + out->m[4] = c; + out->m[5] = 0; + + out->m[6] = 0; + out->m[7] = 0; + out->m[8] = 1; +} + +void Mat3::fromScaling(const Vec2 &vec, Mat3 *out) { + CC_ASSERT(out); + out->m[0] = vec.x; + out->m[1] = 0; + out->m[2] = 0; + + out->m[3] = 0; + out->m[4] = vec.y; + out->m[5] = 0; + + out->m[6] = 0; + out->m[7] = 0; + out->m[8] = 1; +} + +void Mat3::fromQuat(const Quaternion &quat, Mat3 *out) { + CC_ASSERT(out); + float x = quat.x; + float y = quat.y; + float z = quat.z; + float w = quat.w; + float x2 = x + x; + float y2 = y + y; + float z2 = z + z; + + float xx = x * x2; + float yx = y * x2; + float yy = y * y2; + float zx = z * x2; + float zy = z * y2; + float zz = z * z2; + float wx = w * x2; + float wy = w * y2; + float wz = w * z2; + + out->m[0] = 1 - yy - zz; + out->m[3] = yx - wz; + out->m[6] = zx + wy; + + out->m[1] = yx + wz; + out->m[4] = 1 - xx - zz; + out->m[7] = zy - wx; + + out->m[2] = zx - wy; + out->m[5] = zy + wx; + out->m[8] = 1 - xx - yy; +} + +void Mat3::add(const Mat3 &a, const Mat3 &b, Mat3 *out) { + CC_ASSERT(out); + out->m[0] = a.m[0] + b.m[0]; + out->m[1] = a.m[1] + b.m[1]; + out->m[2] = a.m[2] + b.m[2]; + out->m[3] = a.m[3] + b.m[3]; + out->m[4] = a.m[4] + b.m[4]; + out->m[5] = a.m[5] + b.m[5]; + out->m[6] = a.m[6] + b.m[6]; + out->m[7] = a.m[7] + b.m[7]; + out->m[8] = a.m[8] + b.m[8]; +} + +void Mat3::subtract(const Mat3 &a, const Mat3 &b, Mat3 *out) { + CC_ASSERT(out); + out->m[0] = a.m[0] - b.m[0]; + out->m[1] = a.m[1] - b.m[1]; + out->m[2] = a.m[2] - b.m[2]; + out->m[3] = a.m[3] - b.m[3]; + out->m[4] = a.m[4] - b.m[4]; + out->m[5] = a.m[5] - b.m[5]; + out->m[6] = a.m[6] - b.m[6]; + out->m[7] = a.m[7] - b.m[7]; + out->m[8] = a.m[8] - b.m[8]; +} + +bool Mat3::approxEquals(const Mat3 &v, float precision /* = CC_FLOAT_CMP_PRECISION */) const { + return math::isEqualF(m[0], v.m[0], precision) && + math::isEqualF(m[1], v.m[1], precision) && + math::isEqualF(m[2], v.m[2], precision) && + math::isEqualF(m[3], v.m[3], precision) && + math::isEqualF(m[4], v.m[4], precision) && + math::isEqualF(m[5], v.m[5], precision) && + math::isEqualF(m[6], v.m[6], precision) && + math::isEqualF(m[7], v.m[7], precision) && + math::isEqualF(m[8], v.m[8], precision); +} + +const Mat3 Mat3::IDENTITY = Mat3( + 1.F, 0.F, 0.F, + 0.F, 1.F, 0.F, + 0.F, 0.F, 1.F); + +const Mat3 Mat3::ZERO = Mat3( + 0, 0, 0, + 0, 0, 0, + 0, 0, 0); + +NS_CC_MATH_END diff --git a/cocos/math/Mat3.h b/cocos/math/Mat3.h new file mode 100644 index 0000000..91c04e4 --- /dev/null +++ b/cocos/math/Mat3.h @@ -0,0 +1,244 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#define MATRIX3_SIZE (sizeof(float) * 9) + +#include "base/Macros.h" + +#include "math/Mat4.h" +#include "math/Vec2.h" +#include "math/Vec3.h" +#include "math/Vec4.h" + +/** + * @addtogroup base + * @{ + */ + +NS_CC_MATH_BEGIN + +class CC_DLL Mat3 { +public: + /** + * Stores the columns of this 3x3 matrix. + * matrix layout + * |m[0] m[3] m[6]| + * |m[1] m[4] m[7]| + * |m[2] m[5] m[8]| + */ + float m[9]; + + /** + * Default constructor. + * Constructs a matrix initialized to the identity matrix: + * + * 1 0 0 + * 0 1 0 + * 0 0 1 + */ + Mat3(); + + /** + * Constructs a matrix initialized to the specified values which are column-major order. + */ + Mat3(float m00, float m01, float m02, + float m03, float m04, float m05, + float m06, float m07, float m08); + + /** + * Creates a matrix initialized to the specified column-major array. + * + * The passed-in array is in column-major order, so the memory layout of the array is as follows: + * + * 0 3 6 + * 1 4 7 + * 2 5 8 + * + * @param mat An array containing 16 elements in column-major order. + */ + explicit Mat3(const float *mat); + + /** + * Constructs a new matrix by copying the values from the specified matrix. + * + * @param copy The matrix to copy. + */ + Mat3(const Mat3 ©); + + /** + * + * @brief Construct a new Mat 3 object with a Mat4. + * Copies the upper-left 3x3 values of a 4x4 matrix into a 3x3 matrix. + * @param m4 + */ + explicit Mat3(const Mat4 &m4) { + fromMat4(m4, this); + } + + /** + * @brief Construct a new Mat 3 object with a Quaternion + * Calculates a 3x3 matrix from the given quaternion. + * @param quat + */ + explicit Mat3(const Quaternion &quat) { + fromQuat(quat, this); + } + + /** + * Destructor. + */ + ~Mat3() = default; + + /** + * Sets the values of this matrix which are column-major order. + */ + void set(float m00, float m01, float m02, + float m03, float m04, float m05, + float m06, float m07, float m08); + + /** + * Sets the values of this matrix to those in the specified column-major array. + * + * @param mat An array containing 9 elements in column-major format. + */ + void set(const float *mat); + + /** + * Sets the values of this matrix to those of the specified matrix. + * + * @param mat The source matrix. + */ + void set(const Mat3 &mat); + + /** + * return an identity matrix. + */ + static void identity(Mat3 &mat); + + /** + * Transposes matrix. + */ + void transpose(); + + /** + * Transposes a matrix. + */ + static void transpose(const Mat3 &mat, Mat3 *out); + + /** + * Inverts a matrix. + */ + void inverse(); + + /** + * Calculates the adjoint matrix. + */ + static void adjoint(const Mat3 &mat, Mat3 *out); + + /** + * Calculates the determinant of a matrix. + */ + float determinant(); + + /** + * Multiply two matrices explicitly. + */ + static void multiply(const Mat3 &a, const Mat3 &b, Mat3 *out); + + /** + * Multiply a matrix with a translation matrix given by a translation offset. + */ + static void translate(const Mat3 &mat, const Vec2 &vec, Mat3 *out); + + /** + * Rotates a matrix by the given angle. + */ + static void rotate(const Mat3 &mat, float rad, Mat3 *out); + + /** + * Multiply a matrix with a scale matrix given by a scale vector. + */ + static void scale(const Mat3 &mat, const Vec2 &vec, Mat3 *out); + + /** + * Copies the upper-left 3x3 values of a 4x4 matrix into a 3x3 matrix. + */ + static void fromMat4(const Mat4 &mat, Mat3 *out); + + /** + * Creates a matrix from a translation offset. + */ + static void fromTranslation(const Vec2 &vec, Mat3 *out); + + /** + * Creates a matrix from a given angle. + */ + static void fromRotation(float rad, Mat3 *out); + + /** + * Creates a matrix from a scale vector. + */ + static void fromScaling(const Vec2 &vec, Mat3 *out); + + /** + * Sets a third order matrix with view direction and up direction. Then save the results to out matrix + */ + static void fromViewUp(const Vec3 &view, Mat3 *out); + static void fromViewUp(const Vec3 &view, const Vec3 &up, Mat3 *out); + + /** + * Calculates a 3x3 matrix from the given quaternion. + */ + + static void fromQuat(const Quaternion &quat, Mat3 *out); + + /** + * Adds two matrices. + */ + static void add(const Mat3 &a, const Mat3 &b, Mat3 *out); + + /** + * Subtracts matrix b from matrix a. + */ + static void subtract(const Mat3 &a, const Mat3 &b, Mat3 *out); + + /** + * Determines if this matrix is approximately equal to the given matrix. + */ + bool approxEquals(const Mat3 &v, float precision = CC_FLOAT_CMP_PRECISION) const; + + /** equals to a matrix full of zeros */ + static const Mat3 ZERO; + /** equals to the identity matrix */ + static const Mat3 IDENTITY; +}; + +NS_CC_MATH_END + +/** + end of base group + @} + */ diff --git a/cocos/math/Mat4.cpp b/cocos/math/Mat4.cpp new file mode 100644 index 0000000..18a9a07 --- /dev/null +++ b/cocos/math/Mat4.cpp @@ -0,0 +1,1061 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#include "math/Mat4.h" + +#include +#include +#include "base/std/container/array.h" +#include "math/Math.h" +#include "math/MathUtil.h" +#include "math/Quaternion.h" + +NS_CC_MATH_BEGIN + +namespace { + +const ccstd::array, 4> PRE_TRANSFORMS = {{ + {{1, 0, 0, 1}}, // SurfaceTransform.IDENTITY + {{0, 1, -1, 0}}, // SurfaceTransform.ROTATE_90 + {{-1, 0, 0, -1}}, // SurfaceTransform.ROTATE_180 + {{0, -1, 1, 0}} // SurfaceTransform.ROTATE_270 +}}; + +} + +Mat4::Mat4() { + // As JS defautl mat4 is zero, so change it to zero. + *this = IDENTITY; +} + +Mat4::Mat4(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) { + set(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33); +} + +Mat4::Mat4(const float *mat) { + set(mat); +} + +Mat4::Mat4(const Mat4 ©) { + memcpy(m, copy.m, MATRIX_SIZE); +} + +void Mat4::createLookAt(const Vec3 &eyePosition, const Vec3 &targetPosition, const Vec3 &up, Mat4 *dst) { + createLookAt(eyePosition.x, eyePosition.y, eyePosition.z, targetPosition.x, targetPosition.y, targetPosition.z, + up.x, up.y, up.z, dst); +} + +void Mat4::createLookAt(float eyePositionX, float eyePositionY, float eyePositionZ, + float targetPositionX, float targetPositionY, float targetPositionZ, + float upX, float upY, float upZ, Mat4 *dst) { + CC_ASSERT(dst); + + Vec3 eye(eyePositionX, eyePositionY, eyePositionZ); + Vec3 target(targetPositionX, targetPositionY, targetPositionZ); + Vec3 up(upX, upY, upZ); + up.normalize(); + + Vec3 zaxis; + Vec3::subtract(eye, target, &zaxis); + zaxis.normalize(); + + Vec3 xaxis; + Vec3::cross(up, zaxis, &xaxis); + xaxis.normalize(); + + Vec3 yaxis; + Vec3::cross(zaxis, xaxis, &yaxis); + yaxis.normalize(); + + dst->m[0] = xaxis.x; + dst->m[1] = yaxis.x; + dst->m[2] = zaxis.x; + dst->m[3] = 0.0F; + + dst->m[4] = xaxis.y; + dst->m[5] = yaxis.y; + dst->m[6] = zaxis.y; + dst->m[7] = 0.0F; + + dst->m[8] = xaxis.z; + dst->m[9] = yaxis.z; + dst->m[10] = zaxis.z; + dst->m[11] = 0.0F; + + dst->m[12] = -Vec3::dot(xaxis, eye); + dst->m[13] = -Vec3::dot(yaxis, eye); + dst->m[14] = -Vec3::dot(zaxis, eye); + dst->m[15] = 1.0F; +} + +void Mat4::createPerspective(float fieldOfView, float aspectRatio, float zNearPlane, float zFarPlane, + bool isFieldOfViewY, float minClipZ, float projectionSignY, int orientation, Mat4 *dst) { + CC_ASSERT(dst); + CC_ASSERT_NE(zFarPlane, zNearPlane); + CC_ASSERT(fieldOfView != 0.0F); + + const float f = 1.0F / tanf(fieldOfView / 2.0F); + const float nf = 1.0F / (zNearPlane - zFarPlane); + + const float x = isFieldOfViewY ? f / aspectRatio : f; + const float y = (isFieldOfViewY ? f : f * aspectRatio) * projectionSignY; + + const ccstd::array &preTransform = PRE_TRANSFORMS[orientation]; + + dst->m[0] = x * preTransform[0]; + dst->m[1] = x * preTransform[1]; + dst->m[2] = 0.0F; + dst->m[3] = 0.0F; + dst->m[4] = y * preTransform[2]; + dst->m[5] = y * preTransform[3]; + dst->m[6] = 0.0F; + dst->m[7] = 0.0F; + dst->m[8] = 0.0F; + dst->m[9] = 0.0F; + dst->m[10] = (zFarPlane - minClipZ * zNearPlane) * nf; + dst->m[11] = -1.0F; + dst->m[12] = 0.0F; + dst->m[13] = 0.0F; + dst->m[14] = zFarPlane * zNearPlane * nf * (1.0F - minClipZ); + dst->m[15] = 0.0F; +} + +void Mat4::createOrthographic(float left, float right, float bottom, float top, float zNearPlane, float zFarPlane, Mat4 *dst) { + createOrthographicOffCenter(left, right, bottom, top, zNearPlane, zFarPlane, dst); +} + +void Mat4::createOrthographicOffCenter(float left, float right, float bottom, float top, + float zNearPlane, float zFarPlane, Mat4 *dst) { + createOrthographicOffCenter(left, right, bottom, top, zNearPlane, zFarPlane, -1.0F, 1.0F, 0, dst); +} + +void Mat4::createOrthographicOffCenter(float left, float right, float bottom, float top, float zNearPlane, float zFarPlane, float minClipZ, float projectionSignY, int orientation, Mat4 *dst) { + CC_ASSERT(dst); + CC_ASSERT_NE(right, left); + CC_ASSERT_NE(top, bottom); + CC_ASSERT_NE(zFarPlane, zNearPlane); + + const ccstd::array &preTransform = PRE_TRANSFORMS[orientation]; + const float lr = 1.F / (left - right); + const float bt = 1.F / (bottom - top) * projectionSignY; + const float nf = 1.F / (zNearPlane - zFarPlane); + + const float x = -2 * lr; + const float y = -2 * bt; + const float dx = (left + right) * lr; + const float dy = (top + bottom) * bt; + + dst->m[0] = x * preTransform[0]; + dst->m[1] = x * preTransform[1]; + dst->m[2] = 0; + dst->m[3] = 0; + dst->m[4] = y * preTransform[2]; + dst->m[5] = y * preTransform[3]; + dst->m[6] = 0; + dst->m[7] = 0; + dst->m[8] = 0; + dst->m[9] = 0; + dst->m[10] = nf * (1 - minClipZ); + dst->m[11] = 0; + dst->m[12] = dx * preTransform[0] + dy * preTransform[2]; + dst->m[13] = dx * preTransform[1] + dy * preTransform[3]; + dst->m[14] = (zNearPlane - minClipZ * zFarPlane) * nf; + dst->m[15] = 1.F; +} + +void Mat4::createBillboard(const Vec3 &objectPosition, const Vec3 &cameraPosition, + const Vec3 &cameraUpVector, Mat4 *dst) { + createBillboardHelper(objectPosition, cameraPosition, cameraUpVector, nullptr, dst); +} + +void Mat4::createBillboard(const Vec3 &objectPosition, const Vec3 &cameraPosition, + const Vec3 &cameraUpVector, const Vec3 &cameraForwardVector, + Mat4 *dst) { + createBillboardHelper(objectPosition, cameraPosition, cameraUpVector, &cameraForwardVector, dst); +} + +void Mat4::createBillboardHelper(const Vec3 &objectPosition, const Vec3 &cameraPosition, + const Vec3 &cameraUpVector, const Vec3 *cameraForwardVector, + Mat4 *dst) { + Vec3 delta(objectPosition, cameraPosition); + bool isSufficientDelta = delta.lengthSquared() > MATH_EPSILON; + + dst->setIdentity(); + dst->m[3] = objectPosition.x; + dst->m[7] = objectPosition.y; + dst->m[11] = objectPosition.z; + + // As per the contracts for the 2 variants of createBillboard, we need + // either a safe default or a sufficient distance between object and camera. + if (cameraForwardVector || isSufficientDelta) { + Vec3 target = isSufficientDelta ? cameraPosition : (objectPosition - *cameraForwardVector); + + // A billboard is the inverse of a lookAt rotation + Mat4 lookAt; + createLookAt(objectPosition, target, cameraUpVector, &lookAt); + dst->m[0] = lookAt.m[0]; + dst->m[1] = lookAt.m[4]; + dst->m[2] = lookAt.m[8]; + dst->m[4] = lookAt.m[1]; + dst->m[5] = lookAt.m[5]; + dst->m[6] = lookAt.m[9]; + dst->m[8] = lookAt.m[2]; + dst->m[9] = lookAt.m[6]; + dst->m[10] = lookAt.m[10]; + } +} + +void Mat4::createScale(const Vec3 &scale, Mat4 *dst) { + CC_ASSERT(dst); + + memcpy(dst->m, IDENTITY.m, MATRIX_SIZE); + + dst->m[0] = scale.x; + dst->m[5] = scale.y; + dst->m[10] = scale.z; +} + +void Mat4::createScale(float xScale, float yScale, float zScale, Mat4 *dst) { + CC_ASSERT(dst); + + memcpy(dst->m, IDENTITY.m, MATRIX_SIZE); + + dst->m[0] = xScale; + dst->m[5] = yScale; + dst->m[10] = zScale; +} + +void Mat4::createRotation(const Quaternion &q, Mat4 *dst) { + CC_ASSERT(dst); + + float x2 = q.x + q.x; + float y2 = q.y + q.y; + float z2 = q.z + q.z; + + float xx2 = q.x * x2; + float yy2 = q.y * y2; + float zz2 = q.z * z2; + float xy2 = q.x * y2; + float xz2 = q.x * z2; + float yz2 = q.y * z2; + float wx2 = q.w * x2; + float wy2 = q.w * y2; + float wz2 = q.w * z2; + + dst->m[0] = 1.0F - yy2 - zz2; + dst->m[1] = xy2 + wz2; + dst->m[2] = xz2 - wy2; + dst->m[3] = 0.0F; + + dst->m[4] = xy2 - wz2; + dst->m[5] = 1.0F - xx2 - zz2; + dst->m[6] = yz2 + wx2; + dst->m[7] = 0.0F; + + dst->m[8] = xz2 + wy2; + dst->m[9] = yz2 - wx2; + dst->m[10] = 1.0F - xx2 - yy2; + dst->m[11] = 0.0F; + + dst->m[12] = 0.0F; + dst->m[13] = 0.0F; + dst->m[14] = 0.0F; + dst->m[15] = 1.0F; +} + +void Mat4::createRotation(const Vec3 &axis, float angle, Mat4 *dst) { + CC_ASSERT(dst); + + float x = axis.x; + float y = axis.y; + float z = axis.z; + + // Make sure the input axis is normalized. + float n = x * x + y * y + z * z; + n = std::sqrt(n); + + // do nothing for invalid axis + if (n < math::EPSILON) { + return; + } + + n = 1.0F / n; + x *= n; + y *= n; + z *= n; + + float c = std::cos(angle); + float s = std::sin(angle); + + float t = 1.0F - c; + float tx = t * x; + float ty = t * y; + float tz = t * z; + float txy = tx * y; + float txz = tx * z; + float tyz = ty * z; + float sx = s * x; + float sy = s * y; + float sz = s * z; + + dst->m[0] = c + tx * x; + dst->m[1] = txy + sz; + dst->m[2] = txz - sy; + dst->m[3] = 0.0F; + + dst->m[4] = txy - sz; + dst->m[5] = c + ty * y; + dst->m[6] = tyz + sx; + dst->m[7] = 0.0F; + + dst->m[8] = txz + sy; + dst->m[9] = tyz - sx; + dst->m[10] = c + tz * z; + dst->m[11] = 0.0F; + + dst->m[12] = 0.0F; + dst->m[13] = 0.0F; + dst->m[14] = 0.0F; + dst->m[15] = 1.0F; +} + +void Mat4::createRotationX(float angle, Mat4 *dst) { + CC_ASSERT(dst); + + memcpy(dst->m, IDENTITY.m, MATRIX_SIZE); + + float c = std::cos(angle); + float s = std::sin(angle); + + dst->m[5] = c; + dst->m[6] = s; + dst->m[9] = -s; + dst->m[10] = c; +} + +void Mat4::createRotationY(float angle, Mat4 *dst) { + CC_ASSERT(dst); + + memcpy(dst->m, IDENTITY.m, MATRIX_SIZE); + + float c = std::cos(angle); + float s = std::sin(angle); + + dst->m[0] = c; + dst->m[2] = -s; + dst->m[8] = s; + dst->m[10] = c; +} + +void Mat4::createRotationZ(float angle, Mat4 *dst) { + CC_ASSERT(dst); + + memcpy(dst->m, IDENTITY.m, MATRIX_SIZE); + + float c = std::cos(angle); + float s = std::sin(angle); + + dst->m[0] = c; + dst->m[1] = s; + dst->m[4] = -s; + dst->m[5] = c; +} + +void Mat4::createTranslation(const Vec3 &translation, Mat4 *dst) { + CC_ASSERT(dst); + + memcpy(dst->m, IDENTITY.m, MATRIX_SIZE); + + dst->m[12] = translation.x; + dst->m[13] = translation.y; + dst->m[14] = translation.z; +} + +void Mat4::createTranslation(float xTranslation, float yTranslation, float zTranslation, Mat4 *dst) { + CC_ASSERT(dst); + + memcpy(dst->m, IDENTITY.m, MATRIX_SIZE); + + dst->m[12] = xTranslation; + dst->m[13] = yTranslation; + dst->m[14] = zTranslation; +} + +void Mat4::add(float scalar) { + add(scalar, this); +} + +void Mat4::add(float scalar, Mat4 *dst) { + CC_ASSERT(dst); +#ifdef __SSE__ + MathUtil::addMatrix(col, scalar, dst->col); +#else + MathUtil::addMatrix(m, scalar, dst->m); +#endif +} + +void Mat4::add(const Mat4 &mat) { + add(*this, mat, this); +} + +void Mat4::add(const Mat4 &m1, const Mat4 &m2, Mat4 *dst) { + CC_ASSERT(dst); +#ifdef __SSE__ + MathUtil::addMatrix(m1.col, m2.col, dst->col); +#else + MathUtil::addMatrix(m1.m, m2.m, dst->m); +#endif +} + +void Mat4::fromRT(const Quaternion &rotation, const Vec3 &translation, Mat4 *dst) { + const auto x = rotation.x; + const auto y = rotation.y; + const auto z = rotation.z; + const auto w = rotation.w; + const auto x2 = x + x; + const auto y2 = y + y; + const auto z2 = z + z; + + const auto xx = x * x2; + const auto xy = x * y2; + const auto xz = x * z2; + const auto yy = y * y2; + const auto yz = y * z2; + const auto zz = z * z2; + const auto wx = w * x2; + const auto wy = w * y2; + const auto wz = w * z2; + + dst->m[0] = 1 - (yy + zz); + dst->m[1] = xy + wz; + dst->m[2] = xz - wy; + dst->m[3] = 0; + dst->m[4] = xy - wz; + dst->m[5] = 1 - (xx + zz); + dst->m[6] = yz + wx; + dst->m[7] = 0; + dst->m[8] = xz + wy; + dst->m[9] = yz - wx; + dst->m[10] = 1 - (xx + yy); + dst->m[11] = 0; + dst->m[12] = translation.x; + dst->m[13] = translation.y; + dst->m[14] = translation.z; + dst->m[15] = 1; +} + +void Mat4::fromRTS(const Quaternion &rotation, const Vec3 &translation, const Vec3 &scale, Mat4 *dst) { + const float x = rotation.x; + const float y = rotation.y; + const float z = rotation.z; + const float w = rotation.w; + const float x2 = x + x; + const float y2 = y + y; + const float z2 = z + z; + + const float xx = x * x2; + const float xy = x * y2; + const float xz = x * z2; + const float yy = y * y2; + const float yz = y * z2; + const float zz = z * z2; + const float wx = w * x2; + const float wy = w * y2; + const float wz = w * z2; + const float sx = scale.x; + const float sy = scale.y; + const float sz = scale.z; + + dst->m[0] = (1 - (yy + zz)) * sx; + dst->m[1] = (xy + wz) * sx; + dst->m[2] = (xz - wy) * sx; + dst->m[3] = 0; + dst->m[4] = (xy - wz) * sy; + dst->m[5] = (1 - (xx + zz)) * sy; + dst->m[6] = (yz + wx) * sy; + dst->m[7] = 0; + dst->m[8] = (xz + wy) * sz; + dst->m[9] = (yz - wx) * sz; + dst->m[10] = (1 - (xx + yy)) * sz; + dst->m[11] = 0; + dst->m[12] = translation.x; + dst->m[13] = translation.y; + dst->m[14] = translation.z; + dst->m[15] = 1; +} + +void Mat4::toRTS(const Mat4 &src, Quaternion *rotation, Vec3 *translation, Vec3 *scale) { + src.decompose(scale, rotation, translation); +} + +bool Mat4::decompose(Vec3 *scale, Quaternion *rotation, Vec3 *translation) const { + if (translation) { + // Extract the translation. + translation->x = m[12]; + translation->y = m[13]; + translation->z = m[14]; + } + + // Nothing left to do. + if (scale == nullptr && rotation == nullptr) { + return true; + } + + // Extract the scale. + // This is simply the length of each axis (row/column) in the matrix. + Vec3 xaxis(m[0], m[1], m[2]); + float scaleX = xaxis.length(); + + Vec3 yaxis(m[4], m[5], m[6]); + float scaleY = yaxis.length(); + + Vec3 zaxis(m[8], m[9], m[10]); + float scaleZ = zaxis.length(); + + // Determine if we have a negative scale (true if determinant is less than zero). + // In this case, we simply negate a single axis of the scale. + float det = determinant(); + if (det < 0) { + scaleX = -scaleX; + } + + if (scale) { + scale->x = scaleX; + scale->y = scaleY; + scale->z = scaleZ; + } + + // Nothing left to do. + if (rotation == nullptr) { + return true; + } + + // Scale too close to zero, can't decompose rotation. + if (scaleX < MATH_TOLERANCE || scaleY < MATH_TOLERANCE || std::abs(scaleZ) < MATH_TOLERANCE) { + return false; + } + + float rn; + + // Factor the scale out of the matrix axes. + rn = 1.0F / scaleX; + xaxis.x *= rn; + xaxis.y *= rn; + xaxis.z *= rn; + + rn = 1.0F / scaleY; + yaxis.x *= rn; + yaxis.y *= rn; + yaxis.z *= rn; + + rn = 1.0F / scaleZ; + zaxis.x *= rn; + zaxis.y *= rn; + zaxis.z *= rn; + + // Now calculate the rotation from the resulting matrix (axes). + float trace = xaxis.x + yaxis.y + zaxis.z; + + if (trace > 0.0F) { + float s = 0.5F / std::sqrt(trace + 1.0F); + rotation->w = 0.25F / s; + rotation->x = (yaxis.z - zaxis.y) * s; + rotation->y = (zaxis.x - xaxis.z) * s; + rotation->z = (xaxis.y - yaxis.x) * s; + } else { + // Note: since xaxis, yaxis, and zaxis are normalized, + // we will never divide by zero in the code below. + if (xaxis.x > yaxis.y && xaxis.x > zaxis.z) { + float s = 0.5F / std::sqrt(1.0F + xaxis.x - yaxis.y - zaxis.z); + rotation->w = (yaxis.z - zaxis.y) * s; + rotation->x = 0.25F / s; + rotation->y = (yaxis.x + xaxis.y) * s; + rotation->z = (zaxis.x + xaxis.z) * s; + } else if (yaxis.y > zaxis.z) { + float s = 0.5F / std::sqrt(1.0F + yaxis.y - xaxis.x - zaxis.z); + rotation->w = (zaxis.x - xaxis.z) * s; + rotation->x = (yaxis.x + xaxis.y) * s; + rotation->y = 0.25F / s; + rotation->z = (zaxis.y + yaxis.z) * s; + } else { + float s = 0.5F / std::sqrt(1.0F + zaxis.z - xaxis.x - yaxis.y); + rotation->w = (xaxis.y - yaxis.x) * s; + rotation->x = (zaxis.x + xaxis.z) * s; + rotation->y = (zaxis.y + yaxis.z) * s; + rotation->z = 0.25F / s; + } + } + + return true; +} + +float Mat4::determinant() const { + float a0 = m[0] * m[5] - m[1] * m[4]; + float a1 = m[0] * m[6] - m[2] * m[4]; + float a2 = m[0] * m[7] - m[3] * m[4]; + float a3 = m[1] * m[6] - m[2] * m[5]; + float a4 = m[1] * m[7] - m[3] * m[5]; + float a5 = m[2] * m[7] - m[3] * m[6]; + float b0 = m[8] * m[13] - m[9] * m[12]; + float b1 = m[8] * m[14] - m[10] * m[12]; + float b2 = m[8] * m[15] - m[11] * m[12]; + float b3 = m[9] * m[14] - m[10] * m[13]; + float b4 = m[9] * m[15] - m[11] * m[13]; + float b5 = m[10] * m[15] - m[11] * m[14]; + + // Calculate the determinant. + return (a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0); +} + +void Mat4::inverseTranspose(const Mat4 &mat, Mat4 *dst) { + float a00 = mat.m[0]; + float a01 = mat.m[1]; + float a02 = mat.m[2]; + float a03 = mat.m[3]; + float a10 = mat.m[4]; + float a11 = mat.m[5]; + float a12 = mat.m[6]; + float a13 = mat.m[7]; + float a20 = mat.m[8]; + float a21 = mat.m[9]; + float a22 = mat.m[10]; + float a23 = mat.m[11]; + float a30 = mat.m[12]; + float a31 = mat.m[13]; + float a32 = mat.m[14]; + float a33 = mat.m[15]; + + float b00 = a00 * a11 - a01 * a10; + float b01 = a00 * a12 - a02 * a10; + float b02 = a00 * a13 - a03 * a10; + float b03 = a01 * a12 - a02 * a11; + float b04 = a01 * a13 - a03 * a11; + float b05 = a02 * a13 - a03 * a12; + float b06 = a20 * a31 - a21 * a30; + float b07 = a20 * a32 - a22 * a30; + float b08 = a20 * a33 - a23 * a30; + float b09 = a21 * a32 - a22 * a31; + float b10 = a21 * a33 - a23 * a31; + float b11 = a22 * a33 - a23 * a32; + + // Calculate the determinant + float det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + if (det == 0.0) { + return; + } + det = 1 / det; + + dst->m[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + dst->m[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + dst->m[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + dst->m[3] = 0; + + dst->m[4] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + dst->m[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + dst->m[6] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + dst->m[7] = 0; + + dst->m[8] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + dst->m[9] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + dst->m[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + dst->m[11] = 0; + + dst->m[12] = 0; + dst->m[13] = 0; + dst->m[14] = 0; + dst->m[15] = 1; +} + +void Mat4::getScale(Vec3 *scale) const { + decompose(scale, nullptr, nullptr); +} + +bool Mat4::getRotation(Quaternion *rotation) const { + return decompose(nullptr, rotation, nullptr); +} + +void Mat4::getTranslation(Vec3 *translation) const { + decompose(nullptr, nullptr, translation); +} + +void Mat4::getUpVector(Vec3 *dst) const { + CC_ASSERT(dst); + + dst->x = m[4]; + dst->y = m[5]; + dst->z = m[6]; +} + +void Mat4::getDownVector(Vec3 *dst) const { + CC_ASSERT(dst); + + dst->x = -m[4]; + dst->y = -m[5]; + dst->z = -m[6]; +} + +void Mat4::getLeftVector(Vec3 *dst) const { + CC_ASSERT(dst); + + dst->x = -m[0]; + dst->y = -m[1]; + dst->z = -m[2]; +} + +void Mat4::getRightVector(Vec3 *dst) const { + CC_ASSERT(dst); + + dst->x = m[0]; + dst->y = m[1]; + dst->z = m[2]; +} + +void Mat4::getForwardVector(Vec3 *dst) const { + CC_ASSERT(dst); + + dst->x = -m[8]; + dst->y = -m[9]; + dst->z = -m[10]; +} + +void Mat4::getBackVector(Vec3 *dst) const { + CC_ASSERT(dst); + + dst->x = m[8]; + dst->y = m[9]; + dst->z = m[10]; +} + +Mat4 Mat4::clone() const { + Mat4 mat(*this); + return mat; +} + +Mat4 Mat4::getInversed() const { + Mat4 mat(*this); + mat.inverse(); + return mat; +} + +bool Mat4::inverse() { + float a0 = m[0] * m[5] - m[1] * m[4]; + float a1 = m[0] * m[6] - m[2] * m[4]; + float a2 = m[0] * m[7] - m[3] * m[4]; + float a3 = m[1] * m[6] - m[2] * m[5]; + float a4 = m[1] * m[7] - m[3] * m[5]; + float a5 = m[2] * m[7] - m[3] * m[6]; + float b0 = m[8] * m[13] - m[9] * m[12]; + float b1 = m[8] * m[14] - m[10] * m[12]; + float b2 = m[8] * m[15] - m[11] * m[12]; + float b3 = m[9] * m[14] - m[10] * m[13]; + float b4 = m[9] * m[15] - m[11] * m[13]; + float b5 = m[10] * m[15] - m[11] * m[14]; + + // Calculate the determinant. + float det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0; + + // Close to zero, can't invert. + if (std::abs(det) <= MATH_TOLERANCE) { + return false; + } + + // Support the case where m == dst. + det = 1.0F / det; + Mat4 inverse; + inverse.m[0] = (m[5] * b5 - m[6] * b4 + m[7] * b3) * det; + inverse.m[1] = (-m[1] * b5 + m[2] * b4 - m[3] * b3) * det; + inverse.m[2] = (m[13] * a5 - m[14] * a4 + m[15] * a3) * det; + inverse.m[3] = (-m[9] * a5 + m[10] * a4 - m[11] * a3) * det; + + inverse.m[4] = (-m[4] * b5 + m[6] * b2 - m[7] * b1) * det; + inverse.m[5] = (m[0] * b5 - m[2] * b2 + m[3] * b1) * det; + inverse.m[6] = (-m[12] * a5 + m[14] * a2 - m[15] * a1) * det; + inverse.m[7] = (m[8] * a5 - m[10] * a2 + m[11] * a1) * det; + + inverse.m[8] = (m[4] * b4 - m[5] * b2 + m[7] * b0) * det; + inverse.m[9] = (-m[0] * b4 + m[1] * b2 - m[3] * b0) * det; + inverse.m[10] = (m[12] * a4 - m[13] * a2 + m[15] * a0) * det; + inverse.m[11] = (-m[8] * a4 + m[9] * a2 - m[11] * a0) * det; + + inverse.m[12] = (-m[4] * b3 + m[5] * b1 - m[6] * b0) * det; + inverse.m[13] = (m[0] * b3 - m[1] * b1 + m[2] * b0) * det; + inverse.m[14] = (-m[12] * a3 + m[13] * a1 - m[14] * a0) * det; + inverse.m[15] = (m[8] * a3 - m[9] * a1 + m[10] * a0) * det; + + *this = inverse; + + return true; +} + +bool Mat4::isIdentity() const { + return (memcmp(m, &IDENTITY, MATRIX_SIZE) == 0); +} + +void Mat4::multiply(float scalar) { + multiply(scalar, this); +} + +void Mat4::multiply(float scalar, Mat4 *dst) const { + multiply(*this, scalar, dst); +} + +void Mat4::multiply(const Mat4 &m, float scalar, Mat4 *dst) { + CC_ASSERT(dst); +#ifdef __SSE__ + MathUtil::multiplyMatrix(m.col, scalar, dst->col); +#else + MathUtil::multiplyMatrix(m.m, scalar, dst->m); +#endif +} + +void Mat4::multiply(const Mat4 &mat) { + multiply(*this, mat, this); +} + +void Mat4::multiply(const Mat4 &m1, const Mat4 &m2, Mat4 *dst) { + CC_ASSERT(dst); +#ifdef __SSE__ + MathUtil::multiplyMatrix(m1.col, m2.col, dst->col); +#else + MathUtil::multiplyMatrix(m1.m, m2.m, dst->m); +#endif +} + +void Mat4::negate() { +#ifdef __SSE__ + MathUtil::negateMatrix(col, col); +#else + MathUtil::negateMatrix(m, m); +#endif +} + +Mat4 Mat4::getNegated() const { + Mat4 mat(*this); + mat.negate(); + return mat; +} + +void Mat4::rotate(const Quaternion &q) { + rotate(q, this); +} + +void Mat4::rotate(const Quaternion &q, Mat4 *dst) const { + Mat4 r; + createRotation(q, &r); + multiply(*this, r, dst); +} + +void Mat4::rotate(const Vec3 &axis, float angle) { + rotate(axis, angle, this); +} + +void Mat4::rotate(const Vec3 &axis, float angle, Mat4 *dst) const { + Mat4 r; + createRotation(axis, angle, &r); + multiply(*this, r, dst); +} + +void Mat4::rotateX(float angle) { + rotateX(angle, this); +} + +void Mat4::rotateX(float angle, Mat4 *dst) const { + Mat4 r; + createRotationX(angle, &r); + multiply(*this, r, dst); +} + +void Mat4::rotateY(float angle) { + rotateY(angle, this); +} + +void Mat4::rotateY(float angle, Mat4 *dst) const { + Mat4 r; + createRotationY(angle, &r); + multiply(*this, r, dst); +} + +void Mat4::rotateZ(float angle) { + rotateZ(angle, this); +} + +void Mat4::rotateZ(float angle, Mat4 *dst) const { + Mat4 r; + createRotationZ(angle, &r); + multiply(*this, r, dst); +} + +void Mat4::scale(float value) { + scale(value, this); +} + +void Mat4::scale(float value, Mat4 *dst) const { + scale(value, value, value, dst); +} + +void Mat4::scale(float xScale, float yScale, float zScale) { + scale(xScale, yScale, zScale, this); +} + +void Mat4::scale(float xScale, float yScale, float zScale, Mat4 *dst) const { + Mat4 s; + createScale(xScale, yScale, zScale, &s); + multiply(*this, s, dst); +} + +void Mat4::scale(const Vec3 &s) { + scale(s.x, s.y, s.z, this); +} + +void Mat4::scale(const Vec3 &s, Mat4 *dst) const { + scale(s.x, s.y, s.z, dst); +} + +void Mat4::set(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) { + m[0] = m00; + m[1] = m01; + m[2] = m02; + m[3] = m03; + + m[4] = m10; + m[5] = m11; + m[6] = m12; + m[7] = m13; + + m[8] = m20; + m[9] = m21; + m[10] = m22; + m[11] = m23; + + m[12] = m30; + m[13] = m31; + m[14] = m32; + m[15] = m33; +} + +void Mat4::set(const float *mat) { + CC_ASSERT(mat); + memcpy(this->m, mat, MATRIX_SIZE); +} + +void Mat4::set(const Mat4 &mat) { + memcpy(this->m, mat.m, MATRIX_SIZE); +} + +void Mat4::setIdentity() { + memcpy(m, IDENTITY.m, MATRIX_SIZE); +} + +void Mat4::setZero() { + memset(m, 0, MATRIX_SIZE); +} + +void Mat4::subtract(const Mat4 &mat) { + subtract(*this, mat, this); +} + +void Mat4::subtract(const Mat4 &m1, const Mat4 &m2, Mat4 *dst) { + CC_ASSERT(dst); +#ifdef __SSE__ + MathUtil::subtractMatrix(m1.col, m2.col, dst->col); +#else + MathUtil::subtractMatrix(m1.m, m2.m, dst->m); +#endif +} + +void Mat4::transformVector(Vec4 *vector) const { + CC_ASSERT(vector); + transformVector(*vector, vector); +} + +void Mat4::transformVector(const Vec4 &vector, Vec4 *dst) const { + CC_ASSERT(dst); +#ifdef __SSE__ + MathUtil::transformVec4(col, vector.v, dst->v); +#else + MathUtil::transformVec4(m, reinterpret_cast(&vector), reinterpret_cast(dst)); +#endif +} + +void Mat4::translate(float x, float y, float z) { + translate(x, y, z, this); +} + +void Mat4::translate(float x, float y, float z, Mat4 *dst) const { + Mat4 t; + createTranslation(x, y, z, &t); + multiply(*this, t, dst); +} + +void Mat4::translate(const Vec3 &t) { + translate(t.x, t.y, t.z, this); +} + +void Mat4::translate(const Vec3 &t, Mat4 *dst) const { + translate(t.x, t.y, t.z, dst); +} + +void Mat4::transpose() { +#ifdef __SSE__ + MathUtil::transposeMatrix(col, col); +#else + MathUtil::transposeMatrix(m, m); +#endif +} + +Mat4 Mat4::getTransposed() const { + Mat4 mat(*this); + mat.transpose(); + return mat; +} + +bool Mat4::approxEquals(const Mat4 &v, float precision /* = CC_FLOAT_CMP_PRECISION */) const { + return math::isEqualF(m[0], v.m[0], precision) && math::isEqualF(m[1], v.m[1], precision) && + math::isEqualF(m[2], v.m[2], precision) && math::isEqualF(m[3], v.m[3], precision) && + math::isEqualF(m[4], v.m[4], precision) && math::isEqualF(m[5], v.m[5], precision) && + math::isEqualF(m[6], v.m[6], precision) && math::isEqualF(m[7], v.m[7], precision) && + math::isEqualF(m[8], v.m[8], precision) && math::isEqualF(m[9], v.m[9], precision) && + math::isEqualF(m[10], v.m[10], precision) && math::isEqualF(m[11], v.m[11], precision) && + math::isEqualF(m[12], v.m[12], precision) && math::isEqualF(m[13], v.m[13], precision) && + math::isEqualF(m[14], v.m[14], precision) && math::isEqualF(m[15], v.m[15], precision); +} + +const Mat4 Mat4::IDENTITY = Mat4( + 1.0F, 0.0F, 0.0F, 0.0F, + 0.0F, 1.0F, 0.0F, 0.0F, + 0.0F, 0.0F, 1.0F, 0.0F, + 0.0F, 0.0F, 0.0F, 1.0F); + +const Mat4 Mat4::ZERO = Mat4( + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0); + +NS_CC_MATH_END diff --git a/cocos/math/Mat4.h b/cocos/math/Mat4.h new file mode 100644 index 0000000..708584d --- /dev/null +++ b/cocos/math/Mat4.h @@ -0,0 +1,964 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#pragma once + +#include "base/Macros.h" + +#include "math/Vec3.h" +#include "math/Vec4.h" + +#undef __SSE__ +#ifdef __SSE__ + #include +#endif + +/** + * @addtogroup base + * @{ + */ + +NS_CC_MATH_BEGIN + +//class Plane; + +/** + * Defines a 4 x 4 floating point matrix representing a 3D transformation. + * + * Vectors are treated as columns, resulting in a matrix that is represented as follows, + * where x, y and z are the translation components of the matrix: + * + * 1 0 0 x + * 0 1 0 y + * 0 0 1 z + * 0 0 0 1 + * + * This matrix class is directly compatible with OpenGL since its elements are + * laid out in memory exactly as they are expected by OpenGL. + * The matrix uses column-major format such that array indices increase down column first. + * Since matrix multiplication is not commutative, multiplication must be done in the + * correct order when combining transformations. Suppose we have a translation + * matrix T and a rotation matrix R. To first rotate an object around the origin + * and then translate it, you would multiply the two matrices as TR. + * + * Likewise, to first translate the object and then rotate it, you would do RT. + * So generally, matrices must be multiplied in the reverse order in which you + * want the transformations to take place (this also applies to + * the scale, rotate, and translate methods below; these methods are convenience + * methods for post-multiplying by a matrix representing a scale, rotation, or translation). + * + * In the case of repeated local transformations (i.e. rotate around the Z-axis by 0.76 radians, + * then translate by 2.1 along the X-axis, then ...), it is better to use the Transform class + * (which is optimized for that kind of usage). + * + * @see Transform + */ +class CC_DLL Mat4 { +public: + // //temp add conversion + // operator kmMat4() const + // { + // kmMat4 result; + // kmMat4Fill(&result, m); + // return result; + // } + + // Mat4(const kmMat4& mat) + // { + // set(mat.mat); + // } + /** + * Stores the columns of this 4x4 matrix. + * matrix layout + * |m[0] m[4] m[8] m[12]| + * |m[1] m[5] m[9] m[13]| + * |m[2] m[6] m[10] m[14]| + * |m[3] m[7] m[11] m[15]| + */ +#ifdef __SSE__ + union { + __m128 col[4]; + float m[16]; + }; +#else + float m[16]; +#endif + + /** + * Default constructor. + * Constructs a matrix initialized to the identity matrix: + * + * 1 0 0 0 + * 0 1 0 0 + * 0 0 1 0 + * 0 0 0 1 + */ + Mat4(); + + /** + * Constructs a matrix initialized to the specified value. + * + * @param m00 Component in column 0, row 0 position (index 0) + * @param m01 Component in column 0, row 1 position (index 1) + * @param m02 Component in column 0, row 2 position (index 2) + * @param m03 Component in column 0, row 3 position (index 3) + * @param m10 Component in column 1, row 0 position (index 4) + * @param m11 Component in column 1, row 1 position (index 5) + * @param m12 Component in column 1, row 2 position (index 6) + * @param m13 Component in column 1, row 3 position (index 7) + * @param m20 Component in column 2, row 0 position (index 8) + * @param m21 Component in column 2, row 1 position (index 9) + * @param m22 Component in column 2, row 2 position (index 10) + * @param m23 Component in column 2, row 3 position (index 11) + * @param m30 Component in column 3, row 0 position (index 12) + * @param m31 Component in column 3, row 1 position (index 13) + * @param m32 Component in column 3, row 2 position (index 14) + * @param m33 Component in column 3, row 3 position (index 15) + */ + Mat4(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33); + + /** + * Creates a matrix initialized to the specified column-major array. + * + * The passed-in array is in column-major order, so the memory layout of the array is as follows: + * + * 0 4 8 12 + * 1 5 9 13 + * 2 6 10 14 + * 3 7 11 15 + * + * @param mat An array containing 16 elements in column-major order. + */ + explicit Mat4(const float *mat); + + /** + * Constructs a new matrix by copying the values from the specified matrix. + * + * @param copy The matrix to copy. + */ + Mat4(const Mat4 ©); + + /** + * Destructor. + */ + ~Mat4() = default; + + /** + * Creates a view matrix based on the specified input parameters. + * + * @param eyePosition The eye position. + * @param targetPosition The target's center position. + * @param up The up vector. + * @param dst A matrix to store the result in. + */ + static void createLookAt(const Vec3 &eyePosition, const Vec3 &targetPosition, const Vec3 &up, Mat4 *dst); + + /** + * Creates a view matrix based on the specified input parameters. + * + * @param eyePositionX The eye x-coordinate position. + * @param eyePositionY The eye y-coordinate position. + * @param eyePositionZ The eye z-coordinate position. + * @param targetCenterX The target's center x-coordinate position. + * @param targetCenterY The target's center y-coordinate position. + * @param targetCenterZ The target's center z-coordinate position. + * @param upX The up vector x-coordinate value. + * @param upY The up vector y-coordinate value. + * @param upZ The up vector z-coordinate value. + * @param dst A matrix to store the result in. + */ + static void createLookAt(float eyePositionX, float eyePositionY, float eyePositionZ, + float targetPositionX, float targetPositionY, float targetPositionZ, + float upX, float upY, float upZ, Mat4 *dst); + + /** + * Builds a perspective projection matrix based on a field of view and returns by value. + * + * Projection space refers to the space after applying projection transformation from view space. + * After the projection transformation, visible content has x- and y-coordinates ranging from -1 to 1, + * and a z-coordinate ranging from 0 to 1. To obtain the viewable area (in world space) of a scene, + * create a BoundingFrustum and pass the combined view and projection matrix to the constructor. + * + * @param fieldOfView The field of view in the y direction (in degrees). + * @param aspectRatio The aspect ratio, defined as view space width divided by height. + * @param zNearPlane The distance to the near view plane. + * @param zFarPlane The distance to the far view plane. + * @param dst A matrix to store the result in. + */ + static void createPerspective(float fieldOfView, float aspectRatio, float zNearPlane, float zFarPlane, Mat4 *dst = nullptr) { + Mat4::createPerspective(fieldOfView, aspectRatio, zNearPlane, zFarPlane, true, -1.0F, 1.0F, 0, dst); + } + + static void createPerspective(float fieldOfView, float aspectRatio, float zNearPlane, float zFarPlane, + bool isFieldOfViewY = false, float minClipZ = -1, float projectionSignY = 1, + int orientation = 0, Mat4 *dst = nullptr); + + /** + * Creates an orthographic projection matrix. + * + * Projection space refers to the space after applying + * projection transformation from view space. After the + * projection transformation, visible content has + * x and y coordinates ranging from -1 to 1, and z coordinates + * ranging from 0 to 1. + * + * Unlike perspective projection, in orthographic projection + * there is no perspective foreshortening. + * + * The viewable area of this orthographic projection extends + * from left to right on the x-axis, bottom to top on the y-axis, + * and zNearPlane to zFarPlane on the z-axis. These values are + * relative to the position and x, y, and z-axes of the view. + * To obtain the viewable area (in world space) of a scene, + * create a BoundingFrustum and pass the combined view and + * projection matrix to the constructor. + * + * @param left The minimum x-value of the view volume. + * @param right The maximum x-value of the view volume. + * @param bottom The minimum y-value of the view volume. + * @param top The maximum y-value of the view volume. + * @param zNearPlane The minimum z-value of the view volume. + * @param zFarPlane The maximum z-value of the view volume. + * @param dst A matrix to store the result in. + */ + static void createOrthographic(float left, float right, float bottom, float top, float zNearPlane, float zFarPlane, Mat4 *dst); + + static void createOrthographicOffCenter(float left, float right, float bottom, float top, + float zNearPlane, float zFarPlane, Mat4 *dst); + + static void createOrthographicOffCenter(float left, float right, float bottom, float top, + float zNearPlane, float zFarPlane, float minClipZ, + float projectionSignY, int orientation, Mat4 *dst); + + /** + * Creates a spherical billboard that rotates around a specified object position. + * + * This method computes the facing direction of the billboard from the object position + * and camera position. When the object and camera positions are too close, the matrix + * will not be accurate. To avoid this problem, this method defaults to the identity + * rotation if the positions are too close. (See the other overload of createBillboard + * for an alternative approach). + * + * @param objectPosition The position of the object the billboard will rotate around. + * @param cameraPosition The position of the camera. + * @param cameraUpVector The up vector of the camera. + * @param dst A matrix to store the result in. + */ + static void createBillboard(const Vec3 &objectPosition, const Vec3 &cameraPosition, + const Vec3 &cameraUpVector, Mat4 *dst); + + /** + * Creates a spherical billboard that rotates around a specified object position with + * provision for a safe default orientation. + * + * This method computes the facing direction of the billboard from the object position + * and camera position. When the object and camera positions are too close, the matrix + * will not be accurate. To avoid this problem, this method uses the specified camera + * forward vector if the positions are too close. (See the other overload of createBillboard + * for an alternative approach). + * + * @param objectPosition The position of the object the billboard will rotate around. + * @param cameraPosition The position of the camera. + * @param cameraUpVector The up vector of the camera. + * @param cameraForwardVector The forward vector of the camera, used if the positions are too close. + * @param dst A matrix to store the result in. + */ + static void createBillboard(const Vec3 &objectPosition, const Vec3 &cameraPosition, + const Vec3 &cameraUpVector, const Vec3 &cameraForwardVector, + Mat4 *dst); + + //Fills in an existing Mat4 so that it reflects the coordinate system about a specified Plane. + //plane The Plane about which to create a reflection. + //dst A matrix to store the result in. + //static void createReflection(const Plane& plane, Mat4* dst); + + /** + * Creates a scale matrix. + * + * @param scale The amount to scale. + * @param dst A matrix to store the result in. + */ + static void createScale(const Vec3 &scale, Mat4 *dst); + + /** + * Creates a scale matrix. + * + * @param xScale The amount to scale along the x-axis. + * @param yScale The amount to scale along the y-axis. + * @param zScale The amount to scale along the z-axis. + * @param dst A matrix to store the result in. + */ + static void createScale(float xScale, float yScale, float zScale, Mat4 *dst); + + /** + * Creates a rotation matrix from the specified quaternion. + * + * @param quat A quaternion describing a 3D orientation. + * @param dst A matrix to store the result in. + */ + static void createRotation(const Quaternion &quat, Mat4 *dst); + + /** + * Creates a rotation matrix from the specified axis and angle. + * + * @param axis A vector describing the axis to rotate about. + * @param angle The angle (in radians). + * @param dst A matrix to store the result in. + */ + static void createRotation(const Vec3 &axis, float angle, Mat4 *dst); + + /** + * Creates a matrix describing a rotation around the x-axis. + * + * @param angle The angle of rotation (in radians). + * @param dst A matrix to store the result in. + */ + static void createRotationX(float angle, Mat4 *dst); + + /** + * Creates a matrix describing a rotation around the y-axis. + * + * @param angle The angle of rotation (in radians). + * @param dst A matrix to store the result in. + */ + static void createRotationY(float angle, Mat4 *dst); + + /** + * Creates a matrix describing a rotation around the z-axis. + * + * @param angle The angle of rotation (in radians). + * @param dst A matrix to store the result in. + */ + static void createRotationZ(float angle, Mat4 *dst); + + /** + * Creates a translation matrix. + * + * @param translation The translation. + * @param dst A matrix to store the result in. + */ + static void createTranslation(const Vec3 &translation, Mat4 *dst); + + /** + * Creates a translation matrix. + * + * @param xTranslation The translation on the x-axis. + * @param yTranslation The translation on the y-axis. + * @param zTranslation The translation on the z-axis. + * @param dst A matrix to store the result in. + */ + static void createTranslation(float xTranslation, float yTranslation, float zTranslation, Mat4 *dst); + + /** + * Adds a scalar value to each component of this matrix. + * + * @param scalar The scalar to add. + */ + void add(float scalar); + + /** + * Adds a scalar value to each component of this matrix and stores the result in dst. + * + * @param scalar The scalar value to add. + * @param dst A matrix to store the result in. + */ + void add(float scalar, Mat4 *dst); + + /** + * Adds the specified matrix to this matrix. + * + * @param mat The matrix to add. + */ + void add(const Mat4 &mat); + + /** + * Adds the specified matrices and stores the result in dst. + * + * @param m1 The first matrix. + * @param m2 The second matrix. + * @param dst The destination matrix to add to. + */ + static void add(const Mat4 &m1, const Mat4 &m2, Mat4 *dst); + + /** + * Calculate the matrix according to the ratation and translation + */ + static void fromRT(const Quaternion &rotation, const Vec3 &translation, Mat4 *dst); + + /** + * Compose a matrix from scale, rotation and translation, applied in order. + */ + static void fromRTS(const Quaternion &rotation, const Vec3 &translation, const Vec3 &scale, Mat4 *dst); + + /** + * Decomposes the scale, rotation and translation components of this matrix. + */ + static void toRTS(const Mat4 &src, Quaternion *rotation, Vec3 *translation, Vec3 *scale); + + /** + * Decomposes the scale, rotation and translation components of this matrix. + * + * @param scale The scale. + * @param rotation The rotation. + * @param translation The translation. + */ + bool decompose(Vec3 *scale, Quaternion *rotation, Vec3 *translation) const; + + /** + * Computes the determinant of this matrix. + * + * @return The determinant. + */ + float determinant() const; + + /** + * Gets the scalar component of this matrix in the specified vector. + * + * If the scalar component of this matrix has negative parts, + * it is not possible to always extract the exact scalar component; + * instead, a scale vector that is mathematically equivalent to the + * original scale vector is extracted and returned. + * + * @param scale A vector to receive the scale. + */ + void getScale(Vec3 *scale) const; + + /** + * Gets the rotational component of this matrix in the specified quaternion. + * + * @param rotation A quaternion to receive the rotation. + * + * @return true if the rotation is successfully extracted, false otherwise. + */ + bool getRotation(Quaternion *rotation) const; + + /** + * Gets the translational component of this matrix in the specified vector. + * + * @param translation A vector to receive the translation. + */ + void getTranslation(Vec3 *translation) const; + + /** + * Gets the up vector of this matrix. + * + * @param dst The destination vector. + */ + void getUpVector(Vec3 *dst) const; + + /** + * Gets the down vector of this matrix. + * + * @param dst The destination vector. + */ + void getDownVector(Vec3 *dst) const; + + /** + * Gets the left vector of this matrix. + * + * @param dst The destination vector. + */ + void getLeftVector(Vec3 *dst) const; + + /** + * Gets the right vector of this matrix. + * + * @param dst The destination vector. + */ + void getRightVector(Vec3 *dst) const; + + /** + * Gets the forward vector of this matrix. + * + * @param dst The destination vector. + */ + void getForwardVector(Vec3 *dst) const; + + /** + * Gets the backward vector of this matrix. + * + * @param dst The destination vector. + */ + void getBackVector(Vec3 *dst) const; + + /** + * Inverts this matrix. + * + * @return true if the matrix can be inverted, false otherwise. + */ + bool inverse(); + + /** + * Clone this matrix. + * + * @return a new clone matrix. + */ + Mat4 clone() const; + + /** + * Get the inversed matrix. + */ + Mat4 getInversed() const; + + /** + * Determines if this matrix is equal to the identity matrix. + * + * @return true if the matrix is an identity matrix, false otherwise. + */ + bool isIdentity() const; + + /** + * Multiplies the components of this matrix by the specified scalar. + * + * @param scalar The scalar value. + */ + void multiply(float scalar); + + /** + * Multiplies the components of this matrix by a scalar and stores the result in dst. + * + * @param scalar The scalar value. + * @param dst A matrix to store the result in. + */ + void multiply(float scalar, Mat4 *dst) const; + + /** + * Multiplies the components of the specified matrix by a scalar and stores the result in dst. + * + * @param mat The matrix. + * @param scalar The scalar value. + * @param dst A matrix to store the result in. + */ + static void multiply(const Mat4 &mat, float scalar, Mat4 *dst); + + /** + * Multiplies this matrix by the specified one. + * + * @param mat The matrix to multiply. + */ + void multiply(const Mat4 &mat); + + /** + * Multiplies m1 by m2 and stores the result in dst. + * + * @param m1 The first matrix to multiply. + * @param m2 The second matrix to multiply. + * @param dst A matrix to store the result in. + */ + static void multiply(const Mat4 &m1, const Mat4 &m2, Mat4 *dst); + + /** + * Negates this matrix. + */ + void negate(); + + /** + Get the Negated matrix. + */ + Mat4 getNegated() const; + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified quaternion rotation. + * + * @param q The quaternion to rotate by. + */ + void rotate(const Quaternion &q); + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified quaternion rotation and stores the result in dst. + * + * @param q The quaternion to rotate by. + * @param dst A matrix to store the result in. + */ + void rotate(const Quaternion &q, Mat4 *dst) const; + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified rotation about the specified axis. + * + * @param axis The axis to rotate about. + * @param angle The angle (in radians). + */ + void rotate(const Vec3 &axis, float angle); + + /** + * Post-multiplies this matrix by the matrix corresponding to the specified + * rotation about the specified axis and stores the result in dst. + * + * @param axis The axis to rotate about. + * @param angle The angle (in radians). + * @param dst A matrix to store the result in. + */ + void rotate(const Vec3 &axis, float angle, Mat4 *dst) const; + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified rotation around the x-axis. + * + * @param angle The angle (in radians). + */ + void rotateX(float angle); + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified rotation around the x-axis and stores the result in dst. + * + * @param angle The angle (in radians). + * @param dst A matrix to store the result in. + */ + void rotateX(float angle, Mat4 *dst) const; + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified rotation around the y-axis. + * + * @param angle The angle (in radians). + */ + void rotateY(float angle); + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified rotation around the y-axis and stores the result in dst. + * + * @param angle The angle (in radians). + * @param dst A matrix to store the result in. + */ + void rotateY(float angle, Mat4 *dst) const; + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified rotation around the z-axis. + * + * @param angle The angle (in radians). + */ + void rotateZ(float angle); + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified rotation around the z-axis and stores the result in dst. + * + * @param angle The angle (in radians). + * @param dst A matrix to store the result in. + */ + void rotateZ(float angle, Mat4 *dst) const; + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified scale transformation. + * + * @param value The amount to scale along all axes. + */ + void scale(float value); + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified scale transformation and stores the result in dst. + * + * @param value The amount to scale along all axes. + * @param dst A matrix to store the result in. + */ + void scale(float value, Mat4 *dst) const; + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified scale transformation. + * + * @param xScale The amount to scale along the x-axis. + * @param yScale The amount to scale along the y-axis. + * @param zScale The amount to scale along the z-axis. + */ + void scale(float xScale, float yScale, float zScale); + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified scale transformation and stores the result in dst. + * + * @param xScale The amount to scale along the x-axis. + * @param yScale The amount to scale along the y-axis. + * @param zScale The amount to scale along the z-axis. + * @param dst A matrix to store the result in. + */ + void scale(float xScale, float yScale, float zScale, Mat4 *dst) const; + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified scale transformation. + * + * @param s The scale values along the x, y and z axes. + */ + void scale(const Vec3 &s); + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified scale transformation and stores the result in dst. + * + * @param s The scale values along the x, y and z axes. + * @param dst A matrix to store the result in. + */ + void scale(const Vec3 &s, Mat4 *dst) const; + + /** + * Sets the values of this matrix. + * + * @param m00 Component in column 0, row 0 position (index 0) + * @param m01 Component in column 0, row 1 position (index 1) + * @param m02 Component in column 0, row 2 position (index 2) + * @param m03 Component in column 0, row 3 position (index 3) + * @param m10 Component in column 1, row 0 position (index 4) + * @param m11 Component in column 1, row 1 position (index 5) + * @param m12 Component in column 1, row 2 position (index 6) + * @param m13 Component in column 1, row 3 position (index 7) + * @param m20 Component in column 2, row 0 position (index 8) + * @param m21 Component in column 2, row 1 position (index 9) + * @param m22 Component in column 2, row 2 position (index 10) + * @param m23 Component in column 2, row 3 position (index 11) + * @param m30 Component in column 3, row 0 position (index 12) + * @param m31 Component in column 3, row 1 position (index 13) + * @param m32 Component in column 3, row 2 position (index 14) + * @param m33 Component in column 3, row 3 position (index 15) + */ + void set(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33); + + /** + * Sets the values of this matrix to those in the specified column-major array. + * + * @param mat An array containing 16 elements in column-major format. + */ + void set(const float *mat); + + /** + * Sets the values of this matrix to those of the specified matrix. + * + * @param mat The source matrix. + */ + void set(const Mat4 &mat); + + /** + * Sets this matrix to the identity matrix. + */ + void setIdentity(); + + /** + * Sets all elements of the current matrix to zero. + */ + void setZero(); + + /** + * Subtracts the specified matrix from the current matrix. + * + * @param mat The matrix to subtract. + */ + void subtract(const Mat4 &mat); + + /** + * Subtracts the specified matrix from the current matrix. + * + * @param m1 The first matrix. + * @param m2 The second matrix. + * @param dst A matrix to store the result in. + */ + static void subtract(const Mat4 &m1, const Mat4 &m2, Mat4 *dst); + + /** + * Transforms the specified vector by this matrix. + * + * The result of the transformation is stored directly into vector. + * + * @param vector The vector to transform. + */ + void transformVector(Vec4 *vector) const; + + /** + * Transforms the specified vector by this matrix. + * + * @param vector The vector to transform. + * @param dst A vector to store the transformed point in. + */ + void transformVector(const Vec4 &vector, Vec4 *dst) const; + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified translation. + * + * @param x The amount to translate along the x-axis. + * @param y The amount to translate along the y-axis. + * @param z The amount to translate along the z-axis. + */ + void translate(float x, float y, float z); + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified translation and stores the result in dst. + * + * @param x The amount to translate along the x-axis. + * @param y The amount to translate along the y-axis. + * @param z The amount to translate along the z-axis. + * @param dst A matrix to store the result in. + */ + void translate(float x, float y, float z, Mat4 *dst) const; + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified translation. + * + * @param t The translation values along the x, y and z axes. + */ + void translate(const Vec3 &t); + + /** + * Post-multiplies this matrix by the matrix corresponding to the + * specified translation and stores the result in dst. + * + * @param t The translation values along the x, y and z axes. + * @param dst A matrix to store the result in. + */ + void translate(const Vec3 &t, Mat4 *dst) const; + + /** + * Transposes this matrix. + */ + void transpose(); + + /** + * Get the Transposed matrix. + */ + Mat4 getTransposed() const; + + /** + * Calculates the inverse transpose of a matrix and save the results to out matrix + */ + static void inverseTranspose(const Mat4 &mat, Mat4 *dst); + /** + * Calculates the sum of this matrix with the given matrix. + * + * Note: this does not modify this matrix. + * + * @param mat The matrix to add. + * @return The matrix sum. + */ + inline const Mat4 operator+(const Mat4 &mat) const; + + /** + * Adds the given matrix to this matrix. + * + * @param mat The matrix to add. + * @return This matrix, after the addition occurs. + */ + inline Mat4 &operator+=(const Mat4 &mat); + + /** + * Calculates the difference of this matrix with the given matrix. + * + * Note: this does not modify this matrix. + * + * @param mat The matrix to subtract. + * @return The matrix difference. + */ + inline const Mat4 operator-(const Mat4 &mat) const; + + /** + * Subtracts the given matrix from this matrix. + * + * @param mat The matrix to subtract. + * @return This matrix, after the subtraction occurs. + */ + inline Mat4 &operator-=(const Mat4 &mat); + + /** + * Calculates the negation of this matrix. + * + * Note: this does not modify this matrix. + * + * @return The negation of this matrix. + */ + inline const Mat4 operator-() const; + + /** + * Calculates the matrix product of this matrix with the given matrix. + * + * Note: this does not modify this matrix. + * + * @param mat The matrix to multiply by. + * @return The matrix product. + */ + inline const Mat4 operator*(const Mat4 &mat) const; + + /** + * Right-multiplies this matrix by the given matrix. + * + * @param mat The matrix to multiply by. + * @return This matrix, after the multiplication occurs. + */ + inline Mat4 &operator*=(const Mat4 &mat); + + /** + * Determines if this matrix is approximately equal to the given matrix. + */ + bool approxEquals(const Mat4 &v, float precision = CC_FLOAT_CMP_PRECISION) const; + + /** equals to a matrix full of zeros */ + static const Mat4 ZERO; + /** equals to the identity matrix */ + static const Mat4 IDENTITY; + +private: + static void createBillboardHelper(const Vec3 &objectPosition, const Vec3 &cameraPosition, + const Vec3 &cameraUpVector, const Vec3 *cameraForwardVector, + Mat4 *dst); +}; + +/** + * Transforms the given vector by the given matrix. + * + * Note: this treats the given vector as a vector and not as a point. + * + * @param v The vector to transform. + * @param m The matrix to transform by. + * @return This vector, after the transformation occurs. + */ +inline Vec4 &operator*=(Vec4 &v, const Mat4 &m); + +/** + * Transforms the given vector by the given matrix. + * + * Note: this treats the given vector as a vector and not as a point. + * + * @param m The matrix to transform by. + * @param v The vector to transform. + * @return The resulting transformed vector. + */ +inline const Vec4 operator*(const Mat4 &m, const Vec4 &v); + +NS_CC_MATH_END +/** + end of base group + @} + */ +#include "math/Mat4.inl" diff --git a/cocos/math/Mat4.inl b/cocos/math/Mat4.inl new file mode 100644 index 0000000..438d90b --- /dev/null +++ b/cocos/math/Mat4.inl @@ -0,0 +1,86 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#include "math/Mat4.h" + +NS_CC_MATH_BEGIN + +inline const Mat4 Mat4::operator+(const Mat4& mat) const +{ + Mat4 result(*this); + result.add(mat); + return result; +} + +inline Mat4& Mat4::operator+=(const Mat4& mat) +{ + add(mat); + return *this; +} + +inline const Mat4 Mat4::operator-(const Mat4& mat) const +{ + Mat4 result(*this); + result.subtract(mat); + return result; +} + +inline Mat4& Mat4::operator-=(const Mat4& mat) +{ + subtract(mat); + return *this; +} + +inline const Mat4 Mat4::operator-() const +{ + Mat4 mat(*this); + mat.negate(); + return mat; +} + +inline const Mat4 Mat4::operator*(const Mat4& mat) const +{ + Mat4 result(*this); + result.multiply(mat); + return result; +} + +inline Mat4& Mat4::operator*=(const Mat4& mat) +{ + multiply(mat); + return *this; +} + +inline Vec4& operator*=(Vec4& v, const Mat4& m) +{ + m.transformVector(&v); + return v; +} + +inline const Vec4 operator*(const Mat4& m, const Vec4& v) +{ + Vec4 x; + m.transformVector(v, &x); + return x; +} + +NS_CC_MATH_END diff --git a/cocos/math/Math.cpp b/cocos/math/Math.cpp new file mode 100644 index 0000000..c5dc2d9 --- /dev/null +++ b/cocos/math/Math.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "math/Math.h" +#include +#include +#include +#include + +namespace cc { +namespace math { + +const float PI = static_cast(3.14159265358979323846264338327950288419716939937511); + +const float PI_2 = math::PI * 2.0F; +const float PI_DIV2 = math::PI * 0.5F; +const float PI_DIV3 = math::PI / 3.0F; +const float PI_DIV4 = math::PI / 4.0F; +const float PI_DIV5 = math::PI / 5.0F; +const float PI_DIV6 = math::PI / 6.0F; +const float PI_DIV8 = math::PI / 8.0F; +const float PI_SQR = static_cast(9.86960440108935861883449099987615113531369940724079); +const float PI_INV = static_cast(0.31830988618379067153776752674502872406891929148091); +const float EPSILON = std::numeric_limits::epsilon(); +const float LOW_EPSILON = static_cast(1e-04); +const float POS_INFINITY = std::numeric_limits::infinity(); +const float NEG_INFINITY = -std::numeric_limits::infinity(); + +const float LN2 = std::log(2.0F); +const float LN10 = std::log(10.0F); +const float LN2_INV = 1.0F / LN2; +const float LN10_INV = 1.0F / LN10; +const float DEG_TO_RAD = static_cast(0.01745329); +const float RAD_TO_DEG = static_cast(57.29577); +const float MIN_FLOAT = 1.175494351e-38F; +const float MAX_FLOAT = 3.402823466e+38F; + +} // namespace math +} // namespace cc diff --git a/cocos/math/Math.h b/cocos/math/Math.h new file mode 100644 index 0000000..8399558 --- /dev/null +++ b/cocos/math/Math.h @@ -0,0 +1,83 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include +#include +#include "base/Macros.h" + +namespace cc { +namespace math { + +extern CC_DLL const float PI; +extern CC_DLL const float PI_2; +extern CC_DLL const float PI_DIV2; +extern CC_DLL const float PI_DIV3; +extern CC_DLL const float PI_DIV4; +extern CC_DLL const float PI_DIV5; +extern CC_DLL const float PI_DIV6; +extern CC_DLL const float PI_DIV8; +extern CC_DLL const float PI_SQR; +extern CC_DLL const float PI_INV; +extern CC_DLL const float EPSILON; +extern CC_DLL const float LOW_EPSILON; +extern CC_DLL const float POS_INFINITY; +extern CC_DLL const float NEG_INFINITY; +extern CC_DLL const float LN2; +extern CC_DLL const float LN10; +extern CC_DLL const float LN2_INV; +extern CC_DLL const float LN10_INV; +extern CC_DLL const float DEG_TO_RAD; +extern CC_DLL const float RAD_TO_DEG; +extern CC_DLL const float MIN_FLOAT; +extern CC_DLL const float MAX_FLOAT; + +#define CC_FLOAT_CMP_PRECISION (0.00001F) + +template +inline T sgn(T x) { + return (x < T(0) ? T(-1) : (x > T(0) ? T(1) : T(0))); +} + +template +inline bool isPowerOfTwo(T n) { + return (n & (n - 1)) == 0; +} + +inline bool isEqualF(float lhs, float rhs, float precision = 0.000001F) { + const bool hasInf = std::isinf(lhs) || std::isinf(rhs); + return !hasInf && (std::fabs(lhs - rhs) <= (std::fmax(std::fmax(std::fabs(lhs), std::fabs(rhs)), 1.0F) * precision)); +} + +inline bool isNotEqualF(float lhs, float rhs, float precision = 0.000001F) { // same as !isEqualF + const bool hasInf = std::isinf(lhs) || std::isinf(rhs); + return hasInf || (std::fabs(lhs - rhs) > (std::fmax(std::fmax(std::fabs(lhs), std::fabs(rhs)), 1.0F) * precision)); +} + +inline bool isNotZeroF(float v, float precision = 0.000001F) { // same as isNotEqualF(v, 0.0F) + return std::isinf(v) || (std::fabs(v) > (std::fmax(std::fabs(v), 1.0F) * precision)); +} + +} // namespace math +} // namespace cc diff --git a/cocos/math/MathBase.h b/cocos/math/MathBase.h new file mode 100644 index 0000000..16d286b --- /dev/null +++ b/cocos/math/MathBase.h @@ -0,0 +1,102 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef __CCMATHBASE_H__ +#define __CCMATHBASE_H__ + +#include +#include "base/Macros.h" + +#if CC_PLATFORM != CC_PLATFORM_EMSCRIPTEN + #include "base/std/hash/hash.h" +#endif +/** + * @addtogroup base + * @{ + */ + +/**Util macro for conversion from degrees to radians.*/ +#define MATH_DEG_TO_RAD(x) ((x)*0.0174532925f) +/**Util macro for conversion from radians to degrees.*/ +#define MATH_RAD_TO_DEG(x) ((x)*57.29577951f) +/** +@{ Util macro for const float such as epsilon, small float and float precision tolerance. +*/ +#define MATH_FLOAT_SMALL 1.0e-37f +#define MATH_TOLERANCE 2e-37f +#define MATH_PIOVER2 1.57079632679489661923f +#define MATH_EPSILON 0.000001f +/**@}*/ + +//#define MATH_PIOVER4 0.785398163397448309616f +//#define MATH_PIX2 6.28318530717958647693f +//#define MATH_E 2.71828182845904523536f +//#define MATH_LOG10E 0.4342944819032518f +//#define MATH_LOG2E 1.442695040888963387f +//#define MATH_PI 3.14159265358979323846f +//#define MATH_RANDOM_MINUS1_1() ((2.0f*((float)rand()/RAND_MAX))-1.0f) // Returns a random float between -1 and 1. +//#define MATH_RANDOM_0_1() ((float)rand()/RAND_MAX) // Returns a random float between 0 and 1. +//#define MATH_CLAMP(x, lo, hi) ((x < lo) ? lo : ((x > hi) ? hi : x)) +//#ifndef M_1_PI +//#define M_1_PI 0.31830988618379067154 + +#ifdef __cplusplus + #define NS_CC_MATH_BEGIN namespace cc { + #define NS_CC_MATH_END } + #define USING_NS_CC_MATH using namespace cc +#else + #define NS_CC_MATH_BEGIN + #define NS_CC_MATH_END + #define USING_NS_CC_MATH +#endif + +NS_CC_MATH_BEGIN + +#if CC_PLATFORM != CC_PLATFORM_EMSCRIPTEN + +template ::value>> +struct Hasher final { + // NOTE: ccstd::hash_t is a typedef of uint32_t now, sizeof(ccstd::hash_t) == sizeof(size_t) on 32 bits architecture device, + // sizeof(ccstd::hash_t) < sizeof(size_t) on 64 bits architecture device. + // STL containers like ccstd::unordered_map expects the custom Hasher function to return size_t. + // So it's safe to return ccstd::hash_t for operator() function now. + // If we need to define ccstd::hash_t to uint64_t someday, we must take care of the return value of operator(), + // it should be size_t and we need to convert hash value from uint64_t to uint32_t for 32 bit architecture device. + ccstd::hash_t operator()(const T &info) const; +}; + +// make this ccstd::hash compatible +template ::value>> +ccstd::hash_t hash_value(const T &info) { return Hasher()(info); } // NOLINT(readability-identifier-naming) + +#endif // CC_PLATFORM != CC_PLATFORM_EMSCRIPTEN + +NS_CC_MATH_END + +/** + * end of base group + * @} + */ + +#endif // __CCMATHBASE_H__ diff --git a/cocos/math/MathUtil.cpp b/cocos/math/MathUtil.cpp new file mode 100644 index 0000000..b5d976b --- /dev/null +++ b/cocos/math/MathUtil.cpp @@ -0,0 +1,286 @@ +/** +Copyright 2013 BlackBerry Inc. +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Original file from GamePlay3D: http://gameplay3d.org + +This file was modified to fit the cocos2d-x project +*/ + +#include "math/MathUtil.h" +#include "base/Macros.h" + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + #include +#endif + +//#define USE_NEON32 : neon 32 code will be used +//#define USE_NEON64 : neon 64 code will be used +//#define INCLUDE_NEON32 : neon 32 code included +//#define INCLUDE_NEON64 : neon 64 code included +//#define USE_SSE : SSE code used +//#define INCLUDE_SSE : SSE code included + +#if (CC_PLATFORM == CC_PLATFORM_IOS) + #if defined(__arm64__) + #define USE_NEON64 + #define INCLUDE_NEON64 + #endif +#elif (CC_PLATFORM == CC_PLATFORM_ANDROID) + #if defined(__arm64__) || defined(__aarch64__) + #define USE_NEON64 + #define INCLUDE_NEON64 + #elif defined(__ARM_NEON__) + #define INCLUDE_NEON32 + #endif +#endif + +#if defined(__SSE__) + #define USE_SSE + #define INCLUDE_SSE +#endif + +#ifdef INCLUDE_NEON32 + #include "math/MathUtilNeon.inl" +#endif + +#ifdef INCLUDE_NEON64 + #include "math/MathUtilNeon64.inl" +#endif + +#ifdef INCLUDE_SSE + #include "math/MathUtilSSE.inl" +#endif +#include +#include "math/MathUtil.inl" + +NS_CC_MATH_BEGIN + +void MathUtil::smooth(float *x, float target, float elapsedTime, float responseTime) { + CC_ASSERT(x); + + if (elapsedTime > 0) { + *x += (target - *x) * elapsedTime / (elapsedTime + responseTime); + } +} + +void MathUtil::smooth(float *x, float target, float elapsedTime, float riseTime, float fallTime) { + CC_ASSERT(x); + + if (elapsedTime > 0) { + float delta = target - *x; + *x += delta * elapsedTime / (elapsedTime + (delta > 0 ? riseTime : fallTime)); + } +} + +float MathUtil::lerp(float from, float to, float alpha) { + return from * (1.0F - alpha) + to * alpha; +} + +bool MathUtil::isNeon32Enabled() { +#ifdef USE_NEON32 + return true; +#elif (defined(INCLUDE_NEON32) && (CC_PLATFORM == CC_PLATFORM_ANDROID)) + class AndroidNeonChecker { + public: + AndroidNeonChecker() { + _isNeonEnabled = (android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM && (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0); + } + bool isNeonEnabled() const { return _isNeonEnabled; } + + private: + bool _isNeonEnabled; + }; + static AndroidNeonChecker checker; + return checker.isNeonEnabled(); +#else + return false; +#endif +} + +bool MathUtil::isNeon64Enabled() { +#ifdef USE_NEON64 + return true; +#else + return false; +#endif +} + +void MathUtil::addMatrix(const float *m, float scalar, float *dst) { +#ifdef USE_NEON32 + MathUtilNeon::addMatrix(m, scalar, dst); +#elif defined(USE_NEON64) + MathUtilNeon64::addMatrix(m, scalar, dst); +#elif defined(INCLUDE_NEON32) + if (isNeon32Enabled()) { + MathUtilNeon::addMatrix(m, scalar, dst); + } else { + MathUtilC::addMatrix(m, scalar, dst); + } +#else + MathUtilC::addMatrix(m, scalar, dst); +#endif +} + +void MathUtil::addMatrix(const float *m1, const float *m2, float *dst) { +#ifdef USE_NEON32 + MathUtilNeon::addMatrix(m1, m2, dst); +#elif defined(USE_NEON64) + MathUtilNeon64::addMatrix(m1, m2, dst); +#elif defined(INCLUDE_NEON32) + if (isNeon32Enabled()) { + MathUtilNeon::addMatrix(m1, m2, dst); + } else { + MathUtilC::addMatrix(m1, m2, dst); + } +#else + MathUtilC::addMatrix(m1, m2, dst); +#endif +} + +void MathUtil::subtractMatrix(const float *m1, const float *m2, float *dst) { +#ifdef USE_NEON32 + MathUtilNeon::subtractMatrix(m1, m2, dst); +#elif defined(USE_NEON64) + MathUtilNeon64::subtractMatrix(m1, m2, dst); +#elif defined(INCLUDE_NEON32) + if (isNeon32Enabled()) { + MathUtilNeon::subtractMatrix(m1, m2, dst); + } else { + MathUtilC::subtractMatrix(m1, m2, dst); + } +#else + MathUtilC::subtractMatrix(m1, m2, dst); +#endif +} + +void MathUtil::multiplyMatrix(const float *m, float scalar, float *dst) { +#ifdef USE_NEON32 + MathUtilNeon::multiplyMatrix(m, scalar, dst); +#elif defined(USE_NEON64) + MathUtilNeon64::multiplyMatrix(m, scalar, dst); +#elif defined(INCLUDE_NEON32) + if (isNeon32Enabled()) { + MathUtilNeon::multiplyMatrix(m, scalar, dst); + } else { + MathUtilC::multiplyMatrix(m, scalar, dst); + } +#else + MathUtilC::multiplyMatrix(m, scalar, dst); +#endif +} + +void MathUtil::multiplyMatrix(const float *m1, const float *m2, float *dst) { +#ifdef USE_NEON32 + MathUtilNeon::multiplyMatrix(m1, m2, dst); +#elif defined(USE_NEON64) + MathUtilNeon64::multiplyMatrix(m1, m2, dst); +#elif defined(INCLUDE_NEON32) + if (isNeon32Enabled()) { + MathUtilNeon::multiplyMatrix(m1, m2, dst); + } else { + MathUtilC::multiplyMatrix(m1, m2, dst); + } +#else + MathUtilC::multiplyMatrix(m1, m2, dst); +#endif +} + +void MathUtil::negateMatrix(const float *m, float *dst) { +#ifdef USE_NEON32 + MathUtilNeon::negateMatrix(m, dst); +#elif defined(USE_NEON64) + MathUtilNeon64::negateMatrix(m, dst); +#elif defined(INCLUDE_NEON32) + if (isNeon32Enabled()) { + MathUtilNeon::negateMatrix(m, dst); + } else { + MathUtilC::negateMatrix(m, dst); + } +#else + MathUtilC::negateMatrix(m, dst); +#endif +} + +void MathUtil::transposeMatrix(const float *m, float *dst) { +#ifdef USE_NEON32 + MathUtilNeon::transposeMatrix(m, dst); +#elif defined(USE_NEON64) + MathUtilNeon64::transposeMatrix(m, dst); +#elif defined(INCLUDE_NEON32) + if (isNeon32Enabled()) { + MathUtilNeon::transposeMatrix(m, dst); + } else { + MathUtilC::transposeMatrix(m, dst); + } +#else + MathUtilC::transposeMatrix(m, dst); +#endif +} + +void MathUtil::transformVec4(const float *m, float x, float y, float z, float w, float *dst) { +#ifdef USE_NEON32 + MathUtilNeon::transformVec4(m, x, y, z, w, dst); +#elif defined(USE_NEON64) + MathUtilNeon64::transformVec4(m, x, y, z, w, dst); +#elif defined(INCLUDE_NEON32) + if (isNeon32Enabled()) { + MathUtilNeon::transformVec4(m, x, y, z, w, dst); + } else { + MathUtilC::transformVec4(m, x, y, z, w, dst); + } +#else + MathUtilC::transformVec4(m, x, y, z, w, dst); +#endif +} + +void MathUtil::transformVec4(const float *m, const float *v, float *dst) { +#ifdef USE_NEON32 + MathUtilNeon::transformVec4(m, v, dst); +#elif defined(USE_NEON64) + MathUtilNeon64::transformVec4(m, v, dst); +#elif defined(INCLUDE_NEON32) + if (isNeon32Enabled()) { + MathUtilNeon::transformVec4(m, v, dst); + } else { + MathUtilC::transformVec4(m, v, dst); + } +#else + MathUtilC::transformVec4(m, v, dst); +#endif +} + +void MathUtil::crossVec3(const float *v1, const float *v2, float *dst) { +#ifdef USE_NEON32 + MathUtilNeon::crossVec3(v1, v2, dst); +#elif defined(USE_NEON64) + MathUtilNeon64::crossVec3(v1, v2, dst); +#elif defined(INCLUDE_NEON32) + if (isNeon32Enabled()) { + MathUtilNeon::crossVec3(v1, v2, dst); + } else { + MathUtilC::crossVec3(v1, v2, dst); + } +#else + MathUtilC::crossVec3(v1, v2, dst); +#endif +} + +void MathUtil::combineHash(size_t &seed, const size_t &v) { + seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +NS_CC_MATH_END diff --git a/cocos/math/MathUtil.h b/cocos/math/MathUtil.h new file mode 100644 index 0000000..0bdb3a3 --- /dev/null +++ b/cocos/math/MathUtil.h @@ -0,0 +1,149 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#ifndef MATHUTIL_H_ +#define MATHUTIL_H_ + +#ifdef __SSE__ + #include +#endif + +#include "math/MathBase.h" + +/** + * @addtogroup base + * @{ + */ + +NS_CC_MATH_BEGIN + +/** + * Defines a math utility class. + * + * This is primarily used for optimized internal math operations. + */ +class CC_DLL MathUtil { + friend class Mat4; + friend class Vec3; + +public: + /** + * Updates the given scalar towards the given target using a smoothing function. + * The given response time determines the amount of smoothing (lag). A longer + * response time yields a smoother result and more lag. To force the scalar to + * follow the target closely, provide a response time that is very small relative + * to the given elapsed time. + * + * @param x the scalar to update. + * @param target target value. + * @param elapsedTime elapsed time between calls. + * @param responseTime response time (in the same units as elapsedTime). + */ + static void smooth(float *x, float target, float elapsedTime, float responseTime); + + /** + * Updates the given scalar towards the given target using a smoothing function. + * The given rise and fall times determine the amount of smoothing (lag). Longer + * rise and fall times yield a smoother result and more lag. To force the scalar to + * follow the target closely, provide rise and fall times that are very small relative + * to the given elapsed time. + * + * @param x the scalar to update. + * @param target target value. + * @param elapsedTime elapsed time between calls. + * @param riseTime response time for rising slope (in the same units as elapsedTime). + * @param fallTime response time for falling slope (in the same units as elapsedTime). + */ + static void smooth(float *x, float target, float elapsedTime, float riseTime, float fallTime); + + /** + * Linearly interpolates between from value to to value by alpha which is in + * the range [0,1] + * + * @param from the from value. + * @param to the to value. + * @param alpha the alpha value between [0,1] + * + * @return interpolated float value + */ + static float lerp(float from, float to, float alpha); + + /** + * Add hash_combine math according to: + * https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine + * @param seed + * @param v + */ + static void combineHash(size_t &seed, const size_t &v); + +private: + //Indicates that if neon is enabled + static bool isNeon32Enabled(); + static bool isNeon64Enabled(); + +private: +#ifdef __SSE__ + static void addMatrix(const __m128 m[4], float scalar, __m128 dst[4]); + + static void addMatrix(const __m128 m1[4], const __m128 m2[4], __m128 dst[4]); + + static void subtractMatrix(const __m128 m1[4], const __m128 m2[4], __m128 dst[4]); + + static void multiplyMatrix(const __m128 m[4], float scalar, __m128 dst[4]); + + static void multiplyMatrix(const __m128 m1[4], const __m128 m2[4], __m128 dst[4]); + + static void negateMatrix(const __m128 m[4], __m128 dst[4]); + + static void transposeMatrix(const __m128 m[4], __m128 dst[4]); + + static void transformVec4(const __m128 m[4], const __m128 &v, __m128 &dst); +#endif + static void addMatrix(const float *m, float scalar, float *dst); + + static void addMatrix(const float *m1, const float *m2, float *dst); + + static void subtractMatrix(const float *m1, const float *m2, float *dst); + + static void multiplyMatrix(const float *m, float scalar, float *dst); + + static void multiplyMatrix(const float *m1, const float *m2, float *dst); + + static void negateMatrix(const float *m, float *dst); + + static void transposeMatrix(const float *m, float *dst); + + static void transformVec4(const float *m, float x, float y, float z, float w, float *dst); + + static void transformVec4(const float *m, const float *v, float *dst); + + static void crossVec3(const float *v1, const float *v2, float *dst); +}; + +NS_CC_MATH_END +/** + end of base group + @} + */ +#define MATRIX_SIZE (sizeof(float) * 16) + +#endif diff --git a/cocos/math/MathUtil.inl b/cocos/math/MathUtil.inl new file mode 100644 index 0000000..1d32e85 --- /dev/null +++ b/cocos/math/MathUtil.inl @@ -0,0 +1,220 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +NS_CC_MATH_BEGIN + +class MathUtilC +{ +public: + inline static void addMatrix(const float* m, float scalar, float* dst); + + inline static void addMatrix(const float* m1, const float* m2, float* dst); + + inline static void subtractMatrix(const float* m1, const float* m2, float* dst); + + inline static void multiplyMatrix(const float* m, float scalar, float* dst); + + inline static void multiplyMatrix(const float* m1, const float* m2, float* dst); + + inline static void negateMatrix(const float* m, float* dst); + + inline static void transposeMatrix(const float* m, float* dst); + + inline static void transformVec4(const float* m, float x, float y, float z, float w, float* dst); + + inline static void transformVec4(const float* m, const float* v, float* dst); + + inline static void crossVec3(const float* v1, const float* v2, float* dst); +}; + +inline void MathUtilC::addMatrix(const float* m, float scalar, float* dst) +{ + dst[0] = m[0] + scalar; + dst[1] = m[1] + scalar; + dst[2] = m[2] + scalar; + dst[3] = m[3] + scalar; + dst[4] = m[4] + scalar; + dst[5] = m[5] + scalar; + dst[6] = m[6] + scalar; + dst[7] = m[7] + scalar; + dst[8] = m[8] + scalar; + dst[9] = m[9] + scalar; + dst[10] = m[10] + scalar; + dst[11] = m[11] + scalar; + dst[12] = m[12] + scalar; + dst[13] = m[13] + scalar; + dst[14] = m[14] + scalar; + dst[15] = m[15] + scalar; +} + +inline void MathUtilC::addMatrix(const float* m1, const float* m2, float* dst) +{ + dst[0] = m1[0] + m2[0]; + dst[1] = m1[1] + m2[1]; + dst[2] = m1[2] + m2[2]; + dst[3] = m1[3] + m2[3]; + dst[4] = m1[4] + m2[4]; + dst[5] = m1[5] + m2[5]; + dst[6] = m1[6] + m2[6]; + dst[7] = m1[7] + m2[7]; + dst[8] = m1[8] + m2[8]; + dst[9] = m1[9] + m2[9]; + dst[10] = m1[10] + m2[10]; + dst[11] = m1[11] + m2[11]; + dst[12] = m1[12] + m2[12]; + dst[13] = m1[13] + m2[13]; + dst[14] = m1[14] + m2[14]; + dst[15] = m1[15] + m2[15]; +} + +inline void MathUtilC::subtractMatrix(const float* m1, const float* m2, float* dst) +{ + dst[0] = m1[0] - m2[0]; + dst[1] = m1[1] - m2[1]; + dst[2] = m1[2] - m2[2]; + dst[3] = m1[3] - m2[3]; + dst[4] = m1[4] - m2[4]; + dst[5] = m1[5] - m2[5]; + dst[6] = m1[6] - m2[6]; + dst[7] = m1[7] - m2[7]; + dst[8] = m1[8] - m2[8]; + dst[9] = m1[9] - m2[9]; + dst[10] = m1[10] - m2[10]; + dst[11] = m1[11] - m2[11]; + dst[12] = m1[12] - m2[12]; + dst[13] = m1[13] - m2[13]; + dst[14] = m1[14] - m2[14]; + dst[15] = m1[15] - m2[15]; +} + +inline void MathUtilC::multiplyMatrix(const float* m, float scalar, float* dst) +{ + dst[0] = m[0] * scalar; + dst[1] = m[1] * scalar; + dst[2] = m[2] * scalar; + dst[3] = m[3] * scalar; + dst[4] = m[4] * scalar; + dst[5] = m[5] * scalar; + dst[6] = m[6] * scalar; + dst[7] = m[7] * scalar; + dst[8] = m[8] * scalar; + dst[9] = m[9] * scalar; + dst[10] = m[10] * scalar; + dst[11] = m[11] * scalar; + dst[12] = m[12] * scalar; + dst[13] = m[13] * scalar; + dst[14] = m[14] * scalar; + dst[15] = m[15] * scalar; +} + +inline void MathUtilC::multiplyMatrix(const float* m1, const float* m2, float* dst) +{ + // Support the case where m1 or m2 is the same array as dst. + float product[16]; + + product[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3]; + product[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3]; + product[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3]; + product[3] = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3]; + + product[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7]; + product[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7]; + product[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7]; + product[7] = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7]; + + product[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11]; + product[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11]; + product[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11]; + product[11] = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11]; + + product[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15]; + product[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15]; + product[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15]; + product[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15]; + + memcpy(dst, product, MATRIX_SIZE); +} + +inline void MathUtilC::negateMatrix(const float* m, float* dst) +{ + dst[0] = -m[0]; + dst[1] = -m[1]; + dst[2] = -m[2]; + dst[3] = -m[3]; + dst[4] = -m[4]; + dst[5] = -m[5]; + dst[6] = -m[6]; + dst[7] = -m[7]; + dst[8] = -m[8]; + dst[9] = -m[9]; + dst[10] = -m[10]; + dst[11] = -m[11]; + dst[12] = -m[12]; + dst[13] = -m[13]; + dst[14] = -m[14]; + dst[15] = -m[15]; +} + +inline void MathUtilC::transposeMatrix(const float* m, float* dst) +{ + float t[16] = { + m[0], m[4], m[8], m[12], + m[1], m[5], m[9], m[13], + m[2], m[6], m[10], m[14], + m[3], m[7], m[11], m[15] + }; + memcpy(dst, t, MATRIX_SIZE);} + +inline void MathUtilC::transformVec4(const float* m, float x, float y, float z, float w, float* dst) +{ + dst[0] = x * m[0] + y * m[4] + z * m[8] + w * m[12]; + dst[1] = x * m[1] + y * m[5] + z * m[9] + w * m[13]; + dst[2] = x * m[2] + y * m[6] + z * m[10] + w * m[14]; + dst[3] = x * m[3] + y * m[7] + z * m[11] + w * m[15]; +} + +inline void MathUtilC::transformVec4(const float* m, const float* v, float* dst) +{ + // Handle case where v == dst. + float x = v[0] * m[0] + v[1] * m[4] + v[2] * m[8] + v[3] * m[12]; + float y = v[0] * m[1] + v[1] * m[5] + v[2] * m[9] + v[3] * m[13]; + float z = v[0] * m[2] + v[1] * m[6] + v[2] * m[10] + v[3] * m[14]; + float w = v[0] * m[3] + v[1] * m[7] + v[2] * m[11] + v[3] * m[15]; + + dst[0] = x; + dst[1] = y; + dst[2] = z; + dst[3] = w; +} + +inline void MathUtilC::crossVec3(const float* v1, const float* v2, float* dst) +{ + float x = (v1[1] * v2[2]) - (v1[2] * v2[1]); + float y = (v1[2] * v2[0]) - (v1[0] * v2[2]); + float z = (v1[0] * v2[1]) - (v1[1] * v2[0]); + + dst[0] = x; + dst[1] = y; + dst[2] = z; +} + +NS_CC_MATH_END diff --git a/cocos/math/MathUtilNeon.inl b/cocos/math/MathUtilNeon.inl new file mode 100644 index 0000000..2b7b757 --- /dev/null +++ b/cocos/math/MathUtilNeon.inl @@ -0,0 +1,273 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ +NS_CC_MATH_BEGIN + +class MathUtilNeon +{ +public: + inline static void addMatrix(const float* m, float scalar, float* dst); + + inline static void addMatrix(const float* m1, const float* m2, float* dst); + + inline static void subtractMatrix(const float* m1, const float* m2, float* dst); + + inline static void multiplyMatrix(const float* m, float scalar, float* dst); + + inline static void multiplyMatrix(const float* m1, const float* m2, float* dst); + + inline static void negateMatrix(const float* m, float* dst); + + inline static void transposeMatrix(const float* m, float* dst); + + inline static void transformVec4(const float* m, float x, float y, float z, float w, float* dst); + + inline static void transformVec4(const float* m, const float* v, float* dst); + + inline static void crossVec3(const float* v1, const float* v2, float* dst); +}; + +inline void MathUtilNeon::addMatrix(const float* m, float scalar, float* dst) +{ + asm volatile( + "vld1.32 {q0, q1}, [%1]! \n\t" // M[m0-m7] + "vld1.32 {q2, q3}, [%1] \n\t" // M[m8-m15] + "vld1.32 {d8[0]}, [%2] \n\t" // s + "vmov.f32 s17, s16 \n\t" // s + "vmov.f32 s18, s16 \n\t" // s + "vmov.f32 s19, s16 \n\t" // s + + "vadd.f32 q8, q0, q4 \n\t" // DST->M[m0-m3] = M[m0-m3] + s + "vadd.f32 q9, q1, q4 \n\t" // DST->M[m4-m7] = M[m4-m7] + s + "vadd.f32 q10, q2, q4 \n\t" // DST->M[m8-m11] = M[m8-m11] + s + "vadd.f32 q11, q3, q4 \n\t" // DST->M[m12-m15] = M[m12-m15] + s + + "vst1.32 {q8, q9}, [%0]! \n\t" // DST->M[m0-m7] + "vst1.32 {q10, q11}, [%0] \n\t" // DST->M[m8-m15] + : + : "r"(dst), "r"(m), "r"(&scalar) + : "q0", "q1", "q2", "q3", "q4", "q8", "q9", "q10", "q11", "memory" + ); +} + +inline void MathUtilNeon::addMatrix(const float* m1, const float* m2, float* dst) +{ + asm volatile( + "vld1.32 {q0, q1}, [%1]! \n\t" // M1[m0-m7] + "vld1.32 {q2, q3}, [%1] \n\t" // M1[m8-m15] + "vld1.32 {q8, q9}, [%2]! \n\t" // M2[m0-m7] + "vld1.32 {q10, q11}, [%2] \n\t" // M2[m8-m15] + + "vadd.f32 q12, q0, q8 \n\t" // DST->M[m0-m3] = M1[m0-m3] + M2[m0-m3] + "vadd.f32 q13, q1, q9 \n\t" // DST->M[m4-m7] = M1[m4-m7] + M2[m4-m7] + "vadd.f32 q14, q2, q10 \n\t" // DST->M[m8-m11] = M1[m8-m11] + M2[m8-m11] + "vadd.f32 q15, q3, q11 \n\t" // DST->M[m12-m15] = M1[m12-m15] + M2[m12-m15] + + "vst1.32 {q12, q13}, [%0]! \n\t" // DST->M[m0-m7] + "vst1.32 {q14, q15}, [%0] \n\t" // DST->M[m8-m15] + : + : "r"(dst), "r"(m1), "r"(m2) + : "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15", "memory" + ); +} + +inline void MathUtilNeon::subtractMatrix(const float* m1, const float* m2, float* dst) +{ + asm volatile( + "vld1.32 {q0, q1}, [%1]! \n\t" // M1[m0-m7] + "vld1.32 {q2, q3}, [%1] \n\t" // M1[m8-m15] + "vld1.32 {q8, q9}, [%2]! \n\t" // M2[m0-m7] + "vld1.32 {q10, q11}, [%2] \n\t" // M2[m8-m15] + + "vsub.f32 q12, q0, q8 \n\t" // DST->M[m0-m3] = M1[m0-m3] - M2[m0-m3] + "vsub.f32 q13, q1, q9 \n\t" // DST->M[m4-m7] = M1[m4-m7] - M2[m4-m7] + "vsub.f32 q14, q2, q10 \n\t" // DST->M[m8-m11] = M1[m8-m11] - M2[m8-m11] + "vsub.f32 q15, q3, q11 \n\t" // DST->M[m12-m15] = M1[m12-m15] - M2[m12-m15] + + "vst1.32 {q12, q13}, [%0]! \n\t" // DST->M[m0-m7] + "vst1.32 {q14, q15}, [%0] \n\t" // DST->M[m8-m15] + : + : "r"(dst), "r"(m1), "r"(m2) + : "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15", "memory" + ); +} + +inline void MathUtilNeon::multiplyMatrix(const float* m, float scalar, float* dst) +{ + asm volatile( + "vld1.32 {d0[0]}, [%2] \n\t" // M[m0-m7] + "vld1.32 {q4-q5}, [%1]! \n\t" // M[m8-m15] + "vld1.32 {q6-q7}, [%1] \n\t" // s + + "vmul.f32 q8, q4, d0[0] \n\t" // DST->M[m0-m3] = M[m0-m3] * s + "vmul.f32 q9, q5, d0[0] \n\t" // DST->M[m4-m7] = M[m4-m7] * s + "vmul.f32 q10, q6, d0[0] \n\t" // DST->M[m8-m11] = M[m8-m11] * s + "vmul.f32 q11, q7, d0[0] \n\t" // DST->M[m12-m15] = M[m12-m15] * s + + "vst1.32 {q8-q9}, [%0]! \n\t" // DST->M[m0-m7] + "vst1.32 {q10-q11}, [%0] \n\t" // DST->M[m8-m15] + : + : "r"(dst), "r"(m), "r"(&scalar) + : "q0", "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11", "memory" + ); +} + +inline void MathUtilNeon::multiplyMatrix(const float* m1, const float* m2, float* dst) +{ + asm volatile( + "vld1.32 {d16 - d19}, [%1]! \n\t" // M1[m0-m7] + "vld1.32 {d20 - d23}, [%1] \n\t" // M1[m8-m15] + "vld1.32 {d0 - d3}, [%2]! \n\t" // M2[m0-m7] + "vld1.32 {d4 - d7}, [%2] \n\t" // M2[m8-m15] + + "vmul.f32 q12, q8, d0[0] \n\t" // DST->M[m0-m3] = M1[m0-m3] * M2[m0] + "vmul.f32 q13, q8, d2[0] \n\t" // DST->M[m4-m7] = M1[m4-m7] * M2[m4] + "vmul.f32 q14, q8, d4[0] \n\t" // DST->M[m8-m11] = M1[m8-m11] * M2[m8] + "vmul.f32 q15, q8, d6[0] \n\t" // DST->M[m12-m15] = M1[m12-m15] * M2[m12] + + "vmla.f32 q12, q9, d0[1] \n\t" // DST->M[m0-m3] += M1[m0-m3] * M2[m1] + "vmla.f32 q13, q9, d2[1] \n\t" // DST->M[m4-m7] += M1[m4-m7] * M2[m5] + "vmla.f32 q14, q9, d4[1] \n\t" // DST->M[m8-m11] += M1[m8-m11] * M2[m9] + "vmla.f32 q15, q9, d6[1] \n\t" // DST->M[m12-m15] += M1[m12-m15] * M2[m13] + + "vmla.f32 q12, q10, d1[0] \n\t" // DST->M[m0-m3] += M1[m0-m3] * M2[m2] + "vmla.f32 q13, q10, d3[0] \n\t" // DST->M[m4-m7] += M1[m4-m7] * M2[m6] + "vmla.f32 q14, q10, d5[0] \n\t" // DST->M[m8-m11] += M1[m8-m11] * M2[m10] + "vmla.f32 q15, q10, d7[0] \n\t" // DST->M[m12-m15] += M1[m12-m15] * M2[m14] + + "vmla.f32 q12, q11, d1[1] \n\t" // DST->M[m0-m3] += M1[m0-m3] * M2[m3] + "vmla.f32 q13, q11, d3[1] \n\t" // DST->M[m4-m7] += M1[m4-m7] * M2[m7] + "vmla.f32 q14, q11, d5[1] \n\t" // DST->M[m8-m11] += M1[m8-m11] * M2[m11] + "vmla.f32 q15, q11, d7[1] \n\t" // DST->M[m12-m15] += M1[m12-m15] * M2[m15] + + "vst1.32 {d24 - d27}, [%0]! \n\t" // DST->M[m0-m7] + "vst1.32 {d28 - d31}, [%0] \n\t" // DST->M[m8-m15] + + : // output + : "r"(dst), "r"(m1), "r"(m2) // input - note *value* of pointer doesn't change. + : "memory", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" + ); +} + +inline void MathUtilNeon::negateMatrix(const float* m, float* dst) +{ + asm volatile( + "vld1.32 {q0-q1}, [%1]! \n\t" // load m0-m7 + "vld1.32 {q2-q3}, [%1] \n\t" // load m8-m15 + + "vneg.f32 q4, q0 \n\t" // negate m0-m3 + "vneg.f32 q5, q1 \n\t" // negate m4-m7 + "vneg.f32 q6, q2 \n\t" // negate m8-m15 + "vneg.f32 q7, q3 \n\t" // negate m8-m15 + + "vst1.32 {q4-q5}, [%0]! \n\t" // store m0-m7 + "vst1.32 {q6-q7}, [%0] \n\t" // store m8-m15 + : + : "r"(dst), "r"(m) + : "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "memory" + ); +} + +inline void MathUtilNeon::transposeMatrix(const float* m, float* dst) +{ + asm volatile( + "vld4.32 {d0[0], d2[0], d4[0], d6[0]}, [%1]! \n\t" // DST->M[m0, m4, m8, m12] = M[m0-m3] + "vld4.32 {d0[1], d2[1], d4[1], d6[1]}, [%1]! \n\t" // DST->M[m1, m5, m9, m12] = M[m4-m7] + "vld4.32 {d1[0], d3[0], d5[0], d7[0]}, [%1]! \n\t" // DST->M[m2, m6, m10, m12] = M[m8-m11] + "vld4.32 {d1[1], d3[1], d5[1], d7[1]}, [%1] \n\t" // DST->M[m3, m7, m11, m12] = M[m12-m15] + + "vst1.32 {q0-q1}, [%0]! \n\t" // DST->M[m0-m7] + "vst1.32 {q2-q3}, [%0] \n\t" // DST->M[m8-m15] + : + : "r"(dst), "r"(m) + : "q0", "q1", "q2", "q3", "memory" + ); +} + +inline void MathUtilNeon::transformVec4(const float* m, float x, float y, float z, float w, float* dst) +{ + asm volatile( + "vld1.32 {d0[0]}, [%1] \n\t" // V[x] + "vld1.32 {d0[1]}, [%2] \n\t" // V[y] + "vld1.32 {d1[0]}, [%3] \n\t" // V[z] + "vld1.32 {d1[1]}, [%4] \n\t" // V[w] + "vld1.32 {d18 - d21}, [%5]! \n\t" // M[m0-m7] + "vld1.32 {d22 - d25}, [%5] \n\t" // M[m8-m15] + + "vmul.f32 q13, q9, d0[0] \n\t" // DST->V = M[m0-m3] * V[x] + "vmla.f32 q13, q10, d0[1] \n\t" // DST->V += M[m4-m7] * V[y] + "vmla.f32 q13, q11, d1[0] \n\t" // DST->V += M[m8-m11] * V[z] + "vmla.f32 q13, q12, d1[1] \n\t" // DST->V += M[m12-m15] * V[w] + + "vst1.32 {d26}, [%0]! \n\t" // DST->V[x, y] + "vst1.32 {d27[0]}, [%0] \n\t" // DST->V[z] + : + : "r"(dst), "r"(&x), "r"(&y), "r"(&z), "r"(&w), "r"(m) + : "q0", "q9", "q10","q11", "q12", "q13", "memory" + ); +} + +inline void MathUtilNeon::transformVec4(const float* m, const float* v, float* dst) +{ + asm volatile + ( + "vld1.32 {d0, d1}, [%1] \n\t" // V[x, y, z, w] + "vld1.32 {d18 - d21}, [%2]! \n\t" // M[m0-m7] + "vld1.32 {d22 - d25}, [%2] \n\t" // M[m8-m15] + + "vmul.f32 q13, q9, d0[0] \n\t" // DST->V = M[m0-m3] * V[x] + "vmla.f32 q13, q10, d0[1] \n\t" // DST->V = M[m4-m7] * V[y] + "vmla.f32 q13, q11, d1[0] \n\t" // DST->V = M[m8-m11] * V[z] + "vmla.f32 q13, q12, d1[1] \n\t" // DST->V = M[m12-m15] * V[w] + + "vst1.32 {d26, d27}, [%0] \n\t" // DST->V + : + : "r"(dst), "r"(v), "r"(m) + : "q0", "q9", "q10","q11", "q12", "q13", "memory" + ); +} + +inline void MathUtilNeon::crossVec3(const float* v1, const float* v2, float* dst) +{ + asm volatile( + "vld1.32 {d1[1]}, [%1] \n\t" // + "vld1.32 {d0}, [%2] \n\t" // + "vmov.f32 s2, s1 \n\t" // q0 = (v1y, v1z, v1z, v1x) + + "vld1.32 {d2[1]}, [%3] \n\t" // + "vld1.32 {d3}, [%4] \n\t" // + "vmov.f32 s4, s7 \n\t" // q1 = (v2z, v2x, v2y, v2z) + + "vmul.f32 d4, d0, d2 \n\t" // x = v1y * v2z, y = v1z * v2x + "vmls.f32 d4, d1, d3 \n\t" // x -= v1z * v2y, y-= v1x - v2z + + "vmul.f32 d5, d3, d1[1] \n\t" // z = v1x * v2y + "vmls.f32 d5, d0, d2[1] \n\t" // z-= v1y * vx + + "vst1.32 {d4}, [%0]! \n\t" // V[x, y] + "vst1.32 {d5[0]}, [%0] \n\t" // V[z] + : + : "r"(dst), "r"(v1), "r"((v1+1)), "r"(v2), "r"((v2+1)) + : "q0", "q1", "q2", "memory" + ); +} + +NS_CC_MATH_END diff --git a/cocos/math/MathUtilNeon64.inl b/cocos/math/MathUtilNeon64.inl new file mode 100644 index 0000000..e58929c --- /dev/null +++ b/cocos/math/MathUtilNeon64.inl @@ -0,0 +1,265 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +NS_CC_MATH_BEGIN + +class MathUtilNeon64 +{ +public: + inline static void addMatrix(const float* m, float scalar, float* dst); + + inline static void addMatrix(const float* m1, const float* m2, float* dst); + + inline static void subtractMatrix(const float* m1, const float* m2, float* dst); + + inline static void multiplyMatrix(const float* m, float scalar, float* dst); + + inline static void multiplyMatrix(const float* m1, const float* m2, float* dst); + + inline static void negateMatrix(const float* m, float* dst); + + inline static void transposeMatrix(const float* m, float* dst); + + inline static void transformVec4(const float* m, float x, float y, float z, float w, float* dst); + + inline static void transformVec4(const float* m, const float* v, float* dst); + + inline static void crossVec3(const float* v1, const float* v2, float* dst); +}; + +inline void MathUtilNeon64::addMatrix(const float* m, float scalar, float* dst) +{ + asm volatile( + "ld4 {v0.4s, v1.4s, v2.4s, v3.4s}, [%1] \n\t" // M[m0-m7] M[m8-m15] + "ld1r {v4.4s}, [%2] \n\t" //ssss + + "fadd v8.4s, v0.4s, v4.4s \n\t" // DST->M[m0-m3] = M[m0-m3] + s + "fadd v9.4s, v1.4s, v4.4s \n\t" // DST->M[m4-m7] = M[m4-m7] + s + "fadd v10.4s, v2.4s, v4.4s \n\t" // DST->M[m8-m11] = M[m8-m11] + s + "fadd v11.4s, v3.4s, v4.4s \n\t" // DST->M[m12-m15] = M[m12-m15] + s + + "st4 {v8.4s, v9.4s, v10.4s, v11.4s}, [%0] \n\t" // Result in V9 + : + : "r"(dst), "r"(m), "r"(&scalar) + : "v0", "v1", "v2", "v3", "v4", "v8", "v9", "v10", "v11", "memory" + ); +} + +inline void MathUtilNeon64::addMatrix(const float* m1, const float* m2, float* dst) +{ + asm volatile( + "ld4 {v0.4s, v1.4s, v2.4s, v3.4s}, [%1] \n\t" // M1[m0-m7] M1[m8-m15] + "ld4 {v8.4s, v9.4s, v10.4s, v11.4s}, [%2] \n\t" // M2[m0-m7] M2[m8-m15] + + "fadd v12.4s, v0.4s, v8.4s \n\t" // DST->M[m0-m3] = M1[m0-m3] + M2[m0-m3] + "fadd v13.4s, v1.4s, v9.4s \n\t" // DST->M[m4-m7] = M1[m4-m7] + M2[m4-m7] + "fadd v14.4s, v2.4s, v10.4s \n\t" // DST->M[m8-m11] = M1[m8-m11] + M2[m8-m11] + "fadd v15.4s, v3.4s, v11.4s \n\t" // DST->M[m12-m15] = M1[m12-m15] + M2[m12-m15] + + "st4 {v12.4s, v13.4s, v14.4s, v15.4s}, [%0] \n\t" // DST->M[m0-m7] DST->M[m8-m15] + : + : "r"(dst), "r"(m1), "r"(m2) + : "v0", "v1", "v2", "v3", "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", "memory" + ); +} + +inline void MathUtilNeon64::subtractMatrix(const float* m1, const float* m2, float* dst) +{ + asm volatile( + "ld4 {v0.4s, v1.4s, v2.4s, v3.4s}, [%1] \n\t" // M1[m0-m7] M1[m8-m15] + "ld4 {v8.4s, v9.4s, v10.4s, v11.4s}, [%2] \n\t" // M2[m0-m7] M2[m8-m15] + + "fsub v12.4s, v0.4s, v8.4s \n\t" // DST->M[m0-m3] = M1[m0-m3] - M2[m0-m3] + "fsub v13.4s, v1.4s, v9.4s \n\t" // DST->M[m4-m7] = M1[m4-m7] - M2[m4-m7] + "fsub v14.4s, v2.4s, v10.4s \n\t" // DST->M[m8-m11] = M1[m8-m11] - M2[m8-m11] + "fsub v15.4s, v3.4s, v11.4s \n\t" // DST->M[m12-m15] = M1[m12-m15] - M2[m12-m15] + + "st4 {v12.4s, v13.4s, v14.4s, v15.4s}, [%0] \n\t" // DST->M[m0-m7] DST->M[m8-m15] + : + : "r"(dst), "r"(m1), "r"(m2) + : "v0", "v1", "v2", "v3", "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", "memory" + ); +} + +inline void MathUtilNeon64::multiplyMatrix(const float* m, float scalar, float* dst) +{ + asm volatile( + "ld1 {v0.s}[0], [%2] \n\t" //s + "ld4 {v4.4s, v5.4s, v6.4s, v7.4s}, [%1] \n\t" //M[m0-m7] M[m8-m15] + + "fmul v8.4s, v4.4s, v0.s[0] \n\t" // DST->M[m0-m3] = M[m0-m3] * s + "fmul v9.4s, v5.4s, v0.s[0] \n\t" // DST->M[m4-m7] = M[m4-m7] * s + "fmul v10.4s, v6.4s, v0.s[0] \n\t" // DST->M[m8-m11] = M[m8-m11] * s + "fmul v11.4s, v7.4s, v0.s[0] \n\t" // DST->M[m12-m15] = M[m12-m15] * s + + "st4 {v8.4s, v9.4s, v10.4s, v11.4s}, [%0] \n\t" // DST->M[m0-m7] DST->M[m8-m15] + : + : "r"(dst), "r"(m), "r"(&scalar) + : "v0", "v4", "v5", "v6", "v7", "v8", "v9", "v10", "v11", "memory" + ); +} + +inline void MathUtilNeon64::multiplyMatrix(const float* m1, const float* m2, float* dst) +{ + asm volatile( + "ld1 {v8.4s, v9.4s, v10.4s, v11.4s}, [%1] \n\t" // M1[m0-m7] M1[m8-m15] M2[m0-m7] M2[m8-m15] + "ld4 {v0.4s, v1.4s, v2.4s, v3.4s}, [%2] \n\t" // M2[m0-m15] + + + "fmul v12.4s, v8.4s, v0.s[0] \n\t" // DST->M[m0-m3] = M1[m0-m3] * M2[m0] + "fmul v13.4s, v8.4s, v0.s[1] \n\t" // DST->M[m4-m7] = M1[m4-m7] * M2[m4] + "fmul v14.4s, v8.4s, v0.s[2] \n\t" // DST->M[m8-m11] = M1[m8-m11] * M2[m8] + "fmul v15.4s, v8.4s, v0.s[3] \n\t" // DST->M[m12-m15] = M1[m12-m15] * M2[m12] + + "fmla v12.4s, v9.4s, v1.s[0] \n\t" // DST->M[m0-m3] += M1[m0-m3] * M2[m1] + "fmla v13.4s, v9.4s, v1.s[1] \n\t" // DST->M[m4-m7] += M1[m4-m7] * M2[m5] + "fmla v14.4s, v9.4s, v1.s[2] \n\t" // DST->M[m8-m11] += M1[m8-m11] * M2[m9] + "fmla v15.4s, v9.4s, v1.s[3] \n\t" // DST->M[m12-m15] += M1[m12-m15] * M2[m13] + + "fmla v12.4s, v10.4s, v2.s[0] \n\t" // DST->M[m0-m3] += M1[m0-m3] * M2[m2] + "fmla v13.4s, v10.4s, v2.s[1] \n\t" // DST->M[m4-m7] += M1[m4-m7] * M2[m6] + "fmla v14.4s, v10.4s, v2.s[2] \n\t" // DST->M[m8-m11] += M1[m8-m11] * M2[m10] + "fmla v15.4s, v10.4s, v2.s[3] \n\t" // DST->M[m12-m15] += M1[m12-m15] * M2[m14] + + "fmla v12.4s, v11.4s, v3.s[0] \n\t" // DST->M[m0-m3] += M1[m0-m3] * M2[m3] + "fmla v13.4s, v11.4s, v3.s[1] \n\t" // DST->M[m4-m7] += M1[m4-m7] * M2[m7] + "fmla v14.4s, v11.4s, v3.s[2] \n\t" // DST->M[m8-m11] += M1[m8-m11] * M2[m11] + "fmla v15.4s, v11.4s, v3.s[3] \n\t" // DST->M[m12-m15] += M1[m12-m15] * M2[m15] + + "st1 {v12.4s, v13.4s, v14.4s, v15.4s}, [%0] \n\t" // DST->M[m0-m7]// DST->M[m8-m15] + + : // output + : "r"(dst), "r"(m1), "r"(m2) // input - note *value* of pointer doesn't change. + : "memory", "v0", "v1", "v2", "v3", "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15" + ); +} + +inline void MathUtilNeon64::negateMatrix(const float* m, float* dst) +{ + asm volatile( + "ld4 {v0.4s, v1.4s, v2.4s, v3.4s}, [%1] \n\t" // load m0-m7 load m8-m15 + + "fneg v4.4s, v0.4s \n\t" // negate m0-m3 + "fneg v5.4s, v1.4s \n\t" // negate m4-m7 + "fneg v6.4s, v2.4s \n\t" // negate m8-m15 + "fneg v7.4s, v3.4s \n\t" // negate m8-m15 + + "st4 {v4.4s, v5.4s, v6.4s, v7.4s}, [%0] \n\t" // store m0-m7 store m8-m15 + : + : "r"(dst), "r"(m) + : "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "memory" + ); +} + +inline void MathUtilNeon64::transposeMatrix(const float* m, float* dst) +{ + asm volatile( + "ld4 {v0.4s, v1.4s, v2.4s, v3.4s}, [%1] \n\t" // DST->M[m0, m4, m8, m12] = M[m0-m3] + //DST->M[m1, m5, m9, m12] = M[m4-m7] + "st1 {v0.4s, v1.4s, v2.4s, v3.4s}, [%0] \n\t" + : + : "r"(dst), "r"(m) + : "v0", "v1", "v2", "v3", "memory" + ); +} + +inline void MathUtilNeon64::transformVec4(const float* m, float x, float y, float z, float w, float* dst) +{ + asm volatile( + "ld1 {v0.s}[0], [%1] \n\t" // V[x] + "ld1 {v0.s}[1], [%2] \n\t" // V[y] + "ld1 {v0.s}[2], [%3] \n\t" // V[z] + "ld1 {v0.s}[3], [%4] \n\t" // V[w] + "ld1 {v9.4s, v10.4s, v11.4s, v12.4s}, [%5] \n\t" // M[m0-m7] M[m8-m15] + + + "fmul v13.4s, v9.4s, v0.s[0] \n\t" // DST->V = M[m0-m3] * V[x] + "fmla v13.4s, v10.4s, v0.s[1] \n\t" // DST->V += M[m4-m7] * V[y] + "fmla v13.4s, v11.4s, v0.s[2] \n\t" // DST->V += M[m8-m11] * V[z] + "fmla v13.4s, v12.4s, v0.s[3] \n\t" // DST->V += M[m12-m15] * V[w] + + "st1 {v13.4s}, [%0] \n\t" // DST->V + : + : "r"(dst), "r"(&x), "r"(&y), "r"(&z), "r"(&w), "r"(m) + : "v0", "v9", "v10","v11", "v12", "v13", "memory" + ); +} + +inline void MathUtilNeon64::transformVec4(const float* m, const float* v, float* dst) +{ + asm volatile + ( + "ld1 {v0.4s}, [%1] \n\t" // V[x, y, z, w] + "ld1 {v9.4s, v10.4s, v11.4s, v12.4s}, [%2] \n\t" // M[m0-m7] M[m8-m15] + + "fmul v13.4s, v9.4s, v0.s[0] \n\t" // DST->V = M[m0-m3] * V[x] + "fmla v13.4s, v10.4s, v0.s[1] \n\t" // DST->V = M[m4-m7] * V[y] + "fmla v13.4s, v11.4s, v0.s[2] \n\t" // DST->V = M[m8-m11] * V[z] + "fmla v13.4s, v12.4s, v0.s[3] \n\t" // DST->V = M[m12-m15] * V[w] + + "st1 {v13.4s}, [%0] \n\t" // DST->V + : + : "r"(dst), "r"(v), "r"(m) + : "v0", "v9", "v10","v11", "v12", "v13", "memory" + ); +} + +inline void MathUtilNeon64::crossVec3(const float* v1, const float* v2, float* dst) +{ + asm volatile( + "ld1 {v0.2s}, [%2] \n\t" + "ld1 {v0.s}[2], [%1] \n\t" + "mov v0.s[3], v0.s[0] \n\t" // q0 = (v1y, v1z, v1x, v1x) + + "ld1 {v1.4s}, [%3] \n\t" + "mov v1.s[3], v1.s[0] \n\t" // q1 = (v2x, v2y, v2z, v2x) + + "fmul v2.4s, v0.4s, v1.4s \n\t" // x = v1y * v2z, y = v1z * v2x + + + "mov v0.s[0], v0.s[1] \n\t" + "mov v0.s[1], v0.s[2] \n\t" + "mov v0.s[2], v0.s[3] \n\t" + + "mov v1.s[3], v1.s[2] \n\t" + + "fmul v0.4s, v0.4s, v1.4s \n\t" + + "mov v0.s[3], v0.s[1] \n\t" + "mov v0.s[1], v0.s[2] \n\t" + "mov v0.s[2], v0.s[0] \n\t" + + "fsub v2.4s, v0.4s, v2.4s \n\t" + + "mov v2.s[0], v2.s[1] \n\t" + "mov v2.s[1], v2.s[2] \n\t" + "mov v2.s[2], v2.s[3] \n\t" + + "st1 {v2.2s}, [%0], 8 \n\t" // V[x, y] + "st1 {v2.s}[2], [%0] \n\t" // V[z] + : + : "r"(dst), "r"(v1), "r"((v1+1)), "r"(v2), "r"((v2+1)) + : "v0", "v1", "v2", "memory" + ); +} + +NS_CC_MATH_END diff --git a/cocos/math/MathUtilSSE.inl b/cocos/math/MathUtilSSE.inl new file mode 100644 index 0000000..89af5ec --- /dev/null +++ b/cocos/math/MathUtilSSE.inl @@ -0,0 +1,179 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +NS_CC_MATH_BEGIN + +#ifdef __SSE__ + +void MathUtil::addMatrix(const __m128 m[4], float scalar, __m128 dst[4]) +{ + __m128 s = _mm_set1_ps(scalar); + dst[0] = _mm_add_ps(m[0], s); + dst[1] = _mm_add_ps(m[1], s); + dst[2] = _mm_add_ps(m[2], s); + dst[3] = _mm_add_ps(m[3], s); +} + +void MathUtil::addMatrix(const __m128 m1[4], const __m128 m2[4], __m128 dst[4]) +{ + dst[0] = _mm_add_ps(m1[0], m2[0]); + dst[1] = _mm_add_ps(m1[1], m2[1]); + dst[2] = _mm_add_ps(m1[2], m2[2]); + dst[3] = _mm_add_ps(m1[3], m2[3]); +} + +void MathUtil::subtractMatrix(const __m128 m1[4], const __m128 m2[4], __m128 dst[4]) +{ + dst[0] = _mm_sub_ps(m1[0], m2[0]); + dst[1] = _mm_sub_ps(m1[1], m2[1]); + dst[2] = _mm_sub_ps(m1[2], m2[2]); + dst[3] = _mm_sub_ps(m1[3], m2[3]); +} + +void MathUtil::multiplyMatrix(const __m128 m[4], float scalar, __m128 dst[4]) +{ + __m128 s = _mm_set1_ps(scalar); + dst[0] = _mm_mul_ps(m[0], s); + dst[1] = _mm_mul_ps(m[1], s); + dst[2] = _mm_mul_ps(m[2], s); + dst[3] = _mm_mul_ps(m[3], s); +} + +void MathUtil::multiplyMatrix(const __m128 m1[4], const __m128 m2[4], __m128 dst[4]) +{ + __m128 dst0, dst1, dst2, dst3; + { + __m128 e0 = _mm_shuffle_ps(m2[0], m2[0], _MM_SHUFFLE(0, 0, 0, 0)); + __m128 e1 = _mm_shuffle_ps(m2[0], m2[0], _MM_SHUFFLE(1, 1, 1, 1)); + __m128 e2 = _mm_shuffle_ps(m2[0], m2[0], _MM_SHUFFLE(2, 2, 2, 2)); + __m128 e3 = _mm_shuffle_ps(m2[0], m2[0], _MM_SHUFFLE(3, 3, 3, 3)); + + __m128 v0 = _mm_mul_ps(m1[0], e0); + __m128 v1 = _mm_mul_ps(m1[1], e1); + __m128 v2 = _mm_mul_ps(m1[2], e2); + __m128 v3 = _mm_mul_ps(m1[3], e3); + + __m128 a0 = _mm_add_ps(v0, v1); + __m128 a1 = _mm_add_ps(v2, v3); + __m128 a2 = _mm_add_ps(a0, a1); + + dst0 = a2; + } + + { + __m128 e0 = _mm_shuffle_ps(m2[1], m2[1], _MM_SHUFFLE(0, 0, 0, 0)); + __m128 e1 = _mm_shuffle_ps(m2[1], m2[1], _MM_SHUFFLE(1, 1, 1, 1)); + __m128 e2 = _mm_shuffle_ps(m2[1], m2[1], _MM_SHUFFLE(2, 2, 2, 2)); + __m128 e3 = _mm_shuffle_ps(m2[1], m2[1], _MM_SHUFFLE(3, 3, 3, 3)); + + __m128 v0 = _mm_mul_ps(m1[0], e0); + __m128 v1 = _mm_mul_ps(m1[1], e1); + __m128 v2 = _mm_mul_ps(m1[2], e2); + __m128 v3 = _mm_mul_ps(m1[3], e3); + + __m128 a0 = _mm_add_ps(v0, v1); + __m128 a1 = _mm_add_ps(v2, v3); + __m128 a2 = _mm_add_ps(a0, a1); + + dst1 = a2; + } + + { + __m128 e0 = _mm_shuffle_ps(m2[2], m2[2], _MM_SHUFFLE(0, 0, 0, 0)); + __m128 e1 = _mm_shuffle_ps(m2[2], m2[2], _MM_SHUFFLE(1, 1, 1, 1)); + __m128 e2 = _mm_shuffle_ps(m2[2], m2[2], _MM_SHUFFLE(2, 2, 2, 2)); + __m128 e3 = _mm_shuffle_ps(m2[2], m2[2], _MM_SHUFFLE(3, 3, 3, 3)); + + __m128 v0 = _mm_mul_ps(m1[0], e0); + __m128 v1 = _mm_mul_ps(m1[1], e1); + __m128 v2 = _mm_mul_ps(m1[2], e2); + __m128 v3 = _mm_mul_ps(m1[3], e3); + + __m128 a0 = _mm_add_ps(v0, v1); + __m128 a1 = _mm_add_ps(v2, v3); + __m128 a2 = _mm_add_ps(a0, a1); + + dst2 = a2; + } + + { + __m128 e0 = _mm_shuffle_ps(m2[3], m2[3], _MM_SHUFFLE(0, 0, 0, 0)); + __m128 e1 = _mm_shuffle_ps(m2[3], m2[3], _MM_SHUFFLE(1, 1, 1, 1)); + __m128 e2 = _mm_shuffle_ps(m2[3], m2[3], _MM_SHUFFLE(2, 2, 2, 2)); + __m128 e3 = _mm_shuffle_ps(m2[3], m2[3], _MM_SHUFFLE(3, 3, 3, 3)); + + __m128 v0 = _mm_mul_ps(m1[0], e0); + __m128 v1 = _mm_mul_ps(m1[1], e1); + __m128 v2 = _mm_mul_ps(m1[2], e2); + __m128 v3 = _mm_mul_ps(m1[3], e3); + + __m128 a0 = _mm_add_ps(v0, v1); + __m128 a1 = _mm_add_ps(v2, v3); + __m128 a2 = _mm_add_ps(a0, a1); + + dst3 = a2; + } + dst[0] = dst0; + dst[1] = dst1; + dst[2] = dst2; + dst[3] = dst3; +} + +void MathUtil::negateMatrix(const __m128 m[4], __m128 dst[4]) +{ + __m128 z = _mm_setzero_ps(); + dst[0] = _mm_sub_ps(z, m[0]); + dst[1] = _mm_sub_ps(z, m[1]); + dst[2] = _mm_sub_ps(z, m[2]); + dst[3] = _mm_sub_ps(z, m[3]); +} + +void MathUtil::transposeMatrix(const __m128 m[4], __m128 dst[4]) +{ + __m128 tmp0 = _mm_shuffle_ps(m[0], m[1], 0x44); + __m128 tmp2 = _mm_shuffle_ps(m[0], m[1], 0xEE); + __m128 tmp1 = _mm_shuffle_ps(m[2], m[3], 0x44); + __m128 tmp3 = _mm_shuffle_ps(m[2], m[3], 0xEE); + + dst[0] = _mm_shuffle_ps(tmp0, tmp1, 0x88); + dst[1] = _mm_shuffle_ps(tmp0, tmp1, 0xDD); + dst[2] = _mm_shuffle_ps(tmp2, tmp3, 0x88); + dst[3] = _mm_shuffle_ps(tmp2, tmp3, 0xDD); +} + +void MathUtil::transformVec4(const __m128 m[4], const __m128& v, __m128& dst) +{ + __m128 col1 = _mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 0, 0, 0)); + __m128 col2 = _mm_shuffle_ps(v, v, _MM_SHUFFLE(1, 1, 1, 1)); + __m128 col3 = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 2, 2, 2)); + __m128 col4 = _mm_shuffle_ps(v, v, _MM_SHUFFLE(3, 3, 3, 3)); + + dst = _mm_add_ps( + _mm_add_ps(_mm_mul_ps(m[0], col1), _mm_mul_ps(m[1], col2)), + _mm_add_ps(_mm_mul_ps(m[2], col3), _mm_mul_ps(m[3], col4)) + ); +} + +#endif + + +NS_CC_MATH_END diff --git a/cocos/math/Quaternion.cpp b/cocos/math/Quaternion.cpp new file mode 100644 index 0000000..1eeda57 --- /dev/null +++ b/cocos/math/Quaternion.cpp @@ -0,0 +1,585 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#include "math/Quaternion.h" + +#include +#include +#include "base/Macros.h" +#include "math/Mat3.h" +#include "math/Math.h" +#include "math/Utils.h" + +NS_CC_MATH_BEGIN + +const Quaternion Quaternion::ZERO(0.F, 0.F, 0.F, 0.F); + +Quaternion::Quaternion(float xx, float yy, float zz, float ww) +: x(xx), + y(yy), + z(zz), + w(ww) { +} + +Quaternion::Quaternion(float *array) { + set(array); +} + +Quaternion::Quaternion(const Mat4 &m) { + set(m); +} + +Quaternion::Quaternion(const Vec3 &axis, float angle) { + set(axis, angle); +} + +const Quaternion &Quaternion::identity() { + static Quaternion value(0.F, 0.F, 0.F, 1.F); + return value; +} + +const Quaternion &Quaternion::zero() { + static Quaternion value(0.F, 0.F, 0.F, 0.F); + return value; +} + +bool Quaternion::isIdentity() const { + return x == 0.F && y == 0.F && z == 0.F && w == 1.F; +} + +bool Quaternion::isZero() const { + return x == 0.F && y == 0.F && z == 0.F && w == 0.F; +} + +void Quaternion::createFromRotationMatrix(const Mat4 &m, Quaternion *dst) { + m.getRotation(dst); +} + +void Quaternion::createFromAxisAngle(const Vec3 &axis, float angle, Quaternion *dst) { + CC_ASSERT(dst); + + float halfAngle = angle * 0.5F; + float sinHalfAngle = sinf(halfAngle); + + Vec3 normal(axis); + normal.normalize(); + dst->x = normal.x * sinHalfAngle; + dst->y = normal.y * sinHalfAngle; + dst->z = normal.z * sinHalfAngle; + dst->w = cosf(halfAngle); +} + +void Quaternion::createFromAngleZ(float z, Quaternion *dst) { + CC_ASSERT(dst); + + z *= mathutils::HALF_TO_RAD; + dst->x = dst->y = 0.F; + dst->z = sinf(z); + dst->w = cosf(z); +} + +void Quaternion::conjugate() { + x = -x; + y = -y; + z = -z; +} + +Quaternion Quaternion::getConjugated() const { + Quaternion q(*this); + q.conjugate(); + return q; +} + +void Quaternion::inverse() { + float dot = x * x + y * y + z * z + w * w; + float invDot = dot > 0.F ? 1.F / dot : 0.F; + + x = -x * invDot; + y = -y * invDot; + z = -z * invDot; + w = w * invDot; +} + +Quaternion Quaternion::getInversed() const { + Quaternion q(*this); + q.inverse(); + return q; +} + +void Quaternion::multiply(const Quaternion &q) { + multiply(*this, q, this); +} + +void Quaternion::multiply(const Quaternion &q1, const Quaternion &q2, Quaternion *dst) { + CC_ASSERT(dst); + + float x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y; + float y = q1.w * q2.y - q1.x * q2.z + q1.y * q2.w + q1.z * q2.x; + float z = q1.w * q2.z + q1.x * q2.y - q1.y * q2.x + q1.z * q2.w; + float w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z; + + dst->x = x; + dst->y = y; + dst->z = z; + dst->w = w; +} + +void Quaternion::normalize() { + float len = x * x + y * y + z * z + w * w; + if (len > 0.F) { + len = 1.F / sqrt(len); + x *= len; + y *= len; + z *= len; + w *= len; + } else { + x = 0.F; + y = 0.F; + z = 0.F; + w = 0.F; + } +} + +Quaternion Quaternion::getNormalized() const { + Quaternion q(*this); + q.normalize(); + return q; +} + +void Quaternion::set(float xx, float yy, float zz, float ww) { + this->x = xx; + this->y = yy; + this->z = zz; + this->w = ww; +} + +void Quaternion::set(const float *array) { + CC_ASSERT(array); + x = array[0]; + y = array[1]; + z = array[2]; + w = array[3]; +} + +void Quaternion::set(const Mat4 &m) { + Quaternion::createFromRotationMatrix(m, this); +} + +void Quaternion::set(const Vec3 &axis, float angle) { + Quaternion::createFromAxisAngle(axis, angle, this); +} + +void Quaternion::set(const Quaternion &q) { + this->x = q.x; + this->y = q.y; + this->z = q.z; + this->w = q.w; +} + +void Quaternion::setIdentity() { + x = 0.F; + y = 0.F; + z = 0.F; + w = 1.F; +} + +float Quaternion::toAxisAngle(Vec3 *axis) const { + CC_ASSERT(axis); + + Quaternion q(x, y, z, w); + q.normalize(); + axis->x = q.x; + axis->y = q.y; + axis->z = q.z; + axis->normalize(); + + return (2.F * std::acos(q.w)); +} + +void Quaternion::lerp(const Quaternion &q1, const Quaternion &q2, float t, Quaternion *dst) { + CC_ASSERT(dst); + CC_ASSERT(!(t < 0.F || t > 1.F)); + + float t1 = 1.F - t; + dst->x = t1 * q1.x + t * q2.x; + dst->y = t1 * q1.y + t * q2.y; + dst->z = t1 * q1.z + t * q2.z; + dst->w = t1 * q1.w + t * q2.w; +} + +void Quaternion::slerp(const Quaternion &a, const Quaternion &b, float t, Quaternion *dst) { + // benchmarks: http://jsperf.com/quaternion-slerp-implementations + + float scale0 = 0; + float scale1 = 0; + float bx = b.x; + float by = b.y; + float bz = b.z; + float bw = b.w; + + // calc cosine + float cosom = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + // adjust signs (if necessary) + if (cosom < 0.F) { + cosom = -cosom; + bx = -bx; + by = -by; + bz = -bz; + bw = -bw; + } + // calculate coefficients + if ((1.F - cosom) > 0.000001F) { + // standard case (slerp) + const float omega = acos(cosom); + const float sinom = sinf(omega); + scale0 = sinf((1.0F - t) * omega) / sinom; + scale1 = sinf(t * omega) / sinom; + } else { + // "from" and "to" quaternions are very close + // ... so we can do a linear interpolation + scale0 = 1.0F - t; + scale1 = t; + } + // calculate final values + dst->x = scale0 * a.x + scale1 * bx; + dst->y = scale0 * a.y + scale1 * by; + dst->z = scale0 * a.z + scale1 * bz; + dst->w = scale0 * a.w + scale1 * bw; +} + +void Quaternion::sqlerp(const Quaternion &a, const Quaternion &b, const Quaternion &c, const Quaternion &d, float t, Quaternion *dst) { + Quaternion q1(0.F, 0.F, 0.F, 1.F); + Quaternion q2(0.F, 0.F, 0.F, 1.F); + + slerp(a, d, t, &q1); + slerp(b, c, t, &q2); + slerp(q1, q2, 2.F * t * (1.F - t), dst); +} + +void Quaternion::squad(const Quaternion &q1, const Quaternion &q2, const Quaternion &s1, const Quaternion &s2, float t, Quaternion *dst) { + CC_ASSERT(!(t < 0.F || t > 1.F)); + + Quaternion dstQ(0.F, 0.F, 0.F, 1.F); + Quaternion dstS(0.F, 0.F, 0.F, 1.F); + + slerpForSquad(q1, q2, t, &dstQ); + slerpForSquad(s1, s2, t, &dstS); + slerpForSquad(dstQ, dstS, 2.F * t * (1.F - t), dst); +} + +float Quaternion::angle(const Quaternion &a, const Quaternion &b) { + const auto dot = std::min(std::abs(Quaternion::dot(a, b)), 1.0F); + return std::acos(dot) * 2.0F; +} + +void Quaternion::rotateTowards(const Quaternion &from, const Quaternion &to, float maxStep, Quaternion *dst) { + const auto angle = Quaternion::angle(from, to); + if (angle == 0.0F) { + dst->set(to); + return; + } + + const auto t = std::min(maxStep / (angle * 180.F / math::PI), 1.0F); + Quaternion::slerp(from, to, t, dst); +} + +void Quaternion::fromViewUp(const Vec3 &view, Quaternion *out) { + CC_ASSERT(out); + fromViewUp(view, Vec3(0, 1, 0), out); +} +void Quaternion::fromViewUp(const Vec3 &view, const Vec3 &up, Quaternion *out) { + CC_ASSERT(out); + Mat3 mTemp{Mat3::IDENTITY}; + Mat3::fromViewUp(view, up, &mTemp); + Quaternion::fromMat3(mTemp, out); + out->normalize(); +} + +void Quaternion::fromEuler(float x, float y, float z, Quaternion *dst) { + CC_ASSERT(dst); + float halfToRad = 0.5F * cc::math::PI / 180.F; + x *= halfToRad; + y *= halfToRad; + z *= halfToRad; + float sx = std::sin(x); + float cx = std::cos(x); + float sy = std::sin(y); + float cy = std::cos(y); + float sz = std::sin(z); + float cz = std::cos(z); + + dst->x = sx * cy * cz + cx * sy * sz; + dst->y = cx * sy * cz + sx * cy * sz; + dst->z = cx * cy * sz - sx * sy * cz; + dst->w = cx * cy * cz - sx * sy * sz; +} + +void Quaternion::toEuler(const Quaternion &q, bool outerZ, Vec3 *out) { + CC_ASSERT(out); + float x{q.x}; + float y{q.y}; + float z{q.z}; + float w{q.w}; + float bank{0}; + float heading{0}; + float attitude{0}; + float test = x * y + z * w; + float r2d = 180.F / math::PI; + if (test > 0.499999) { + bank = 0; + heading = 2 * atan2(x, w) * r2d; + attitude = 90; + } else if (test < -0.499999) { + bank = 0; + heading = -2 * atan2(x, w) * r2d; + attitude = -90; + } else { + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + bank = atan2(2 * x * w - 2 * y * z, 1 - 2 * sqx - 2 * sqz) * r2d; + heading = atan2(2 * y * w - 2 * x * z, 1 - 2 * sqy - 2 * sqz) * r2d; + attitude = asin(2 * test) * r2d; + if (outerZ) { + bank = static_cast(-180.F * math::sgn(bank + 1e-6) + bank); + heading = static_cast(-180.F * math::sgn(heading + 1e-6) + heading); + attitude = static_cast(180.F * math::sgn(attitude + 1e-6) - attitude); + } + } + out->x = bank; + out->y = heading; + out->z = attitude; +} + +void Quaternion::fromMat3(const Mat3 &m, Quaternion *out) { + CC_ASSERT(out); + float m00 = m.m[0]; + float m01 = m.m[1]; + float m02 = m.m[2]; + float m10 = m.m[3]; + float m11 = m.m[4]; + float m12 = m.m[5]; + float m20 = m.m[6]; + float m21 = m.m[7]; + float m22 = m.m[8]; + + float fourXSquaredMinus1 = m00 - m11 - m22; + float fourYSquaredMinus1 = m11 - m00 - m22; + float fourZSquaredMinus1 = m22 - m00 - m11; + float fourWSquaredMinus1 = m00 + m11 + m22; + + int biggestIndex = 0; + float fourBiggestSquaredMinus1 = fourWSquaredMinus1; + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; + } + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourYSquaredMinus1; + biggestIndex = 2; + } + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourZSquaredMinus1; + biggestIndex = 3; + } + + float biggestVal = std::sqrt(fourBiggestSquaredMinus1 + 1) * 0.5F; + float mult = 0.25F / biggestVal; + switch (biggestIndex) { + case 0: + out->w = biggestVal; + out->x = (m12 - m21) * mult; + out->y = (m20 - m02) * mult; + out->z = (m01 - m10) * mult; + break; + case 1: + out->w = (m12 - m21) * mult; + out->x = biggestVal; + out->y = (m01 + m10) * mult; + out->z = (m20 + m02) * mult; + break; + case 2: + out->w = (m20 - m02) * mult; + out->x = (m01 + m10) * mult; + out->y = biggestVal; + out->z = (m12 + m21) * mult; + break; + case 3: + out->w = (m01 - m10) * mult; + out->x = (m20 + m02) * mult; + out->y = (m12 + m21) * mult; + out->z = biggestVal; + break; + default: // Silence a -Wswitch-default warning in GCC. Should never actually get here. Assert is just for sanity. + CC_ASSERT(false); + out->w = 1.F; + out->x = 0.F; + out->y = 0.F; + out->z = 0.F; + break; + } +} + +void Quaternion::slerp(float q1x, float q1y, float q1z, float q1w, float q2x, float q2y, float q2z, float q2w, float t, float *dstx, float *dsty, float *dstz, float *dstw) { + // Fast slerp implementation by kwhatmough: + // It contains no division operations, no trig, no inverse trig + // and no sqrt. Not only does this code tolerate small constraint + // errors in the input quaternions, it actually corrects for them. + CC_ASSERT(dstx && dsty && dstz && dstw); + CC_ASSERT(!(t < 0.F || t > 1.F)); + + if (t == 0.F) { + *dstx = q1x; + *dsty = q1y; + *dstz = q1z; + *dstw = q1w; + return; + } + + if (t == 1.F) { + *dstx = q2x; + *dsty = q2y; + *dstz = q2z; + *dstw = q2w; + return; + } + + if (q1x == q2x && q1y == q2y && q1z == q2z && q1w == q2w) { + *dstx = q1x; + *dsty = q1y; + *dstz = q1z; + *dstw = q1w; + return; + } + + float halfY; + float alpha; + float beta; + + float u; + float f1; + float f2a; + float f2b; + + float ratio1; + float ratio2; + + float halfSecHalfTheta; + float versHalfTheta; + + float sqNotU; + float sqU; + + float cosTheta = q1w * q2w + q1x * q2x + q1y * q2y + q1z * q2z; + + // As usual in all slerp implementations, we fold theta. + alpha = cosTheta >= 0 ? 1.F : -1.F; + halfY = 1.F + alpha * cosTheta; + + // Here we bisect the interval, so we need to fold t as well. + f2b = t - 0.5F; + u = f2b >= 0 ? f2b : -f2b; + f2a = u - f2b; + f2b += u; + u += u; + f1 = 1.F - u; + + // One iteration of Newton to get 1-cos(theta / 2) to good accuracy. + halfSecHalfTheta = 1.09F - (0.476537F - 0.0903321F * halfY) * halfY; + halfSecHalfTheta *= 1.5F - halfY * halfSecHalfTheta * halfSecHalfTheta; + versHalfTheta = 1.F - halfY * halfSecHalfTheta; + + // Evaluate series expansions of the coefficients. + sqNotU = f1 * f1; + ratio2 = 0.0000440917108F * versHalfTheta; + ratio1 = -0.00158730159F + (sqNotU - 16.F) * ratio2; + ratio1 = 0.0333333333F + ratio1 * (sqNotU - 9.F) * versHalfTheta; + ratio1 = -0.333333333F + ratio1 * (sqNotU - 4.F) * versHalfTheta; + ratio1 = 1.F + ratio1 * (sqNotU - 1.F) * versHalfTheta; + + sqU = u * u; + ratio2 = -0.00158730159F + (sqU - 16.F) * ratio2; + ratio2 = 0.0333333333F + ratio2 * (sqU - 9.F) * versHalfTheta; + ratio2 = -0.333333333F + ratio2 * (sqU - 4.F) * versHalfTheta; + ratio2 = 1.F + ratio2 * (sqU - 1.F) * versHalfTheta; + + // Perform the bisection and resolve the folding done earlier. + f1 *= ratio1 * halfSecHalfTheta; + f2a *= ratio2; + f2b *= ratio2; + alpha *= f1 + f2a; + beta = f1 + f2b; + + // Apply final coefficients to a and b as usual. + float w = alpha * q1w + beta * q2w; + float x = alpha * q1x + beta * q2x; + float y = alpha * q1y + beta * q2y; + float z = alpha * q1z + beta * q2z; + + // This final adjustment to the quaternion's length corrects for + // any small constraint error in the inputs q1 and q2 But as you + // can see, it comes at the cost of 9 additional multiplication + // operations. If this error-correcting feature is not required, + // the following code may be removed. + f1 = 1.5F - 0.5F * (w * w + x * x + y * y + z * z); + *dstw = w * f1; + *dstx = x * f1; + *dsty = y * f1; + *dstz = z * f1; +} + +void Quaternion::slerpForSquad(const Quaternion &q1, const Quaternion &q2, float t, Quaternion *dst) { + CC_ASSERT(dst); + + // cos(omega) = q1 * q2; + // slerp(q1, q2, t) = (q1*sin((1-t)*omega) + q2*sin(t*omega))/sin(omega); + // q1 = +- q2, slerp(q1,q2,t) = q1. + // This is a straight-forward implementation of the formula of slerp. It does not do any sign switching. + float c = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; + + if (std::abs(c) >= 1.F) { + dst->x = q1.x; + dst->y = q1.y; + dst->z = q1.z; + dst->w = q1.w; + return; + } + + float omega = std::acos(c); + float s = std::sqrt(1.F - c * c); + if (std::abs(s) <= 0.00001F) { + dst->x = q1.x; + dst->y = q1.y; + dst->z = q1.z; + dst->w = q1.w; + return; + } + + float r1 = std::sin((1 - t) * omega) / s; + float r2 = std::sin(t * omega) / s; + dst->x = (q1.x * r1 + q2.x * r2); + dst->y = (q1.y * r1 + q2.y * r2); + dst->z = (q1.z * r1 + q2.z * r2); + dst->w = (q1.w * r1 + q2.w * r2); +} + +NS_CC_MATH_END diff --git a/cocos/math/Quaternion.h b/cocos/math/Quaternion.h new file mode 100644 index 0000000..c4c2ca4 --- /dev/null +++ b/cocos/math/Quaternion.h @@ -0,0 +1,479 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#pragma once + +#include "math/Mat4.h" +#include "math/Vec3.h" +//#include "Plane.h" + +/** + * @addtogroup base + * @{ + */ + +NS_CC_MATH_BEGIN + +class Mat4; +class Mat3; +/** + * Defines a 4-element quaternion that represents the orientation of an object in space. + * + * Quaternions are typically used as a replacement for Euler angles and rotation matrices as a way to achieve smooth interpolation and avoid gimbal lock. + * + * Note that this quaternion class does not automatically keep the quaternion normalized. Therefore, care must be taken to normalize the quaternion when necessary, by calling the normalize method. + * This class provides three methods for doing quaternion interpolation: lerp, slerp, and squad. + * + * lerp (linear interpolation): the interpolation curve gives a straight line in quaternion space. It is simple and fast to compute. The only problem is that it does not provide constant angular velocity. Note that a constant velocity is not necessarily a requirement for a curve; + * slerp (spherical linear interpolation): the interpolation curve forms a great arc on the quaternion unit sphere. Slerp provides constant angular velocity; + * squad (spherical spline interpolation): interpolating between a series of rotations using slerp leads to the following problems: + * - the curve is not smooth at the control points; + * - the angular velocity is not constant; + * - the angular velocity is not continuous at the control points. + * + * Since squad is continuously differentiable, it remedies the first and third problems mentioned above. + * The slerp method provided here is intended for interpolation of principal rotations. It treats +q and -q as the same principal rotation and is at liberty to use the negative of either input. The resulting path is always the shorter arc. + * + * The lerp method provided here interpolates strictly in quaternion space. Note that the resulting path may pass through the origin if interpolating between a quaternion and its exact negative. + * + * As an example, consider the following quaternions: + * + * q1 = (0.6, 0.8, 0.0, 0.0), + * q2 = (0.0, 0.6, 0.8, 0.0), + * q3 = (0.6, 0.0, 0.8, 0.0), and + * q4 = (-0.8, 0.0, -0.6, 0.0). + * For the point p = (1.0, 1.0, 1.0), the following figures show the trajectories of p using lerp, slerp, and squad. + */ +class CC_DLL Quaternion { +public: + /** + * The x-value of the quaternion's vector component. + */ + float x{0.0F}; + /** + * The y-value of the quaternion's vector component. + */ + float y{0.0F}; + /** + * The z-value of the quaternion's vector component. + */ + float z{0.0F}; + /** + * The scalar component of the quaternion. + */ + float w{1.0F}; + + /** + * Constructs a quaternion initialized to (0, 0, 0, 1). + */ + Quaternion() = default; + + /** + * Constructs a quaternion. + * + * @param xx The x component of the quaternion. + * @param yy The y component of the quaternion. + * @param zz The z component of the quaternion. + * @param ww The w component of the quaternion. + */ + Quaternion(float xx, float yy, float zz, float ww); + + /** + * Constructs a new quaternion from the values in the specified array. + * + * @param array The values for the new quaternion. + */ + explicit Quaternion(float *array); + + /** + * Constructs a quaternion equal to the rotational part of the specified matrix. + * + * @param m The matrix. + */ + explicit Quaternion(const Mat4 &m); + + /** + * Constructs a quaternion equal to the rotation from the specified axis and angle. + * + * @param axis A vector describing the axis of rotation. + * @param angle The angle of rotation (in radians). + */ + Quaternion(const Vec3 &axis, float angle); + + /** + * Returns the identity quaternion. + * + * @return The identity quaternion. + */ + static const Quaternion &identity(); + + /** + * Returns the quaternion with all zeros. + * + * @return The quaternion. + */ + static const Quaternion &zero(); + + /** + * Determines if this quaternion is equal to the identity quaternion. + * + * @return true if it is the identity quaternion, false otherwise. + */ + bool isIdentity() const; + + /** + * Determines if this quaternion is all zeros. + * + * @return true if this quaternion is all zeros, false otherwise. + */ + bool isZero() const; + + /** + * Calculates the quaternion with Euler angles, the rotation order is YZX + */ + static void fromEuler(float x, float y, float z, Quaternion *dst); + + /** + * Converts the quaternion to angles, result angle x, y in the range of [-180, 180], z in the range of [-90, 90] interval, the rotation order is YZX + */ + static void toEuler(const Quaternion &q, bool outerZ, Vec3 *out); + + /** + * Creates a quaternion equal to the rotational part of the specified matrix + * and stores the result in dst. + * + * @param m The matrix. + * @param dst A quaternion to store the conjugate in. + */ + static void createFromRotationMatrix(const Mat4 &m, Quaternion *dst); + + /** + * Creates this quaternion equal to the rotation from the specified axis and angle + * and stores the result in dst. + * + * @param axis A vector describing the axis of rotation. + * @param angle The angle of rotation (in radians). + * @param dst A quaternion to store the conjugate in. + */ + static void createFromAxisAngle(const Vec3 &axis, float angle, Quaternion *dst); + + /** + * @en Calculates the quaternion with given 2D angle (0, 0, z). + * @zh 根据 2D 角度(0, 0, z)计算四元数 + * + * @param out Output quaternion + * @param z Angle to rotate around Z axis in degrees. + */ + static void createFromAngleZ(float z, Quaternion *dst); + + /** + * Sets this quaternion to the conjugate of itself. + */ + void conjugate(); + + /** + * Gets the conjugate of this quaternion. + * + */ + Quaternion getConjugated() const; + + /** + * Sets this quaternion to the inverse of itself. + * + * Note that the inverse of a quaternion is equal to its conjugate + * when the quaternion is unit-length. For this reason, it is more + * efficient to use the conjugate method directly when you know your + * quaternion is already unit-length. + */ + void inverse(); + + /** + * Gets the inverse of this quaternion. + * + * Note that the inverse of a quaternion is equal to its conjugate + * when the quaternion is unit-length. For this reason, it is more + * efficient to use the conjugate method directly when you know your + * quaternion is already unit-length. + */ + Quaternion getInversed() const; + + /** + * Multiplies this quaternion by the specified one and stores the result in this quaternion. + * + * @param q The quaternion to multiply. + */ + void multiply(const Quaternion &q); + + /** + * Multiplies the specified quaternions and stores the result in dst. + * + * @param q1 The first quaternion. + * @param q2 The second quaternion. + * @param dst A quaternion to store the result in. + */ + static void multiply(const Quaternion &q1, const Quaternion &q2, Quaternion *dst); + + /** + * Normalizes this quaternion to have unit length. + * + * If the quaternion already has unit length or if the length + * of the quaternion is zero, this method does nothing. + */ + void normalize(); + + /** + * Get the normalized quaternion. + * + * If the quaternion already has unit length or if the length + * of the quaternion is zero, this method simply copies + * this vector. + */ + Quaternion getNormalized() const; + + /** + * Sets the elements of the quaternion to the specified values. + * + * @param xx The new x-value. + * @param yy The new y-value. + * @param zz The new z-value. + * @param ww The new w-value. + */ + void set(float xx, float yy, float zz, float ww); + + /** + * Sets the elements of the quaternion from the values in the specified array. + * + * @param array An array containing the elements of the quaternion in the order x, y, z, w. + */ + void set(const float *array); + + /** + * Sets the quaternion equal to the rotational part of the specified matrix. + * + * @param m The matrix. + */ + void set(const Mat4 &m); + + /** + * Sets the quaternion equal to the rotation from the specified axis and angle. + * + * @param axis The axis of rotation. + * @param angle The angle of rotation (in radians). + */ + void set(const Vec3 &axis, float angle); + + /** + * Sets the elements of this quaternion to a copy of the specified quaternion. + * + * @param q The quaternion to copy. + */ + void set(const Quaternion &q); + + /** + * Sets this quaternion to be equal to the identity quaternion. + */ + void setIdentity(); + + /** + * Converts this Quaternion4f to axis-angle notation. The axis is normalized. + * + * @param e The Vec3f which stores the axis. + * + * @return The angle (in radians). + */ + float toAxisAngle(Vec3 *axis) const; + + /** + * Interpolates between two quaternions using linear interpolation. + * + * The interpolation curve for linear interpolation between + * quaternions gives a straight line in quaternion space. + * + * @param q1 The first quaternion. + * @param q2 The second quaternion. + * @param t The interpolation coefficient. + * @param dst A quaternion to store the result in. + */ + static void lerp(const Quaternion &q1, const Quaternion &q2, float t, Quaternion *dst); + + /** + * Calculates the quaternion with the three-dimensional transform matrix, considering no scale included in the matrix + */ + static void fromMat3(const Mat3 &m, Quaternion *out); + + /** + * Calculates the quaternion with the up direction and the direction of the viewport + */ + static void fromViewUp(const Vec3 &view, Quaternion *out); + static void fromViewUp(const Vec3 &view, const Vec3 &up, Quaternion *out); + + /** + * Interpolates between two quaternions using spherical linear interpolation. + * + * Spherical linear interpolation provides smooth transitions between different + * orientations and is often useful for animating models or cameras in 3D. + * + * Note: For accurate interpolation, the input quaternions must be at (or close to) unit length. + * This method does not automatically normalize the input quaternions, so it is up to the + * caller to ensure they call normalize beforehand, if necessary. + * + * @param q1 The first quaternion. + * @param q2 The second quaternion. + * @param t The interpolation coefficient. + * @param dst A quaternion to store the result in. + */ + static void slerp(const Quaternion &a, const Quaternion &b, float t, Quaternion *dst); + + /** + * @en Spherical quaternion interpolation with two control points + * + * @param out the receiving quaternion + * @param a the first operand + * @param b the second operand + * @param c the third operand + * @param d the fourth operand + * @param t interpolation amount, in the range [0-1], between the two inputs + * @returns out + */ + static void sqlerp(const Quaternion &a, const Quaternion &b, const Quaternion &c, const Quaternion &d, float t, Quaternion *dst); + + /** + * Interpolates over a series of quaternions using spherical spline interpolation. + * + * Spherical spline interpolation provides smooth transitions between different + * orientations and is often useful for animating models or cameras in 3D. + * + * Note: For accurate interpolation, the input quaternions must be unit. + * This method does not automatically normalize the input quaternions, + * so it is up to the caller to ensure they call normalize beforehand, if necessary. + * + * @param q1 The first quaternion. + * @param q2 The second quaternion. + * @param s1 The first control point. + * @param s2 The second control point. + * @param t The interpolation coefficient. + * @param dst A quaternion to store the result in. + */ + /** + * @deprecated since v3.8.0 please use [[sqlerp]] instead + */ + static void squad(const Quaternion &q1, const Quaternion &q2, const Quaternion &s1, const Quaternion &s2, float t, Quaternion *dst); + + /** + * @en Quaternion dot product (scalar product) + * @zh 四元数点积(数量积) + * @param a The first unit quaternion + * @param b The second unit quaternion + */ + static inline float dot(const Quaternion &a, const Quaternion &b) { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + + /** + * @en Gets the angular distance between two unit quaternions + * @zh 获取两个单位四元数的夹角 + * @param a The first unit quaternion + * @param b The second unit quaternion + * @returns Angle between the two quaternions in radians + */ + static float angle(const Quaternion &a, const Quaternion &b); + + /** + * @en Rotate a `from` unit quaternion towards `to` unit quaternion + * @zh 将一个起始单位四元数旋转到一个目标单位四元数 + * @param from The first unit quaternion + * @param to The second unit quaternion + * @param maxStep The maximum angle of rotation in degrees + * @returns new unit quaternion generated during rotation + */ + static void rotateTowards(const Quaternion &from, const Quaternion &to, float maxStep, Quaternion *dst); + + /** + * Calculates the quaternion product of this quaternion with the given quaternion. + * + * Note: this does not modify this quaternion. + * + * @param q The quaternion to multiply. + * @return The quaternion product. + */ + inline const Quaternion operator*(const Quaternion &q) const; + + /** + * Multiplies this quaternion with the given quaternion. + * + * @param q The quaternion to multiply. + * @return This quaternion, after the multiplication occurs. + */ + inline Quaternion &operator*=(const Quaternion &q); + + /** + * Determines if this quaternion is approximately equal to the given quaternion. + */ + inline bool approxEquals(const Quaternion &v, float precision = CC_FLOAT_CMP_PRECISION) const { + return math::isEqualF(x, v.x, precision) && math::isEqualF(y, v.y, precision) && math::isEqualF(z, v.z, precision) && math::isEqualF(w, v.w, precision); + } + + /** equals to Quaternion(0,0,0, 0) */ + static const Quaternion ZERO; + +private: + /** + * Interpolates between two quaternions using spherical linear interpolation. + * + * Spherical linear interpolation provides smooth transitions between different + * orientations and is often useful for animating models or cameras in 3D. + * + * Note: For accurate interpolation, the input quaternions must be at (or close to) unit length. + * This method does not automatically normalize the input quaternions, so it is up to the + * caller to ensure they call normalize beforehand, if necessary. + * + * @param q1x The x component of the first quaternion. + * @param q1y The y component of the first quaternion. + * @param q1z The z component of the first quaternion. + * @param q1w The w component of the first quaternion. + * @param q2x The x component of the second quaternion. + * @param q2y The y component of the second quaternion. + * @param q2z The z component of the second quaternion. + * @param q2w The w component of the second quaternion. + * @param t The interpolation coefficient. + * @param dstx A pointer to store the x component of the slerp in. + * @param dsty A pointer to store the y component of the slerp in. + * @param dstz A pointer to store the z component of the slerp in. + * @param dstw A pointer to store the w component of the slerp in. + */ + /** + * @deprecated since v3.8.0 + */ + static void slerp(float q1x, float q1y, float q1z, float q1w, float q2x, float q2y, float q2z, float q2w, float t, float *dstx, float *dsty, float *dstz, float *dstw); + + /** + * @deprecated since v3.8.0 + */ + static void slerpForSquad(const Quaternion &q1, const Quaternion &q2, float t, Quaternion *dst); +}; + +NS_CC_MATH_END +/** + end of base group + @} + */ +#include "math/Quaternion.inl" diff --git a/cocos/math/Quaternion.inl b/cocos/math/Quaternion.inl new file mode 100644 index 0000000..830b568 --- /dev/null +++ b/cocos/math/Quaternion.inl @@ -0,0 +1,38 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#include "math/Quaternion.h" + +NS_CC_MATH_BEGIN + +inline const Quaternion Quaternion::operator*(const Quaternion& q) const { + Quaternion result(*this); + result.multiply(q); + return result; +} + +inline Quaternion& Quaternion::operator*=(const Quaternion& q) { + multiply(q); + return *this; +} + +NS_CC_MATH_END diff --git a/cocos/math/Utils.cpp b/cocos/math/Utils.cpp new file mode 100644 index 0000000..77a8b32 --- /dev/null +++ b/cocos/math/Utils.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "math/Utils.h" + +#include +#include +#include +#include "math/Math.h" + +namespace { +std::random_device rd; +std::uniform_real_distribution uniformReal{0.0F, 1.0F}; +} // namespace + +namespace cc { + +namespace mathutils { + +float random() { + return uniformReal(rd); +} + +float absMaxComponent(const Vec3 &v) { + return absMax(absMax(v.x, v.y), v.z); +} + +float maxComponent(const Vec3 &v) { + return std::max(std::max(v.x, v.y), v.z); +} + +uint16_t floatToHalf(float fval) { + union { + float f; + unsigned int ui; + } u = {fval}; + unsigned int ui = u.ui; + + int s = (ui >> 16) & 0x8000; // NOLINT + int em = ui & 0x7fffffff; // NOLINT + + /* bias exponent and round to nearest; 112 is relative exponent bias (127-15) */ + int h = (em - (112 << 23) + (1 << 12)) >> 13; + + /* underflow: flush to zero; 113 encodes exponent -14 */ + h = (em < (113 << 23)) ? 0 : h; + + /* overflow: infinity; 143 encodes exponent 16 */ + h = (em >= (143 << 23)) ? 0x7c00 : h; + + /* NaN; note that we convert all types of NaN to qNaN */ + h = (em > (255 << 23)) ? 0x7e00 : h; + + return static_cast(s | h); +} + +float halfToFloat(uint16_t hval) { + union { + float f; + unsigned int ui; + } u; + + int s = (hval >> 15) & 0x00000001; + int em = hval & 0x00007fff; + int m = 0; + + if (em > 0) { + /* normalized */ + if (em > 30 << 10) { + /* overflow: infinity */ + em = 255 << 23; + } else { + em = (em + (112 << 10)) << 13; + } + } else { + /* denormalized */ + if (em > 25 << 10) { + /* underflow: flush to zero */ + em = 0; + } else { + em = (em + (113 << 10)) >> 1; + } + } + + u.ui = ((s << 31)) | em | m; // NOLINT + return u.f; +} + +} // namespace mathutils + +} // namespace cc diff --git a/cocos/math/Utils.h b/cocos/math/Utils.h new file mode 100644 index 0000000..95e5ea9 --- /dev/null +++ b/cocos/math/Utils.h @@ -0,0 +1,292 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "math/Vec3.h" + +namespace cc { + +namespace mathutils { + +constexpr auto EPSILON = 0.000001; +constexpr auto D2R = M_PI / 180.0; +constexpr auto R2D = 180.0 / M_PI; +constexpr auto HALF_TO_RAD = 0.5 * D2R; +/** + * @en Tests whether or not the arguments have approximately the same value, within an absolute
+ * or relative tolerance of glMatrix.EPSILON (an absolute tolerance is used for values less
+ * than or equal to 1.0, and a relative tolerance is used for larger values) + * @zh 在glMatrix的绝对或相对容差范围内,测试参数是否具有近似相同的值。
+ * EPSILON(小于等于1.0的值采用绝对公差,大于1.0的值采用相对公差) + * @param a The first number to test. + * @param b The second number to test. + * @return True if the numbers are approximately equal, false otherwise. + */ +template +bool equals(F a, F b) { + static_assert(std::is_floating_point::value, "number expected"); + return std::fabs(a - b) <= EPSILON * std::max(1.0F, std::max(std::fabs(a), std::fabs(b))); +} + +/** + * @en Tests whether or not the arguments have approximately the same value by given maxDiff
+ * @zh 通过给定的最大差异,测试参数是否具有近似相同的值。 + * @param a The first number to test. + * @param b The second number to test. + * @param maxDiff Maximum difference. + * @return True if the numbers are approximately equal, false otherwise. + */ +template +bool approx(F a, F b, F maxDiff) { + static_assert(std::is_floating_point::value, "number expected"); + return std::fabs(a - b) <= maxDiff; +} + +template +bool approx(F a, F b) { + static_assert(std::is_floating_point::value, "number expected"); + return std::fabs(a - b) <= EPSILON; +} + +/** + * @en Clamps a value between a minimum float and maximum float value.
+ * @zh 返回最小浮点数和最大浮点数之间的一个数值。可以使用 clamp 函数将不断变化的数值限制在范围内。 + * @param val + * @param min + * @param max + */ +template +typename std::enable_if::value, F>::type +clamp(F val, F min, F max) { + if (min > max) { + const auto temp = min; + min = max; + max = temp; + } + + return val < min ? min : val > max ? max + : val; +} + +/** + * @en Clamps a value between 0 and 1.
+ * @zh 将值限制在0和1之间。 + * @param val + */ +template +auto clamp01(F val) { + static_assert(std::is_floating_point::value, "number expected"); + return val < 0 ? 0 : val > 1 ? 1 + : val; +} + +/** + * @param from + * @param to + * @param ratio - The interpolation coefficient. + */ +template +auto lerp(F from, F to, F ratio) { + static_assert(std::is_floating_point::value, "number expected"); + return from + (to - from) * ratio; +} + +/** + * @en Convert Degree To Radian
+ * @zh 把角度换算成弧度。 + * @param {Number} a Angle in Degrees + */ +template +auto toRadian(F a) { + static_assert(std::is_floating_point::value, "number expected"); + return a * D2R; +} + +/** + * @en Convert Radian To Degree
+ * @zh 把弧度换算成角度。 + * @param {Number} a Angle in Radian + */ +template +auto toDegree(F a) { + return a * R2D; +} + +/** + * @method random + */ +float random(); + +/** + * @en Returns a floating-point random number between min (inclusive) and max (exclusive).
+ * @zh 返回最小(包含)和最大(不包含)之间的浮点随机数。 + * @method randomRange + * @param min + * @param max + * @return The random number. + */ +template +auto randomRange(T min, T max) { + return random() * (max - min) + min; +} + +/** + * @en Returns a random integer between min (inclusive) and max (exclusive).
+ * @zh 返回最小(包含)和最大(不包含)之间的随机整数。 + * @param min + * @param max + * @return The random integer. + */ +template +auto randomRangeInt(T min, T max) { + static_assert(std::is_arithmetic::value, "number expected"); + return floor(randomRange(min, max)); +} + +/** + * Linear congruential generator using Hull-Dobell Theorem. + * + * @param seed The random seed. + * @return The pseudo random. + */ +template +auto pseudoRandom(In seed) { + static_assert(std::is_arithmetic::value, "number expected"); + seed = (seed * 9301 + 49297) % 233280; + return seed / 233280.0; +} + +/** + * Returns a floating-point pseudo-random number between min (inclusive) and max (exclusive). + * + * @param seed + * @param min + * @param max + * @return The random number. + */ +template +auto pseudoRandomRange(In seed, In min, In max) { + static_assert(std::is_arithmetic::value, "number expected"); + return pseudoRandom(seed) * (max - min) + min; +} + +/** + * @en Returns a pseudo-random integer between min (inclusive) and max (exclusive).
+ * @zh 返回最小(包含)和最大(不包含)之间的浮点伪随机数。 + * @param seed + * @param min + * @param max + * @return The random integer. + */ +template +auto pseudoRandomRangeInt(In seed, In min, In max) { + return floor(pseudoRandomRange(seed, min, max)); +} + +/** + * Returns the next power of two for the value.
+ * + * @param val + * @return The the next power of two. + */ +template +auto nextPow2(T val) { + // ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --val; + val = (val >> 1) | val; + val = (val >> 2) | val; + val = (val >> 4) | val; + val = (val >> 8) | val; + val = (val >> 16) | val; + ++val; + return val; +} + +/** + * @en Returns float remainder for t / length.
+ * @zh 返回t / length的浮点余数。 + * @param t Time start at 0. + * @param length Time of one cycle. + * @return The Time wrapped in the first cycle. + */ +template +auto repeat(T t, T length) { + return t - floor(t / length) * length; +} + +/** + * Returns time wrapped in ping-pong mode. + * + * @param t Time start at 0. + * @param length Time of one cycle. + * @return The time wrapped in the first cycle. + */ +template +auto pingPong(T t, T length) { + t = repeat(t, length * 2); + t = length - std::fabs(t - length); + return t; +} + +/** + * @en Returns ratio of a value within a given range.
+ * @zh 返回给定范围内的值的比率。 + * @param from Start value. + * @param to End value. + * @param value Given value. + * @return The ratio between [from, to]. + */ +template +auto inverseLerp(T from, T to, T value) { + return (value - from) / (to - from); +} + +/** + * @zh 对所有分量的绝对值进行比较大小,返回绝对值最大的分量。 + * @param v 类 Vec3 结构 + * @returns 绝对值最大的分量 + */ +float absMaxComponent(const Vec3 &v); + +float maxComponent(const Vec3 &v); + +/** + * @zh 对 a b 的绝对值进行比较大小,返回绝对值最大的值。 + * @param a number + * @param b number + */ +template +auto absMax(F a, F b) { + return std::fabs(a) > std::fabs(b) ? a : b; +} + +uint16_t floatToHalf(float fval); + +float halfToFloat(uint16_t hval); + +} // namespace mathutils +} // namespace cc diff --git a/cocos/math/Vec2.cpp b/cocos/math/Vec2.cpp new file mode 100644 index 0000000..4134069 --- /dev/null +++ b/cocos/math/Vec2.cpp @@ -0,0 +1,300 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#include "math/Vec2.h" +#include "base/Macros.h" +#include "math/MathUtil.h" + +NS_CC_MATH_BEGIN + +// returns true if segment a-b intersects with segment c-d. s->e is the overlap part +bool isOneDimensionSegmentOverlap(float a, float b, float c, float d, float *s, float *e) { + float abmin = std::min(a, b); + float abmax = std::max(a, b); + float cdmin = std::min(c, d); + float cdmax = std::max(c, d); + + if (abmax < cdmin || cdmax < abmin) { + // abmin->abmax->cdmin->cdmax or cdmin->cdmax->abmin->abmax + return false; + } + + if (abmin >= cdmin && abmin <= cdmax) { + // cdmin->abmin->cdmax->abmax or cdmin->abmin->abmax->cdmax + if (s != nullptr) *s = abmin; + if (e != nullptr) *e = cdmax < abmax ? cdmax : abmax; + } else if (abmax >= cdmin && abmax <= cdmax) { + // ABmin->CDmin->ABmax->CDmax + if (s != nullptr) *s = cdmin; + if (e != nullptr) *e = abmax; + } else { + // ABmin->CDmin->CDmax->ABmax + if (s != nullptr) *s = cdmin; + if (e != nullptr) *e = cdmax; + } + return true; +} + +// cross product of 2 vector. a->b X c->d +float crossProduct2Vector(const Vec2 &a, const Vec2 &b, const Vec2 &c, const Vec2 &d) { + return (d.y - c.y) * (b.x - a.x) - (d.x - c.x) * (b.y - a.y); +} + +float Vec2::angle(const Vec2 &v1, const Vec2 &v2) { + const auto magSqr1 = v1.x * v1.x + v1.y * v1.y; + const auto magSqr2 = v2.x * v2.x + v2.y * v2.y; + + if (magSqr1 == 0.0F || magSqr2 == 0.0F) { + return 0.0F; + } + + const auto dot = v1.x * v2.x + v1.y * v2.y; + auto cosine = dot / (sqrt(magSqr1 * magSqr2)); + cosine = clampf(cosine, -1.0, 1.0); + return acos(cosine); +} + +void Vec2::add(const Vec2 &v1, const Vec2 &v2, Vec2 *dst) { + CC_ASSERT(dst); + + dst->x = v1.x + v2.x; + dst->y = v1.y + v2.y; +} + +void Vec2::clamp(const Vec2 &min, const Vec2 &max) { + CC_ASSERT(!(min.x > max.x || min.y > max.y)); + + // Clamp the x value. + if (x < min.x) { + x = min.x; + } + if (x > max.x) { + x = max.x; + } + + // Clamp the y value. + if (y < min.y) { + y = min.y; + } + if (y > max.y) { + y = max.y; + } +} + +void Vec2::clamp(const Vec2 &v, const Vec2 &min, const Vec2 &max, Vec2 *dst) { + CC_ASSERT(dst); + CC_ASSERT(!(min.x > max.x || min.y > max.y)); + + // Clamp the x value. + dst->x = v.x; + if (dst->x < min.x) { + dst->x = min.x; + } + if (dst->x > max.x) { + dst->x = max.x; + } + + // Clamp the y value. + dst->y = v.y; + if (dst->y < min.y) { + dst->y = min.y; + } + if (dst->y > max.y) { + dst->y = max.y; + } +} + +float Vec2::distance(const Vec2 &v) const { + float dx = v.x - x; + float dy = v.y - y; + + return std::sqrt(dx * dx + dy * dy); +} + +float Vec2::dot(const Vec2 &v1, const Vec2 &v2) { + return (v1.x * v2.x + v1.y * v2.y); +} + +float Vec2::length() const { + return std::sqrt(x * x + y * y); +} + +void Vec2::normalize() { + float len = x * x + y * y; + if (len > 0.0F) { + len = 1.0F / std::sqrt(len); + x *= len; + y *= len; + } +} + +Vec2 Vec2::getNormalized() const { + Vec2 v(*this); + v.normalize(); + return v; +} + +void Vec2::rotate(const Vec2 &point, float angle) { + float sinAngle = std::sin(angle); + float cosAngle = std::cos(angle); + + if (point.isZero()) { + float tempX = x * cosAngle - y * sinAngle; + y = y * cosAngle + x * sinAngle; + x = tempX; + } else { + float tempX = x - point.x; + float tempY = y - point.y; + + x = tempX * cosAngle - tempY * sinAngle + point.x; + y = tempY * cosAngle + tempX * sinAngle + point.y; + } +} + +void Vec2::set(const float *array) { + CC_ASSERT(array); + + x = array[0]; + y = array[1]; +} + +void Vec2::subtract(const Vec2 &v1, const Vec2 &v2, Vec2 *dst) { + CC_ASSERT(dst); + + dst->x = v1.x - v2.x; + dst->y = v1.y - v2.y; +} + +bool Vec2::equals(const Vec2 &target) const { + return (std::abs(this->x - target.x) < FLT_EPSILON) && (std::abs(this->y - target.y) < FLT_EPSILON); +} + +bool Vec2::fuzzyEquals(const Vec2 &b, float var) const { + if (x - var <= b.x && b.x <= x + var) { + if (y - var <= b.y && b.y <= y + var) { + return true; + } + } + + return false; +} + +float Vec2::getAngle(const Vec2 &other) const { + return Vec2::angle(*this, other); +} + +Vec2 Vec2::rotateByAngle(const Vec2 &pivot, float angle) const { + return pivot + (*this - pivot).rotate(Vec2::forAngle(angle)); +} + +bool Vec2::isLineIntersect(const Vec2 &a, const Vec2 &b, + const Vec2 &c, const Vec2 &d, + float *s, float *t) { + // FAIL: Line undefined + if ((a.x == b.x && a.y == b.y) || (c.x == d.x && c.y == d.y)) { + return false; + } + + const float denom = crossProduct2Vector(a, b, c, d); + + if (denom == 0) { + // Lines parallel or overlap + return false; + } + + if (s != nullptr) *s = crossProduct2Vector(c, d, c, a) / denom; + if (t != nullptr) *t = crossProduct2Vector(a, b, c, a) / denom; + + return true; +} + +bool Vec2::isLineParallel(const Vec2 &a, const Vec2 &b, + const Vec2 &c, const Vec2 &d) { + // FAIL: Line undefined + if ((a.x == b.x && a.y == b.y) || (c.x == d.x && c.y == d.y)) { + return false; + } + + if (crossProduct2Vector(a, b, c, d) == 0) { + // line overlap + return !(crossProduct2Vector(c, d, c, a) == 0 || crossProduct2Vector(a, b, c, a) == 0); + } + + return false; +} + +bool Vec2::isLineOverlap(const Vec2 &a, const Vec2 &b, + const Vec2 &c, const Vec2 &d) { + // FAIL: Line undefined + if ((a.x == b.x && a.y == b.y) || (c.x == d.x && c.y == d.y)) { + return false; + } + + return (crossProduct2Vector(a, b, c, d) == 0 && (crossProduct2Vector(c, d, c, a) == 0 || crossProduct2Vector(a, b, c, a) == 0)); +} + +bool Vec2::isSegmentOverlap(const Vec2 &a, const Vec2 &b, const Vec2 &c, const Vec2 &d, Vec2 *s, Vec2 *e) { + if (isLineOverlap(a, b, c, d)) { + return isOneDimensionSegmentOverlap(a.x, b.x, c.x, d.x, &s->x, &e->x) && + isOneDimensionSegmentOverlap(a.y, b.y, c.y, d.y, &s->y, &e->y); + } + + return false; +} + +bool Vec2::isSegmentIntersect(const Vec2 &a, const Vec2 &b, const Vec2 &c, const Vec2 &d) { + float s; + float t; + + return (isLineIntersect(a, b, c, d, &s, &t) && (s >= 0.0F && s <= 1.0F && t >= 0.0F && t <= 1.0F)); +} + +Vec2 Vec2::getIntersectPoint(const Vec2 &a, const Vec2 &b, const Vec2 &c, const Vec2 &d) { + float s; + float t; + + if (isLineIntersect(a, b, c, d, &s, &t)) { + // Vec2 of intersection + Vec2 p; + p.x = a.x + s * (b.x - a.x); + p.y = a.y + s * (b.y - a.y); + return p; + } + + return Vec2::ZERO; +} + +const Vec2 Vec2::ZERO(0.0F, 0.0F); +const Vec2 Vec2::ONE(1.0F, 1.0F); +const Vec2 Vec2::UNIT_X(1.0F, 0.0F); +const Vec2 Vec2::UNIT_Y(0.0F, 1.0F); +const Vec2 Vec2::ANCHOR_MIDDLE(0.5F, 0.5F); +const Vec2 Vec2::ANCHOR_BOTTOM_LEFT(0.0F, 0.0F); +const Vec2 Vec2::ANCHOR_TOP_LEFT(0.0F, 1.0F); +const Vec2 Vec2::ANCHOR_BOTTOM_RIGHT(1.0F, 0.0F); +const Vec2 Vec2::ANCHOR_TOP_RIGHT(1.0F, 1.0F); +const Vec2 Vec2::ANCHOR_MIDDLE_RIGHT(1.0F, 0.5F); +const Vec2 Vec2::ANCHOR_MIDDLE_LEFT(0.0F, 0.5F); +const Vec2 Vec2::ANCHOR_MIDDLE_TOP(0.5F, 1.0F); +const Vec2 Vec2::ANCHOR_MIDDLE_BOTTOM(0.5F, 0.0F); + +NS_CC_MATH_END diff --git a/cocos/math/Vec2.h b/cocos/math/Vec2.h new file mode 100644 index 0000000..dcf5aba --- /dev/null +++ b/cocos/math/Vec2.h @@ -0,0 +1,703 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#pragma once + +#include +#include +#include +#include "math/Math.h" +#include "math/MathBase.h" + +NS_CC_MATH_BEGIN + +/** Clamp a value between from and to. + */ + +inline float clampf(float value, float minInclusive, float maxInclusive) { + if (minInclusive > maxInclusive) { + std::swap(minInclusive, maxInclusive); + } + return value < minInclusive ? minInclusive : value < maxInclusive ? value + : maxInclusive; +} + +class Mat4; + +/** + * Defines a 2-element floating point vector. + */ +class CC_DLL Vec2 { +public: + /** + * The x coordinate. + */ + float x{0.F}; + + /** + * The y coordinate. + */ + float y{0.F}; + + /** + * Constructs a new vector initialized to all zeros. + */ + Vec2(); + + /** + * Constructs a new vector initialized to the specified values. + * + * @param xx The x coordinate. + * @param yy The y coordinate. + */ + Vec2(float xx, float yy); + + /** + * Constructs a new vector from the values in the specified array. + * + * @param array An array containing the elements of the vector in the order x, y. + */ + explicit Vec2(const float *array); + + /** + * Constructs a vector that describes the direction between the specified points. + * + * @param p1 The first point. + * @param p2 The second point. + */ + Vec2(const Vec2 &p1, const Vec2 &p2); + + /** + * Constructs a new vector that is a copy of the specified vector. + * + * @param copy The vector to copy. + */ + Vec2(const Vec2 ©); + + /** + * Destructor. + */ + ~Vec2(); + + /** + * Indicates whether this vector contains all zeros. + * + * @return true if this vector contains all zeros, false otherwise. + */ + inline bool isZero() const; + + /** + * Indicates whether this vector contains all ones. + * + * @return true if this vector contains all ones, false otherwise. + */ + inline bool isOne() const; + + /** + * Returns the angle (in radians) between the specified vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * + * @return The angle between the two vectors (in radians). + */ + static float angle(const Vec2 &v1, const Vec2 &v2); + + /** + * Adds the elements of the specified vector to this one. + * + * @param v The vector to add. + */ + inline void add(const Vec2 &v); + + /** + * Adds the specified vectors and stores the result in dst. + * + * @param v1 The first vector. + * @param v2 The second vector. + * @param dst A vector to store the result in. + */ + static void add(const Vec2 &v1, const Vec2 &v2, Vec2 *dst); + + /** + * Clamps this vector within the specified range. + * + * @param min The minimum value. + * @param max The maximum value. + */ + void clamp(const Vec2 &min, const Vec2 &max); + + /** + * Clamps the specified vector within the specified range and returns it in dst. + * + * @param v The vector to clamp. + * @param min The minimum value. + * @param max The maximum value. + * @param dst A vector to store the result in. + */ + static void clamp(const Vec2 &v, const Vec2 &min, const Vec2 &max, Vec2 *dst); + + /** + * Returns the distance between this vector and v. + * + * @param v The other vector. + * + * @return The distance between this vector and v. + * + * @see distanceSquared + */ + float distance(const Vec2 &v) const; + + /** + * Returns the squared distance between this vector and v. + * + * When it is not necessary to get the exact distance between + * two vectors (for example, when simply comparing the + * distance between different vectors), it is advised to use + * this method instead of distance. + * + * @param v The other vector. + * + * @return The squared distance between this vector and v. + * + * @see distance + */ + inline float distanceSquared(const Vec2 &v) const; + + /** + * Returns the dot product of this vector and the specified vector. + * + * @param v The vector to compute the dot product with. + * + * @return The dot product. + */ + inline float dot(const Vec2 &v) const; + + /** + * Returns the dot product between the specified vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * + * @return The dot product between the vectors. + */ + static float dot(const Vec2 &v1, const Vec2 &v2); + + /** + * Computes the length of this vector. + * + * @return The length of the vector. + * + * @see lengthSquared + */ + float length() const; + + /** + * Returns the squared length of this vector. + * + * When it is not necessary to get the exact length of a + * vector (for example, when simply comparing the lengths of + * different vectors), it is advised to use this method + * instead of length. + * + * @return The squared length of the vector. + * + * @see length + */ + inline float lengthSquared() const; + + /** + * Negates this vector. + */ + inline void negate(); + + /** + * Normalizes this vector. + * + * This method normalizes this Vec2 so that it is of + * unit length (in other words, the length of the vector + * after calling this method will be 1.0f). If the vector + * already has unit length or if the length of the vector + * is zero, this method does nothing. + * + * @return This vector, after the normalization occurs. + */ + void normalize(); + + /** + Get the normalized vector. + */ + Vec2 getNormalized() const; + + /** + * Scales all elements of this vector by the specified value. + * + * @param scalar The scalar value. + */ + inline void scale(float scalar); + + /** + * Scales each element of this vector by the matching component of scale. + * + * @param scale The vector to scale by. + */ + inline void scale(const Vec2 &scale); + + /** + * Rotates this vector by angle (specified in radians) around the given point. + * + * @param point The point to rotate around. + * @param angle The angle to rotate by (in radians). + */ + void rotate(const Vec2 &point, float angle); + + /** + * Sets the elements of this vector to the specified values. + * + * @param xx The new x coordinate. + * @param yy The new y coordinate. + */ + inline void set(float xx, float yy); + + /** + * Sets the elements of this vector from the values in the specified array. + * + * @param array An array containing the elements of the vector in the order x, y. + */ + void set(const float *array); + + /** + * Sets the elements of this vector to those in the specified vector. + * + * @param v The vector to copy. + */ + inline void set(const Vec2 &v); + + /** + * Sets this vector to the directional vector between the specified points. + * + * @param p1 The first point. + * @param p2 The second point. + */ + inline void set(const Vec2 &p1, const Vec2 &p2); + + /** + * Sets the elements of this vector to zero. + */ + inline void setZero(); + + /** + * Subtracts this vector and the specified vector as (this - v) + * and stores the result in this vector. + * + * @param v The vector to subtract. + */ + inline void subtract(const Vec2 &v); + + /** + * Subtracts the specified vectors and stores the result in dst. + * The resulting vector is computed as (v1 - v2). + * + * @param v1 The first vector. + * @param v2 The second vector. + * @param dst The destination vector. + */ + static void subtract(const Vec2 &v1, const Vec2 &v2, Vec2 *dst); + + /** + * Updates this vector towards the given target using a smoothing function. + * The given response time determines the amount of smoothing (lag). A longer + * response time yields a smoother result and more lag. To force this vector to + * follow the target closely, provide a response time that is very small relative + * to the given elapsed time. + * + * @param target target value. + * @param elapsedTime elapsed time between calls. + * @param responseTime response time (in the same units as elapsedTime). + */ + inline void smooth(const Vec2 &target, float elapsedTime, float responseTime); + + /** + * Calculates the sum of this vector with the given vector. + * + * Note: this does not modify this vector. + * + * @param v The vector to add. + * @return The vector sum. + */ + inline const Vec2 operator+(const Vec2 &v) const; + + /** + * Adds the given vector to this vector. + * + * @param v The vector to add. + * @return This vector, after the addition occurs. + */ + inline Vec2 &operator+=(const Vec2 &v); + + /** + * Calculates the sum of this vector with the given vector. + * + * Note: this does not modify this vector. + * + * @param v The vector to add. + * @return The vector sum. + */ + inline const Vec2 operator-(const Vec2 &v) const; + + /** + * Subtracts the given vector from this vector. + * + * @param v The vector to subtract. + * @return This vector, after the subtraction occurs. + */ + inline Vec2 &operator-=(const Vec2 &v); + + /** + * Calculates the negation of this vector. + * + * Note: this does not modify this vector. + * + * @return The negation of this vector. + */ + inline const Vec2 operator-() const; + + /** + * Calculates the scalar product of this vector with the given value. + * + * Note: this does not modify this vector. + * + * @param s The value to scale by. + * @return The scaled vector. + */ + inline const Vec2 operator*(float s) const; + + /** + * Scales this vector by the given value. + * + * @param s The value to scale by. + * @return This vector, after the scale occurs. + */ + inline Vec2 &operator*=(float s); + + /** + * Returns the components of this vector divided by the given constant + * + * Note: this does not modify this vector. + * + * @param s the constant to divide this vector with + * @return a smaller vector + */ + inline const Vec2 operator/(float s) const; + + /** + * Determines if this vector is less than the given vector. + * + * @param v The vector to compare against. + * + * @return True if this vector is less than the given vector, false otherwise. + */ + inline bool operator<(const Vec2 &v) const; + + /** + * Determines if this vector is greater than the given vector. + * + * @param v The vector to compare against. + * + * @return True if this vector is greater than the given vector, false otherwise. + */ + inline bool operator>(const Vec2 &v) const; + + /** + * Determines if this vector is equal to the given vector. + * + * @param v The vector to compare against. + * + * @return True if this vector is equal to the given vector, false otherwise. + */ + inline bool operator==(const Vec2 &v) const; + + /** + * Determines if this vector is not equal to the given vector. + * + * @param v The vector to compare against. + * + * @return True if this vector is not equal to the given vector, false otherwise. + */ + inline bool operator!=(const Vec2 &v) const; + + /** + * Determines if this vector is approximately equal to the given vector. + */ + inline bool approxEquals(const Vec2 &v, float precision = CC_FLOAT_CMP_PRECISION) const { + return math::isEqualF(x, v.x, precision) && math::isEqualF(y, v.y, precision); + } + + inline void setPoint(float xx, float yy); + /** + * @js NA + */ + bool equals(const Vec2 &target) const; + + /** @returns if points have fuzzy equality which means equal with some degree of variance. + @since v2.1.4 + */ + bool fuzzyEquals(const Vec2 &b, float var) const; + + /** Calculates distance between point an origin + @return float + @since v2.1.4 + */ + inline float getLength() const { + return sqrtf(x * x + y * y); + }; + + /** Calculates the square length of a Vec2 (not calling sqrt() ) + @return float + @since v2.1.4 + */ + inline float getLengthSq() const { + return dot(*this); //x*x + y*y; + }; + + /** Calculates the square distance between two points (not calling sqrt() ) + @return float + @since v2.1.4 + */ + inline float getDistanceSq(const Vec2 &other) const { + return (*this - other).getLengthSq(); + }; + + /** Calculates the distance between two points + @return float + @since v2.1.4 + */ + inline float getDistance(const Vec2 &other) const { + return (*this - other).getLength(); + }; + + /** @returns the angle in radians between this vector and the x axis + @since v2.1.4 + */ + inline float getAngle() const { + return atan2f(y, x); + }; + + /** @returns the angle in radians between two vector directions + @since v2.1.4 + */ + float getAngle(const Vec2 &other) const; + + /** Calculates cross product of two points. + @return float + @since v2.1.4 + */ + inline float cross(const Vec2 &other) const { + return x * other.y - y * other.x; + }; + + /** Calculates perpendicular of v, rotated 90 degrees counter-clockwise -- cross(v, perp(v)) >= 0 + @return Vec2 + @since v2.1.4 + */ + inline Vec2 getPerp() const { + return Vec2(-y, x); + }; + + /** Calculates midpoint between two points. + @return Vec2 + @since v3.0 + */ + inline Vec2 getMidpoint(const Vec2 &other) const { + return Vec2((x + other.x) / 2.0F, (y + other.y) / 2.0F); + } + + /** Clamp a point between from and to. + @since v3.0 + */ + inline Vec2 getClampPoint(const Vec2 &minInclusive, const Vec2 &maxInclusive) const { + return Vec2(clampf(x, minInclusive.x, maxInclusive.x), clampf(y, minInclusive.y, maxInclusive.y)); + } + + /** Run a math operation function on each point component + * absf, floorf, ceilf, roundf + * any function that has the signature: float func(float); + * For example: let's try to take the floor of x,y + * p.compOp(floorf); + @since v3.0 + */ + inline Vec2 compOp(const std::function &function) const { + return Vec2(function(x), function(y)); + } + + /** Calculates perpendicular of v, rotated 90 degrees clockwise -- cross(v, rperp(v)) <= 0 + @return Vec2 + @since v2.1.4 + */ + inline Vec2 getRPerp() const { + return Vec2(y, -x); + }; + + /** Calculates the projection of this over other. + @return Vec2 + @since v2.1.4 + */ + inline Vec2 project(const Vec2 &other) const { + return other * (dot(other) / other.dot(other)); + }; + + /** Complex multiplication of two points ("rotates" two points). + @return Vec2 vector with an angle of this.getAngle() + other.getAngle(), + and a length of this.getLength() * other.getLength(). + @since v2.1.4 + */ + inline Vec2 rotate(const Vec2 &other) const { + return Vec2(x * other.x - y * other.y, x * other.y + y * other.x); + }; + + /** Unrotates two points. + @return Vec2 vector with an angle of this.getAngle() - other.getAngle(), + and a length of this.getLength() * other.getLength(). + @since v2.1.4 + */ + inline Vec2 unrotate(const Vec2 &other) const { + return Vec2(x * other.x + y * other.y, y * other.x - x * other.y); + }; + + /** Linear Interpolation between two points a and b + @returns + alpha == 0 ? a + alpha == 1 ? b + otherwise a value between a..b + @since v2.1.4 + */ + inline Vec2 lerp(const Vec2 &other, float alpha) const { + return *this * (1.F - alpha) + other * alpha; + }; + + /** Rotates a point counter clockwise by the angle around a pivot + @param pivot is the pivot, naturally + @param angle is the angle of rotation ccw in radians + @returns the rotated point + @since v2.1.4 + */ + Vec2 rotateByAngle(const Vec2 &pivot, float angle) const; + + static inline Vec2 forAngle(const float a) { + return Vec2(cosf(a), sinf(a)); + } + + /** A general line-line intersection test + @param a the start point for the first line L1 = (a - b) + @param b the end point for the first line L1 = (a - b) + @param c the start point for the second line L2 = (c - d) + @param d the end point for the second line L2 = (c - d) + @param s the range for a hitpoint in L1 (p = a + s*(b - a)) + @param t the range for a hitpoint in L2 (p = c + t*(d - c)) + @return whether these two lines intersects. + + Note that to truly test intersection for segments we have to make + sure that s & t lie within [0..1] and for rays, make sure s & t > 0 + the hit point is c + t * (d - c); + the hit point also is a + s * (b - a); + @since 3.0 + */ + static bool isLineIntersect(const Vec2 &a, const Vec2 &b, + const Vec2 &c, const Vec2 &d, + float *s = nullptr, float *t = nullptr); + + /** + returns true if Line a-b overlap with segment c-d + @since v3.0 + */ + static bool isLineOverlap(const Vec2 &a, const Vec2 &b, + const Vec2 &c, const Vec2 &d); + + /** + returns true if Line a-b parallel with segment c-d + @since v3.0 + */ + static bool isLineParallel(const Vec2 &a, const Vec2 &b, + const Vec2 &c, const Vec2 &d); + + /** + returns true if Segment a-b overlap with segment c-d + @since v3.0 + */ + static bool isSegmentOverlap(const Vec2 &a, const Vec2 &b, + const Vec2 &c, const Vec2 &d, + Vec2 *s = nullptr, Vec2 *e = nullptr); + + /** + returns true if Segment a-b intersects with segment c-d + @since v3.0 + */ + static bool isSegmentIntersect(const Vec2 &a, const Vec2 &b, const Vec2 &c, const Vec2 &d); + + /** + returns the intersection point of line a-b, c-d + @since v3.0 + */ + static Vec2 getIntersectPoint(const Vec2 &a, const Vec2 &b, const Vec2 &c, const Vec2 &d); + + /** equals to Vec2(0,0) */ + static const Vec2 ZERO; + /** equals to Vec2(1,1) */ + static const Vec2 ONE; + /** equals to Vec2(1,0) */ + static const Vec2 UNIT_X; + /** equals to Vec2(0,1) */ + static const Vec2 UNIT_Y; + /** equals to Vec2(0.5, 0.5) */ + static const Vec2 ANCHOR_MIDDLE; + /** equals to Vec2(0, 0) */ + static const Vec2 ANCHOR_BOTTOM_LEFT; + /** equals to Vec2(0, 1) */ + static const Vec2 ANCHOR_TOP_LEFT; + /** equals to Vec2(1, 0) */ + static const Vec2 ANCHOR_BOTTOM_RIGHT; + /** equals to Vec2(1, 1) */ + static const Vec2 ANCHOR_TOP_RIGHT; + /** equals to Vec2(1, 0.5) */ + static const Vec2 ANCHOR_MIDDLE_RIGHT; + /** equals to Vec2(0, 0.5) */ + static const Vec2 ANCHOR_MIDDLE_LEFT; + /** equals to Vec2(0.5, 1) */ + static const Vec2 ANCHOR_MIDDLE_TOP; + /** equals to Vec2(0.5, 0) */ + static const Vec2 ANCHOR_MIDDLE_BOTTOM; +}; + +/** + * Calculates the scalar product of the given vector with the given value. + * + * @param x The value to scale by. + * @param v The vector to scale. + * @return The scaled vector. + */ +inline const Vec2 operator*(float x, const Vec2 &v); + +using Point = Vec2; + +NS_CC_MATH_END + +#include "math/Vec2.inl" diff --git a/cocos/math/Vec2.inl b/cocos/math/Vec2.inl new file mode 100644 index 0000000..2eb9dfd --- /dev/null +++ b/cocos/math/Vec2.inl @@ -0,0 +1,199 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#include "math/Vec2.h" + +NS_CC_MATH_BEGIN + +inline Vec2::Vec2() +: x(0.0f), y(0.0f) { +} + +inline Vec2::Vec2(float xx, float yy) +: x(xx), y(yy) { +} + +inline Vec2::Vec2(const float* array) { + set(array); +} + +inline Vec2::Vec2(const Vec2& p1, const Vec2& p2) { + set(p1, p2); +} + +inline Vec2::Vec2(const Vec2& copy) { + set(copy); +} + +inline Vec2::~Vec2() { +} + +inline bool Vec2::isZero() const { + return x == 0.0f && y == 0.0f; +} + +bool Vec2::isOne() const { + return x == 1.0f && y == 1.0f; +} + +inline void Vec2::add(const Vec2& v) { + x += v.x; + y += v.y; +} + +inline float Vec2::distanceSquared(const Vec2& v) const { + float dx = v.x - x; + float dy = v.y - y; + return (dx * dx + dy * dy); +} + +inline float Vec2::dot(const Vec2& v) const { + return (x * v.x + y * v.y); +} + +inline float Vec2::lengthSquared() const { + return (x * x + y * y); +} + +inline void Vec2::negate() { + x = -x; + y = -y; +} + +inline void Vec2::scale(float scalar) { + x *= scalar; + y *= scalar; +} + +inline void Vec2::scale(const Vec2& scale) { + x *= scale.x; + y *= scale.y; +} + +inline void Vec2::set(float xx, float yy) { + this->x = xx; + this->y = yy; +} + +inline void Vec2::set(const Vec2& v) { + this->x = v.x; + this->y = v.y; +} + +inline void Vec2::set(const Vec2& p1, const Vec2& p2) { + x = p2.x - p1.x; + y = p2.y - p1.y; +} + +void Vec2::setZero() { + x = y = 0.0f; +} + +inline void Vec2::subtract(const Vec2& v) { + x -= v.x; + y -= v.y; +} + +inline void Vec2::smooth(const Vec2& target, float elapsedTime, float responseTime) { + if (elapsedTime > 0) { + *this += (target - *this) * (elapsedTime / (elapsedTime + responseTime)); + } +} + +inline const Vec2 Vec2::operator+(const Vec2& v) const { + Vec2 result(*this); + result.add(v); + return result; +} + +inline Vec2& Vec2::operator+=(const Vec2& v) { + add(v); + return *this; +} + +inline const Vec2 Vec2::operator-(const Vec2& v) const { + Vec2 result(*this); + result.subtract(v); + return result; +} + +inline Vec2& Vec2::operator-=(const Vec2& v) { + subtract(v); + return *this; +} + +inline const Vec2 Vec2::operator-() const { + Vec2 result(*this); + result.negate(); + return result; +} + +inline const Vec2 Vec2::operator*(float s) const { + Vec2 result(*this); + result.scale(s); + return result; +} + +inline Vec2& Vec2::operator*=(float s) { + scale(s); + return *this; +} + +inline const Vec2 Vec2::operator/(const float s) const { + const float inv = 1.0f / s; + return Vec2(this->x * inv, this->y * inv); +} + +inline bool Vec2::operator<(const Vec2& v) const { + if (x == v.x) { + return y < v.y; + } + return x < v.x; +} + +inline bool Vec2::operator>(const Vec2& v) const { + if (x == v.x) { + return y > v.y; + } + return x > v.x; +} + +inline bool Vec2::operator==(const Vec2& v) const { + return x == v.x && y == v.y; +} + +inline bool Vec2::operator!=(const Vec2& v) const { + return x != v.x || y != v.y; +} + +inline const Vec2 operator*(float x, const Vec2& v) { + Vec2 result(v); + result.scale(x); + return result; +} + +void Vec2::setPoint(float xx, float yy) { + this->x = xx; + this->y = yy; +} + +NS_CC_MATH_END diff --git a/cocos/math/Vec3.cpp b/cocos/math/Vec3.cpp new file mode 100644 index 0000000..dcd81c0 --- /dev/null +++ b/cocos/math/Vec3.cpp @@ -0,0 +1,403 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#include "math/Vec3.h" +#include "base/Macros.h" +#include "math/Mat3.h" +#include "math/Math.h" +#include "math/MathUtil.h" +#include "math/Quaternion.h" + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + #include +#endif + +#if (CC_PLATFORM == CC_PLATFORM_IOS) + #if defined(__arm64__) + #define USE_NEON64 + #define INCLUDE_NEON64 + #endif +#elif (CC_PLATFORM == CC_PLATFORM_ANDROID) + #if defined(__arm64__) || defined(__aarch64__) + #define USE_NEON64 + #define INCLUDE_NEON64 + #elif defined(__ARM_NEON__) + #define INCLUDE_NEON32 + #endif +#endif + +#if defined(USE_NEON64) || defined(USE_NEON32) || defined(INCLUDE_NEON32) + #include "enoki/array.h" + #ifndef ENOKI_ARM_NEON + #error "ENOKI_ARM_NEON isn't enabled!" + #endif +using SimdVec4 = enoki::Array; +#endif + +NS_CC_MATH_BEGIN + +Vec3::Vec3(float xx, float yy, float zz) +: x(xx), + y(yy), + z(zz) { +} + +Vec3::Vec3(const float *array) { + set(array); +} + +Vec3::Vec3(const Vec3 &p1, const Vec3 &p2) { + set(p1, p2); +} + +Vec3::Vec3(const Vec3 ©) { + set(copy); +} + +Vec3 Vec3::fromColor(unsigned int color) { + float components[3]; + int componentIndex = 0; + for (int i = 2; i >= 0; --i) { + auto component = (color >> i * 8) & 0x0000ff; + + components[componentIndex++] = static_cast(component) / 255.0F; + } + + Vec3 value(components); + return value; +} + +void Vec3::transformInverseRTS(const Vec3 &v, const Quaternion &r, const Vec3 &t, const Vec3 &s, Vec3 *out) { + CC_ASSERT(out); + const float x = v.x - t.x; + const float y = v.y - t.y; + const float z = v.z - t.z; + + const float ix = r.w * x - r.y * z + r.z * y; + const float iy = r.w * y - r.z * x + r.x * z; + const float iz = r.w * z - r.x * y + r.y * x; + const float iw = r.x * x + r.y * y + r.z * z; + out->x = (ix * r.w + iw * r.x + iy * r.z - iz * r.y) / s.x; + out->y = (iy * r.w + iw * r.y + iz * r.x - ix * r.z) / s.y; + out->z = (iz * r.w + iw * r.z + ix * r.y - iy * r.x) / s.z; +} + +float Vec3::angle(const Vec3 &v1, const Vec3 &v2) { + const auto magSqr1 = v1.x * v1.x + v1.y * v1.y + v1.z * v1.z; + const auto magSqr2 = v2.x * v2.x + v2.y * v2.y + v2.z * v2.z; + + if (magSqr1 == 0.0F || magSqr2 == 0.0F) { + return 0.0F; + } + + const auto dot = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; + auto cosine = dot / (sqrt(magSqr1 * magSqr2)); + cosine = clampf(cosine, -1.0, 1.0); + return acos(cosine); +} + +void Vec3::add(const Vec3 &v1, const Vec3 &v2, Vec3 *dst) { + CC_ASSERT(dst); + + dst->x = v1.x + v2.x; + dst->y = v1.y + v2.y; + dst->z = v1.z + v2.z; +} + +void Vec3::clamp(const Vec3 &min, const Vec3 &max) { + CC_ASSERT(!(min.x > max.x || min.y > max.y || min.z > max.z)); + + // Clamp the x value. + if (x < min.x) { + x = min.x; + } + if (x > max.x) { + x = max.x; + } + + // Clamp the y value. + if (y < min.y) { + y = min.y; + } + if (y > max.y) { + y = max.y; + } + + // Clamp the z value. + if (z < min.z) { + z = min.z; + } + if (z > max.z) { + z = max.z; + } +} + +void Vec3::clamp(const Vec3 &v, const Vec3 &min, const Vec3 &max, Vec3 *dst) { + CC_ASSERT(dst); + CC_ASSERT(!(min.x > max.x || min.y > max.y || min.z > max.z)); + + // Clamp the x value. + dst->x = v.x; + if (dst->x < min.x) { + dst->x = min.x; + } + if (dst->x > max.x) { + dst->x = max.x; + } + + // Clamp the y value. + dst->y = v.y; + if (dst->y < min.y) { + dst->y = min.y; + } + if (dst->y > max.y) { + dst->y = max.y; + } + + // Clamp the z value. + dst->z = v.z; + if (dst->z < min.z) { + dst->z = min.z; + } + if (dst->z > max.z) { + dst->z = max.z; + } +} + +void Vec3::cross(const Vec3 &v) { + cross(*this, v, this); +} + +void Vec3::cross(const Vec3 &v1, const Vec3 &v2, Vec3 *dst) { + dst->set( + v1.y * v2.z - v1.z * v2.y, + v1.z * v2.x - v1.x * v2.z, + v1.x * v2.y - v1.y * v2.x); +} + +void Vec3::multiply(const Vec3 &v) { + x *= v.x; + y *= v.y; + z *= v.z; +} + +void Vec3::multiply(const Vec3 &v1, const Vec3 &v2, Vec3 *dst) { + dst->x = v1.x * v2.x; + dst->y = v1.y * v2.y; + dst->z = v1.z * v2.z; +} + +void Vec3::transformMat3(const Vec3 &v, const Mat3 &m) { + const float ix = v.x; + const float iy = v.y; + const float iz = v.z; + x = ix * m.m[0] + iy * m.m[3] + iz * m.m[6]; + y = ix * m.m[1] + iy * m.m[4] + iz * m.m[7]; + z = ix * m.m[2] + iy * m.m[5] + iz * m.m[8]; +} + +void Vec3::transformMat4Neon(const Vec3 &v, const Mat4 &m) { +#if defined(USE_NEON64) || defined(USE_NEON32) || defined(INCLUDE_NEON32) + alignas(16) float tmpV0[4]; + + auto row0 = enoki::load_unaligned(&m.m[0]); + auto row1 = enoki::load_unaligned(&m.m[4]); + auto row2 = enoki::load_unaligned(&m.m[8]); + auto row3 = enoki::load_unaligned(&m.m[12]); + + row0 *= v.x; + row1 *= v.y; + row2 *= v.z; + + row0 = row0 + row1 + row2 + row3; + enoki::store(tmpV0, row0); + float rhw; + + if (CC_PREDICT_TRUE(math::isNotZeroF(tmpV0[3]))) { + rhw = 1.F / tmpV0[3]; + } else { + rhw = 1.F; + } + + SimdVec4 tmpV1{rhw}; + row0 *= tmpV1; + enoki::store(tmpV0, row0); + + x = tmpV0[0]; + y = tmpV0[1]; + z = tmpV0[2]; +#endif +} + +void Vec3::transformMat4C(const Vec3 &v, const Mat4 &m) { + alignas(16) float tmp[4] = {v.x, v.y, v.z, 1.0F}; + MathUtil::transformVec4(m.m, tmp, tmp); + float rhw = math::isNotZeroF(tmp[3]) ? 1.F / tmp[3] : 1.F; + x = tmp[0] * rhw; + y = tmp[1] * rhw; + z = tmp[2] * rhw; +} + +void Vec3::transformMat4(const Vec3 &v, const Mat4 &m) { +#if defined(USE_NEON64) + transformMat4Neon(v, m); +#elif defined(INCLUDE_NEON32) + if (CC_PREDICT_TRUE(MathUtil::isNeon32Enabled())) { + transformMat4Neon(v, m); + } else { + transformMat4C(v, m); + } +#else + transformMat4C(v, m); +#endif +} + +void Vec3::transformMat4(const Vec3 &v, const Mat4 &m, Vec3 *dst) { + dst->transformMat4(v, m); +} + +void Vec3::transformMat4Normal(const Vec3 &v, const Mat4 &m, Vec3 *dst) { + float x = v.x; + float y = v.y; + float z = v.z; + float rhw = m.m[3] * x + m.m[7] * y + m.m[11] * z; + rhw = (rhw != 0.0F ? 1.0F / rhw : 1.0F); + dst->x = (m.m[0] * x + m.m[4] * y + m.m[8] * z) * rhw; + dst->y = (m.m[1] * x + m.m[5] * y + m.m[9] * z) * rhw; + dst->z = (m.m[2] * x + m.m[6] * y + m.m[10] * z) * rhw; +} + +void Vec3::moveTowards(const Vec3 ¤t, const Vec3 &target, float maxStep, Vec3 *dst) { + const auto deltaX = target.x - current.x; + const auto deltaY = target.y - current.y; + const auto deltaZ = target.z - current.z; + + const auto distanceSqr = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ; + if (distanceSqr == 0.0F || (maxStep >= 0.0F && distanceSqr < maxStep * maxStep)) { + dst->set(target); + return; + } + + const auto distance = std::sqrt(distanceSqr); + const auto scale = maxStep / distance; + dst->set( + current.x + deltaX * scale, + current.y + deltaY * scale, + current.z + deltaZ * scale); +} + +void Vec3::transformQuat(const Quaternion &q) { + const float qx = q.x; + const float qy = q.y; + const float qz = q.z; + const float qw = q.w; + + // calculate quat * vec + const float ix = qw * x + qy * z - qz * y; + const float iy = qw * y + qz * x - qx * z; + const float iz = qw * z + qx * y - qy * x; + const float iw = -qx * x - qy * y - qz * z; + + // calculate result * inverse quat + x = ix * qw + iw * -qx + iy * -qz - iz * -qy; + y = iy * qw + iw * -qy + iz * -qx - ix * -qz; + z = iz * qw + iw * -qz + ix * -qy - iy * -qx; +} + +float Vec3::distance(const Vec3 &v) const { + const float dx = v.x - x; + const float dy = v.y - y; + const float dz = v.z - z; + + return std::sqrt(dx * dx + dy * dy + dz * dz); +} + +float Vec3::distanceSquared(const Vec3 &v) const { + const float dx = v.x - x; + const float dy = v.y - y; + const float dz = v.z - z; + + return (dx * dx + dy * dy + dz * dz); +} + +float Vec3::dot(const Vec3 &v) const { + return (x * v.x + y * v.y + z * v.z); +} + +float Vec3::dot(const Vec3 &v1, const Vec3 &v2) { + return (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z); +} + +void Vec3::normalize() { + float len = x * x + y * y + z * z; + if (len > 0.0F) { + len = 1.0F / std::sqrt(len); + x *= len; + y *= len; + z *= len; + } +} + +Vec3 Vec3::getNormalized() const { + Vec3 v(*this); + v.normalize(); + return v; +} + +void Vec3::subtract(const Vec3 &v1, const Vec3 &v2, Vec3 *dst) { + CC_ASSERT(dst); + + dst->x = v1.x - v2.x; + dst->y = v1.y - v2.y; + dst->z = v1.z - v2.z; +} + +void Vec3::max(const Vec3 &v1, const Vec3 &v2, Vec3 *dst) { + CC_ASSERT(dst); + + dst->x = std::fmaxf(v1.x, v2.x); + dst->y = std::fmaxf(v1.y, v2.y); + dst->z = std::fmaxf(v1.z, v2.z); +} + +void Vec3::min(const Vec3 &v1, const Vec3 &v2, Vec3 *dst) { + CC_ASSERT(dst); + + dst->x = std::fminf(v1.x, v2.x); + dst->y = std::fminf(v1.y, v2.y); + dst->z = std::fminf(v1.z, v2.z); +} + +void Vec3::smooth(const Vec3 &target, float elapsedTime, float responseTime) { + if (elapsedTime > 0.0F) { + *this += (target - *this) * (elapsedTime / (elapsedTime + responseTime)); + } +} + +const Vec3 Vec3::ZERO(0.0F, 0.0F, 0.0F); +const Vec3 Vec3::ONE(1.0F, 1.0F, 1.0F); +const Vec3 Vec3::UNIT_X(1.0F, 0.0F, 0.0F); +const Vec3 Vec3::UNIT_Y(0.0F, 1.0F, 0.0F); +const Vec3 Vec3::UNIT_Z(0.0F, 0.0F, 1.0F); +const Vec3 Vec3::FORWARD(0.0F, 0.0F, -1.0F); + +NS_CC_MATH_END diff --git a/cocos/math/Vec3.h b/cocos/math/Vec3.h new file mode 100644 index 0000000..a8805b5 --- /dev/null +++ b/cocos/math/Vec3.h @@ -0,0 +1,655 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#pragma once + +#include +#include "math/Math.h" +#include "math/MathBase.h" + +/** + * @addtogroup base + * @{ + */ + +NS_CC_MATH_BEGIN + +class Mat4; +class Quaternion; +class Mat3; + +/** + * Defines a 3-element floating point vector. + * + * When using a vector to represent a surface normal, + * the vector should typically be normalized. + * Other uses of directional vectors may wish to leave + * the magnitude of the vector intact. When used as a point, + * the elements of the vector represent a position in 3D space. + */ +class CC_DLL Vec3 { +public: + /** + * The x-coordinate. + */ + float x{}; + + /** + * The y-coordinate. + */ + float y{}; + + /** + * The z-coordinate. + */ + float z{}; + + /** + * Constructs a new vector initialized to all zeros. + */ + Vec3() = default; + + /** + * Constructs a new vector initialized to the specified values. + * + * @param xx The x coordinate. + * @param yy The y coordinate. + * @param zz The z coordinate. + */ + Vec3(float xx, float yy, float zz); + + /** + * Constructs a new vector from the values in the specified array. + * + * @param array An array containing the elements of the vector in the order x, y, z. + */ + explicit Vec3(const float *array); + + /** + * Constructs a vector that describes the direction between the specified points. + * + * @param p1 The first point. + * @param p2 The second point. + */ + Vec3(const Vec3 &p1, const Vec3 &p2); + + /** + * Constructs a new vector that is a copy of the specified vector. + * + * @param copy The vector to copy. + */ + Vec3(const Vec3 ©); + + /** + * Creates a new vector from an integer interpreted as an RGB value. + * E.g. 0xff0000 represents red or the vector (1, 0, 0). + * + * @param color The integer to interpret as an RGB value. + * + * @return A vector corresponding to the interpreted RGB color. + */ + static Vec3 fromColor(unsigned int color); + + /** + * Destructor. + */ + ~Vec3() = default; + + /** + * Indicates whether this vector contains all zeros. + * + * @return true if this vector contains all zeros, false otherwise. + */ + inline bool isZero() const; + + /** + * Indicates whether this vector contains all ones. + * + * @return true if this vector contains all ones, false otherwise. + */ + inline bool isOne() const; + + /** + * Returns the angle (in radians) between the specified vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * + * @return The angle between the two vectors (in radians). + */ + static float angle(const Vec3 &v1, const Vec3 &v2); + + /** + * Transforms the current vector with given scale, rotation and translation in reverse order + */ + static void transformInverseRTS(const Vec3 &v, const Quaternion &r, const Vec3 &t, const Vec3 &s, Vec3 *out); + + /** + * Adds the elements of the specified vector to this one. + * + * @param v The vector to add. + */ + inline void add(const Vec3 &v); + + /** + * Adds the elements of this vector to the specified values. + * + * @param xx The add x coordinate. + * @param yy The add y coordinate. + * @param zz The add z coordinate. + */ + inline void add(float xx, float yy, float zz); + + /** + * Adds the specified vectors and stores the result in dst. + * + * @param v1 The first vector. + * @param v2 The second vector. + * @param dst A vector to store the result in. + */ + static void add(const Vec3 &v1, const Vec3 &v2, Vec3 *dst); + + /** + * Clamps this vector within the specified range. + * + * @param min The minimum value. + * @param max The maximum value. + */ + void clamp(const Vec3 &min, const Vec3 &max); + + /** + * Clamps the specified vector within the specified range and returns it in dst. + * + * @param v The vector to clamp. + * @param min The minimum value. + * @param max The maximum value. + * @param dst A vector to store the result in. + */ + static void clamp(const Vec3 &v, const Vec3 &min, const Vec3 &max, Vec3 *dst); + + /** + * Sets this vector to the cross product between itself and the specified vector. + * + * @param v The vector to compute the cross product with. + */ + void cross(const Vec3 &v); + + /** + * Computes the cross product of the specified vectors and stores the result in dst. + * + * @param v1 The first vector. + * @param v2 The second vector. + * @param dst A vector to store the result in. + */ + static void cross(const Vec3 &v1, const Vec3 &v2, Vec3 *dst); + + /** + * Multiply the elements of the specified vector to this one. + * + * @param v The vector to multiply. + */ + void multiply(const Vec3 &v); + + /** + * Multiply the specified vectors and stores the result in dst. + * + * @param v1 The first vector. + * @param v2 The second vector. + * @param dst A vector to store the result in. + */ + static void multiply(const Vec3 &v1, const Vec3 &v2, Vec3 *dst); + + /** + * Transforms this vector by the specified Mat3 and stores the result in this vector. + * + * @param v The Vec3 to transform. + * @param m The matrix. + */ + void transformMat3(const Vec3 &v, const Mat3 &m); + + /** + * Transforms the input vector by the specified Mat4 and stores the result in this vector. + * + * @param v The Vec3 to transform. + * @param m The matrix. + */ + void transformMat4(const Vec3 &v, const Mat4 &m); + + /** + * Transforms this vector by the specified Mat4 and stores the result in this vector. + * @param m The matrix. + */ + inline void transformMat4(const Mat4 &m) { + transformMat4(*this, m); + } + + /** + * Transforms vector v by the specified Mat4 and stores the result in dst vector. + * @zh 向量与四维矩阵乘法,默认向量第四位为 1。 + * @param v The Vec3 to transform. + * @param m The matrix. + * @param dst The destination vector + */ + static void transformMat4(const Vec3 &v, const Mat4 &m, Vec3 *dst); + + /** + * @en Vector and fourth order matrix multiplication, will complete the vector with a fourth element as one + * @zh 向量与四维矩阵乘法,默认向量第四位为 0。 + */ + static void transformMat4Normal(const Vec3 &v, const Mat4 &m, Vec3 *dst); + + /** + * @en Calculates a new position from current to target no more than `maxStep` distance. + * @zh 计算一个新位置从当前位置移动不超过 `maxStep` 距离到目标位置。 + * @param current current position + * @param target target position + * @param maxStep maximum moving distance + */ + static void moveTowards(const Vec3 ¤t, const Vec3 &target, float maxStep, Vec3 *dst); + + /** + * Transforms this vector by the specified quaternion and stores the result in this vector. + * + * @param q The quaternion to multiply. + */ + void transformQuat(const Quaternion &q); + + /** + * Returns the distance between this vector and v. + * + * @param v The other vector. + * + * @return The distance between this vector and v. + * + * @see distanceSquared + */ + float distance(const Vec3 &v) const; + + /** + * Returns the squared distance between this vector and v. + * + * When it is not necessary to get the exact distance between + * two vectors (for example, when simply comparing the + * distance between different vectors), it is advised to use + * this method instead of distance. + * + * @param v The other vector. + * + * @return The squared distance between this vector and v. + * + * @see distance + */ + float distanceSquared(const Vec3 &v) const; + + /** + * Returns the dot product of this vector and the specified vector. + * + * @param v The vector to compute the dot product with. + * + * @return The dot product. + */ + float dot(const Vec3 &v) const; + + /** + * Returns the dot product between the specified vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * + * @return The dot product between the vectors. + */ + static float dot(const Vec3 &v1, const Vec3 &v2); + + /** + * Computes the length of this vector. + * + * @return The length of the vector. + * + * @see lengthSquared + */ + inline float length() const; + + /** + * Returns the squared length of this vector. + * + * When it is not necessary to get the exact length of a + * vector (for example, when simply comparing the lengths of + * different vectors), it is advised to use this method + * instead of length. + * + * @return The squared length of the vector. + * + * @see length + */ + inline float lengthSquared() const; + + /** + * Negates this vector. + */ + inline void negate(); + + /** + * Normalizes this vector. + * + * This method normalizes this Vec3 so that it is of + * unit length (in other words, the length of the vector + * after calling this method will be 1.0f). If the vector + * already has unit length or if the length of the vector + * is zero, this method does nothing. + */ + void normalize(); + + /** + * Get the normalized vector. + * + * @return normalized vector. + */ + Vec3 getNormalized() const; + + /** + * Scales all elements of this vector by the specified value. + * + * @param scalar The scalar value. + */ + inline void scale(float scalar); + + /** + * Sets the elements of this vector to the specified values. + * + * @param xx The new x coordinate. + * @param yy The new y coordinate. + * @param zz The new z coordinate. + */ + inline void set(float xx, float yy, float zz); + + /** + * Sets the elements of this vector from the values in the specified array. + * + * @param array An array containing the elements of the vector in the order x, y, z. + */ + inline void set(const float *array); + + /** + * Sets the elements of this vector to those in the specified vector. + * + * @param v The vector to copy. + */ + inline void set(const Vec3 &v); + + /** + * Sets this vector to the directional vector between the specified points. + * + * @param p1 The vector to subtract. + * @param p2 The vector to subtracted. + */ + inline void set(const Vec3 &p1, const Vec3 &p2); + + /** + * Sets the elements of this vector to zero. + */ + inline void setZero(); + + /** + * Subtracts this vector and the specified vector as (this - v) + * and stores the result in this vector. + * + * @param v The vector to subtract. + */ + inline void subtract(const Vec3 &v); + + /** + * Subtracts the specified vectors and stores the result in dst. + * The resulting vector is computed as (v1 - v2). + * + * @param v1 The first vector. + * @param v2 The second vector. + * @param dst The destination vector. + */ + static void subtract(const Vec3 &v1, const Vec3 &v2, Vec3 *dst); + + /** + * Get the maximum value in the vector + * + * @param v1 To be compared vector. + * @param v2 To be compared vector. + * @param dst The destination vector. + */ + static void max(const Vec3 &v1, const Vec3 &v2, Vec3 *dst); + + /** + * Get the minimum value in the vector + * + * @param v1 To be compared vector. + * @param v2 To be compared vector. + * @param dst The destination vector. + */ + static void min(const Vec3 &v1, const Vec3 &v2, Vec3 *dst); + + /** + * Updates this vector towards the given target using a smoothing function. + * The given response time determines the amount of smoothing (lag). A longer + * response time yields a smoother result and more lag. To force this vector to + * follow the target closely, provide a response time that is very small relative + * to the given elapsed time. + * + * @param target target value. + * @param elapsedTime elapsed time between calls. + * @param responseTime response time (in the same units as elapsedTime). + */ + void smooth(const Vec3 &target, float elapsedTime, float responseTime); + + /** + * Linear interpolation between two vectors A and B by alpha which + * is in the range [0,1] + */ + inline Vec3 lerp(const Vec3 &target, float alpha) const; + + /** + * Calculates the sum of this vector with the given vector. + * + * Note: this does not modify this vector. + * + * @param v The vector to add. + * @return The vector sum. + */ + inline const Vec3 operator+(const Vec3 &v) const; + + /** + * Adds the given vector to this vector. + * + * @param v The vector to add. + * @return This vector, after the addition occurs. + */ + inline Vec3 &operator+=(const Vec3 &v); + + /** + * Calculates the difference of this vector with the given vector. + * + * Note: this does not modify this vector. + * + * @param v The vector to subtract. + * @return The vector difference. + */ + inline const Vec3 operator-(const Vec3 &v) const; + + /** + * Subtracts the given vector from this vector. + * + * @param v The vector to subtract. + * @return This vector, after the subtraction occurs. + */ + inline Vec3 &operator-=(const Vec3 &v); + + /** + * Calculates the negation of this vector. + * + * Note: this does not modify this vector. + * + * @return The negation of this vector. + */ + inline const Vec3 operator-() const; + + /** + * Calculates the scalar product of this vector with the given value. + * + * Note: this does not modify this vector. + * + * @param s The value to scale by. + * @return The scaled vector. + */ + inline const Vec3 operator*(float s) const; + + /** + * Multiply with a vector. + * + * @param rhs The value to scale by. + * @return The scaled vector. + */ + inline Vec3 operator*(const Vec3 &rhs) const; + + /** + * Scales this vector by the given value. + * + * @param s The value to scale by. + * @return This vector, after the scale occurs. + */ + inline Vec3 &operator*=(float s); + + /** + * Returns the components of this vector divided by the given constant + * + * Note: this does not modify this vector. + * + * @param s the constant to divide this vector with + * @return a smaller vector + */ + inline const Vec3 operator/(float s) const; + + /** + * Divide by a vector. + * + * Note: this does not modify this vector. + * + * @param rhs the vector to divide this vector with + * @return a vector + */ + inline Vec3 operator/(const Vec3 &rhs) const; + + /** + * Returns true if the vector's scalar components are all greater + * that the ones of the vector it is compared against. + * + * @param rhs Compare the size of two vectors + * @return bool + */ + inline bool operator<(const Vec3 &rhs) const { + return x < rhs.x && y < rhs.y && z < rhs.z; + } + + inline bool operator<=(const Vec3 &rhs) const { + return x <= rhs.x && y <= rhs.y && z <= rhs.z; + } + + /** + * Returns true if the vector's scalar components are all smaller + * that the ones of the vector it is compared against. + * + * @param rhs Compare the size of two vectors + * @return bool + */ + inline bool operator>(const Vec3 &rhs) const { + return x > rhs.x && y > rhs.y && z > rhs.z; + } + + inline bool operator>=(const Vec3 &rhs) const { + return x >= rhs.x && y >= rhs.y && z >= rhs.z; + } + + /** + * Determines if this vector is equal to the given vector. + * + * @param v The vector to compare against. + * + * @return True if this vector is equal to the given vector, false otherwise. + */ + inline bool operator==(const Vec3 &v) const; + + /** + * Assign from another vector. + * + * @param rhs the vector to divide this vector with + * + * @return a vector + */ + inline Vec3 &operator=(const Vec3 &rhs) noexcept = default; + + /** + * Determines if this vector is not equal to the given vector. + * + * @param v The vector to compare against. + * + * @return True if this vector is not equal to the given vector, false otherwise. + */ + inline bool operator!=(const Vec3 &v) const; + + /** + * Determines if this vector is approximately equal to the given vector. + */ + inline bool approxEquals(const Vec3 &v, float precision = CC_FLOAT_CMP_PRECISION) const { + return math::isEqualF(x, v.x, precision) && math::isEqualF(y, v.y, precision) && math::isEqualF(z, v.z, precision); + } + + /** equals to Vec3(0,0,0) */ + static const Vec3 ZERO; + /** equals to Vec3(1,1,1) */ + static const Vec3 ONE; + /** equals to Vec3(1,0,0) */ + static const Vec3 UNIT_X; + /** equals to Vec3(0,1,0) */ + static const Vec3 UNIT_Y; + /** equals to Vec3(0,0,1) */ + static const Vec3 UNIT_Z; + /** equals to Vec3(0,0,-1) */ + static const Vec3 FORWARD; + +private: + void transformMat4C(const Vec3 &v, const Mat4 &m); + void transformMat4Neon(const Vec3 &v, const Mat4 &m); +}; + +/** + * Calculates the scalar product of the given vector with the given value. + * + * @param x The value to scale by. + * @param v The vector to scale. + * @return The scaled vector. + */ +inline const Vec3 operator*(float x, const Vec3 &v); + +//typedef Vec3 Point3; + +NS_CC_MATH_END +/** + end of base group + @} + */ +#include "math/Vec3.inl" diff --git a/cocos/math/Vec3.inl b/cocos/math/Vec3.inl new file mode 100644 index 0000000..b2b4f1b --- /dev/null +++ b/cocos/math/Vec3.inl @@ -0,0 +1,176 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#pragma once +#include +#include "math/Mat4.h" +#include "math/Vec3.h" + +NS_CC_MATH_BEGIN + +inline bool Vec3::isZero() const { + return x == 0.0f && y == 0.0f && z == 0.0f; +} + +inline bool Vec3::isOne() const { + return x == 1.0f && y == 1.0f && z == 1.0f; +} + +inline void Vec3::add(const Vec3& v) { + x += v.x; + y += v.y; + z += v.z; +} + +inline void Vec3::add(float xx, float yy, float zz) { + x += xx; + y += yy; + z += zz; +} + +inline float Vec3::length() const { + return std::sqrt(x * x + y * y + z * z); +} + +inline float Vec3::lengthSquared() const { + return (x * x + y * y + z * z); +} + +inline void Vec3::negate() { + x = -x; + y = -y; + z = -z; +} + +inline void Vec3::scale(float scalar) { + x *= scalar; + y *= scalar; + z *= scalar; +} + +inline Vec3 Vec3::lerp(const Vec3& target, float alpha) const { + return *this * (1.f - alpha) + target * alpha; +} + +inline void Vec3::set(float xx, float yy, float zz) { + this->x = xx; + this->y = yy; + this->z = zz; +} + +inline void Vec3::set(const float* array) { + CC_ASSERT(array); + + x = array[0]; + y = array[1]; + z = array[2]; +} + +inline void Vec3::set(const Vec3& v) { + this->x = v.x; + this->y = v.y; + this->z = v.z; +} + +inline void Vec3::set(const Vec3& p1, const Vec3& p2) { + x = p2.x - p1.x; + y = p2.y - p1.y; + z = p2.z - p1.z; +} + +inline void Vec3::setZero() { + x = y = z = 0.0f; +} + +inline void Vec3::subtract(const Vec3& v) { + x -= v.x; + y -= v.y; + z -= v.z; +} + +inline const Vec3 Vec3::operator+(const Vec3& v) const { + Vec3 result(*this); + result.add(v); + return result; +} + +inline Vec3& Vec3::operator+=(const Vec3& v) { + add(v); + return *this; +} + +inline const Vec3 Vec3::operator-(const Vec3& v) const { + Vec3 result(*this); + result.subtract(v); + return result; +} + +inline Vec3& Vec3::operator-=(const Vec3& v) { + subtract(v); + return *this; +} + +inline const Vec3 Vec3::operator-() const { + Vec3 result(*this); + result.negate(); + return result; +} + +inline const Vec3 Vec3::operator*(float s) const { + Vec3 result(*this); + result.scale(s); + return result; +} + +inline Vec3 Vec3::operator*(const Vec3& rhs) const { + return Vec3(x * rhs.x, y * rhs.y, z * rhs.z); +} + +inline Vec3& Vec3::operator*=(float s) { + scale(s); + return *this; +} + +inline const Vec3 Vec3::operator/(const float s) const { + const float inv = 1.0F / s; + return Vec3(this->x * inv, this->y * inv, this->z * inv); +} + +inline Vec3 Vec3::operator/(const Vec3& rhs) const { + return Vec3(x / rhs.x, y / rhs.y, z / rhs.z); +} + +inline bool Vec3::operator==(const Vec3& v) const { + return x == v.x && y == v.y && z == v.z; +} + +inline bool Vec3::operator!=(const Vec3& v) const { + return x != v.x || y != v.y || z != v.z; +} + +inline const Vec3 operator*(float x, const Vec3& v) { + Vec3 result(v); + result.scale(x); + return result; +} + +NS_CC_MATH_END diff --git a/cocos/math/Vec4.cpp b/cocos/math/Vec4.cpp new file mode 100644 index 0000000..a473280 --- /dev/null +++ b/cocos/math/Vec4.cpp @@ -0,0 +1,342 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#include "math/Vec4.h" + +#include +#include "base/Macros.h" +#include "math/MathUtil.h" + +#if CC_PLATFORM != CC_PLATFORM_EMSCRIPTEN + #include "base/std/hash/hash.h" +#endif + +NS_CC_MATH_BEGIN + +Vec4::Vec4() +: x(0.0F), + y(0.0F), + z(0.0F), + w(0.0F) { +} + +Vec4::Vec4(float xx, float yy, float zz, float ww) +: x(xx), + y(yy), + z(zz), + w(ww) { +} + +Vec4::Vec4(const float *src) { + set(src); +} + +Vec4::Vec4(const Vec4 &p1, const Vec4 &p2) { + set(p1, p2); +} + +Vec4::Vec4(const Vec4 ©) { + set(copy); +} + +Vec4 Vec4::fromColor(unsigned int color) { + float components[4]; + int componentIndex = 0; + for (int i = 3; i >= 0; --i) { + uint32_t component = (color >> i * 8) & 0x000000ff; + + components[componentIndex++] = static_cast(component) / 255.0F; + } + + Vec4 value(components); + return value; +} + +bool Vec4::isZero() const { + return x == 0.0F && y == 0.0F && z == 0.0F && w == 0.0F; +} + +bool Vec4::isOne() const { + return x == 1.0F && y == 1.0F && z == 1.0F && w == 1.0F; +} + +float Vec4::angle(const Vec4 &v1, const Vec4 &v2) { + const float dx = (v1.y * v2.z - v1.z * v2.y); + const float dy = (v1.z * v2.x - v1.x * v2.z); + const float dz = (v1.x * v2.y - v1.y * v2.x); + const float dotVal = (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z); + + return std::atan2(std::sqrt(dx * dx + dy * dy + dz * dz) + MATH_FLOAT_SMALL, dotVal); +} + +void Vec4::add(const Vec4 &v) { + x += v.x; + y += v.y; + z += v.z; + w += v.w; +} + +void Vec4::add(const Vec4 &v1, const Vec4 &v2, Vec4 *dst) { + CC_ASSERT(dst); + + dst->x = v1.x + v2.x; + dst->y = v1.y + v2.y; + dst->z = v1.z + v2.z; + dst->w = v1.w + v2.w; +} + +void Vec4::clamp(const Vec4 &min, const Vec4 &max) { + CC_ASSERT(!(min.x > max.x || min.y > max.y || min.z > max.z || min.w > max.w)); + + // Clamp the x value. + if (x < min.x) { + x = min.x; + } + if (x > max.x) { + x = max.x; + } + + // Clamp the y value. + if (y < min.y) { + y = min.y; + } + if (y > max.y) { + y = max.y; + } + + // Clamp the z value. + if (z < min.z) { + z = min.z; + } + if (z > max.z) { + z = max.z; + } + + // Clamp the z value. + if (w < min.w) { + w = min.w; + } + if (w > max.w) { + w = max.w; + } +} + +void Vec4::clamp(const Vec4 &v, const Vec4 &min, const Vec4 &max, Vec4 *dst) { + CC_ASSERT(dst); + CC_ASSERT(!(min.x > max.x || min.y > max.y || min.z > max.z || min.w > max.w)); + + // Clamp the x value. + dst->x = v.x; + if (dst->x < min.x) { + dst->x = min.x; + } + if (dst->x > max.x) { + dst->x = max.x; + } + + // Clamp the y value. + dst->y = v.y; + if (dst->y < min.y) { + dst->y = min.y; + } + if (dst->y > max.y) { + dst->y = max.y; + } + + // Clamp the z value. + dst->z = v.z; + if (dst->z < min.z) { + dst->z = min.z; + } + if (dst->z > max.z) { + dst->z = max.z; + } + + // Clamp the w value. + dst->w = v.w; + if (dst->w < min.w) { + dst->w = min.w; + } + if (dst->w > max.w) { + dst->w = max.w; + } +} + +float Vec4::distance(const Vec4 &v) const { + float dx = v.x - x; + float dy = v.y - y; + float dz = v.z - z; + float dw = v.w - w; + + return std::sqrt(dx * dx + dy * dy + dz * dz + dw * dw); +} + +float Vec4::distanceSquared(const Vec4 &v) const { + float dx = v.x - x; + float dy = v.y - y; + float dz = v.z - z; + float dw = v.w - w; + + return (dx * dx + dy * dy + dz * dz + dw * dw); +} + +float Vec4::dot(const Vec4 &v) const { + return (x * v.x + y * v.y + z * v.z + w * v.w); +} + +float Vec4::dot(const Vec4 &v1, const Vec4 &v2) { + return (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z + v1.w * v2.w); +} + +float Vec4::length() const { + return std::sqrt(x * x + y * y + z * z + w * w); +} + +float Vec4::lengthSquared() const { + return (x * x + y * y + z * z + w * w); +} + +void Vec4::negate() { + x = -x; + y = -y; + z = -z; + w = -w; +} + +/** +* The inverse value of this vector. +* +* This method set each component to its inverse value, zero +* will become infinity. +*/ +void Vec4::inverse(const Vec4 &v, Vec4 *dst) { + CC_ASSERT(dst); + + dst->x = 1.0F / v.x; + dst->y = 1.0F / v.y; + dst->z = 1.0F / v.z; + dst->w = 1.0F / v.w; +} + +void Vec4::normalize() { + float n = x * x + y * y + z * z + w * w; + // Already normalized. + if (n == 1.0F) { + return; + } + + n = std::sqrt(n); + // Too close to zero. + if (n < MATH_TOLERANCE) { + return; + } + + n = 1.0F / n; + x *= n; + y *= n; + z *= n; + w *= n; +} + +Vec4 Vec4::getNormalized() const { + Vec4 v(*this); + v.normalize(); + return v; +} + +void Vec4::scale(float scalar) { + x *= scalar; + y *= scalar; + z *= scalar; + w *= scalar; +} + +void Vec4::set(float xx, float yy, float zz, float ww) { + this->x = xx; + this->y = yy; + this->z = zz; + this->w = ww; +} + +void Vec4::set(const float *array) { + CC_ASSERT(array); + + x = array[0]; + y = array[1]; + z = array[2]; + w = array[3]; +} + +void Vec4::set(const Vec4 &v) { + this->x = v.x; + this->y = v.y; + this->z = v.z; + this->w = v.w; +} + +void Vec4::set(const Vec4 &p1, const Vec4 &p2) { + x = p2.x - p1.x; + y = p2.y - p1.y; + z = p2.z - p1.z; + w = p2.w - p1.w; +} + +void Vec4::subtract(const Vec4 &v) { + x -= v.x; + y -= v.y; + z -= v.z; + w -= v.w; +} + +void Vec4::subtract(const Vec4 &v1, const Vec4 &v2, Vec4 *dst) { + CC_ASSERT(dst); + + dst->x = v1.x - v2.x; + dst->y = v1.y - v2.y; + dst->z = v1.z - v2.z; + dst->w = v1.w - v2.w; +} + +void Vec4::lerp(const Vec4 &a, const Vec4 &b, float t, Vec4 *dst) { + CC_ASSERT(dst); + dst->x = a.x + t * (b.x - a.x); + dst->y = a.y + t * (b.y - a.y); + dst->z = a.z + t * (b.z - a.z); + dst->w = a.w + t * (b.w - a.w); +} + +const Vec4 Vec4::ZERO = Vec4(0.0F, 0.0F, 0.0F, 0.0F); +const Vec4 Vec4::ONE = Vec4(1.0F, 1.0F, 1.0F, 1.0F); +const Vec4 Vec4::UNIT_X = Vec4(1.0F, 0.0F, 0.0F, 0.0F); +const Vec4 Vec4::UNIT_Y = Vec4(0.0F, 1.0F, 0.0F, 0.0F); +const Vec4 Vec4::UNIT_Z = Vec4(0.0F, 0.0F, 1.0F, 0.0F); +const Vec4 Vec4::UNIT_W = Vec4(0.0F, 0.0F, 0.0F, 1.0F); + +#if CC_PLATFORM != CC_PLATFORM_EMSCRIPTEN +template <> +ccstd::hash_t Hasher::operator()(const Vec4 &v) const { + return ccstd::hash_range(reinterpret_cast(&v.x), + reinterpret_cast(&v.x + 4)); +} +#endif + +NS_CC_MATH_END diff --git a/cocos/math/Vec4.h b/cocos/math/Vec4.h new file mode 100644 index 0000000..f87a3a9 --- /dev/null +++ b/cocos/math/Vec4.h @@ -0,0 +1,483 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#pragma once + +#undef __SSE__ +#ifdef __SSE__ + #include +#endif + +#include "math/Math.h" +#include "math/MathBase.h" + +/** + * @addtogroup base + * @{ + */ + +NS_CC_MATH_BEGIN + +class Mat4; + +/** + * Defines 4-element floating point vector. + */ +class CC_DLL Vec4 { +public: +#ifdef __SSE__ + union { + struct { + float x; + float y; + float z; + float w; + }; + __m128 v; + }; +#else + /** + * The x-coordinate. + */ + float x; + + /** + * The y-coordinate. + */ + float y; + + /** + * The z-coordinate. + */ + float z; + + /** + * The w-coordinate. + */ + float w; +#endif + /** + * Constructs a new vector initialized to all zeros. + */ + Vec4(); + + /** + * Constructs a new vector initialized to the specified values. + * + * @param xx The x coordinate. + * @param yy The y coordinate. + * @param zz The z coordinate. + * @param ww The w coordinate. + */ + Vec4(float xx, float yy, float zz, float ww); + + /** + * Constructs a new vector from the values in the specified array. + * + * @param array An array containing the elements of the vector in the order x, y, z, w. + */ + explicit Vec4(const float *src); + + /** + * Constructs a vector that describes the direction between the specified points. + * + * @param p1 The first point. + * @param p2 The second point. + */ + Vec4(const Vec4 &p1, const Vec4 &p2); + + /** + * Constructor. + * + * Creates a new vector that is a copy of the specified vector. + * + * @param copy The vector to copy. + */ + Vec4(const Vec4 ©); + + /** + * Creates a new vector from an integer interpreted as an RGBA value. + * E.g. 0xff0000ff represents opaque red or the vector (1, 0, 0, 1). + * + * @param color The integer to interpret as an RGBA value. + * + * @return A vector corresponding to the interpreted RGBA color. + */ + static Vec4 fromColor(unsigned int color); + + /** + * Indicates whether this vector contains all zeros. + * + * @return true if this vector contains all zeros, false otherwise. + */ + bool isZero() const; + + /** + * Indicates whether this vector contains all ones. + * + * @return true if this vector contains all ones, false otherwise. + */ + bool isOne() const; + + /** + * Returns the angle (in radians) between the specified vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * + * @return The angle between the two vectors (in radians). + */ + static float angle(const Vec4 &v1, const Vec4 &v2); + + /** + * Adds the elements of the specified vector to this one. + * + * @param v The vector to add. + */ + void add(const Vec4 &v); + + /** + * Adds the specified vectors and stores the result in dst. + * + * @param v1 The first vector. + * @param v2 The second vector. + * @param dst A vector to store the result in. + */ + static void add(const Vec4 &v1, const Vec4 &v2, Vec4 *dst); + + /** + * Clamps this vector within the specified range. + * + * @param min The minimum value. + * @param max The maximum value. + */ + void clamp(const Vec4 &min, const Vec4 &max); + + /** + * Clamps the specified vector within the specified range and returns it in dst. + * + * @param v The vector to clamp. + * @param min The minimum value. + * @param max The maximum value. + * @param dst A vector to store the result in. + */ + static void clamp(const Vec4 &v, const Vec4 &min, const Vec4 &max, Vec4 *dst); + + /** + * Returns the distance between this vector and v. + * + * @param v The other vector. + * + * @return The distance between this vector and v. + * + * @see distanceSquared + */ + float distance(const Vec4 &v) const; + + /** + * Returns the squared distance between this vector and v. + * + * When it is not necessary to get the exact distance between + * two vectors (for example, when simply comparing the + * distance between different vectors), it is advised to use + * this method instead of distance. + * + * @param v The other vector. + * + * @return The squared distance between this vector and v. + * + * @see distance + */ + float distanceSquared(const Vec4 &v) const; + + /** + * Returns the dot product of this vector and the specified vector. + * + * @param v The vector to compute the dot product with. + * + * @return The dot product. + */ + float dot(const Vec4 &v) const; + + /** + * Returns the dot product between the specified vectors. + * + * @param v1 The first vector. + * @param v2 The second vector. + * + * @return The dot product between the vectors. + */ + static float dot(const Vec4 &v1, const Vec4 &v2); + + /** + * Computes the length of this vector. + * + * @return The length of the vector. + * + * @see lengthSquared + */ + float length() const; + + /** + * Returns the squared length of this vector. + * + * When it is not necessary to get the exact length of a + * vector (for example, when simply comparing the lengths of + * different vectors), it is advised to use this method + * instead of length. + * + * @return The squared length of the vector. + * + * @see length + */ + float lengthSquared() const; + + /** + * Negates this vector. + */ + void negate(); + + /** + * The inverse value of this vector. + * + * This method set each component to its inverse value, zero + * will become infinity. + */ + static void inverse(const Vec4 &v, Vec4 *dst); + + /** + * Normalizes this vector. + * + * This method normalizes this Vec4 so that it is of + * unit length (in other words, the length of the vector + * after calling this method will be 1.0f). If the vector + * already has unit length or if the length of the vector + * is zero, this method does nothing. + * + * @return This vector, after the normalization occurs. + */ + void normalize(); + + /** + * Get the normalized vector. + */ + Vec4 getNormalized() const; + + /** + * Scales all elements of this vector by the specified value. + * + * @param scalar The scalar value. + */ + void scale(float scalar); + + /** + * Sets the elements of this vector to the specified values. + * + * @param xx The new x coordinate. + * @param yy The new y coordinate. + * @param zz The new z coordinate. + * @param ww The new w coordinate. + */ + void set(float xx, float yy, float zz, float ww); + + /** + * Sets the elements of this vector from the values in the specified array. + * + * @param array An array containing the elements of the vector in the order x, y, z, w. + */ + void set(const float *array); + + /** + * Sets the elements of this vector to those in the specified vector. + * + * @param v The vector to copy. + */ + void set(const Vec4 &v); + + /** + * Sets this vector to the directional vector between the specified points. + * + * @param p1 The first point. + * @param p2 The second point. + */ + void set(const Vec4 &p1, const Vec4 &p2); + + /** + * Subtracts this vector and the specified vector as (this - v) + * and stores the result in this vector. + * + * @param v The vector to subtract. + */ + void subtract(const Vec4 &v); + + /** + * Subtracts the specified vectors and stores the result in dst. + * The resulting vector is computed as (v1 - v2). + * + * @param v1 The first vector. + * @param v2 The second vector. + * @param dst The destination vector. + */ + static void subtract(const Vec4 &v1, const Vec4 &v2, Vec4 *dst); + + /** + * @en Calculates the linear interpolation between two vectors with a given ratio + * @zh 逐元素向量线性插值: A + t * (B - A) + */ + static void lerp(const Vec4 &a, const Vec4 &b, float t, Vec4 *dst); + + /** + * Calculates the sum of this vector with the given vector. + * + * Note: this does not modify this vector. + * + * @param v The vector to add. + * @return The vector sum. + */ + inline const Vec4 operator+(const Vec4 &v) const; + + /** + * Adds the given vector to this vector. + * + * @param v The vector to add. + * @return This vector, after the addition occurs. + */ + inline Vec4 &operator+=(const Vec4 &v); + + /** + * Calculates the sum of this vector with the given vector. + * + * Note: this does not modify this vector. + * + * @param v The vector to add. + * @return The vector sum. + */ + inline const Vec4 operator-(const Vec4 &v) const; + + /** + * Subtracts the given vector from this vector. + * + * @param v The vector to subtract. + * @return This vector, after the subtraction occurs. + */ + inline Vec4 &operator-=(const Vec4 &v); + + /** + * Calculates the negation of this vector. + * + * Note: this does not modify this vector. + * + * @return The negation of this vector. + */ + inline const Vec4 operator-() const; + + /** + * Calculates the scalar product of this vector with the given value. + * + * Note: this does not modify this vector. + * + * @param s The value to scale by. + * @return The scaled vector. + */ + inline const Vec4 operator*(float s) const; + + /** + * Scales this vector by the given value. + * + * @param s The value to scale by. + * @return This vector, after the scale occurs. + */ + inline Vec4 &operator*=(float s); + + /** + * Returns the components of this vector divided by the given constant + * + * Note: this does not modify this vector. + * + * @param s the constant to divide this vector with + * @return a smaller vector + */ + inline const Vec4 operator/(float s) const; + + /** + * Determines if this vector is less than the given vector. + * + * @param v The vector to compare against. + * + * @return True if this vector is less than the given vector, false otherwise. + */ + inline bool operator<(const Vec4 &v) const; + + /** + * Determines if this vector is equal to the given vector. + * + * @param v The vector to compare against. + * + * @return True if this vector is equal to the given vector, false otherwise. + */ + inline bool operator==(const Vec4 &v) const; + + /** + * Determines if this vector is not equal to the given vector. + * + * @param v The vector to compare against. + * + * @return True if this vector is not equal to the given vector, false otherwise. + */ + inline bool operator!=(const Vec4 &v) const; + + /** + * Determines if this vector is approximately equal to the given vector. + */ + inline bool approxEquals(const Vec4 &v, float precision = CC_FLOAT_CMP_PRECISION) const { + return math::isEqualF(x, v.x, precision) && math::isEqualF(y, v.y, precision) && math::isEqualF(z, v.z, precision) && math::isEqualF(w, v.w, precision); + } + + /** equals to Vec4(0,0,0,0) */ + static const Vec4 ZERO; + /** equals to Vec4(1,1,1,1) */ + static const Vec4 ONE; + /** equals to Vec4(1,0,0,0) */ + static const Vec4 UNIT_X; + /** equals to Vec4(0,1,0,0) */ + static const Vec4 UNIT_Y; + /** equals to Vec4(0,0,1,0) */ + static const Vec4 UNIT_Z; + /** equals to Vec4(0,0,0,1) */ + static const Vec4 UNIT_W; +}; + +/** + * Calculates the scalar product of the given vector with the given value. + * + * @param x The value to scale by. + * @param v The vector to scale. + * @return The scaled vector. + */ +inline const Vec4 operator*(float x, const Vec4 &v); + +NS_CC_MATH_END +/** + end of base group + @} + */ +#include "math/Vec4.inl" diff --git a/cocos/math/Vec4.inl b/cocos/math/Vec4.inl new file mode 100644 index 0000000..0b95021 --- /dev/null +++ b/cocos/math/Vec4.inl @@ -0,0 +1,116 @@ +/** + Copyright 2013 BlackBerry Inc. + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Original file from GamePlay3D: http://gameplay3d.org + + This file was modified to fit the cocos2d-x project + */ + +#include "math/Mat4.h" +#include "math/Vec4.h" + +NS_CC_MATH_BEGIN + +inline const Vec4 Vec4::operator+(const Vec4& v) const +{ + Vec4 result(*this); + result.add(v); + return result; +} + +inline Vec4& Vec4::operator+=(const Vec4& v) +{ + add(v); + return *this; +} + +inline const Vec4 Vec4::operator-(const Vec4& v) const +{ + Vec4 result(*this); + result.subtract(v); + return result; +} + +inline Vec4& Vec4::operator-=(const Vec4& v) +{ + subtract(v); + return *this; +} + +inline const Vec4 Vec4::operator-() const +{ + Vec4 result(*this); + result.negate(); + return result; +} + +inline const Vec4 Vec4::operator*(float s) const +{ + Vec4 result(*this); + result.scale(s); + return result; +} + +inline Vec4& Vec4::operator*=(float s) +{ + scale(s); + return *this; +} + +inline const Vec4 Vec4::operator/(const float s) const +{ + return Vec4(this->x / s, this->y / s, this->z / s, this->w / s); +} + +inline bool Vec4::operator<(const Vec4& v) const +{ + if (x == v.x) + { + if (y == v.y) + { + if (z < v.z) + { + if (w < v.w) + { + return w < v.w; + } + } + return z < v.z; + } + return y < v.y; + } + return x < v.x; +} + +inline bool Vec4::operator==(const Vec4& v) const +{ + return x==v.x && y==v.y && z==v.z && w==v.w; +} + +inline bool Vec4::operator!=(const Vec4& v) const +{ + return x!=v.x || y!=v.y || z!=v.z || w!=v.w; +} + +inline const Vec4 operator*(float x, const Vec4& v) +{ + Vec4 result(v); + result.scale(x); + return result; +} + +NS_CC_MATH_END diff --git a/cocos/network/Downloader-curl.cpp b/cocos/network/Downloader-curl.cpp new file mode 100644 index 0000000..e120843 --- /dev/null +++ b/cocos/network/Downloader-curl.cpp @@ -0,0 +1,781 @@ +/**************************************************************************** + Copyright (c) 2015-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "network/Downloader-curl.h" + +#include +#include +#include + +#include "application/ApplicationManager.h" +#include "base/Scheduler.h" +#include "base/StringUtil.h" +#include "base/memory/Memory.h" +#include "base/std/container/deque.h" +#include "base/std/container/set.h" +#include "base/std/container/vector.h" +#include "network/Downloader.h" +#include "platform/FileUtils.h" + +// **NOTE** +// In the file: +// member function with suffix "Proc" designed called in DownloaderCURL::_threadProc +// member function without suffix designed called in main thread + +#ifndef CC_CURL_POLL_TIMEOUT_MS + #define CC_CURL_POLL_TIMEOUT_MS 50 +#endif + +namespace cc { +namespace network { + +//////////////////////////////////////////////////////////////////////////////// +// Implementation DownloadTaskCURL + +class DownloadTaskCURL : public IDownloadTask { + static int _sSerialId; + + // if more than one task write to one file, cause file broken + // so use a set to check this situation + static ccstd::set _sStoragePathSet; + +public: + int serialId; + + DownloadTaskCURL() + : serialId(_sSerialId++), + _fp(nullptr) { + _initInternal(); + DLLOG("Construct DownloadTaskCURL %p", this); + } + + virtual ~DownloadTaskCURL() { + // if task destroyed unnormally, we should release WritenFileName stored in set. + // Normally, this action should done when task finished. + if (_tempFileName.length() && _sStoragePathSet.end() != _sStoragePathSet.find(_tempFileName)) { + DownloadTaskCURL::_sStoragePathSet.erase(_tempFileName); + } + if (_fp) { + fclose(_fp); + _fp = nullptr; + } + DLLOG("Destruct DownloadTaskCURL %p", this); + } + + bool init(const ccstd::string &filename, const ccstd::string &tempSuffix) { + if (0 == filename.length()) { + // data task + _buf.reserve(CURL_MAX_WRITE_SIZE); + return true; + } + + // file task + _fileName = filename; + _tempFileName = filename; + _tempFileName.append(tempSuffix); + + if (_sStoragePathSet.end() != _sStoragePathSet.find(_tempFileName)) { + // there is another task uses this storage path + _errCode = DownloadTask::ERROR_FILE_OP_FAILED; + _errCodeInternal = 0; + _errDescription = "More than one download file task write to same file:"; + _errDescription.append(_tempFileName); + return false; + } + _sStoragePathSet.insert(_tempFileName); + + // open temp file handle for write + bool ret = false; + do { + ccstd::string dir; + size_t found = _tempFileName.find_last_of("/\\"); + if (found == ccstd::string::npos) { + _errCode = DownloadTask::ERROR_INVALID_PARAMS; + _errCodeInternal = 0; + _errDescription = "Can't find dirname in storagePath."; + break; + } + + // ensure directory is exist + auto util = FileUtils::getInstance(); + dir = _tempFileName.substr(0, found + 1); + if (false == util->isDirectoryExist(dir)) { + if (false == util->createDirectory(dir)) { + _errCode = DownloadTask::ERROR_FILE_OP_FAILED; + _errCodeInternal = 0; + _errDescription = "Can't create dir:"; + _errDescription.append(dir); + break; + } + } + + // open file + _fp = fopen(util->getSuitableFOpen(_tempFileName).c_str(), "ab"); + if (nullptr == _fp) { + _errCode = DownloadTask::ERROR_FILE_OP_FAILED; + _errCodeInternal = 0; + _errDescription = "Can't open file:"; + _errDescription.append(_tempFileName); + } + ret = true; + } while (0); + + return ret; + } + + void initProc() { + std::lock_guard lock(_mutex); + _initInternal(); + } + + void setErrorProc(int code, int codeInternal, const char *desc) { + std::lock_guard lock(_mutex); + _errCode = code; + _errCodeInternal = codeInternal; + _errDescription = desc; + } + + size_t writeDataProc(unsigned char *buffer, size_t size, size_t count) { + std::lock_guard lock(_mutex); + size_t ret = 0; + if (_fp) { + ret = fwrite(buffer, size, count, _fp); + } else { + ret = size * count; + auto cap = _buf.capacity(); + auto bufSize = _buf.size(); + if (cap < bufSize + ret) { + _buf.reserve(bufSize * 2); + } + _buf.insert(_buf.end(), buffer, buffer + ret); + } + if (ret) { + _bytesReceived += ret; + _totalBytesReceived += ret; + } + return ret; + } + +private: + friend class DownloaderCURL; + + // for lock object instance + std::mutex _mutex; + + // header info + bool _acceptRanges; + bool _headerAchieved; + uint32_t _totalBytesExpected; + + ccstd::string _header; // temp buffer for receive header string, only used in thread proc + + // progress + uint32_t _bytesReceived; + uint32_t _totalBytesReceived; + + // error + int _errCode; + int _errCodeInternal; + ccstd::string _errDescription; + + // for saving data + ccstd::string _fileName; + ccstd::string _tempFileName; + ccstd::vector _buf; + FILE *_fp; + + void _initInternal() { + _acceptRanges = (false); + _headerAchieved = (false); + _bytesReceived = (0); + _totalBytesReceived = (0); + _totalBytesExpected = (0); + _errCode = (DownloadTask::ERROR_NO_ERROR); + _errCodeInternal = (CURLE_OK); + _header.resize(0); + _header.reserve(384); // pre alloc header string buffer + } +}; +int DownloadTaskCURL::_sSerialId; +ccstd::set DownloadTaskCURL::_sStoragePathSet; + +typedef std::pair, DownloadTaskCURL *> TaskWrapper; + +//////////////////////////////////////////////////////////////////////////////// +// Implementation DownloaderCURL::Impl +// This class shared by DownloaderCURL and work thread. +class DownloaderCURL::Impl : public std::enable_shared_from_this { +public: + DownloaderHints hints; + + Impl() + // : _thread(nullptr) + { + DLLOG("Construct DownloaderCURL::Impl %p", this); + } + + ~Impl() { + DLLOG("Destruct DownloaderCURL::Impl %p %d", this, _thread.joinable()); + } + + void addTask(std::shared_ptr task, DownloadTaskCURL *coTask) { + if (DownloadTask::ERROR_NO_ERROR == coTask->_errCode) { + std::lock_guard lock(_requestMutex); + _requestQueue.push_back(make_pair(task, coTask)); + } else { + std::lock_guard lock(_finishedMutex); + _finishedQueue.push_back(make_pair(task, coTask)); + } + } + + void run() { + std::lock_guard lock(_threadMutex); + if (false == _thread.joinable()) { + std::thread newThread(&DownloaderCURL::Impl::_threadProc, this); + _thread.swap(newThread); + } + } + + void stop() { + std::lock_guard lock(_threadMutex); + if (_thread.joinable()) { + _thread.detach(); + } + } + + bool stoped() { + std::lock_guard lock(_threadMutex); + return false == _thread.joinable() ? true : false; + } + + void getProcessTasks(ccstd::vector &outList) { + std::lock_guard lock(_processMutex); + outList.reserve(_processSet.size()); + outList.insert(outList.end(), _processSet.begin(), _processSet.end()); + } + + void getFinishedTasks(ccstd::vector &outList) { + std::lock_guard lock(_finishedMutex); + outList.reserve(_finishedQueue.size()); + outList.insert(outList.end(), _finishedQueue.begin(), _finishedQueue.end()); + _finishedQueue.clear(); + } + +private: + static size_t _outputHeaderCallbackProc(void *buffer, size_t size, size_t count, void *userdata) { + int strLen = int(size * count); + DLLOG(" _outputHeaderCallbackProc: %.*s", strLen, buffer); + DownloadTaskCURL &coTask = *((DownloadTaskCURL *)(userdata)); + coTask._header.append((const char *)buffer, strLen); + return strLen; + } + + static size_t _outputDataCallbackProc(void *buffer, size_t size, size_t count, void *userdata) { + // DLLOG(" _outputDataCallbackProc: size(%ld), count(%ld)", size, count); + DownloadTaskCURL *coTask = (DownloadTaskCURL *)userdata; + + // If your callback function returns CURL_WRITEFUNC_PAUSE it will cause this transfer to become paused. + return coTask->writeDataProc((unsigned char *)buffer, size, count); + } + + // this function designed call in work thread + // the curl handle destroyed in _threadProc + // handle inited for get header + void _initCurlHandleProc(CURL *handle, TaskWrapper &wrapper, bool forContent = false) { + const DownloadTask &task = *wrapper.first; + const DownloadTaskCURL *coTask = wrapper.second; + + // set url + ccstd::string url(task.requestURL); + curl_easy_setopt(handle, CURLOPT_URL, StringUtil::replaceAll(url, " ", "%20").c_str()); + + // set write func + if (forContent) { + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, DownloaderCURL::Impl::_outputDataCallbackProc); + } else { + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, DownloaderCURL::Impl::_outputHeaderCallbackProc); + } + curl_easy_setopt(handle, CURLOPT_WRITEDATA, coTask); + + curl_easy_setopt(handle, CURLOPT_NOPROGRESS, true); + // curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, DownloaderCURL::Impl::_progressCallbackProc); + // curl_easy_setopt(handle, CURLOPT_XFERINFODATA, coTask); + + curl_easy_setopt(handle, CURLOPT_FAILONERROR, true); + curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L); + + if (forContent) { + /** if server acceptRanges and local has part of file, we continue to download **/ + if (coTask->_acceptRanges && coTask->_totalBytesReceived > 0) { + curl_easy_setopt(handle, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)coTask->_totalBytesReceived); + } + } else { + // get header options + curl_easy_setopt(handle, CURLOPT_HEADER, 1); + curl_easy_setopt(handle, CURLOPT_NOBODY, 1); + } + + // if (!sProxy.empty()) + // { + // curl_easy_setopt(curl, CURLOPT_PROXY, sProxy.c_str()); + // } + if (hints.timeoutInSeconds) { + curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, hints.timeoutInSeconds); + } + + static const long LOW_SPEED_LIMIT = 1; + static const long LOW_SPEED_TIME = 10; + curl_easy_setopt(handle, CURLOPT_LOW_SPEED_LIMIT, LOW_SPEED_LIMIT); + curl_easy_setopt(handle, CURLOPT_LOW_SPEED_TIME, LOW_SPEED_TIME); + + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, false); + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, false); + + static const int MAX_REDIRS = 5; + if (MAX_REDIRS) { + curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, true); + curl_easy_setopt(handle, CURLOPT_MAXREDIRS, MAX_REDIRS); + } + } + + // get header info, if success set handle to content download state + bool _getHeaderInfoProc(CURL *handle, TaskWrapper &wrapper) { + DownloadTaskCURL &coTask = *wrapper.second; + CURLcode rc = CURLE_OK; + do { + long httpResponseCode = 0; + rc = curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &httpResponseCode); + if (CURLE_OK != rc) { + break; + } + if (200 != httpResponseCode) { + char buf[256] = {0}; + sprintf(buf, "When request url(%s) header info, return unexcept http response code(%ld)", wrapper.first->requestURL.c_str(), httpResponseCode); + coTask.setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, CURLE_OK, buf); + } + + // curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &effectiveUrl); + // curl_easy_getinfo(handle, CURLINFO_CONTENT_TYPE, &contentType); + double contentLen = 0; + rc = curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLen); + if (CURLE_OK != rc) { + break; + } + + bool acceptRanges = (ccstd::string::npos != coTask._header.find("Accept-Ranges")) ? true : false; + + // get current file size + uint32_t fileSize = 0; + if (acceptRanges && coTask._tempFileName.length()) { + fileSize = FileUtils::getInstance()->getFileSize(coTask._tempFileName); + } + + // set header info to coTask + std::lock_guard lock(coTask._mutex); + coTask._totalBytesExpected = (uint32_t)contentLen; + coTask._acceptRanges = acceptRanges; + if (acceptRanges && fileSize > 0) { + coTask._totalBytesReceived = fileSize; + } + coTask._headerAchieved = true; + } while (0); + + if (CURLE_OK != rc) { + coTask.setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, rc, curl_easy_strerror(rc)); + } + return coTask._headerAchieved; + } + + void _threadProc() { + DLLOG("++++DownloaderCURL::Impl::_threadProc begin %p", this); + // the holder prevent DownloaderCURL::Impl class instance be destruct in main thread + auto holder = this->shared_from_this(); + auto thisThreadId = std::this_thread::get_id(); + uint32_t countOfMaxProcessingTasks = this->hints.countOfMaxProcessingTasks; + // init curl content + CURLM *curlmHandle = curl_multi_init(); + ccstd::unordered_map coTaskMap; + int runningHandles = 0; + CURLMcode mcode = CURLM_OK; + int rc = 0; // select return code + + do { + // check the thread should exit or not + { + std::lock_guard lock(_threadMutex); + // if the Impl stoped, this->_thread.reset will be called, thus _thread.get_id() not equal with thisThreadId + if (thisThreadId != this->_thread.get_id()) { + break; + } + } + + if (runningHandles) { + // get timeout setting from multi-handle + long timeoutMS = -1; + curl_multi_timeout(curlmHandle, &timeoutMS); + + if (timeoutMS < 0) { + timeoutMS = 1000; + } + + /* get file descriptors from the transfers */ + fd_set fdread; + fd_set fdwrite; + fd_set fdexcep; + int maxfd = -1; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + FD_ZERO(&fdexcep); + + mcode = curl_multi_fdset(curlmHandle, &fdread, &fdwrite, &fdexcep, &maxfd); + if (CURLM_OK != mcode) { + break; + } + + // do wait action + if (maxfd == -1) { + std::this_thread::sleep_for(std::chrono::milliseconds(CC_CURL_POLL_TIMEOUT_MS)); + rc = 0; + } else { + struct timeval timeout; + + timeout.tv_sec = timeoutMS / 1000; + timeout.tv_usec = (timeoutMS % 1000) * 1000; + + rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); + } + + if (rc < 0) { + DLLOG(" _threadProc: select return unexpect code: %d", rc); + } + } + + if (coTaskMap.size()) { + mcode = CURLM_CALL_MULTI_PERFORM; + while (CURLM_CALL_MULTI_PERFORM == mcode) { + mcode = curl_multi_perform(curlmHandle, &runningHandles); + } + if (CURLM_OK != mcode) { + break; + } + + struct CURLMsg *m; + do { + int msgq = 0; + m = curl_multi_info_read(curlmHandle, &msgq); + if (m && (m->msg == CURLMSG_DONE)) { + CURL *curlHandle = m->easy_handle; + CURLcode errCode = m->data.result; + + TaskWrapper wrapper = coTaskMap[curlHandle]; + + // remove from multi-handle + curl_multi_remove_handle(curlmHandle, curlHandle); + bool reinited = false; + do { + if (CURLE_OK != errCode) { + wrapper.second->setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, errCode, curl_easy_strerror(errCode)); + break; + } + + // if the task is content download task, cleanup the handle + if (wrapper.second->_headerAchieved) { + break; + } + + // the task is get header task + // first, we get info from response + if (false == _getHeaderInfoProc(curlHandle, wrapper)) { + // the error info has been set in _getHeaderInfoProc + break; + } + + // after get header info success + // wrapper.second->_totalBytesReceived inited by local file size + // if the local file size equal with the content size from header, the file has downloaded finish + if (wrapper.second->_totalBytesReceived && + wrapper.second->_totalBytesReceived == wrapper.second->_totalBytesExpected) { + // the file has download complete + // break to move this task to finish queue + break; + } + // reinit curl handle for download content + curl_easy_reset(curlHandle); + _initCurlHandleProc(curlHandle, wrapper, true); + mcode = curl_multi_add_handle(curlmHandle, curlHandle); + if (CURLM_OK != mcode) { + wrapper.second->setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, mcode, curl_multi_strerror(mcode)); + break; + } + reinited = true; + } while (0); + + if (reinited) { + continue; + } + curl_easy_cleanup(curlHandle); + DLLOG(" _threadProc task clean cur handle :%p with errCode:%d", curlHandle, errCode); + + // remove from coTaskMap + coTaskMap.erase(curlHandle); + + // remove from _processSet + { + std::lock_guard lock(_processMutex); + if (_processSet.end() != _processSet.find(wrapper)) { + _processSet.erase(wrapper); + } + } + + // add to finishedQueue + { + std::lock_guard lock(_finishedMutex); + _finishedQueue.push_back(wrapper); + } + } + } while (m); + } + + // process tasks in _requestList + auto size = coTaskMap.size(); + while (0 == countOfMaxProcessingTasks || size < countOfMaxProcessingTasks) { + // get task wrapper from request queue + TaskWrapper wrapper; + { + std::lock_guard lock(_requestMutex); + if (_requestQueue.size()) { + wrapper = _requestQueue.front(); + _requestQueue.pop_front(); + } + } + + // if request queue is empty, the wrapper.first is nullptr + if (!wrapper.first) { + break; + } + + wrapper.second->initProc(); + + // create curl handle from task and add into curl multi handle + CURL *curlHandle = curl_easy_init(); + + if (nullptr == curlHandle) { + wrapper.second->setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, 0, "Alloc curl handle failed."); + std::lock_guard lock(_finishedMutex); + _finishedQueue.push_back(wrapper); + continue; + } + + // init curl handle for get header info + _initCurlHandleProc(curlHandle, wrapper); + + // add curl handle to process list + mcode = curl_multi_add_handle(curlmHandle, curlHandle); + if (CURLM_OK != mcode) { + wrapper.second->setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, mcode, curl_multi_strerror(mcode)); + std::lock_guard lock(_finishedMutex); + _finishedQueue.push_back(wrapper); + continue; + } + + DLLOG(" _threadProc task create curl handle:%p", curlHandle); + coTaskMap[curlHandle] = wrapper; + std::lock_guard lock(_processMutex); + _processSet.insert(wrapper); + } + } while (coTaskMap.size()); + + curl_multi_cleanup(curlmHandle); + this->stop(); + DLLOG("----DownloaderCURL::Impl::_threadProc end"); + } + + std::thread _thread; + ccstd::deque _requestQueue; + ccstd::set _processSet; + ccstd::deque _finishedQueue; + + std::mutex _threadMutex; + std::mutex _requestMutex; + std::mutex _processMutex; + std::mutex _finishedMutex; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Implementation DownloaderCURL +DownloaderCURL::DownloaderCURL(const DownloaderHints &hints) +: _impl(std::make_shared()), + _currTask(nullptr) { + DLLOG("Construct DownloaderCURL %p", this); + _impl->hints = hints; + _scheduler = CC_CURRENT_ENGINE()->getScheduler(); + + _transferDataToBuffer = [this](void *buf, uint32_t len) -> uint32_t { + DownloadTaskCURL &coTask = *_currTask; + uint32_t dataLen = coTask._buf.size(); + if (len < dataLen) { + return 0; + } + + memcpy(buf, coTask._buf.data(), dataLen); + coTask._buf.resize(0); + return dataLen; + }; + + char key[128]; + sprintf(key, "DownloaderCURL(%p)", this); + _schedulerKey = key; + + if (auto sche = _scheduler.lock()) { + sche->schedule(std::bind(&DownloaderCURL::onSchedule, this, std::placeholders::_1), + this, + 0.1f, + true, + _schedulerKey); + } +} + +DownloaderCURL::~DownloaderCURL() { + if (auto sche = _scheduler.lock()) { + sche->unschedule(_schedulerKey, this); + } + + _impl->stop(); + DLLOG("Destruct DownloaderCURL %p", this); +} + +IDownloadTask *DownloaderCURL::createCoTask(std::shared_ptr &task) { + DownloadTaskCURL *coTask = ccnew DownloadTaskCURL; + coTask->init(task->storagePath, _impl->hints.tempFileNameSuffix); + + DLLOG(" DownloaderCURL: createTask: Id(%d)", coTask->serialId); + + _impl->addTask(task, coTask); + _impl->run(); + + if (auto sche = _scheduler.lock()) { + sche->resumeTarget(this); + } + return coTask; +} + +void DownloaderCURL::abort(const std::unique_ptr &task) { + // REFINE + // https://github.com/cocos-creator/cocos2d-x-lite/pull/1291 + DLLOG("%s isn't implemented!\n", __FUNCTION__); +} + +void DownloaderCURL::onSchedule(float) { + ccstd::vector tasks; + + // update processing tasks + _impl->getProcessTasks(tasks); + for (auto &wrapper : tasks) { + const DownloadTask &task = *wrapper.first; + DownloadTaskCURL &coTask = *wrapper.second; + + std::lock_guard lock(coTask._mutex); + if (coTask._bytesReceived) { + _currTask = &coTask; + onTaskProgress(task, + coTask._bytesReceived, + coTask._totalBytesReceived, + coTask._totalBytesExpected, + _transferDataToBuffer); + _currTask = nullptr; + coTask._bytesReceived = 0; + } + } + tasks.resize(0); + + // update finished tasks + _impl->getFinishedTasks(tasks); + if (_impl->stoped()) { + if (auto sche = _scheduler.lock()) { + sche->pauseTarget(this); + } + } + + for (auto &wrapper : tasks) { + const DownloadTask &task = *wrapper.first; + DownloadTaskCURL &coTask = *wrapper.second; + + // if there is bytesReceived, call progress update first + if (coTask._bytesReceived) { + _currTask = &coTask; + onTaskProgress(task, + coTask._bytesReceived, + coTask._totalBytesReceived, + coTask._totalBytesExpected, + _transferDataToBuffer); + coTask._bytesReceived = 0; + _currTask = nullptr; + } + + // if file task, close file handle and rename file if needed + if (coTask._fp) { + fclose(coTask._fp); + coTask._fp = nullptr; + do { + if (0 == coTask._fileName.length()) { + break; + } + + auto util = FileUtils::getInstance(); + // if file already exist, remove it + if (util->isFileExist(coTask._fileName)) { + if (false == util->removeFile(coTask._fileName)) { + coTask._errCode = DownloadTask::ERROR_FILE_OP_FAILED; + coTask._errCodeInternal = 0; + coTask._errDescription = "Can't remove old file: "; + coTask._errDescription.append(coTask._fileName); + break; + } + } + + // rename file + if (util->renameFile(coTask._tempFileName, coTask._fileName)) { + // success, remove storage from set + DownloadTaskCURL::_sStoragePathSet.erase(coTask._tempFileName); + break; + } + // failed + coTask._errCode = DownloadTask::ERROR_FILE_OP_FAILED; + coTask._errCodeInternal = 0; + coTask._errDescription = "Can't renamefile from: "; + coTask._errDescription.append(coTask._tempFileName); + coTask._errDescription.append(" to: "); + coTask._errDescription.append(coTask._fileName); + } while (0); + } + // needn't lock coTask here, because tasks has removed form _impl + onTaskFinish(task, coTask._errCode, coTask._errCodeInternal, coTask._errDescription, coTask._buf); + DLLOG(" DownloaderCURL: finish Task: Id(%d)", coTask.serialId); + } +} + +} // namespace network +} // namespace cc diff --git a/cocos/network/Downloader-curl.h b/cocos/network/Downloader-curl.h new file mode 100644 index 0000000..19de775 --- /dev/null +++ b/cocos/network/Downloader-curl.h @@ -0,0 +1,63 @@ +/**************************************************************************** + Copyright (c) 2015-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "network/DownloaderImpl.h" + +namespace cc { +class Scheduler; +} + +namespace cc { +namespace network { +class DownloadTaskCURL; +struct DownloaderHints; + +class DownloaderCURL : public IDownloaderImpl { +public: + explicit DownloaderCURL(const DownloaderHints &hints); + ~DownloaderCURL() override; + + IDownloadTask *createCoTask(std::shared_ptr &task) override; + + void abort(const std::unique_ptr &task) override; + +protected: + class Impl; + std::shared_ptr _impl; + + // for transfer data on schedule + DownloadTaskCURL *_currTask; // temp ref + std::function _transferDataToBuffer; + + // scheduler for update processing and finished task in main schedule + void onSchedule(float); + ccstd::string _schedulerKey; + std::weak_ptr _scheduler; +}; + +} // namespace network +} // namespace cc diff --git a/cocos/network/Downloader-java.cpp b/cocos/network/Downloader-java.cpp new file mode 100644 index 0000000..c67075b --- /dev/null +++ b/cocos/network/Downloader-java.cpp @@ -0,0 +1,293 @@ +/**************************************************************************** + Copyright (c) 2015-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "network/Downloader-java.h" + +#include +#include "application/ApplicationManager.h" +#include "base/StringUtil.h" +#include "base/memory/Memory.h" +#include "network/Downloader.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/JniImp.h" + +#ifndef JCLS_DOWNLOADER + #define JCLS_DOWNLOADER "com/cocos/lib/CocosDownloader" +#endif +#define JARG_STR "Ljava/lang/String;" +#define JARG_DOWNLOADER "L" JCLS_DOWNLOADER ";" + +#ifndef ORG_DOWNLOADER_CLASS_NAME + #define ORG_DOWNLOADER_CLASS_NAME com_cocos_lib_CocosDownloader +#endif +#define JNI_DOWNLOADER(FUNC) JNI_METHOD1(ORG_DOWNLOADER_CLASS_NAME, FUNC) + +ccstd::unordered_map sDownloaderMap; +std::mutex sDownloaderMutex; + +static void insertDownloaderJava(int id, cc::network::DownloaderJava *downloaderPtr) { + std::lock_guard guard(sDownloaderMutex); + sDownloaderMap.insert(std::make_pair(id, downloaderPtr)); +} + +static void eraseDownloaderJava(int id) { + std::lock_guard guard(sDownloaderMutex); + sDownloaderMap.erase(id); +} + +/** + * If not found, return nullptr, otherwise return the Downloader + */ +static cc::network::DownloaderJava *findDownloaderJava(int id) { + std::lock_guard guard(sDownloaderMutex); + auto iter = sDownloaderMap.find(id); + if (sDownloaderMap.end() == iter) { + return nullptr; + } + return iter->second; +} + +namespace cc { +namespace network { + +static int sTaskCounter = 0; +static int sDownloaderCounter = 0; + +struct DownloadTaskAndroid : public IDownloadTask { + DownloadTaskAndroid() + : id(++sTaskCounter) { + DLLOG("Construct DownloadTaskAndroid: %p", this); + } + ~DownloadTaskAndroid() override { + DLLOG("Destruct DownloadTaskAndroid: %p", this); + } + + int id; + std::shared_ptr task; // reference to DownloadTask, when task finish, release +}; + +DownloaderJava::DownloaderJava(const DownloaderHints &hints) +: _id(++sDownloaderCounter), + _impl(nullptr) { + DLLOG("Construct DownloaderJava: %p", this); + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_DOWNLOADER, + "createDownloader", + "(II" JARG_STR "I)" JARG_DOWNLOADER)) { + jobject jStr = methodInfo.env->NewStringUTF(hints.tempFileNameSuffix.c_str()); + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, + methodInfo.methodID, + _id, + hints.timeoutInSeconds, + jStr, + hints.countOfMaxProcessingTasks); + _impl = methodInfo.env->NewGlobalRef(jObj); + DLLOG("android downloader: jObj: %p, _impl: %p", jObj, _impl); + //It's not thread-safe here, use thread-safe method instead + //sDownloaderMap.insert(make_pair(_id, this)); + insertDownloaderJava(_id, this); + ccDeleteLocalRef(methodInfo.env, jStr); + ccDeleteLocalRef(methodInfo.env, jObj); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } +} + +DownloaderJava::~DownloaderJava() { + if (_impl != nullptr) { + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_DOWNLOADER, + "cancelAllRequests", + "(" JARG_DOWNLOADER ")V")) { + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, + methodInfo.methodID, + _impl); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } + //It's not thread-safe here, use thread-safe method instead + //sDownloaderMap.erase(_id); + eraseDownloaderJava(_id); + JniHelper::getEnv()->DeleteGlobalRef(_impl); + } + DLLOG("Destruct DownloaderJava: %p", this); +} + +IDownloadTask *DownloaderJava::createCoTask(std::shared_ptr &task) { + auto *coTask = ccnew DownloadTaskAndroid; + coTask->task = task; + + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_DOWNLOADER, + "createTask", + "(" JARG_DOWNLOADER "I" JARG_STR JARG_STR "[" JARG_STR ")V")) { + jclass jclassString = methodInfo.env->FindClass("java/lang/String"); + ccstd::string url(task->requestURL); + jstring jstrURL = methodInfo.env->NewStringUTF(StringUtil::replaceAll(url, " ", "%20").c_str()); + jstring jstrPath = methodInfo.env->NewStringUTF(task->storagePath.c_str()); + jobjectArray jarrayHeader = methodInfo.env->NewObjectArray(task->header.size() * 2, jclassString, nullptr); + const ccstd::unordered_map &headMap = task->header; + int index = 0; + for (const auto &it : headMap) { + methodInfo.env->SetObjectArrayElement(jarrayHeader, index++, methodInfo.env->NewStringUTF(it.first.c_str())); + methodInfo.env->SetObjectArrayElement(jarrayHeader, index++, methodInfo.env->NewStringUTF(it.second.c_str())); + } + methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, _impl, coTask->id, jstrURL, jstrPath, jarrayHeader); + for (int i = 0; i < index; ++i) { + ccDeleteLocalRef(methodInfo.env, methodInfo.env->GetObjectArrayElement(jarrayHeader, i)); + } + ccDeleteLocalRef(methodInfo.env, jclassString); + ccDeleteLocalRef(methodInfo.env, jstrURL); + ccDeleteLocalRef(methodInfo.env, jstrPath); + ccDeleteLocalRef(methodInfo.env, jarrayHeader); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } + + DLLOG("DownloaderJava::createCoTask id: %d", coTask->id); + _taskMap.insert(std::make_pair(coTask->id, coTask)); + return coTask; +} + +void DownloaderJava::abort(const std::unique_ptr &task) { + auto iter = _taskMap.begin(); + for (; iter != _taskMap.end(); iter++) { + if (task.get() == iter->second) { + break; + } + } + if (_impl != nullptr && iter != _taskMap.end()) { + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_DOWNLOADER, + "abort", + "(" JARG_DOWNLOADER "I" + ")V")) { + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, + methodInfo.methodID, + _impl, + iter->first); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + + DownloadTaskAndroid *coTask = iter->second; + _taskMap.erase(iter); + ccstd::vector emptyBuffer; + onTaskFinish(*coTask->task, + DownloadTask::ERROR_ABORT, + DownloadTask::ERROR_ABORT, + "downloadFile:fail abort", + emptyBuffer); + coTask->task.reset(); + } + } + DLLOG("DownloaderJava:abort"); +} + +void DownloaderJava::onProcessImpl(int taskId, uint32_t dl, uint32_t dlNow, uint32_t dlTotal) { + DLLOG("DownloaderJava::onProgress(taskId: %d, dl: %lld, dlnow: %lld, dltotal: %lld)", taskId, dl, dlNow, dlTotal); + auto iter = _taskMap.find(taskId); + if (_taskMap.end() == iter) { + DLLOG("DownloaderJava::onProgress can't find task with id: %d", taskId); + return; + } + DownloadTaskAndroid *coTask = iter->second; + std::function transferDataToBuffer; + onTaskProgress(*coTask->task, dl, dlNow, dlTotal, transferDataToBuffer); +} + +void DownloaderJava::onFinishImpl(int taskId, int errCode, const char *errStr, const ccstd::vector &data) { + DLLOG("DownloaderJava::onFinishImpl(taskId: %d, errCode: %d, errStr: %s)", taskId, errCode, (errStr) ? errStr : "null"); + auto iter = _taskMap.find(taskId); + if (_taskMap.end() == iter) { + DLLOG("DownloaderJava::onFinishImpl can't find task with id: %d", taskId); + return; + } + DownloadTaskAndroid *coTask = iter->second; + ccstd::string str = (errStr ? errStr : ""); + _taskMap.erase(iter); + onTaskFinish(*coTask->task, + (errStr || (errCode != 0)) ? DownloadTask::ERROR_IMPL_INTERNAL : DownloadTask::ERROR_NO_ERROR, + errCode, + str, + data); + coTask->task.reset(); +} +} // namespace network +} // namespace cc + +extern "C" { + +JNIEXPORT void JNICALL JNI_DOWNLOADER(nativeOnProgress)(JNIEnv * /*env*/, jobject /*obj*/, jint id, jint taskId, jlong dl, jlong dlnow, jlong dltotal) { + auto func = [=]() -> void { + DLLOG("_nativeOnProgress(id: %d, taskId: %d, dl: %lld, dlnow: %lld, dltotal: %lld)", id, taskId, dl, dlnow, dltotal); + //It's not thread-safe here, use thread-safe method instead + cc::network::DownloaderJava *downloader = findDownloaderJava(id); + if (nullptr == downloader) { + DLLOG("_nativeOnProgress can't find downloader by key: %p for task: %d", clazz, id); + return; + } + downloader->onProcessImpl((int)taskId, (uint32_t)dl, (uint32_t)dlnow, (uint32_t)dltotal); + }; + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func); +} + +JNIEXPORT void JNICALL JNI_DOWNLOADER(nativeOnFinish)(JNIEnv *env, jobject /*obj*/, jint id, jint taskId, jint errCode, jstring errStr, jbyteArray data) { + ccstd::string errStrTmp; + ccstd::vector dataTmp; + if (errStr) { + const char *nativeErrStr = env->GetStringUTFChars(errStr, JNI_FALSE); + errStrTmp = nativeErrStr; + env->ReleaseStringUTFChars(errStr, nativeErrStr); + } + if (data && env->GetArrayLength(data) > 0) { + auto len = env->GetArrayLength(data); + dataTmp.resize(len); + env->GetByteArrayRegion(data, 0, len, reinterpret_cast(dataTmp.data())); + } + auto func = [errStrTmp = std::move(errStrTmp), dataTmp = std::move(dataTmp), id, taskId, errCode]() -> void { + DLLOG("_nativeOnFinish(id: %d, taskId: %d)", id, taskId); + //It's not thread-safe here, use thread-safe method instead + cc::network::DownloaderJava *downloader = findDownloaderJava(id); + if (nullptr == downloader) { + DLLOG("_nativeOnFinish can't find downloader id: %d for task: %d", id, taskId); + return; + } + ccstd::vector buf; + if (!errStrTmp.empty()) { + // failure + downloader->onFinishImpl((int)taskId, (int)errCode, errStrTmp.c_str(), buf); + return; + } + + // success + downloader->onFinishImpl((int)taskId, (int)errCode, nullptr, dataTmp); + }; + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func); +} + +} // extern "C" { diff --git a/cocos/network/Downloader-java.h b/cocos/network/Downloader-java.h new file mode 100644 index 0000000..6d36b25 --- /dev/null +++ b/cocos/network/Downloader-java.h @@ -0,0 +1,56 @@ +/**************************************************************************** + Copyright (c) 2015-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "network/DownloaderImpl.h" + +class _jobject; // NOLINT(bugprone-reserved-identifier) + +namespace cc { +namespace network { +class DownloadTaskAndroid; +struct DownloaderHints; + +class DownloaderJava : public IDownloaderImpl { +public: + explicit DownloaderJava(const DownloaderHints &hints); + ~DownloaderJava() override; + + IDownloadTask *createCoTask(std::shared_ptr &task) override; + + void abort(const std::unique_ptr &task) override; + + // designed called by internal + void onProcessImpl(int taskId, uint32_t dl, uint32_t dlNow, uint32_t dlTotal); + void onFinishImpl(int taskId, int errCode, const char *errStr, const ccstd::vector &data); + +protected: + int _id; + _jobject *_impl; + ccstd::unordered_map _taskMap; +}; +} // namespace network +} // namespace cc diff --git a/cocos/network/Downloader.cpp b/cocos/network/Downloader.cpp new file mode 100644 index 0000000..226907c --- /dev/null +++ b/cocos/network/Downloader.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** + Copyright (c) 2015-2016 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "network/Downloader.h" +#include +#include "base/memory/Memory.h" + +// include platform specific implement class +#if (CC_PLATFORM == CC_PLATFORM_MACOS || CC_PLATFORM == CC_PLATFORM_IOS) + + #include "network/DownloaderImpl-apple.h" + #define DownloaderImpl DownloaderApple //NOLINT(readability-identifier-naming) + +#elif (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS) + + #include "network/Downloader-java.h" + #define DownloaderImpl DownloaderJava //NOLINT(readability-identifier-naming) + +#else + + #include "network/Downloader-curl.h" + #define DownloaderImpl DownloaderCURL //NOLINT(readability-identifier-naming) + +#endif + +namespace cc { +namespace network { + +DownloadTask::DownloadTask() { + DLLOG("Construct DownloadTask %p", this); +} + +DownloadTask::~DownloadTask() { + DLLOG("Destruct DownloadTask %p", this); +} + +//////////////////////////////////////////////////////////////////////////////// +// Implement Downloader +Downloader::Downloader() { + DownloaderHints hints; + ccnew_placement(this) Downloader(hints); +} + +Downloader::Downloader(const DownloaderHints &hints) { + DLLOG("Construct Downloader %p", this); + _impl = std::make_unique(hints); + _impl->onTaskProgress = [this](const DownloadTask &task, + uint32_t bytesReceived, + uint32_t totalBytesReceived, + uint32_t totalBytesExpected, + std::function & /*transferDataToBuffer*/) { + if (onTaskProgress) { + onTaskProgress(task, bytesReceived, totalBytesReceived, totalBytesExpected); + } + }; + + _impl->onTaskFinish = [this](const DownloadTask &task, + int errorCode, + int errorCodeInternal, + const ccstd::string &errorStr, + const ccstd::vector &data) { + if (DownloadTask::ERROR_NO_ERROR != errorCode) { + if (onTaskError) { + onTaskError(task, errorCode, errorCodeInternal, errorStr); + } + return; + } + + // success callback + if (task.storagePath.length()) { + if (onFileTaskSuccess) { + onFileTaskSuccess(task); + } + } else { + // data task + if (onDataTaskSuccess) { + onDataTaskSuccess(task, data); + } + } + }; +} + +Downloader::~Downloader() { + DLLOG("Destruct Downloader %p", this); +} + +std::shared_ptr Downloader::createDataTask(const ccstd::string &srcUrl, const ccstd::string &identifier /* = ""*/) { + auto *iTask = ccnew DownloadTask(); + std::shared_ptr task(iTask); + do { + iTask->requestURL = srcUrl; + iTask->identifier = identifier; + if (0 == srcUrl.length()) { + if (onTaskError) { + onTaskError(*task, DownloadTask::ERROR_INVALID_PARAMS, 0, "URL or is empty."); + } + task.reset(); + break; + } + iTask->_coTask.reset(_impl->createCoTask(task)); + } while (false); + + return task; +} + +std::shared_ptr Downloader::createDownloadTask(const ccstd::string &srcUrl, + const ccstd::string &storagePath, + const ccstd::unordered_map &header, + const ccstd::string &identifier /* = ""*/) { + auto *iTask = ccnew DownloadTask(); + std::shared_ptr task(iTask); + do { + iTask->requestURL = srcUrl; + iTask->storagePath = storagePath; + iTask->identifier = identifier; + iTask->header = header; + if (0 == srcUrl.length() || 0 == storagePath.length()) { + if (onTaskError) { + onTaskError(*task, DownloadTask::ERROR_INVALID_PARAMS, 0, "URL or storage path is empty."); + } + task.reset(); + break; + } + iTask->_coTask.reset(_impl->createCoTask(task)); + } while (false); + + return task; +} +std::shared_ptr Downloader::createDownloadTask(const ccstd::string &srcUrl, + const ccstd::string &storagePath, + const ccstd::string &identifier /* = ""*/) { + const ccstd::unordered_map emptyHeader; + return createDownloadTask(srcUrl, storagePath, emptyHeader, identifier); +} + +void Downloader::abort(const std::shared_ptr &task) { + _impl->abort(task->_coTask); +} +//ccstd::string Downloader::getFileNameFromUrl(const ccstd::string& srcUrl) +//{ +// // Find file name and file extension +// ccstd::string filename; +// unsigned long found = srcUrl.find_last_of("/\\"); +// if (found != ccstd::string::npos) +// filename = srcUrl.substr(found+1); +// return filename; +//} + +} // namespace network +} // namespace cc diff --git a/cocos/network/Downloader.h b/cocos/network/Downloader.h new file mode 100644 index 0000000..dcafff0 --- /dev/null +++ b/cocos/network/Downloader.h @@ -0,0 +1,125 @@ +/**************************************************************************** + Copyright (c) 2015-2016 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/Macros.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" + +namespace cc { +namespace network { + +class IDownloadTask; +class IDownloaderImpl; +class Downloader; + +class CC_DLL DownloadTask final { +public: + static const int ERROR_NO_ERROR = 0; + static const int ERROR_INVALID_PARAMS = -1; + static const int ERROR_FILE_OP_FAILED = -2; + static const int ERROR_IMPL_INTERNAL = -3; + static const int ERROR_ABORT = -4; + + ccstd::string identifier; + ccstd::string requestURL; + ccstd::string storagePath; + ccstd::unordered_map header; + + DownloadTask(); + virtual ~DownloadTask(); + +private: + friend class Downloader; + std::unique_ptr _coTask; +}; + +struct CC_DLL DownloaderHints { + uint32_t countOfMaxProcessingTasks{6}; + uint32_t timeoutInSeconds{45}; + ccstd::string tempFileNameSuffix{".tmp"}; +}; + +class CC_DLL Downloader final { +public: + Downloader(); + explicit Downloader(const DownloaderHints &hints); + ~Downloader(); + + std::function &data)> + onDataTaskSuccess; + + std::function onFileTaskSuccess; + + std::function + onTaskProgress; + + std::function + onTaskError; + + void setOnSuccess(const std::function &callback) { onFileTaskSuccess = callback; }; + + void setOnProgress(const std::function &callback) { onTaskProgress = callback; }; + + void setOnError(const std::function &callback) { onTaskError = callback; }; + + // CC_DEPRECATED(3.6, "Use setOnProgress instead") // needed for bindings, so not uncomment this line + void setOnTaskProgress(const std::function &callback) { onTaskProgress = callback; }; + + std::shared_ptr createDataTask(const ccstd::string &srcUrl, const ccstd::string &identifier = ""); + + std::shared_ptr createDownloadTask(const ccstd::string &srcUrl, const ccstd::string &storagePath, const ccstd::string &identifier = ""); + + std::shared_ptr createDownloadTask(const ccstd::string &srcUrl, const ccstd::string &storagePath, const ccstd::unordered_map &header, const ccstd::string &identifier = ""); + + void abort(const std::shared_ptr &task); + +private: + std::unique_ptr _impl; +}; + +} // namespace network +} // namespace cc diff --git a/cocos/network/DownloaderImpl-apple.h b/cocos/network/DownloaderImpl-apple.h new file mode 100644 index 0000000..acc9a71 --- /dev/null +++ b/cocos/network/DownloaderImpl-apple.h @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2015-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "network/DownloaderImpl.h" + +namespace cc { +namespace network { +struct DownloaderHints; +class DownloaderApple : public IDownloaderImpl { +public: + DownloaderApple(const DownloaderHints &hints); + virtual ~DownloaderApple(); + + virtual IDownloadTask *createCoTask(std::shared_ptr &task) override; + + virtual void abort(const std::unique_ptr &task) override; + +private: + void *_impl; +}; +} // namespace network +} // namespace cc diff --git a/cocos/network/DownloaderImpl-apple.mm b/cocos/network/DownloaderImpl-apple.mm new file mode 100644 index 0000000..f9ca1da --- /dev/null +++ b/cocos/network/DownloaderImpl-apple.mm @@ -0,0 +1,705 @@ +/**************************************************************************** + Copyright (c) 2015-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "network/DownloaderImpl-apple.h" +#import +#include "application/ApplicationManager.h" +#include "base/memory/Memory.h" +#include "base/std/container/queue.h" +#include "base/UTF8.h" +#include "network/Downloader.h" +#include "platform/FileUtils.h" +//////////////////////////////////////////////////////////////////////////////// +// OC Classes Declaration + +// this wrapper used to wrap C++ class DownloadTask into NSMutableDictionary +@interface DownloadTaskWrapper : NSObject { + std::shared_ptr _task; + NSMutableArray *_dataArray; +} +// temp vars for dataTask: didReceivedData callback +@property (nonatomic) uint32_t bytesReceived; +@property (nonatomic) uint32_t totalBytesReceived; + +- (id)init:(std::shared_ptr &)t; +- (const cc::network::DownloadTask *)get; +- (void)addData:(NSData *)data; +- (uint32_t)transferDataToBuffer:(void *)buffer lengthOfBuffer:(uint32_t)len; + +@end + +@interface DownloaderAppleImpl : NSObject { + const cc::network::DownloaderApple *_outer; + cc::network::DownloaderHints _hints; + ccstd::queue _taskQueue; +} +@property (nonatomic, strong) NSURLSession *downloadSession; +@property (nonatomic, strong) NSMutableDictionary *taskDict; // ocTask: DownloadTaskWrapper + +- (id)init:(const cc::network::DownloaderApple *)o hints:(const cc::network::DownloaderHints &)hints; +- (const cc::network::DownloaderHints &)getHints; +- (NSURLSessionDataTask *)createDataTask:(std::shared_ptr &)task; +- (NSURLSessionDownloadTask *)createDownloadTask:(std::shared_ptr &)task; +- (void)saveResumeData:(NSData *)resumeData forTaskStoragePath:(const ccstd::string &) path; +- (void)abort:(NSURLSessionTask *)task; +- (void)doDestroy; + +@end + +//////////////////////////////////////////////////////////////////////////////// +// C++ Classes Implementation + +namespace cc { +namespace network { +struct DownloadTaskApple : public IDownloadTask { + DownloadTaskApple() + : dataTask(nil), + downloadTask(nil) { + DLLOG("Construct DownloadTaskApple %p", this); + } + + virtual ~DownloadTaskApple() { + DLLOG("Destruct DownloadTaskApple %p", this); + } + NSURLSessionDataTask *dataTask; + NSURLSessionDownloadTask *downloadTask; +}; +#define DeclareDownloaderImplVar DownloaderAppleImpl *impl = (__bridge DownloaderAppleImpl *)_impl +// the _impl's type is id, we should convert it to subclass before call it's methods +DownloaderApple::DownloaderApple(const DownloaderHints &hints) +: _impl(nil) { + DLLOG("Construct DownloaderApple %p", this); + _impl = (__bridge void *)[[DownloaderAppleImpl alloc] init:this hints:hints]; +} + +DownloaderApple::~DownloaderApple() { + DeclareDownloaderImplVar; + [impl doDestroy]; + DLLOG("Destruct DownloaderApple %p", this); +} +IDownloadTask *DownloaderApple::createCoTask(std::shared_ptr &task) { + DownloadTaskApple *coTask = ccnew DownloadTaskApple(); + DeclareDownloaderImplVar; + if (task->storagePath.length()) { +#if CC_PLATFORM == CC_PLATFORM_IOS + NSString *requesetURL = [NSString stringWithFormat:@"%s", task->requestURL.c_str()]; + NSString *savaPath = [NSString stringWithFormat:@"%s", (cc::FileUtils::getInstance()->getWritablePath() + "resumeData.plist").c_str()]; + NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:savaPath]; + NSString *hasResumeData = [dict objectForKey:requesetURL]; + if ([hasResumeData isEqual: @"YES"]) { + CC_CURRENT_ENGINE()->getScheduler()->schedule([=](float dt) mutable { + coTask->downloadTask = [impl createDownloadTask:task]; + },this , 0, 0, 0.1F, false, task->requestURL); + } else { + coTask->downloadTask = [impl createDownloadTask:task]; + } +#else + coTask->downloadTask = [impl createDownloadTask:task]; +#endif + } else { + coTask->dataTask = [impl createDataTask:task]; + } + return coTask; +} +void DownloaderApple::abort(const std::unique_ptr &task) { + DLLOG("DownloaderApple:abort"); + DeclareDownloaderImplVar; + cc::network::DownloadTaskApple *downloadTask = (cc::network::DownloadTaskApple *)task.get(); + NSURLSessionTask *taskOC = downloadTask->dataTask ? downloadTask->dataTask : downloadTask->downloadTask; + [impl abort:taskOC]; +} +} // namespace network +} // namespace cc + +//////////////////////////////////////////////////////////////////////////////// +// OC Classes Implementation +@implementation DownloadTaskWrapper + +- (id)init:(std::shared_ptr &)t { + DLLOG("Construct DonloadTaskWrapper %p", self); + _dataArray = [NSMutableArray arrayWithCapacity:8]; + [_dataArray retain]; + _task = t; + return self; +} + +- (const cc::network::DownloadTask *)get { + return _task.get(); +} + +- (void)addData:(NSData *)data { + [_dataArray addObject:data]; + self.bytesReceived += static_cast(data.length); + self.totalBytesReceived += static_cast(data.length); +} + +- (uint32_t)transferDataToBuffer:(void *)buffer lengthOfBuffer:(uint32_t)len { + uint32_t bytesReceived = 0; + int receivedDataObject = 0; + + __block char *p = (char *)buffer; + for (NSData *data in _dataArray) { + // check + if (bytesReceived + data.length > len) { + break; + } + + // copy data + [data enumerateByteRangesUsingBlock:^(const void *bytes, + NSRange byteRange, + BOOL *stop) { + memcpy(p, bytes, byteRange.length); + p += byteRange.length; + *stop = NO; + }]; + + // accumulate + bytesReceived += data.length; + ++receivedDataObject; + } + + // remove receivedNSDataObject from dataArray + [_dataArray removeObjectsInRange:NSMakeRange(0, receivedDataObject)]; + self.bytesReceived -= bytesReceived; + return bytesReceived; +} + +- (void)dealloc { + [_dataArray release]; + [super dealloc]; + DLLOG("Destruct DownloadTaskWrapper %p", self); +} + +@end + +@implementation DownloaderAppleImpl +- (id)init:(const cc::network::DownloaderApple *)o hints:(const cc::network::DownloaderHints &)hints { + DLLOG("Construct DownloaderAppleImpl %p", self); + // save outer task ref + _outer = o; + _hints = hints; + // create task dictionary + self.taskDict = [NSMutableDictionary dictionary]; + +#if CC_PLATFORM == CC_PLATFORM_IOS + // create backgroundSession for iOS to support background download + self.downloadSession = [self backgroundURLSession]; + + // cancel and save last running tasks + [self.downloadSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { + for (NSURLSessionDownloadTask *downloadTask in downloadTasks) { + // cancelTask with resume Data + if ((long)downloadTask.state == 0) { + [(NSURLSessionDownloadTask *)downloadTask cancelByProducingResumeData:^(NSData *resumeData) { + if(downloadTask.originalRequest.URL.absoluteString) { + NSString *tempFilePathWithHash = [NSString stringWithFormat:@"%s%lu%s", cc::FileUtils::getInstance()->getWritablePath().c_str(), (unsigned long)[downloadTask.originalRequest.URL.absoluteString hash], _hints.tempFileNameSuffix.c_str()]; + [resumeData writeToFile:tempFilePathWithHash atomically:YES]; + } else if (downloadTask.currentRequest.URL.absoluteString) { + NSString *tempFilePathWithHash = [NSString stringWithFormat:@"%s%lu%s", cc::FileUtils::getInstance()->getWritablePath().c_str(), (unsigned long)[downloadTask.currentRequest.URL.absoluteString hash], _hints.tempFileNameSuffix.c_str()]; + [resumeData writeToFile:tempFilePathWithHash atomically:YES]; + } + }]; + } + } + }]; +#else + NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; + self.downloadSession = [NSURLSession sessionWithConfiguration:defaultConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]]; +#endif + return self; +} + +#pragma mark - backgroundURLSession +- (NSURLSession *)backgroundURLSession { + // use for backgroundSession global unique identifier, remove it if use singleton instance in the future. + static int sessionId = 0; + // Single thread delegate mode + // NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + // queue.maxConcurrentOperationCount = 1; + NSString* identifierStr = [NSString stringWithFormat:@"%s%d", "BackgroundDownloadIdentifier" , sessionId]; + NSURLSessionConfiguration *backgroudConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifierStr]; + sessionId++; + return [NSURLSession sessionWithConfiguration:backgroudConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]]; +} + +- (const cc::network::DownloaderHints &)getHints { + return _hints; +} + +- (NSURLSessionDataTask *)createDataTask:(std::shared_ptr &)task { + const char *urlStr = task->requestURL.c_str(); + DLLOG("DownloaderAppleImpl createDataTask: %s", urlStr); + NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:urlStr]]; + NSURLRequest *request = nil; + if (_hints.timeoutInSeconds > 0) { + request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:(NSTimeInterval)_hints.timeoutInSeconds]; + } else { + request = [NSURLRequest requestWithURL:url]; + } + NSURLSessionDataTask *ocTask = [self.downloadSession dataTaskWithRequest:request]; + DownloadTaskWrapper *taskWrapper = [[DownloadTaskWrapper alloc] init:task]; + [self.taskDict setObject:taskWrapper forKey:ocTask]; + [taskWrapper release]; + + if (_taskQueue.size() < _hints.countOfMaxProcessingTasks) { + [ocTask resume]; + _taskQueue.push(nil); + } else { + _taskQueue.push(ocTask); + } + return ocTask; +}; + +- (NSURLSessionDownloadTask *)createDownloadTask:(std::shared_ptr &)task { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + NSString *requesetURL = [NSString stringWithFormat:@"%s", task->requestURL.c_str()]; + [dict setObject:@"YES" forKey:requesetURL]; + NSString *savaPath = [NSString stringWithFormat:@"%s", (cc::FileUtils::getInstance()->getWritablePath() + "resumeData.plist").c_str()]; + [dict writeToFile:savaPath atomically:YES]; + + const char *urlStr = task->requestURL.c_str(); + DLLOG("DownloaderAppleImpl createDownloadTask: %s", urlStr); + NSString *resourcePath = [NSString stringWithUTF8String:urlStr]; + NSString *encodePath = [resourcePath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "].invertedSet]; + NSURL *url = [NSURL URLWithString:encodePath]; + NSMutableURLRequest *request = nil; + if (_hints.timeoutInSeconds > 0) { + request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:(NSTimeInterval)_hints.timeoutInSeconds]; + } else { + request = [NSMutableURLRequest requestWithURL:url]; + } + for (auto it = task->header.begin(); it != task->header.end(); ++it) { + NSString *keyStr = [NSString stringWithUTF8String:it->first.c_str()]; + NSString *valueStr = [NSString stringWithUTF8String:it->second.c_str()]; + [request setValue:valueStr forHTTPHeaderField:keyStr]; + } + NSString *tempFilePath = [NSString stringWithFormat:@"%s%s", task->storagePath.c_str(), _hints.tempFileNameSuffix.c_str()]; + NSData *resumeData = [NSData dataWithContentsOfFile:tempFilePath]; + + NSString *tempFilePathWithHash = [NSString stringWithFormat:@"%s%lu%s", cc::FileUtils::getInstance()->getWritablePath().c_str(), (unsigned long)[url hash], _hints.tempFileNameSuffix.c_str()]; + NSData *resumeData2 = [NSData dataWithContentsOfFile:tempFilePathWithHash]; + + NSURLSessionDownloadTask *ocTask = nil; + if (resumeData && task->header.size() <= 0) { + ocTask = [self.downloadSession downloadTaskWithResumeData:resumeData]; + } else if (resumeData2 && task->header.size() <= 0) { + ocTask = [self.downloadSession downloadTaskWithResumeData:resumeData2]; + } else { + ocTask = [self.downloadSession downloadTaskWithRequest:request]; + } + + DownloadTaskWrapper *taskWrapper = [[DownloadTaskWrapper alloc] init:task]; + [self.taskDict setObject:taskWrapper forKey:ocTask]; + [taskWrapper release]; + + if (_taskQueue.size() < _hints.countOfMaxProcessingTasks) { + [ocTask resume]; + _taskQueue.push(nil); + } else { + _taskQueue.push(ocTask); + } + return ocTask; +}; + +- (void)saveResumeData:(NSData *)resumeData forTaskStoragePath:(const ccstd::string &) path { + NSString *tempFilePath = [NSString stringWithFormat:@"%s%s", path.c_str(), _hints.tempFileNameSuffix.c_str()]; + NSString *tempFileDir = [tempFilePath stringByDeletingLastPathComponent]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDir = false; + if ([fileManager fileExistsAtPath:tempFileDir isDirectory:&isDir]) { + if (NO == isDir) { + // REFINE: the directory is a file, not a directory, how to echo to developer? + DLLOG("DownloaderAppleImpl temp dir is a file!"); + return; + } + } else { + NSURL *tempFileURL = [NSURL fileURLWithPath:tempFileDir]; + if (NO == [fileManager createDirectoryAtURL:tempFileURL withIntermediateDirectories:YES attributes:nil error:nil]) { + // create directory failed + DLLOG("DownloaderAppleImpl create temp dir failed"); + return; + } + } + [resumeData writeToFile:tempFilePath atomically:YES]; +} + +- (void)abort:(NSURLSessionTask *)task { + // cancel all download task + NSEnumerator *enumeratorKey = [self.taskDict keyEnumerator]; + for (NSURLSessionTask *taskKey in enumeratorKey) { + if (task != taskKey) { + continue; + } + DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:taskKey]; + + // no resume support for a data task + ccstd::string storagePath = [wrapper get]->storagePath; + if (storagePath.length() == 0) { + [taskKey cancel]; + } else { + [(NSURLSessionDownloadTask *)taskKey cancelByProducingResumeData:^(NSData *resumeData) { + if (resumeData) { + [self saveResumeData:resumeData forTaskStoragePath:storagePath]; + } + }]; + } + break; + } +} + +- (void)doDestroy { + // cancel all download task + NSEnumerator *enumeratorKey = [self.taskDict keyEnumerator]; + for (NSURLSessionDownloadTask *task in enumeratorKey) { + DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:task]; + + // no resume support for a data task + ccstd::string storagePath = [wrapper get]->storagePath; + if (storagePath.length() == 0) { + [task cancel]; + } else { + [task cancelByProducingResumeData:^(NSData *resumeData) { + if (resumeData) { + [self saveResumeData:resumeData forTaskStoragePath:storagePath]; + } + }]; + } + } + _outer = nullptr; + + while (!_taskQueue.empty()) + _taskQueue.pop(); + + [self.downloadSession invalidateAndCancel]; + [self release]; +} + +- (void)dealloc { + DLLOG("Destruct DownloaderAppleImpl %p", self); + self.downloadSession = nil; + self.taskDict = nil; + [super dealloc]; +} +#pragma mark - NSURLSessionTaskDelegate methods + +//@optional + +/* An HTTP request is attempting to perform a redirection to a different + * URL. You must invoke the completion routine to allow the + * redirection, allow the redirection with a modified request, or + * pass nil to the completionHandler to cause the body of the redirection + * response to be delivered as the payload of this request. The default + * is to follow redirections. + * + * For tasks in background sessions, redirections will always be followed and this method will not be called. + */ +//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task +//willPerformHTTPRedirection:(NSHTTPURLResponse *)response +// newRequest:(NSURLRequest *)request +// completionHandler:(void (^)(NSURLRequest *))completionHandler; + +/* The task has received a request specific authentication challenge. + * If this delegate is not implemented, the session specific authentication challenge + * will *NOT* be called and the behavior will be the same as using the default handling + * disposition. + */ +//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task +//didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge +// completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler; + +/* Sent if a task requires a new, unopened body stream. This may be + * necessary when authentication has failed for any request that + * involves a body stream. + */ +//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task +// needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler; + +/* Sent periodically to notify the delegate of upload progress. This + * information is also available as properties of the task. + */ +//- (void)URLSession:(NSURLSession *)session task :(NSURLSessionTask *)task +// didSendBodyData:(uint32_t)bytesSent +// totalBytesSent:(uint32_t)totalBytesSent +// totalBytesExpectedToSend:(uint32_t)totalBytesExpectedToSend; + +/* Sent as the last message related to a specific task. Error may be + * nil, which implies that no error occurred and this task is complete. + */ +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task + didCompleteWithError:(NSError *)error { + DLLOG("DownloaderAppleImpl task: \"%s\" didCompleteWithError: %d errDesc: %s", [task.originalRequest.URL.absoluteString cStringUsingEncoding:NSUTF8StringEncoding], (error ? static_cast(error.code) : 0), [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding]); + + // clean wrapper C++ object + DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:task]; + + if (wrapper) { + if (_outer) { + if (error) { + int errorCode = static_cast(error.code); + NSString *errorMsg = error.localizedDescription; + if (error.code == NSURLErrorCancelled) { + //cancel + errorCode = cc::network::DownloadTask::ERROR_ABORT; + errorMsg = @"downloadFile:abort"; + } + ccstd::vector buf; // just a placeholder + _outer->onTaskFinish(*[wrapper get], + cc::network::DownloadTask::ERROR_IMPL_INTERNAL, + errorCode, + [errorMsg cStringUsingEncoding:NSUTF8StringEncoding], + buf); + } else if (![wrapper get]->storagePath.length()) { + // call onTaskFinish for a data task + // (for a file download task, callback is called in didFinishDownloadingToURL) + ccstd::string errorString; + + const uint32_t buflen = [wrapper totalBytesReceived]; + ccstd::vector data((size_t)buflen); + char *buf = (char *)data.data(); + [wrapper transferDataToBuffer:buf lengthOfBuffer:buflen]; + + _outer->onTaskFinish(*[wrapper get], + cc::network::DownloadTask::ERROR_NO_ERROR, + 0, + errorString, + data); + } else { + NSInteger statusCode = ((NSHTTPURLResponse *)task.response).statusCode; + + // Check for error status code + if (statusCode >= 400) { + ccstd::vector buf; // just a placeholder + const char *originalURL = [task.originalRequest.URL.absoluteString cStringUsingEncoding:NSUTF8StringEncoding]; + ccstd::string errorMessage = cc::StringUtils::format("Downloader: Failed to download %s with status code (%d)", originalURL, static_cast(statusCode)); + + _outer->onTaskFinish(*[wrapper get], + cc::network::DownloadTask::ERROR_IMPL_INTERNAL, + 0, + errorMessage, + buf); + } + } + } + } + if (error) { + if ([error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]) { + NSData *resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]; + + if([error.userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]) { + NSString *url = [error.userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]; + NSLog(@"url:%@",url); + NSString *tempFilePath = [NSString stringWithFormat:@"%s%lu%s", cc::FileUtils::getInstance()->getWritablePath().c_str(), (unsigned long)[url hash], _hints.tempFileNameSuffix.c_str()]; + [resumeData writeToFile:tempFilePath atomically:YES]; + } + } + } + [self.taskDict removeObjectForKey:task]; + + while (!_taskQueue.empty() && _taskQueue.front() == nil) { + _taskQueue.pop(); + } + if (!_taskQueue.empty()) { + [_taskQueue.front() resume]; + _taskQueue.pop(); + } +} + +#pragma mark - NSURLSessionDataDelegate methods +//@optional +/* The task has received a response and no further messages will be + * received until the completion block is called. The disposition + * allows you to cancel a request or to turn a data task into a + * download task. This delegate message is optional - if you do not + * implement it, you can get the response as a property of the task. + * + * This method will not be called for background upload tasks (which cannot be converted to download tasks). + */ +//- (void)URLSession:(NSURLSession *)session dataTask :(NSURLSessionDataTask *)dataTask +// didReceiveResponse:(NSURLResponse *)response +// completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler +//{ +// DLLOG("DownloaderAppleImpl dataTask: response:%s", [response.description cStringUsingEncoding:NSUTF8StringEncoding]); +// completionHandler(NSURLSessionResponseAllow); +//} + +/* Notification that a data task has become a download task. No + * future messages will be sent to the data task. + */ +//- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask +//didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask; + +/* Sent when data is available for the delegate to consume. It is + * assumed that the delegate will retain and not copy the data. As + * the data may be discontiguous, you should use + * [NSData enumerateByteRangesUsingBlock:] to access it. + */ +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + DLLOG("DownloaderAppleImpl dataTask: \"%s\" didReceiveDataLen %d", + [dataTask.originalRequest.URL.absoluteString cStringUsingEncoding:NSUTF8StringEncoding], + static_cast(data.length)); + if (nullptr == _outer) { + return; + } + DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:dataTask]; + [wrapper addData:data]; + + std::function transferDataToBuffer = + [wrapper](void *buffer, uint32_t bufLen) -> uint32_t { + return [wrapper transferDataToBuffer:buffer lengthOfBuffer:bufLen]; + }; + + if (wrapper) { + _outer->onTaskProgress(*[wrapper get], + wrapper.bytesReceived, + wrapper.totalBytesReceived, + static_cast(dataTask.countOfBytesExpectedToReceive), + transferDataToBuffer); + } +} + +/* Invoke the completion routine with a valid NSCachedURLResponse to + * allow the resulting data to be cached, or pass nil to prevent + * caching. Note that there is no guarantee that caching will be + * attempted for a given resource, and you should not rely on this + * message to receive the resource data. + */ +//- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask +// willCacheResponse:(NSCachedURLResponse *)proposedResponse +// completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler; + +#pragma mark - NSURLSessionDownloadDelegate methods + +/* Sent when a download task that has completed a download. The delegate should + * copy or move the file at the given location to a new location as it will be + * removed when the delegate message returns. URLSession:task:didCompleteWithError: will + * still be called. + */ +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask + didFinishDownloadingToURL:(NSURL *)location { + DLLOG("DownloaderAppleImpl downloadTask: \"%s\" didFinishDownloadingToURL %s", + [downloadTask.originalRequest.URL.absoluteString cStringUsingEncoding:NSUTF8StringEncoding], + [location.absoluteString cStringUsingEncoding:NSUTF8StringEncoding]); + if (nullptr == _outer) { + return; + } + + // On iOS 9 a response with status code 4xx(Client Error) or 5xx(Server Error) + // might end up calling this delegate method, saving the error message to the storage path + // and treating this download task as a successful one, so we need to check the status code here + NSInteger statusCode = ((NSHTTPURLResponse *)downloadTask.response).statusCode; + if (statusCode >= 400) { + return; + } + + DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:downloadTask]; + if (wrapper) { + const char *storagePath = [wrapper get]->storagePath.c_str(); + NSString *destPath = [NSString stringWithUTF8String:storagePath]; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *destURL = nil; + + do { + if ([destPath hasPrefix:@"file://"]) { + break; + } + + if ('/' == [destPath characterAtIndex:0]) { + destURL = [NSURL fileURLWithPath:destPath]; + break; + } + + // relative path, store to user domain default + NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]; + NSURL *documentsDirectory = URLs[0]; + destURL = [documentsDirectory URLByAppendingPathComponent:destPath]; + } while (0); + + // Make sure we overwrite anything that's already there + [fileManager removeItemAtURL:destURL error:NULL]; + + // copy file to dest location + int errorCode = cc::network::DownloadTask::ERROR_NO_ERROR; + int errorCodeInternal = 0; + ccstd::string errorString; + + NSError *error = nil; + if ([fileManager copyItemAtURL:location toURL:destURL error:&error]) { + // success, remove temp file if it exist + if (_hints.tempFileNameSuffix.length()) { + NSString *tempStr = [[destURL absoluteString] stringByAppendingFormat:@"%s", _hints.tempFileNameSuffix.c_str()]; + NSURL *tempDestUrl = [NSURL URLWithString:tempStr]; + [fileManager removeItemAtURL:tempDestUrl error:NULL]; + } + } else { + errorCode = cc::network::DownloadTask::ERROR_FILE_OP_FAILED; + if (error) { + errorCodeInternal = static_cast(error.code); + errorString = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding]; + } + } + ccstd::vector buf; // just a placeholder + _outer->onTaskFinish(*[wrapper get], errorCode, errorCodeInternal, errorString, buf); + + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + NSString *requesetURL = [NSString stringWithFormat:@"%s", [wrapper get]->requestURL.c_str()]; + [dict removeObjectForKey:requesetURL]; + NSString *savaPath = [NSString stringWithFormat:@"%s", (cc::FileUtils::getInstance()->getWritablePath() + "resumeData.plist").c_str()]; + [dict writeToFile:savaPath atomically:YES]; + } +} + +// @optional +/* Sent periodically to notify the delegate of download progress. */ +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask + didWriteData:(int64_t)bytesWritten + totalBytesWritten:(int64_t)totalBytesWritten + totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { + // NSLog(@"DownloaderAppleImpl downloadTask: \"%@\" received: %lld total: %lld", downloadTask.originalRequest.URL, totalBytesWritten, totalBytesExpectedToWrite); + + if (nullptr == _outer) { + return; + } + + DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:downloadTask]; + std::function transferDataToBuffer; // just a placeholder + if (wrapper) { + _outer->onTaskProgress(*[wrapper get], static_cast(bytesWritten), static_cast(totalBytesWritten), static_cast(totalBytesExpectedToWrite), transferDataToBuffer); + } +} + +/* Sent when a download has been resumed. If a download failed with an + * error, the -userInfo dictionary of the error will contain an + * NSURLSessionDownloadTaskResumeData key, whose value is the resume + * data. + */ +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask + didResumeAtOffset:(int64_t)fileOffset + expectedTotalBytes:(int64_t)expectedTotalBytes { + NSLog(@"[REFINE]DownloaderAppleImpl downloadTask: \"%@\" didResumeAtOffset: %lld", downloadTask.originalRequest.URL, fileOffset); +} + +@end diff --git a/cocos/network/DownloaderImpl.h b/cocos/network/DownloaderImpl.h new file mode 100644 index 0000000..731e144 --- /dev/null +++ b/cocos/network/DownloaderImpl.h @@ -0,0 +1,78 @@ +/**************************************************************************** + Copyright (c) 2015-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/Log.h" +#include "base/Macros.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" + +//#define CC_DOWNLOADER_DEBUG +#ifdef CC_DOWNLOADER_DEBUG + #define DLLOG(format, ...) CC_LOG_DEBUG(format, ##__VA_ARGS__) +#else + #define DLLOG(...) \ + do { \ + } while (0) +#endif + +namespace cc { +namespace network { +class DownloadTask; + +class CC_DLL IDownloadTask { +public: + virtual ~IDownloadTask() = default; +}; + +class IDownloaderImpl { +public: + virtual ~IDownloaderImpl() = default; + + std::function &transferDataToBuffer)> + onTaskProgress; + + std::function &data)> + onTaskFinish; + + virtual IDownloadTask *createCoTask(std::shared_ptr &task) = 0; + + virtual void abort(const std::unique_ptr &task) = 0; +}; + +} // namespace network +} // namespace cc diff --git a/cocos/network/HttpAsynConnection-apple.h b/cocos/network/HttpAsynConnection-apple.h new file mode 100644 index 0000000..f3fa046 --- /dev/null +++ b/cocos/network/HttpAsynConnection-apple.h @@ -0,0 +1,70 @@ +/**************************************************************************** + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef __HTTPASYNCONNECTION_H__ +#define __HTTPASYNCONNECTION_H__ +/// @cond DO_NOT_SHOW + +#if (CC_PLATFORM == CC_PLATFORM_MACOS) || (CC_PLATFORM == CC_PLATFORM_IOS) + + #import + #import +/// @cond +@interface HttpAsynConnection : NSObject { + NSURLSession *session; +} + +// The original URL to download. Due to redirects the actual content may come from another URL +@property (strong) NSString *srcURL; + +@property (strong) NSString *sslFile; + +@property (copy) NSDictionary *responseHeader; + +@property (strong) NSMutableData *responseData; + +@property (readonly) NSInteger getDataTime; + +@property (readonly) NSInteger responseCode; +@property (readonly) NSString *statusString; + +@property (strong) NSError *responseError; +@property (strong) NSError *connError; + +@property (strong) NSURLSessionDataTask *task; + +@property bool finish; + +@property (strong) NSRunLoop *runLoop; + +// instructs the class to start the request. +- (void)startRequest:(NSURLRequest *)request; + +@end + +#endif // #if (CC_PLATFORM == CC_PLATFORM_MACOS) || (CC_PLATFORM == CC_PLATFORM_IOS) + +/// @endcond +#endif //__HTTPASYNCONNECTION_H__ diff --git a/cocos/network/HttpAsynConnection-apple.m b/cocos/network/HttpAsynConnection-apple.m new file mode 100755 index 0000000..f22ef68 --- /dev/null +++ b/cocos/network/HttpAsynConnection-apple.m @@ -0,0 +1,184 @@ +/**************************************************************************** + Copyright (c) 2013-2017 Chukong Technologies Inc. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#if (CC_PLATFORM == CC_PLATFORM_MACOS) || (CC_PLATFORM == CC_PLATFORM_IOS) + + #import "network/HttpAsynConnection-apple.h" + +@interface HttpAsynConnection () + +@property (readwrite) NSString *statusString; + +- (BOOL)shouldTrustProtectionSpace:(NSURLProtectionSpace *)protectionSpace; + +@end + +@implementation HttpAsynConnection + +@synthesize srcURL = srcURL; +@synthesize sslFile = sslFile; +@synthesize responseHeader = responseHeader; +@synthesize responseData = responseData; +@synthesize getDataTime = getDataTime; +@synthesize responseCode = responseCode; +@synthesize statusString = statusString; +@synthesize responseError = responseError; +@synthesize connError = connError; +@synthesize task = task; +@synthesize finish = finish; +@synthesize runLoop = runLoop; + +- (void)dealloc { + [srcURL release]; + [sslFile release]; + [responseHeader release]; + [responseData release]; + [responseError release]; + [runLoop release]; + [connError release]; + [super dealloc]; +} + +- (void)startRequest:(NSURLRequest *)request { + #ifdef CC_DEBUG + // NSLog(@"Starting to load %@", srcURL); + #endif + + finish = false; + + self.responseData = [NSMutableData data]; + getDataTime = 0; + + self.responseError = nil; + self.connError = nil; + + session = [NSURLSession sharedSession]; + + task = [session dataTaskWithRequest:request + completionHandler:^(NSData *data, NSURLResponse *response, NSError *_Nullable error) { + if (error != nil) { + self.connError = error; + finish = true; + return; + } + if (response != nil) { + #ifdef CC_DEBUG + // NSLog(@"Received response from request to url %@", srcURL); + #endif + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + //NSLog(@"All headers = %@", [httpResponse allHeaderFields]); + self.responseHeader = [httpResponse allHeaderFields]; + + responseCode = httpResponse.statusCode; + self.statusString = [NSHTTPURLResponse localizedStringForStatusCode:responseCode]; + if (responseCode == 200) + self.statusString = @"OK"; + + /*The individual values of the numeric status codes defined for HTTP/1.1 + | "200" ; OK + | "201" ; Created + | "202" ; Accepted + | "203" ; Non-Authoritative Information + | "204" ; No Content + | "205" ; Reset Content + | "206" ; Partial Content + */ + if (responseCode < 200 || responseCode >= 300) { // something went wrong, abort the whole thing + self.responseError = [NSError errorWithDomain:@"CCBackendDomain" + code:responseCode + userInfo:@{NSLocalizedDescriptionKey : @"Bad HTTP Response Code"}]; + } + + [responseData setLength:0]; + } + + if (data != nil) { + [responseData appendData:data]; + getDataTime++; + } + + finish = true; + }]; + [task resume]; +} + +//Server evaluates client's certificate +- (BOOL)shouldTrustProtectionSpace:(NSURLProtectionSpace *)protectionSpace { + if (sslFile == nil) + return YES; + //load the bundle client certificate + NSString *certPath = [[NSBundle mainBundle] pathForResource:sslFile ofType:@"der"]; + NSData *certData = [[NSData alloc] initWithContentsOfFile:certPath]; + CFDataRef certDataRef = (CFDataRef)certData; + SecCertificateRef cert = SecCertificateCreateWithData(NULL, certDataRef); + + //Establish a chain of trust anchored on our bundled certificate + CFArrayRef certArrayRef = CFArrayCreate(NULL, (void *)&cert, 1, NULL); + SecTrustRef serverTrust = protectionSpace.serverTrust; + SecTrustSetAnchorCertificates(serverTrust, certArrayRef); + + //Verify that trust + SecTrustResultType trustResult; + SecTrustEvaluate(serverTrust, &trustResult); + + if (trustResult == kSecTrustResultRecoverableTrustFailure) { + CFDataRef errDataRef = SecTrustCopyExceptions(serverTrust); + SecTrustSetExceptions(serverTrust, errDataRef); + SecTrustEvaluate(serverTrust, &trustResult); + CFRelease(errDataRef); + } + [certData release]; + if (cert) { + CFRelease(cert); + } + if (certArrayRef) { + CFRelease(certArrayRef); + } + //Did our custom trust chain evaluate successfully? + return trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed; +} + +- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable credential))completionHandler { + id sender = challenge.sender; + NSURLProtectionSpace *protectionSpace = challenge.protectionSpace; + + //Should server trust client? + if ([self shouldTrustProtectionSpace:protectionSpace]) { + SecTrustRef trust = [protectionSpace serverTrust]; + // + // SecCertificateRef certificate = SecTrustGetCertificateAtIndex(trust, 0); + // + // NSData *serverCertificateData = (NSData*)SecCertificateCopyData(certificate); + // NSString *serverCertificateDataHash = [[serverCertificateData base64EncodedString] ] + NSURLCredential *credential = [NSURLCredential credentialForTrust:trust]; + completionHandler(NSURLSessionAuthChallengeUseCredential, credential); + } else { + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + } +} + +@end + +#endif // #if (CC_PLATFORM == CC_PLATFORM_MACOS) || (CC_PLATFORM == CC_PLATFORM_IOS) diff --git a/cocos/network/HttpClient-apple.mm b/cocos/network/HttpClient-apple.mm new file mode 100644 index 0000000..bcafaf9 --- /dev/null +++ b/cocos/network/HttpClient-apple.mm @@ -0,0 +1,529 @@ +/**************************************************************************** + Copyright (c) 2012 greathqy + Copyright (c) 2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "network/HttpClient.h" + +#include + +#import "network/HttpAsynConnection-apple.h" +#include "network/HttpCookie.h" +#include "platform/FileUtils.h" +#include "application/ApplicationManager.h" +#include "base/Scheduler.h" +#include "base/memory/Memory.h" +#include "base/ThreadPool.h" + +namespace cc { + +namespace network { + +static HttpClient *_httpClient = nullptr; // pointer to singleton +static LegacyThreadPool *gThreadPool = nullptr; + +static int processTask(HttpClient *client, HttpRequest *request, NSString *requestType, void *stream, long *errorCode, void *headerStream, char *errorBuffer); + +// Worker thread +void HttpClient::networkThread() { + increaseThreadCount(); + + while (true) @autoreleasepool { + HttpRequest *request; + + // step 1: send http request if the requestQueue isn't empty + { + std::lock_guard lock(_requestQueueMutex); + while (_requestQueue.empty()) { + _sleepCondition.wait(_requestQueueMutex); + } + request = _requestQueue.at(0); + _requestQueue.erase(0); + } + + if (request == _requestSentinel) { + break; + } + + // Create a HttpResponse object, the default setting is http access failed + HttpResponse *response = ccnew HttpResponse(request); + response->addRef(); + + processResponse(response, _responseMessage); + + // add response packet into queue + _responseQueueMutex.lock(); + _responseQueue.pushBack(response); + _responseQueueMutex.unlock(); + + _schedulerMutex.lock(); + if (auto sche = _scheduler.lock()) { + sche->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); + } + _schedulerMutex.unlock(); + } + + // cleanup: if worker thread received quit signal, clean up un-completed request queue + _requestQueueMutex.lock(); + _requestQueue.clear(); + _requestQueueMutex.unlock(); + + _responseQueueMutex.lock(); + _responseQueue.clear(); + _responseQueueMutex.unlock(); + + decreaseThreadCountAndMayDeleteThis(); +} + +// Worker thread +void HttpClient::networkThreadAlone(HttpRequest *request, HttpResponse *response) { + increaseThreadCount(); + + char responseMessage[RESPONSE_BUFFER_SIZE] = {0}; + processResponse(response, responseMessage); + + _schedulerMutex.lock(); + if (auto sche = _scheduler.lock()) { + sche->performFunctionInCocosThread([this, response, request] { + const ccHttpRequestCallback &callback = request->getResponseCallback(); + + if (callback != nullptr) { + callback(this, response); + } + + response->release(); + // do not release in other thread + request->release(); + }); + } + _schedulerMutex.unlock(); + decreaseThreadCountAndMayDeleteThis(); +} + +//Process Request +static int processTask(HttpClient *client, HttpRequest *request, NSString *requestType, void *stream, long *responseCode, void *headerStream, char *errorBuffer) { + if (nullptr == client) { + strcpy(errorBuffer, "client object is invalid"); + return 0; + } + + //create request with url + NSString *urlstring = [NSString stringWithUTF8String:request->getUrl()]; + NSURL *url = [NSURL URLWithString:urlstring]; + + NSMutableURLRequest *nsrequest = [NSMutableURLRequest requestWithURL:url + cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData + timeoutInterval:request->getTimeout()]; + + //set request type + [nsrequest setHTTPMethod:requestType]; + + /* get custom header data (if set) */ + ccstd::vector headers = request->getHeaders(); + if (!headers.empty()) { + /* append custom headers one by one */ + for (auto &header : headers) { + unsigned long i = header.find(':', 0); + unsigned long length = header.size(); + ccstd::string field = header.substr(0, i); + ccstd::string value = header.substr(i + 1); + // trim \n at the end of the string + if (!value.empty() && value[value.size() - 1] == '\n') { + value.erase(value.size() - 1); + } + // trim leading space (header is field: value format) + if (!value.empty() && value[0] == ' ') { + value.erase(0, 1); + } + NSString *headerField = [NSString stringWithUTF8String:field.c_str()]; + NSString *headerValue = [NSString stringWithUTF8String:value.c_str()]; + [nsrequest setValue:headerValue forHTTPHeaderField:headerField]; + } + } + + //if request type is post or put,set header and data + if ([requestType isEqual:@"POST"] || [requestType isEqual:@"PUT"] || [requestType isEqual:@"PATCH"]) { + char *requestDataBuffer = request->getRequestData(); + if (nullptr != requestDataBuffer && 0 != request->getRequestDataSize()) { + NSData *postData = [NSData dataWithBytes:requestDataBuffer length:request->getRequestDataSize()]; + [nsrequest setHTTPBody:postData]; + } + } + + //read cookie properties from file and set cookie + ccstd::string cookieFilename = client->getCookieFilename(); + if (!cookieFilename.empty() && nullptr != client->getCookie()) { + const CookiesInfo *cookieInfo = client->getCookie()->getMatchCookie(request->getUrl()); + if (cookieInfo != nullptr) { + NSString *domain = [NSString stringWithCString:cookieInfo->domain.c_str() encoding:[NSString defaultCStringEncoding]]; + NSString *path = [NSString stringWithCString:cookieInfo->path.c_str() encoding:[NSString defaultCStringEncoding]]; + NSString *value = [NSString stringWithCString:cookieInfo->value.c_str() encoding:[NSString defaultCStringEncoding]]; + NSString *name = [NSString stringWithCString:cookieInfo->name.c_str() encoding:[NSString defaultCStringEncoding]]; + + // create the properties for a cookie + NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys:name, NSHTTPCookieName, + value, NSHTTPCookieValue, path, NSHTTPCookiePath, + domain, NSHTTPCookieDomain, + nil]; + + // create the cookie from the properties + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:properties]; + + // add the cookie to the cookie storage + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; + } + } + + HttpAsynConnection *httpAsynConn = [[HttpAsynConnection new] autorelease]; + httpAsynConn.srcURL = urlstring; + httpAsynConn.sslFile = nil; + + ccstd::string sslCaFileName = client->getSSLVerification(); + if (!sslCaFileName.empty()) { + long len = sslCaFileName.length(); + long pos = sslCaFileName.rfind('.', len - 1); + + httpAsynConn.sslFile = [NSString stringWithUTF8String:sslCaFileName.substr(0, pos).c_str()]; + } + [httpAsynConn startRequest:nsrequest]; + + while (httpAsynConn.finish != true) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + } + + //if http connection return error + if (httpAsynConn.connError != nil) { + NSString *errorString = [httpAsynConn.connError localizedDescription]; + strcpy(errorBuffer, [errorString UTF8String]); + return 0; + } + + //if http response got error, just log the error + if (httpAsynConn.responseError != nil) { + NSString *errorString = [httpAsynConn.responseError localizedDescription]; + strcpy(errorBuffer, [errorString UTF8String]); + } + + *responseCode = httpAsynConn.responseCode; + + //add cookie to cookies vector + if (!cookieFilename.empty()) { + NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:httpAsynConn.responseHeader forURL:url]; + for (NSHTTPCookie *cookie in cookies) { + //NSLog(@"Cookie: %@", cookie); + NSString *domain = cookie.domain; + //BOOL session = cookie.sessionOnly; + NSString *path = cookie.path; + BOOL secure = cookie.isSecure; + NSDate *date = cookie.expiresDate; + NSString *name = cookie.name; + NSString *value = cookie.value; + + CookiesInfo cookieInfo; + cookieInfo.domain = [domain cStringUsingEncoding:NSUTF8StringEncoding]; + cookieInfo.path = [path cStringUsingEncoding:NSUTF8StringEncoding]; + cookieInfo.secure = (secure == YES) ? true : false; + cookieInfo.expires = [[NSString stringWithFormat:@"%ld", (long)[date timeIntervalSince1970]] cStringUsingEncoding:NSUTF8StringEncoding]; + cookieInfo.name = [name cStringUsingEncoding:NSUTF8StringEncoding]; + cookieInfo.value = [value cStringUsingEncoding:NSUTF8StringEncoding]; + cookieInfo.tailmatch = true; + + client->getCookie()->updateOrAddCookie(&cookieInfo); + } + } + + //handle response header + NSMutableString *header = [NSMutableString string]; + [header appendFormat:@"HTTP/1.1 %ld %@\n", (long)httpAsynConn.responseCode, httpAsynConn.statusString]; + for (id key in httpAsynConn.responseHeader) { + [header appendFormat:@"%@: %@\n", key, [httpAsynConn.responseHeader objectForKey:key]]; + } + if (header.length > 0) { + NSRange range = NSMakeRange(header.length - 1, 1); + [header deleteCharactersInRange:range]; + } + NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding]; + ccstd::vector *headerBuffer = (ccstd::vector *)headerStream; + const void *headerptr = [headerData bytes]; + long headerlen = [headerData length]; + headerBuffer->insert(headerBuffer->end(), (char *)headerptr, (char *)headerptr + headerlen); + + //handle response data + ccstd::vector *recvBuffer = (ccstd::vector *)stream; + const void *ptr = [httpAsynConn.responseData bytes]; + long len = [httpAsynConn.responseData length]; + recvBuffer->insert(recvBuffer->end(), (char *)ptr, (char *)ptr + len); + + return 1; +} + +// HttpClient implementation +HttpClient *HttpClient::getInstance() { + if (_httpClient == nullptr) { + _httpClient = ccnew HttpClient(); + } + + return _httpClient; +} + +void HttpClient::destroyInstance() { + if (nullptr == _httpClient) { + CC_LOG_DEBUG("HttpClient singleton is nullptr"); + return; + } + + CC_LOG_DEBUG("HttpClient::destroyInstance begin"); + + auto thiz = _httpClient; + _httpClient = nullptr; + + if (auto sche = thiz->_scheduler.lock()) { + sche->unscheduleAllForTarget(thiz); + } + thiz->_schedulerMutex.lock(); + thiz->_scheduler.reset(); + thiz->_schedulerMutex.unlock(); + + thiz->_requestQueueMutex.lock(); + thiz->_requestQueue.pushBack(thiz->_requestSentinel); + thiz->_requestQueueMutex.unlock(); + + thiz->_sleepCondition.notify_one(); + thiz->decreaseThreadCountAndMayDeleteThis(); + + CC_LOG_DEBUG("HttpClient::destroyInstance() finished!"); +} + +void HttpClient::enableCookies(const char *cookieFile) { + _cookieFileMutex.lock(); + if (cookieFile) { + _cookieFilename = ccstd::string(cookieFile); + _cookieFilename = FileUtils::getInstance()->fullPathForFilename(_cookieFilename); + } else { + _cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); + } + _cookieFileMutex.unlock(); + + if (nullptr == _cookie) { + _cookie = ccnew HttpCookie; + } + _cookie->setCookieFileName(_cookieFilename); + _cookie->readFile(); +} + +void HttpClient::setSSLVerification(const ccstd::string &caFile) { + std::lock_guard lock(_sslCaFileMutex); + _sslCaFilename = caFile; +} + +HttpClient::HttpClient() +: _isInited(false), + _timeoutForConnect(30), + _timeoutForRead(60), + _threadCount(0), + _cookie(nullptr) { + CC_LOG_DEBUG("In the constructor of HttpClient!"); + if (gThreadPool == nullptr) { + gThreadPool = LegacyThreadPool::newFixedThreadPool(4); + } + _requestSentinel = ccnew HttpRequest(); + _requestSentinel->addRef(); + memset(_responseMessage, 0, sizeof(char) * RESPONSE_BUFFER_SIZE); + _scheduler = CC_CURRENT_ENGINE()->getScheduler(); + increaseThreadCount(); +} + +HttpClient::~HttpClient() { + CC_SAFE_RELEASE(_requestSentinel); + if (!_cookieFilename.empty() && nullptr != _cookie) { + _cookie->writeFile(); + CC_SAFE_DELETE(_cookie); + } + CC_LOG_DEBUG("HttpClient destructor"); +} + +//Lazy create semaphore & mutex & thread +bool HttpClient::lazyInitThreadSemaphore() { + if (_isInited) { + return true; + } else { + auto t = std::thread(CC_CALLBACK_0(HttpClient::networkThread, this)); + t.detach(); + _isInited = true; + } + + return true; +} + +//Add a get task to queue +void HttpClient::send(HttpRequest *request) { + if (false == lazyInitThreadSemaphore()) { + return; + } + + if (!request) { + return; + } + + request->addRef(); + + _requestQueueMutex.lock(); + _requestQueue.pushBack(request); + _requestQueueMutex.unlock(); + + // Notify thread start to work + _sleepCondition.notify_one(); +} + +void HttpClient::sendImmediate(HttpRequest *request) { + if (!request) { + return; + } + + request->addRef(); + // Create a HttpResponse object, the default setting is http access failed + HttpResponse *response = ccnew HttpResponse(request); + response->addRef(); // NOTE: RefCounted object's reference count is changed to 0 now. so needs to addRef after ccnew. + + gThreadPool->pushTask([this, request, response](int /*tid*/) { HttpClient::networkThreadAlone(request, response); }); +} + +// Poll and notify main thread if responses exists in queue +void HttpClient::dispatchResponseCallbacks() { + // log("CCHttpClient::dispatchResponseCallbacks is running"); + //occurs when cocos thread fires but the network thread has already quited + HttpResponse *response = nullptr; + _responseQueueMutex.lock(); + if (!_responseQueue.empty()) { + response = _responseQueue.at(0); + _responseQueue.erase(0); + } + _responseQueueMutex.unlock(); + + if (response) { + HttpRequest *request = response->getHttpRequest(); + const ccHttpRequestCallback &callback = request->getResponseCallback(); + + if (callback != nullptr) { + callback(this, response); + } + + response->release(); + // do not release in other thread + request->release(); + } +} + +// Process Response +void HttpClient::processResponse(HttpResponse *response, char *responseMessage) { + auto request = response->getHttpRequest(); + long responseCode = -1; + int retValue = 0; + NSString *requestType = nil; + + // Process the request -> get response packet + switch (request->getRequestType()) { + case HttpRequest::Type::GET: // HTTP GET + requestType = @"GET"; + break; + + case HttpRequest::Type::POST: // HTTP POST + requestType = @"POST"; + break; + + case HttpRequest::Type::PUT: + requestType = @"PUT"; + break; + + case HttpRequest::Type::HEAD: + requestType = @"HEAD"; + break; + + case HttpRequest::Type::DELETE: + requestType = @"DELETE"; + break; + + case HttpRequest::Type::PATCH: + requestType = @"PATCH"; + break; + + default: + CC_ABORT(); + break; + } + + retValue = processTask(this, + request, + requestType, + response->getResponseData(), + &responseCode, + response->getResponseHeader(), + responseMessage); + + // write data to HttpResponse + response->setResponseCode(responseCode); + + if (retValue != 0) { + response->setSucceed(true); + } else { + response->setSucceed(false); + response->setErrorBuffer(responseMessage); + } +} + +void HttpClient::increaseThreadCount() { + _threadCountMutex.lock(); + ++_threadCount; + _threadCountMutex.unlock(); +} + +void HttpClient::decreaseThreadCountAndMayDeleteThis() { + bool needDeleteThis = false; + _threadCountMutex.lock(); + --_threadCount; + if (0 == _threadCount) { + needDeleteThis = true; + } + + _threadCountMutex.unlock(); + if (needDeleteThis) { + delete this; + } +} + +const ccstd::string &HttpClient::getCookieFilename() { + std::lock_guard lock(_cookieFileMutex); + return _cookieFilename; +} + +const ccstd::string &HttpClient::getSSLVerification() { + std::lock_guard lock(_sslCaFileMutex); + return _sslCaFilename; +} + +} // namespace network + +} // namespace cc diff --git a/cocos/network/HttpClient-java.cpp b/cocos/network/HttpClient-java.cpp new file mode 100644 index 0000000..86b1d94 --- /dev/null +++ b/cocos/network/HttpClient-java.cpp @@ -0,0 +1,969 @@ +/**************************************************************************** + Copyright (c) 2012 greathqy + Copyright (c) 2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "network/HttpClient.h" + +#include +#include +#include +#include "application/ApplicationManager.h" +#include "base/Log.h" +#include "base/ThreadPool.h" +#include "base/UTF8.h" +#include "base/std/container/queue.h" +#include "platform/FileUtils.h" +#include "platform/java/jni/JniHelper.h" + +#ifndef JCLS_HTTPCLIENT + #define JCLS_HTTPCLIENT "com/cocos/lib/CocosHttpURLConnection" +#endif + +namespace cc { + +namespace network { + +using HttpRequestHeaders = ccstd::vector; +using HttpRequestHeadersIter = HttpRequestHeaders::iterator; +using HttpCookies = ccstd::vector; +using HttpCookiesIter = HttpCookies::iterator; + +static HttpClient *gHttpClient = nullptr; // pointer to singleton +static LegacyThreadPool *gThreadPool = nullptr; + +struct CookiesInfo { + ccstd::string domain; + bool tailmatch; + ccstd::string path; + bool secure; + ccstd::string key; + ccstd::string value; + ccstd::string expires; +}; + +//static size_t writeData(void *ptr, size_t size, size_t nmemb, void *stream) +static size_t writeData(void *buffer, size_t sizes, HttpResponse *response) { + auto *recvBuffer = static_cast *>(response->getResponseData()); + recvBuffer->clear(); + recvBuffer->insert(recvBuffer->end(), static_cast(buffer), (static_cast(buffer)) + sizes); + return sizes; +} + +//static size_t writeHeaderData(void *ptr, size_t size, size_t nmemb, void *stream) +size_t writeHeaderData(void *buffer, size_t sizes, HttpResponse *response) { + auto *recvBuffer = static_cast *>(response->getResponseHeader()); + recvBuffer->clear(); + recvBuffer->insert(recvBuffer->end(), static_cast(buffer), static_cast(buffer) + sizes); + return sizes; +} + +class HttpURLConnection { +public: + explicit HttpURLConnection(HttpClient *httpClient) + : _client(httpClient), + _httpURLConnection(nullptr), + _contentLength(0) { + } + + ~HttpURLConnection() { + if (_httpURLConnection != nullptr) { + JniHelper::getEnv()->DeleteGlobalRef(_httpURLConnection); + } + } + + void setRequestMethod(const char *method) { // NOLINT + _requestmethod = method; + + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "setRequestMethod", + "(Ljava/net/HttpURLConnection;Ljava/lang/String;)V")) { + jstring jstr = methodInfo.env->NewStringUTF(_requestmethod.c_str()); + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, jstr); + ccDeleteLocalRef(methodInfo.env, jstr); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + } + + bool init(HttpRequest *request) { + createHttpURLConnection(request->getUrl()); + if (!configure(request)) { + return false; + } + /* get custom header data (if set) */ + HttpRequestHeaders headers = request->getHeaders(); + if (!headers.empty()) { + /* append custom headers one by one */ + for (auto &header : headers) { + uint32_t len = header.length(); + ccstd::string::size_type pos = header.find(':'); + if (ccstd::string::npos == pos || pos >= len) { + continue; + } + ccstd::string str1 = header.substr(0, pos); + ccstd::string str2 = header.substr(pos + 1); + // trim \n at the end of the string + if (!str2.empty() && str2[str2.size() - 1] == '\n') { + str2.erase(str2.size() - 1); + } + // trim leading space (header is field: value format) + if (!str2.empty() && str2[0] == ' ') { + str2.erase(0, 1); + } + addRequestHeader(str1.c_str(), str2.c_str()); + } + } + + addCookiesForRequestHeader(); + + return true; + } + + int connect() { // NOLINT + int suc = 0; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "connect", + "(Ljava/net/HttpURLConnection;)I")) { + suc = methodInfo.env->CallStaticIntMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + + return suc; + } + + void disconnect() { // NOLINT + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "disconnect", + "(Ljava/net/HttpURLConnection;)V")) { + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + } + + int getResponseCode() { // NOLINT + int responseCode = 0; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "getResponseCode", + "(Ljava/net/HttpURLConnection;)I")) { + responseCode = methodInfo.env->CallStaticIntMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + + return responseCode; + } + + char *getResponseMessage() { // NOLINT + char *message = nullptr; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "getResponseMessage", + "(Ljava/net/HttpURLConnection;)Ljava/lang/String;")) { + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + message = getBufferFromJString(static_cast(jObj), methodInfo.env); + if (nullptr != jObj) { + ccDeleteLocalRef(methodInfo.env, jObj); + } + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + + return message; + } + + void sendRequest(HttpRequest *request) { // NOLINT + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "sendRequest", + "(Ljava/net/HttpURLConnection;[B)V")) { + jbyteArray bytearray; + auto dataSize = static_cast(request->getRequestDataSize()); + bytearray = methodInfo.env->NewByteArray(dataSize); + methodInfo.env->SetByteArrayRegion(bytearray, 0, dataSize, reinterpret_cast(request->getRequestData())); + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, bytearray); + ccDeleteLocalRef(methodInfo.env, bytearray); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + } + + size_t saveResponseCookies(const char *responseCookies, size_t count) { // NOLINT + if (nullptr == responseCookies || strlen(responseCookies) == 0 || count == 0) { + return 0; + } + + if (_cookieFileName.empty()) { + _cookieFileName = FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"; + } + + FILE *fp = fopen(_cookieFileName.c_str(), "w"); + if (nullptr == fp) { + CC_LOG_DEBUG("can't create or open response cookie files"); + return 0; + } + + fwrite(responseCookies, sizeof(char), count, fp); + + fclose(fp); + + return count; + } + + char *getResponseHeaders() { // NOLINT + char *headers = nullptr; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "getResponseHeaders", + "(Ljava/net/HttpURLConnection;)Ljava/lang/String;")) { + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + headers = getBufferFromJString(static_cast(jObj), methodInfo.env); + if (nullptr != jObj) { + ccDeleteLocalRef(methodInfo.env, jObj); + } + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + + return headers; + } + + char *getResponseContent(HttpResponse *response) { // NOLINT + if (nullptr == response) { + return nullptr; + } + + char *content = nullptr; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "getResponseContent", + "(Ljava/net/HttpURLConnection;)[B")) { + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + + _contentLength = getCStrFromJByteArray(static_cast(jObj), methodInfo.env, &content); + if (nullptr != jObj) { + ccDeleteLocalRef(methodInfo.env, jObj); + } + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + + return content; + } + + char *getResponseHeaderByKey(const char *key) { + char *value = nullptr; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "getResponseHeaderByKey", + "(Ljava/net/HttpURLConnection;Ljava/lang/String;)Ljava/lang/String;")) { + jstring jstrKey = methodInfo.env->NewStringUTF(key); + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, jstrKey); + value = getBufferFromJString(static_cast(jObj), methodInfo.env); + ccDeleteLocalRef(methodInfo.env, jstrKey); + if (nullptr != jObj) { + ccDeleteLocalRef(methodInfo.env, jObj); + } + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + + return value; + } + + int getResponseHeaderByKeyInt(const char *key) { + int contentLength = 0; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "getResponseHeaderByKeyInt", + "(Ljava/net/HttpURLConnection;Ljava/lang/String;)I")) { + jstring jstrKey = methodInfo.env->NewStringUTF(key); + contentLength = methodInfo.env->CallStaticIntMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, jstrKey); + ccDeleteLocalRef(methodInfo.env, jstrKey); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + + return contentLength; + } + + char *getResponseHeaderByIdx(int idx) { + char *header = nullptr; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "getResponseHeaderByIdx", + "(Ljava/net/HttpURLConnection;I)Ljava/lang/String;")) { + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, idx); + header = getBufferFromJString(static_cast(jObj), methodInfo.env); + if (nullptr != jObj) { + ccDeleteLocalRef(methodInfo.env, jObj); + } + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + + return header; + } + + const ccstd::string &getCookieFileName() const { + return _cookieFileName; + } + + void setCookieFileName(ccstd::string &filename) { //NOLINT + _cookieFileName = filename; + } + + int getContentLength() const { + return _contentLength; + } + +private: + void createHttpURLConnection(const ccstd::string &url) { + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "createHttpURLConnection", + "(Ljava/lang/String;)Ljava/net/HttpURLConnection;")) { + _url = url; + jstring jurl = methodInfo.env->NewStringUTF(url.c_str()); + jobject jObj = methodInfo.env->CallStaticObjectMethod(methodInfo.classID, methodInfo.methodID, jurl); + _httpURLConnection = methodInfo.env->NewGlobalRef(jObj); + ccDeleteLocalRef(methodInfo.env, jurl); + ccDeleteLocalRef(methodInfo.env, jObj); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + } + + void addRequestHeader(const char *key, const char *value) { + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "addRequestHeader", + "(Ljava/net/HttpURLConnection;Ljava/lang/String;Ljava/lang/String;)V")) { + jstring jstrKey = methodInfo.env->NewStringUTF(key); + jstring jstrVal = methodInfo.env->NewStringUTF(value); + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, jstrKey, jstrVal); + ccDeleteLocalRef(methodInfo.env, jstrKey); + ccDeleteLocalRef(methodInfo.env, jstrVal); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + } + + void addCookiesForRequestHeader() { + if (_client->getCookieFilename().empty()) { + return; + } + + _cookieFileName = FileUtils::getInstance()->fullPathForFilename(_client->getCookieFilename()); + + ccstd::string cookiesInfo = FileUtils::getInstance()->getStringFromFile(_cookieFileName); + + if (cookiesInfo.empty()) { + return; + } + + HttpCookies cookiesVec; + cookiesVec.clear(); + + std::stringstream stream(cookiesInfo); + ccstd::string item; + while (std::getline(stream, item, '\n')) { + cookiesVec.push_back(item); + } + + if (cookiesVec.empty()) { + return; + } + + ccstd::vector cookiesInfoVec; + cookiesInfoVec.clear(); + + for (auto &cookies : cookiesVec) { + if (cookies.find("#HttpOnly_") != ccstd::string::npos) { + cookies = cookies.substr(10); + } + + if (cookies.at(0) == '#') { + continue; + } + + CookiesInfo co; + std::stringstream streamInfo(cookies); + ccstd::string item; + ccstd::vector elems; + + while (std::getline(streamInfo, item, '\t')) { + elems.push_back(item); + } + + co.domain = elems[0]; + if (co.domain.at(0) == '.') { + co.domain = co.domain.substr(1); + } + co.tailmatch = strcmp("TRUE", elems.at(1).c_str()) != 0; + co.path = elems.at(2); + co.secure = strcmp("TRUE", elems.at(3).c_str()) != 0; + co.expires = elems.at(4); + co.key = elems.at(5); + co.value = elems.at(6); + cookiesInfoVec.push_back(co); + } + + ccstd::string sendCookiesInfo; + int cookiesCount = 0; + for (auto &cookieInfo : cookiesInfoVec) { + if (_url.find(cookieInfo.domain) != ccstd::string::npos) { + ccstd::string keyValue = cookieInfo.key; + keyValue.append("="); + keyValue.append(cookieInfo.value); + if (cookiesCount != 0) { + sendCookiesInfo.append(";"); + } + + sendCookiesInfo.append(keyValue); + } + cookiesCount++; + } + + //set Cookie + addRequestHeader("Cookie", sendCookiesInfo.c_str()); + } + + void setReadAndConnectTimeout(int readMiliseconds, int connectMiliseconds) { + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "setReadAndConnectTimeout", + "(Ljava/net/HttpURLConnection;II)V")) { + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, readMiliseconds, connectMiliseconds); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + } + + void setVerifySSL() { + if (_client->getSSLVerification().empty()) { + return; + } + + ccstd::string fullpath = FileUtils::getInstance()->fullPathForFilename(_client->getSSLVerification()); + + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + JCLS_HTTPCLIENT, + "setVerifySSL", + "(Ljava/net/HttpURLConnection;Ljava/lang/String;)V")) { + jstring jstrfullpath = methodInfo.env->NewStringUTF(fullpath.c_str()); + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, jstrfullpath); + ccDeleteLocalRef(methodInfo.env, jstrfullpath); + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + } else { + CC_LOG_ERROR("HttpClient::%s failed!", __FUNCTION__); + } + } + + bool configure(HttpRequest *request) { + if (nullptr == _httpURLConnection) { + return false; + } + + if (nullptr == _client) { + return false; + } + + setReadAndConnectTimeout(static_cast(request->getTimeout() * 1000), + static_cast(request->getTimeout() * 1000)); + + setVerifySSL(); + + return true; + } + + char *getBufferFromJString(jstring jstr, JNIEnv *env) { //NOLINT(readability-convert-member-functions-to-static) + if (nullptr == jstr) { + return nullptr; + } + ccstd::string strValue = cc::StringUtils::getStringUTFCharsJNI(env, jstr); + size_t size = strValue.size() + 1; + char *retVal = static_cast(malloc(size)); + if (retVal == nullptr) { + return nullptr; + } + memcpy(retVal, strValue.c_str(), size); + return retVal; + } + + int getCStrFromJByteArray(jbyteArray jba, JNIEnv *env, char **ppData) { //NOLINT(readability-convert-member-functions-to-static) + if (nullptr == jba) { + *ppData = nullptr; + return 0; + } + + int len = env->GetArrayLength(jba); + auto *str = static_cast(malloc(sizeof(jbyte) * len)); + env->GetByteArrayRegion(jba, 0, len, str); + + *ppData = reinterpret_cast(str); + return len; + } + + const ccstd::string &getCookieString() const { + return _responseCookies; + } + +private: // NOLINT(readability-redundant-access-specifiers) + HttpClient *_client; + jobject _httpURLConnection; + ccstd::string _requestmethod; + ccstd::string _responseCookies; + ccstd::string _cookieFileName; + ccstd::string _url; + int _contentLength; +}; + +// Process Response +void HttpClient::processResponse(HttpResponse *response, char *responseMessage) { + auto *request = response->getHttpRequest(); + HttpRequest::Type requestType = request->getRequestType(); + + if (HttpRequest::Type::GET != requestType && + HttpRequest::Type::POST != requestType && + HttpRequest::Type::PUT != requestType && + HttpRequest::Type::HEAD != requestType && + HttpRequest::Type::DELETE != requestType) { + CC_ABORT(); + return; + } + + long responseCode = -1L; // NOLINT + int retValue = 0; + + HttpURLConnection urlConnection(this); + if (!urlConnection.init(request)) { + response->setSucceed(false); + response->setErrorBuffer("HttpURLConnection init failed"); + return; + } + + switch (requestType) { + case HttpRequest::Type::GET: + urlConnection.setRequestMethod("GET"); + break; + + case HttpRequest::Type::POST: + urlConnection.setRequestMethod("POST"); + break; + + case HttpRequest::Type::PUT: + urlConnection.setRequestMethod("PUT"); + break; + + case HttpRequest::Type::HEAD: + urlConnection.setRequestMethod("HEAD"); + break; + + case HttpRequest::Type::DELETE: + urlConnection.setRequestMethod("DELETE"); + break; + + case HttpRequest::Type::PATCH: + urlConnection.setRequestMethod("PATCH"); + break; + + default: + break; + } + + int suc = urlConnection.connect(); + if (0 != suc) { + response->setSucceed(false); + response->setErrorBuffer("connect failed"); + response->setResponseCode(responseCode); + return; + } + + if (HttpRequest::Type::POST == requestType || + HttpRequest::Type::PUT == requestType || + HttpRequest::Type::PATCH == requestType) { + urlConnection.sendRequest(request); + } + + responseCode = urlConnection.getResponseCode(); + + if (0 == responseCode) { + response->setSucceed(false); + response->setErrorBuffer("connect failed"); + response->setResponseCode(-1); + return; + } + + char *headers = urlConnection.getResponseHeaders(); + if (nullptr != headers) { + writeHeaderData(headers, strlen(headers), response); + } + free(headers); + + //get and save cookies + char *cookiesInfo = urlConnection.getResponseHeaderByKey("set-cookie"); + if (nullptr != cookiesInfo) { + urlConnection.saveResponseCookies(cookiesInfo, strlen(cookiesInfo)); + } + free(cookiesInfo); + + //content len + int contentLength = urlConnection.getResponseHeaderByKeyInt("Content-Length"); + char *contentInfo = urlConnection.getResponseContent(response); + if (nullptr != contentInfo) { + auto *recvBuffer = static_cast *>(response->getResponseData()); + recvBuffer->clear(); + recvBuffer->insert(recvBuffer->begin(), contentInfo, contentInfo + urlConnection.getContentLength()); + } + free(contentInfo); + + char *messageInfo = urlConnection.getResponseMessage(); + if (messageInfo) { + strcpy(responseMessage, messageInfo); + free(messageInfo); + } + + urlConnection.disconnect(); + + // write data to HttpResponse + response->setResponseCode(responseCode); + + if (responseCode == -1) { + response->setSucceed(false); + if (responseMessage != nullptr) { + response->setErrorBuffer(responseMessage); + } else { + response->setErrorBuffer("response code error!"); + } + } else { + response->setSucceed(true); + } +} + +// Worker thread +void HttpClient::networkThread() { + increaseThreadCount(); + + while (true) { + HttpRequest *request; + + // step 1: send http request if the requestQueue isn't empty + { + std::lock_guard lock(_requestQueueMutex); + while (_requestQueue.empty()) { + _sleepCondition.wait(_requestQueueMutex); + } + request = _requestQueue.at(0); + _requestQueue.erase(0); + } + + if (request == _requestSentinel) { + break; + } + + // Create a HttpResponse object, the default setting is http access failed + auto *response = ccnew HttpResponse(request); + response->addRef(); // NOTE: RefCounted object's reference count is changed to 0 now. so needs to addRef after ccnew. + processResponse(response, _responseMessage); + + // add response packet into queue + _responseQueueMutex.lock(); + _responseQueue.pushBack(response); + _responseQueueMutex.unlock(); + + _schedulerMutex.lock(); + if (auto sche = _scheduler.lock()) { + sche->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); // NOLINT + } + _schedulerMutex.unlock(); + } + + // cleanup: if worker thread received quit signal, clean up un-completed request queue + _requestQueueMutex.lock(); + _requestQueue.clear(); + _requestQueueMutex.unlock(); + + _responseQueueMutex.lock(); + _responseQueue.clear(); + _responseQueueMutex.unlock(); + + decreaseThreadCountAndMayDeleteThis(); +} + +// Worker thread +void HttpClient::networkThreadAlone(HttpRequest *request, HttpResponse *response) { + increaseThreadCount(); + + char responseMessage[RESPONSE_BUFFER_SIZE] = {0}; + processResponse(response, responseMessage); + + _schedulerMutex.lock(); + if (auto sche = _scheduler.lock()) { + sche->performFunctionInCocosThread([this, response, request] { + const ccHttpRequestCallback &callback = request->getResponseCallback(); + + if (callback != nullptr) { + callback(this, response); + } + + response->release(); + // do not release in other thread + request->release(); + }); + } + _schedulerMutex.unlock(); + decreaseThreadCountAndMayDeleteThis(); +} + +// HttpClient implementation +HttpClient *HttpClient::getInstance() { + if (gHttpClient == nullptr) { + gHttpClient = ccnew HttpClient(); + } + + return gHttpClient; +} + +void HttpClient::destroyInstance() { + if (gHttpClient == nullptr) { + CC_LOG_DEBUG("HttpClient singleton is nullptr"); + return; + } + + CC_LOG_DEBUG("HttpClient::destroyInstance ..."); + + auto *thiz = gHttpClient; + gHttpClient = nullptr; + + if (auto sche = thiz->_scheduler.lock()) { + sche->unscheduleAllForTarget(thiz); + } + thiz->_schedulerMutex.lock(); + thiz->_scheduler.reset(); + thiz->_schedulerMutex.unlock(); + + { + std::lock_guard lock(thiz->_requestQueueMutex); + thiz->_requestQueue.pushBack(thiz->_requestSentinel); + } + thiz->_sleepCondition.notify_one(); + + thiz->decreaseThreadCountAndMayDeleteThis(); + CC_LOG_DEBUG("HttpClient::destroyInstance() finished!"); +} + +void HttpClient::enableCookies(const char *cookieFile) { + std::lock_guard lock(_cookieFileMutex); + if (cookieFile) { + _cookieFilename = ccstd::string(cookieFile); + } else { + _cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); + } +} + +void HttpClient::setSSLVerification(const ccstd::string &caFile) { + std::lock_guard lock(_sslCaFileMutex); + _sslCaFilename = caFile; +} + +HttpClient::HttpClient() +: _isInited(false), + _timeoutForConnect(30), + _timeoutForRead(60), + _threadCount(0), + _cookie(nullptr), + _requestSentinel(ccnew HttpRequest()) { + CC_LOG_DEBUG("In the constructor of HttpClient!"); + _requestSentinel->addRef(); + if (gThreadPool == nullptr) { + gThreadPool = LegacyThreadPool::newFixedThreadPool(4); + } + increaseThreadCount(); + _scheduler = CC_CURRENT_ENGINE()->getScheduler(); +} + +HttpClient::~HttpClient() { + CC_LOG_DEBUG("In the destructor of HttpClient!"); + CC_SAFE_RELEASE(_requestSentinel); +} + +//Lazy create semaphore & mutex & thread +bool HttpClient::lazyInitThreadSemaphore() { + if (_isInited) { + return true; + } + + auto t = std::thread(CC_CALLBACK_0(HttpClient::networkThread, this)); // NOLINT + t.detach(); + _isInited = true; + + return true; +} + +//Add a get task to queue +void HttpClient::send(HttpRequest *request) { + if (!lazyInitThreadSemaphore()) { + return; + } + + if (nullptr == request) { + return; + } + + request->addRef(); + + _requestQueueMutex.lock(); + _requestQueue.pushBack(request); + _requestQueueMutex.unlock(); + + // Notify thread start to work + _sleepCondition.notify_one(); +} + +void HttpClient::sendImmediate(HttpRequest *request) { + if (nullptr == request) { + return; + } + + request->addRef(); + // Create a HttpResponse object, the default setting is http access failed + auto *response = ccnew HttpResponse(request); + response->addRef(); // NOTE: RefCounted object's reference count is changed to 0 now. so needs to addRef after ccnew. + + gThreadPool->pushTask([this, request, response](int /*tid*/) { HttpClient::networkThreadAlone(request, response); }); +} + +// Poll and notify main thread if responses exists in queue +void HttpClient::dispatchResponseCallbacks() { + // log("CCHttpClient::dispatchResponseCallbacks is running"); + //occurs when cocos thread fires but the network thread has already quited + HttpResponse *response = nullptr; + + _responseQueueMutex.lock(); + + if (!_responseQueue.empty()) { + response = _responseQueue.at(0); + _responseQueue.erase(0); + } + + _responseQueueMutex.unlock(); + + if (response) { + HttpRequest *request = response->getHttpRequest(); + const ccHttpRequestCallback &callback = request->getResponseCallback(); + + if (callback != nullptr) { + callback(this, response); + } + + response->release(); + // do not release in other thread + request->release(); + } +} + +void HttpClient::increaseThreadCount() { + _threadCountMutex.lock(); + ++_threadCount; + _threadCountMutex.unlock(); +} + +void HttpClient::decreaseThreadCountAndMayDeleteThis() { + bool needDeleteThis = false; + _threadCountMutex.lock(); + --_threadCount; + if (0 == _threadCount) { + needDeleteThis = true; + } + + _threadCountMutex.unlock(); + if (needDeleteThis) { + delete this; + } +} + +const ccstd::string &HttpClient::getCookieFilename() { + std::lock_guard lock(_cookieFileMutex); + return _cookieFilename; +} + +const ccstd::string &HttpClient::getSSLVerification() { + std::lock_guard lock(_sslCaFileMutex); + return _sslCaFilename; +} + +} // namespace network + +} // namespace cc diff --git a/cocos/network/HttpClient.cpp b/cocos/network/HttpClient.cpp new file mode 100644 index 0000000..f4a7289 --- /dev/null +++ b/cocos/network/HttpClient.cpp @@ -0,0 +1,579 @@ +/**************************************************************************** + Copyright (c) 2012 greathqy + Copyright (c) 2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "network/HttpClient.h" +#include +#include +#include "application/ApplicationManager.h" +#include "base/Log.h" +#include "base/ThreadPool.h" +#include "base/memory/Memory.h" +#include "platform/FileUtils.h" +#include "platform/StdC.h" + +namespace cc { + +namespace network { + +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) +typedef int int32_t; +#endif + +static HttpClient *_httpClient = nullptr; // pointer to singleton +static LegacyThreadPool *gThreadPool = nullptr; + +typedef size_t (*write_callback)(void *ptr, size_t size, size_t nmemb, void *stream); + +// Callback function used by libcurl for collect response data +static size_t writeData(void *ptr, size_t size, size_t nmemb, void *stream) { + ccstd::vector *recvBuffer = (ccstd::vector *)stream; + size_t sizes = size * nmemb; + + // add data to the end of recvBuffer + // write data maybe called more than once in a single request + recvBuffer->insert(recvBuffer->end(), (char *)ptr, (char *)ptr + sizes); + + return sizes; +} + +// Callback function used by libcurl for collect header data +static size_t writeHeaderData(void *ptr, size_t size, size_t nmemb, void *stream) { + ccstd::vector *recvBuffer = (ccstd::vector *)stream; + size_t sizes = size * nmemb; + + // add data to the end of recvBuffer + // write data maybe called more than once in a single request + recvBuffer->insert(recvBuffer->end(), (char *)ptr, (char *)ptr + sizes); + + return sizes; +} + +static int processGetTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char *errorBuffer); +static int processPostTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char *errorBuffer); +static int processPutTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char *errorBuffer); +static int processDeleteTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char *errorBuffer); +static int processPatchTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char *errorBuffer); +// int processDownloadTask(HttpRequest *task, write_callback callback, void *stream, int32_t *errorCode); + +// Worker thread +void HttpClient::networkThread() { + increaseThreadCount(); + + while (true) { + HttpRequest *request; + + // step 1: send http request if the requestQueue isn't empty + { + std::lock_guard lock(_requestQueueMutex); + while (_requestQueue.empty()) { + _sleepCondition.wait(_requestQueueMutex); + } + request = _requestQueue.at(0); + _requestQueue.erase(0); + } + + if (request == _requestSentinel) { + break; + } + + // step 2: libcurl sync access + + // Create a HttpResponse object, the default setting is http access failed + HttpResponse *response = ccnew HttpResponse(request); + response->addRef(); // NOTE: RefCounted object's reference count is changed to 0 now. so needs to addRef after ccnew. + + processResponse(response, _responseMessage); + + // add response packet into queue + _responseQueueMutex.lock(); + _responseQueue.pushBack(response); + _responseQueueMutex.unlock(); + + _schedulerMutex.lock(); + if (auto sche = _scheduler.lock()) { + sche->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); + } + _schedulerMutex.unlock(); + } + + // cleanup: if worker thread received quit signal, clean up un-completed request queue + _requestQueueMutex.lock(); + _requestQueue.clear(); + _requestQueueMutex.unlock(); + + _responseQueueMutex.lock(); + _responseQueue.clear(); + _responseQueueMutex.unlock(); + + decreaseThreadCountAndMayDeleteThis(); +} + +// Worker thread +void HttpClient::networkThreadAlone(HttpRequest *request, HttpResponse *response) { + increaseThreadCount(); + + char responseMessage[RESPONSE_BUFFER_SIZE] = {0}; + processResponse(response, responseMessage); + + _schedulerMutex.lock(); + if (auto sche = _scheduler.lock()) { + sche->performFunctionInCocosThread([this, response, request] { + const ccHttpRequestCallback &callback = request->getResponseCallback(); + + if (callback != nullptr) { + callback(this, response); + } + response->release(); + // do not release in other thread + request->release(); + }); + } + _schedulerMutex.unlock(); + + decreaseThreadCountAndMayDeleteThis(); +} + +//Configure curl's timeout property +static bool configureCURL(HttpClient *client, HttpRequest *request, CURL *handle, char *errorBuffer) { + if (!handle) { + return false; + } + + int32_t code; + code = curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errorBuffer); + if (code != CURLE_OK) { + return false; + } + // In the openharmony platform, the long type must be used, otherwise there will be an exception. + long timeout = static_cast(request->getTimeout()); + code = curl_easy_setopt(handle, CURLOPT_TIMEOUT, timeout); + if (code != CURLE_OK) { + return false; + } + code = curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, timeout); + if (code != CURLE_OK) { + return false; + } + + ccstd::string sslCaFilename = client->getSSLVerification(); + if (sslCaFilename.empty()) { + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L); + } else { + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 2L); + curl_easy_setopt(handle, CURLOPT_CAINFO, sslCaFilename.c_str()); + } + + // FIXED #3224: The subthread of CCHttpClient interrupts main thread if timeout comes. + // Document is here: http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOSIGNAL + curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L); + + curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, ""); + + return true; +} + +class CURLRaii { + /// Instance of CURL + CURL *_curl; + /// Keeps custom header data + curl_slist *_headers; + +public: + CURLRaii() + : _curl(curl_easy_init()), + _headers(nullptr) { + } + + ~CURLRaii() { + if (_curl) + curl_easy_cleanup(_curl); + /* free the linked list for header data */ + if (_headers) + curl_slist_free_all(_headers); + } + + template + bool setOption(CURLoption option, T data) { + return CURLE_OK == curl_easy_setopt(_curl, option, data); + } + + /** + * @brief Inits CURL instance for common usage + * @param request Null not allowed + * @param callback Response write callback + * @param stream Response write stream + */ + bool init(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, write_callback headerCallback, void *headerStream, char *errorBuffer) { + if (!_curl) + return false; + if (!configureCURL(client, request, _curl, errorBuffer)) + return false; + + /* get custom header data (if set) */ + ccstd::vector headers = request->getHeaders(); + if (!headers.empty()) { + /* append custom headers one by one */ + for (auto &header : headers) + _headers = curl_slist_append(_headers, header.c_str()); + /* set custom headers for curl */ + if (!setOption(CURLOPT_HTTPHEADER, _headers)) + return false; + } + ccstd::string cookieFilename = client->getCookieFilename(); + if (!cookieFilename.empty()) { + if (!setOption(CURLOPT_COOKIEFILE, cookieFilename.c_str())) { + return false; + } + if (!setOption(CURLOPT_COOKIEJAR, cookieFilename.c_str())) { + return false; + } + } + + return setOption(CURLOPT_URL, request->getUrl()) && setOption(CURLOPT_WRITEFUNCTION, callback) && setOption(CURLOPT_WRITEDATA, stream) && setOption(CURLOPT_HEADERFUNCTION, headerCallback) && setOption(CURLOPT_HEADERDATA, headerStream); + } + + /// @param responseCode Null not allowed + bool perform(long *responseCode) { + if (CURLE_OK != curl_easy_perform(_curl)) + return false; + CURLcode code = curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, responseCode); + if (code != CURLE_OK || !(*responseCode >= 200 && *responseCode < 300)) { + CC_LOG_ERROR("Curl curl_easy_getinfo failed: %s", curl_easy_strerror(code)); + return false; + } + // Get some mor data. + + return true; + } +}; + +//Process Get Request +static int processGetTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) { + CURLRaii curl; + bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_FOLLOWLOCATION, true) && curl.perform(responseCode); + return ok ? 0 : 1; +} + +//Process POST Request +static int processPostTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) { + CURLRaii curl; + bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_POST, 1) && curl.setOption(CURLOPT_POSTFIELDS, request->getRequestData()) && curl.setOption(CURLOPT_POSTFIELDSIZE, request->getRequestDataSize()) && curl.perform(responseCode); + return ok ? 0 : 1; +} + +//Process PUT Request +static int processPutTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) { + CURLRaii curl; + bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_CUSTOMREQUEST, "PUT") && curl.setOption(CURLOPT_POSTFIELDS, request->getRequestData()) && curl.setOption(CURLOPT_POSTFIELDSIZE, request->getRequestDataSize()) && curl.perform(responseCode); + return ok ? 0 : 1; +} + +//Process HEAD Request +static int processHeadTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) { + CURLRaii curl; + bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_NOBODY, "HEAD") && curl.setOption(CURLOPT_POSTFIELDS, request->getRequestData()) && curl.setOption(CURLOPT_POSTFIELDSIZE, request->getRequestDataSize()) && curl.perform(responseCode); + return ok ? 0 : 1; +} + +//Process DELETE Request +static int processDeleteTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) { + CURLRaii curl; + bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_CUSTOMREQUEST, "DELETE") && curl.setOption(CURLOPT_FOLLOWLOCATION, true) && curl.perform(responseCode); + return ok ? 0 : 1; +} + +//Process PATCH Request +static int processPatchTask(HttpClient *client, HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) { + CURLRaii curl; + bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_CUSTOMREQUEST, "PATCH") && curl.setOption(CURLOPT_POSTFIELDS, request->getRequestData()) && curl.setOption(CURLOPT_POSTFIELDSIZE, request->getRequestDataSize()) && curl.perform(responseCode); + return ok ? 0 : 1; +} + +// HttpClient implementation +HttpClient *HttpClient::getInstance() { + if (_httpClient == nullptr) { + _httpClient = ccnew HttpClient(); + } + + return _httpClient; +} + +void HttpClient::destroyInstance() { + if (nullptr == _httpClient) { + CC_LOG_DEBUG("HttpClient singleton is nullptr"); + return; + } + + CC_LOG_DEBUG("HttpClient::destroyInstance begin"); + auto thiz = _httpClient; + _httpClient = nullptr; + + if (auto sche = thiz->_scheduler.lock()) { + sche->unscheduleAllForTarget(thiz); + } + + thiz->_schedulerMutex.lock(); + thiz->_scheduler.reset(); + thiz->_schedulerMutex.unlock(); + + thiz->_requestQueueMutex.lock(); + thiz->_requestQueue.pushBack(thiz->_requestSentinel); + thiz->_requestQueueMutex.unlock(); + + thiz->_sleepCondition.notify_one(); + thiz->decreaseThreadCountAndMayDeleteThis(); + + CC_LOG_DEBUG("HttpClient::destroyInstance() finished!"); +} + +void HttpClient::enableCookies(const char *cookieFile) { + std::lock_guard lock(_cookieFileMutex); + if (cookieFile) { + _cookieFilename = ccstd::string(cookieFile); + } else { + _cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); + } +} + +void HttpClient::setSSLVerification(const ccstd::string &caFile) { + std::lock_guard lock(_sslCaFileMutex); + _sslCaFilename = caFile; +} + +HttpClient::HttpClient() +: _isInited(false), + _timeoutForConnect(30), + _timeoutForRead(60), + _threadCount(0), + _cookie(nullptr), + _requestSentinel(ccnew HttpRequest()) { + CC_LOG_DEBUG("In the constructor of HttpClient!"); + _requestSentinel->addRef(); + if (gThreadPool == nullptr) { + gThreadPool = LegacyThreadPool::newFixedThreadPool(4); + } + memset(_responseMessage, 0, RESPONSE_BUFFER_SIZE * sizeof(char)); + _scheduler = CC_CURRENT_ENGINE()->getScheduler(); + increaseThreadCount(); +} + +HttpClient::~HttpClient() { + CC_SAFE_RELEASE(_requestSentinel); + CC_LOG_DEBUG("HttpClient destructor"); +} + +//Lazy create semaphore & mutex & thread +bool HttpClient::lazyInitThreadSemaphore() { + if (_isInited) { + return true; + } else { + auto t = std::thread(CC_CALLBACK_0(HttpClient::networkThread, this)); + t.detach(); + _isInited = true; + } + + return true; +} + +//Add a get task to queue +void HttpClient::send(HttpRequest *request) { + if (false == lazyInitThreadSemaphore()) { + return; + } + + if (!request) { + return; + } + + request->addRef(); + + _requestQueueMutex.lock(); + _requestQueue.pushBack(request); + _requestQueueMutex.unlock(); + + // Notify thread start to work + _sleepCondition.notify_one(); +} + +void HttpClient::sendImmediate(HttpRequest *request) { + if (!request) { + return; + } + + request->addRef(); + // Create a HttpResponse object, the default setting is http access failed + HttpResponse *response = ccnew HttpResponse(request); + response->addRef(); // NOTE: RefCounted object's reference count is changed to 0 now. so needs to addRef after ccnew. + + gThreadPool->pushTask([this, request, response](int /*tid*/) { HttpClient::networkThreadAlone(request, response); }); +} + +// Poll and notify main thread if responses exists in queue +void HttpClient::dispatchResponseCallbacks() { + // log("CCHttpClient::dispatchResponseCallbacks is running"); + //occurs when cocos thread fires but the network thread has already quited + HttpResponse *response = nullptr; + + _responseQueueMutex.lock(); + if (!_responseQueue.empty()) { + response = _responseQueue.at(0); + _responseQueue.erase(0); + } + _responseQueueMutex.unlock(); + + if (response) { + HttpRequest *request = response->getHttpRequest(); + const ccHttpRequestCallback &callback = request->getResponseCallback(); + + if (callback != nullptr) { + callback(this, response); + } + + response->release(); + // do not release in other thread + request->release(); + } +} + +// Process Response +void HttpClient::processResponse(HttpResponse *response, char *responseMessage) { + auto request = response->getHttpRequest(); + long responseCode = -1; + int retValue = 0; + + // Process the request -> get response packet + switch (request->getRequestType()) { + case HttpRequest::Type::GET: // HTTP GET + retValue = processGetTask(this, request, + writeData, + response->getResponseData(), + &responseCode, + writeHeaderData, + response->getResponseHeader(), + responseMessage); + break; + + case HttpRequest::Type::POST: // HTTP POST + retValue = processPostTask(this, request, + writeData, + response->getResponseData(), + &responseCode, + writeHeaderData, + response->getResponseHeader(), + responseMessage); + break; + + case HttpRequest::Type::PUT: + retValue = processPutTask(this, request, + writeData, + response->getResponseData(), + &responseCode, + writeHeaderData, + response->getResponseHeader(), + responseMessage); + break; + + case HttpRequest::Type::HEAD: + retValue = processHeadTask(this, request, + writeData, + response->getResponseData(), + &responseCode, + writeHeaderData, + response->getResponseHeader(), + responseMessage); + break; + + case HttpRequest::Type::DELETE: + retValue = processDeleteTask(this, request, + writeData, + response->getResponseData(), + &responseCode, + writeHeaderData, + response->getResponseHeader(), + responseMessage); + break; + + case HttpRequest::Type::PATCH: + retValue = processPatchTask(this, request, + writeData, + response->getResponseData(), + &responseCode, + writeHeaderData, + response->getResponseHeader(), + responseMessage); + break; + + default: + CC_ABORT(); + break; + } + + // write data to HttpResponse + response->setResponseCode(responseCode); + if (retValue != 0) { + response->setSucceed(false); + response->setErrorBuffer(responseMessage); + } else { + response->setSucceed(true); + } +} + +void HttpClient::increaseThreadCount() { + _threadCountMutex.lock(); + ++_threadCount; + _threadCountMutex.unlock(); +} + +void HttpClient::decreaseThreadCountAndMayDeleteThis() { + bool needDeleteThis = false; + _threadCountMutex.lock(); + --_threadCount; + if (0 == _threadCount) { + needDeleteThis = true; + } + + _threadCountMutex.unlock(); + if (needDeleteThis) { + delete this; + } +} + +const ccstd::string &HttpClient::getCookieFilename() { + std::lock_guard lock(_cookieFileMutex); + return _cookieFilename; +} + +const ccstd::string &HttpClient::getSSLVerification() { + std::lock_guard lock(_sslCaFileMutex); + return _sslCaFilename; +} + +} // namespace network + +} // namespace cc diff --git a/cocos/network/HttpClient.h b/cocos/network/HttpClient.h new file mode 100644 index 0000000..26ecb9e --- /dev/null +++ b/cocos/network/HttpClient.h @@ -0,0 +1,181 @@ +/**************************************************************************** + Copyright (c) 2012 greathqy + Copyright (c) 2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/RefVector.h" +#include "network/HttpCookie.h" +#include "network/HttpRequest.h" +#include "network/HttpResponse.h" + +/** + * @addtogroup network + * @{ + */ + +namespace cc { +class Scheduler; +namespace network { + +/** Singleton that handles asynchronous http requests. + * + * Once the request completed, a callback will issued in main thread when it provided during make request. + * + * @lua NA + */ +class CC_DLL HttpClient { +public: + /** + * The buffer size of _responseMessage + */ + static const int RESPONSE_BUFFER_SIZE = 256; + + /** + * Get instance of HttpClient. + * + * @return the instance of HttpClient. + */ + static HttpClient *getInstance(); + + /** + * Release the instance of HttpClient. + */ + static void destroyInstance(); + + /** + * Enable cookie support. + * + * @param cookieFile the filepath of cookie file. + */ + void enableCookies(const char *cookieFile); + + /** + * Get the cookie filename + * + * @return the cookie filename + */ + const ccstd::string &getCookieFilename(); + + /** + * Set root certificate path for SSL verification. + * + * @param caFile a full path of root certificate.if it is empty, SSL verification is disabled. + */ + void setSSLVerification(const ccstd::string &caFile); + + /** + * Get the ssl CA filename + * + * @return the ssl CA filename + */ + const ccstd::string &getSSLVerification(); + + /** + * Add a get request to task queue + * + * @param request a HttpRequest object, which includes url, response callback etc. + please make sure request->_requestData is clear before calling "send" here. + */ + void send(HttpRequest *request); + + /** + * Immediate send a request + * + * @param request a HttpRequest object, which includes url, response callback etc. + please make sure request->_requestData is clear before calling "sendImmediate" here. + */ + void sendImmediate(HttpRequest *request); + + HttpCookie *getCookie() const { return _cookie; } + + std::mutex &getCookieFileMutex() { return _cookieFileMutex; } + + std::mutex &getSSLCaFileMutex() { return _sslCaFileMutex; } + +private: + HttpClient(); + virtual ~HttpClient(); + bool init(); + + /** + * Init pthread mutex, semaphore, and create new thread for http requests + * @return bool + */ + bool lazyInitThreadSemaphore(); + void networkThread(); + void networkThreadAlone(HttpRequest *request, HttpResponse *response); + /** Poll function called from main thread to dispatch callbacks when http requests finished **/ + void dispatchResponseCallbacks(); + + void processResponse(HttpResponse *response, char *responseMessage); + void increaseThreadCount(); + void decreaseThreadCountAndMayDeleteThis(); + +private: // NOLINT(readability-redundant-access-specifiers) + bool _isInited; + + int _timeoutForConnect; + std::mutex _timeoutForConnectMutex; + + int _timeoutForRead; + std::mutex _timeoutForReadMutex; + + int _threadCount; + std::mutex _threadCountMutex; + + std::weak_ptr _scheduler; + std::mutex _schedulerMutex; + + RefVector _requestQueue; + std::mutex _requestQueueMutex; + + RefVector _responseQueue; + std::mutex _responseQueueMutex; + + ccstd::string _cookieFilename; + std::mutex _cookieFileMutex; + + ccstd::string _sslCaFilename; + std::mutex _sslCaFileMutex; + + HttpCookie *_cookie; + + std::condition_variable_any _sleepCondition; + + char _responseMessage[RESPONSE_BUFFER_SIZE]; + + HttpRequest *_requestSentinel; +}; + +} // namespace network + +} // namespace cc + +// end group +/// @} diff --git a/cocos/network/HttpCookie.cpp b/cocos/network/HttpCookie.cpp new file mode 100644 index 0000000..ef83af0 --- /dev/null +++ b/cocos/network/HttpCookie.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "network/HttpCookie.h" +#include +#include +#include +#include "platform/FileUtils.h" + +void HttpCookie::readFile() { + ccstd::string inString = cc::FileUtils::getInstance()->getStringFromFile(_cookieFileName); + if (!inString.empty()) { + ccstd::vector cookiesVec; + cookiesVec.clear(); + + std::stringstream stream(inString); + ccstd::string item; + while (std::getline(stream, item, '\n')) { + cookiesVec.push_back(item); + } + + if (cookiesVec.empty()) { + return; + } + + _cookies.clear(); + + for (auto &cookie : cookiesVec) { + if (cookie.length() == 0) { + continue; + } + + if (cookie.find("#HttpOnly_") != ccstd::string::npos) { + cookie = cookie.substr(10); + } + + if (cookie.at(0) == '#') { + continue; + } + + CookiesInfo co; + std::stringstream streamInfo(cookie); + ccstd::vector elems; + ccstd::string elemsItem; + + while (std::getline(streamInfo, elemsItem, '\t')) { + elems.push_back(elemsItem); + } + + co.domain = elems[0]; + if (co.domain.at(0) == '.') { + co.domain = co.domain.substr(1); + } + co.tailmatch = (strcmp("TRUE", elems[1].c_str()) != 0); + co.path = elems[2]; + co.secure = (strcmp("TRUE", elems[3].c_str()) != 0); + co.expires = elems[4]; + co.name = elems[5]; + co.value = elems[6]; + _cookies.push_back(co); + } + } +} + +const ccstd::vector *HttpCookie::getCookies() const { + return &_cookies; +} + +const CookiesInfo *HttpCookie::getMatchCookie(const ccstd::string &url) const { + for (const auto &cookie : _cookies) { + if (url.find(cookie.domain) != ccstd::string::npos) { + return &cookie; + } + } + + return nullptr; +} + +void HttpCookie::updateOrAddCookie(CookiesInfo *cookie) { + for (auto &iter : _cookies) { + if (cookie->domain == iter.domain) { + iter = *cookie; + return; + } + } + _cookies.push_back(*cookie); +} + +void HttpCookie::writeFile() { + FILE *out; + out = fopen(_cookieFileName.c_str(), "w"); + fputs( + "# Netscape HTTP Cookie File\n" + "# http://curl.haxx.se/docs/http-cookies.html\n" + "# This file was generated by cocos2d-x! Edit at your own risk.\n" + "# Test cocos2d-x cookie write.\n\n", + out); + + ccstd::string line; + for (auto &cookie : _cookies) { + line.clear(); + line.append(cookie.domain); + line.append(1, '\t'); + cookie.tailmatch ? line.append("TRUE") : line.append("FALSE"); + line.append(1, '\t'); + line.append(cookie.path); + line.append(1, '\t'); + cookie.secure ? line.append("TRUE") : line.append("FALSE"); + line.append(1, '\t'); + line.append(cookie.expires); + line.append(1, '\t'); + line.append(cookie.name); + line.append(1, '\t'); + line.append(cookie.value); + //line.append(1, '\n'); + + fprintf(out, "%s\n", line.c_str()); + } + + fclose(out); +} + +void HttpCookie::setCookieFileName(const ccstd::string &filename) { + _cookieFileName = filename; +} diff --git a/cocos/network/HttpCookie.h b/cocos/network/HttpCookie.h new file mode 100644 index 0000000..f9837b1 --- /dev/null +++ b/cocos/network/HttpCookie.h @@ -0,0 +1,60 @@ +/**************************************************************************** + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef HTTP_COOKIE_H +#define HTTP_COOKIE_H +/// @cond DO_NOT_SHOW + +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +struct CookiesInfo { + ccstd::string domain; + bool tailmatch; + ccstd::string path; + bool secure; + ccstd::string name; + ccstd::string value; + ccstd::string expires; +}; + +class HttpCookie { +public: + void readFile(); + + void writeFile(); + void setCookieFileName(const ccstd::string &fileName); + + const ccstd::vector *getCookies() const; + const CookiesInfo *getMatchCookie(const ccstd::string &url) const; + void updateOrAddCookie(CookiesInfo *cookie); + +private: + ccstd::string _cookieFileName; + ccstd::vector _cookies; +}; + +/// @endcond +#endif /* HTTP_COOKIE_H */ diff --git a/cocos/network/HttpRequest.h b/cocos/network/HttpRequest.h new file mode 100644 index 0000000..ef0ffbf --- /dev/null +++ b/cocos/network/HttpRequest.h @@ -0,0 +1,268 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef __HTTP_REQUEST_H__ +#define __HTTP_REQUEST_H__ + +#include "base/Macros.h" +#include "base/RefCounted.h" + +#include +#include "base/std/container/string.h" + +/** + * @addtogroup network + * @{ + */ + +namespace cc { + +namespace network { + +class HttpClient; +class HttpResponse; + +using ccHttpRequestCallback = std::function; + +/** + * Defines the object which users must packed for HttpClient::send(HttpRequest*) method. + * Please refer to tests/test-cpp/Classes/ExtensionTest/NetworkTest/HttpClientTest.cpp as a sample + * @since v2.0.2 + * + * @lua NA + */ + +#if (CC_PLATFORM == CC_PLATFORM_WINRT) + #ifdef DELETE + #undef DELETE + #endif +#endif + +class CC_DLL HttpRequest : public RefCounted { +public: + /** + * The HttpRequest type enum used in the HttpRequest::setRequestType. + */ + enum class Type { + GET, + POST, + PUT, + DELETE, + HEAD, + PATCH, + UNKNOWN, + }; + + /** + * Constructor. + * Because HttpRequest object will be used between UI thread and network thread, + requestObj->autorelease() is forbidden to avoid crashes in AutoreleasePool + new/retain/release still works, which means you need to release it manually + Please refer to HttpRequestTest.cpp to find its usage. + */ + HttpRequest() + : _callback(nullptr) {} + + /** Destructor. */ + ~HttpRequest() override = default; + + // setter/getters for properties + + /** + * Set request type of HttpRequest object before being sent,now it support the enum value of HttpRequest::Type. + * + * @param type the request type. + */ + inline void setRequestType(Type type) { + _requestType = type; + } + + /** + * Get the request type of HttpRequest object. + * + * @return HttpRequest::Type. + */ + inline Type getRequestType() const { + return _requestType; + } + + /** + * Set the url address of HttpRequest object. + * The url value could be like these: "http://httpbin.org/ip" or "https://httpbin.org/get" + * + * @param url the string object. + */ + inline void setUrl(const ccstd::string &url) { + _url = url; + } + + /** + * Get the url address of HttpRequest object. + * + * @return const char* the pointer of _url. + */ + inline const char *getUrl() const { + return _url.c_str(); + } + + /** + * Set the request data of HttpRequest object. + * + * @param buffer the buffer of request data, it support binary data. + * @param len the size of request data. + */ + inline void setRequestData(const char *buffer, size_t len) { + _requestData.assign(buffer, buffer + len); + } + + /** + * Get the request data pointer of HttpRequest object. + * + * @return char* the request data pointer. + */ + inline char *getRequestData() { + if (!_requestData.empty()) { + return _requestData.data(); + } + + return nullptr; + } + + /** + * Get the size of request data + * + * @return uint32_t the size of request data + */ + inline uint32_t getRequestDataSize() const { + return static_cast(_requestData.size()); + } + + /** + * Set a string tag to identify your request. + * This tag can be found in HttpResponse->getHttpRequest->getTag(). + * + * @param tag the string object. + */ + inline void setTag(const ccstd::string &tag) { + _tag = tag; + } + + /** + * Get the string tag to identify the request. + * The best practice is to use it in your MyClass::onMyHttpRequestCompleted(sender, HttpResponse*) callback. + * + * @return const char* the pointer of _tag + */ + inline const char *getTag() const { + return _tag.c_str(); + } + + /** + * Set user-customed data of HttpRequest object. + * You can attach a customed data in each request, and get it back in response callback. + * But you need to new/delete the data pointer manully. + * + * @param userData the string pointer + */ + inline void setUserData(void *userData) { + _userData = userData; + } + + /** + * Get the user-customed data pointer which were pre-setted. + * Don't forget to delete it. HttpClient/HttpResponse/HttpRequest will do nothing with this pointer. + * + * @return void* the pointer of user-customed data. + */ + inline void *getUserData() const { + return _userData; + } + + /** + * Set response callback function of HttpRequest object. + * When response come back, we would call _pCallback to process response data. + * + * @param callback the ccHttpRequestCallback function. + */ + inline void setResponseCallback(const ccHttpRequestCallback &callback) { + _callback = callback; + } + + /** + * Get ccHttpRequestCallback callback function. + * + * @return const ccHttpRequestCallback& ccHttpRequestCallback callback function. + */ + inline const ccHttpRequestCallback &getResponseCallback() const { + return _callback; + } + + /** + * Set custom-defined headers. + * + * @param pHeaders the string vector of custom-defined headers. + */ + inline void setHeaders(const ccstd::vector &headers) { + _headers = headers; + } + + /** + * Get custom headers. + * + * @return ccstd::vector the string vector of custom-defined headers. + */ + inline ccstd::vector getHeaders() const { + return _headers; + } + + inline void setTimeout(float timeoutInSeconds) { + _timeoutInSeconds = timeoutInSeconds; + } + + inline float getTimeout() const { + return _timeoutInSeconds; + } + +protected: + // properties + Type _requestType{Type::UNKNOWN}; /// kHttpRequestGet, kHttpRequestPost or other enums + ccstd::string _url; /// target url that this request is sent to + ccstd::vector _requestData; /// used for POST + ccstd::string _tag; /// user defined tag, to identify different requests in response callback + ccHttpRequestCallback _callback; /// C++11 style callbacks + void *_userData{nullptr}; /// You can add your customed data here + ccstd::vector _headers; /// custom http headers + float _timeoutInSeconds{10.F}; +}; + +} // namespace network + +} // namespace cc + +// end group +/// @} + +#endif //__HTTP_REQUEST_H__ diff --git a/cocos/network/HttpResponse.h b/cocos/network/HttpResponse.h new file mode 100644 index 0000000..974fac5 --- /dev/null +++ b/cocos/network/HttpResponse.h @@ -0,0 +1,211 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef __HTTP_RESPONSE__ +#define __HTTP_RESPONSE__ + +#include "network/HttpRequest.h" + +/** + * @addtogroup network + * @{ + */ + +namespace cc { + +namespace network { + +/** + * @brief defines the object which users will receive at onHttpCompleted(sender, HttpResponse) callback. + * Please refer to samples/TestCpp/Classes/ExtensionTest/NetworkTest/HttpClientTest.cpp as a sample. + * @since v2.0.2. + * @lua NA + */ +class CC_DLL HttpResponse : public cc::RefCounted { +public: + /** + * Constructor, it's used by HttpClient internal, users don't need to create HttpResponse manually. + * @param request the corresponding HttpRequest which leads to this response. + */ + HttpResponse(HttpRequest *request) + : _pHttpRequest(request), + _succeed(false), + _responseDataString("") { + if (_pHttpRequest) { + _pHttpRequest->addRef(); + } + } + + /** + * Destructor, it will be called in HttpClient internal. + * Users don't need to destruct HttpResponse object manually. + */ + virtual ~HttpResponse() { + if (_pHttpRequest) { + _pHttpRequest->release(); + } + } + + // getters, will be called by users + + /** + * Get the corresponding HttpRequest object which leads to this response. + * There's no paired setter for it, because it's already set in class constructor + * @return HttpRequest* the corresponding HttpRequest object which leads to this response. + */ + inline HttpRequest *getHttpRequest() const { + return _pHttpRequest; + } + + /** + * To see if the http request is returned successfully. + * Although users can judge if (http response code = 200), we want an easier way. + * If this getter returns false, you can call getResponseCode and getErrorBuffer to find more details. + * @return bool the flag that represent whether the http request return successfully or not. + */ + inline bool isSucceed() const { + return _succeed; + } + + /** + * Get the http response data. + * @return ccstd::vector* the pointer that point to the _responseData. + */ + inline ccstd::vector *getResponseData() { + return &_responseData; + } + + /** + * Get the response headers. + * @return ccstd::vector* the pointer that point to the _responseHeader. + */ + inline ccstd::vector *getResponseHeader() { + return &_responseHeader; + } + + /** + * Get the http response code to judge whether response is successful or not. + * I know that you want to see the _responseCode is 200. + * If _responseCode is not 200, you should check the meaning for _responseCode by the net. + * @return long the value of _responseCode + */ + inline long getResponseCode() const { + return _responseCode; + } + + /** + * Get the error buffer which will tell you more about the reason why http request failed. + * @return const char* the pointer that point to _errorBuffer. + */ + inline const char *getErrorBuffer() const { + return _errorBuffer.c_str(); + } + + // setters, will be called by HttpClient + // users should avoid invoking these methods + + /** + * Set whether the http request is returned successfully or not, + * This setter is mainly used in HttpClient, users mustn't set it directly + * @param value the flag represent whether the http request is successful or not. + */ + inline void setSucceed(bool value) { + _succeed = value; + } + + /** + * Set the http response data buffer, it is used by HttpClient. + * @param data the pointer point to the response data buffer. + */ + inline void setResponseData(ccstd::vector *data) { + _responseData = *data; + } + + /** + * Set the http response headers buffer, it is used by HttpClient. + * @param data the pointer point to the response headers buffer. + */ + inline void setResponseHeader(ccstd::vector *data) { + _responseHeader = *data; + } + + /** + * Set the http response code. + * @param value the http response code that represent whether the request is successful or not. + */ + inline void setResponseCode(long value) { + _responseCode = value; + } + + /** + * Set the error buffer which will tell you more the reason why http request failed. + * @param value a string pointer that point to the reason. + */ + inline void setErrorBuffer(const char *value) { + _errorBuffer.clear(); + if (value != nullptr) + _errorBuffer.assign(value); + } + + /** + * Set the response data by the string pointer and the defined size. + * @param value a string pointer that point to response data buffer. + * @param n the defined size that the response data buffer would be copied. + */ + inline void setResponseDataString(const char *value, size_t n) { + _responseDataString.clear(); + _responseDataString.assign(value, n); + } + + /** + * Get the string pointer that point to the response data. + * @return const char* the string pointer that point to the response data. + */ + inline const char *getResponseDataString() const { + return _responseDataString.c_str(); + } + +protected: + bool initWithRequest(HttpRequest *request); + + // properties + HttpRequest *_pHttpRequest; /// the corresponding HttpRequest pointer who leads to this response + bool _succeed; /// to indicate if the http request is successful simply + ccstd::vector _responseData; /// the returned raw data. You can also dump it as a string + ccstd::vector _responseHeader; /// the returned raw header data. You can also dump it as a string + long _responseCode; /// the status code returned from libcurl, e.g. 200, 404 + ccstd::string _errorBuffer; /// if _responseCode != 200, please read _errorBuffer to find the reason + ccstd::string _responseDataString; // the returned raw data. You can also dump it as a string +}; + +} // namespace network + +} // namespace cc + +// end group +/// @} + +#endif //__HTTP_RESPONSE_H__ diff --git a/cocos/network/SocketIO.cpp b/cocos/network/SocketIO.cpp new file mode 100644 index 0000000..baa5c72 --- /dev/null +++ b/cocos/network/SocketIO.cpp @@ -0,0 +1,1069 @@ +/**************************************************************************** + Copyright (c) 2015 Chris Hannon http://www.channon.us + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "network/SocketIO.h" +#include +#include +#include +#include +#include "application/ApplicationManager.h" +#include "base/Log.h" +#include "base/UTF8.h" +#include "base/memory/Memory.h" +#include "network/HttpClient.h" +#include "network/Uri.h" +#include "network/WebSocket.h" + +#include "json/document-wrapper.h" +#include "json/rapidjson.h" +#include "json/stringbuffer.h" +#include "json/writer.h" + +namespace cc { + +namespace network { + +//class declarations + +class SocketIOPacketV10x; + +class SocketIOPacket { +public: + enum class SocketIOVersion { + V09X, + V10X + }; + + SocketIOPacket(); + virtual ~SocketIOPacket(); + void initWithType(const ccstd::string &packetType); + void initWithTypeIndex(int index); + + ccstd::string toString() const; + virtual int typeAsNumber() const; + const ccstd::string &typeForIndex(int index) const; + + void setEndpoint(const ccstd::string &endpoint) { _endpoint = endpoint; }; + const ccstd::string &getEndpoint() const { return _endpoint; }; + void setEvent(const ccstd::string &event) { _name = event; }; + const ccstd::string &getEvent() const { return _name; }; + + void addData(const ccstd::string &data); + ccstd::vector getData() const { return _args; }; + virtual ccstd::string stringify() const; + + static SocketIOPacket *createPacketWithType(const ccstd::string &type, SocketIOVersion version); + static SocketIOPacket *createPacketWithTypeIndex(int type, SocketIOVersion version); + +protected: + ccstd::string _pId; //id message + ccstd::string _ack; // + ccstd::string _name; //event name + ccstd::vector _args; //we will be using a vector of strings to store multiple data + ccstd::string _endpoint; // + ccstd::string _endpointseparator; //socket.io 1.x requires a ',' between endpoint and payload + ccstd::string _type; //message type + ccstd::string _separator; //for stringify the object + ccstd::vector _types; //types of messages +}; + +class SocketIOPacketV10x : public SocketIOPacket { +public: + SocketIOPacketV10x(); + ~SocketIOPacketV10x() override; + int typeAsNumber() const override; + ccstd::string stringify() const override; + +private: + ccstd::vector _typesMessage; +}; + +SocketIOPacket::SocketIOPacket() : _separator(":") { + _types.emplace_back("disconnect"); + _types.emplace_back("connect"); + _types.emplace_back("heartbeat"); + _types.emplace_back("message"); + _types.emplace_back("json"); + _types.emplace_back("event"); + _types.emplace_back("ack"); + _types.emplace_back("error"); + _types.emplace_back("noop"); +} + +SocketIOPacket::~SocketIOPacket() { + _types.clear(); +} + +void SocketIOPacket::initWithType(const ccstd::string &packetType) { + _type = packetType; +} +void SocketIOPacket::initWithTypeIndex(int index) { + _type = _types.at(index); +} + +ccstd::string SocketIOPacket::toString() const { + std::stringstream encoded; + encoded << this->typeAsNumber(); + encoded << this->_separator; + + ccstd::string pIdL = _pId; + if (_ack == "data") { + pIdL += "+"; + } + + // Do not write pid for acknowledgements + if (_type != "ack") { + encoded << pIdL; + } + encoded << this->_separator; + + // Add the endpoint for the namespace to be used if not the default namespace "" or "/", and as long as it is not an ACK, heartbeat, or disconnect packet + if (_endpoint != "/" && !_endpoint.empty() && _type != "ack" && _type != "heartbeat" && _type != "disconnect") { + encoded << _endpoint << _endpointseparator; + } + encoded << this->_separator; + + if (!_args.empty()) { + ccstd::string ackpId; + // This is an acknowledgement packet, so, prepend the ack pid to the data + if (_type == "ack") { + ackpId += pIdL + "+"; + } + + encoded << ackpId << this->stringify(); + } + + return encoded.str(); +} +int SocketIOPacket::typeAsNumber() const { + ccstd::string::size_type num = 0; + auto item = std::find(_types.begin(), _types.end(), _type); + if (item != _types.end()) { + num = item - _types.begin(); + } + return static_cast(num); +} +const ccstd::string &SocketIOPacket::typeForIndex(int index) const { + return _types.at(index); +} + +void SocketIOPacket::addData(const ccstd::string &data) { + this->_args.push_back(data); +} + +ccstd::string SocketIOPacket::stringify() const { + ccstd::string outS; + if (_type == "message") { + outS = _args[0]; + } else { + rapidjson::StringBuffer s; + rapidjson::Writer writer(s); + + writer.StartObject(); + writer.String("name"); + writer.String(_name.c_str()); + + writer.String("args"); + + writer.StartArray(); + + for (const auto &item : _args) { + writer.String(item.c_str()); + } + + writer.EndArray(); + writer.EndObject(); + + outS = s.GetString(); + + CC_LOG_INFO("create args object: %s:", outS.c_str()); + } + + return outS; +} + +SocketIOPacketV10x::SocketIOPacketV10x() { + _separator = ""; + _endpointseparator = ","; + _types.emplace_back("disconnected"); + _types.emplace_back("connected"); + _types.emplace_back("heartbeat"); + _types.emplace_back("pong"); + _types.emplace_back("message"); + _types.emplace_back("upgrade"); + _types.emplace_back("noop"); + _typesMessage.emplace_back("connect"); + _typesMessage.emplace_back("disconnect"); + _typesMessage.emplace_back("event"); + _typesMessage.emplace_back("ack"); + _typesMessage.emplace_back("error"); + _typesMessage.emplace_back("binarevent"); + _typesMessage.emplace_back("binaryack"); +} + +int SocketIOPacketV10x::typeAsNumber() const { + ccstd::vector::size_type num = 0; + auto item = std::find(_typesMessage.begin(), _typesMessage.end(), _type); + if (item != _typesMessage.end()) { //it's a message + num = item - _typesMessage.begin(); + num += 40; + } else { + item = std::find(_types.begin(), _types.end(), _type); + num += item - _types.begin(); + } + return static_cast(num); +} + +ccstd::string SocketIOPacketV10x::stringify() const { + ccstd::string outS; + + rapidjson::StringBuffer s; + rapidjson::Writer writer(s); + + writer.StartArray(); + writer.String(_name.c_str()); + + for (const auto &item : _args) { + writer.String(item.c_str()); + } + + writer.EndArray(); + + outS = s.GetString(); + + CC_LOG_INFO("create args object: %s:", outS.c_str()); + + return outS; +} + +SocketIOPacketV10x::~SocketIOPacketV10x() { + _types.clear(); + _typesMessage.clear(); +} + +SocketIOPacket *SocketIOPacket::createPacketWithType(const ccstd::string &type, SocketIOPacket::SocketIOVersion version) { + SocketIOPacket *ret; + switch (version) { + case SocketIOPacket::SocketIOVersion::V09X: + ret = ccnew SocketIOPacket; + break; + case SocketIOPacket::SocketIOVersion::V10X: + ret = ccnew SocketIOPacketV10x; + break; + } + ret->initWithType(type); + return ret; +} + +SocketIOPacket *SocketIOPacket::createPacketWithTypeIndex(int type, SocketIOPacket::SocketIOVersion version) { + SocketIOPacket *ret; + switch (version) { + case SocketIOPacket::SocketIOVersion::V09X: + ret = ccnew SocketIOPacket; + break; + case SocketIOPacket::SocketIOVersion::V10X: + return ccnew SocketIOPacketV10x; + break; + } + ret->initWithTypeIndex(type); + return ret; +} + +/** + * @brief The implementation of the socket.io connection + * Clients/endpoints may share the same impl to accomplish multiplexing on the same websocket + */ +class SIOClientImpl : public cc::RefCounted, + public WebSocket::Delegate { +private: + int _heartbeat, _timeout; + ccstd::string _sid; + Uri _uri; + ccstd::string _caFilePath; + bool _connected; + SocketIOPacket::SocketIOVersion _version; + + WebSocket *_ws; + + RefMap _clients; + +public: + SIOClientImpl(Uri uri, ccstd::string caFilePath); + ~SIOClientImpl() override; + + static SIOClientImpl *create(const Uri &uri, const ccstd::string &caFilePath); + + void onOpen(WebSocket *ws) override; + void onMessage(WebSocket *ws, const WebSocket::Data &data) override; + void onClose(WebSocket *ws, uint16_t code, const ccstd::string &reason, bool wasClean) override; + void onError(WebSocket *ws, const WebSocket::ErrorCode &error) override; + + void connect(); + void disconnect(); + static bool init(); + void handshake(); + void handshakeResponse(HttpClient *sender, HttpResponse *response); + void openSocket(); + void heartbeat(float dt); + + SIOClient *getClient(const ccstd::string &endpoint); + void addClient(const ccstd::string &endpoint, SIOClient *client); + + void connectToEndpoint(const ccstd::string &endpoint); + void disconnectFromEndpoint(const ccstd::string &endpoint); + + void send(const ccstd::string &endpoint, const ccstd::string &s); + void send(SocketIOPacket *packet); + void emit(const ccstd::string &endpoint, const ccstd::string &eventname, const ccstd::string &args); +}; + +//method implementations + +//begin SIOClientImpl methods +SIOClientImpl::SIOClientImpl(Uri uri, ccstd::string caFilePath) : _uri(std::move(uri)), + _caFilePath(std::move(caFilePath)), + _connected(false), + _ws(nullptr) { +} + +SIOClientImpl::~SIOClientImpl() { + CC_ASSERT(!_connected); + + CC_SAFE_RELEASE(_ws); +} + +void SIOClientImpl::handshake() { + CC_LOG_INFO("SIOClientImpl::handshake() called"); + + std::stringstream pre; + + if (_uri.isSecure()) { + pre << "https://"; + } else { + pre << "http://"; + } + + pre << _uri.getAuthority() << "/socket.io/1/?EIO=2&transport=polling&b64=true"; + + auto *request = ccnew HttpRequest(); + request->setUrl(pre.str()); + request->setRequestType(HttpRequest::Type::GET); + + request->setResponseCallback([this](auto &&pH1, auto &&pH2) { this->handshakeResponse(std::forward(pH1), std::forward(pH2)); }); + request->setTag("handshake"); + + CC_LOG_INFO("SIOClientImpl::handshake() waiting"); + + if (_uri.isSecure() && !_caFilePath.empty()) { + HttpClient::getInstance()->setSSLVerification(_caFilePath); + } + HttpClient::getInstance()->send(request); + + request->release(); +} + +void SIOClientImpl::handshakeResponse(HttpClient * /*sender*/, HttpResponse *response) { + CC_LOG_INFO("SIOClientImpl::handshakeResponse() called"); + + if (0 != strlen(response->getHttpRequest()->getTag())) { + CC_LOG_INFO("%s completed", response->getHttpRequest()->getTag()); + } + + auto statusCode = static_cast(response->getResponseCode()); + char statusString[64] = {}; + sprintf(statusString, "HTTP Status Code: %d, tag = %s", statusCode, response->getHttpRequest()->getTag()); + CC_LOG_INFO("response code: %ld", statusCode); + + if (!response->isSucceed() || statusCode >= 400) { + CC_LOG_ERROR("SIOClientImpl::handshake() failed"); + CC_LOG_ERROR("error buffer: %s", response->getErrorBuffer()); + + for (auto &client : _clients) { + client.second->getDelegate()->onError(client.second, response->getErrorBuffer()); + } + + onClose(nullptr, 1015, "handshake_failure", false); + return; + } + + CC_LOG_INFO("SIOClientImpl::handshake() succeeded"); + + ccstd::vector *buffer = response->getResponseData(); + std::stringstream s; + s.str(""); + + for (const auto &iter : *buffer) { + s << iter; + } + + CC_LOG_INFO("SIOClientImpl::handshake() dump data: %s", s.str().c_str()); + + ccstd::string res = s.str(); + ccstd::string sid; + int heartbeat = 0; + int timeout = 0; + + if (res.find('}') != ccstd::string::npos) { + CC_LOG_INFO("SIOClientImpl::handshake() Socket.IO 1.x detected"); + _version = SocketIOPacket::SocketIOVersion::V10X; + // sample: 97:0{"sid":"GMkL6lzCmgMvMs9bAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000} + // 96:0{"sid":"jzrjDlQusSUxLTd3AAAV","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000}2:40 + ccstd::string::size_type a; + ccstd::string::size_type b; + a = res.find('{'); + ccstd::string temp = res.substr(a, res.size() - a); + + // find the sid + a = temp.find(':'); + b = temp.find(','); + + sid = temp.substr(a + 2, b - (a + 3)); + + temp = temp.erase(0, b + 1); + + // chomp past the upgrades + b = temp.find(','); + + temp = temp.erase(0, b + 1); + + // get the pingInterval / heartbeat + a = temp.find(':'); + b = temp.find(','); + + ccstd::string heartbeatStr = temp.substr(a + 1, b - a); + heartbeat = atoi(heartbeatStr.c_str()) / 1000; + temp = temp.erase(0, b + 1); + + // get the timeout + a = temp.find(':'); + b = temp.find('}'); + + ccstd::string timeoutStr = temp.substr(a + 1, b - a); + timeout = atoi(timeoutStr.c_str()) / 1000; + CC_LOG_INFO("done parsing 1.x"); + + } else { + CC_LOG_INFO("SIOClientImpl::handshake() Socket.IO 0.9.x detected"); + _version = SocketIOPacket::SocketIOVersion::V09X; + // sample: 3GYzE9md2Ig-lm3cf8Rv:60:60:websocket,htmlfile,xhr-polling,jsonp-polling + size_t pos = 0; + + pos = res.find(':'); + if (pos != ccstd::string::npos) { + sid = res.substr(0, pos); + res.erase(0, pos + 1); + } + + pos = res.find(':'); + if (pos != ccstd::string::npos) { + heartbeat = atoi(res.substr(pos + 1, res.size()).c_str()); + } + + pos = res.find(':'); + if (pos != ccstd::string::npos) { + timeout = atoi(res.substr(pos + 1, res.size()).c_str()); + } + } + + _sid = sid; + _heartbeat = heartbeat; + _timeout = timeout; + + openSocket(); +} + +void SIOClientImpl::openSocket() { + CC_LOG_INFO("SIOClientImpl::openSocket() called"); + + std::stringstream s; + + if (_uri.isSecure()) { + s << "wss://"; + } else { + s << "ws://"; + } + + switch (_version) { + case SocketIOPacket::SocketIOVersion::V09X: + s << _uri.getAuthority() << "/socket.io/1/websocket/" << _sid; + break; + case SocketIOPacket::SocketIOVersion::V10X: + s << _uri.getAuthority() << "/socket.io/1/websocket/?EIO=2&transport=websocket&sid=" << _sid; + break; + } + + _ws = ccnew WebSocket(); + if (!_ws->init(*this, s.str(), nullptr, _caFilePath)) { + CC_SAFE_RELEASE_NULL(_ws); + } +} + +bool SIOClientImpl::init() { + CC_LOG_INFO("SIOClientImpl::init() successful"); + return true; +} + +void SIOClientImpl::connect() { + this->handshake(); +} + +void SIOClientImpl::disconnect() { + if (_ws->getReadyState() == WebSocket::State::OPEN) { + ccstd::string s; + ccstd::string endpoint; + s = ""; + endpoint = ""; + + if (_version == SocketIOPacket::SocketIOVersion::V09X) { + s = "0::" + endpoint; + } else { + s = "41" + endpoint; + } + _ws->send(s); + } + + CC_CURRENT_ENGINE()->getScheduler()->unscheduleAllForTarget(this); + + _connected = false; + + SocketIO::getInstance()->removeSocket(_uri.getAuthority()); + + // Close websocket connection should be at last. + _ws->closeAsync(); +} + +SIOClientImpl *SIOClientImpl::create(const Uri &uri, const ccstd::string &caFilePath) { + auto *s = ccnew SIOClientImpl(uri, caFilePath); + + if (s && s->init()) { + return s; + } + + return nullptr; +} + +SIOClient *SIOClientImpl::getClient(const ccstd::string &endpoint) { + return _clients.at(endpoint); +} + +void SIOClientImpl::addClient(const ccstd::string &endpoint, SIOClient *client) { + _clients.insert(endpoint, client); +} + +void SIOClientImpl::connectToEndpoint(const ccstd::string &endpoint) { + SocketIOPacket *packet = SocketIOPacket::createPacketWithType("connect", _version); + packet->setEndpoint(endpoint); + this->send(packet); + delete packet; +} + +void SIOClientImpl::disconnectFromEndpoint(const ccstd::string &endpoint) { + if (_clients.size() == 1 || endpoint == "/") { + CC_LOG_INFO("SIOClientImpl::disconnectFromEndpoint out of endpoints, checking for disconnect"); + + if (_connected) { + this->disconnect(); + } + } else { + ccstd::string path = endpoint == "/" ? "" : endpoint; + + ccstd::string s = "0::" + path; + + _ws->send(s); + _clients.erase(endpoint); + } +} + +void SIOClientImpl::heartbeat(float /*dt*/) { + SocketIOPacket *packet = SocketIOPacket::createPacketWithType("heartbeat", _version); + + this->send(packet); + delete packet; + + CC_LOG_INFO("Heartbeat sent"); +} + +void SIOClientImpl::send(const ccstd::string &endpoint, const ccstd::string &s) { + switch (_version) { + case SocketIOPacket::SocketIOVersion::V09X: { + SocketIOPacket *packet = SocketIOPacket::createPacketWithType("message", _version); + packet->setEndpoint(endpoint); + packet->addData(s); + this->send(packet); + delete packet; + break; + } + case SocketIOPacket::SocketIOVersion::V10X: { + this->emit(endpoint, "message", s); + break; + } + } +} + +void SIOClientImpl::send(SocketIOPacket *packet) { + ccstd::string req = packet->toString(); + if (_connected) { + CC_LOG_INFO("-->SEND:%s", req.data()); + _ws->send(req); + } else + CC_LOG_INFO("Cant send the message (%s) because disconnected", req.c_str()); +} + +void SIOClientImpl::emit(const ccstd::string &endpoint, const ccstd::string &eventname, const ccstd::string &args) { + CC_LOG_INFO("Emitting event \"%s\"", eventname.c_str()); + SocketIOPacket *packet = SocketIOPacket::createPacketWithType("event", _version); + packet->setEndpoint(endpoint == "/" ? "" : endpoint); + packet->setEvent(eventname); + packet->addData(args); + this->send(packet); + delete packet; +} + +void SIOClientImpl::onOpen(WebSocket * /*ws*/) { + _connected = true; + + SocketIO::getInstance()->addSocket(_uri.getAuthority(), this); + + if (_version == SocketIOPacket::SocketIOVersion::V10X) { + ccstd::string s = "5"; //That's a ping https://github.com/Automattic/engine.io-parser/blob/1b8e077b2218f4947a69f5ad18be2a512ed54e93/lib/index.js#L21 + _ws->send(s); + } + + CC_CURRENT_ENGINE()->getScheduler()->schedule([this](auto &&pH1) { this->heartbeat(std::forward(pH1)); }, this, (static_cast(_heartbeat) * .9F), false, "heartbeat"); + + for (auto &client : _clients) { + client.second->onOpen(); + } + + CC_LOG_INFO("SIOClientImpl::onOpen socket connected!"); +} + +void SIOClientImpl::onMessage(WebSocket * /*ws*/, const WebSocket::Data &data) { + CC_LOG_INFO("SIOClientImpl::onMessage received: %s", data.bytes); + + ccstd::string payload = data.bytes; + int control = atoi(payload.substr(0, 1).c_str()); + payload = payload.substr(1, payload.size() - 1); + + SIOClient *c = nullptr; + + switch (_version) { + case SocketIOPacket::SocketIOVersion::V09X: { + ccstd::string msgid; + ccstd::string endpoint; + ccstd::string sData; + ccstd::string eventname; + + ccstd::string::size_type pos; + ccstd::string::size_type pos2; + + pos = payload.find(':'); + if (pos != ccstd::string::npos) { + payload.erase(0, pos + 1); + } + + pos = payload.find(':'); + if (pos != ccstd::string::npos) { + msgid = std::to_string(atoi(payload.substr(0, pos + 1).c_str())); + } + payload.erase(0, pos + 1); + + pos = payload.find(':'); + if (pos != ccstd::string::npos) { + endpoint = payload.substr(0, pos); + payload.erase(0, pos + 1); + } else { + endpoint = payload; + } + + if (endpoint.empty()) endpoint = "/"; + + c = getClient(endpoint); + + sData = payload; + + if (c == nullptr) CC_LOG_INFO("SIOClientImpl::onMessage client lookup returned nullptr"); + + switch (control) { + case 0: + CC_LOG_INFO("Received Disconnect Signal for Endpoint: %s\n", endpoint.c_str()); + disconnectFromEndpoint(endpoint); + if (c) { + c->fireEvent("disconnect", payload); + } + break; + case 1: + CC_LOG_INFO("Connected to endpoint: %s \n", endpoint.c_str()); + if (c) { + c->onConnect(); + c->fireEvent("connect", payload); + } + break; + case 2: + CC_LOG_INFO("Heartbeat received\n"); + break; + case 3: + CC_LOG_INFO("Message received: %s \n", sData.c_str()); + if (c) c->getDelegate()->onMessage(c, sData); + if (c) c->fireEvent("message", sData); + break; + case 4: + CC_LOG_INFO("JSON Message Received: %s \n", sData.c_str()); + if (c) c->getDelegate()->onMessage(c, sData); + if (c) c->fireEvent("json", sData); + break; + case 5: + CC_LOG_INFO("Event Received with data: %s \n", sData.c_str()); + + if (c) { + eventname = ""; + pos = sData.find(':'); + pos2 = sData.find(','); + if (pos2 > pos) { + eventname = sData.substr(pos + 2, pos2 - (pos + 3)); + sData = sData.substr(pos2 + 9, sData.size() - (pos2 + 11)); + } + + c->fireEvent(eventname, sData); + } + + break; + case 6: + CC_LOG_INFO("Message Ack\n"); + break; + case 7: + CC_LOG_ERROR("Error\n"); + //if (c) c->getDelegate()->onError(c, s_data); + if (c) c->fireEvent("error", sData); + break; + case 8: + CC_LOG_INFO("Noop\n"); + break; + } + } break; + case SocketIOPacket::SocketIOVersion::V10X: { + switch (control) { + case 0: + CC_LOG_INFO("Not supposed to receive control 0 for websocket"); + CC_LOG_INFO("That's not good"); + break; + case 1: + CC_LOG_INFO("Not supposed to receive control 1 for websocket"); + break; + case 2: + CC_LOG_INFO("Ping received, send pong"); + payload = "3" + payload; + _ws->send(payload); + break; + case 3: + CC_LOG_INFO("Pong received"); + if (payload == "probe") { + CC_LOG_INFO("Request Update"); + _ws->send("5"); + } + break; + case 4: { + int control2 = payload.at(0) - '0'; + CC_LOG_INFO("Message code: [%i]", control2); + + ccstd::string endpoint; + + ccstd::string::size_type a = payload.find('/'); + ccstd::string::size_type b = payload.find('['); + + if (b != ccstd::string::npos) { + if (a != ccstd::string::npos && a < b) { + //we have an endpoint and a payload + endpoint = payload.substr(a, b - (a + 1)); + } + } else if (a != ccstd::string::npos) { + //we have an endpoint with no payload + endpoint = payload.substr(a, payload.size() - a); + } + + // we didn't find and endpoint and we are in the default namespace + if (endpoint.empty()) endpoint = "/"; + + c = getClient(endpoint); + + payload = payload.substr(1); + + if (endpoint != "/") payload = payload.substr(endpoint.size()); + if (endpoint != "/" && !payload.empty()) payload = payload.substr(1); + + switch (control2) { + case 0: + CC_LOG_INFO("Socket Connected"); + if (c) { + c->onConnect(); + c->fireEvent("connect", payload); + } + break; + case 1: + CC_LOG_INFO("Socket Disconnected"); + disconnectFromEndpoint(endpoint); + c->fireEvent("disconnect", payload); + break; + case 2: { + CC_LOG_INFO("Event Received (%s)", payload.c_str()); + + ccstd::string::size_type payloadFirstSlashPos = payload.find('\"'); + ccstd::string::size_type payloadSecondSlashPos = payload.substr(payloadFirstSlashPos + 1).find('\"'); + + ccstd::string eventname = payload.substr(payloadFirstSlashPos + 1, + payloadSecondSlashPos - payloadFirstSlashPos + 1); + + CC_LOG_INFO("event name %s between %i and %i", eventname.c_str(), + payloadFirstSlashPos, payloadSecondSlashPos); + + payload = payload.substr(payloadSecondSlashPos + 4, + payload.size() - (payloadSecondSlashPos + 5)); + + if (c) c->fireEvent(eventname, payload); + if (c) c->getDelegate()->onMessage(c, payload); + + } break; + case 3: + CC_LOG_INFO("Message Ack"); + break; + case 4: + CC_LOG_ERROR("Error"); + if (c) c->fireEvent("error", payload); + break; + case 5: + CC_LOG_INFO("Binary Event"); + break; + case 6: + CC_LOG_INFO("Binary Ack"); + break; + } + } break; + case 5: + CC_LOG_INFO("Upgrade required"); + break; + case 6: + CC_LOG_INFO("Noop\n"); + break; + } + } break; + } +} + +void SIOClientImpl::onClose(WebSocket * /*ws*/, uint16_t /*code*/, const ccstd::string & /*reason*/, bool /*wasClean*/) { + if (!_clients.empty()) { + for (auto &client : _clients) { + client.second->socketClosed(); + } + // discard this client + _connected = false; + if (CC_CURRENT_APPLICATION() != nullptr) { + CC_CURRENT_APPLICATION()->getEngine()->getScheduler()->unscheduleAllForTarget(this); + } + + SocketIO::getInstance()->removeSocket(_uri.getAuthority()); + _clients.clear(); + } + + this->release(); +} + +void SIOClientImpl::onError(WebSocket * /*ws*/, const WebSocket::ErrorCode &error) { + CC_LOG_ERROR("Websocket error received: %d", static_cast(error)); +} + +//begin SIOClient methods +SIOClient::SIOClient(ccstd::string path, SIOClientImpl *impl, SocketIO::SIODelegate &delegate) +: _path(std::move(path)), + _connected(false), + _socket(impl), + _delegate(&delegate) { + static uint32_t instanceIdCounter = 0; + _instanceId = instanceIdCounter++; +} + +SIOClient::~SIOClient() { + CC_ASSERT(!_connected); +} + +void SIOClient::onOpen() { + if (_path != "/") { + _socket->connectToEndpoint(_path); + } +} + +void SIOClient::onConnect() { + _connected = true; +} + +void SIOClient::send(const ccstd::string &s) { + if (_connected) { + _socket->send(_path, s); + } else { + _delegate->onError(this, "Client not yet connected"); + } +} + +void SIOClient::emit(const ccstd::string &eventname, const ccstd::string &args) { + if (_connected) { + _socket->emit(_path, eventname, args); + } else { + _delegate->onError(this, "Client not yet connected"); + } +} + +void SIOClient::disconnect() { + if (_connected) { + _connected = false; + _socket->disconnectFromEndpoint(_path); + } +} + +void SIOClient::socketClosed() { + _connected = false; + + _delegate->onClose(this); + + this->release(); +} + +void SIOClient::on(const ccstd::string &eventName, SIOEvent e) { + _eventRegistry[eventName] = std::move(e); +} + +void SIOClient::fireEvent(const ccstd::string &eventName, const ccstd::string &data) { + CC_LOG_INFO("SIOClient::fireEvent called with event name: %s and data: %s", eventName.c_str(), data.c_str()); + + _delegate->fireEventToScript(this, eventName, data); + + if (_eventRegistry[eventName]) { + SIOEvent e = _eventRegistry[eventName]; + + e(this, data); + + return; + } + + CC_LOG_INFO("SIOClient::fireEvent no native event with name %s found", eventName.c_str()); +} + +void SIOClient::setTag(const char *tag) { + _tag = tag; +} + +uint32_t SIOClient::getInstanceId() const { + return _instanceId; +} + +//begin SocketIO methods +SocketIO *SocketIO::inst = nullptr; + +SocketIO::SocketIO() = default; + +SocketIO::~SocketIO() = default; + +SocketIO *SocketIO::getInstance() { + if (nullptr == inst) { + inst = ccnew SocketIO(); + } + + return inst; +} + +void SocketIO::destroyInstance() { + CC_SAFE_DELETE(inst); +} + +SIOClient *SocketIO::connect(SIODelegate &delegate, const ccstd::string &uri) { + return SocketIO::connect(uri, delegate); +} + +SIOClient *SocketIO::connect(const ccstd::string &uri, SIODelegate &delegate) { + return SocketIO::connect(uri, delegate, ""); +} + +SIOClient *SocketIO::connect(const ccstd::string &uri, SIODelegate &delegate, const ccstd::string &caFilePath) { + Uri uriObj = Uri::parse(uri); + + SIOClientImpl *socket = SocketIO::getInstance()->getSocket(uriObj.getAuthority()); + SIOClient *c = nullptr; + + ccstd::string path = uriObj.getPath(); + if (path.empty()) { + path = "/"; + } + + if (socket == nullptr) { + //create a new socket, new client, connect + socket = SIOClientImpl::create(uriObj, caFilePath); + + c = ccnew SIOClient(path, socket, delegate); + + socket->addClient(path, c); + + socket->connect(); + } else { + //check if already connected to endpoint, handle + c = socket->getClient(path); + + if (c == nullptr) { + c = ccnew SIOClient(path, socket, delegate); + + socket->addClient(path, c); + + socket->connectToEndpoint(path); + } else { + CC_LOG_DEBUG("SocketIO: disconnect previous client"); + c->disconnect(); + + CC_LOG_DEBUG("SocketIO: recreate a new socket, new client, connect"); + SIOClientImpl *newSocket = SIOClientImpl::create(uriObj, caFilePath); + auto *newC = ccnew SIOClient(path, newSocket, delegate); + + newSocket->addClient(path, newC); + newSocket->connect(); + + return newC; + } + } + + return c; +} + +SIOClientImpl *SocketIO::getSocket(const ccstd::string &uri) { + return _sockets.at(uri); +} + +void SocketIO::addSocket(const ccstd::string &uri, SIOClientImpl *socket) { + _sockets.insert(uri, socket); +} + +void SocketIO::removeSocket(const ccstd::string &uri) { + _sockets.erase(uri); +} + +} // namespace network + +} // namespace cc diff --git a/cocos/network/SocketIO.h b/cocos/network/SocketIO.h new file mode 100644 index 0000000..118e69c --- /dev/null +++ b/cocos/network/SocketIO.h @@ -0,0 +1,261 @@ +/**************************************************************************** + Copyright (c) 2015 Chris Hannon http://www.channon.us + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" +#include "base/RefMap.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" + +/** + * @addtogroup network + * @{ + */ + +namespace cc { + +namespace network { + +//forward declarations +class SIOClientImpl; +class SIOClient; + +/** + * Singleton and wrapper class to provide static creation method as well as registry of all sockets. + * + * @lua NA + */ +class CC_DLL SocketIO { +public: + /** + * Get instance of SocketIO. + * + * @return SocketIO* the instance of SocketIO. + */ + static SocketIO *getInstance(); + static void destroyInstance(); + + /** + * The delegate class to process socket.io events. + * @lua NA + */ + class SIODelegate { + public: + /** Destructor of SIODelegate. */ + virtual ~SIODelegate() = default; + /** + * This is kept for backwards compatibility, connect is now fired as a socket.io event "connect" + * + * This function would be called when the related SIOClient object receive messages that mean it have connected to endpoint successfully. + * + * @param client the connected SIOClient object. + */ + virtual void onConnect(SIOClient * /*client*/) { CC_LOG_DEBUG("SIODelegate onConnect fired"); }; + /** + * This is kept for backwards compatibility, message is now fired as a socket.io event "message" + * + * This function would be called when the related SIOClient object receive message or json message. + * + * @param client the connected SIOClient object. + * @param data the message,it could be json message + */ + virtual void onMessage(SIOClient * /*client*/, const ccstd::string &data) { CC_LOG_DEBUG("SIODelegate onMessage fired with data: %s", data.c_str()); }; + /** + * Pure virtual callback function, this function should be overridden by the subclass. + * + * This function would be called when the related SIOClient object disconnect or receive disconnect signal. + * + * @param client the connected SIOClient object. + */ + virtual void onClose(SIOClient *client) = 0; + /** + * Pure virtual callback function, this function should be overridden by the subclass. + * + * This function would be called when the related SIOClient object receive error signal or didn't connect the endpoint but do some network operation, eg.,send and emit,etc. + * + * @param client the connected SIOClient object. + * @param data the error message + */ + virtual void onError(SIOClient *client, const ccstd::string &data) = 0; + /** + * Fire event to script when the related SIOClient object receive the fire event signal. + * + * @param client the connected SIOClient object. + * @param eventName the event's name. + * @param data the event's data information. + */ + virtual void fireEventToScript(SIOClient * /*client*/, const ccstd::string &eventName, const ccstd::string &data) { CC_LOG_DEBUG("SIODelegate event '%s' fired with data: %s", eventName.c_str(), data.c_str()); }; + }; + + /** + * Static client creation method, similar to socketio.connect(uri) in JS. + * @param uri the URI of the socket.io server. + * @param delegate the delegate which want to receive events from the socket.io client. + * @return SIOClient* an initialized SIOClient if connected successfully, otherwise nullptr. + */ + static SIOClient *connect(const ccstd::string &uri, SIODelegate &delegate); + + /** + * Static client creation method, similar to socketio.connect(uri) in JS. + * @param uri the URI of the socket.io server. + * @param delegate the delegate which want to receive events from the socket.io client. + * @param caFilePath The ca file path for wss connection + * @return SIOClient* an initialized SIOClient if connected successfully, otherwise nullptr. + */ + static SIOClient *connect(const ccstd::string &uri, SIODelegate &delegate, const ccstd::string &caFilePath); + + /** + * Static client creation method, similar to socketio.connect(uri) in JS. + * @param delegate the delegate which want to receive events from the socket.io client. + * @param uri the URI of the socket.io server. + * @return SIOClient* an initialized SIOClient if connected successfully, otherwise nullptr. + */ + CC_DEPRECATED_ATTRIBUTE static SIOClient *connect(SIODelegate &delegate, const ccstd::string &uri); + +private: + SocketIO(); + virtual ~SocketIO(); + + static SocketIO *inst; + + cc::RefMap _sockets; + + SIOClientImpl *getSocket(const ccstd::string &uri); + void addSocket(const ccstd::string &uri, SIOClientImpl *socket); + void removeSocket(const ccstd::string &uri); + + friend class SIOClientImpl; + + CC_DISALLOW_COPY_MOVE_ASSIGN(SocketIO) +}; + +//c++11 style callbacks entities will be created using CC_CALLBACK (which uses std::bind) +using SIOEvent = std::function; +//c++11 map to callbacks +using EventRegistry = ccstd::unordered_map; + +/** + * A single connection to a socket.io endpoint. + * + * @lua NA + */ +class CC_DLL SIOClient +: public cc::RefCounted { +private: + friend class SocketIO; // Only SocketIO class could contruct a SIOClient instance. + + ccstd::string _path, _tag; + bool _connected; + SIOClientImpl *_socket; + + SocketIO::SIODelegate *_delegate; + + EventRegistry _eventRegistry; + uint32_t _instanceId; + + void fireEvent(const ccstd::string &eventName, const ccstd::string &data); + + void onOpen(); + void onConnect(); + void socketClosed(); + + friend class SIOClientImpl; + + /** + * Constructor of SIOClient class. + * + * @param host the string that represent the host address. + * @param port the int value represent the port number. + * @param path the string that represent endpoint. + * @param impl the SIOClientImpl object. + * @param delegate the SIODelegate object. + */ + SIOClient(ccstd::string path, SIOClientImpl *impl, SocketIO::SIODelegate &delegate); + /** + * Destructor of SIOClient class. + */ + ~SIOClient() override; + +public: + /** + * Get the delegate for the client + * @return the delegate object for the client + */ + SocketIO::SIODelegate *getDelegate() { return _delegate; }; + + /** + * Disconnect from the endpoint, onClose will be called for the delegate when complete + */ + void disconnect(); + /** + * Send a message to the socket.io server. + * + * @param s message. + */ + void send(const ccstd::string &s); + /** + * Emit the eventname and the args to the endpoint that _path point to. + * @param eventname + * @param args + */ + void emit(const ccstd::string &eventname, const ccstd::string &args); + /** + * Used to register a socket.io event callback. + * Event argument should be passed using CC_CALLBACK2(&Base::function, this). + * @param eventName the name of event. + * @param e the callback function. + */ + void on(const ccstd::string &eventName, SIOEvent e); + + /** + * Set tag of SIOClient. + * The tag is used to distinguish the various SIOClient objects. + * @param tag string object. + */ + void setTag(const char *tag); + + /** + * Get tag of SIOClient. + * @return const char* the pointer point to the _tag. + */ + const char *getTag() { + return _tag.c_str(); + } + + /** + * Gets instance id + */ + uint32_t getInstanceId() const; +}; + +} // namespace network + +} // namespace cc + +// end group +/// @} diff --git a/cocos/network/Uri.cpp b/cocos/network/Uri.cpp new file mode 100644 index 0000000..efb28f7 --- /dev/null +++ b/cocos/network/Uri.cpp @@ -0,0 +1,340 @@ +/* + * Copyright 2017 Facebook, Inc. + * Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Uri class is based on the original file here https://github.com/facebook/folly/blob/master/folly/Uri.cpp + */ + +#include "network/Uri.h" +#include "base/Log.h" // For CC_LOG_ERROR macro + +#include +#include + +#include +#include + +#undef LIKELY +#undef UNLIKELY + +#if defined(__GNUC__) && __GNUC__ >= 4 + #define LIKELY(x) (__builtin_expect((x), 1)) + #define UNLIKELY(x) (__builtin_expect((x), 0)) +#else + #define LIKELY(x) (x) + #define UNLIKELY(x) (x) +#endif + +namespace { + +template +ccstd::string toString(T arg) { + std::stringstream ss; + ss << arg; + return ss.str(); +} + +ccstd::string submatch(const std::smatch &m, int idx) { + auto &sub = m[idx]; + return ccstd::string(sub.first, sub.second); +} + +template +void toLower(String &s) { + for (auto &c : s) { + c = char(tolower(c)); + } +} + +} // namespace + +namespace cc { + +namespace network { + +Uri::Uri() +: _isValid(false), + _isSecure(false), + _hasAuthority(false), + _port(0) { +} + +Uri::Uri(const Uri &o) { + *this = o; +} + +Uri::Uri(Uri &&o) { + *this = std::move(o); +} + +Uri &Uri::operator=(const Uri &o) { + if (this != &o) { + _isValid = o._isValid; + _isSecure = o._isSecure; + _scheme = o._scheme; + _username = o._username; + _password = o._password; + _host = o._host; + _hostName = o._hostName; + _hasAuthority = o._hasAuthority; + _port = o._port; + _authority = o._authority; + _pathEtc = o._pathEtc; + _path = o._path; + _query = o._query; + _fragment = o._fragment; + _queryParams = o._queryParams; + } + + return *this; +} + +Uri &Uri::operator=(Uri &&o) { + if (this != &o) { + _isValid = o._isValid; + o._isValid = false; + _isSecure = o._isSecure; + o._isSecure = false; + _scheme = std::move(o._scheme); + _username = std::move(o._username); + _password = std::move(o._password); + _host = std::move(o._host); + _hostName = std::move(o._hostName); + _hasAuthority = o._hasAuthority; + o._hasAuthority = false; + _port = o._port; + o._port = 0; + _authority = std::move(o._authority); + _pathEtc = std::move(o._pathEtc); + _path = std::move(o._path); + _query = std::move(o._query); + _fragment = std::move(o._fragment); + _queryParams = std::move(o._queryParams); + } + return *this; +} + +bool Uri::operator==(const Uri &o) const { + return (_isValid == o._isValid && _isSecure == o._isSecure && _scheme == o._scheme && _username == o._username && _password == o._password && _host == o._host && _hostName == o._hostName && _hasAuthority == o._hasAuthority && _port == o._port && _authority == o._authority && _pathEtc == o._pathEtc && _path == o._path && _query == o._query && _fragment == o._fragment && _queryParams == o._queryParams); +} + +Uri Uri::parse(const ccstd::string &str) { + Uri uri; + + if (!uri.doParse(str)) { + uri.clear(); + } + + return uri; +} + +bool Uri::doParse(const ccstd::string &str) { + static const std::regex uriRegex( + "([a-zA-Z][a-zA-Z0-9+.-]*):" // scheme: + "([^?#]*)" // authority and path + "(?:\\?([^#]*))?" // ?query + "(?:#(.*))?"); // #fragment + static const std::regex authorityAndPathRegex("//([^/]*)(/.*)?"); + + if (str.empty()) { + CC_LOG_ERROR("%s", "Empty URI is invalid!"); + return false; + } + + bool hasScheme = true; + ; + ccstd::string copied(str); + if (copied.find("://") == ccstd::string::npos) { + hasScheme = false; + copied.insert(0, "abc://"); // Just make regex happy. + } + + std::smatch match; + if (UNLIKELY(!std::regex_match(copied.cbegin(), copied.cend(), match, uriRegex))) { + CC_LOG_ERROR("Invalid URI: %s", str.c_str()); + return false; + } + + if (hasScheme) { + _scheme = submatch(match, 1); + toLower(_scheme); + if (_scheme == "https" || _scheme == "wss") { + _isSecure = true; + } + } + + ccstd::string authorityAndPath(match[2].first, match[2].second); + std::smatch authorityAndPathMatch; + if (!std::regex_match(authorityAndPath.cbegin(), + authorityAndPath.cend(), + authorityAndPathMatch, + authorityAndPathRegex)) { + // Does not start with //, doesn't have authority + _hasAuthority = false; + _path = authorityAndPath; + } else { + static const std::regex authorityRegex( + "(?:([^@:]*)(?::([^@]*))?@)?" // username, password + "(\\[[^\\]]*\\]|[^\\[:]*)" // host (IP-literal (e.g. '['+IPv6+']', + // dotted-IPv4, or named host) + "(?::(\\d*))?"); // port + + auto authority = authorityAndPathMatch[1]; + std::smatch authorityMatch; + if (!std::regex_match(authority.first, + authority.second, + authorityMatch, + authorityRegex)) { + ccstd::string invalidAuthority(authority.first, authority.second); + CC_LOG_ERROR("Invalid URI authority: %s", invalidAuthority.c_str()); + return false; + } + + ccstd::string port(authorityMatch[4].first, authorityMatch[4].second); + if (!port.empty()) { + _port = static_cast(atoi(port.c_str())); + } + + _hasAuthority = true; + _username = submatch(authorityMatch, 1); + _password = submatch(authorityMatch, 2); + _host = submatch(authorityMatch, 3); + _path = submatch(authorityAndPathMatch, 2); + } + + _query = submatch(match, 3); + _fragment = submatch(match, 4); + _isValid = true; + + // Assign authority part + // + // Port is 5 characters max and we have up to 3 delimiters. + _authority.reserve(getHost().size() + getUserName().size() + getPassword().size() + 8); + + if (!getUserName().empty() || !getPassword().empty()) { + _authority.append(getUserName()); + + if (!getPassword().empty()) { + _authority.push_back(':'); + _authority.append(getPassword()); + } + + _authority.push_back('@'); + } + + _authority.append(getHost()); + + if (getPort() != 0) { + _authority.push_back(':'); + _authority.append(::toString(getPort())); + } + + // Assign path etc part + _pathEtc = _path; + if (!_query.empty()) { + _pathEtc += '?'; + _pathEtc += _query; + } + + if (!_fragment.empty()) { + _pathEtc += '#'; + _pathEtc += _fragment; + } + + // Assign host name + if (!_host.empty() && _host[0] == '[') { + // If it starts with '[', then it should end with ']', this is ensured by + // regex + _hostName = _host.substr(1, _host.size() - 2); + } else { + _hostName = _host; + } + + return true; +} + +void Uri::clear() { + _isValid = false; + _isSecure = false; + _scheme.clear(); + _username.clear(); + _password.clear(); + _host.clear(); + _hostName.clear(); + _hasAuthority = false; + _port = 0; + _authority.clear(); + _pathEtc.clear(); + _path.clear(); + _query.clear(); + _fragment.clear(); + _queryParams.clear(); +} + +const ccstd::vector> &Uri::getQueryParams() { + if (!_query.empty() && _queryParams.empty()) { + // Parse query string + static const std::regex queryParamRegex( + "(^|&)" /*start of query or start of parameter "&"*/ + "([^=&]*)=?" /*parameter name and "=" if value is expected*/ + "([^=&]*)" /*parameter value*/ + "(?=(&|$))" /*forward reference, next should be end of query or + start of next parameter*/ + ); + std::cregex_iterator paramBeginItr( + _query.data(), _query.data() + _query.size(), queryParamRegex); + std::cregex_iterator paramEndItr; + for (auto itr = paramBeginItr; itr != paramEndItr; itr++) { + if (itr->length(2) == 0) { + // key is empty, ignore it + continue; + } + _queryParams.emplace_back( + ccstd::string((*itr)[2].first, (*itr)[2].second), // parameter name + ccstd::string((*itr)[3].first, (*itr)[3].second) // parameter value + ); + } + } + return _queryParams; +} + +ccstd::string Uri::toString() const { + std::stringstream ss; + if (_hasAuthority) { + ss << _scheme << "://"; + if (!_password.empty()) { + ss << _username << ":" << _password << "@"; + } else if (!_username.empty()) { + ss << _username << "@"; + } + ss << _host; + if (_port != 0) { + ss << ":" << _port; + } + } else { + ss << _scheme << ":"; + } + ss << _path; + if (!_query.empty()) { + ss << "?" << _query; + } + if (!_fragment.empty()) { + ss << "#" << _fragment; + } + return ss.str(); +} + +} // namespace network + +} // namespace cc diff --git a/cocos/network/Uri.h b/cocos/network/Uri.h new file mode 100644 index 0000000..cc07747 --- /dev/null +++ b/cocos/network/Uri.h @@ -0,0 +1,182 @@ +/* + * Copyright 2017 Facebook, Inc. + * Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Uri class is based on the original file here https://github.com/facebook/folly/blob/master/folly/Uri.cpp + */ + +#pragma once + +#include +#include "base/Macros.h" +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +/** + * @addtogroup network + * @{ + */ + +namespace cc { + +namespace network { + +/** + * Class representing a URI. + * + * Consider http://www.facebook.com/foo/bar?key=foo#anchor + * + * The URI is broken down into its parts: scheme ("http"), authority + * (ie. host and port, in most cases: "www.facebook.com"), path + * ("/foo/bar"), query ("key=foo") and fragment ("anchor"). The scheme is + * lower-cased. + * + * If this Uri represents a URL, note that, to prevent ambiguity, the component + * parts are NOT percent-decoded; you should do this yourself with + * uriUnescape() (for the authority and path) and uriUnescape(..., + * UriEscapeMode::QUERY) (for the query, but probably only after splitting at + * '&' to identify the individual parameters). + */ +class CC_DLL Uri { +public: + /** + * Parse a Uri from a string. Throws std::invalid_argument on parse error. + */ + static Uri parse(const ccstd::string &str); + + /** Default constructor */ + Uri(); + + /** Copy constructor */ + Uri(const Uri &o); + + /** Move constructor */ + Uri(Uri &&o); + + /** Copy assignment */ + Uri &operator=(const Uri &o); + + /** Move assignment */ + Uri &operator=(Uri &&o); + + /** Checks whether two Uri instances contain the same values */ + bool operator==(const Uri &o) const; + + /** Checks wether it's a valid URI */ + bool isValid() const { return _isValid; } + + /** Checks whether it's a SSL connection */ + bool isSecure() const { return _isSecure; } + + /** Gets the scheme name for this URI. */ + const ccstd::string &getScheme() const { return _scheme; } + + /** Gets the user name with the specified URI. */ + const ccstd::string &getUserName() const { return _username; } + + /** Gets the password with the specified URI. */ + const ccstd::string &getPassword() const { return _password; } + /** + * Get host part of URI. If host is an IPv6 address, square brackets will be + * returned, for example: "[::1]". + */ + const ccstd::string &getHost() const { return _host; } + /** + * Get host part of URI. If host is an IPv6 address, square brackets will not + * be returned, for exmaple "::1"; otherwise it returns the same thing as + * getHost(). + * + * getHostName() is what one needs to call if passing the host to any other tool + * or API that connects to that host/port; e.g. getaddrinfo() only understands + * IPv6 host without square brackets + */ + const ccstd::string &getHostName() const { return _hostName; } + + /** Gets the port number of the URI. */ + uint16_t getPort() const { return _port; } + + /** Gets the path part of the URI. */ + const ccstd::string &getPath() const { return _path; } + + /// Gets the path, query and fragment parts of the URI. + const ccstd::string &getPathEtc() const { return _pathEtc; } + + /** Gets the query part of the URI. */ + const ccstd::string &getQuery() const { return _query; } + + /** Gets the fragment part of the URI */ + const ccstd::string &getFragment() const { return _fragment; } + + /** Gets the authority part (userName, password, host and port) of the URI. + * @note If the port number is a well-known port + * number for the given scheme (e.g., 80 for http), it + * is not included in the authority. + */ + const ccstd::string &getAuthority() const { return _authority; } + + /** Gets a string representation of the URI. */ + ccstd::string toString() const; + + /** + * Get query parameters as key-value pairs. + * e.g. for URI containing query string: key1=foo&key2=&key3&=bar&=bar= + * In returned list, there are 3 entries: + * "key1" => "foo" + * "key2" => "" + * "key3" => "" + * Parts "=bar" and "=bar=" are ignored, as they are not valid query + * parameters. "=bar" is missing parameter name, while "=bar=" has more than + * one equal signs, we don't know which one is the delimiter for key and + * value. + * + * Note, this method is not thread safe, it might update internal state, but + * only the first call to this method update the state. After the first call + * is finished, subsequent calls to this method are thread safe. + * + * @return query parameter key-value pairs in a vector, each element is a + * pair of which the first element is parameter name and the second + * one is parameter value + */ + const ccstd::vector> &getQueryParams(); + + /** Clears all parts of the URI. */ + void clear(); + +private: + bool doParse(const ccstd::string &str); + + bool _isValid; + bool _isSecure; + ccstd::string _scheme; + ccstd::string _username; + ccstd::string _password; + ccstd::string _host; + ccstd::string _hostName; + bool _hasAuthority; + uint16_t _port; + ccstd::string _authority; + ccstd::string _pathEtc; + ccstd::string _path; + ccstd::string _query; + ccstd::string _fragment; + ccstd::vector> _queryParams; +}; + +} // namespace network + +} // namespace cc + +// end group +/// @} diff --git a/cocos/network/WebSocket-apple.mm b/cocos/network/WebSocket-apple.mm new file mode 100644 index 0000000..4ac527f --- /dev/null +++ b/cocos/network/WebSocket-apple.mm @@ -0,0 +1,314 @@ +/**************************************************************************** + Copyright (c) 2017-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "base/Data.h" +#include "network/WebSocket.h" + +#import "SocketRocket/SocketRocket.h" + +#if !__has_feature(objc_arc) + #error WebSocket must be compiled with ARC enabled +#endif + +namespace { +ccstd::vector websocketInstances; +} + +@interface WebSocketImpl : NSObject { +} +@end + +// + +@implementation WebSocketImpl { + SRWebSocket *_ws; + cc::network::WebSocket *_ccws; + cc::network::WebSocket::Delegate *_delegate; + + ccstd::string _url; + ccstd::string _selectedProtocol; + bool _isDestroyed; +} + +- (id)initWithURL:(const ccstd::string &)url protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates ws:(cc::network::WebSocket *)ccws delegate:(const cc::network::WebSocket::Delegate &)delegate { + if (self = [super init]) { + _ccws = ccws; + _delegate = const_cast(&delegate); + _url = url; + NSURL *nsUrl = [[NSURL alloc] initWithString:[[NSString alloc] initWithUTF8String:_url.c_str()]]; + _ws = [[SRWebSocket alloc] initWithURL:nsUrl protocols:protocols allowsUntrustedSSLCertificates:allowsUntrustedSSLCertificates]; + _ws.delegate = self; + [_ws open]; + _isDestroyed = false; + } + return self; +} + +- (void)dealloc { + // NSLog(@"WebSocketImpl-apple dealloc: %p, SRWebSocket ref: %ld", self, CFGetRetainCount((__bridge CFTypeRef)_ws)); +} + +- (void)sendString:(NSString *)message { + [_ws sendString:message error:nil]; +} + +- (void)sendData:(NSData *)data { + [_ws sendData:data error:nil]; +} + +- (void)close { + _isDestroyed = true; + _ccws->addRef(); + _delegate->onClose(_ccws, 1000, "close_normal", true); + [_ws close]; + _ccws->release(); +} + +- (void)closeAsync { + [_ws close]; +} + +- (cc::network::WebSocket::State)getReadyState { + cc::network::WebSocket::State ret; + SRReadyState state = _ws.readyState; + switch (state) { + case SR_OPEN: + ret = cc::network::WebSocket::State::OPEN; + break; + case SR_CONNECTING: + ret = cc::network::WebSocket::State::CONNECTING; + break; + case SR_CLOSING: + ret = cc::network::WebSocket::State::CLOSING; + break; + default: + ret = cc::network::WebSocket::State::CLOSED; + break; + } + return ret; +} + +- (const ccstd::string &)getUrl { + return _url; +} + +- (const ccstd::string &)getProtocol { + return _selectedProtocol; +} + +- (cc::network::WebSocket::Delegate *)getDelegate { + return (cc::network::WebSocket::Delegate *)_delegate; +} + +// Delegate methods + +- (void)webSocketDidOpen:(SRWebSocket *)webSocket; +{ + if (!_isDestroyed) { + // NSLog(@"Websocket Connected"); + if (webSocket.protocol != nil) + _selectedProtocol = [webSocket.protocol UTF8String]; + _delegate->onOpen(_ccws); + } else { + NSLog(@"WebSocketImpl webSocketDidOpen was destroyed!"); + } +} + +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; +{ + if (!_isDestroyed) { + NSLog(@":( Websocket Failed With Error %@", error); + _delegate->onError(_ccws, cc::network::WebSocket::ErrorCode::UNKNOWN); + [self webSocket:webSocket didCloseWithCode:0 reason:@"onerror" wasClean:YES]; + } else { + NSLog(@"WebSocketImpl didFailWithError was destroyed!"); + } +} + +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(nonnull NSString *)string { + if (!_isDestroyed) { + cc::network::WebSocket::Data data; + data.bytes = const_cast([string cStringUsingEncoding:NSUTF8StringEncoding]); + data.len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + data.isBinary = false; + data.issued = 0; + data.ext = nullptr; + + _delegate->onMessage(_ccws, data); + } else { + NSLog(@"WebSocketImpl didReceiveMessageWithString was destroyed!"); + } +} + +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithData:(NSData *)nsData { + if (!_isDestroyed) { + cc::network::WebSocket::Data data; + data.bytes = (char *)nsData.bytes; + data.len = nsData.length; + data.isBinary = true; + data.issued = 0; + data.ext = nullptr; + _delegate->onMessage(_ccws, data); + } else { + NSLog(@"WebSocketImpl didReceiveMessageWithData was destroyed!"); + } +} + +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { + if (!_isDestroyed) { + auto codeArg = static_cast(code); + ccstd::string reasonArg = reason == nil ? "no_resaon" : ccstd::string([reason UTF8String]); + bool wasCleanArg = static_cast(wasClean); + _delegate->onClose(_ccws, codeArg, reasonArg, wasCleanArg); + } + else + NSLog(@"WebSocketImpl didCloseWithCode was destroyed!"); +} + +@end + +namespace cc { + +namespace network { + +void WebSocket::closeAllConnections() { + if (websocketInstances.empty()) { + return; + } + + for (auto iter = websocketInstances.cbegin(); iter != websocketInstances.cend(); ++iter) { + (*iter)->close(); + ++iter; + } + + websocketInstances.clear(); +} + +WebSocket::WebSocket() { + websocketInstances.push_back(this); +} + +WebSocket::~WebSocket() { + // NSLog(@"In the destructor of WebSocket-apple (%p).", this); + if (websocketInstances.empty()) { + return; + } + + auto iter = std::find(websocketInstances.begin(), websocketInstances.end(), this); + if (iter != websocketInstances.end()) { + websocketInstances.erase(iter); + } else { + NSLog(@"ERROR: WebSocket instance wasn't added to the container which saves websocket instances!"); + } +} + +bool WebSocket::init(const Delegate &delegate, + const ccstd::string &url, + const ccstd::vector *protocols /* = nullptr*/, + const ccstd::string &caFilePath /* = ""*/) { + if (url.empty()) + return false; + + NSMutableArray *nsProtocols = [[NSMutableArray alloc] init]; + if (protocols != nullptr) { + for (const auto &protocol : *protocols) { + [nsProtocols addObject:[[NSString alloc] initWithUTF8String:protocol.c_str()]]; + } + } + _impl = [[WebSocketImpl alloc] initWithURL:url protocols:nsProtocols allowsUntrustedSSLCertificates:NO ws:this delegate:delegate]; + + return _impl != nil; +} + +void WebSocket::send(const ccstd::string &message) { + if ([_impl getReadyState] == State::OPEN) { + NSString *str = [[NSString alloc] initWithBytes:message.data() length:message.length() encoding:NSUTF8StringEncoding]; + [_impl sendString:str]; + } else { + NSLog(@"Couldn't send message since websocket wasn't opened!"); + } +} + +void WebSocket::send(const unsigned char *binaryMsg, unsigned int len) { + if ([_impl getReadyState] == State::OPEN) { + NSData *data = [[NSData alloc] initWithBytes:binaryMsg length:(NSUInteger)len]; + [_impl sendData:data]; + } else { + NSLog(@"Couldn't send message since websocket wasn't opened!"); + } +} + +void WebSocket::close() { + if ([_impl getReadyState] == State::CLOSING || [_impl getReadyState] == State::CLOSED) { + NSLog(@"WebSocket (%p) was closed, no need to close it again!", this); + return; + } + + [_impl close]; +} + +void WebSocket::closeAsync() { + if ([_impl getReadyState] == State::CLOSING || [_impl getReadyState] == State::CLOSED) { + NSLog(@"WebSocket (%p) was closed, no need to close it again!", this); + return; + } + + [_impl closeAsync]; +} + +void WebSocket::closeAsync(int code, const ccstd::string &reason) { + //lws_close_reason() replacement required + closeAsync(); +} + +ccstd::string WebSocket::getExtensions() const { + //TODO websocket extensions + return ""; +} + +size_t WebSocket::getBufferedAmount() const { + //TODO pending send bytes + return 0; +} + +WebSocket::State WebSocket::getReadyState() const { + return [_impl getReadyState]; +} + +const ccstd::string &WebSocket::getUrl() const { + return [_impl getUrl]; +} + +const ccstd::string &WebSocket::getProtocol() const { + return [_impl getProtocol]; +} + +WebSocket::Delegate *WebSocket::getDelegate() const { + return [_impl getDelegate]; +} + +} // namespace network + +} // namespace cc diff --git a/cocos/network/WebSocket-libwebsockets.cpp b/cocos/network/WebSocket-libwebsockets.cpp new file mode 100644 index 0000000..092dfaf --- /dev/null +++ b/cocos/network/WebSocket-libwebsockets.cpp @@ -0,0 +1,1406 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +/*************************************************************************** +* "[WebSocket module] is based in part on the work of the libwebsockets project +* (http://libwebsockets.org)" +*****************************************************************************/ + +// clang-format off +#include "base/Macros.h" +#include "base/memory/Memory.h" +#include "uv.h" +// clang-format on + +#if __OHOS__ || __LINUX__ || __QNX__ + #include "libwebsockets.h" +#else + #include "websockets/libwebsockets.h" +#endif + +#include +#include +#include +#include +#include +#include // for std::shared_ptr +#include +#include +#include "application/ApplicationManager.h" +#include "base/Scheduler.h" +#include "base/std/container/list.h" +#include "base/std/container/queue.h" +#include "base/std/container/string.h" +#include "network/Uri.h" +#include "network/WebSocket.h" + +#include "platform/FileUtils.h" +#include "platform/StdC.h" + +#define NS_NETWORK_BEGIN \ + namespace cc { \ + namespace network { +#define NS_NETWORK_END \ + } \ + } + +#define WS_RX_BUFFER_SIZE (65536) +#define WS_RESERVE_RECEIVE_BUFFER_SIZE (4096) + +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #define WS_ENABLE_LIBUV 1 +#else + #define WS_ENABLE_LIBUV 0 +#endif + +#ifdef LOG_TAG + #undef LOG_TAG +#endif +#define LOG_TAG "WebSocket.cpp" + +struct lws; +struct LwsProtocols; +struct lws_vhost; + +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) +// log, CC_LOG_DEBUG aren't threadsafe, since we uses sub threads for parsing pcm data, threadsafe log output +// is needed. Define the following macros (ALOGV, ALOGD, ALOGI, ALOGW, ALOGE) for threadsafe log output. + +//IDEA: Move _winLog, winLog to a separated file +static void _winLog(const char *format, va_list args) { + static const int MAX_LOG_LENGTH = 16 * 1024; + int bufferSize = MAX_LOG_LENGTH; + char *buf = nullptr; + + do { + buf = ccnew char[bufferSize]; + if (buf == nullptr) + return; // not enough memory + + int ret = vsnprintf(buf, bufferSize - 3, format, args); + if (ret < 0) { + bufferSize *= 2; + + delete[] buf; + } else + break; + + } while (true); + + strcat(buf, "\n"); + + int pos = 0; + int len = strlen(buf); + char tempBuf[MAX_LOG_LENGTH + 1] = {0}; + WCHAR wszBuf[MAX_LOG_LENGTH + 1] = {0}; + + do { + std::copy(buf + pos, buf + pos + MAX_LOG_LENGTH, tempBuf); + + tempBuf[MAX_LOG_LENGTH] = 0; + + MultiByteToWideChar(CP_UTF8, 0, tempBuf, -1, wszBuf, sizeof(wszBuf)); + OutputDebugStringW(wszBuf); + + pos += MAX_LOG_LENGTH; + + } while (pos < len); + + delete[] buf; +} + +static void wsLog(const char *format, ...) { + va_list args; + va_start(args, format); + _winLog(format, args); + va_end(args); +} + +#else + #define wsLog printf //NOLINT +#endif + +#define DO_QUOTEME(x) #x +#define QUOTEME(x) DO_QUOTEME(x) + +// Since CC_LOG_DEBUG isn't thread safe, we uses LOGD for multi-thread logging. +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #if CC_DEBUG > 0 + #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + #else + #define LOGD(...) + #endif + + #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#elif defined(__OHOS__) + #include "cocos/base/Log.h" + #define LOGD(...) CC_LOG_DEBUG(__VA_ARGS__) + #define LOGE(...) CC_LOG_ERROR(__VA_ARGS__) +#else + #if CC_DEBUG > 0 + #define LOGD(fmt, ...) wsLog("D/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__) + #else + #define LOGD(fmt, ...) + #endif + + #define LOGE(fmt, ...) wsLog("E/" LOG_TAG " (" QUOTEME(__LINE__) "): " fmt "", ##__VA_ARGS__) +#endif + +static void printWebSocketLog(int level, const char *line) { +#if CC_DEBUG > 0 + static const char *const LOG_LEVEL_NAMES[] = { + "ERR", + "WARN", + "NOTICE", + "INFO", + "DEBUG", + "PARSER", + "HEADER", + "EXTENSION", + "CLIENT", + "LATENCY", + }; + + char buf[30] = {0}; + int n; + + for (n = 0; n < LLL_COUNT; n++) { + if (level != (1 << n)) { + continue; + } + sprintf(buf, "%s: ", LOG_LEVEL_NAMES[n]); + break; + } + + LOGD("%s%s\n", buf, line); + +#endif // #if CC_DEBUG > 0 +} + +class WebSocketImpl { +public: + static void closeAllConnections(); + explicit WebSocketImpl(cc::network::WebSocket *ws); + ~WebSocketImpl(); + + bool init(const cc::network::WebSocket::Delegate &delegate, + const ccstd::string &url, + const ccstd::vector *protocols = nullptr, + const ccstd::string &caFilePath = ""); + + void send(const ccstd::string &message); + void send(const unsigned char *binaryMsg, unsigned int len); + void close(); + void closeAsync(); + void closeAsync(int code, const ccstd::string &reason); + cc::network::WebSocket::State getReadyState() const; + const ccstd::string &getUrl() const; + const ccstd::string &getProtocol() const; + cc::network::WebSocket::Delegate *getDelegate() const; + + size_t getBufferedAmount() const; + ccstd::string getExtensions() const; + +private: + // The following callback functions are invoked in websocket thread + void onClientOpenConnectionRequest(); + int onSocketCallback(struct lws *wsi, enum lws_callback_reasons reason, void *in, ssize_t len); + + int onClientWritable(); + int onClientReceivedData(void *in, ssize_t len); + int onConnectionOpened(); + int onConnectionError(); + int onConnectionClosed(uint16_t code, const ccstd::string &reason); + + struct lws_vhost *createVhost(struct lws_protocols *protocols, int *sslConnection); + + cc::network::WebSocket *_ws; + cc::network::WebSocket::State _readyState; + std::mutex _readyStateMutex; + ccstd::string _url; + ccstd::vector _receivedData; + + struct lws *_wsInstance; + struct lws_protocols *_lwsProtocols; + ccstd::string _clientSupportedProtocols; + ccstd::string _selectedProtocol; + + std::shared_ptr> _isDestroyed; + cc::network::WebSocket::Delegate *_delegate; + + std::mutex _closeMutex; + std::condition_variable _closeCondition; + + ccstd::vector _enabledExtensions; + + enum class CloseState { + NONE, + SYNC_CLOSING, + SYNC_CLOSED, + ASYNC_CLOSING + }; + CloseState _closeState; + + ccstd::string _caFilePath; + + friend class WsThreadHelper; + friend class WebSocketCallbackWrapper; +}; + +enum WsMsg { + WS_MSG_TO_SUBTRHEAD_SENDING_STRING = 0, + WS_MSG_TO_SUBTRHEAD_SENDING_BINARY, + WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION +}; + +class WsThreadHelper; + +static ccstd::vector *websocketInstances{nullptr}; +static std::recursive_mutex instanceMutex; +static struct lws_context *wsContext{nullptr}; +static WsThreadHelper *wsHelper{nullptr}; +static std::atomic_bool wsPolling{false}; + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS || CC_PLATFORM == CC_PLATFORM_OPENHARMONY) +static ccstd::string getFileNameForPath(const ccstd::string &filePath) { + ccstd::string fileName = filePath; + const size_t lastSlashIdx = fileName.find_last_of("\\/"); + if (ccstd::string::npos != lastSlashIdx) { + fileName.erase(0, lastSlashIdx + 1); + } + return fileName; +} +#endif + +static struct lws_protocols defaultProtocols[2]; + +static lws_context_creation_info convertToContextCreationInfo(const struct lws_protocols *protocols, bool peerServerCert) { + lws_context_creation_info info; + memset(&info, 0, sizeof(info)); + /* + * create the websocket context. This tracks open connections and + * knows how to route any traffic and which protocol version to use, + * and if each connection is client or server side. + * + * For this client-only demo, we tell it to not listen on any port. + */ + + info.port = CONTEXT_PORT_NO_LISTEN; + info.protocols = protocols; + + // IDEA: Disable 'permessage-deflate' extension temporarily because of issues: + // https://github.com/cocos2d/cocos2d-x/issues/16045, https://github.com/cocos2d/cocos2d-x/issues/15767 + // libwebsockets issue: https://github.com/warmcat/libwebsockets/issues/593 + // Currently, we couldn't find out the exact reason. + // libwebsockets official said it's probably an issue of user code + // since 'libwebsockets' passed AutoBahn stressed Test. + + // info.extensions = exts; + + info.gid = -1; + info.uid = -1; + if (peerServerCert) { + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + } else { + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED; + } +#if WS_ENABLE_LIBUV + info.options |= LWS_SERVER_OPTION_LIBUV; +#endif + info.user = nullptr; + + return info; +} + +class WsMessage { +public: + WsMessage() : id(++idCount) {} + unsigned int id; + unsigned int what{0}; // message type + cc::network::WebSocket::Data *data{nullptr}; + void *user{nullptr}; + +private: + static unsigned int idCount; +}; + +unsigned int WsMessage::idCount = 0; + +/** + * @brief Websocket thread helper, it's used for sending message between UI thread and websocket thread. + */ +class WsThreadHelper { +public: + WsThreadHelper(); + ~WsThreadHelper(); + + // Creates a new thread + bool createWebSocketThread(); + // Quits websocket thread. + void quitWebSocketThread(); + + // Sends message to Cocos thread. It's needed to be invoked in Websocket thread. + static void sendMessageToCocosThread(const std::function &cb); + + // Sends message to Websocket thread. It's needs to be invoked in Cocos thread. + void sendMessageToWebSocketThread(WsMessage *msg); + + size_t countBufferedBytes(const WebSocketImpl *ws); + + // Waits the sub-thread (websocket thread) to exit, + void joinWebSocketThread() const; + + static void onSubThreadStarted(); + static void onSubThreadLoop(); + static void onSubThreadEnded(); + +protected: + void wsThreadEntryFunc() const; + +public: + ccstd::list *_subThreadWsMessageQueue; + std::mutex _subThreadWsMessageQueueMutex; + std::thread *_subThreadInstance{nullptr}; + +private: + bool _needQuit{false}; +}; + +// Wrapper for converting websocket callback from static function to member function of WebSocket class. +class WebSocketCallbackWrapper { +public: + static int onSocketCallback(struct lws *wsi, enum lws_callback_reasons reason, void * /*user*/, void *in, size_t len) { + // Gets the user data from context. We know that it's a 'WebSocket' instance. + if (wsi == nullptr) { + return 0; + } + int ret = 0; + { + std::lock_guard lk(instanceMutex); + auto *ws = static_cast(lws_wsi_user(wsi)); + if (ws != nullptr && websocketInstances != nullptr) { + if (std::find(websocketInstances->begin(), websocketInstances->end(), ws) != websocketInstances->end()) { + ret = ws->onSocketCallback(wsi, reason, in, len); + } + } else { + // LOGD("ws instance is nullptr.\n"); + } + } + + return ret; + } +}; + +// Implementation of WsThreadHelper +WsThreadHelper::WsThreadHelper() + +{ + _subThreadWsMessageQueue = ccnew ccstd::list(); +} + +WsThreadHelper::~WsThreadHelper() { + joinWebSocketThread(); + CC_SAFE_DELETE(_subThreadInstance); + delete _subThreadWsMessageQueue; +} + +bool WsThreadHelper::createWebSocketThread() { + // Creates websocket thread + _subThreadInstance = ccnew std::thread(&WsThreadHelper::wsThreadEntryFunc, this); + return true; +} + +void WsThreadHelper::quitWebSocketThread() { + _needQuit = true; +} + +void WsThreadHelper::onSubThreadLoop() { + if (wsContext) { + // _readyStateMutex.unlock(); + wsHelper->_subThreadWsMessageQueueMutex.lock(); + bool isEmpty = wsHelper->_subThreadWsMessageQueue->empty(); + + if (!isEmpty) { + auto iter = wsHelper->_subThreadWsMessageQueue->begin(); + for (; iter != wsHelper->_subThreadWsMessageQueue->end();) { + auto *msg = (*iter); + auto *ws = static_cast(msg->user); + // REFINE: ws may be a invalid pointer + if (msg->what == WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION) { + ws->onClientOpenConnectionRequest(); + delete *iter; + iter = wsHelper->_subThreadWsMessageQueue->erase(iter); + } else { + ++iter; + } + } + } + wsHelper->_subThreadWsMessageQueueMutex.unlock(); + // Windows: Cause delay 40ms for event WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION + // Android: Let libuv lws to decide when to stop + wsPolling = true; + lws_service(wsContext, WS_ENABLE_LIBUV ? 40 : 4); + wsPolling = false; + } +} + +void WsThreadHelper::onSubThreadStarted() { + int logLevel = LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO /* | LLL_DEBUG | LLL_PARSER | LLL_HEADER*/ | LLL_EXT | LLL_CLIENT | LLL_LATENCY; + lws_set_log_level(logLevel, printWebSocketLog); + + memset(defaultProtocols, 0, 2 * sizeof(struct lws_protocols)); + + defaultProtocols[0].name = ""; + defaultProtocols[0].callback = WebSocketCallbackWrapper::onSocketCallback; + defaultProtocols[0].rx_buffer_size = WS_RX_BUFFER_SIZE; + defaultProtocols[0].id = std::numeric_limits::max(); + + lws_context_creation_info creationInfo = convertToContextCreationInfo(defaultProtocols, true); + wsContext = lws_create_context(&creationInfo); +#if WS_ENABLE_LIBUV + if (lws_uv_initloop(wsContext, nullptr, 0)) { + LOGE("WsThreadHelper: failed to init libuv"); + } +#endif +} + +void WsThreadHelper::onSubThreadEnded() { + if (wsContext != nullptr) { + lws_context_destroy(wsContext); +#if WS_ENABLE_LIBUV + lws_context_destroy2(wsContext); +#endif + } +} + +void WsThreadHelper::wsThreadEntryFunc() const { + LOGD("WebSocket thread start, helper instance: %p\n", this); + onSubThreadStarted(); + + while (!_needQuit) { + onSubThreadLoop(); + } + + onSubThreadEnded(); + + LOGD("WebSocket thread exit, helper instance: %p\n", this); +} + +void WsThreadHelper::sendMessageToCocosThread(const std::function &cb) { + if (CC_CURRENT_APPLICATION() != nullptr) { + CC_CURRENT_APPLICATION()->getEngine()->getScheduler()->performFunctionInCocosThread(cb); + } +} + +void WsThreadHelper::sendMessageToWebSocketThread(WsMessage *msg) { + std::lock_guard lk(_subThreadWsMessageQueueMutex); + _subThreadWsMessageQueue->push_back(msg); +} + +size_t WsThreadHelper::countBufferedBytes(const WebSocketImpl *ws) { + std::lock_guard lk(_subThreadWsMessageQueueMutex); + size_t total = 0; + for (auto *msg : *_subThreadWsMessageQueue) { + if (msg->user == ws && msg->data && (msg->what == WS_MSG_TO_SUBTRHEAD_SENDING_STRING || msg->what == WS_MSG_TO_SUBTRHEAD_SENDING_BINARY)) { + total += msg->data->getRemain(); + } + } + return total; +} + +void WsThreadHelper::joinWebSocketThread() const { + if (_subThreadInstance->joinable()) { + _subThreadInstance->join(); + } +} + +// Define a WebSocket frame +class WebSocketFrame { +public: + bool init(unsigned char *buf, ssize_t len) { + if (buf == nullptr && len > 0) { + return false; + } + + if (!_data.empty()) { + LOGD("WebSocketFrame was initialized, should not init it again!\n"); + return false; + } + + _data.resize(LWS_PRE + len); + if (len > 0) { + std::copy(buf, buf + len, _data.begin() + LWS_PRE); + } + + _payload = _data.data() + LWS_PRE; + _payloadLength = len; + _frameLength = len; + return true; + } + + void update(ssize_t issued) { + _payloadLength -= issued; + _payload += issued; + } + + unsigned char *getPayload() const { return _payload; } + ssize_t getPayloadLength() const { return _payloadLength; } + ssize_t getFrameLength() const { return _frameLength; } + +private: + unsigned char *_payload{nullptr}; + ssize_t _payloadLength{0}; + + ssize_t _frameLength{0}; + ccstd::vector _data; +}; + +// + +void WebSocketImpl::closeAllConnections() { + if (websocketInstances != nullptr) { + ssize_t count = websocketInstances->size(); + for (ssize_t i = count - 1; i >= 0; i--) { + WebSocketImpl *instance = websocketInstances->at(i); + instance->close(); + } + + std::lock_guard lk(instanceMutex); + websocketInstances->clear(); + delete websocketInstances; + websocketInstances = nullptr; + } +} + +WebSocketImpl::WebSocketImpl(cc::network::WebSocket *ws) +: _ws(ws), + _readyState(cc::network::WebSocket::State::CONNECTING), + _wsInstance(nullptr), + _lwsProtocols(nullptr), + _isDestroyed(std::make_shared>(false)), + _delegate(nullptr), + _closeState(CloseState::NONE) { + // reserve data buffer to avoid allocate memory frequently + _receivedData.reserve(WS_RESERVE_RECEIVE_BUFFER_SIZE); + + { + std::lock_guard lk(instanceMutex); + if (websocketInstances == nullptr) { + websocketInstances = ccnew ccstd::vector(); + } + websocketInstances->push_back(this); + } + + // NOTE: !!! Be careful while merging cocos2d-x-lite back to cocos2d-x. !!! + // 'close' is a synchronous operation which may wait some seconds to make sure connection is closed. + // But JSB doesn't need to listen on EVENT_RESET event to close connection, + // since finalize callback (refer to 'WebSocket_finalize' function in jsb_websocket.cpp) will invoke 'closeAsync'. + // + // std::shared_ptr> isDestroyed = _isDestroyed; + // _resetDirectorListener = cc::Director::getInstance()->getEventDispatcher()->addCustomEventListener(cc::Director::EVENT_RESET, [this, isDestroyed](cc::EventCustom*){ + // if (*isDestroyed) + // return; + // close(); + // }); +} + +WebSocketImpl::~WebSocketImpl() { + LOGD("In the destructor of WebSocket (%p)\n", this); + + std::unique_lock lk(instanceMutex); + + if (websocketInstances != nullptr) { + auto iter = std::find(websocketInstances->begin(), websocketInstances->end(), this); + if (iter != websocketInstances->end()) { + websocketInstances->erase(iter); + } else { + LOGD("ERROR: WebSocket instance (%p) wasn't added to the container which saves websocket instances!\n", this); + } + } + + if (websocketInstances == nullptr || websocketInstances->empty()) { + lk.unlock(); + wsHelper->quitWebSocketThread(); + LOGD("before join ws thread\n"); + wsHelper->joinWebSocketThread(); + LOGD("after join ws thread\n"); + + CC_SAFE_DELETE(wsHelper); + } + + // NOTE: Refer to the comment in constructor!!! + // cc::Director::getInstance()->getEventDispatcher()->removeEventListener(_resetDirectorListener); + + *_isDestroyed = true; +} + +bool WebSocketImpl::init(const cc::network::WebSocket::Delegate &delegate, + const ccstd::string &url, + const ccstd::vector *protocols /* = nullptr*/, + const ccstd::string &caFilePath /* = ""*/) { + _delegate = const_cast(&delegate); + _url = url; + _caFilePath = caFilePath; + + if (_url.empty()) { + return false; + } + + if (protocols != nullptr && !protocols->empty()) { + size_t size = protocols->size(); + _lwsProtocols = static_cast(malloc((size + 1) * sizeof(struct lws_protocols))); + memset(_lwsProtocols, 0, (size + 1) * sizeof(struct lws_protocols)); + + static uint32_t wsId = 0; + + for (size_t i = 0; i < size; ++i) { + _lwsProtocols[i].callback = WebSocketCallbackWrapper::onSocketCallback; + size_t nameLen = protocols->at(i).length(); + char *name = static_cast(malloc(nameLen + 1)); + name[nameLen] = '\0'; + strcpy(name, protocols->at(i).c_str()); + _lwsProtocols[i].name = name; + _lwsProtocols[i].id = ++wsId; + _lwsProtocols[i].rx_buffer_size = WS_RX_BUFFER_SIZE; + _lwsProtocols[i].per_session_data_size = 0; + _lwsProtocols[i].user = nullptr; + + _clientSupportedProtocols += name; + if (i < (size - 1)) { + _clientSupportedProtocols += ","; + } + } + } + + bool isWebSocketThreadCreated = true; + if (wsHelper == nullptr) { + wsHelper = ccnew WsThreadHelper(); + isWebSocketThreadCreated = false; + } + + auto *msg = ccnew WsMessage(); + msg->what = WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION; + msg->user = this; + wsHelper->sendMessageToWebSocketThread(msg); + + // fixed https://github.com/cocos2d/cocos2d-x/issues/17433 + // createWebSocketThread has to be after message WS_MSG_TO_SUBTHREAD_CREATE_CONNECTION was sent. + // And websocket thread should only be created once. + if (!isWebSocketThreadCreated) { + wsHelper->createWebSocketThread(); + } + +#if WS_ENABLE_LIBUV + if (wsContext && wsPolling) { + auto *loop = lws_uv_getloop(wsContext, 0); + if (loop) { + uv_stop(loop); + } + } +#endif + + return true; +} + +size_t WebSocketImpl::getBufferedAmount() const { + return wsHelper->countBufferedBytes(this); +} + +ccstd::string WebSocketImpl::getExtensions() const { + //join vector with ";" + if (_enabledExtensions.empty()) return ""; + ccstd::string ret; + for (const auto &enabledExtension : _enabledExtensions) ret += (enabledExtension + "; "); + ret += _enabledExtensions[_enabledExtensions.size() - 1]; + return ret; +} + +void WebSocketImpl::send(const ccstd::string &message) { + if (_readyState == cc::network::WebSocket::State::OPEN) { + // In main thread + auto *data = ccnew cc::network::WebSocket::Data(); + data->bytes = static_cast(malloc(message.length() + 1)); + // Make sure the last byte is '\0' + data->bytes[message.length()] = '\0'; + strcpy(data->bytes, message.c_str()); + data->len = static_cast(message.length()); + + auto *msg = ccnew WsMessage(); + msg->what = WS_MSG_TO_SUBTRHEAD_SENDING_STRING; + msg->data = data; + msg->user = this; + wsHelper->sendMessageToWebSocketThread(msg); + } else { + LOGD("Couldn't send message since websocket wasn't opened!\n"); + } +} + +void WebSocketImpl::send(const unsigned char *binaryMsg, unsigned int len) { + if (_readyState == cc::network::WebSocket::State::OPEN) { + // In main thread + auto *data = ccnew cc::network::WebSocket::Data(); + if (len == 0) { + // If data length is zero, allocate 1 byte for safe. + data->bytes = static_cast(malloc(1)); + data->bytes[0] = '\0'; + } else { + data->bytes = static_cast(malloc(len)); + memcpy(data->bytes, binaryMsg, len); + } + data->len = len; + + auto *msg = ccnew WsMessage(); + msg->what = WS_MSG_TO_SUBTRHEAD_SENDING_BINARY; + msg->data = data; + msg->user = this; + wsHelper->sendMessageToWebSocketThread(msg); + } else { + LOGD("Couldn't send message since websocket wasn't opened!\n"); + } +} + +void WebSocketImpl::close() { + if (_closeState != CloseState::NONE) { + LOGD("close was invoked, don't invoke it again!\n"); + return; + } + + _closeState = CloseState::SYNC_CLOSING; + LOGD("close: WebSocket (%p) is closing...\n", this); + { + _readyStateMutex.lock(); + if (_readyState == cc::network::WebSocket::State::CLOSED) { + // If readState is closed, it means that onConnectionClosed was invoked in websocket thread, + // but the callback of performInCocosThread has not been triggered. We need to invoke + // onClose to release the websocket instance. + _readyStateMutex.unlock(); + _delegate->onClose(_ws, 1000, "close_normal", true); + return; + } + + _readyState = cc::network::WebSocket::State::CLOSING; + _readyStateMutex.unlock(); + } + + { + std::unique_lock lkClose(_closeMutex); + _closeCondition.wait(lkClose); + _closeState = CloseState::SYNC_CLOSED; + } + + // Wait 5 milliseconds for onConnectionClosed to exit! + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + _delegate->onClose(_ws, 1000, "close_normal", true); +} + +void WebSocketImpl::closeAsync(int code, const ccstd::string &reason) { + if (_wsInstance) { + lws_close_reason(_wsInstance, static_cast(code), reinterpret_cast(const_cast(reason.c_str())), reason.length()); + } + closeAsync(); +} + +void WebSocketImpl::closeAsync() { + if (_closeState != CloseState::NONE) { + LOGD("close was invoked, don't invoke it again!\n"); + return; + } + + _closeState = CloseState::ASYNC_CLOSING; + + LOGD("closeAsync: WebSocket (%p) is closing...\n", this); + std::lock_guard lk(_readyStateMutex); + if (_readyState == cc::network::WebSocket::State::CLOSED || _readyState == cc::network::WebSocket::State::CLOSING) { + LOGD("closeAsync: WebSocket (%p) was closed, no need to close it again!\n", this); + return; + } + + _readyState = cc::network::WebSocket::State::CLOSING; +} + +cc::network::WebSocket::State WebSocketImpl::getReadyState() const { + std::lock_guard lk(const_cast(this)->_readyStateMutex); + return _readyState; +} + +const ccstd::string &WebSocketImpl::getUrl() const { + return _url; +} + +const ccstd::string &WebSocketImpl::getProtocol() const { + return _selectedProtocol; +} + +cc::network::WebSocket::Delegate *WebSocketImpl::getDelegate() const { + return _delegate; +} + +struct lws_vhost *WebSocketImpl::createVhost(struct lws_protocols *protocols, int *sslConnectionOut) { + auto *fileUtils = cc::FileUtils::getInstance(); + bool isCAFileExist = fileUtils->isFileExist(_caFilePath); + if (isCAFileExist) { + _caFilePath = fileUtils->fullPathForFilename(_caFilePath); + } + + lws_context_creation_info info = convertToContextCreationInfo(protocols, isCAFileExist); + + int sslConnection = *sslConnectionOut; + if (sslConnection != 0) { + if (isCAFileExist) { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS || CC_PLATFORM == CC_PLATFORM_OPENHARMONY) + // if ca file is in the apk, try to extract it to writable path + ccstd::string writablePath = fileUtils->getWritablePath(); + ccstd::string caFileName = getFileNameForPath(_caFilePath); + ccstd::string newCaFilePath = writablePath + caFileName; + + if (fileUtils->isFileExist(newCaFilePath)) { + LOGD("CA file (%s) in writable path exists!", newCaFilePath.c_str()); + _caFilePath = newCaFilePath; + info.ssl_ca_filepath = _caFilePath.c_str(); + } else { + if (fileUtils->isFileExist(_caFilePath)) { + ccstd::string fullPath = fileUtils->fullPathForFilename(_caFilePath); + LOGD("Found CA file: %s", fullPath.c_str()); + if (fullPath[0] != '/') { + LOGD("CA file is in APK"); + auto caData = fileUtils->getDataFromFile(fullPath); + if (!caData.isNull()) { + FILE *fp = fopen(newCaFilePath.c_str(), "wb"); + if (fp != nullptr) { + LOGD("New CA file path: %s", newCaFilePath.c_str()); + fwrite(caData.getBytes(), caData.getSize(), 1, fp); + fclose(fp); + _caFilePath = newCaFilePath; + info.ssl_ca_filepath = _caFilePath.c_str(); + } else { + CC_ABORT(); // Open new CA file failed + } + } else { + CC_ABORT(); // CA file is empty! + } + } else { + LOGD("CA file isn't in APK!"); + _caFilePath = fullPath; + info.ssl_ca_filepath = _caFilePath.c_str(); + } + } else { + CC_ABORT(); // CA file doesn't exist! + } + } +#else + info.ssl_ca_filepath = _caFilePath.c_str(); +#endif + } else { + LOGD("WARNING: CA Root file isn't set. SSL connection will not peer server certificate\n"); + *sslConnectionOut = sslConnection | LCCSCF_ALLOW_SELFSIGNED | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK; + } + } + + lws_vhost *vhost = lws_create_vhost(wsContext, &info); + + return vhost; +} + +void WebSocketImpl::onClientOpenConnectionRequest() { + if (nullptr != wsContext) { + static const struct lws_extension EXTS[] = { + {"permessage-deflate", + lws_extension_callback_pm_deflate, + // client_no_context_takeover extension is not supported in the current version, it will cause connection fail + // It may be a bug of lib websocket build + // "permessage-deflate; client_no_context_takeover; client_max_window_bits" + "permessage-deflate; client_max_window_bits"}, + {"deflate-frame", + lws_extension_callback_pm_deflate, + "deflate_frame"}, + {nullptr, nullptr, nullptr /* terminator */}}; + + _readyStateMutex.lock(); + _readyState = cc::network::WebSocket::State::CONNECTING; + _readyStateMutex.unlock(); + + cc::network::Uri uri = cc::network::Uri::parse(_url); + LOGD("scheme: %s, host: %s, port: %d, path: %s\n", uri.getScheme().c_str(), uri.getHostName().c_str(), static_cast(uri.getPort()), uri.getPathEtc().c_str()); + + int sslConnection = 0; + if (uri.isSecure()) { + sslConnection = LCCSCF_USE_SSL; + } + + struct lws_vhost *vhost = nullptr; + if (_lwsProtocols != nullptr) { + vhost = createVhost(_lwsProtocols, &sslConnection); + } else { + vhost = createVhost(defaultProtocols, &sslConnection); + } + + int port = static_cast(uri.getPort()); + if (port == 0) { + port = uri.isSecure() ? 443 : 80; + } + + const ccstd::string &hostName = uri.getHostName(); + ccstd::string path = uri.getPathEtc(); + const ccstd::string &authority = uri.getAuthority(); + if (path.empty()) { + path = "/"; + } + + struct lws_client_connect_info connectInfo; + memset(&connectInfo, 0, sizeof(connectInfo)); + connectInfo.context = wsContext; + connectInfo.address = hostName.c_str(); + connectInfo.port = port; + connectInfo.ssl_connection = sslConnection; + connectInfo.path = path.c_str(); + connectInfo.host = hostName.c_str(); + connectInfo.origin = authority.c_str(); + connectInfo.protocol = _clientSupportedProtocols.empty() ? nullptr : _clientSupportedProtocols.c_str(); + connectInfo.ietf_version_or_minus_one = -1; + connectInfo.userdata = this; + connectInfo.client_exts = EXTS; + connectInfo.vhost = vhost; + + _wsInstance = lws_client_connect_via_info(&connectInfo); + + if (nullptr == _wsInstance) { + onConnectionError(); + return; + } + } else { + LOGE("Create websocket context failed!"); + } +} + +int WebSocketImpl::onClientWritable() { + // LOGD("onClientWritable ... \n"); + { + std::lock_guard readMutex(_readyStateMutex); + if (_readyState == cc::network::WebSocket::State::CLOSING) { + LOGD("Closing websocket (%p) connection.\n", this); + return -1; + } + } + + do { + std::lock_guard lk(wsHelper->_subThreadWsMessageQueueMutex); + + if (wsHelper->_subThreadWsMessageQueue->empty()) { + break; + } + + auto iter = wsHelper->_subThreadWsMessageQueue->begin(); + + while (iter != wsHelper->_subThreadWsMessageQueue->end()) { + WsMessage *msg = *iter; + if (msg->user == this) { + break; + } + ++iter; + } + + ssize_t bytesWrite = 0; + if (iter != wsHelper->_subThreadWsMessageQueue->end()) { + WsMessage *subThreadMsg = *iter; + + auto *data = subThreadMsg->data; + + const ssize_t cBufferSize = WS_RX_BUFFER_SIZE; + + const ssize_t remaining = data->len - data->issued; + const ssize_t n = std::min(remaining, cBufferSize); + + WebSocketFrame *frame = nullptr; + + if (data->ext) { + frame = static_cast(data->ext); + } else { + frame = ccnew WebSocketFrame(); + bool success = frame && frame->init(reinterpret_cast(data->bytes + data->issued), n); + if (success) { + data->ext = frame; + } else { // If frame initialization failed, delete the frame and drop the sending data + // These codes should never be called. + LOGD("WebSocketFrame initialization failed, drop the sending data, msg(%d)\n", (int)subThreadMsg->id); + delete frame; + CC_SAFE_FREE(data->bytes); + CC_SAFE_DELETE(data); + wsHelper->_subThreadWsMessageQueue->erase(iter); + CC_SAFE_DELETE(subThreadMsg); + break; + } + } + + int writeProtocol; + + if (data->issued == 0) { + if (WS_MSG_TO_SUBTRHEAD_SENDING_STRING == subThreadMsg->what) { + writeProtocol = LWS_WRITE_TEXT; + } else { + writeProtocol = LWS_WRITE_BINARY; + } + + // If we have more than 1 fragment + if (data->len > cBufferSize) { + writeProtocol |= LWS_WRITE_NO_FIN; + } + } else { + // we are in the middle of fragments + writeProtocol = LWS_WRITE_CONTINUATION; + // and if not in the last fragment + if (remaining != n) { + writeProtocol |= LWS_WRITE_NO_FIN; + } + } + + bytesWrite = lws_write(_wsInstance, frame->getPayload(), frame->getPayloadLength(), static_cast(writeProtocol)); + + // Handle the result of lws_write + // Buffer overrun? + if (bytesWrite < 0) { + LOGD("ERROR: msg(%u), lws_write return: %d, but it should be %d, drop this message.\n", subThreadMsg->id, (int)bytesWrite, (int)n); + // socket error, we need to close the socket connection + CC_SAFE_FREE(data->bytes); + delete (static_cast(data->ext)); + data->ext = nullptr; + CC_SAFE_DELETE(data); + wsHelper->_subThreadWsMessageQueue->erase(iter); + CC_SAFE_DELETE(subThreadMsg); + + closeAsync(); + } else if (bytesWrite < frame->getPayloadLength()) { + frame->update(bytesWrite); + LOGD("frame wasn't sent completely, bytesWrite: %d, remain: %d\n", (int)bytesWrite, (int)frame->getPayloadLength()); + } + // Do we have another fragments to send? + else if (remaining > frame->getFrameLength() && bytesWrite == frame->getPayloadLength()) { + // A frame was totally sent, plus data->issued to send next frame + LOGD("msg(%u) append: %d + %d = %d\n", subThreadMsg->id, (int)data->issued, (int)frame->getFrameLength(), (int)(data->issued + frame->getFrameLength())); + data->issued += frame->getFrameLength(); + delete (static_cast(data->ext)); + data->ext = nullptr; + } + // Safely done! + else { + LOGD("Safely done, msg(%d)!\n", subThreadMsg->id); + if (remaining == frame->getFrameLength()) { + LOGD("msg(%u) append: %d + %d = %d\n", subThreadMsg->id, (int)data->issued, (int)frame->getFrameLength(), (int)(data->issued + frame->getFrameLength())); + LOGD("msg(%u) was totally sent!\n", subThreadMsg->id); + } else { + LOGD("ERROR: msg(%u), remaining(%d) < bytesWrite(%d)\n", subThreadMsg->id, (int)remaining, (int)frame->getFrameLength()); + LOGD("Drop the msg(%u)\n", subThreadMsg->id); + closeAsync(); + } + + CC_SAFE_FREE(data->bytes); + delete (static_cast(data->ext)); + data->ext = nullptr; + CC_SAFE_DELETE(data); + wsHelper->_subThreadWsMessageQueue->erase(iter); + CC_SAFE_DELETE(subThreadMsg); + + LOGD("-----------------------------------------------------------\n"); + } + } + + } while (false); + + if (_wsInstance != nullptr) { + lws_callback_on_writable(_wsInstance); + } + + return 0; +} + +int WebSocketImpl::onClientReceivedData(void *in, ssize_t len) { + // In websocket thread + static int packageIndex = 0; + packageIndex++; + if (in != nullptr && len > 0) { + LOGD("Receiving data:index:%d, len=%d\n", packageIndex, (int)len); + + auto *inData = static_cast(in); + _receivedData.insert(_receivedData.end(), inData, inData + len); + } else { + LOGD("Empty message received, index=%d!\n", packageIndex); + } + + // If no more data pending, send it to the client thread + size_t remainingSize = lws_remaining_packet_payload(_wsInstance); + int isFinalFragment = lws_is_final_fragment(_wsInstance); + // LOGD("remainingSize: %d, isFinalFragment: %d\n", (int)remainingSize, isFinalFragment); + + if (remainingSize == 0 && isFinalFragment) { + auto *frameData = ccnew ccstd::vector(std::move(_receivedData)); + + // reset capacity of received data buffer + _receivedData.reserve(WS_RESERVE_RECEIVE_BUFFER_SIZE); + + ssize_t frameSize = frameData->size(); + + bool isBinary = (lws_frame_is_binary(_wsInstance) != 0); + + if (!isBinary) { + frameData->push_back('\0'); + } + + std::shared_ptr> isDestroyed = _isDestroyed; + wsHelper->sendMessageToCocosThread([this, frameData, frameSize, isBinary, isDestroyed]() { + // In UI thread + LOGD("Notify data len %d to Cocos thread.\n", (int)frameSize); + + cc::network::WebSocket::Data data; + data.isBinary = isBinary; + data.bytes = static_cast(frameData->data()); + data.len = frameSize; + + if (*isDestroyed) { + LOGD("WebSocket instance was destroyed!\n"); + } else { + _delegate->onMessage(_ws, data); + } + + delete frameData; + }); + } + + return 0; +} + +int WebSocketImpl::onConnectionOpened() { + const lws_protocols *lwsSelectedProtocol = lws_get_protocol(_wsInstance); + _selectedProtocol = lwsSelectedProtocol->name; + LOGD("onConnectionOpened...: %p, client protocols: %s, server selected protocol: %s\n", this, _clientSupportedProtocols.c_str(), _selectedProtocol.c_str()); + /* + * start the ball rolling, + * LWS_CALLBACK_CLIENT_WRITEABLE will come next service + */ + lws_callback_on_writable(_wsInstance); + + { + std::lock_guard lk(_readyStateMutex); + if (_readyState == cc::network::WebSocket::State::CLOSING || _readyState == cc::network::WebSocket::State::CLOSED) { + return 0; + } + _readyState = cc::network::WebSocket::State::OPEN; + } + + std::shared_ptr> isDestroyed = _isDestroyed; + wsHelper->sendMessageToCocosThread([this, isDestroyed]() { + if (*isDestroyed) { + LOGD("WebSocket instance was destroyed!\n"); + } else { + _delegate->onOpen(_ws); + } + }); + return 0; +} + +int WebSocketImpl::onConnectionError() { + { + std::lock_guard lk(_readyStateMutex); + LOGD("WebSocket (%p) onConnectionError, state: %d ...\n", this, (int)_readyState); + if (_readyState == cc::network::WebSocket::State::CLOSED) { + return 0; + } + _readyState = cc::network::WebSocket::State::CLOSING; + } + + std::shared_ptr> isDestroyed = _isDestroyed; + wsHelper->sendMessageToCocosThread([this, isDestroyed]() { + if (*isDestroyed) { + LOGD("WebSocket instance was destroyed!\n"); + } else { + _delegate->onError(_ws, cc::network::WebSocket::ErrorCode::CONNECTION_FAILURE); + } + }); + + onConnectionClosed(static_cast(cc::network::WebSocket::ErrorCode::CONNECTION_FAILURE), "connection error"); + + return 0; +} + +int WebSocketImpl::onConnectionClosed(uint16_t code, const ccstd::string &reason) { + { + std::lock_guard lk(_readyStateMutex); + LOGD("WebSocket (%p) onConnectionClosed, state: %d ...\n", this, (int)_readyState); + if (_readyState == cc::network::WebSocket::State::CLOSED) { + return 0; + } + + if (_readyState == cc::network::WebSocket::State::CLOSING) { + if (_closeState == CloseState::SYNC_CLOSING) { + LOGD("onConnectionClosed, WebSocket (%p) is closing by client synchronously.\n", this); + for (;;) { + std::lock_guard lkClose(_closeMutex); + _closeCondition.notify_one(); + if (_closeState == CloseState::SYNC_CLOSED) { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + return 0; + } + + if (_closeState == CloseState::ASYNC_CLOSING) { + LOGD("onConnectionClosed, WebSocket (%p) is closing by client asynchronously.\n", this); + } else { + LOGD("onConnectionClosed, WebSocket (%p) is closing by server.\n", this); + } + } else { + LOGD("onConnectionClosed, WebSocket (%p) is closing by server.\n", this); + } + + _readyState = cc::network::WebSocket::State::CLOSED; + } + + std::shared_ptr> isDestroyed = _isDestroyed; + wsHelper->sendMessageToCocosThread([this, isDestroyed, code, reason]() { + if (*isDestroyed) { + LOGD("WebSocket instance (%p) was destroyed!\n", this); + } else { + _delegate->onClose(_ws, code, reason, true); + } + }); + + LOGD("WebSocket (%p) onConnectionClosed DONE!\n", this); + return 0; +} + +int WebSocketImpl::onSocketCallback(struct lws * /*wsi*/, enum lws_callback_reasons reason, void *in, ssize_t len) { + //LOGD("socket callback for %d reason\n", reason); + + int ret = 0; + switch (reason) { + case LWS_CALLBACK_CLIENT_ESTABLISHED: + ret = onConnectionOpened(); + break; + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + ret = onConnectionError(); + break; + + case LWS_CALLBACK_WSI_DESTROY: + ret = onConnectionClosed(static_cast(LWS_CALLBACK_WSI_DESTROY), "lws_callback_wsi_destroy"); + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + ret = onClientReceivedData(in, len); + break; + + case LWS_CALLBACK_CLIENT_WRITEABLE: + ret = onClientWritable(); + break; + case LWS_CALLBACK_CHANGE_MODE_POLL_FD: + case LWS_CALLBACK_LOCK_POLL: + case LWS_CALLBACK_UNLOCK_POLL: + break; + case LWS_CALLBACK_PROTOCOL_INIT: + LOGD("protocol init..."); + break; + case LWS_CALLBACK_PROTOCOL_DESTROY: + LOGD("protocol destroy..."); + break; + case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY: + if (in && len > 0) { + _enabledExtensions.emplace_back(static_cast(in), 0, len); + } + break; + default: + LOGD("WebSocket (%p) Unhandled websocket event: %d\n", this, reason); + break; + } + + return ret; +} + +NS_NETWORK_BEGIN + +/*static*/ +void WebSocket::closeAllConnections() { + WebSocketImpl::closeAllConnections(); +} + +WebSocket::WebSocket() { + _impl = ccnew WebSocketImpl(this); +} + +WebSocket::~WebSocket() { + delete _impl; +} + +bool WebSocket::init(const Delegate &delegate, + const ccstd::string &url, + const ccstd::vector *protocols /* = nullptr*/, + const ccstd::string &caFilePath /* = ""*/) { + return _impl->init(delegate, url, protocols, caFilePath); +} + +void WebSocket::send(const ccstd::string &message) { + _impl->send(message); +} + +void WebSocket::send(const unsigned char *binaryMsg, unsigned int len) { + _impl->send(binaryMsg, len); +} + +void WebSocket::close() { + _impl->close(); +} + +void WebSocket::closeAsync() { + _impl->closeAsync(); +} +void WebSocket::closeAsync(int code, const ccstd::string &reason) { + _impl->closeAsync(code, reason); +} + +WebSocket::State WebSocket::getReadyState() const { + return _impl->getReadyState(); +} + +ccstd::string WebSocket::getExtensions() const { + return _impl->getExtensions(); +} + +size_t WebSocket::getBufferedAmount() const { + return _impl->getBufferedAmount(); +} + +const ccstd::string &WebSocket::getUrl() const { + return _impl->getUrl(); +} + +const ccstd::string &WebSocket::getProtocol() const { + return _impl->getProtocol(); +} + +WebSocket::Delegate *WebSocket::getDelegate() const { + return _impl->getDelegate(); +} + +NS_NETWORK_END diff --git a/cocos/network/WebSocket-okhttp.cpp b/cocos/network/WebSocket-okhttp.cpp new file mode 100644 index 0000000..4fe86d5 --- /dev/null +++ b/cocos/network/WebSocket-okhttp.cpp @@ -0,0 +1,436 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include +#include "WebSocket.h" +#include "application/ApplicationManager.h" +#include "base/Scheduler.h" +#include "base/UTF8.h" +#include "base/memory/Memory.h" +#include "platform/FileUtils.h" +#include "platform/java/jni/JniHelper.h" + +#ifdef JAVA_CLASS_WEBSOCKET + #error "JAVA_CLASS_WEBSOCKET is already defined" +#endif +#ifdef HANDLE_TO_WS_OKHTTP3 + #error "HANDLE_TO_WS_OKHTTP3 is already defined" +#endif + +#define JAVA_CLASS_WEBSOCKET "com/cocos/lib/websocket/CocosWebSocket" +#define HANDLE_TO_WS_OKHTTP3(handler) \ + reinterpret_cast(static_cast(handler)) + +namespace { +//NOLINTNEXTLINE +void split_string(const std::string &s, std::vector &v, const std::string &c) { + v.clear(); + std::string::size_type pos1; + std::string::size_type pos2; + pos2 = s.find(c); + pos1 = 0; + while (std::string::npos != pos2) { + v.push_back(s.substr(pos1, pos2 - pos1)); + + pos1 = pos2 + c.size(); + pos2 = s.find(c, pos1); + } + if (pos1 != s.length()) { + v.push_back(s.substr(pos1)); + } +} +} // namespace + +using cc::JniHelper; +using cc::network::WebSocket; +class WebSocketImpl final { +public: + static const char *connectID; + static const char *removeHandlerID; + static const char *sendBinaryID; + static const char *sendStringID; + static const char *closeID; + static const char *getBufferedAmountID; + static std::atomic_int64_t idGenerator; + static std::unordered_map allConnections; + + static void closeAllConnections(); + + explicit WebSocketImpl(cc::network::WebSocket *websocket); + ~WebSocketImpl(); + + bool init(const cc::network::WebSocket::Delegate &delegate, + const std::string &url, + const std::vector *protocols = nullptr, + const std::string &caFilePath = ""); + + void send(const std::string &message); + void send(const unsigned char *binaryMsg, unsigned int len); + void close(); + void closeAsync(); + void closeAsync(int code, const std::string &reason); + cc::network::WebSocket::State getReadyState() const { return _readyState; } + const std::string &getUrl() const { return _url; } + const std::string &getProtocol() const { return _protocolString; } + cc::network::WebSocket::Delegate *getDelegate() const { return _delegate; } + + size_t getBufferedAmount() const; + std::string getExtensions() const { return _extensions; } + + void onOpen(const std::string &protocol, const std::string &headers); + void onClose(int code, const std::string &reason, bool wasClean); + void onError(int code, const std::string &reason); + void onStringMessage(const std::string &message); + void onBinaryMessage(const uint8_t *buf, size_t len); + +private: + WebSocket *_socket{nullptr}; + WebSocket::Delegate *_delegate{nullptr}; + jobject _javaSocket{nullptr}; + int64_t _identifier{0}; + std::string _protocolString; + std::string _selectedProtocol; + std::string _url; + std::string _extensions; + WebSocket::State _readyState{WebSocket::State::CONNECTING}; + std::unordered_map _headerMap{}; +}; + +const char *WebSocketImpl::connectID = "_connect"; +const char *WebSocketImpl::removeHandlerID = "_removeHander"; +const char *WebSocketImpl::sendBinaryID = "_send"; +const char *WebSocketImpl::sendStringID = "_send"; +const char *WebSocketImpl::closeID = "_close"; +const char *WebSocketImpl::getBufferedAmountID = "_getBufferedAmountID"; +std::atomic_int64_t WebSocketImpl::idGenerator{0}; +std::unordered_map WebSocketImpl::allConnections{}; + +void WebSocketImpl::closeAllConnections() { + std::unordered_map tmp = std::move(allConnections); + for (auto &t : tmp) { + t.second->closeAsync(); + } +} + +WebSocketImpl::WebSocketImpl(WebSocket *websocket) : _socket(websocket) { + _identifier = idGenerator.fetch_add(1); + allConnections.emplace(_identifier, this); +} + +WebSocketImpl::~WebSocketImpl() { + auto *env = JniHelper::getEnv(); + JniHelper::getEnv()->DeleteGlobalRef(_javaSocket); + _javaSocket = nullptr; + allConnections.erase(_identifier); +} + +bool WebSocketImpl::init(const cc::network::WebSocket::Delegate &delegate, const std::string &url, + const std::vector *protocols, const std::string &caFilePath) { + auto *env = JniHelper::getEnv(); + auto handler = static_cast(reinterpret_cast(this)); + bool tcpNoDelay = false; + bool perMessageDeflate = true; + int64_t timeout = 60 * 60 * 1000 /*ms*/; //TODO(PatriceJiang): set timeout + std::vector headers; //TODO(PatriceJiang): allow set headers + _url = url; + _delegate = const_cast(&delegate); + if (protocols != nullptr && !protocols->empty()) { + // protocol should add to Request Header as part of the original handshake for key + // Sec-WebSocket-Protocol, use ',' to separate more than one. + // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers + std::string item; + auto it = protocols->begin(); + while (it != protocols->end()) { + item = *it++; + _protocolString.append(item); + if (it != protocols->end()) { + _protocolString.append(", "); + } + } + } + // header + jobject jObj = JniHelper::newObject(JAVA_CLASS_WEBSOCKET, _identifier, handler, headers, tcpNoDelay, perMessageDeflate, timeout); + _javaSocket = env->NewGlobalRef(jObj); + JniHelper::callObjectVoidMethod(jObj, JAVA_CLASS_WEBSOCKET, connectID, url, _protocolString, caFilePath); + env->DeleteLocalRef(jObj); + _readyState = WebSocket::State::CONNECTING; + return true; +} + +void WebSocketImpl::send(const std::string &message) { + if (_readyState == WebSocket::State::OPEN) { + JniHelper::callObjectVoidMethod(_javaSocket, JAVA_CLASS_WEBSOCKET, sendStringID, message); + } else { + CC_LOG_ERROR("Couldn't send message since WebSocket wasn't opened!"); + } +} + +void WebSocketImpl::send(const unsigned char *binaryMsg, unsigned int len) { + if (_readyState == WebSocket::State::OPEN) { + JniHelper::callObjectVoidMethod(_javaSocket, JAVA_CLASS_WEBSOCKET, sendBinaryID, std::make_pair(binaryMsg, static_cast(len))); + } else { + CC_LOG_ERROR("Couldn't send message since WebSocket wasn't opened!"); + } +} + +void WebSocketImpl::close() { + closeAsync(); // close only run in async mode +} + +void WebSocketImpl::closeAsync() { + closeAsync(1000, "normal closure"); +} + +void WebSocketImpl::closeAsync(int code, const std::string &reason) { + CC_LOG_DEBUG("close WebSocket: %p, code: %d, reason: %s", + this, + code, + reason.c_str()); + if (_readyState == WebSocket::State::CLOSED || + _readyState == WebSocket::State::CLOSING) { + CC_LOG_ERROR("close: WebSocket (%p) was closed, no need to close it again!", this); + return; + } + _readyState = WebSocket::State::CLOSING; // update state -> CLOSING + JniHelper::callObjectVoidMethod(_javaSocket, JAVA_CLASS_WEBSOCKET, closeID, code, reason); +} + +size_t WebSocketImpl::getBufferedAmount() const { + jlong buffAmount = JniHelper::callObjectLongMethod(_javaSocket, JAVA_CLASS_WEBSOCKET, getBufferedAmountID); + return static_cast(buffAmount); +} + +void WebSocketImpl::onOpen(const std::string &protocol, const std::string &headers) { + _selectedProtocol = protocol; + std::vector headerTokens; + split_string(headers, headerTokens, "\n"); + std::vector headerKV; + for (auto &kv : headerTokens) { + split_string(kv, headerKV, ": "); + _headerMap.insert(std::pair(headerKV[0], + headerKV[1])); + if (headerKV[0] == "Sec-WebSocket-Extensions") { + _extensions = headerKV[1]; + } + } + if (_readyState == WebSocket::State::CLOSING || _readyState == WebSocket::State::CLOSED) { + CC_LOG_DEBUG("websocket is closing"); + } else { + _readyState = WebSocket::State::OPEN; // update state -> OPEN + _delegate->onOpen(_socket); + } +} + +void WebSocketImpl::onClose(int code, const std::string &reason, bool wasClean) { + _readyState = WebSocket::State::CLOSED; // update state -> CLOSED + _delegate->onClose(_socket, code, reason, wasClean); +} + +void WebSocketImpl::onError(int code, const std::string &reason) { + CC_LOG_DEBUG("WebSocket (%p) onError, state: %d ...", this, (int)_readyState); + if (_readyState != WebSocket::State::CLOSED) { + _readyState = WebSocket::State::CLOSED; // update state -> CLOSED + _delegate->onError(_socket, static_cast(code)); + } + onClose(code, reason, false); +} + +void WebSocketImpl::onBinaryMessage(const uint8_t *buf, size_t len) { + WebSocket::Data data; + data.bytes = reinterpret_cast(const_cast(buf)); + data.len = static_cast(len); + data.isBinary = true; + _delegate->onMessage(_socket, data); +} + +void WebSocketImpl::onStringMessage(const std::string &message) { + WebSocket::Data data; + data.bytes = const_cast(message.c_str()); + data.len = static_cast(message.length()); + data.isBinary = false; + _delegate->onMessage(_socket, data); +} + +namespace cc { +namespace network { +/*static*/ +void WebSocket::closeAllConnections() { + WebSocketImpl::closeAllConnections(); +} + +WebSocket::WebSocket() { + _impl = ccnew WebSocketImpl(this); +} + +WebSocket::~WebSocket() { + delete _impl; +} + +bool WebSocket::init(const Delegate &delegate, + const std::string &url, + const std::vector *protocols /* = nullptr*/, + const std::string &caFilePath /* = ""*/) { + return _impl->init(delegate, url, protocols, caFilePath); +} + +void WebSocket::send(const std::string &message) { + _impl->send(message); +} + +void WebSocket::send(const unsigned char *binaryMsg, unsigned int len) { + _impl->send(binaryMsg, len); +} + +void WebSocket::close() { + _impl->close(); +} + +void WebSocket::closeAsync() { + _impl->closeAsync(); +} +void WebSocket::closeAsync(int code, const std::string &reason) { + _impl->closeAsync(code, reason); +} + +WebSocket::State WebSocket::getReadyState() const { + return _impl->getReadyState(); +} + +std::string WebSocket::getExtensions() const { + return _impl->getExtensions(); +} + +size_t WebSocket::getBufferedAmount() const { + return _impl->getBufferedAmount(); +} + +const std::string &WebSocket::getUrl() const { + return _impl->getUrl(); +} + +const std::string &WebSocket::getProtocol() const { + return _impl->getProtocol(); +} + +WebSocket::Delegate *WebSocket::getDelegate() const { + return _impl->getDelegate(); +} + +} // namespace network +} // namespace cc + +extern "C" { +#ifdef JNI_PATH + #error "JNI_PATH is already defined" +#endif + +#ifdef RUN_IN_GAMETHREAD + #error "RUN_IN_GAMETHREAD is already defined" +#endif + +#define JNI_PATH(methodName) Java_com_cocos_lib_websocket_CocosWebSocket_##methodName +#define RUN_IN_GAMETHREAD(task) \ + do { \ + if (CC_CURRENT_APPLICATION()) { \ + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() { \ + task; \ + }); \ + } \ + } while (0) + +JNIEXPORT void JNICALL JNI_PATH(NativeInit)(JNIEnv * /*env*/, jclass /*clazz*/) { + // nop + // may cache jni objects in the future +} + +JNIEXPORT void JNICALL +JNI_PATH(nativeOnStringMessage)(JNIEnv * /*env*/, + jobject /*ctx*/, + jstring msg, + jlong /*identifier*/, + jlong handler) { + auto *wsOkHttp3 = HANDLE_TO_WS_OKHTTP3(handler); // NOLINT(performance-no-int-to-ptr) + std::string msgStr = JniHelper::jstring2string(msg); + RUN_IN_GAMETHREAD(wsOkHttp3->onStringMessage(msgStr)); +} + +JNIEXPORT void JNICALL +JNI_PATH(nativeOnBinaryMessage)(JNIEnv *env, + jobject /*ctx*/, + jbyteArray msg, + jlong /*identifier*/, + jlong handler) { + auto *wsOkHttp3 = HANDLE_TO_WS_OKHTTP3(handler); // NOLINT(performance-no-int-to-ptr) + jobject strongRef = env->NewGlobalRef(msg); + RUN_IN_GAMETHREAD(do { + auto *env = JniHelper::getEnv(); + auto len = env->GetArrayLength(static_cast(strongRef)); + jboolean isCopy = JNI_FALSE; + jbyte *array = env->GetByteArrayElements(static_cast(strongRef), &isCopy); + wsOkHttp3->onBinaryMessage(reinterpret_cast(array), len); + env->DeleteGlobalRef(strongRef); + } while (false)); +} + +JNIEXPORT void JNICALL +JNI_PATH(nativeOnOpen)(JNIEnv * /*env*/, + jobject /*ctx*/, + jstring protocol, + jstring header, + jlong /*identifier*/, + jlong handler) { + auto *wsOkHttp3 = HANDLE_TO_WS_OKHTTP3(handler); // NOLINT(performance-no-int-to-ptr) + auto protocolStr = JniHelper::jstring2string(protocol); + auto headerStr = JniHelper::jstring2string(header); + RUN_IN_GAMETHREAD(wsOkHttp3->onOpen(protocolStr, headerStr)); +} + +JNIEXPORT void JNICALL +JNI_PATH(nativeOnClosed)(JNIEnv * /*env*/, + jobject /*ctx*/, + jint code, + jstring reason, + jlong /*identifier*/, + jlong handler) { + auto *wsOkHttp3 = HANDLE_TO_WS_OKHTTP3(handler); // NOLINT(performance-no-int-to-ptr) + auto closeReason = JniHelper::jstring2string(reason); + RUN_IN_GAMETHREAD(wsOkHttp3->onClose(static_cast(code), closeReason, true)); +} + +JNIEXPORT void JNICALL +JNI_PATH(nativeOnError)(JNIEnv * /*env*/, + jobject /*ctx*/, + jstring reason, + jlong /*identifier*/, + jlong handler) { + auto *wsOkHttp3 = HANDLE_TO_WS_OKHTTP3(handler); // NOLINT(performance-no-int-to-ptr) + int unknownError = static_cast(cc::network::WebSocket::ErrorCode::UNKNOWN); + auto errorReason = JniHelper::jstring2string(reason); + RUN_IN_GAMETHREAD(wsOkHttp3->onError(unknownError, errorReason)); +} + +#undef JNI_PATH +} + +#undef HANDLE_TO_WS_OKHTTP3 diff --git a/cocos/network/WebSocket.h b/cocos/network/WebSocket.h new file mode 100644 index 0000000..e3b3469 --- /dev/null +++ b/cocos/network/WebSocket.h @@ -0,0 +1,251 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/RefCounted.h" +#include "base/std/container/vector.h" +#include "platform/StdC.h" + +#include +#include "base/std/container/string.h" +#ifndef OBJC_CLASS + #ifdef __OBJC__ + #define OBJC_CLASS(name) @class name + #else + #define OBJC_CLASS(name) class name + #endif +#endif // OBJC_CLASS + +OBJC_CLASS(WebSocketImpl); + +/** + * @addtogroup network + * @{ + */ + +namespace cc { + +namespace network { + +/** + * WebSocket is wrapper of the libwebsockets-protocol, let the develop could call the websocket easily. + * Please note that all public methods of WebSocket have to be invoked on Cocos Thread. + */ +class CC_DLL WebSocket : public RefCounted { +public: + /** + * Close all connections and wait for all websocket threads to exit + * @note This method has to be invoked on Cocos Thread + */ + static void closeAllConnections(); + + /** + * Constructor of WebSocket. + * + * @js ctor + */ + WebSocket(); + +private: + /** + * Destructor of WebSocket. + * + * @js NA + * @lua NA + */ + ~WebSocket() override; + +public: + /** + * Data structure for message + */ + struct Data { + Data() = default; + char *bytes{nullptr}; + uint32_t len{0}, issued{0}; + bool isBinary{false}; + void *ext{nullptr}; + uint32_t getRemain() const { return std::max(static_cast(0), len - issued); } + }; + + /** + * ErrorCode enum used to represent the error in the websocket. + */ + enum class ErrorCode { + TIME_OUT, /** < value 0 */ + CONNECTION_FAILURE, /** < value 1 */ + UNKNOWN, /** < value 2 */ + }; + + /** + * State enum used to represent the Websocket state. + */ + enum class State { + CONNECTING, /** < value 0 */ + OPEN, /** < value 1 */ + CLOSING, /** < value 2 */ + CLOSED, /** < value 3 */ + }; + + /** + * The delegate class is used to process websocket events. + * + * The most member function are pure virtual functions,they should be implemented the in subclass. + * @lua NA + */ + class Delegate { + public: + /** Destructor of Delegate. */ + virtual ~Delegate() = default; + /** + * This function to be called after the client connection complete a handshake with the remote server. + * This means that the WebSocket connection is ready to send and receive data. + * + * @param ws The WebSocket object connected + */ + virtual void onOpen(WebSocket *ws) = 0; + /** + * This function to be called when data has appeared from the server for the client connection. + * + * @param ws The WebSocket object connected. + * @param data Data object for message. + */ + virtual void onMessage(WebSocket *ws, const Data &data) = 0; + /** + * When the WebSocket object connected wants to close or the protocol won't get used at all and current _readyState is State::CLOSING,this function is to be called. + * + * @param ws The WebSocket object connected. + */ + virtual void onClose(WebSocket *ws, uint16_t code, const ccstd::string &reason, bool wasClean) = 0; + /** + * This function is to be called in the following cases: + * 1. client connection is failed. + * 2. the request client connection has been unable to complete a handshake with the remote server. + * 3. the protocol won't get used at all after this callback and current _readyState is State::CONNECTING. + * 4. when a socket descriptor needs to be removed from an external polling array. in is again the struct libwebsocket_pollargs containing the fd member to be removed. If you are using the internal polling loop, you can just ignore it and current _readyState is State::CONNECTING. + * + * @param ws The WebSocket object connected. + * @param error WebSocket::ErrorCode enum,would be ErrorCode::TIME_OUT or ErrorCode::CONNECTION_FAILURE. + */ + virtual void onError(WebSocket *ws, const ErrorCode &error) = 0; + }; + + /** + * @brief The initialized method for websocket. + * It needs to be invoked right after websocket instance is allocated. + * @param delegate The delegate which want to receive event from websocket. + * @param url The URL of websocket server. + * @param protocols The websocket protocols that agree with websocket server + * @param caFilePath The ca file path for wss connection + * @return true: Success, false: Failure. + * @lua NA + */ + bool init(const Delegate &delegate, + const ccstd::string &url, + const ccstd::vector *protocols = nullptr, + const ccstd::string &caFilePath = ""); + + /** + * @brief Sends string data to websocket server. + * + * @param message string data. + * @lua sendstring + */ + void send(const ccstd::string &message); + + /** + * @brief Sends binary data to websocket server. + * + * @param binaryMsg binary string data. + * @param len the size of binary string data. + * @lua sendstring + */ + void send(const unsigned char *binaryMsg, unsigned int len); + + /** + * @brief Closes the connection to server synchronously. + * @note It's a synchronous method, it will not return until websocket thread exits. + */ + void close(); + + /** + * @brief Closes the connection to server asynchronously. + * @note It's an asynchronous method, it just notifies websocket thread to exit and returns directly, + * If using 'closeAsync' to close websocket connection, + * be careful of not using destructed variables in the callback of 'onClose'. + */ + void closeAsync(); + + /** + * @brief Closes the connection to server asynchronously. + * @note It's an asynchronous method, it just notifies websocket thread to exit and returns directly, + * If using 'closeAsync' to close websocket connection, + * be careful of not using destructed variables in the callback of 'onClose'. + * @param code close reason + * @param reason reason text description + */ + void closeAsync(int code, const ccstd::string &reason); + + /** + * @brief Gets current state of connection. + * @return State the state value could be State::CONNECTING, State::OPEN, State::CLOSING or State::CLOSED + */ + State getReadyState() const; + + /** + * @brief Gets the URL of websocket connection. + */ + const ccstd::string &getUrl() const; + + /** + * @brief Returns the number of bytes of data that have been queued using calls to send() but not yet transmitted to the network. + */ + size_t getBufferedAmount() const; + + /** + * @brief Returns the extensions selected by the server. + */ + ccstd::string getExtensions() const; + + /** + * @brief Gets the protocol selected by websocket server. + */ + const ccstd::string &getProtocol() const; + + Delegate *getDelegate() const; + +private: + WebSocketImpl *_impl{nullptr}; +}; + +} // namespace network + +} // namespace cc + +// end group +/// @} diff --git a/cocos/network/WebSocketServer.cpp b/cocos/network/WebSocketServer.cpp new file mode 100644 index 0000000..3e742f9 --- /dev/null +++ b/cocos/network/WebSocketServer.cpp @@ -0,0 +1,770 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include +#include +#include + +#include "application/ApplicationManager.h" +#include "base/Log.h" +#include "base/Scheduler.h" +#include "base/memory/Memory.h" +#include "cocos/network/WebSocketServer.h" + +#define MAX_MSG_PAYLOAD 2048 +#define SEND_BUFF 1024 + +namespace { + +std::atomic_int32_t aliveServer{0}; //debug info + +struct lws_protocols protocols[] = { + {"", //protocol name + cc::network::WebSocketServer::websocketServerCallback, + sizeof(int), + MAX_MSG_PAYLOAD}, + {nullptr, nullptr, 0}}; + +const struct lws_extension EXTS[] = { + {"permessage-deflate", + lws_extension_callback_pm_deflate, + "permessage-deflate; client_on_context_takeover; client_max_window_bits"}, + {"deflate-frame", + lws_extension_callback_pm_deflate, + "deflate-frame"}, + {nullptr, nullptr, nullptr}}; + +struct AsyncTaskData { + std::mutex mtx; + ccstd::list> tasks; +}; + +// run in server thread loop +void flushTasksInServerLoopCb(uv_async_t *async) { + auto *data = static_cast(async->data); + std::lock_guard guard(data->mtx); + while (!data->tasks.empty()) { + // fetch task, run task + data->tasks.front()(); + // drop task + data->tasks.pop_front(); + } +} +void initLibuvAsyncHandle(uv_loop_t *loop, uv_async_t *async) { + memset(async, 0, sizeof(uv_async_t)); + uv_async_init(loop, async, flushTasksInServerLoopCb); + async->data = ccnew AsyncTaskData(); +} + +// run in game thread, dispatch runnable object into server loop +void schedule_task_into_server_thread_task_queue(uv_async_t *async, std::function func) { + auto *data = static_cast(async->data); + if (data) { + std::lock_guard guard(data->mtx); + data->tasks.emplace_back(func); + } + //notify server thread to invoke `flushTasksInServerLoopCb()` + uv_async_send(async); +} + +} // namespace + +namespace cc { +namespace network { + +#define RUN_IN_GAMETHREAD(task) \ + do { \ + if (CC_CURRENT_APPLICATION()) { \ + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() { \ + task; \ + }); \ + } \ + } while (0) + +#define DISPATCH_CALLBACK_IN_GAMETHREAD() \ + do { \ + data->setCallback([callback](const ccstd::string &msg) { \ + auto wrapper = [callback, msg]() { callback(msg); }; \ + RUN_IN_GAMETHREAD(wrapper()); \ + }); \ + } while (0) + +#define RUN_IN_SERVERTHREAD(task) \ + do { \ + schedule_task_into_server_thread_task_queue(&_async, [=]() { \ + task; \ + }); \ + } while (0) + +//#define LOGE() CCLOG("WSS: %s", __FUNCTION__) +#define LOGE() + +DataFrame::DataFrame(const ccstd::string &data) { + _underlyingData.resize(data.size() + LWS_PRE); + memcpy(getData(), data.c_str(), data.length()); +} + +DataFrame::DataFrame(const void *data, int len, bool isBinary) : _isBinary(isBinary) { + _underlyingData.resize(len + LWS_PRE); + memcpy(getData(), data, len); +} + +void DataFrame::append(unsigned char *p, int len) { + _underlyingData.insert(_underlyingData.end(), p, p + len); +} + +int DataFrame::slice(unsigned char **p, int len) { + *p = getData() + _consumed; + if (_consumed + len > size()) { + return size() - _consumed; + } + return len; +} + +int DataFrame::consume(int len) { + _consumed = (len + _consumed) > size() ? size() : (len + _consumed); + return _consumed; +} + +int DataFrame::remain() const { + return size() - _consumed; +} + +ccstd::string DataFrame::toString() { + return ccstd::string(reinterpret_cast(getData()), size()); +} + +WebSocketServer::WebSocketServer() { + aliveServer.fetch_add(1); +} + +WebSocketServer::~WebSocketServer() { + aliveServer.fetch_sub(1); + destroyContext(); +} + +bool WebSocketServer::close(const std::function &callback) { + if (_serverState.load() != ServerThreadState::RUNNING) { + return false; + } + _serverState.store(ServerThreadState::STOPPED); + _onclose_cb = callback; + if (_ctx) { + lws_libuv_stop(_ctx); + } + return true; +} + +void WebSocketServer::closeAsync(const std::function &callback) { + if (_serverState.load() != ServerThreadState::RUNNING) { + return; + } + RUN_IN_SERVERTHREAD(this->close(callback)); +} + +void WebSocketServer::listen(const std::shared_ptr &server, int port, const ccstd::string &host, const std::function &callback) { + auto tryLock = server->_serverLock.try_lock(); + if (!tryLock) { + CC_LOG_WARNING("websocketserver is already running!"); + if (callback) { + RUN_IN_GAMETHREAD(callback("Error: Server is already running!")); + } + return; + } + + server->_serverState = ServerThreadState::RUNNING; + //lws_set_log_level(-1, nullptr); + + if (server->_ctx) { + server->destroyContext(); + if (callback) { + RUN_IN_GAMETHREAD(callback("Error: lws_context already created!")); + } + RUN_IN_GAMETHREAD(if (server->_onerror) server->_onerror("websocket listen error!")); + server->_serverState = ServerThreadState::ST_ERROR; + server->_serverLock.unlock(); + return; + } + + server->_host = host; + + struct lws_context_creation_info info; + memset(&info, 0, sizeof(info)); + info.port = port; + info.iface = server->_host.empty() ? nullptr : server->_host.c_str(); + info.protocols = protocols; + info.gid = -1; + info.uid = -1; + info.extensions = EXTS; + info.options = LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_LIBUV | LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME; + info.timeout_secs = 60; + info.max_http_header_pool = 1; + info.user = server.get(); + + server->_ctx = lws_create_context(&info); + + if (!server->_ctx) { + if (callback) { + RUN_IN_GAMETHREAD(callback("Error: Failed to create lws_context!")); + } + RUN_IN_GAMETHREAD(if (server->_onerror) server->_onerror("websocket listen error!")); + server->_serverState = ServerThreadState::ST_ERROR; + server->_serverLock.unlock(); + return; + } + uv_loop_t *loop = nullptr; + if (lws_uv_initloop(server->_ctx, loop, 0)) { + if (callback) { + RUN_IN_GAMETHREAD(callback("Error: Failed to create libuv loop!")); + } + RUN_IN_GAMETHREAD(if (server->_onerror) server->_onerror("websocket listen error, failed to create libuv loop!")); + server->_serverState = ServerThreadState::ST_ERROR; + server->_serverLock.unlock(); + server->destroyContext(); + return; + } + + loop = lws_uv_getloop(server->_ctx, 0); + initLibuvAsyncHandle(loop, &server->_async); + RUN_IN_GAMETHREAD(if (server->_onlistening) server->_onlistening("")); + RUN_IN_GAMETHREAD(if (server->_onbegin) server->_onbegin()); + RUN_IN_GAMETHREAD(if (callback) callback("")); + + lws_libuv_run(server->_ctx, 0); + uv_close(reinterpret_cast(&server->_async), nullptr); + + RUN_IN_GAMETHREAD(if (server->_onclose) server->_onclose("")); + RUN_IN_GAMETHREAD(if (server->_onclose_cb) server->_onclose_cb("")); + RUN_IN_GAMETHREAD(if (server->_onend) server->_onend()); + server->_serverState = ServerThreadState::STOPPED; + server->destroyContext(); + server->_serverLock.unlock(); +} + +void WebSocketServer::listenAsync(std::shared_ptr &server, int port, const ccstd::string &host, const std::function &callback) { + std::thread([=]() { + WebSocketServer::listen(server, port, host, callback); + }).detach(); +} + +ccstd::vector> WebSocketServer::getConnections() const { + std::lock_guard guard(_connsMtx); + ccstd::vector> ret; + for (auto itr : _conns) { + ret.emplace_back(itr.second); + } + return ret; +} + +void WebSocketServer::onCreateClient(struct lws *wsi) { + LOGE(); + std::shared_ptr conn = std::make_shared(wsi); + //char ip[221] = { 0 }; + //char addr[221] = { 0 }; + //lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), ip, 220, addr, 220); + //lws_get_peer_simple(wsi, ip, 220); + { + std::lock_guard guard(_connsMtx); + _conns.emplace(wsi, conn); + } + RUN_IN_GAMETHREAD(if (_onconnection) _onconnection(conn)); + conn->onConnected(); +} + +void WebSocketServer::onDestroyClient(struct lws *wsi) { + LOGE(); + std::shared_ptr conn = findConnection(wsi); + if (conn) { + conn->onDestroyClient(); + } + std::lock_guard guard(_connsMtx); + _conns.erase(wsi); +} +void WebSocketServer::onCloseClient(struct lws *wsi) { + std::shared_ptr conn = findConnection(wsi); + if (conn) { + conn->onClientCloseInit(); + } +} + +void WebSocketServer::onCloseClientInit(struct lws *wsi, void *in, int len) { + int16_t code; + char *msg = nullptr; + + std::shared_ptr conn = findConnection(wsi); + + if (conn && len > 2) { + code = ntohs(*(int16_t *)in); + msg = static_cast(in) + sizeof(code); + ccstd::string cp(msg, len - sizeof(code)); + conn->onClientCloseInit(code, cp); + } else { + conn->onClientCloseInit(LWS_CLOSE_STATUS_NORMAL, "Normal"); + } +} + +void WebSocketServer::onClientReceive(struct lws *wsi, void *in, int len) { + std::shared_ptr conn = findConnection(wsi); + if (conn) { + conn->onMessageReceive(in, len); + } +} +int WebSocketServer::onServerWritable(struct lws *wsi) { + LOGE(); + std::shared_ptr conn = findConnection(wsi); + if (conn) { + return conn->onDrainMessage(); + } + return 0; +} + +void WebSocketServer::onClientHTTP(struct lws *wsi) { + LOGE(); + std::shared_ptr conn = findConnection(wsi); + if (conn) { + conn->onHTTP(); + } +} + +std::shared_ptr WebSocketServer::findConnection(struct lws *wsi) { + std::shared_ptr conn; + { + std::lock_guard guard(_connsMtx); + auto itr = _conns.find(wsi); + if (itr != _conns.end()) { + conn = itr->second; + } + } + return conn; +} + +void WebSocketServer::destroyContext() { + _serverState.store(ServerThreadState::DESTROYED); + if (_ctx) { + lws_context_destroy(_ctx); + lws_context_destroy2(_ctx); + _ctx = nullptr; + } + if (_async.data) { + delete static_cast(_async.data); + _async.data = nullptr; + } +} + +WebSocketServerConnection::WebSocketServerConnection(struct lws *wsi) : _wsi(wsi) { + uv_loop_t *loop = lws_uv_getloop(lws_get_context(wsi), 0); + initLibuvAsyncHandle(loop, &_async); +} + +WebSocketServerConnection::~WebSocketServerConnection() { + if (_async.data) { + delete static_cast(_async.data); + _async.data = nullptr; + } + CC_LOG_INFO("~destroy ws connection"); +} + +bool WebSocketServerConnection::send(std::shared_ptr data) { + _sendQueue.emplace_back(data); + if (!_wsi || _closed || _readyState == ReadyState::CLOSING) { + return false; + } + lws_callback_on_writable(_wsi); + return true; +} + +void WebSocketServerConnection::sendTextAsync(const ccstd::string &text, const std::function &callback) { + LOGE(); + std::shared_ptr data = std::make_shared(text); + if (callback) { + DISPATCH_CALLBACK_IN_GAMETHREAD(); + } + RUN_IN_SERVERTHREAD(this->send(data)); +} + +void WebSocketServerConnection::sendBinaryAsync(const void *in, size_t len, const std::function &callback) { + LOGE(); + std::shared_ptr data = std::make_shared(in, len); + if (callback) { + DISPATCH_CALLBACK_IN_GAMETHREAD(); + } + RUN_IN_SERVERTHREAD(this->send(data)); +} + +bool WebSocketServerConnection::close(int code, const ccstd::string &reason) { + if (!_wsi) return false; + _readyState = ReadyState::CLOSING; + _closeReason = reason; + _closeCode = code; + onClientCloseInit(); + //trigger callback to return -1 which indicates connection closed + lws_callback_on_writable(_wsi); + return true; +} + +void WebSocketServerConnection::closeAsync(int code, const ccstd::string &reason) { + RUN_IN_SERVERTHREAD(this->close(code, reason)); +} + +void WebSocketServerConnection::onConnected() { + _readyState = ReadyState::OPEN; + RUN_IN_GAMETHREAD(if (_onconnect) _onconnect()); +} + +void WebSocketServerConnection::onMessageReceive(void *in, int len) { + bool isFinal = static_cast(lws_is_final_fragment(_wsi)); + bool isBinary = static_cast(lws_frame_is_binary(_wsi)); + + if (!_prevPkg) { + _prevPkg = std::make_shared(in, len, isBinary); + } else { + _prevPkg->append(static_cast(in), len); + } + + if (isFinal) { + //trigger event + std::shared_ptr fullpkg = _prevPkg; + if (isBinary) { + RUN_IN_GAMETHREAD(if (_onbinary) _onbinary(fullpkg)); + } + if (!isBinary) { + RUN_IN_GAMETHREAD(if (_ontext) _ontext(fullpkg)); + } + + RUN_IN_GAMETHREAD(if (_onmessage) _onmessage(fullpkg)); + + _prevPkg.reset(); + } +} + +int WebSocketServerConnection::onDrainMessage() { + if (!_wsi) return -1; + if (_closed) return -1; + if (_readyState == ReadyState::CLOSING) { + return -1; + } + if (_readyState != ReadyState::OPEN) return 0; + unsigned char *p = nullptr; + int sendLength = 0; + int finishLength = 0; + int flags = 0; + + ccstd::vector buff(SEND_BUFF + LWS_PRE); + + if (!_sendQueue.empty()) { + std::shared_ptr frag = _sendQueue.front(); + + sendLength = frag->slice(&p, SEND_BUFF); + + if (frag->isFront()) { + if (frag->isBinary()) { + flags |= LWS_WRITE_BINARY; + } + if (frag->isString()) { + flags |= LWS_WRITE_TEXT; + } + } + + if (frag->remain() != sendLength) { + // remain bytes > 0 + // not FIN + flags |= LWS_WRITE_NO_FIN; + } + + if (!frag->isFront()) { + flags |= LWS_WRITE_CONTINUATION; + } + + finishLength = lws_write(_wsi, p, sendLength, static_cast(flags)); + + if (finishLength == 0) { + frag->onFinish("Connection Closed"); + return -1; + } + if (finishLength < 0) { + frag->onFinish("Send Error!"); + return -1; + } + frag->consume(finishLength); + + if (frag->remain() == 0) { + frag->onFinish(""); + _sendQueue.pop_front(); + } + lws_callback_on_writable(_wsi); + } + + return 0; +} + +void WebSocketServerConnection::onHTTP() { + if (!_wsi) return; + + _headers.clear(); + + int n = 0; + int len; + ccstd::vector buf(256); + const char *c; + do { + auto idx = static_cast(n); + c = reinterpret_cast(lws_token_to_string(idx)); + if (!c) { + n++; + break; + } + len = lws_hdr_total_length(_wsi, idx); + if (!len) { + n++; + continue; + } + if (len + 1 > buf.size()) { + buf.resize(len + 1); + } + lws_hdr_copy(_wsi, buf.data(), static_cast(buf.size()), idx); + buf[len] = '\0'; + _headers.emplace(ccstd::string(c), ccstd::string(buf.data())); + n++; + } while (c); +} + +void WebSocketServerConnection::onClientCloseInit(int code, const ccstd::string &msg) { + _closeCode = code; + _closeReason = msg; +} + +void WebSocketServerConnection::onClientCloseInit() { + if (_closed) return; + lws_close_reason(_wsi, static_cast(_closeCode), const_cast(reinterpret_cast(_closeReason.c_str())), _closeReason.length()); + _closed = true; +} + +void WebSocketServerConnection::onDestroyClient() { + _readyState = ReadyState::CLOSED; + //on wsi destroyed + if (_wsi) { + RUN_IN_GAMETHREAD(if (_onclose) _onclose(_closeCode, _closeReason)); + RUN_IN_GAMETHREAD(if (_onend) _onend()); + uv_close(reinterpret_cast(&_async), nullptr); + } +} + +ccstd::vector WebSocketServerConnection::getProtocols() { + ccstd::vector ret; + if (_wsi) { + //TODO(): cause abort + //const struct lws_protocols* protos = lws_get_protocol(_wsi); + //while (protos && protos->name != nullptr) + //{ + // ret.emplace_back(protos->name); + // protos++; + //} + } + return ret; +} + +ccstd::unordered_map WebSocketServerConnection::getHeaders() { + if (!_wsi) return {}; + return _headers; +} + +int WebSocketServer::websocketServerCallback(struct lws *wsi, enum lws_callback_reasons reason, + void * /*user*/, void *in, size_t len) { + int ret = 0; + WebSocketServer *server = nullptr; + lws_context *ctx = nullptr; + + if (wsi) { + ctx = lws_get_context(wsi); + } + if (ctx) { + server = static_cast(lws_context_user(ctx)); + } + + if (!server) { + return 0; + } + + switch (reason) { + case LWS_CALLBACK_ESTABLISHED: + break; + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + break; + case LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH: + break; + case LWS_CALLBACK_CLIENT_ESTABLISHED: + break; + case LWS_CALLBACK_CLOSED: + break; + case LWS_CALLBACK_CLOSED_HTTP: + break; + case LWS_CALLBACK_RECEIVE: + server->onClientReceive(wsi, in, static_cast(len)); + break; + case LWS_CALLBACK_RECEIVE_PONG: + break; + case LWS_CALLBACK_CLIENT_RECEIVE: + server->onClientReceive(wsi, in, static_cast(len)); + break; + case LWS_CALLBACK_CLIENT_RECEIVE_PONG: + break; + case LWS_CALLBACK_CLIENT_WRITEABLE: + //ret = server->onClientWritable(wsi); + break; + case LWS_CALLBACK_SERVER_WRITEABLE: + ret = server->onServerWritable(wsi); + break; + case LWS_CALLBACK_HTTP: + break; + case LWS_CALLBACK_HTTP_BODY: + break; + case LWS_CALLBACK_HTTP_BODY_COMPLETION: + break; + case LWS_CALLBACK_HTTP_FILE_COMPLETION: + break; + case LWS_CALLBACK_HTTP_WRITEABLE: + break; + case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: + break; + case LWS_CALLBACK_FILTER_HTTP_CONNECTION: + break; + case LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED: + break; + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: + break; + case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: + break; + case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: + break; + case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION: + break; + case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: + break; + case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY: + break; + case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED: + break; + case LWS_CALLBACK_PROTOCOL_INIT: + break; + case LWS_CALLBACK_PROTOCOL_DESTROY: + break; + case LWS_CALLBACK_WSI_CREATE: + server->onCreateClient(wsi); + break; + case LWS_CALLBACK_WSI_DESTROY: + server->onDestroyClient(wsi); + break; + case LWS_CALLBACK_GET_THREAD_ID: + break; + case LWS_CALLBACK_ADD_POLL_FD: + break; + case LWS_CALLBACK_DEL_POLL_FD: + break; + case LWS_CALLBACK_CHANGE_MODE_POLL_FD: + break; + case LWS_CALLBACK_LOCK_POLL: + break; + case LWS_CALLBACK_UNLOCK_POLL: + break; + case LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY: + break; + case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: + server->onCloseClientInit(wsi, in, static_cast(len)); + break; + case LWS_CALLBACK_WS_EXT_DEFAULTS: + break; + case LWS_CALLBACK_CGI: + break; + case LWS_CALLBACK_CGI_TERMINATED: + break; + case LWS_CALLBACK_CGI_STDIN_DATA: + break; + case LWS_CALLBACK_CGI_STDIN_COMPLETED: + break; + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + break; + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + server->onCloseClient(wsi); + break; + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: + break; + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + break; + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: + break; + case LWS_CALLBACK_HTTP_BIND_PROTOCOL: + break; + case LWS_CALLBACK_HTTP_DROP_PROTOCOL: + break; + case LWS_CALLBACK_CHECK_ACCESS_RIGHTS: + break; + case LWS_CALLBACK_PROCESS_HTML: + break; + case LWS_CALLBACK_ADD_HEADERS: + server->onClientHTTP(wsi); + break; + case LWS_CALLBACK_SESSION_INFO: + break; + case LWS_CALLBACK_GS_EVENT: + break; + case LWS_CALLBACK_HTTP_PMO: + break; + case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: + break; + case LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION: + break; + case LWS_CALLBACK_RAW_RX: + break; + case LWS_CALLBACK_RAW_CLOSE: + break; + case LWS_CALLBACK_RAW_WRITEABLE: + break; + case LWS_CALLBACK_RAW_ADOPT: + break; + case LWS_CALLBACK_RAW_ADOPT_FILE: + break; + case LWS_CALLBACK_RAW_RX_FILE: + break; + case LWS_CALLBACK_RAW_WRITEABLE_FILE: + break; + case LWS_CALLBACK_RAW_CLOSE_FILE: + break; + case LWS_CALLBACK_SSL_INFO: + break; + case LWS_CALLBACK_CHILD_WRITE_VIA_PARENT: + break; + case LWS_CALLBACK_CHILD_CLOSING: + break; + case LWS_CALLBACK_CGI_PROCESS_ATTACH: + break; + case LWS_CALLBACK_USER: + break; + default: + break; + } + return ret; +} + +} // namespace network +} // namespace cc diff --git a/cocos/network/WebSocketServer.h b/cocos/network/WebSocketServer.h new file mode 100644 index 0000000..8bb6a8d --- /dev/null +++ b/cocos/network/WebSocketServer.h @@ -0,0 +1,297 @@ +/**************************************************************************** + Copyright (c) 2019-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +// clang-format on + +#include +#include +#include +#include +#include +#include +#include "base/Macros.h" +#include "base/std/container/list.h" +#include "base/std/container/string.h" +#include "base/std/container/vector.h" + +#include "base/std/container/unordered_map.h" +#include "uv.h" + +#if CC_PLATFORM == CC_PLATFORM_OHOS + #include "libwebsockets.h" +#else + #include "websockets/libwebsockets.h" +#endif + +namespace cc { +namespace network { + +class WebSocketServer; +class WebSocketServerConnection; + +/** + * receive/send data buffer with reserved bytes + */ +class DataFrame { +public: + explicit DataFrame(const ccstd::string &data); + + DataFrame(const void *data, int len, bool isBinary = true); + + virtual ~DataFrame() = default; + + void append(unsigned char *p, int len); + + int slice(unsigned char **p, int len); + + int consume(int len); + + int remain() const; + + inline bool isBinary() const { return _isBinary; } + inline bool isString() const { return !_isBinary; } + inline bool isFront() const { return _consumed == 0; } + + void setCallback(const std::function &callback) { + _callback = callback; + } + + void onFinish(const ccstd::string &message) { + if (_callback) { + _callback(message); + } + } + + inline int size() const { return static_cast(_underlyingData.size() - LWS_PRE); } + + ccstd::string toString(); + + unsigned char *getData() { return _underlyingData.data() + LWS_PRE; } + +private: + ccstd::vector _underlyingData; + int _consumed = 0; + bool _isBinary = false; + std::function _callback; +}; + +class CC_DLL WebSocketServerConnection { +public: + explicit WebSocketServerConnection(struct lws *wsi); + virtual ~WebSocketServerConnection(); + + enum ReadyState { + CONNECTING = 1, + OPEN = 2, + CLOSING = 3, + CLOSED = 4 + }; + + void sendTextAsync(const ccstd::string &, const std::function &callback); + + void sendBinaryAsync(const void *, size_t len, const std::function &callback); + + void closeAsync(int code, const ccstd::string &reason); + + /** stream is not implemented*/ + //bool beginBinary(); + + /** should implement in JS */ + // bool send(); + // bool sendPing(ccstd::string) + + //int getSocket(); + //std::shared_ptr& getServer(); + //ccstd::string& getPath(); + + inline int getReadyState() const { + return static_cast(_readyState); + } + + ccstd::unordered_map getHeaders(); + + ccstd::vector getProtocols(); + + inline void setOnClose(const std::function &cb) { + _onclose = cb; + } + + inline void setOnError(const std::function &cb) { + _onerror = cb; + } + + inline void setOnText(const std::function)> &cb) { + _ontext = cb; + } + + inline void setOnBinary(const std::function)> &cb) { + _onbinary = cb; + } + + inline void setOnMessage(const std::function)> &cb) { + _onmessage = cb; + } + + inline void setOnConnect(const std::function &cb) { + _onconnect = cb; + } + + inline void setOnEnd(const std::function &cb) { + _onend = cb; + } + + void onClientCloseInit(); + + inline void setData(void *data) { _data = data; } + inline void *getData() const { return _data; } + +private: + bool send(std::shared_ptr data); + bool close(int code, const ccstd::string &reason); + + inline void scheduleSend() { + if (_wsi) { + lws_callback_on_writable(_wsi); + } + } + + void onConnected(); + void onMessageReceive(void *in, int len); + int onDrainMessage(); + void onHTTP(); + void onClientCloseInit(int code, const ccstd::string &msg); + + void onDestroyClient(); + + struct lws *_wsi = nullptr; + ccstd::unordered_map _headers; + ccstd::list> _sendQueue; + std::shared_ptr _prevPkg; + bool _closed = false; + ccstd::string _closeReason = "close connection"; + int _closeCode = 1000; + std::atomic _readyState{ReadyState::CLOSED}; + + // Attention: do not reference **this** in callbacks + std::function _onclose; + std::function _onerror; + std::function)> _ontext; + std::function)> _onbinary; + std::function)> _onmessage; + std::function _onconnect; + std::function _onend; + uv_async_t _async = {}; + void *_data = nullptr; + + friend class WebSocketServer; +}; + +class CC_DLL WebSocketServer { +public: + WebSocketServer(); + virtual ~WebSocketServer(); + + static void listenAsync(std::shared_ptr &server, int port, const ccstd::string &host, const std::function &callback); + void closeAsync(const std::function &callback = nullptr); + + ccstd::vector> getConnections() const; + + void setOnListening(const std::function &cb) { + _onlistening = cb; + } + + void setOnError(const std::function &cb) { + _onerror = cb; + } + + void setOnClose(const std::function &cb) { + _onclose = cb; + } + + void setOnConnection(const std::function)> &cb) { + _onconnection = cb; + } + + inline void setOnEnd(const std::function &cb) { + _onend = cb; + } + + inline void setOnBegin(const std::function &cb) { + _onbegin = cb; + } + + inline void setData(void *d) { _data = d; } + inline void *getData() const { return _data; } + +protected: + static void listen(const std::shared_ptr &server, int port, const ccstd::string &host, const std::function &callback); + bool close(const std::function &callback = nullptr); + + void onCreateClient(struct lws *wsi); + void onDestroyClient(struct lws *wsi); + void onCloseClient(struct lws *wsi); + void onCloseClientInit(struct lws *wsi, void *in, int len); + void onClientReceive(struct lws *wsi, void *in, int len); + int onServerWritable(struct lws *wsi); + void onClientHTTP(struct lws *wsi); + +private: + std::shared_ptr findConnection(struct lws *wsi); + void destroyContext(); + + ccstd::string _host; + lws_context *_ctx = nullptr; + uv_async_t _async = {}; + + mutable std::mutex _connsMtx; + ccstd::unordered_map> _conns; + + // Attention: do not reference **this** in callbacks + std::function _onlistening; + std::function _onerror; + std::function _onclose; + std::function _onclose_cb; + std::function _onend; + std::function _onbegin; + std::function)> _onconnection; + + enum class ServerThreadState { + NOT_BOOTED, + ST_ERROR, + RUNNING, + STOPPED, + DESTROYED + }; + std::atomic _serverState{ServerThreadState::NOT_BOOTED}; + std::mutex _serverLock; + void *_data = nullptr; + +public: + static int websocketServerCallback(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len); +}; +} // namespace network +} // namespace cc diff --git a/cocos/physics/PhysicsSDK.h b/cocos/physics/PhysicsSDK.h new file mode 100644 index 0000000..c225526 --- /dev/null +++ b/cocos/physics/PhysicsSDK.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/sdk/Joint.h" +#include "physics/sdk/RigidBody.h" +#include "physics/sdk/Shape.h" +#include "physics/sdk/World.h" +#include "physics/sdk/CharacterController.h" diff --git a/cocos/physics/PhysicsSelector.h b/cocos/physics/PhysicsSelector.h new file mode 100644 index 0000000..572096f --- /dev/null +++ b/cocos/physics/PhysicsSelector.h @@ -0,0 +1,47 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#if defined(USE_PHYSICS_BULLET) + #include "physics/bullet/Bullet.h" +#else + #include "physics/physx/PhysX.h" + #define WrappedWorld PhysXWorld + #define WrappedRigidBody PhysXRigidBody + #define WrappedSphereShape PhysXSphere + #define WrappedBoxShape PhysXBox + #define WrappedPlaneShape PhysXPlane + #define WrappedCapsuleShape PhysXCapsule + #define WrappedTrimeshShape PhysXTrimesh + #define WrappedTerrainShape PhysXTerrain + #define WrappedConeShape PhysXCone + #define WrappedCylinderShape PhysXCylinder + #define WrappedRevoluteJoint PhysXRevolute + #define WrappedFixedJoint PhysXFixedJoint + #define WrappedSphericalJoint PhysXSpherical + #define WrappedGenericJoint PhysXGenericJoint + #define WrappedCapsuleCharacterController PhysXCapsuleCharacterController + #define WrappedBoxCharacterController PhysXBoxCharacterController +#endif diff --git a/cocos/physics/physx/PhysX.h b/cocos/physics/physx/PhysX.h new file mode 100644 index 0000000..718b70a --- /dev/null +++ b/cocos/physics/physx/PhysX.h @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/PhysXWorld.h" + +#include "physics/physx/PhysXRigidBody.h" + +#include "physics/physx/shapes/PhysXBox.h" +#include "physics/physx/shapes/PhysXCapsule.h" +#include "physics/physx/shapes/PhysXCone.h" +#include "physics/physx/shapes/PhysXCylinder.h" +#include "physics/physx/shapes/PhysXPlane.h" +#include "physics/physx/shapes/PhysXSphere.h" +#include "physics/physx/shapes/PhysXTerrain.h" +#include "physics/physx/shapes/PhysXTrimesh.h" + +#include "physics/physx/joints/PhysXFixedJoint.h" +#include "physics/physx/joints/PhysXGenericJoint.h" +#include "physics/physx/joints/PhysXRevolute.h" +#include "physics/physx/joints/PhysXSpherical.h" + +#include "physics/physx/character-controllers/PhysXBoxCharacterController.h" +#include "physics/physx/character-controllers/PhysXCapsuleCharacterController.h" diff --git a/cocos/physics/physx/PhysXEventManager.cpp b/cocos/physics/physx/PhysXEventManager.cpp new file mode 100644 index 0000000..9733819 --- /dev/null +++ b/cocos/physics/physx/PhysXEventManager.cpp @@ -0,0 +1,195 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/PhysXEventManager.h" +#include +#include "physics/physx/PhysXInc.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +void PhysXEventManager::SimulationEventCallback::onTrigger(physx::PxTriggerPair *pairs, physx::PxU32 count) { + for (physx::PxU32 i = 0; i < count; i++) { + const physx::PxTriggerPair &triggerPair = pairs[i]; + if (triggerPair.flags & (physx::PxTriggerPairFlag::eREMOVED_SHAPE_TRIGGER | physx::PxTriggerPairFlag::eREMOVED_SHAPE_OTHER)) { + continue; + } + + bool processed = false; + + //collider trigger event + if (!processed) { + const auto &selfIter = getPxShapeMap().find(reinterpret_cast(triggerPair.triggerShape)); + const auto &otherIter = getPxShapeMap().find(reinterpret_cast(triggerPair.otherShape)); + if (selfIter != getPxShapeMap().end() && otherIter != getPxShapeMap().end()) { + processed = true; + const auto &self = selfIter->second; + const auto &other = otherIter->second; + auto &pairs = mManager->getTriggerPairs(); + const auto &iter = std::find_if(pairs.begin(), pairs.end(), [self, other](std::shared_ptr &pair) { + return (pair->shapeA == self || pair->shapeA == other) && (pair->shapeB == self || pair->shapeB == other); + }); + if (triggerPair.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) { + if (iter == pairs.end()) pairs.push_back(std::shared_ptr(ccnew TriggerEventPair{self, other})); + } else if (triggerPair.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) { + if (iter != pairs.end()) iter->get()->state = ETouchState::EXIT; + } + } + } + + //cct trigger event + if (!processed) { + const auto &shapeIter = getPxShapeMap().find(reinterpret_cast(triggerPair.triggerShape)); + const auto &cctIter = getPxCCTMap().find(reinterpret_cast(triggerPair.otherShape)); + if (shapeIter != getPxShapeMap().end() && cctIter != getPxCCTMap().end()) { + processed = true; + const auto &shape = shapeIter->second; + const auto &cct = cctIter->second; + auto &pairs = mManager->getCCTTriggerPairs(); + const auto &iter = std::find_if(pairs.begin(), pairs.end(), [shape, cct](std::shared_ptr &pair) { + return (pair->shape == shape && pair->cct == cct); + }); + if (triggerPair.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) { + if (iter == pairs.end()) pairs.push_back(std::shared_ptr(ccnew CCTTriggerEventPair{cct, shape})); + } else if (triggerPair.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) { + if (iter != pairs.end()) iter->get()->state = ETouchState::EXIT; + } + } + } + + //cct trigger event + if (!processed) { + const auto &cctIter = getPxCCTMap().find(reinterpret_cast(triggerPair.triggerShape)); + const auto &shapeIter = getPxShapeMap().find(reinterpret_cast(triggerPair.otherShape)); + if (shapeIter != getPxShapeMap().end() && cctIter != getPxCCTMap().end()) { + processed = true; + const auto &shape = shapeIter->second; + const auto &cct = cctIter->second; + auto &pairs = mManager->getCCTTriggerPairs(); + const auto &iter = std::find_if(pairs.begin(), pairs.end(), [shape, cct](std::shared_ptr &pair) { + return (pair->shape == shape && pair->cct == cct); + }); + if (triggerPair.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) { + if (iter == pairs.end()) pairs.push_back(std::shared_ptr(ccnew CCTTriggerEventPair{cct, shape})); + } else if (triggerPair.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) { + if (iter != pairs.end()) iter->get()->state = ETouchState::EXIT; + } + } + } + } +} + +void PhysXEventManager::SimulationEventCallback::onContact(const physx::PxContactPairHeader & /*header*/, const physx::PxContactPair *pairs, physx::PxU32 count) { + for (physx::PxU32 i = 0; i < count; i++) { + const physx::PxContactPair &cp = pairs[i]; + if (cp.flags & (physx::PxContactPairFlag::eREMOVED_SHAPE_0 | physx::PxContactPairFlag::eREMOVED_SHAPE_1)) { + continue; + } + + const auto &selfIter = getPxShapeMap().find(reinterpret_cast(cp.shapes[0])); + const auto &otherIter = getPxShapeMap().find(reinterpret_cast(cp.shapes[1])); + if (selfIter == getPxShapeMap().end() || otherIter == getPxShapeMap().end()) { + continue; + } + + const auto &self = selfIter->second; + const auto &other = otherIter->second; + auto &pairs = mManager->getConatctPairs(); + auto iter = std::find_if(pairs.begin(), pairs.end(), [self, other](std::shared_ptr &pair) { + return (pair->shapeA == self || pair->shapeA == other) && (pair->shapeB == self || pair->shapeB == other); + }); + + if (iter == pairs.end()) { + pairs.push_back(std::shared_ptr(ccnew ContactEventPair{self, other})); + iter = pairs.end() - 1; + } + + if (cp.events & physx::PxPairFlag::eNOTIFY_TOUCH_PERSISTS) { + iter->get()->state = ETouchState::STAY; + } else if (cp.events & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) { + iter->get()->state = ETouchState::ENTER; + } else if (cp.events & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) { + iter->get()->state = ETouchState::EXIT; + } + + const physx::PxU8 &contactCount = cp.contactCount; + iter->get()->contacts.resize(contactCount); + if (contactCount > 0) { + cp.extractContacts(reinterpret_cast(&iter->get()->contacts[0]), contactCount); + } + } +} + +void PhysXEventManager::refreshPairs() { + for (auto iter = getTriggerPairs().begin(); iter != getTriggerPairs().end();) { + uintptr_t wrapperPtrShapeA = PhysXWorld::getInstance().getWrapperPtrWithObjectID(iter->get()->shapeA); + uintptr_t wrapperPtrShapeB = PhysXWorld::getInstance().getWrapperPtrWithObjectID(iter->get()->shapeB); + if (wrapperPtrShapeA == 0 || wrapperPtrShapeB == 0) { + iter = getTriggerPairs().erase(iter); + continue; + } + + const auto &selfIter = getPxShapeMap().find(reinterpret_cast(&(reinterpret_cast(wrapperPtrShapeA)->getShape()))); + const auto &otherIter = getPxShapeMap().find(reinterpret_cast(&(reinterpret_cast(wrapperPtrShapeB)->getShape()))); + if (selfIter == getPxShapeMap().end() || otherIter == getPxShapeMap().end()) { + iter = getTriggerPairs().erase(iter); + } else if (iter->get()->state == ETouchState::EXIT) { + iter = getTriggerPairs().erase(iter); + } else { + iter->get()->state = ETouchState::STAY; + iter++; + } + } + + for (auto iter = getCCTTriggerPairs().begin(); iter != getCCTTriggerPairs().end();) { + uintptr_t wrapperPtrCCT = PhysXWorld::getInstance().getWrapperPtrWithObjectID(iter->get()->cct); + uintptr_t wrapperPtrShape = PhysXWorld::getInstance().getWrapperPtrWithObjectID(iter->get()->shape); + if (wrapperPtrCCT == 0 || wrapperPtrShape == 0) { + iter = getCCTTriggerPairs().erase(iter); + continue; + } + + const auto& cctIter = getPxCCTMap().find(reinterpret_cast(&(reinterpret_cast(wrapperPtrCCT)->getCCT()))); + const auto& shapeIter = getPxShapeMap().find(reinterpret_cast(&(reinterpret_cast(wrapperPtrShape)->getShape()))); + if (cctIter == getPxCCTMap().end() || shapeIter == getPxShapeMap().end()) { + iter = getCCTTriggerPairs().erase(iter); + } + else if (iter->get()->state == ETouchState::EXIT) { + iter = getCCTTriggerPairs().erase(iter); + } + else { + iter->get()->state = ETouchState::STAY; + iter++; + } + } + + getConatctPairs().clear(); + getCCTShapePairs().clear(); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXEventManager.h b/cocos/physics/physx/PhysXEventManager.h new file mode 100644 index 0000000..6cc2f0d --- /dev/null +++ b/cocos/physics/physx/PhysXEventManager.h @@ -0,0 +1,76 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" +#include "base/memory/Memory.h" +#include "base/std/container/vector.h" +#include "physics/physx/PhysXInc.h" +#include "physics/spec/IWorld.h" + +namespace cc { +namespace physics { + +class PhysXEventManager final { +public: + PhysXEventManager() { + _mCallback = ccnew SimulationEventCallback(this); + } + + ~PhysXEventManager() { + delete _mCallback; + } + + struct SimulationEventCallback : public physx::PxSimulationEventCallback { + void onConstraintBreak(physx::PxConstraintInfo * /*constraints*/, physx::PxU32 /*count*/) override{}; + void onWake(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override{}; + void onSleep(physx::PxActor ** /*actors*/, physx::PxU32 /*count*/) override{}; + void onTrigger(physx::PxTriggerPair * /*pairs*/, physx::PxU32 /*count*/) override; + void onContact(const physx::PxContactPairHeader & /*pairHeader*/, const physx::PxContactPair * /*pairs*/, physx::PxU32 /*nbPairs*/) override; + void onAdvance(const physx::PxRigidBody *const * /*bodyBuffer*/, const physx::PxTransform * /*poseBuffer*/, const physx::PxU32 /*count*/) override{}; + PhysXEventManager *mManager; + + public: + explicit SimulationEventCallback(PhysXEventManager *m) : mManager(m) {} + }; + + inline SimulationEventCallback &getEventCallback() { return *_mCallback; } + inline ccstd::vector> &getTriggerPairs() { return _mTriggerPairs; } + inline ccstd::vector>& getConatctPairs() { return _mConatctPairs; } + inline ccstd::vector>& getCCTShapePairs() { return _mCCTShapePairs; } + inline ccstd::vector> &getCCTTriggerPairs() { return _mCCTTriggerPairs; } + void refreshPairs(); + +private: + ccstd::vector> _mTriggerPairs; + ccstd::vector> _mConatctPairs; + ccstd::vector> _mCCTShapePairs; + ccstd::vector> _mCCTTriggerPairs; + SimulationEventCallback *_mCallback; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXFilterShader.cpp b/cocos/physics/physx/PhysXFilterShader.cpp new file mode 100644 index 0000000..b0e0bac --- /dev/null +++ b/cocos/physics/physx/PhysXFilterShader.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/PhysXFilterShader.h" +#include "physics/physx/PhysXInc.h" + +namespace cc { +namespace physics { + +physx::PxFilterFlags simpleFilterShader( + physx::PxFilterObjectAttributes attributes0, physx::PxFilterData fd0, + physx::PxFilterObjectAttributes attributes1, physx::PxFilterData fd1, + physx::PxPairFlags &pairFlags, const void *constantBlock, physx::PxU32 constantBlockSize) { + PX_UNUSED(constantBlock); + PX_UNUSED(constantBlockSize); + // group mask filter + if (!(fd0.word0 & fd1.word1) || !(fd0.word1 & fd1.word0)) { + return physx::PxFilterFlag::eSUPPRESS; + } + + if (physx::PxFilterObjectIsTrigger(attributes0) || physx::PxFilterObjectIsTrigger(attributes1)) { + pairFlags |= physx::PxPairFlag::eTRIGGER_DEFAULT | physx::PxPairFlag::eNOTIFY_TOUCH_CCD; + return physx::PxFilterFlag::eDEFAULT; + } + if (!physx::PxFilterObjectIsKinematic(attributes0) || !physx::PxFilterObjectIsKinematic(attributes1)) { + pairFlags |= physx::PxPairFlag::eSOLVE_CONTACT; + } + pairFlags |= physx::PxPairFlag::eDETECT_DISCRETE_CONTACT | + physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST | physx::PxPairFlag::eNOTIFY_TOUCH_PERSISTS | + physx::PxPairFlag::eDETECT_CCD_CONTACT | physx::PxPairFlag::eNOTIFY_CONTACT_POINTS; + return physx::PxFilterFlag::eDEFAULT; +} + +physx::PxFilterFlags advanceFilterShader( + physx::PxFilterObjectAttributes attributes0, physx::PxFilterData fd0, + physx::PxFilterObjectAttributes attributes1, physx::PxFilterData fd1, + physx::PxPairFlags &pairFlags, const void *constantBlock, physx::PxU32 constantBlockSize) { + PX_UNUSED(constantBlock); + PX_UNUSED(constantBlockSize); + // group mask filter + if (!(fd0.word0 & fd1.word1) || !(fd0.word1 & fd1.word0)) { + return physx::PxFilterFlag::eSUPPRESS; + } + + pairFlags = physx::PxPairFlags(0); + + // trigger filter + if (physx::PxFilterObjectIsTrigger(attributes0) || physx::PxFilterObjectIsTrigger(attributes1)) { + pairFlags |= physx::PxPairFlag::eDETECT_DISCRETE_CONTACT; + + // need trigger event? + const physx::PxU16 needTriggerEvent = (fd0.word3 & DETECT_TRIGGER_EVENT) | (fd1.word3 & DETECT_TRIGGER_EVENT); + if (needTriggerEvent) { + pairFlags |= physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST; + return physx::PxFilterFlag::eDEFAULT; + } + return physx::PxFilterFlag::eSUPPRESS; + } + + // need detect ccd contact? + const physx::PxU16 needDetectCCD = (fd0.word3 & DETECT_CONTACT_CCD) | (fd1.word3 & DETECT_CONTACT_CCD); + if (needDetectCCD) pairFlags |= physx::PxPairFlag::eDETECT_CCD_CONTACT; + + if (!physx::PxFilterObjectIsKinematic(attributes0) || !physx::PxFilterObjectIsKinematic(attributes1)) { + pairFlags |= physx::PxPairFlag::eSOLVE_CONTACT; + } + + // simple collision process + pairFlags |= physx::PxPairFlag::eDETECT_DISCRETE_CONTACT; + + // need contact event? + const physx::PxU16 needContactEvent = (fd0.word3 & DETECT_CONTACT_EVENT) | (fd1.word3 & DETECT_CONTACT_EVENT); + if (needContactEvent) pairFlags |= physx::PxPairFlag::eNOTIFY_TOUCH_FOUND | physx::PxPairFlag::eNOTIFY_TOUCH_LOST | physx::PxPairFlag::eNOTIFY_TOUCH_PERSISTS; + + // need contact point? + const physx::PxU16 needContactPoint = (fd0.word3 & DETECT_CONTACT_POINT) | (fd1.word3 & DETECT_CONTACT_POINT); + if (needContactPoint) pairFlags |= physx::PxPairFlag::eNOTIFY_CONTACT_POINTS; + + return physx::PxFilterFlag::eDEFAULT; +} + +physx::PxQueryHitType::Enum QueryFilterShader::preFilter(const physx::PxFilterData &filterData, const physx::PxShape *shape, + const physx::PxRigidActor *actor, physx::PxHitFlags &queryFlags) { + PX_UNUSED(actor); + PX_UNUSED(queryFlags); + if ((filterData.word3 & QUERY_CHECK_TRIGGER) && shape->getFlags().isSet(physx::PxShapeFlag::eTRIGGER_SHAPE)) { + return physx::PxQueryHitType::eNONE; + } + return filterData.word3 & QUERY_SINGLE_HIT ? physx::PxQueryHitType::eBLOCK : physx::PxQueryHitType::eTOUCH; +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXFilterShader.h b/cocos/physics/physx/PhysXFilterShader.h new file mode 100644 index 0000000..187f777 --- /dev/null +++ b/cocos/physics/physx/PhysXFilterShader.h @@ -0,0 +1,62 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/PhysXInc.h" + +namespace cc { +namespace physics { + +constexpr physx::PxU32 QUERY_FILTER = 1 << 0; +constexpr physx::PxU32 QUERY_CHECK_TRIGGER = 1 << 1; +constexpr physx::PxU32 QUERY_SINGLE_HIT = 1 << 2; +constexpr physx::PxU32 DETECT_TRIGGER_EVENT = 1 << 3; +constexpr physx::PxU32 DETECT_CONTACT_EVENT = 1 << 4; +constexpr physx::PxU32 DETECT_CONTACT_POINT = 1 << 5; +constexpr physx::PxU32 DETECT_CONTACT_CCD = 1 << 6; + +physx::PxFilterFlags simpleFilterShader( + physx::PxFilterObjectAttributes attributes0, physx::PxFilterData fd0, + physx::PxFilterObjectAttributes attributes1, physx::PxFilterData fd1, + physx::PxPairFlags &pairFlags, const void *constantBlock, physx::PxU32 constantBlockSize); + +physx::PxFilterFlags advanceFilterShader( + physx::PxFilterObjectAttributes attributes0, physx::PxFilterData fd0, + physx::PxFilterObjectAttributes attributes1, physx::PxFilterData fd1, + physx::PxPairFlags &pairFlags, const void *constantBlock, physx::PxU32 constantBlockSize); + +class QueryFilterShader : public physx::PxSceneQueryFilterCallback { +public: + physx::PxQueryHitType::Enum preFilter(const physx::PxFilterData &filterData, const physx::PxShape *shape, + const physx::PxRigidActor *actor, physx::PxHitFlags &queryFlags) override; + physx::PxQueryHitType::Enum postFilter(const physx::PxFilterData &filterData, const physx::PxQueryHit &hit) override { + PX_UNUSED(filterData); + PX_UNUSED(hit); + return physx::PxQueryHitType::eNONE; + }; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXInc.h b/cocos/physics/physx/PhysXInc.h new file mode 100644 index 0000000..8bab6d9 --- /dev/null +++ b/cocos/physics/physx/PhysXInc.h @@ -0,0 +1,45 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#if defined(_MSC_VER) + #pragma warning(disable : 4250) + #pragma warning(disable : 4996) +#endif + +#include "base/Macros.h" + +#if !defined(NDEBUG) ^ defined(_DEBUG) + #ifdef CC_DEBUG + #define _DEBUG + #else + #define NDEBUG + #endif +#endif + +#include +#include +#include +#include diff --git a/cocos/physics/physx/PhysXRigidBody.cpp b/cocos/physics/physx/PhysXRigidBody.cpp new file mode 100644 index 0000000..0fe35d4 --- /dev/null +++ b/cocos/physics/physx/PhysXRigidBody.cpp @@ -0,0 +1,269 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/PhysXRigidBody.h" +#include "physics/physx/PhysXInc.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" + +using physx::PxActorFlag; +using physx::PxForceMode; +using physx::PxReal; +using physx::PxTransform; +using physx::PxVec3; + +namespace cc { +namespace physics { + +PhysXRigidBody::PhysXRigidBody() { + _mObjectID = PhysXWorld::getInstance().addWrapperObject(reinterpret_cast(this)); +} + +void PhysXRigidBody::initialize(Node *node, ERigidBodyType t, uint32_t g) { + _mGroup = g; + PhysXWorld &ins = PhysXWorld::getInstance(); + _mSharedBody = ins.getSharedBody(node, this); + getSharedBody().reference(true); + getSharedBody().setType(t); +} + +void PhysXRigidBody::onEnable() { + _mEnabled = true; + getSharedBody().enabled(true); +} + +void PhysXRigidBody::onDisable() { + _mEnabled = false; + getSharedBody().enabled(false); +} + +void PhysXRigidBody::onDestroy() { + getSharedBody().reference(false); + PhysXWorld::getInstance().removeWrapperObject(_mObjectID); +} + +bool PhysXRigidBody::isAwake() { + if (!getSharedBody().isInWorld() || getSharedBody().isStatic()) return false; + return !getSharedBody().getImpl().rigidDynamic->isSleeping(); +} + +bool PhysXRigidBody::isSleepy() { + return false; +} + +bool PhysXRigidBody::isSleeping() { + if (!getSharedBody().isInWorld() || getSharedBody().isStatic()) return true; + return getSharedBody().getImpl().rigidDynamic->isSleeping(); +} + +void PhysXRigidBody::setType(ERigidBodyType v) { + getSharedBody().setType(v); +} + +void PhysXRigidBody::setMass(float v) { + getSharedBody().setMass(v); +} + +void PhysXRigidBody::setLinearDamping(float v) { + if (getSharedBody().isStatic()) return; + getSharedBody().getImpl().rigidDynamic->setLinearDamping(v); +} + +void PhysXRigidBody::setAngularDamping(float v) { + if (getSharedBody().isStatic()) return; + getSharedBody().getImpl().rigidDynamic->setAngularDamping(v); +} + +void PhysXRigidBody::useGravity(bool v) { + if (getSharedBody().isStatic()) return; + getSharedBody().getImpl().rigidDynamic->setActorFlag(PxActorFlag::eDISABLE_GRAVITY, !v); +} + +void PhysXRigidBody::useCCD(bool v) { + if (getSharedBody().isStatic()) return; + getSharedBody().getImpl().rigidDynamic->setRigidBodyFlag(physx::PxRigidBodyFlag::eENABLE_CCD, v); +} + +void PhysXRigidBody::setLinearFactor(float x, float y, float z) { + if (getSharedBody().isStatic()) return; + getSharedBody().getImpl().rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_X, x == 0.); + getSharedBody().getImpl().rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Y, y == 0.); + getSharedBody().getImpl().rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Z, z == 0.); +} + +void PhysXRigidBody::setAngularFactor(float x, float y, float z) { + if (getSharedBody().isStatic()) return; + getSharedBody().getImpl().rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_X, x == 0.); + getSharedBody().getImpl().rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y, y == 0.); + getSharedBody().getImpl().rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Z, z == 0.); +} + +void PhysXRigidBody::setAllowSleep(bool v) { + if (!getSharedBody().isDynamic()) return; + PxReal wc = v ? 0.0001F : FLT_MAX; + getSharedBody().getImpl().rigidDynamic->setWakeCounter(wc); +} + +void PhysXRigidBody::wakeUp() { + if (!getSharedBody().isInWorld() || getSharedBody().isStatic()) return; + getSharedBody().getImpl().rigidDynamic->wakeUp(); +} + +void PhysXRigidBody::sleep() { + if (!getSharedBody().isInWorld() || getSharedBody().isStatic()) return; + getSharedBody().getImpl().rigidDynamic->putToSleep(); +} + +void PhysXRigidBody::clearState() { + if (!getSharedBody().isInWorld()) return; + clearForces(); + clearVelocity(); +} + +void PhysXRigidBody::clearForces() { + if (!getSharedBody().isInWorld()) return; + getSharedBody().clearForces(); +} + +void PhysXRigidBody::clearVelocity() { + getSharedBody().clearVelocity(); +} + +void PhysXRigidBody::setSleepThreshold(float v) { + if (getSharedBody().isStatic()) return; + //(approximated) mass-normalized kinetic energy + float ke = 0.5F * v * v; + getSharedBody().getImpl().rigidDynamic->setSleepThreshold(ke); +} + +float PhysXRigidBody::getSleepThreshold() { + float ke = getSharedBody().getImpl().rigidDynamic->getSleepThreshold(); + float v = sqrtf(2.F * ke); + return v; +} + +cc::Vec3 PhysXRigidBody::getLinearVelocity() { + if (getSharedBody().isStatic()) return cc::Vec3::ZERO; + cc::Vec3 cv; + pxSetVec3Ext(cv, getSharedBody().getImpl().rigidDynamic->getLinearVelocity()); + return cv; +} + +void PhysXRigidBody::setLinearVelocity(float x, float y, float z) { + if (getSharedBody().isStatic()) return; + getSharedBody().getImpl().rigidDynamic->setLinearVelocity(PxVec3{x, y, z}); +} + +cc::Vec3 PhysXRigidBody::getAngularVelocity() { + if (getSharedBody().isStatic()) return cc::Vec3::ZERO; + cc::Vec3 cv; + pxSetVec3Ext(cv, getSharedBody().getImpl().rigidDynamic->getAngularVelocity()); + return cv; +} + +void PhysXRigidBody::setAngularVelocity(float x, float y, float z) { + if (getSharedBody().isStatic()) return; + getSharedBody().getImpl().rigidDynamic->setAngularVelocity(PxVec3{x, y, z}); +} + +void PhysXRigidBody::applyForce(float x, float y, float z, float rx, float ry, float rz) { + if (!getSharedBody().isInWorld() || getSharedBody().isStaticOrKinematic()) return; + const PxVec3 force{x, y, z}; + if (force.isZero()) return; + auto *body = getSharedBody().getImpl().rigidDynamic; + body->addForce(force, PxForceMode::eFORCE, true); + const PxVec3 torque = (PxVec3{rx, ry, rz}).cross(force); + if (!torque.isZero()) body->addTorque(torque, PxForceMode::eFORCE, true); +} + +void PhysXRigidBody::applyLocalForce(float x, float y, float z, float rx, float ry, float rz) { + if (!getSharedBody().isInWorld() || getSharedBody().isStaticOrKinematic()) return; + const PxVec3 force{x, y, z}; + if (force.isZero()) return; + auto *body = getSharedBody().getImpl().rigidDynamic; + const PxTransform bodyPose = body->getGlobalPose(); + const PxVec3 worldForce = bodyPose.rotate(force); + const PxVec3 worldPos = bodyPose.rotate(PxVec3{rx, ry, rz}); + body->addForce(worldForce, PxForceMode::eFORCE, true); + const PxVec3 torque = worldPos.cross(worldForce); + if (!torque.isZero()) body->addTorque(torque, PxForceMode::eFORCE, true); +} + +void PhysXRigidBody::applyImpulse(float x, float y, float z, float rx, float ry, float rz) { + if (!getSharedBody().isInWorld() || getSharedBody().isStaticOrKinematic()) return; + const PxVec3 impulse{x, y, z}; + if (impulse.isZero()) return; + auto *body = getSharedBody().getImpl().rigidDynamic; + const PxVec3 torque = (PxVec3{rx, ry, rz}).cross(impulse); + body->addForce(impulse, PxForceMode::eIMPULSE, true); + if (!torque.isZero()) body->addTorque(torque, PxForceMode::eIMPULSE, true); +} + +void PhysXRigidBody::applyLocalImpulse(float x, float y, float z, float rx, float ry, float rz) { + if (!getSharedBody().isInWorld() || getSharedBody().isStaticOrKinematic()) return; + const PxVec3 impulse{x, y, z}; + if (impulse.isZero()) return; + auto *body = getSharedBody().getImpl().rigidDynamic; + const PxTransform bodyPose = body->getGlobalPose(); + const PxVec3 worldImpulse = bodyPose.rotate(impulse); + const PxVec3 worldPos = bodyPose.rotate(PxVec3{rx, ry, rz}); + body->addForce(worldImpulse, PxForceMode::eIMPULSE, true); + const PxVec3 torque = worldPos.cross(worldImpulse); + if (!torque.isZero()) body->addTorque(torque, PxForceMode::eIMPULSE, true); +} + +void PhysXRigidBody::applyTorque(float x, float y, float z) { + if (!getSharedBody().isInWorld() || getSharedBody().isStaticOrKinematic()) return; + PxVec3 torque{x, y, z}; + if (torque.isZero()) return; + getSharedBody().getImpl().rigidDynamic->addTorque(torque, PxForceMode::eFORCE, true); +} + +void PhysXRigidBody::applyLocalTorque(float x, float y, float z) { + if (!getSharedBody().isInWorld() || getSharedBody().isStaticOrKinematic()) return; + PxVec3 torque{x, y, z}; + if (torque.isZero()) return; + auto *body = getSharedBody().getImpl().rigidDynamic; + const PxTransform bodyPose = body->getGlobalPose(); + body->addTorque(bodyPose.rotate(PxVec3{x, y, z}), PxForceMode::eFORCE, true); +} + +uint32_t PhysXRigidBody::getGroup() { + return getSharedBody().getGroup(); +} + +void PhysXRigidBody::setGroup(uint32_t g) { + getSharedBody().setGroup(g); +} + +uint32_t PhysXRigidBody::getMask() { + return getSharedBody().getMask(); +} + +void PhysXRigidBody::setMask(uint32_t m) { + getSharedBody().setMask(m); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXRigidBody.h b/cocos/physics/physx/PhysXRigidBody.h new file mode 100644 index 0000000..1acd34d --- /dev/null +++ b/cocos/physics/physx/PhysXRigidBody.h @@ -0,0 +1,91 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "physics/physx//PhysXInc.h" +#include "physics/physx/PhysXSharedBody.h" +#include "physics/spec/IBody.h" + +namespace cc { +namespace physics { + +class PhysXRigidBody final : public IRigidBody { +public: + PhysXRigidBody(); + ~PhysXRigidBody() override = default; + inline bool isEnabled() const { return _mEnabled; } + inline const PhysXSharedBody &getSharedBody() const { return *_mSharedBody; } + inline PhysXSharedBody &getSharedBody() { return *_mSharedBody; } + void initialize(Node *node, ERigidBodyType t, uint32_t g) override; + void onEnable() override; + void onDisable() override; + void onDestroy() override; + bool isAwake() override; + bool isSleepy() override; + bool isSleeping() override; + void setType(ERigidBodyType v) override; + void setMass(float v) override; + void setLinearDamping(float v) override; + void setAngularDamping(float v) override; + void useGravity(bool v) override; + void useCCD(bool v) override; + void setLinearFactor(float x, float y, float z) override; + void setAngularFactor(float x, float y, float z) override; + void setAllowSleep(bool v) override; + void wakeUp() override; + void sleep() override; + void clearState() override; + void clearForces() override; + void clearVelocity() override; + void setSleepThreshold(float v) override; + float getSleepThreshold() override; + cc::Vec3 getLinearVelocity() override; + void setLinearVelocity(float x, float y, float z) override; + cc::Vec3 getAngularVelocity() override; + void setAngularVelocity(float x, float y, float z) override; + void applyForce(float x, float y, float z, float rx, float ry, float rz) override; + void applyLocalForce(float x, float y, float z, float rx, float ry, float rz) override; + void applyImpulse(float x, float y, float z, float rx, float ry, float rz) override; + void applyLocalImpulse(float x, float y, float z, float rx, float ry, float rz) override; + void applyTorque(float x, float y, float z) override; + void applyLocalTorque(float x, float y, float z) override; + uint32_t getGroup() override; + uint32_t getMask() override; + void setGroup(uint32_t g) override; + void setMask(uint32_t m) override; + inline uint32_t getInitialGroup() const { return _mGroup; } + uint32_t getObjectID() const override { return _mObjectID; }; + +protected: + // physx::PhysXWorld* mWrappedWorld; + PhysXSharedBody *_mSharedBody{nullptr}; + uint32_t _mGroup{1}; + bool _mEnabled{false}; + uint32_t _mObjectID{0}; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXSharedBody.cpp b/cocos/physics/physx/PhysXSharedBody.cpp new file mode 100644 index 0000000..5209416 --- /dev/null +++ b/cocos/physics/physx/PhysXSharedBody.cpp @@ -0,0 +1,354 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/PhysXSharedBody.h" +#include +#include "base/memory/Memory.h" +#include "physics/physx/PhysXInc.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "physics/physx/joints/PhysXJoint.h" +#include "physics/physx/shapes/PhysXShape.h" + +using physx::PxFilterData; +using physx::PxForceMode; +using physx::PxIdentity; +using physx::PxJointActorIndex; +using physx::PxPhysics; +using physx::PxQuat; +using physx::PxRigidActor; +using physx::PxRigidBodyExt; +using physx::PxRigidBodyFlag; +using physx::PxTransform; +using physx::PxVec3; + +namespace cc { +namespace physics { +ccstd::unordered_map PhysXSharedBody::sharedBodesMap; + +static int idCounter = 0; +PhysXSharedBody::PhysXSharedBody( + Node *node, + PhysXWorld *const world, + PhysXRigidBody *const body) : _mID(idCounter++), + _mRef(0), + _mType(ERigidBodyType::STATIC), + _mIsStatic(true), + _mIndex(-1), + _mFilterData(1, 1, 1, 0), + _mStaticActor(nullptr), + _mDynamicActor(nullptr), + _mWrappedWorld(world), + _mWrappedBody(body) { + _mImpl.ptr = 0; + _mNode = node; +}; + +PhysXSharedBody *PhysXSharedBody::getSharedBody(const Node *node, PhysXWorld *const world, PhysXRigidBody *const body) { + auto iter = sharedBodesMap.find(const_cast(node)); + PhysXSharedBody *newSB; + if (iter != sharedBodesMap.end()) { + newSB = iter->second; + } else { + newSB = ccnew PhysXSharedBody(const_cast(node), world, body); + newSB->_mFilterData.word0 = 1; + newSB->_mFilterData.word1 = world->getMaskByIndex(0); + sharedBodesMap.insert(std::pair(const_cast(node), newSB)); + } + if (body != nullptr) { + auto g = body->getInitialGroup(); + newSB->_mFilterData.word0 = g; + newSB->_mFilterData.word1 = world->getMaskByIndex(static_cast(log2(g))); + } + return newSB; +} + +PhysXSharedBody::~PhysXSharedBody() { + sharedBodesMap.erase(_mNode); + if (_mStaticActor != nullptr) PX_RELEASE(_mStaticActor); + if (_mDynamicActor != nullptr) PX_RELEASE(_mDynamicActor); +} + +PhysXSharedBody::UActor PhysXSharedBody::getImpl() { + initActor(); + _mImpl.ptr = isStatic() ? reinterpret_cast(_mStaticActor) : reinterpret_cast(_mDynamicActor); + return _mImpl; +} + +void PhysXSharedBody::setType(ERigidBodyType v) { + if (_mType == v) return; + _mType = v; + initActor(); + if (isStatic()) { + _mImpl.ptr = reinterpret_cast(_mStaticActor); + } else { + _mImpl.ptr = reinterpret_cast(_mDynamicActor); + _mImpl.rigidDynamic->setRigidBodyFlag(physx::PxRigidBodyFlag::eKINEMATIC, _mType == ERigidBodyType::KINEMATIC); + } +} + +void PhysXSharedBody::reference(bool v) { + v ? _mRef++ : _mRef--; + if (_mRef == 0) delete this; +} + +void PhysXSharedBody::enabled(bool v) { + if (v) { + if (_mIndex < 0) { + _mIndex = 1; + _mWrappedWorld->addActor(*this); + } + } else { + auto *wb = _mWrappedBody; + auto ws = _mWrappedShapes; + auto isRemove = ws.empty() && (wb == nullptr || (wb != nullptr && !wb->isEnabled())); + if (isRemove) { + _mIndex = -1; + if (!isStaticOrKinematic()) { + clearVelocity(); + } + _mWrappedWorld->removeActor(*this); + } + } +} + +void PhysXSharedBody::initActor() { + const bool temp = _mIsStatic; + if (isStatic()) { + _mIsStatic = true; + initStaticActor(); + } else { + _mIsStatic = false; + initDynamicActor(); + } + if (temp != _mIsStatic) switchActor(temp); +} + +void PhysXSharedBody::switchActor(const bool isStaticBefore) { + if (_mStaticActor == nullptr || _mDynamicActor == nullptr) return; + PxRigidActor &a0 = isStaticBefore ? *reinterpret_cast(_mStaticActor) : *reinterpret_cast(_mDynamicActor); + PxRigidActor &a1 = !isStaticBefore ? *reinterpret_cast(_mStaticActor) : *reinterpret_cast(_mDynamicActor); + if (_mIndex >= 0) { + _mWrappedWorld->getScene().removeActor(a0, false); + _mWrappedWorld->getScene().addActor(a1); + } + for (auto const &ws : _mWrappedShapes) { + a0.detachShape(ws->getShape(), false); + a1.attachShape(ws->getShape()); + } + if (isStaticBefore) { + if (isDynamic()) _mDynamicActor->wakeUp(); + _mDynamicActor->setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, isKinematic()); + PxRigidBodyExt::setMassAndUpdateInertia(*_mDynamicActor, _mMass); + } +} + +void PhysXSharedBody::initStaticActor() { + if (_mStaticActor == nullptr) { + PxTransform transform{PxIdentity}; + getNode()->updateWorldTransform(); + pxSetVec3Ext(transform.p, getNode()->getWorldPosition()); + pxSetQuatExt(transform.q, getNode()->getWorldRotation()); + if (!transform.p.isFinite()) transform.p = PxVec3{PxIdentity}; + if (!transform.q.isUnit()) transform.q = PxQuat{PxIdentity}; + PxPhysics &phy = PxGetPhysics(); + _mStaticActor = phy.createRigidStatic(transform); + } +} + +void PhysXSharedBody::initDynamicActor() { + if (_mDynamicActor == nullptr) { + PxTransform transform{PxIdentity}; + getNode()->updateWorldTransform(); + pxSetVec3Ext(transform.p, getNode()->getWorldPosition()); + pxSetQuatExt(transform.q, getNode()->getWorldRotation()); + if (!transform.p.isFinite()) transform.p = PxVec3{PxIdentity}; + if (!transform.q.isUnit()) transform.q = PxQuat{PxIdentity}; + PxPhysics &phy = PxGetPhysics(); + _mDynamicActor = phy.createRigidDynamic(transform); + _mDynamicActor->setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, isKinematic()); + } +} + +void PhysXSharedBody::syncScale() { + for (auto const &sb : _mWrappedShapes) { + sb->updateScale(); + } + + for (auto const &sb : _mWrappedJoints0) { + sb->updateScale0(); + } + + for (auto const &sb : _mWrappedJoints1) { + sb->updateScale1(); + } +} + +void PhysXSharedBody::syncSceneToPhysics() { + uint32_t getChangedFlags = getNode()->getChangedFlags(); + if (getChangedFlags) { + if (getChangedFlags & static_cast(TransformBit::SCALE)) syncScale(); + auto wp = getImpl().rigidActor->getGlobalPose(); + if (getChangedFlags & static_cast(TransformBit::POSITION)) { + getNode()->updateWorldTransform(); + pxSetVec3Ext(wp.p, getNode()->getWorldPosition()); + } + if (getChangedFlags & static_cast(TransformBit::ROTATION)) { + getNode()->updateWorldTransform(); + pxSetQuatExt(wp.q, getNode()->getWorldRotation()); + } + + if (isKinematic()) { + getImpl().rigidDynamic->setKinematicTarget(wp); + } else { + getImpl().rigidActor->setGlobalPose(wp, true); + } + } +} + +void PhysXSharedBody::syncSceneWithCheck() { + if (getNode()->getChangedFlags() & static_cast(TransformBit::SCALE)) syncScale(); + auto wp = getImpl().rigidActor->getGlobalPose(); + bool needUpdate = false; + getNode()->updateWorldTransform(); + if (wp.p != getNode()->getWorldPosition()) { + pxSetVec3Ext(wp.p, getNode()->getWorldPosition()); + needUpdate = true; + } + const auto nr = getNode()->getWorldRotation(); + if (wp.q.x != nr.x && wp.q.y != nr.y && wp.q.z != nr.z) { + pxSetQuatExt(wp.q, getNode()->getWorldRotation()); + needUpdate = true; + } + if (needUpdate) { + getImpl().rigidActor->setGlobalPose(wp, true); + } +} + +void PhysXSharedBody::syncPhysicsToScene() { + if (isStaticOrKinematic()) return; + if (_mDynamicActor->isSleeping()) return; + const PxTransform &wp = getImpl().rigidActor->getGlobalPose(); + getNode()->setWorldPosition(wp.p.x, wp.p.y, wp.p.z); + getNode()->setWorldRotation(wp.q.x, wp.q.y, wp.q.z, wp.q.w); + getNode()->setChangedFlags(getNode()->getChangedFlags() | static_cast(TransformBit::POSITION) | static_cast(TransformBit::ROTATION)); +} + +void PhysXSharedBody::addShape(const PhysXShape &shape) { + auto beg = _mWrappedShapes.begin(); + auto end = _mWrappedShapes.end(); + auto iter = find(beg, end, &shape); + if (iter == end) { + shape.getShape().setSimulationFilterData(_mFilterData); + shape.getShape().setQueryFilterData(_mFilterData); + getImpl().rigidActor->attachShape(shape.getShape()); + _mWrappedShapes.push_back(&const_cast(shape)); + if (!shape.isTrigger()) { + if (isDynamic()) PxRigidBodyExt::setMassAndUpdateInertia(*getImpl().rigidDynamic, _mMass); + } + } +} + +void PhysXSharedBody::removeShape(const PhysXShape &shape) { + auto beg = _mWrappedShapes.begin(); + auto end = _mWrappedShapes.end(); + auto iter = find(beg, end, &shape); + if (iter != end) { + _mWrappedShapes.erase(iter); + getImpl().rigidActor->detachShape(shape.getShape(), true); + if (!const_cast(shape).isTrigger()) { + if (isDynamic()) PxRigidBodyExt::setMassAndUpdateInertia(*getImpl().rigidDynamic, _mMass); + } + } +} + +void PhysXSharedBody::addJoint(const PhysXJoint &joint, const PxJointActorIndex::Enum index) { + if (index == PxJointActorIndex::eACTOR1) { + auto beg = _mWrappedJoints1.begin(); + auto end = _mWrappedJoints1.end(); + auto iter = find(beg, end, &joint); + if (iter == end) _mWrappedJoints1.push_back(&const_cast(joint)); + } else { + auto beg = _mWrappedJoints0.begin(); + auto end = _mWrappedJoints0.end(); + auto iter = find(beg, end, &joint); + if (iter == end) _mWrappedJoints0.push_back(&const_cast(joint)); + } +} + +void PhysXSharedBody::removeJoint(const PhysXJoint &joint, const PxJointActorIndex::Enum index) { + if (index == PxJointActorIndex::eACTOR1) { + auto beg = _mWrappedJoints1.begin(); + auto end = _mWrappedJoints1.end(); + auto iter = find(beg, end, &joint); + if (iter != end) _mWrappedJoints1.erase(iter); + } else { + auto beg = _mWrappedJoints0.begin(); + auto end = _mWrappedJoints0.end(); + auto iter = find(beg, end, &joint); + if (iter != end) _mWrappedJoints0.erase(iter); + } +} + +void PhysXSharedBody::setMass(float v) { + if (v <= 0) v = 1e-7F; + _mMass = v; + if (isDynamic()) PxRigidBodyExt::setMassAndUpdateInertia(*getImpl().rigidDynamic, _mMass); +} + +void PhysXSharedBody::setGroup(uint32_t v) { + _mFilterData.word0 = v; + setCollisionFilter(_mFilterData); +} + +void PhysXSharedBody::setMask(uint32_t v) { + _mFilterData.word1 = v; + setCollisionFilter(_mFilterData); +} + +void PhysXSharedBody::setCollisionFilter(const PxFilterData &data) { + if (isDynamic()) _mDynamicActor->wakeUp(); + for (auto const &ws : _mWrappedShapes) { + ws->getShape().setQueryFilterData(data); + ws->getShape().setSimulationFilterData(data); + } +} + +void PhysXSharedBody::clearForces() { + if (!isInWorld()) return; + if (isStaticOrKinematic()) return; + _mDynamicActor->clearForce(PxForceMode::eFORCE); + _mDynamicActor->clearForce(PxForceMode::eIMPULSE); + _mDynamicActor->clearTorque(PxForceMode::eFORCE); + _mDynamicActor->clearTorque(PxForceMode::eIMPULSE); +} + +void PhysXSharedBody::clearVelocity() { + if (isStaticOrKinematic()) return; + _mDynamicActor->setLinearVelocity(PxVec3{PxIdentity}, false); + _mDynamicActor->setAngularVelocity(PxVec3{PxIdentity}, false); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXSharedBody.h b/cocos/physics/physx/PhysXSharedBody.h new file mode 100644 index 0000000..c406f84 --- /dev/null +++ b/cocos/physics/physx/PhysXSharedBody.h @@ -0,0 +1,109 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/std/container/unordered_map.h" +#include "core/scene-graph/Node.h" +#include "physics/physx/PhysXInc.h" +#include "physics/spec/IBody.h" + +namespace cc { + +namespace physics { + +class PhysXWorld; +class PhysXShape; +class PhysXJoint; +class PhysXRigidBody; + +class PhysXSharedBody final { +public: + static PhysXSharedBody *getSharedBody(const Node *node, PhysXWorld *world, PhysXRigidBody *body); + PhysXSharedBody() = delete; + PhysXSharedBody(const PhysXSharedBody &other) = delete; + PhysXSharedBody(PhysXSharedBody &&other) = delete; + void reference(bool v); + void enabled(bool v); + inline bool isInWorld() { return _mIndex >= 0; } + inline bool isStatic() { return static_cast(_mType) & static_cast(ERigidBodyType::STATIC); } + inline bool isKinematic() { return static_cast(_mType) & static_cast(ERigidBodyType::KINEMATIC); } + inline bool isStaticOrKinematic() { return static_cast(_mType) & static_cast(ERigidBodyType::STATIC) || static_cast(_mType) & static_cast(ERigidBodyType::KINEMATIC); } + inline bool isDynamic() { return !isStaticOrKinematic(); } + inline Node *getNode() const { return _mNode; } + inline PhysXWorld &getWorld() const { return *_mWrappedWorld; } + union UActor { + uintptr_t ptr; + physx::PxRigidActor *rigidActor; + physx::PxRigidStatic *rigidStatic; + physx::PxRigidDynamic *rigidDynamic; + }; + UActor getImpl(); + void setType(ERigidBodyType v); + void setMass(float v); + void syncScale(); + void syncSceneToPhysics(); + void syncSceneWithCheck(); + void syncPhysicsToScene(); + void addShape(const PhysXShape &shape); + void removeShape(const PhysXShape &shape); + void addJoint(const PhysXJoint &joint, physx::PxJointActorIndex::Enum index); + void removeJoint(const PhysXJoint &joint, physx::PxJointActorIndex::Enum index); + void setCollisionFilter(const physx::PxFilterData &data); + void clearForces(); + void clearVelocity(); + void setGroup(uint32_t v); + void setMask(uint32_t v); + inline uint32_t getGroup() const { return _mFilterData.word0; } + inline uint32_t getMask() const { return _mFilterData.word1; } + +private: + static ccstd::unordered_map sharedBodesMap; + const uint32_t _mID; + uint8_t _mRef; + bool _mIsStatic; + ERigidBodyType _mType; + float _mMass; + int _mIndex; + physx::PxFilterData _mFilterData; + Node *_mNode; + UActor _mImpl; + physx::PxRigidStatic *_mStaticActor; + physx::PxRigidDynamic *_mDynamicActor; + PhysXWorld *_mWrappedWorld; + PhysXRigidBody *_mWrappedBody; + ccstd::vector _mWrappedShapes; + ccstd::vector _mWrappedJoints0; + ccstd::vector _mWrappedJoints1; + PhysXSharedBody(Node *node, PhysXWorld *world, PhysXRigidBody *body); + ~PhysXSharedBody(); + void initActor(); + void switchActor(bool isStaticBefore); + void initStaticActor(); + void initDynamicActor(); +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXUtils.cpp b/cocos/physics/physx/PhysXUtils.cpp new file mode 100644 index 0000000..a182936 --- /dev/null +++ b/cocos/physics/physx/PhysXUtils.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/PhysXUtils.h" + +namespace cc { +namespace physics { + +void pxSetFromTwoVectors(physx::PxQuat &out, const physx::PxVec3 &a, const physx::PxVec3 &b) { + float dot = a.dot(b); + if (dot < -0.999999) { + physx::PxVec3 c = (physx::PxVec3{1., 0., 0.}).cross(a); + if (c.magnitude() < 0.000001) c = (physx::PxVec3{0., 1., 0.}).cross(a); + c.normalize(); + out = physx::PxQuat(physx::PxPi, c); + } else if (dot > 0.999999) { + out = physx::PxQuat{physx::PxIdentity}; + } else { + physx::PxVec3 c = a.cross(b); + out = physx::PxQuat{c.x, c.y, c.z, 1 + dot}; + out.normalize(); + } +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXUtils.h b/cocos/physics/physx/PhysXUtils.h new file mode 100644 index 0000000..59a79a7 --- /dev/null +++ b/cocos/physics/physx/PhysXUtils.h @@ -0,0 +1,170 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" +#include "renderer/pipeline/Define.h" +#include "math/Vec3.h" +#include "math/Vec4.h" +#include "physics/physx/PhysXFilterShader.h" +#include "physics/physx/PhysXInc.h" + +#define PX_RELEASE(x) \ + if (x) { \ + (x)->release(); \ + (x) = NULL; \ + } + +namespace cc { +namespace physics { + +inline bool operator!=(const physx::PxVec3 &a, const cc::Vec3 &b) { + return a.x != b.x || a.y == b.y || a.z == b.z; +} + +inline bool operator!=(const cc::Vec3 &a, const physx::PxVec3 &b) { + return a.x != b.x || a.y == b.y || a.z == b.z; +} + +inline bool operator==(const physx::PxVec3 &a, const cc::Vec3 &b) { + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +inline bool operator==(const cc::Vec3 &a, const physx::PxVec3 &b) { + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +inline physx::PxVec3 operator*(const physx::PxVec3 &a, const cc::Vec3 &b) { + return physx::PxVec3{a.x * b.x, a.y * b.y, a.z * b.z}; +} + +inline cc::Vec3 operator*(const cc::Vec3 &a, const physx::PxVec3 &b) { + return cc::Vec3{a.x * b.x, a.y * b.y, a.z * b.z}; +} + +inline physx::PxVec3 operator+(const physx::PxVec3 &a, const cc::Vec3 &b) { + return physx::PxVec3{a.x + b.x, a.y + b.y, a.z + b.z}; +} + +inline cc::Vec3 operator+(const cc::Vec3 &a, const physx::PxVec3 &b) { + return cc::Vec3{a.x + b.x, a.y + b.y, a.z + b.z}; +} + +inline physx::PxVec3 &operator*=(physx::PxVec3 &a, const cc::Vec3 &b) { + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; + return a; +} + +inline cc::Vec3 &operator*=(cc::Vec3 &a, const physx::PxVec3 &b) { + a.x *= b.x; + a.y *= b.y; + a.z *= b.z; + return a; +} + +inline bool operator!=(const physx::PxQuat &a, const cc::Vec4 &b) { + return a.x != b.x || a.y == b.y || a.z == b.z || a.w == b.w; +} + +inline bool operator!=(const cc::Vec4 &a, const physx::PxQuat &b) { + return a.x != b.x || a.y == b.y || a.z == b.z || a.w == b.w; +} + +inline bool operator==(const physx::PxQuat &a, const cc::Vec4 &b) { + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +inline bool operator==(const cc::Vec4 &a, const physx::PxQuat &b) { + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +inline void pxSetVec3Ext(physx::PxVec3 &v, const cc::Vec3 &cv) { + v = physx::PxVec3(cv.x, cv.y, cv.z); +} + +inline void pxSetVec3Ext(cc::Vec3 &v, const physx::PxVec3 &cv) { + v = cc::Vec3(cv.x, cv.y, cv.z); +} + +template +inline void pxSetQuatExt(T1 &p, const T2 &cp) { + p = T1(cp.x, cp.y, cp.z, cp.w); +} + +inline void pxSetColor(gfx::Color& color, physx::PxU32 rgba) { + color.z = ((rgba >> 16) & 0xff); + color.y = ((rgba >> 8) & 0xff); + color.x = ((rgba) & 0xff); + color.w = 255; +} + +template +inline T pxAbsMax(const T &a, const T &b) { + return physx::PxAbs(a) > physx::PxAbs(b) ? a : b; +} + +void pxSetFromTwoVectors(physx::PxQuat &out, const physx::PxVec3 &a, const physx::PxVec3 &b); + +inline ccstd::unordered_map &getPxShapeMap() { + static ccstd::unordered_map m; + return m; +} + +//physx::PxCharacterController ptr <--> PhysxCharacterController ObjectID +inline ccstd::unordered_map& getPxCCTMap() { + static ccstd::unordered_map m; + return m; +} + +inline ccstd::unordered_map &getPxMaterialMap() { + static ccstd::unordered_map m; + return m; +} + +inline physx::PxMaterial &getDefaultMaterial() { + return *(reinterpret_cast(getPxMaterialMap()[0])); +} + +inline ccstd::vector &getPxRaycastHitBuffer() { + static ccstd::vector m{12}; + return m; +} + +inline ccstd::vector &getPxSweepHitBuffer() { + static ccstd::vector m{12}; + return m; +} + +inline QueryFilterShader &getQueryFilterShader() { + static QueryFilterShader shader; + return shader; +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXWorld.cpp b/cocos/physics/physx/PhysXWorld.cpp new file mode 100644 index 0000000..a962ab3 --- /dev/null +++ b/cocos/physics/physx/PhysXWorld.cpp @@ -0,0 +1,619 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/PhysXWorld.h" +#include "base/memory/Memory.h" +#include "physics/physx/PhysXFilterShader.h" +#include "physics/physx/PhysXInc.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/joints/PhysXJoint.h" +#include "physics/spec/IWorld.h" +#include "core/Root.h" +#include "scene/Camera.h" +#include "scene/RenderWindow.h" +#include "renderer/pipeline/Define.h" +#include "renderer/pipeline/RenderPipeline.h" + +namespace cc { +namespace physics { + +PhysXWorld *PhysXWorld::instance = nullptr; +uint32_t PhysXWorld::_msWrapperObjectID = 1; // starts from 1 because 0 means null +uint32_t PhysXWorld::_msPXObjectID = 0; + +PhysXWorld &PhysXWorld::getInstance() { + return *instance; +} + +physx::PxFoundation &PhysXWorld::getFundation() { + return *getInstance()._mFoundation; +} + +physx::PxCooking &PhysXWorld::getCooking() { + return *getInstance()._mCooking; +} + +physx::PxPhysics &PhysXWorld::getPhysics() { + return *getInstance()._mPhysics; +} + +physx::PxControllerManager &PhysXWorld::getControllerManager() { + return *getInstance()._mControllerManager; +} + +PhysXWorld::PhysXWorld() { + instance = this; + static physx::PxDefaultAllocator gAllocator; + static physx::PxDefaultErrorCallback gErrorCallback; + _mFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, gAllocator, gErrorCallback); + physx::PxTolerancesScale scale{}; + _mCooking = PxCreateCooking(PX_PHYSICS_VERSION, *_mFoundation, physx::PxCookingParams(scale)); + + physx::PxPvd *pvd = nullptr; +#ifdef CC_DEBUG + pvd = _mPvd = physx::PxCreatePvd(*_mFoundation); + physx::PxPvdTransport *transport = physx::PxDefaultPvdSocketTransportCreate("127.0.0.1", 5425, 10); + _mPvd->connect(*transport, physx::PxPvdInstrumentationFlag::eALL); +#endif + _mPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *_mFoundation, scale, true, pvd); + PxInitExtensions(*_mPhysics, pvd); + _mDispatcher = physx::PxDefaultCpuDispatcherCreate(0); + + _mEventMgr = ccnew PhysXEventManager(); + + physx::PxSceneDesc sceneDesc(_mPhysics->getTolerancesScale()); + sceneDesc.gravity = physx::PxVec3(0.0F, -10.0F, 0.0F); + sceneDesc.cpuDispatcher = _mDispatcher; + sceneDesc.kineKineFilteringMode = physx::PxPairFilteringMode::eKEEP; + sceneDesc.staticKineFilteringMode = physx::PxPairFilteringMode::eKEEP; + sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD; + sceneDesc.filterShader = simpleFilterShader; + sceneDesc.simulationEventCallback = &_mEventMgr->getEventCallback(); + _mScene = _mPhysics->createScene(sceneDesc); + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eSCALE, 1.0F); + + _mControllerManager = PxCreateControllerManager(*_mScene); + + _mCollisionMatrix[0] = 1; + + createMaterial(0, 0.6F, 0.6F, 0.1F, 2, 2); +} + +PhysXWorld::~PhysXWorld() { + auto &materialMap = getPxMaterialMap(); + // clear material cache + materialMap.clear(); + delete _mEventMgr; + PhysXJoint::releaseTempRigidActor(); + PX_RELEASE(_mControllerManager); + PX_RELEASE(_mScene); + PX_RELEASE(_mDispatcher); + PX_RELEASE(_mPhysics); +#ifdef CC_DEBUG + physx::PxPvdTransport *transport = _mPvd->getTransport(); + PX_RELEASE(_mPvd); + PX_RELEASE(transport); +#endif + // release cooking before foundation + PX_RELEASE(_mCooking); + PxCloseExtensions(); + PX_RELEASE(_mFoundation); +} + +void PhysXWorld::step(float fixedTimeStep) { + _mScene->simulate(fixedTimeStep); + _mScene->fetchResults(true); + syncPhysicsToScene(); +#if CC_USE_GEOMETRY_RENDERER + debugDraw(); +#endif +} + +#if CC_USE_GEOMETRY_RENDERER +pipeline::GeometryRenderer* PhysXWorld::getDebugRenderer () { + auto cameras = Root::getInstance()->getMainWindow()->getCameras(); + scene::Camera* camera = nullptr; + for (int c = 0; c < cameras.size(); c++) { + if (!cameras[c]) + continue; + const bool defaultCamera = cameras[c]->getVisibility() & static_cast(pipeline::LayerList::DEFAULT); + if (defaultCamera) { + camera = cameras[c]; + break; + } + } + + if (camera) { + camera->initGeometryRenderer(); + return camera->getGeometryRenderer(); + } + + return nullptr; +} + +void PhysXWorld::debugDraw () { + pipeline::GeometryRenderer* debugRenderer = getDebugRenderer(); + if (!debugRenderer) return; + _debugLineCount = 0; + static Vec3 v0, v1; + static gfx::Color c; + auto& rb = _mScene->getRenderBuffer(); + // lines + for (int i = 0; i < rb.getNbLines(); i++) { + if (_debugLineCount < _MAX_DEBUG_LINE_COUNT){ + _debugLineCount++; + const physx::PxDebugLine& line = rb.getLines()[i]; + pxSetColor(c, line.color0); + pxSetVec3Ext(v0, line.pos0); + pxSetVec3Ext(v1, line.pos1); + debugRenderer->addLine(v0, v1, c); + } + } + // triangles + for (int i = 0; i < rb.getNbTriangles(); i++) { + if (_debugLineCount < _MAX_DEBUG_LINE_COUNT - 3) { + _debugLineCount = _debugLineCount + 3; + const physx::PxDebugTriangle& triangle = rb.getTriangles()[i]; + pxSetColor(c, triangle.color0); + pxSetVec3Ext(v0, triangle.pos0); + pxSetVec3Ext(v1, triangle.pos1); + debugRenderer->addLine(v0, v1, c); + pxSetVec3Ext(v0, triangle.pos1); + pxSetVec3Ext(v1, triangle.pos2); + debugRenderer->addLine(v0, v1, c); + pxSetVec3Ext(v0, triangle.pos2); + pxSetVec3Ext(v1, triangle.pos0); + debugRenderer->addLine(v0, v1, c); + } + } +} + +void PhysXWorld::setDebugDrawMode() { + if (uint32_t(_debugDrawFlags) & uint32_t(EPhysicsDrawFlags::WIRE_FRAME)) { + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_SHAPES, 1); + } else { + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_SHAPES, 0); + } + + bool drawConstraint = bool(uint32_t(_debugDrawFlags) & uint32_t(EPhysicsDrawFlags::CONSTRAINT)); + float internalConstraintSize = drawConstraint ? _debugConstraintSize : 0; + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eJOINT_LOCAL_FRAMES, internalConstraintSize); + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eJOINT_LIMITS, internalConstraintSize); + + if (uint32_t(_debugDrawFlags) & uint32_t(EPhysicsDrawFlags::AABB)) { + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_AABBS, 1); + } else { + _mScene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_AABBS, 0); + } +} + +void PhysXWorld::setDebugDrawFlags(EPhysicsDrawFlags flags) { + _debugDrawFlags = flags; + setDebugDrawMode(); +} + +EPhysicsDrawFlags PhysXWorld::getDebugDrawFlags() { + return _debugDrawFlags; +} + +void PhysXWorld::setDebugDrawConstraintSize(float size) { + _debugConstraintSize = size; + setDebugDrawMode(); +} + +float PhysXWorld::getDebugDrawConstraintSize() { + return _debugConstraintSize; +} +#endif + +void PhysXWorld::setGravity(float x, float y, float z) { + _mScene->setGravity(physx::PxVec3(x, y, z)); +} + +void PhysXWorld::destroy() { +} + +void PhysXWorld::setCollisionMatrix(uint32_t index, uint32_t mask) { + if (index > 31) return; + _mCollisionMatrix[index] = mask; +} + +uint32_t PhysXWorld::createConvex(ConvexDesc &desc) { + physx::PxConvexMeshDesc convexDesc; + convexDesc.points.count = desc.positionLength; + convexDesc.points.stride = sizeof(physx::PxVec3); + convexDesc.points.data = static_cast(desc.positions); + convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX; + physx::PxConvexMesh *convexMesh = getCooking().createConvexMesh(convexDesc, PxGetPhysics().getPhysicsInsertionCallback()); + uint32_t pxObjectID = addPXObject(reinterpret_cast(convexMesh)); + return pxObjectID; +} + +uint32_t PhysXWorld::createTrimesh(TrimeshDesc &desc) { + physx::PxTriangleMeshDesc meshDesc; + meshDesc.points.count = desc.positionLength; + meshDesc.points.stride = sizeof(physx::PxVec3); + meshDesc.points.data = static_cast(desc.positions); + meshDesc.triangles.count = desc.triangleLength; + if (desc.isU16) { + meshDesc.triangles.stride = 3 * sizeof(physx::PxU16); + meshDesc.triangles.data = static_cast(desc.triangles); + meshDesc.flags = physx::PxMeshFlag::e16_BIT_INDICES; + } else { + meshDesc.triangles.stride = 3 * sizeof(physx::PxU32); + meshDesc.triangles.data = static_cast(desc.triangles); + } + physx::PxTriangleMesh *triangleMesh = getCooking().createTriangleMesh(meshDesc, PxGetPhysics().getPhysicsInsertionCallback()); + uint32_t pxObjectID = addPXObject(reinterpret_cast(triangleMesh)); + return pxObjectID; +} + +uint32_t PhysXWorld::createHeightField(HeightFieldDesc &desc) { + const auto rows = desc.rows; + const auto columns = desc.columns; + const physx::PxU32 counts = rows * columns; + auto *samples = ccnew physx::PxHeightFieldSample[counts]; + for (physx::PxU32 r = 0; r < rows; r++) { + for (physx::PxU32 c = 0; c < columns; c++) { + const auto index = c + r * columns; + auto v = (static_cast(desc.samples))[index]; + samples[index].height = v; + } + } + physx::PxHeightFieldDesc hfDesc; + hfDesc.nbRows = rows; + hfDesc.nbColumns = columns; + hfDesc.samples.data = samples; + hfDesc.samples.stride = sizeof(physx::PxHeightFieldSample); + physx::PxHeightField *hf = getCooking().createHeightField(hfDesc, PxGetPhysics().getPhysicsInsertionCallback()); + delete[] samples; + uint32_t pxObjectID = addPXObject(reinterpret_cast(hf)); + return pxObjectID; +} + +bool PhysXWorld::createMaterial(uint16_t id, float f, float df, float r, + uint8_t m0, uint8_t m1) { + physx::PxMaterial *mat; + auto &m = getPxMaterialMap(); + if (m.find(id) == m.end()) { + mat = PxGetPhysics().createMaterial(f, df, r); + // add reference count avoid auto releasing by physx + mat->acquireReference(); + m[id] = reinterpret_cast(mat); + mat->setFrictionCombineMode(physx::PxCombineMode::Enum(m0)); + mat->setRestitutionCombineMode(physx::PxCombineMode::Enum(m1)); + } else { + mat = reinterpret_cast(m[id]); + mat->setStaticFriction(f); + mat->setDynamicFriction(df); + mat->setRestitution(r); + mat->setFrictionCombineMode(physx::PxCombineMode::Enum(m0)); + mat->setRestitutionCombineMode(physx::PxCombineMode::Enum(m1)); + } + return true; +} + +uintptr_t PhysXWorld::getPXMaterialPtrWithMaterialID(uint32_t materialID) { + auto &m = getPxMaterialMap(); + auto const &it = m.find(materialID); + if (it == m.end()) { + return 0; + } else { + return it->second; + } +} + +void PhysXWorld::emitEvents() { + _mEventMgr->refreshPairs(); +} + +void PhysXWorld::syncSceneToPhysics() { + for (auto const &sb : _mSharedBodies) { + sb->syncSceneToPhysics(); + } + for (auto const &cct : _mCCTs) { + cct->syncSceneToPhysics(); + } +} + +uint32_t PhysXWorld::getMaskByIndex(uint32_t i) { + if (i > 31) i = 0; + return _mCollisionMatrix[i]; +} + +void PhysXWorld::syncPhysicsToScene() { + for (auto const &sb : _mSharedBodies) { + sb->syncPhysicsToScene(); + } +} + +void PhysXWorld::syncSceneWithCheck() { + for (auto const &sb : _mSharedBodies) { + sb->syncSceneWithCheck(); + } +} + +void PhysXWorld::setAllowSleep(bool val) { +} + +void PhysXWorld::addActor(const PhysXSharedBody &sb) { + auto beg = _mSharedBodies.begin(); + auto end = _mSharedBodies.end(); + auto iter = find(beg, end, &sb); + if (iter == end) { + _mScene->addActor(*(const_cast(sb).getImpl().rigidActor)); + _mSharedBodies.push_back(&const_cast(sb)); + } +} + +void PhysXWorld::removeActor(const PhysXSharedBody &sb) { + auto beg = _mSharedBodies.begin(); + auto end = _mSharedBodies.end(); + auto iter = find(beg, end, &sb); + if (iter != end) { + _mScene->removeActor(*(const_cast(sb).getImpl().rigidActor), true); + _mSharedBodies.erase(iter); + } +} + +void PhysXWorld::addCCT (const PhysXCharacterController &cct) { + auto beg = _mCCTs.begin(); + auto end = _mCCTs.end(); + auto iter = find(beg, end, &cct); + if (iter == end) { + _mCCTs.push_back(&const_cast(cct)); + } +} + +void PhysXWorld::removeCCT(const PhysXCharacterController&cct) { + auto beg = _mCCTs.begin(); + auto end = _mCCTs.end(); + auto iter = find(beg, end, &cct); + if (iter != end) { + _mCCTs.erase(iter); + } +} + +bool PhysXWorld::raycast(RaycastOptions &opt) { + physx::PxQueryCache *cache = nullptr; + const auto o = opt.origin; + const auto ud = opt.unitDir; + physx::PxVec3 origin{o.x, o.y, o.z}; + physx::PxVec3 unitDir{ud.x, ud.y, ud.z}; + unitDir.normalize(); + physx::PxHitFlags flags = physx::PxHitFlag::ePOSITION | physx::PxHitFlag::eNORMAL; + physx::PxSceneQueryFilterData filterData; + filterData.data.word0 = opt.mask; + filterData.data.word3 = QUERY_FILTER | (opt.queryTrigger ? 0 : QUERY_CHECK_TRIGGER); + filterData.flags = physx::PxQueryFlag::eSTATIC | physx::PxQueryFlag::eDYNAMIC | physx::PxQueryFlag::ePREFILTER; + auto &hitBuffer = getPxRaycastHitBuffer(); + bool result = false; + const auto nbTouches = physx::PxSceneQueryExt::raycastMultiple( + getScene(), origin, unitDir, opt.distance, flags, hitBuffer.data(), + static_cast(hitBuffer.size()), result, filterData, &getQueryFilterShader(), cache); + if (nbTouches == 0 || nbTouches == -1) return false; + auto &r = raycastResult(); + r.resize(nbTouches); + for (physx::PxI32 i = 0; i < nbTouches; i++) { + const auto &shapeIter = getPxShapeMap().find(reinterpret_cast(hitBuffer[i].shape)); + if (shapeIter == getPxShapeMap().end()) return false; + r[i].shape = shapeIter->second; + r[i].distance = hitBuffer[i].distance; + pxSetVec3Ext(r[i].hitNormal, hitBuffer[i].normal); + pxSetVec3Ext(r[i].hitPoint, hitBuffer[i].position); + } + return true; +} + +ccstd::vector &PhysXWorld::raycastResult() { + static ccstd::vector hits; + return hits; +} + +bool PhysXWorld::raycastClosest(RaycastOptions &opt) { + physx::PxRaycastHit hit; + physx::PxQueryCache *cache = nullptr; + const auto o = opt.origin; + const auto ud = opt.unitDir; + physx::PxVec3 origin{o.x, o.y, o.z}; + physx::PxVec3 unitDir{ud.x, ud.y, ud.z}; + unitDir.normalize(); + physx::PxHitFlags flags = physx::PxHitFlag::ePOSITION | physx::PxHitFlag::eNORMAL; + physx::PxSceneQueryFilterData filterData; + filterData.data.word0 = opt.mask; + filterData.data.word3 = QUERY_FILTER | (opt.queryTrigger ? 0 : QUERY_CHECK_TRIGGER) | QUERY_SINGLE_HIT; + filterData.flags = physx::PxQueryFlag::eSTATIC | physx::PxQueryFlag::eDYNAMIC | physx::PxQueryFlag::ePREFILTER; + const auto result = physx::PxSceneQueryExt::raycastSingle( + getScene(), origin, unitDir, opt.distance, flags, + hit, filterData, &getQueryFilterShader(), cache); + if (result) { + auto &r = raycastClosestResult(); + const auto &shapeIter = getPxShapeMap().find(reinterpret_cast(hit.shape)); + if (shapeIter == getPxShapeMap().end()) return false; + r.shape = shapeIter->second; + r.distance = hit.distance; + pxSetVec3Ext(r.hitPoint, hit.position); + pxSetVec3Ext(r.hitNormal, hit.normal); + } + return result; +} + +RaycastResult &PhysXWorld::raycastClosestResult() { + static RaycastResult hit; + return hit; +} + +bool PhysXWorld::sweepBox(RaycastOptions &opt, float halfExtentX, float halfExtentY, float halfExtentZ, + float orientationW, float orientationX, float orientationY, float orientationZ) { + return sweep(opt, physx::PxBoxGeometry{ halfExtentX, halfExtentY, halfExtentZ}, + physx::PxQuat(orientationX, orientationY, orientationZ, orientationW)); +} + +bool PhysXWorld::sweepBoxClosest(RaycastOptions &opt, float halfExtentX, float halfExtentY, float halfExtentZ, + float orientationW, float orientationX, float orientationY, float orientationZ) { + return sweepClosest(opt, physx::PxBoxGeometry{ halfExtentX, halfExtentY, halfExtentZ}, + physx::PxQuat(orientationX, orientationY, orientationZ, orientationW)); +} + +bool PhysXWorld::sweepSphere(RaycastOptions &opt, float radius) { + return sweep(opt, physx::PxSphereGeometry{ radius }, physx::PxQuat(0, 0, 0, 1)); +} + +bool PhysXWorld::sweepSphereClosest(RaycastOptions &opt, float radius) { + return sweepClosest(opt, physx::PxSphereGeometry{ radius }, physx::PxQuat(0, 0, 0, 1)); +} + +bool PhysXWorld::sweepCapsule(RaycastOptions &opt, float radius, float height, + float orientationW, float orientationX, float orientationY, float orientationZ) { + //add an extra 90 degree rotation to PxCapsuleGeometry whose axis is originally along the X axis + physx::PxQuat finalOrientation = physx::PxQuat(physx::PxPiDivTwo, physx::PxVec3{0.F, 0.F, 1.F}); + finalOrientation = physx::PxQuat(orientationX, orientationY, orientationZ, orientationW) * finalOrientation; + return sweep(opt, physx::PxCapsuleGeometry{ radius, height/2.f }, finalOrientation); +} + +bool PhysXWorld::sweepCapsuleClosest(RaycastOptions &opt, float radius, float height, + float orientationW, float orientationX, float orientationY, float orientationZ) { + //add an extra 90 degree rotation to PxCapsuleGeometry whose axis is originally along the X axis + physx::PxQuat finalOrientation = physx::PxQuat(physx::PxPiDivTwo, physx::PxVec3{0.F, 0.F, 1.F}); + finalOrientation = physx::PxQuat(orientationX, orientationY, orientationZ, orientationW) * finalOrientation; + return sweepClosest(opt, physx::PxCapsuleGeometry{ radius, height/2.f }, finalOrientation); +} + +bool PhysXWorld::sweep(RaycastOptions &opt, const physx::PxGeometry &geometry, const physx::PxQuat &orientation) { + physx::PxQueryCache *cache = nullptr; + const auto o = opt.origin; + const auto ud = opt.unitDir; + physx::PxVec3 origin{o.x, o.y, o.z}; + physx::PxVec3 unitDir{ud.x, ud.y, ud.z}; + unitDir.normalize(); + physx::PxTransform pose{origin, orientation}; + physx::PxHitFlags flags = physx::PxHitFlag::ePOSITION | physx::PxHitFlag::eNORMAL; + physx::PxSceneQueryFilterData filterData; + filterData.data.word0 = opt.mask; + filterData.data.word3 = QUERY_FILTER | (opt.queryTrigger ? 0 : QUERY_CHECK_TRIGGER); + filterData.flags = physx::PxQueryFlag::eSTATIC | physx::PxQueryFlag::eDYNAMIC | physx::PxQueryFlag::ePREFILTER; + auto &hitBuffer = getPxSweepHitBuffer(); + bool result = false; + const auto nbTouches = physx::PxSceneQueryExt::sweepMultiple( + getScene(), geometry, pose, unitDir, opt.distance, flags, hitBuffer.data(), + static_cast(hitBuffer.size()), result, filterData, &getQueryFilterShader(), cache); + if (nbTouches == 0 || nbTouches == -1) return false; + auto &r = sweepResult(); + r.resize(nbTouches); + for (physx::PxI32 i = 0; i < nbTouches; i++) { + const auto &shapeIter = getPxShapeMap().find(reinterpret_cast(hitBuffer[i].shape)); + if (shapeIter == getPxShapeMap().end()) return false; + r[i].shape = shapeIter->second; + r[i].distance = hitBuffer[i].distance; + pxSetVec3Ext(r[i].hitNormal, hitBuffer[i].normal); + pxSetVec3Ext(r[i].hitPoint, hitBuffer[i].position); + } + return true; +} + +ccstd::vector &PhysXWorld::sweepResult() { + static ccstd::vector hits; + return hits; +} + +bool PhysXWorld::sweepClosest(RaycastOptions &opt, const physx::PxGeometry &geometry, const physx::PxQuat &orientation) { + physx::PxSweepHit hit; + physx::PxQueryCache *cache = nullptr; + const auto o = opt.origin; + const auto ud = opt.unitDir; + physx::PxVec3 origin{o.x, o.y, o.z}; + physx::PxVec3 unitDir{ud.x, ud.y, ud.z}; + unitDir.normalize(); + physx::PxTransform pose{origin, orientation}; + physx::PxHitFlags flags = physx::PxHitFlag::ePOSITION | physx::PxHitFlag::eNORMAL; + physx::PxSceneQueryFilterData filterData; + filterData.data.word0 = opt.mask; + filterData.data.word3 = QUERY_FILTER | (opt.queryTrigger ? 0 : QUERY_CHECK_TRIGGER) | QUERY_SINGLE_HIT; + filterData.flags = physx::PxQueryFlag::eSTATIC | physx::PxQueryFlag::eDYNAMIC | physx::PxQueryFlag::ePREFILTER; + const auto result = physx::PxSceneQueryExt::sweepSingle( + getScene(), geometry, pose, unitDir, opt.distance, flags, + hit, filterData, &getQueryFilterShader(), cache, 0); + if (result) { + auto &r = sweepClosestResult(); + const auto &shapeIter = getPxShapeMap().find(reinterpret_cast(hit.shape)); + if (shapeIter == getPxShapeMap().end()) return false; + r.shape = shapeIter->second; + r.distance = hit.distance; + pxSetVec3Ext(r.hitPoint, hit.position); + pxSetVec3Ext(r.hitNormal, hit.normal); + } + return result; +} + +RaycastResult &PhysXWorld::sweepClosestResult() { + static RaycastResult hit; + return hit; +} + +uint32_t PhysXWorld::addPXObject(uintptr_t PXObjectPtr) { + uint32_t pxObjectID = _msPXObjectID; + _msPXObjectID++; + assert(_msPXObjectID < 0xffffffff); + + _mPXObjects[pxObjectID] = PXObjectPtr; + return pxObjectID; +}; + +void PhysXWorld::removePXObject(uint32_t pxObjectID) { + _mPXObjects.erase(pxObjectID); +} + +uintptr_t PhysXWorld::getPXPtrWithPXObjectID(uint32_t pxObjectID) { + auto const &iter = _mPXObjects.find(pxObjectID); + if (iter == _mPXObjects.end()) { + return 0; + } + return iter->second; +}; + +uint32_t PhysXWorld::addWrapperObject(uintptr_t wrapperObjectPtr) { + uint32_t wrapprtObjectID = _msWrapperObjectID; + _msWrapperObjectID++; + assert(_msWrapperObjectID < 0xffffffff); + + _mWrapperObjects[wrapprtObjectID] = wrapperObjectPtr; + return wrapprtObjectID; +}; + +void PhysXWorld::removeWrapperObject(uint32_t wrapperObjectID) { + _mWrapperObjects.erase(wrapperObjectID); +} + +uintptr_t PhysXWorld::getWrapperPtrWithObjectID(uint32_t wrapperObjectID) { + if (wrapperObjectID == 0) { + return 0; + } + auto const &iter = _mWrapperObjects.find(wrapperObjectID); + if (iter == _mWrapperObjects.end()) + return 0; + return iter->second; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/PhysXWorld.h b/cocos/physics/physx/PhysXWorld.h new file mode 100644 index 0000000..3315a45 --- /dev/null +++ b/cocos/physics/physx/PhysXWorld.h @@ -0,0 +1,177 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" +#include "base/std/container/vector.h" +#include "core/scene-graph/Node.h" +#include "physics/physx/PhysXEventManager.h" +#include "physics/physx/PhysXFilterShader.h" +#include "physics/physx/PhysXInc.h" +#include "physics/physx/PhysXRigidBody.h" +#include "physics/physx/PhysXSharedBody.h" +#include "physics/physx/character-controllers/PhysXCharacterController.h" +#include "physics/spec/IWorld.h" +#include "renderer/pipeline/GeometryRenderer.h" + +namespace cc { +namespace physics { + +class PhysXWorld final : virtual public IPhysicsWorld { +public: + static PhysXWorld &getInstance(); + static physx::PxFoundation &getFundation(); + static physx::PxCooking &getCooking(); + static physx::PxPhysics &getPhysics(); + static physx::PxControllerManager &getControllerManager(); + + PhysXWorld(); + ~PhysXWorld() override; + void step(float fixedTimeStep) override; + void setGravity(float x, float y, float z) override; + void setAllowSleep(bool v) override; + void emitEvents() override; + void setCollisionMatrix(uint32_t index, uint32_t mask) override; + bool raycast(RaycastOptions &opt) override; + bool raycastClosest(RaycastOptions &opt) override; + ccstd::vector &raycastResult() override; + RaycastResult &raycastClosestResult() override; + + bool sweep(RaycastOptions &opt, const physx::PxGeometry &geometry, const physx::PxQuat &orientation); + bool sweepClosest(RaycastOptions &opt, const physx::PxGeometry &geometry, const physx::PxQuat &orientation); + bool sweepBox(RaycastOptions &opt, float halfExtentX, float halfExtentY, float halfExtentZ, + float orientationW, float orientationX, float orientationY, float orientationZ) override; + bool sweepBoxClosest(RaycastOptions &opt, float halfExtentX, float halfExtentY, float halfExtentZ, + float orientationW, float orientationX, float orientationY, float orientationZ) override; + bool sweepSphere(RaycastOptions &opt, float radius) override; + bool sweepSphereClosest(RaycastOptions &opt, float radius) override; + bool sweepCapsule(RaycastOptions &opt, float radius, float height, + float orientationW, float orientationX, float orientationY, float orientationZ) override; + bool sweepCapsuleClosest(RaycastOptions &opt, float radius, float height, + float orientationW, float orientationX, float orientationY, float orientationZ) override; + ccstd::vector &sweepResult() override; + RaycastResult &sweepClosestResult() override; + + uint32_t createConvex(ConvexDesc &desc) override; + uint32_t createTrimesh(TrimeshDesc &desc) override; + uint32_t createHeightField(HeightFieldDesc &desc) override; + bool createMaterial(uint16_t id, float f, float df, float r, + uint8_t m0, uint8_t m1) override; + inline ccstd::vector> &getTriggerEventPairs() override { + return _mEventMgr->getTriggerPairs(); + } + inline ccstd::vector> &getContactEventPairs() override { + return _mEventMgr->getConatctPairs(); + } + inline ccstd::vector> &getCCTShapeEventPairs() override { + return _mEventMgr->getCCTShapePairs(); + } + inline ccstd::vector> &getCCTTriggerEventPairs() override { + return _mEventMgr->getCCTTriggerPairs(); + } + void syncSceneToPhysics() override; + void syncSceneWithCheck() override; + void destroy() override; + + inline PhysXSharedBody *getSharedBody( + const Node *node, + PhysXRigidBody *const body = nullptr) { + return PhysXSharedBody::getSharedBody(node, this, body); + } + + inline physx::PxScene &getScene() const { return *_mScene; } + uint32_t getMaskByIndex(uint32_t i); + void syncPhysicsToScene(); + void addActor(const PhysXSharedBody &sb); + void removeActor(const PhysXSharedBody &sb); + void addCCT(const PhysXCharacterController &cct); + void removeCCT(const PhysXCharacterController &cct); + + // Mapping PhysX Object ID and Pointer + uint32_t addPXObject(uintptr_t PXObjectPtr); + void removePXObject(uint32_t pxObjectID); + uintptr_t getPXPtrWithPXObjectID(uint32_t pxObjectID); + + // Mapping Wrapper PhysX Object ID and Pointer + uint32_t addWrapperObject(uintptr_t wrapperObjectPtr); + void removeWrapperObject(uint32_t wrapperObjectID); + uintptr_t getWrapperPtrWithObjectID(uint32_t wrapperObjectID); + + uintptr_t getPXMaterialPtrWithMaterialID(uint32_t materialID); + + float getFixedTimeStep() const override { return _fixedTimeStep; } + void setFixedTimeStep(float fixedTimeStep) override { _fixedTimeStep = fixedTimeStep; } + +#if CC_USE_GEOMETRY_RENDERER + void setDebugDrawFlags(EPhysicsDrawFlags flags) override; + EPhysicsDrawFlags getDebugDrawFlags() override; + + void setDebugDrawConstraintSize(float size) override; + float getDebugDrawConstraintSize() override; + +private: + pipeline::GeometryRenderer *getDebugRenderer(); + void debugDraw(); + void setDebugDrawMode(); +#else + void setDebugDrawFlags(EPhysicsDrawFlags flags) override{}; + EPhysicsDrawFlags getDebugDrawFlags() override { return EPhysicsDrawFlags::NONE; }; + + void setDebugDrawConstraintSize(float size) override{}; + float getDebugDrawConstraintSize() override { return 0.0; }; +#endif +private: + static PhysXWorld *instance; + physx::PxFoundation *_mFoundation; + physx::PxCooking *_mCooking; + physx::PxPhysics *_mPhysics; + physx::PxControllerManager *_mControllerManager = NULL; + +#ifdef CC_DEBUG + physx::PxPvd *_mPvd; +#endif + physx::PxDefaultCpuDispatcher *_mDispatcher; + physx::PxScene *_mScene; + PhysXEventManager *_mEventMgr; + uint32_t _mCollisionMatrix[31]; + ccstd::vector _mSharedBodies; + ccstd::vector _mCCTs; + + static uint32_t _msWrapperObjectID; + static uint32_t _msPXObjectID; + ccstd::unordered_map _mPXObjects; + ccstd::unordered_map _mWrapperObjects; + + float _fixedTimeStep{1 / 60.0F}; + + uint32_t _debugLineCount = 0; + uint32_t _MAX_DEBUG_LINE_COUNT = 16384; + EPhysicsDrawFlags _debugDrawFlags = EPhysicsDrawFlags::NONE; + float _debugConstraintSize = 0.3; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/character-controllers/PhysXBoxCharacterController.cpp b/cocos/physics/physx/character-controllers/PhysXBoxCharacterController.cpp new file mode 100644 index 0000000..83a13f9 --- /dev/null +++ b/cocos/physics/physx/character-controllers/PhysXBoxCharacterController.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/character-controllers/PhysXBoxCharacterController.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "math/Utils.h" + +namespace cc { +namespace physics { +PhysXBoxCharacterController::PhysXBoxCharacterController() : +_mHalfHeight(0.5F), _mHalfSideExtent(0.5F), _mHalfForwardExtent(0.5F) { +} + +void PhysXBoxCharacterController::setHalfHeight(float v) { + _mHalfHeight = v; + updateScale(); +} + +void PhysXBoxCharacterController::setHalfSideExtent(float v) { + _mHalfSideExtent = v; + updateScale(); +} + +void PhysXBoxCharacterController::setHalfForwardExtent(float v) { + _mHalfForwardExtent = v; + updateScale(); +} + +void PhysXBoxCharacterController::onComponentSet() { + create(); +} + +void PhysXBoxCharacterController::create() { + release(); + + physx::PxControllerManager& controllerManager = PhysXWorld::getInstance().getControllerManager(); + auto pxMtl = reinterpret_cast(PhysXWorld::getInstance().getPXMaterialPtrWithMaterialID(0)); + + physx::PxBoxControllerDesc boxDesc; + boxDesc.halfHeight = _mHalfSideExtent; + boxDesc.halfSideExtent = _mHalfSideExtent; + boxDesc.halfForwardExtent = _mHalfForwardExtent; + boxDesc.density = 10.0; + boxDesc.scaleCoeff = 0.8; + boxDesc.volumeGrowth = 1.5; + boxDesc.contactOffset = fmaxf(0.f, _mContactOffset); + boxDesc.stepOffset = _mStepOffset; + boxDesc.slopeLimit = cos(_mSlopeLimit * mathutils::D2R); + boxDesc.upDirection = physx::PxVec3(0, 1, 0); + //node is at capsule's center + Vec3 worldPos = _mNode->getWorldPosition(); + worldPos += scaledCenter(); + boxDesc.position = physx::PxExtendedVec3(worldPos.x, worldPos.y, worldPos.z); + boxDesc.material = pxMtl; + boxDesc.userData = this; + boxDesc.reportCallback = &report; + _impl = static_cast(controllerManager.createController(boxDesc)); + + updateScale(); + insertToCCTMap(); + updateFilterData(); +} + +void PhysXBoxCharacterController::updateScale(){ + updateGeometry(); +} + +void PhysXBoxCharacterController::updateGeometry(){ + if(!_impl) return; + + auto *node = _mNode; + node->updateWorldTransform(); + float s = _mHalfSideExtent * physx::PxAbs(node->getWorldScale().x); + float h = _mHalfHeight * physx::PxAbs(node->getWorldScale().y); + float f = _mHalfForwardExtent * physx::PxAbs(node->getWorldScale().z); + static_cast(_impl)->setHalfSideExtent(physx::PxMax(s, PX_NORMALIZATION_EPSILON)); + static_cast(_impl)->setHalfHeight(physx::PxMax(h, PX_NORMALIZATION_EPSILON)); + static_cast(_impl)->setHalfForwardExtent(physx::PxMax(f, PX_NORMALIZATION_EPSILON)); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/character-controllers/PhysXBoxCharacterController.h b/cocos/physics/physx/character-controllers/PhysXBoxCharacterController.h new file mode 100644 index 0000000..8e0833f --- /dev/null +++ b/cocos/physics/physx/character-controllers/PhysXBoxCharacterController.h @@ -0,0 +1,54 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/character-controllers/PhysXCharacterController.h" + +namespace cc { +namespace physics { + +class PhysXBoxCharacterController final : public PhysXCharacterController, public IBoxCharacterController { +public: + PhysXBoxCharacterController(); + ~PhysXBoxCharacterController() override = default; + + // IBoxCharacterController + void setHalfHeight(float v) override; + void setHalfSideExtent(float v) override; + void setHalfForwardExtent(float v) override; + // IBoxCharacterController END + +private: + float _mHalfHeight; + float _mHalfSideExtent; + float _mHalfForwardExtent; + void create() override; + void onComponentSet() override; + void updateScale() override; + void updateGeometry(); +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/character-controllers/PhysXCapsuleCharacterController.cpp b/cocos/physics/physx/character-controllers/PhysXCapsuleCharacterController.cpp new file mode 100644 index 0000000..f4801f6 --- /dev/null +++ b/cocos/physics/physx/character-controllers/PhysXCapsuleCharacterController.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/character-controllers/PhysXCapsuleCharacterController.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "math/Utils.h" + +namespace cc { +namespace physics { +PhysXCapsuleCharacterController::PhysXCapsuleCharacterController() : +_mRadius(0.5F), _mHeight(1.0F) { +} + +void PhysXCapsuleCharacterController::setRadius(float v) { + _mRadius = v; + updateScale(); +} + +void PhysXCapsuleCharacterController::setHeight(float v) { + _mHeight = v; + updateScale(); +} + +void PhysXCapsuleCharacterController::onComponentSet() { + create(); +} + +void PhysXCapsuleCharacterController::create() { + release(); + + physx::PxControllerManager& controllerManager = PhysXWorld::getInstance().getControllerManager(); + auto pxMtl = reinterpret_cast(PhysXWorld::getInstance().getPXMaterialPtrWithMaterialID(0)); + + physx::PxCapsuleControllerDesc capsuleDesc; + capsuleDesc.height = _mHeight; + capsuleDesc.radius = _mRadius; + capsuleDesc.climbingMode = physx::PxCapsuleClimbingMode::eCONSTRAINED; + capsuleDesc.density = 10.0; + capsuleDesc.scaleCoeff = 0.8; + capsuleDesc.volumeGrowth = 1.5; + capsuleDesc.contactOffset = fmaxf(0.f, _mContactOffset); + capsuleDesc.stepOffset = _mStepOffset; + capsuleDesc.slopeLimit = cos(_mSlopeLimit * mathutils::D2R); + capsuleDesc.upDirection = physx::PxVec3(0, 1, 0); + //node is at capsule's center + Vec3 worldPos = _mNode->getWorldPosition(); + worldPos += scaledCenter(); + capsuleDesc.position = physx::PxExtendedVec3(worldPos.x, worldPos.y, worldPos.z); + capsuleDesc.material = pxMtl; + capsuleDesc.userData = this; + capsuleDesc.reportCallback = &report; + _impl = static_cast(controllerManager.createController(capsuleDesc)); + + updateScale(); + insertToCCTMap(); + updateFilterData(); +} + +void PhysXCapsuleCharacterController::updateScale(){ + updateGeometry(); +} + +void PhysXCapsuleCharacterController::updateGeometry() { + if(!_impl) return; + + auto *node = _mNode; + node->updateWorldTransform(); + float r = _mRadius * pxAbsMax(node->getWorldScale().x, node->getWorldScale().z); + float h = _mHeight * physx::PxAbs(node->getWorldScale().y); + static_cast(_impl)->setRadius(physx::PxMax(r, PX_NORMALIZATION_EPSILON)); + static_cast(_impl)->setHeight(physx::PxMax(h, PX_NORMALIZATION_EPSILON)); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/character-controllers/PhysXCapsuleCharacterController.h b/cocos/physics/physx/character-controllers/PhysXCapsuleCharacterController.h new file mode 100644 index 0000000..d8622e6 --- /dev/null +++ b/cocos/physics/physx/character-controllers/PhysXCapsuleCharacterController.h @@ -0,0 +1,52 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/character-controllers/PhysXCharacterController.h" + +namespace cc { +namespace physics { + +class PhysXCapsuleCharacterController final : public PhysXCharacterController, public ICapsuleCharacterController { +public: + PhysXCapsuleCharacterController(); + ~PhysXCapsuleCharacterController() override = default; + + // ICapsuleCharacterController + void setRadius(float v) override; + void setHeight(float v) override; + // ICapsuleCharacterController END + +private: + float _mRadius; + float _mHeight; + void create() override; + void onComponentSet() override; + void updateScale() override; + void updateGeometry(); +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/character-controllers/PhysXCharacterController.cpp b/cocos/physics/physx/character-controllers/PhysXCharacterController.cpp new file mode 100644 index 0000000..66635d2 --- /dev/null +++ b/cocos/physics/physx/character-controllers/PhysXCharacterController.cpp @@ -0,0 +1,288 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/character-controllers/PhysXCharacterController.h" +#include "base/std/container/unordered_map.h" +#include "physics/physx/shapes/PhysXShape.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" + +namespace cc { +namespace physics { + +//PxShape --> PhysXShape +static PhysXShape* convertPxShape2PhysXShape(physx::PxShape* shape) { + //PxShape --> PhysXShape ID + const auto& hitShape = getPxShapeMap().find(reinterpret_cast(shape)); + if (hitShape == getPxShapeMap().end()) { + return nullptr; + } + //PhysXShape ID --> PhysXShape pointer + uintptr_t wrapperPtrShape = PhysXWorld::getInstance().getWrapperPtrWithObjectID(hitShape->second); + if (wrapperPtrShape == 0) { + return nullptr; + } + return reinterpret_cast(wrapperPtrShape); +} + +void ControllerHitReport::onShapeHit(const physx::PxControllerShapeHit& hit) { + const auto& hitShape = getPxShapeMap().find(reinterpret_cast(hit.shape)); + assert(hitShape!= getPxShapeMap().end()); + uint32_t shapeObjectID = hitShape->second; + + const auto& cct = getPxCCTMap().find(reinterpret_cast(hit.controller)); + assert(cct != getPxCCTMap().end()); + uint32_t cctObjectID = cct->second; + + CharacterControllerContact contact; + contact.worldPosition = cc::Vec3(hit.worldPos.x, hit.worldPos.y, hit.worldPos.z); + contact.worldNormal = cc::Vec3(hit.worldNormal.x, hit.worldNormal.y, hit.worldNormal.z); + contact.motionDirection = cc::Vec3(hit.dir.x, hit.dir.y, hit.dir.z); + contact.motionLength = hit.length; + + std::shared_ptr pair = std::make_shared(cctObjectID, shapeObjectID); + pair->contacts.push_back(contact); + + auto& pairs = PhysXWorld::getInstance().getCCTShapeEventPairs(); + pairs.push_back(pair); +} + +void ControllerHitReport::onControllerHit(const physx::PxControllersHit& hit) { +} + +void ControllerHitReport::onObstacleHit(const physx::PxControllerObstacleHit& hit) { +} + +physx::PxQueryHitType::Enum QueryFilterCallback::preFilter(const physx::PxFilterData& filterData, +const physx::PxShape* shape, const physx::PxRigidActor* actor, physx::PxHitFlags& queryFlags) { + //PxShape --> PhysXShape ID + const auto &hitShape = getPxShapeMap().find(reinterpret_cast(shape)); + if (hitShape == getPxShapeMap().end()) { + return physx::PxQueryHitType::eNONE; + } + //PhysXShape ID --> PhysXShape pointer + uintptr_t wrapperPtrShape = PhysXWorld::getInstance().getWrapperPtrWithObjectID(hitShape->second); + if(wrapperPtrShape == 0){ + return physx::PxQueryHitType::eNONE; + } + PhysXShape* wrapperShape = reinterpret_cast(wrapperPtrShape); + if (!(filterData.word0 & wrapperShape->getMask()) || !(filterData.word1 & wrapperShape->getGroup())) { + return physx::PxQueryHitType::eNONE; + } + return physx::PxQueryHitType::eBLOCK; +} + +physx::PxQueryHitType::Enum QueryFilterCallback::postFilter(const physx::PxFilterData& filterData, const physx::PxQueryHit& hit){ + PX_UNUSED(filterData); + PX_UNUSED(hit); + return physx::PxQueryHitType::eNONE; +} + +PhysXCharacterController::PhysXCharacterController() { + _mObjectID = PhysXWorld::getInstance().addWrapperObject(reinterpret_cast(this)); + _mFilterData = physx::PxFilterData(1, 1, 1, 0 ); +}; + +void PhysXCharacterController::release() { + eraseFromCCTMap(); + if(_impl){ + _impl->release(); + _impl = nullptr; + } +} + +bool PhysXCharacterController::initialize(Node *node) { + _mNode = node; + onComponentSet(); + + if (_impl == nullptr) { + return false; + } else { + PhysXWorld::getInstance().addCCT(*this); + return true; + } +} + +void PhysXCharacterController::onEnable() { + _mEnabled = true; +} + +void PhysXCharacterController::onDisable() { + _mEnabled = false; +} + +void PhysXCharacterController::onDestroy() { + release(); + PhysXWorld::getInstance().removeCCT(*this); + PhysXWorld::getInstance().removeWrapperObject(_mObjectID); +} + +cc::Vec3 PhysXCharacterController::getPosition() { + const physx::PxExtendedVec3& pos = _impl->getPosition(); + cc::Vec3 cv(pos.x, pos.y, pos.z); + return cv; +} + +void PhysXCharacterController::setPosition(float x, float y, float z) { + _impl->setPosition(physx::PxExtendedVec3{x, y, z}); +} + +bool PhysXCharacterController::onGround() { + return (_pxCollisionFlags & physx::PxControllerCollisionFlag::Enum::eCOLLISION_DOWN); +} + +void PhysXCharacterController::syncSceneToPhysics() { + uint32_t getChangedFlags = _mNode->getChangedFlags(); + if (getChangedFlags & static_cast(TransformBit::SCALE)) syncScale(); + //teleport + if (getChangedFlags & static_cast(TransformBit::POSITION)) { + const auto & cctPos = _mNode->getWorldPosition() + scaledCenter(); + setPosition(cctPos.x, cctPos.y, cctPos.z); + } + +} + +void PhysXCharacterController::syncScale () { + updateScale(); +} + +//move +void PhysXCharacterController::move(float x, float y, float z, float minDist, float elapsedTime) { + physx::PxVec3 disp{x, y, z}; + controllerFilter.mFilterData = &_mFilterData; + controllerFilter.mFilterCallback = &_mFilterCallback; + PhysXWorld::getInstance().getControllerManager().setOverlapRecoveryModule(_mOverlapRecovery); + _pxCollisionFlags = _impl->move(disp, minDist, elapsedTime, controllerFilter); +} + +void PhysXCharacterController::setStepOffset(float v) { + _mStepOffset = v; + _impl->setStepOffset(v); +} + +float PhysXCharacterController::getStepOffset() { + return _mStepOffset; +} + +void PhysXCharacterController::setSlopeLimit(float v) { + _mSlopeLimit = v; + _impl->setSlopeLimit(cos(_mSlopeLimit * mathutils::D2R)); +} + +float PhysXCharacterController::getSlopeLimit() { + return _mSlopeLimit; +} + +void PhysXCharacterController::setContactOffset(float v) { + _mContactOffset = v; + _impl->setContactOffset(v); +} + +float PhysXCharacterController::getContactOffset() { + return _mContactOffset; +} + +void PhysXCharacterController::setDetectCollisions(bool v) { + physx::PxRigidDynamic* actor = _impl->getActor(); + physx::PxShape* shape; + actor->getShapes(&shape, 1); + shape->setFlag(physx::PxShapeFlag::eSIMULATION_SHAPE, v); +} + +void PhysXCharacterController::setOverlapRecovery(bool v) { + _mOverlapRecovery = v; +} + +void PhysXCharacterController::setCenter(float x, float y, float z){ + _mCenter = Vec3(x, y, z); +} + +uint32_t PhysXCharacterController::getGroup() { + return _mFilterData.word0; +} + +void PhysXCharacterController::setGroup(uint32_t g) { + _mFilterData.word0 = g; + updateFilterData(); +} + +uint32_t PhysXCharacterController::getMask() { + return _mFilterData.word1; +} + +void PhysXCharacterController::setMask(uint32_t m) { + _mFilterData.word1 = m; + updateFilterData(); +} + +void PhysXCharacterController::updateEventListener(EShapeFilterFlag flag) { + _mFilterData.word3 |= physx::PxU32(flag); + updateFilterData(); +} + +void PhysXCharacterController::updateFilterData() { + setSimulationFilterData(_mFilterData); +} + +void PhysXCharacterController::setSimulationFilterData(physx::PxFilterData filterData) { + physx::PxRigidDynamic* actor = _impl->getActor(); + physx::PxShape* shape; + actor->getShapes(&shape, 1); + shape->setSimulationFilterData(filterData); +} + +void PhysXCharacterController::syncPhysicsToScene() { + _mNode->setWorldPosition(getPosition() - scaledCenter()); +} + +void PhysXCharacterController::insertToCCTMap() { + if (_impl) { + getPxCCTMap().insert(std::pair(reinterpret_cast(_impl), getObjectID())); + getPxCCTMap().insert(std::pair(reinterpret_cast(getShape()), getObjectID())); + } +} + +void PhysXCharacterController::eraseFromCCTMap() { + if (_impl) { + getPxCCTMap().erase(reinterpret_cast(_impl)); + getPxCCTMap().erase(reinterpret_cast(getShape())); + } +} + +cc::Vec3 PhysXCharacterController::scaledCenter() { + return _mNode->getWorldScale() * _mCenter; +} + +physx::PxShape* PhysXCharacterController::getShape() { + if (_impl) { + //cct's shape + physx::PxRigidDynamic* actor = _impl->getActor(); + physx::PxShape* shape; + actor->getShapes(&shape, 1); + return shape; + } + return nullptr; +} +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/character-controllers/PhysXCharacterController.h b/cocos/physics/physx/character-controllers/PhysXCharacterController.h new file mode 100644 index 0000000..da11603 --- /dev/null +++ b/cocos/physics/physx/character-controllers/PhysXCharacterController.h @@ -0,0 +1,123 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "core/scene-graph/Node.h" +#include "physics/physx/PhysXInc.h" +#include "physics/spec/ICharacterController.h" + +namespace cc { +namespace physics { + +class ControllerHitReport : public physx::PxUserControllerHitReport { +public: + virtual void onShapeHit(const physx::PxControllerShapeHit& hit) override; + virtual void onControllerHit(const physx::PxControllersHit& hit) override; + virtual void onObstacleHit(const physx::PxControllerObstacleHit& hit) override; +}; + +class QueryFilterCallback : public physx::PxQueryFilterCallback { +public: + virtual physx::PxQueryHitType::Enum preFilter(const physx::PxFilterData& filterData, const physx::PxShape* shape, + const physx::PxRigidActor* actor, physx::PxHitFlags& queryFlags) override; + + virtual physx::PxQueryHitType::Enum postFilter(const physx::PxFilterData& filterData, const physx::PxQueryHit& hit) override; +}; + +class PhysXCharacterController : virtual public IBaseCharacterController { + PX_NOCOPY(PhysXCharacterController) + PhysXCharacterController(); + +public: + ~PhysXCharacterController() override = default; + + void syncScale(); + void syncSceneToPhysics(); + virtual void syncPhysicsToScene() override; + + //ILifecycle + void onEnable() override; + void onDisable() override; + void onDestroy() override; + //ILifecycle END + + //ICharacterController + bool initialize(Node* node) override; + virtual cc::Vec3 getPosition() override; + virtual void setPosition(float x, float y, float z) override; + virtual bool onGround() override; + virtual void move(float x, float y, float z, float minDist, float elapsedTime) override; + virtual void setStepOffset(float v) override; + virtual float getStepOffset() override; + virtual void setSlopeLimit(float v) override; + virtual float getSlopeLimit() override; + virtual void setContactOffset(float v) override; + virtual float getContactOffset() override; + virtual void setDetectCollisions(bool v) override; + virtual void setOverlapRecovery(bool v) override; + virtual void setCenter(float x, float y, float z) override; + + uint32_t getGroup() override; + void setGroup(uint32_t g) override; + uint32_t getMask() override; + void setMask(uint32_t m) override; + void updateEventListener(EShapeFilterFlag flag) override; + uint32_t getObjectID() const override { return _mObjectID; }; + //ICharacterController END + + inline physx::PxController& getCCT() { return *_impl; }; + +protected: + physx::PxController* _impl{ nullptr }; + uint8_t _mFlag{ 0 }; + bool _mEnabled{ false }; + uint32_t _mObjectID{ 0 }; + Node* _mNode{ nullptr }; + physx::PxFilterData _mFilterData; + ControllerHitReport report; + QueryFilterCallback _mFilterCallback; + physx::PxControllerFilters controllerFilter; + physx::PxControllerCollisionFlags _pxCollisionFlags; + bool _mOverlapRecovery{ true }; + float _mContactOffset{ 0.01f }; + float _mStepOffset{ 1.f }; + float _mSlopeLimit{ 45.f }; + cc::Vec3 _mCenter{ 0.f, 0.f, 0.f }; + + void release(); + void updateFilterData(); + void setSimulationFilterData(physx::PxFilterData filterData); + virtual void create() = 0; + virtual void onComponentSet() = 0; + virtual void updateScale() = 0; + void insertToCCTMap(); + void eraseFromCCTMap(); + cc::Vec3 scaledCenter(); + physx::PxShape* getShape(); +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/joints/PhysXFixedJoint.cpp b/cocos/physics/physx/joints/PhysXFixedJoint.cpp new file mode 100644 index 0000000..fd172d9 --- /dev/null +++ b/cocos/physics/physx/joints/PhysXFixedJoint.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/joints/PhysXFixedJoint.h" +#include "math/Quaternion.h" +#include "physics/physx/PhysXSharedBody.h" +#include "physics/physx/PhysXUtils.h" + +namespace cc { +namespace physics { + +void PhysXFixedJoint::onComponentSet() { + _transA = physx::PxTransform(physx::PxIdentity); + _transB = physx::PxTransform(physx::PxIdentity); + + physx::PxRigidActor *actor0 = _mSharedBody->getImpl().rigidActor; + physx::PxRigidActor *actor1 = nullptr; + + if (_mConnectedBody) { + actor1 = _mConnectedBody->getImpl().rigidActor; + } + + _mJoint = PxFixedJointCreate(PxGetPhysics(), actor0, _transA, actor1, _transB); + + updatePose(); + setEnableDebugVisualization(true); +} + +void PhysXFixedJoint::setBreakForce(float force) { + _breakForce = force; + _mJoint->setBreakForce(_breakForce, _breakTorque); +} + +void PhysXFixedJoint::setBreakTorque(float torque) { + _breakTorque = torque; + _mJoint->setBreakForce(_breakForce, _breakTorque); +} + +void PhysXFixedJoint::updateScale0() { + updatePose(); +} + +void PhysXFixedJoint::updateScale1() { + updatePose(); +} + +void PhysXFixedJoint::updatePose() { + _transA = physx::PxTransform(physx::PxIdentity); + _transB = physx::PxTransform(physx::PxIdentity); + + pxSetVec3Ext(_transA.p, _mSharedBody->getNode()->getWorldPosition()); + pxSetQuatExt(_transA.q, _mSharedBody->getNode()->getWorldRotation()); + if (_mConnectedBody) { + pxSetVec3Ext(_transB.p, _mConnectedBody->getNode()->getWorldPosition()); + pxSetQuatExt(_transB.q, _mConnectedBody->getNode()->getWorldRotation()); + } + _mJoint->setLocalPose(physx::PxJointActorIndex::eACTOR0, _transA.getInverse()); + _mJoint->setLocalPose(physx::PxJointActorIndex::eACTOR1, _transB.getInverse()); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/joints/PhysXFixedJoint.h b/cocos/physics/physx/joints/PhysXFixedJoint.h new file mode 100644 index 0000000..d042018 --- /dev/null +++ b/cocos/physics/physx/joints/PhysXFixedJoint.h @@ -0,0 +1,53 @@ + +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/joints/PhysXJoint.h" + +namespace cc { +namespace physics { + +class PhysXFixedJoint final : public PhysXJoint, public IFixedJoint { +public: + PhysXFixedJoint() {} + ~PhysXFixedJoint() override = default; + + void setBreakForce(float force) override; + void setBreakTorque(float torque) override; + void updateScale0() override; + void updateScale1() override; + +private: + void onComponentSet() override; + void updatePose(); + float _breakForce = 0.0F; + float _breakTorque = 0.0F; + physx::PxTransform _transA; + physx::PxTransform _transB; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/joints/PhysXGenericJoint.cpp b/cocos/physics/physx/joints/PhysXGenericJoint.cpp new file mode 100644 index 0000000..cace104 --- /dev/null +++ b/cocos/physics/physx/joints/PhysXGenericJoint.cpp @@ -0,0 +1,508 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/joints/PhysXGenericJoint.h" +#include +#include "math/Mat4.h" +#include "math/Quaternion.h" +#include "math/Utils.h" +#include "math/Vec3.h" +#include "physics/physx/PhysXSharedBody.h" +#include "physics/physx/PhysXUtils.h" + +namespace cc { +namespace physics { + +void PhysXGenericJoint::onComponentSet() { + _mJoint = PxD6JointCreate(PxGetPhysics(), &getTempRigidActor(), physx::PxTransform{physx::PxIdentity}, nullptr, physx::PxTransform{physx::PxIdentity}); + setEnableDebugVisualization(true); +} + +inline auto mapAxis(uint32_t index) -> physx::PxD6Axis::Enum { + switch (index) { + case 0: + return physx::PxD6Axis::Enum::eX; + case 1: + return physx::PxD6Axis::Enum::eY; + case 2: + return physx::PxD6Axis::Enum::eZ; + case 3: + return physx::PxD6Axis::Enum::eTWIST; + case 4: + return physx::PxD6Axis::Enum::eSWING1; + case 5: + return physx::PxD6Axis::Enum::eSWING2; + default: + assert(false && "unsupported axis"); + return physx::PxD6Axis::Enum::eX; + } +} + +void PhysXGenericJoint::setConstraintMode(uint32_t index, uint32_t mode) { + physx::PxD6Axis::Enum axis{mapAxis(index)}; + physx::PxD6Motion::Enum motion{}; + switch (mode) { + case 0: + motion = physx::PxD6Motion::Enum::eFREE; + break; + case 1: + motion = physx::PxD6Motion::Enum::eLIMITED; + break; + case 2: + motion = physx::PxD6Motion::Enum::eLOCKED; + break; + default: + motion = physx::PxD6Motion::Enum::eFREE; + break; + } + switch (index) { + case 0: + _linearLimit.x = motion; + break; + case 1: + _linearLimit.y = motion; + break; + case 2: + _linearLimit.z = motion; + break; + case 3: + _angularLimit.eTwist = motion; + break; + case 4: + case 5: + _angularLimit.eSwing = motion; + break; + default: + break; + } + auto* _joint{static_cast(_mJoint)}; + _joint->setMotion(axis, motion); +} + +void PhysXGenericJoint::setLinearLimit(uint32_t index, float lower, float upper) { + assert(index < 3); // linear index should be 1, 2, or 3 + _linearLimit.lower[index] = lower; + _linearLimit.upper[index] = upper; + updateLinearLimit(); +} + +void PhysXGenericJoint::setAngularExtent(float twist, float swing1, float swing2) { + _angularLimit.twistExtent = mathutils::toRadian(std::fmax(twist, 1e-9)); + _angularLimit.swing1Extent = mathutils::toRadian(std::fmax(swing1, 1e-9)); + _angularLimit.swing2Extent = mathutils::toRadian(std::fmax(swing2, 1e-9)); + + updateTwistLimit(); + updateSwingLimit(); +} + +void PhysXGenericJoint::setLinearSoftConstraint(bool enable) { + _linearLimit.soft = enable; + updateLinearLimit(); +} + +void PhysXGenericJoint::setLinearStiffness(float stiffness) { + _linearLimit.stiffness = stiffness; + updateLinearLimit(); +} + +void PhysXGenericJoint::setLinearDamping(float damping) { + _linearLimit.damping = damping; + updateLinearLimit(); +} + +void PhysXGenericJoint::setLinearRestitution(float restitution) { + _linearLimit.restitution = restitution; + updateLinearLimit(); +} + +void PhysXGenericJoint::setSwingSoftConstraint(bool enable) { + _angularLimit.swingSoft = enable; + updateSwingLimit(); +} + +void PhysXGenericJoint::setSwingStiffness(float stiffness) { + _angularLimit.swingStiffness = stiffness; + updateSwingLimit(); +} + +void PhysXGenericJoint::setSwingDamping(float damping) { + _angularLimit.swingDamping = damping; + updateSwingLimit(); +} + +void PhysXGenericJoint::setSwingRestitution(float restitution) { + _angularLimit.swingRestitution = restitution; + updateSwingLimit(); +} + +void PhysXGenericJoint::setTwistSoftConstraint(bool enable) { + _angularLimit.twistSoft = enable; + updateTwistLimit(); +} + +void PhysXGenericJoint::setTwistStiffness(float stiffness) { + _angularLimit.twistStiffness = stiffness; + updateTwistLimit(); +} + +void PhysXGenericJoint::setTwistDamping(float damping) { + _angularLimit.twistDamping = damping; + updateTwistLimit(); +} + +void PhysXGenericJoint::setTwistRestitution(float restitution) { + _angularLimit.twistRestitution = restitution; + updateTwistLimit(); +} + +void PhysXGenericJoint::setDriverMode(uint32_t index, uint32_t mode) { + switch (index) { + case 0: + _linearMotor.xDrive = mode; + break; + case 1: + _linearMotor.yDrive = mode; + break; + case 2: + _linearMotor.zDrive = mode; + break; + case 3: + _angularMotor.twistDrive = mode; + break; + case 4: + _angularMotor.swingDrive1 = mode; + break; + case 5: + _angularMotor.swingDrive2 = mode; + break; + default: + break; + } + this->updateDrive(index); +} + +void PhysXGenericJoint::setLinearMotorTarget(float x, float y, float z) { + auto& p = _linearMotor.target; + p.x = x; + p.y = y; + p.z = z; + this->updateDrivePosition(); +} + +void PhysXGenericJoint::setLinearMotorVelocity(float x, float y, float z) { + auto& v = _linearMotor.velocity; + v.x = x; + v.y = y; + v.z = z; + this->updateDriveVelocity(); +} + +void PhysXGenericJoint::setLinearMotorForceLimit(float limit) { + _linearMotor.forceLimit = limit; + updateDrive(0); + updateDrive(1); + updateDrive(2); +} + +void PhysXGenericJoint::setAngularMotorTarget(float x, float y, float z) { + auto& p = _angularMotor.target; + p.x = x; + p.y = y; + p.z = z; + this->updateDrivePosition(); +} + +void PhysXGenericJoint::setAngularMotorVelocity(float x, float y, float z) { + auto& v = _angularMotor.velocity; + v.x = -mathutils::toRadian(x); + v.y = -mathutils::toRadian(y); + v.z = -mathutils::toRadian(z); + this->updateDriveVelocity(); +} + +void PhysXGenericJoint::setAngularMotorForceLimit(float limit) { + _angularMotor.forceLimit = limit; + this->updateDrive(3); + this->updateDrive(4); + this->updateDrive(5); +} + +void PhysXGenericJoint::setPivotA(float x, float y, float z) { + _mPivotA.x = x; + _mPivotA.y = y; + _mPivotA.z = z; + updatePose(); +} +void PhysXGenericJoint::setPivotB(float x, float y, float z) { + _mPivotB.x = x; + _mPivotB.y = y; + _mPivotB.z = z; + updatePose(); +} +void PhysXGenericJoint::setAutoPivotB(bool autoPivot) { + _mAutoPivotB = autoPivot; + updatePose(); +} +void PhysXGenericJoint::setAxis(float x, float y, float z) { + _mAxis.x = x; + _mAxis.y = y; + _mAxis.z = z; + updatePose(); +} +void PhysXGenericJoint::setSecondaryAxis(float x, float y, float z) { + _mSecondary.x = x; + _mSecondary.y = y; + _mSecondary.z = z; + updatePose(); +} + +void PhysXGenericJoint::setBreakForce(float force) { + _breakForce = force; + physx::PxD6Joint* joint{static_cast(_mJoint)}; + joint->getBreakForce(_breakForce, _breakTorque); +} +void PhysXGenericJoint::setBreakTorque(float torque) { + _breakTorque = torque; + physx::PxD6Joint* joint{static_cast(_mJoint)}; + joint->getBreakForce(_breakForce, _breakTorque); +} +void PhysXGenericJoint::updateScale0() { + this->updatePose(); +} +void PhysXGenericJoint::updateScale1() { + this->updatePose(); +} + +void PhysXGenericJoint::updateLinearLimit() { + physx::PxD6Joint* joint{static_cast(_mJoint)}; + const auto lower = _linearLimit.lower; + const auto upper = _linearLimit.upper; + auto limitx = joint->getLinearLimit(physx::PxD6Axis::Enum::eX); + auto limity = joint->getLinearLimit(physx::PxD6Axis::Enum::eY); + auto limitz = joint->getLinearLimit(physx::PxD6Axis::Enum::eZ); + if (_linearLimit.soft) { + limitx.stiffness = _linearLimit.stiffness; + limitx.damping = _linearLimit.damping; + } else { + limitx.stiffness = 0.0; + limitx.damping = 0.0; + } + limitx.contactDistance = 0.1; + limitx.bounceThreshold = 0.1; + limitx.restitution = _linearLimit.restitution; + + limity = limitx; + limitz = limitx; + if (_linearLimit.x == physx::PxD6Motion::Enum::eLIMITED) { + limitx.upper = upper[0]; + limitx.lower = lower[0]; + joint->setLinearLimit(physx::PxD6Axis::Enum::eX, limitx); + } + if (_linearLimit.y == physx::PxD6Motion::Enum::eLIMITED) { + limity.upper = upper[1]; + limity.lower = lower[1]; + joint->setLinearLimit(physx::PxD6Axis::Enum::eY, limity); + } + if (_linearLimit.z == physx::PxD6Motion::Enum::eLIMITED) { + limitz.upper = upper[2]; + limitz.lower = lower[2]; + joint->setLinearLimit(physx::PxD6Axis::Enum::eZ, limitz); + } +} + +void PhysXGenericJoint::updateSwingLimit() { + if (_angularLimit.eSwing != physx::PxD6Motion::Enum::eLIMITED) { + return; + } + + physx::PxD6Joint* joint{static_cast(_mJoint)}; + auto coneLimit = joint->getSwingLimit(); + if (_angularLimit.swingSoft) { + coneLimit.stiffness = _angularLimit.swingStiffness; + coneLimit.damping = _angularLimit.swingDamping; + } else { + coneLimit.stiffness = 0; + coneLimit.damping = 0; + } + coneLimit.bounceThreshold = 0.1; + coneLimit.contactDistance = 0.1; + coneLimit.restitution = _angularLimit.swingRestitution; + coneLimit.yAngle = math::PI; + coneLimit.zAngle = math::PI; + if (_angularLimit.eSwing == 1) { + coneLimit.yAngle = _angularLimit.swing1Extent * 0.5; + coneLimit.zAngle = _angularLimit.swing2Extent * 0.5; + joint->setSwingLimit(coneLimit); + } +} + +void PhysXGenericJoint::updateTwistLimit() { + if (_angularLimit.eTwist != physx::PxD6Motion::Enum::eLIMITED) { + return; + } + physx::PxD6Joint* joint{static_cast(_mJoint)}; + auto twistLimit = joint->getTwistLimit(); + twistLimit.bounceThreshold = 0.1; + twistLimit.contactDistance = 0.1; + twistLimit.restitution = _angularLimit.twistRestitution; + if (_angularLimit.twistSoft) { + twistLimit.stiffness = _angularLimit.twistStiffness; + twistLimit.damping = _angularLimit.twistDamping; + } else { + twistLimit.damping = 0; + twistLimit.stiffness = _angularLimit.twistStiffness; + } + if (_angularLimit.eTwist == 1) { + twistLimit.lower = _angularLimit.twistExtent * -0.5; + twistLimit.upper = _angularLimit.twistExtent * 0.5; + joint->setTwistLimit(twistLimit); + } +} + +void PhysXGenericJoint::updateDrive(uint32_t axis) { + assert(axis < 6 && "axis should be in [0, 5]"); + physx::PxD6Joint* joint{static_cast(_mJoint)}; + auto drive = joint->getDrive(static_cast(axis)); + + uint32_t mode = 0; + physx::PxD6Drive::Enum driveAxis = physx::PxD6Drive::Enum::eX; + switch (axis) { + case 0: + mode = _linearMotor.xDrive; + driveAxis = physx::PxD6Drive::Enum::eX; + break; + case 1: + mode = _linearMotor.yDrive; + driveAxis = physx::PxD6Drive::Enum::eY; + break; + case 2: + mode = _linearMotor.zDrive; + driveAxis = physx::PxD6Drive::Enum::eZ; + break; + case 3: + mode = _angularMotor.twistDrive; + driveAxis = physx::PxD6Drive::Enum::eTWIST; + break; + case 4: + case 5: + if (_angularMotor.swingDrive1 == 2 || _angularMotor.swingDrive2 == 2) { + mode = 2; + } else if (_angularMotor.swingDrive1 == 1 || _angularMotor.swingDrive2 == 1) { + mode = 1; + } else { + mode = 0; + } + driveAxis = physx::PxD6Drive::Enum::eSWING; + break; + default: + break; + }; + + if (axis < 3) { + drive.forceLimit = _linearMotor.forceLimit; + } else if (axis == 3) { + drive.forceLimit = _angularMotor.forceLimit; + } else if (axis < 6) { + drive.forceLimit = _angularMotor.forceLimit; + } + + if (mode == 0) { // disabled + drive.forceLimit = 0; + } else if (mode == 1) { // servo + drive.damping = 0.0; + drive.stiffness = 1000.0; + } else if (mode == 2) { // induction + drive.damping = 1000.0; + drive.stiffness = 0.0; + } + joint->setDrive(driveAxis, drive); +} + +void PhysXGenericJoint::updateDrivePosition() { + physx::PxD6Joint* joint{static_cast(_mJoint)}; + physx::PxTransform trans{}; + + const auto& ld = _linearMotor.target; + const auto& ad = _angularMotor.target; + + Quaternion quat; + Quaternion::fromEuler(ad.x, ad.y, ad.z, &quat); + pxSetVec3Ext(trans.p, Vec3(ld.x, ld.y, ld.z)); + pxSetQuatExt(trans.q, quat); + + joint->setDrivePosition(trans, true); +} + +void PhysXGenericJoint::updateDriveVelocity() { + physx::PxD6Joint* joint{static_cast(_mJoint)}; + joint->setDriveVelocity(_linearMotor.velocity, _angularMotor.velocity, true); +} + +void PhysXGenericJoint::updatePose() { + physx::PxTransform pose0{physx::PxIdentity}; + physx::PxTransform pose1{physx::PxIdentity}; + auto* node0 = _mSharedBody->getNode(); + node0->updateWorldTransform(); + _mTertiary = _mAxis.cross(_mSecondary); + + Mat4 transform( + _mAxis.x, _mAxis.y, _mAxis.z, 0.F, + _mSecondary.x, _mSecondary.y, _mSecondary.z, 0.F, + _mTertiary.x, _mTertiary.y, _mTertiary.z, 0.F, + 0.F, 0.F, 0.F, 1.F); + + Quaternion quat{transform}; + pose0.q = physx::PxQuat{quat.x, quat.y, quat.z, quat.w}; + Vec3 pos = Vec3(_mPivotA.x, _mPivotA.y, _mPivotA.z) * node0->getWorldScale(); + pose0.p = physx::PxVec3{pos.x, pos.y, pos.z}; + _mJoint->setLocalPose(physx::PxJointActorIndex::eACTOR0, pose0); + + if (_mConnectedBody) { + auto* node1 = _mConnectedBody->getNode(); + node1->updateWorldTransform(); + const auto& rot_1_i = node1->getWorldRotation().getInversed(); + if (_mAutoPivotB) { + pos.transformQuat(node0->getWorldRotation()); + pos += node0->getWorldPosition(); + pos -= node1->getWorldPosition(); + pos.transformQuat(rot_1_i); + pxSetVec3Ext(pose1.p, pos); + } else { + pose1.p = _mPivotB * node1->getWorldScale(); + } + Quaternion::multiply(node0->getWorldRotation(), quat, &quat); + Quaternion::multiply(rot_1_i, quat, &quat); + pxSetQuatExt(pose1.q, quat); + } else { + pos.transformQuat(node0->getWorldRotation()); + pos += node0->getWorldPosition(); + Quaternion::multiply(node0->getWorldRotation(), quat, &quat); + pose1.p = physx::PxVec3{pos.x, pos.y, pos.z}; + pose1.q = physx::PxQuat{quat.x, quat.y, quat.z, quat.w}; + } + _mJoint->setLocalPose(physx::PxJointActorIndex::eACTOR1, pose1); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/joints/PhysXGenericJoint.h b/cocos/physics/physx/joints/PhysXGenericJoint.h new file mode 100644 index 0000000..3958aeb --- /dev/null +++ b/cocos/physics/physx/joints/PhysXGenericJoint.h @@ -0,0 +1,143 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/joints/PhysXJoint.h" + +namespace cc { +namespace physics { + +class PhysXGenericJoint final : public PhysXJoint, public IGenericJoint { +public: + PhysXGenericJoint() = default; + ~PhysXGenericJoint() = default; + + void setConstraintMode(uint32_t index, uint32_t mode) override; + void setLinearLimit(uint32_t index, float lower, float upper) override; // linear only, angular use extent is better + void setAngularExtent(float twist, float swing1, float swing2) override; + void setLinearSoftConstraint(bool enable) override; + void setLinearStiffness(float stiffness) override; + void setLinearDamping(float damping) override; + void setLinearRestitution(float restitution) override; + + // TODO (yiwenxue): add new function to set angular limit + void setSwingSoftConstraint(bool enable) override; + void setTwistSoftConstraint(bool enable) override; + void setSwingStiffness(float stiffness) override; + void setSwingDamping(float damping) override; + void setSwingRestitution(float restitution) override; + void setTwistStiffness(float stiffness) override; + void setTwistDamping(float damping) override; + void setTwistRestitution(float restitution) override; + + void setDriverMode(uint32_t index, uint32_t mode) override; + void setLinearMotorTarget(float x, float y, float z) override; + void setLinearMotorVelocity(float x, float y, float z) override; + void setLinearMotorForceLimit(float limit) override; + + void setAngularMotorTarget(float x, float y, float z) override; + void setAngularMotorVelocity(float x, float y, float z) override; + void setAngularMotorForceLimit(float limit) override; + + void setPivotA(float x, float y, float z) override; + void setPivotB(float x, float y, float z) override; + void setAutoPivotB(bool autoPivot) override; + void setAxis(float x, float y, float z) override; + void setSecondaryAxis(float x, float y, float z) override; + + void setBreakForce(float force) override; + void setBreakTorque(float torque) override; + + void updateScale0() override; + void updateScale1() override; + +private: + void onComponentSet() override; + void updatePose(); + void updateLinearLimit(); + + void updateSwingLimit(); + void updateTwistLimit(); + + void updateDrive(uint32_t axis); + void updateDrivePosition(); + void updateDriveVelocity(); + + physx::PxVec3 _mPivotA{physx::PxZero}; + physx::PxVec3 _mPivotB{physx::PxZero}; + bool _mAutoPivotB{false}; + physx::PxVec3 _mAxis{physx::PxZero}; + physx::PxVec3 _mSecondary{physx::PxZero}; + physx::PxVec3 _mTertiary{physx::PxZero}; + + float _breakForce{0.F}; + float _breakTorque{0.F}; + + struct { + physx::PxD6Motion::Enum x, y, z; + float lower[3]; + float upper[3]; + + bool soft; + float stiffness; + float damping; + float restitution; + float forceLimit; + } _linearLimit; + + struct { + physx::PxD6Motion::Enum eTwist, eSwing, eSlerp; + float swing1Extent, swing2Extent, twistExtent; + + bool swingSoft, twistSoft; + float swingStiffness; + float swingDamping; + float swingRestitution; + float twistStiffness; + float twistDamping; + float twistRestitution; + } _angularLimit; + + struct { + uint32_t xDrive; + uint32_t yDrive; + uint32_t zDrive; + float forceLimit; + physx::PxVec3 target; + physx::PxVec3 velocity; + } _linearMotor; + + struct { + uint32_t twistDrive; + uint32_t swingDrive1; + uint32_t swingDrive2; + float forceLimit; + physx::PxVec3 target; + physx::PxVec3 velocity; + } _angularMotor; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/joints/PhysXJoint.cpp b/cocos/physics/physx/joints/PhysXJoint.cpp new file mode 100644 index 0000000..1b1fa4a --- /dev/null +++ b/cocos/physics/physx/joints/PhysXJoint.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/joints/PhysXJoint.h" +#include "physics/physx/PhysXSharedBody.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" + +namespace cc { +namespace physics { + +physx::PxRigidActor *PhysXJoint::tempRigidActor = nullptr; + +PhysXJoint::PhysXJoint() { + _mObjectID = PhysXWorld::getInstance().addWrapperObject(reinterpret_cast(this)); +}; + +void PhysXJoint::initialize(Node *node) { + auto &ins = PhysXWorld::getInstance(); + _mSharedBody = ins.getSharedBody(node); + _mSharedBody->reference(true); + onComponentSet(); +} + +void PhysXJoint::onEnable() { + _mSharedBody->addJoint(*this, physx::PxJointActorIndex::eACTOR0); + if (_mConnectedBody) { + _mConnectedBody->addJoint(*this, physx::PxJointActorIndex::eACTOR1); + _mJoint->setActors(_mSharedBody->getImpl().rigidActor, _mConnectedBody->getImpl().rigidActor); + } else { + _mJoint->setActors(_mSharedBody->getImpl().rigidActor, nullptr); + } +} + +void PhysXJoint::onDisable() { + _mJoint->setActors(&getTempRigidActor(), nullptr); + _mSharedBody->removeJoint(*this, physx::PxJointActorIndex::eACTOR0); + if (_mConnectedBody) _mConnectedBody->removeJoint(*this, physx::PxJointActorIndex::eACTOR1); +} + +void PhysXJoint::onDestroy() { + _mSharedBody->reference(false); + PhysXWorld::getInstance().removeWrapperObject(_mObjectID); +} + +void PhysXJoint::setConnectedBody(uint32_t rigidBodyID) { + PhysXRigidBody *pxRigidBody = reinterpret_cast(PhysXWorld::getInstance().getWrapperPtrWithObjectID(rigidBodyID)); + if (pxRigidBody == nullptr) + return; + + + auto *oldConnectedBody = _mConnectedBody; + if (oldConnectedBody) { + oldConnectedBody->removeJoint(*this, physx::PxJointActorIndex::eACTOR1); + } + + uintptr_t nodePtr = reinterpret_cast(pxRigidBody->getSharedBody().getNode()); + if (nodePtr) { + auto &ins = PhysXWorld::getInstance(); + _mConnectedBody = ins.getSharedBody(reinterpret_cast(nodePtr)); + _mConnectedBody->addJoint(*this, physx::PxJointActorIndex::eACTOR1); + } else { + _mConnectedBody = nullptr; + } + if (_mJoint) { + _mJoint->setActors(_mSharedBody->getImpl().rigidActor, _mConnectedBody ? _mConnectedBody->getImpl().rigidActor : nullptr); + } + + if (oldConnectedBody) { + if (oldConnectedBody->isDynamic()) { + oldConnectedBody->getImpl().rigidDynamic->wakeUp(); + } + } + + updateScale0(); + updateScale1(); +} + +void PhysXJoint::setEnableCollision(const bool v) { + _mEnableCollision = v; + if (_mJoint) { + _mJoint->setConstraintFlag(physx::PxConstraintFlag::eCOLLISION_ENABLED, _mEnableCollision); + } +} + +void PhysXJoint::setEnableDebugVisualization(const bool v) { + _mEnableDebugVisualization = v; + if (_mJoint) { + _mJoint->setConstraintFlag(physx::PxConstraintFlag::eVISUALIZATION, _mEnableDebugVisualization); + } +} + +physx::PxRigidActor &PhysXJoint::getTempRigidActor() { + if (!PhysXJoint::tempRigidActor) { + PhysXJoint::tempRigidActor = PxGetPhysics().createRigidDynamic(physx::PxTransform{physx::PxIdentity}); + } + return *PhysXJoint::tempRigidActor; +}; + +void PhysXJoint::releaseTempRigidActor() { + if (PhysXJoint::tempRigidActor) { + PhysXJoint::tempRigidActor->release(); + PhysXJoint::tempRigidActor = nullptr; + } +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/joints/PhysXJoint.h b/cocos/physics/physx/joints/PhysXJoint.h new file mode 100644 index 0000000..d17f48c --- /dev/null +++ b/cocos/physics/physx/joints/PhysXJoint.h @@ -0,0 +1,69 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "core/scene-graph/Node.h" +#include "physics/physx/PhysXInc.h" +#include "physics/spec/IJoint.h" + +namespace cc { +namespace physics { +class PhysXSharedBody; + +class PhysXJoint : virtual public IBaseJoint { + PX_NOCOPY(PhysXJoint) + +public: + PhysXJoint(); + ~PhysXJoint() override = default; + void initialize(Node *node) override; + void onEnable() override; + void onDisable() override; + void onDestroy() override; + void setConnectedBody(uint32_t rigidBodyID) override; + void setEnableCollision(bool v) override; + void setEnableDebugVisualization(bool v); + virtual void updateScale0() = 0; + virtual void updateScale1() = 0; + static physx::PxRigidActor &getTempRigidActor(); + static void releaseTempRigidActor(); + uint32_t getObjectID() const override { return _mObjectID; }; + +protected: + physx::PxJoint *_mJoint{nullptr}; + PhysXSharedBody *_mSharedBody{nullptr}; + PhysXSharedBody *_mConnectedBody{nullptr}; + bool _mEnableCollision{false}; + bool _mEnableDebugVisualization{ false }; + virtual void onComponentSet() = 0; + uint32_t _mObjectID{0}; + +private: + static physx::PxRigidActor *tempRigidActor; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/joints/PhysXRevolute.cpp b/cocos/physics/physx/joints/PhysXRevolute.cpp new file mode 100644 index 0000000..b8e6917 --- /dev/null +++ b/cocos/physics/physx/joints/PhysXRevolute.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/joints/PhysXRevolute.h" +#include "math/Quaternion.h" +#include "math/Utils.h" +#include "math/Vec3.h" +#include "physics/physx/PhysXSharedBody.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" + +namespace cc { +namespace physics { + +void PhysXRevolute::onComponentSet() { + _mJoint = PxRevoluteJointCreate(PxGetPhysics(), &getTempRigidActor(), physx::PxTransform{physx::PxIdentity}, nullptr, physx::PxTransform{physx::PxIdentity}); + _mlimit.stiffness = 0; + _mlimit.damping = 0; + _mlimit.restitution = 0.4; + _mlimit.contactDistance = 0.01; + + auto *joint = static_cast(_mJoint); + joint->setConstraintFlag(physx::PxConstraintFlag::ePROJECTION, true); + joint->setConstraintFlag(physx::PxConstraintFlag::eDRIVE_LIMITS_ARE_FORCES, true); + joint->setProjectionAngularTolerance(0.2); + joint->setProjectionLinearTolerance(0.2); + + setEnableDebugVisualization(true); +} + +void PhysXRevolute::setPivotA(float x, float y, float z) { + _mPivotA = physx::PxVec3{x, y, z}; + updatePose(); +} + +void PhysXRevolute::setPivotB(float x, float y, float z) { + _mPivotB = physx::PxVec3{x, y, z}; + updatePose(); +} + +void PhysXRevolute::setAxis(float x, float y, float z) { + _mAxis = physx::PxVec3{x, y, z}; + updatePose(); +} + +void PhysXRevolute::setLimitEnabled(bool v) { + _limitEnabled = v; + auto *joint = static_cast(_mJoint); + joint->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eLIMIT_ENABLED, _limitEnabled); + if (v) { + joint->setLimit(_mlimit); + } +} + +void PhysXRevolute::setLowerLimit(float v) { + _lowerLimit = v; + _mlimit.lower = mathutils::toRadian(_lowerLimit); + if (_limitEnabled) { + auto *joint = static_cast(_mJoint); + joint->setLimit(_mlimit); + } +} + +void PhysXRevolute::setUpperLimit(float v) { + _upperLimit = v; + _mlimit.upper = mathutils::toRadian(_upperLimit); + if (_limitEnabled) { + auto *joint = static_cast(_mJoint); + joint->setLimit(_mlimit); + } +} + +void PhysXRevolute::setMotorEnabled(bool v) { + _motorEnabled = v; + auto *joint = static_cast(_mJoint); + joint->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eDRIVE_ENABLED, _motorEnabled); + if (v) { + joint->setDriveVelocity(_motorVelocity / 60.0); + joint->setDriveForceLimit(_motorForceLimit); + } +} + +void PhysXRevolute::setMotorVelocity(float v) { + _motorVelocity = v; + if (_motorEnabled) { + auto *joint = static_cast(_mJoint); + joint->setDriveVelocity(_motorVelocity / 60.0); + } +} + +void PhysXRevolute::setMotorForceLimit(float v) { + _motorForceLimit = v; + if (_motorEnabled) { + auto *joint = static_cast(_mJoint); + joint->setDriveForceLimit(_motorForceLimit); + } +} + +void PhysXRevolute::updateScale0() { + updatePose(); +} + +void PhysXRevolute::updateScale1() { + updatePose(); +} + +void PhysXRevolute::updatePose() { + physx::PxTransform pose0{physx::PxIdentity}; + physx::PxTransform pose1{physx::PxIdentity}; + + auto xAxis = _mAxis.getNormalized(); + auto yAxis = physx::PxVec3(0, 1, 0); + auto zAxis = _mAxis.cross(yAxis); + if (zAxis.magnitude() < 0.0001) { + yAxis = physx::PxVec3(0, 0, 1).cross(xAxis); + zAxis = xAxis.cross(yAxis); + } else { + yAxis = zAxis.cross(xAxis); + } + + yAxis = yAxis.getNormalized(); + zAxis = zAxis.getNormalized(); + + Mat4 transform( + xAxis.x, xAxis.y, xAxis.z, 0, + yAxis.x, yAxis.y, yAxis.z, 0, + zAxis.x, zAxis.y, zAxis.z, 0, + 0.F, 0.F, 0.F, 1.F); + + auto quat = Quaternion(); + transform.getRotation(&quat); + + // pos and rot in with respect to bodyA + auto *node0 = _mSharedBody->getNode(); + node0->updateWorldTransform(); + pose0.p = _mPivotA * node0->getWorldScale(); + pose0.q = physx::PxQuat(quat.x, quat.y, quat.z, quat.w); + _mJoint->setLocalPose(physx::PxJointActorIndex::eACTOR0, pose0); + + if (_mConnectedBody) { + auto *node1 = _mConnectedBody->getNode(); + node1->updateWorldTransform(); + pose1.p = _mPivotB * node1->getWorldScale(); + const auto &rot_0 = node0->getWorldRotation(); + const auto &rot_1_i = node1->getWorldRotation().getInversed(); + pose1.q = physx::PxQuat(rot_1_i.x, rot_1_i.y, rot_1_i.z, rot_1_i.w) * physx::PxQuat(rot_0.x, rot_0.y, rot_0.z, rot_0.w) * pose0.q; + } else { + const auto &wr = node0->getWorldRotation(); + auto pos = Vec3(_mPivotA.x, _mPivotA.y, _mPivotA.z); + pos.multiply(node0->getWorldScale()); + pos.transformQuat(node0->getWorldRotation()); + pos.add(node0->getWorldPosition()); + pose1.p = physx::PxVec3(pos.x, pos.y, pos.z); + pose1.q = physx::PxQuat{wr.x, wr.y, wr.z, wr.w} * pose0.q; + } + _mJoint->setLocalPose(physx::PxJointActorIndex::eACTOR1, pose1); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/joints/PhysXRevolute.h b/cocos/physics/physx/joints/PhysXRevolute.h new file mode 100644 index 0000000..32081af --- /dev/null +++ b/cocos/physics/physx/joints/PhysXRevolute.h @@ -0,0 +1,67 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/joints/PhysXJoint.h" + +namespace cc { +namespace physics { + +class PhysXRevolute final : public PhysXJoint, public IRevoluteJoint { +public: + PhysXRevolute() : _mPivotA(physx::PxZero), + _mPivotB(physx::PxZero), + _mAxis(physx::PxZero) {} + ~PhysXRevolute() override = default; + void setPivotA(float x, float y, float z) override; + void setPivotB(float x, float y, float z) override; + void setAxis(float x, float y, float z) override; + void setLimitEnabled(bool v) override; + void setLowerLimit(float v) override; + void setUpperLimit(float v) override; + void setMotorEnabled(bool v) override; + void setMotorVelocity(float v) override; + void setMotorForceLimit(float v) override; + void updateScale0() override; + void updateScale1() override; + +private: + void onComponentSet() override; + void updatePose(); + physx::PxVec3 _mPivotA; + physx::PxVec3 _mPivotB; + physx::PxVec3 _mAxis; + physx::PxJointAngularLimitPair _mlimit{0, 0}; + + bool _limitEnabled{false}; + float _lowerLimit{0.0F}; + float _upperLimit{0.0F}; + bool _motorEnabled{false}; + float _motorVelocity{0.0F}; + float _motorForceLimit{0.0F}; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/joints/PhysXSpherical.cpp b/cocos/physics/physx/joints/PhysXSpherical.cpp new file mode 100644 index 0000000..df51ecb --- /dev/null +++ b/cocos/physics/physx/joints/PhysXSpherical.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/joints/PhysXSpherical.h" +#include "math/Quaternion.h" +#include "physics/physx/PhysXSharedBody.h" +#include "physics/physx/PhysXUtils.h" + +namespace cc { +namespace physics { + +void PhysXSpherical::onComponentSet() { + _mJoint = PxSphericalJointCreate(PxGetPhysics(), &getTempRigidActor(), physx::PxTransform{physx::PxIdentity}, nullptr, physx::PxTransform{physx::PxIdentity}); + setEnableDebugVisualization(true); +} + +void PhysXSpherical::setPivotA(float x, float y, float z) { + _mPivotA = physx::PxVec3{x, y, z}; + updatePose(); +} + +void PhysXSpherical::setPivotB(float x, float y, float z) { + _mPivotB = physx::PxVec3{x, y, z}; + updatePose(); +} + +void PhysXSpherical::updateScale0() { + updatePose(); +} + +void PhysXSpherical::updateScale1() { + updatePose(); +} + +void PhysXSpherical::updatePose() { + physx::PxTransform pose0{physx::PxIdentity}; + physx::PxTransform pose1{physx::PxIdentity}; + auto *node0 = _mSharedBody->getNode(); + node0->updateWorldTransform(); + pose0.p = _mPivotA * node0->getWorldScale(); + _mJoint->setLocalPose(physx::PxJointActorIndex::eACTOR0, pose0); + if (_mConnectedBody) { + auto *node1 = _mConnectedBody->getNode(); + node1->updateWorldTransform(); + pose1.p = _mPivotB * node1->getWorldScale(); + } else { + const auto &wr = node0->getWorldRotation(); + auto pos = Vec3(_mPivotA.x, _mPivotA.y, _mPivotA.z); + pos.multiply(node0->getWorldScale()); + pos.transformQuat(node0->getWorldRotation()); + pos.add(node0->getWorldPosition()); + pose1.p = physx::PxVec3(pos.x, pos.y, pos.z); + pose1.q = physx::PxQuat{wr.x, wr.y, wr.z, wr.w} * pose0.q; + } + _mJoint->setLocalPose(physx::PxJointActorIndex::eACTOR1, pose1); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/joints/PhysXSpherical.h b/cocos/physics/physx/joints/PhysXSpherical.h new file mode 100644 index 0000000..e31e59b --- /dev/null +++ b/cocos/physics/physx/joints/PhysXSpherical.h @@ -0,0 +1,51 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/joints/PhysXJoint.h" + +namespace cc { +namespace physics { + +class PhysXSpherical final : public PhysXJoint, public ISphericalJoint { +public: + PhysXSpherical() : _mPivotA(physx::PxZero), + _mPivotB(physx::PxZero){}; + + ~PhysXSpherical() override = default; + void setPivotA(float x, float y, float z) override; + void setPivotB(float x, float y, float z) override; + void updateScale0() override; + void updateScale1() override; + +private: + void onComponentSet() override; + void updatePose(); + physx::PxVec3 _mPivotA; + physx::PxVec3 _mPivotB; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXBox.cpp b/cocos/physics/physx/shapes/PhysXBox.cpp new file mode 100644 index 0000000..755f75f --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXBox.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/shapes/PhysXBox.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +PhysXBox::PhysXBox() : _mHalfExtents(0.5){}; + +void PhysXBox::setSize(float x, float y, float z) { + _mHalfExtents = physx::PxVec3{x / 2, y / 2, z / 2}; + updateGeometry(); + getShape().setGeometry(getPxGeometry()); +} + +void PhysXBox::onComponentSet() { + updateGeometry(); + _mShape = PxGetPhysics().createShape(getPxGeometry(), getDefaultMaterial(), true); +} + +void PhysXBox::updateScale() { + updateGeometry(); + getShape().setGeometry(getPxGeometry()); + updateCenter(); +} + +void PhysXBox::updateGeometry() { + auto *node = getSharedBody().getNode(); + auto &geo = getPxGeometry(); + geo.halfExtents = _mHalfExtents; + node->updateWorldTransform(); + geo.halfExtents *= node->getWorldScale(); + geo.halfExtents = geo.halfExtents.abs(); + if (geo.halfExtents.minElement() <= 0.0) { + geo.halfExtents = geo.halfExtents.maximum(physx::PxVec3{PX_NORMALIZATION_EPSILON}); + } +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXBox.h b/cocos/physics/physx/shapes/PhysXBox.h new file mode 100644 index 0000000..1e0b4e1 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXBox.h @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +class PhysXBox final : public PhysXShape, public IBoxShape { +public: + PhysXBox(); + ~PhysXBox() override = default; + void setSize(float x, float y, float z) override; + void updateScale() override; + +private: + physx::PxVec3 _mHalfExtents; + void updateGeometry(); + void onComponentSet() override; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXCapsule.cpp b/cocos/physics/physx/shapes/PhysXCapsule.cpp new file mode 100644 index 0000000..bfda4c1 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXCapsule.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/shapes/PhysXCapsule.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +PhysXCapsule::PhysXCapsule() : _mRadius(0.5F), + _mCylinderHeight(1.0F), + _mDirection(EAxisDirection::Y_AXIS){}; + +void PhysXCapsule::setRadius(float r) { + _mRadius = r; + updateGeometry(); + getShape().setGeometry(getPxGeometry()); +} + +void PhysXCapsule::setCylinderHeight(float v) { + _mCylinderHeight = v; + updateGeometry(); + getShape().setGeometry(getPxGeometry()); +} + +void PhysXCapsule::setDirection(EAxisDirection v) { + _mDirection = v; + updateGeometry(); + getShape().setGeometry(getPxGeometry()); +} + +void PhysXCapsule::onComponentSet() { + updateGeometry(); + _mShape = PxGetPhysics().createShape(getPxGeometry(), getDefaultMaterial(), true); +} + +void PhysXCapsule::updateScale() { + updateGeometry(); + getShape().setGeometry(getPxGeometry()); + updateCenter(); +} + +void PhysXCapsule::updateGeometry() { + auto *node = getSharedBody().getNode(); + auto &geo = getPxGeometry(); + float rs = 1.F; + float hs = 1.F; + node->updateWorldTransform(); + switch (_mDirection) { + case EAxisDirection::X_AXIS: + hs = physx::PxAbs(node->getWorldScale().x); + rs = pxAbsMax(node->getWorldScale().y, node->getWorldScale().z); + _mRotation = physx::PxQuat{physx::PxIdentity}; + break; + case EAxisDirection::Z_AXIS: + hs = physx::PxAbs(node->getWorldScale().z); + rs = pxAbsMax(node->getWorldScale().x, node->getWorldScale().y); + _mRotation = physx::PxQuat(physx::PxPiDivTwo, physx::PxVec3{0.F, 1.F, 0.F}); + break; + case EAxisDirection::Y_AXIS: + default: + hs = physx::PxAbs(node->getWorldScale().y); + rs = pxAbsMax(node->getWorldScale().x, node->getWorldScale().z); + _mRotation = physx::PxQuat(physx::PxPiDivTwo, physx::PxVec3{0.F, 0.F, 1.F}); + break; + } + geo.radius = physx::PxMax(physx::PxAbs(_mRadius * rs), PX_NORMALIZATION_EPSILON); + geo.halfHeight = physx::PxMax(physx::PxAbs(_mCylinderHeight / 2 * hs), PX_NORMALIZATION_EPSILON); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXCapsule.h b/cocos/physics/physx/shapes/PhysXCapsule.h new file mode 100644 index 0000000..eebf3a5 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXCapsule.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +class PhysXCapsule final : public PhysXShape, public ICapsuleShape { +public: + PhysXCapsule(); + ~PhysXCapsule() override = default; + void setRadius(float r) override; + void setCylinderHeight(float v) override; + void setDirection(EAxisDirection v) override; + void updateScale() override; + +private: + float _mRadius; + float _mCylinderHeight; + EAxisDirection _mDirection; + void updateGeometry(); + void onComponentSet() override; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXCone.cpp b/cocos/physics/physx/shapes/PhysXCone.cpp new file mode 100644 index 0000000..268f9d1 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXCone.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/shapes/PhysXCone.h" +#include +#include "math/Quaternion.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +PhysXCone::PhysXCone() : _mMesh(nullptr){}; + +void PhysXCone::setConvex(uint32_t objectID) { + uintptr_t handle = PhysXWorld::getInstance().getPXPtrWithPXObjectID(objectID); + if (handle == 0) return; + if (reinterpret_cast(_mMesh) == handle) return; + _mMesh = reinterpret_cast(handle); + if (_mShape) { + } +} + +void PhysXCone::onComponentSet() { + if (_mMesh) { + physx::PxConvexMeshGeometry geom; + geom.convexMesh = _mMesh; + // geom.meshFlags = PxConvexMeshGeometryFlags::eTIGHT_BOUNDS; + _mShape = PxGetPhysics().createShape(geom, getDefaultMaterial(), true); + updateGeometry(); + } +} + +void PhysXCone::setCone(float r, float h, EAxisDirection d) { + _mData.radius = r; + _mData.height = h; + _mData.direction = d; + updateGeometry(); +} + +void PhysXCone::updateGeometry() { + if (!_mShape) return; + static physx::PxMeshScale scale; + auto *node = getSharedBody().getNode(); + node->updateWorldTransform(); + pxSetVec3Ext(scale.scale, node->getWorldScale()); + scale.scale.y *= std::max(0.0001F, _mData.height / 2); + const auto radius = std::max(0.0001F, _mData.radius * 2); + const auto xzMaxNorm = std::max(scale.scale.x, scale.scale.z); + scale.scale.x = scale.scale.z = radius * xzMaxNorm; + Quaternion quat; + switch (_mData.direction) { + case EAxisDirection::X_AXIS: + quat.set(Vec3::UNIT_Z, physx::PxPiDivTwo); + pxSetQuatExt(scale.rotation, quat); + break; + case EAxisDirection::Y_AXIS: + default: + scale.rotation = physx::PxQuat{physx::PxIdentity}; + break; + case EAxisDirection::Z_AXIS: + quat.set(Vec3::UNIT_X, physx::PxPiDivTwo); + pxSetQuatExt(scale.rotation, quat); + break; + } + physx::PxConvexMeshGeometry geom; + if (getShape().getConvexMeshGeometry(geom)) { + geom.scale = scale; + getShape().setGeometry(geom); + } + pxSetQuatExt(_mRotation, quat); +} + +void PhysXCone::updateScale() { + updateGeometry(); + updateCenter(); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXCone.h b/cocos/physics/physx/shapes/PhysXCone.h new file mode 100644 index 0000000..273ce61 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXCone.h @@ -0,0 +1,54 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +class PhysXCone final : public PhysXShape, public IConeShape { +public: + PhysXCone(); + ~PhysXCone() override = default; + void setConvex(uint32_t objectID) override; + void setCone(float r, float h, EAxisDirection d) override; + void updateScale() override; + + struct Cone { + float radius; + float height; + EAxisDirection direction; + }; + +private: + physx::PxConvexMesh *_mMesh; + Cone _mData; + void updateGeometry(); + void onComponentSet() override; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXCylinder.cpp b/cocos/physics/physx/shapes/PhysXCylinder.cpp new file mode 100644 index 0000000..3343ac0 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXCylinder.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/shapes/PhysXCylinder.h" +#include +#include "math/Quaternion.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +PhysXCylinder::PhysXCylinder() : _mMesh(nullptr){}; + +void PhysXCylinder::setConvex(uint32_t objectID) { + uintptr_t handle = PhysXWorld::getInstance().getPXPtrWithPXObjectID(objectID); + if (handle == 0) return; + if (reinterpret_cast(_mMesh) == handle) return; + _mMesh = reinterpret_cast(handle); + if (_mShape) { + // TODO(Administrator): ... + } +} + +void PhysXCylinder::onComponentSet() { + if (_mMesh) { + physx::PxConvexMeshGeometry geom; + geom.convexMesh = _mMesh; + // geom.meshFlags = PxConvexMeshGeometryFlags::eTIGHT_BOUNDS; + _mShape = PxGetPhysics().createShape(geom, getDefaultMaterial(), true); + updateGeometry(); + } +} + +void PhysXCylinder::setCylinder(float r, float h, EAxisDirection d) { + _mData.radius = r; + _mData.height = h; + _mData.direction = d; + updateGeometry(); +} + +void PhysXCylinder::updateGeometry() { + if (!_mShape) return; + static physx::PxMeshScale scale; + auto *node = getSharedBody().getNode(); + node->updateWorldTransform(); + pxSetVec3Ext(scale.scale, node->getWorldScale()); + scale.scale.y *= std::max(0.0001F, _mData.height / 2); + const auto radius = std::max(0.0001F, _mData.radius * 2); + const auto xzMaxNorm = std::max(scale.scale.x, scale.scale.z); + scale.scale.x = scale.scale.z = radius * xzMaxNorm; + Quaternion quat; + switch (_mData.direction) { + case EAxisDirection::X_AXIS: + quat.set(Vec3::UNIT_Z, physx::PxPiDivTwo); + pxSetQuatExt(scale.rotation, quat); + break; + case EAxisDirection::Y_AXIS: + default: + scale.rotation = physx::PxQuat{physx::PxIdentity}; + break; + case EAxisDirection::Z_AXIS: + quat.set(Vec3::UNIT_X, physx::PxPiDivTwo); + pxSetQuatExt(scale.rotation, quat); + break; + } + physx::PxConvexMeshGeometry geom; + if (getShape().getConvexMeshGeometry(geom)) { + geom.scale = scale; + getShape().setGeometry(geom); + } + pxSetQuatExt(_mRotation, quat); +} + +void PhysXCylinder::updateScale() { + updateGeometry(); + updateCenter(); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXCylinder.h b/cocos/physics/physx/shapes/PhysXCylinder.h new file mode 100644 index 0000000..12b19ac --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXCylinder.h @@ -0,0 +1,54 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +class PhysXCylinder final : public PhysXShape, public ICylinderShape { +public: + PhysXCylinder(); + ~PhysXCylinder() override = default; + void setConvex(uint32_t objectID) override; + void setCylinder(float r, float h, EAxisDirection d) override; + void updateScale() override; + + struct Cylinder { + float radius; + float height; + EAxisDirection direction; + }; + +private: + physx::PxConvexMesh *_mMesh; + Cylinder _mData; + void updateGeometry(); + void onComponentSet() override; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXPlane.cpp b/cocos/physics/physx/shapes/PhysXPlane.cpp new file mode 100644 index 0000000..00f47a4 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXPlane.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/shapes/PhysXPlane.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +PhysXPlane::PhysXPlane() : _mConstant(0.F), + _mNormal(0.F, 1.F, 0.F){}; + +void PhysXPlane::setConstant(float x) { + _mConstant = x; + updateCenter(); +} + +void PhysXPlane::setNormal(float x, float y, float z) { + _mNormal = physx::PxVec3{x, y, z}; + updateCenter(); +} + +void PhysXPlane::onComponentSet() { + _mShape = PxGetPhysics().createShape(getPxGeometry(), getDefaultMaterial(), true); + updateCenter(); +} + +void PhysXPlane::updateScale() { + updateCenter(); +} + +void PhysXPlane::updateCenter() { + auto* node = getSharedBody().getNode(); + auto& geo = getPxGeometry(); + physx::PxTransform local; + pxSetFromTwoVectors(local.q, physx::PxVec3{1.F, 0.F, 0.F}, _mNormal); + local.p = _mNormal * _mConstant + _mCenter; + getShape().setLocalPose(local); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXPlane.h b/cocos/physics/physx/shapes/PhysXPlane.h new file mode 100644 index 0000000..12680c9 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXPlane.h @@ -0,0 +1,48 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +class PhysXPlane final : public PhysXShape, public IPlaneShape { +public: + PhysXPlane(); + ~PhysXPlane() override = default; + void setConstant(float x) override; + void setNormal(float x, float y, float z) override; + void updateScale() override; + +private: + physx::PxVec3 _mNormal; + physx::PxReal _mConstant; + void updateCenter() override; + void onComponentSet() override; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXShape.cpp b/cocos/physics/physx/shapes/PhysXShape.cpp new file mode 100644 index 0000000..829a544 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXShape.cpp @@ -0,0 +1,157 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/shapes/PhysXShape.h" +#include "base/std/container/unordered_map.h" +#include "physics/physx/PhysXSharedBody.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" + +namespace cc { +namespace physics { + +PhysXShape::PhysXShape() : _mCenter(physx::PxIdentity), _mRotation(physx::PxIdentity) { + _mObjectID = PhysXWorld::getInstance().addWrapperObject(reinterpret_cast(this)); +}; + +void PhysXShape::initialize(Node *node) { + PhysXWorld &ins = PhysXWorld::getInstance(); + _mSharedBody = ins.getSharedBody(node); + getSharedBody().reference(true); + onComponentSet(); + insertToShapeMap(); +} + +void PhysXShape::onEnable() { + _mEnabled = true; + if (_mShape) getSharedBody().addShape(*this); + getSharedBody().enabled(true); +} + +void PhysXShape::onDisable() { + _mEnabled = false; + if (_mShape) getSharedBody().removeShape(*this); + getSharedBody().enabled(false); +} + +void PhysXShape::onDestroy() { + getSharedBody().reference(false); + eraseFromShapeMap(); + PhysXWorld::getInstance().removeWrapperObject(_mObjectID); +} + +void PhysXShape::setMaterial(uint16_t id, float f, float df, float r, + uint8_t m0, uint8_t m1) { + if (!_mShape) return; + PhysXWorld::getInstance().createMaterial(id, f, df, r, m0, m1); + auto *mat = reinterpret_cast(PhysXWorld::getInstance().getPXMaterialPtrWithMaterialID(id)); + getShape().setMaterials(&mat, 1); +} + +void PhysXShape::setAsTrigger(bool v) { + if (v) { + getShape().setFlag(physx::PxShapeFlag::eSIMULATION_SHAPE, !v); + getShape().setFlag(physx::PxShapeFlag::eTRIGGER_SHAPE, v); + } else { + getShape().setFlag(physx::PxShapeFlag::eTRIGGER_SHAPE, v); + getShape().setFlag(physx::PxShapeFlag::eSIMULATION_SHAPE, !v); + } + if (_mEnabled) { + getSharedBody().removeShape(*this); + getSharedBody().addShape(*this); + } +} + +void PhysXShape::setCenter(float x, float y, float z) { + _mCenter = physx::PxVec3{x, y, z}; + updateCenter(); +} + +uint32_t PhysXShape::getGroup() { + return getSharedBody().getGroup(); +} + +void PhysXShape::setGroup(uint32_t g) { + getSharedBody().setGroup(g); +} + +uint32_t PhysXShape::getMask() { + return getSharedBody().getMask(); +} + +void PhysXShape::setMask(uint32_t m) { + getSharedBody().setMask(m); +} + +void PhysXShape::updateEventListener(EShapeFilterFlag flag) { +} + +geometry::AABB &PhysXShape::getAABB() { + static IntrusivePtr aabb; // this variable is shared with JS with refcounter + if (!aabb) { + aabb = new geometry::AABB; + } + if (_mShape) { + auto bounds = physx::PxShapeExt::getWorldBounds(getShape(), *getSharedBody().getImpl().rigidActor); + pxSetVec3Ext(aabb->center, (bounds.maximum + bounds.minimum) / 2); + pxSetVec3Ext(aabb->halfExtents, (bounds.maximum - bounds.minimum) / 2); + } + return *aabb; +} + +geometry::Sphere &PhysXShape::getBoundingSphere() { + static IntrusivePtr sphere; // this variable is shared with JS with refcounter + if (!sphere) { + sphere = new geometry::Sphere; + } + if (_mShape) sphere->define(getAABB()); + return *sphere; +} + +void PhysXShape::updateFilterData(const physx::PxFilterData &data) { +} + +void PhysXShape::updateCenter() { + if (!_mShape) return; + auto &sb = getSharedBody(); + auto *node = sb.getNode(); + node->updateWorldTransform(); + physx::PxTransform local{_mCenter * node->getWorldScale(), _mRotation}; + getShape().setLocalPose(local); +} + +void PhysXShape::insertToShapeMap() { + if (_mShape) { + getPxShapeMap().insert(std::pair(reinterpret_cast(&getShape()), getObjectID())); + } +} + +void PhysXShape::eraseFromShapeMap() { + if (_mShape) { + getPxShapeMap().erase(reinterpret_cast(&getShape())); + } +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXShape.h b/cocos/physics/physx/shapes/PhysXShape.h new file mode 100644 index 0000000..bcff8ef --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXShape.h @@ -0,0 +1,89 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Macros.h" +#include "core/scene-graph/Node.h" +#include "physics/physx/PhysXInc.h" +#include "physics/spec/IShape.h" + +namespace cc { +namespace physics { +class PhysXSharedBody; + +template +inline T &getPxGeometry() { + static T geo; + return geo; +} + +class PhysXShape : virtual public IBaseShape { + PX_NOCOPY(PhysXShape) + PhysXShape(); + +public: + ~PhysXShape() override = default; + void initialize(Node *node) override; + void onEnable() override; + void onDisable() override; + void onDestroy() override; + void setMaterial(uint16_t id, float f, float df, float r, + uint8_t m0, uint8_t m1) override; + void setAsTrigger(bool v) override; + void setCenter(float x, float y, float z) override; + geometry::AABB &getAABB() override; + geometry::Sphere &getBoundingSphere() override; + void updateEventListener(EShapeFilterFlag flag) override; + uint32_t getGroup() override; + void setGroup(uint32_t g) override; + uint32_t getMask() override; + void setMask(uint32_t m) override; + virtual void updateScale() = 0; + inline physx::PxVec3 &getCenter() { return _mCenter; } + inline physx::PxShape &getShape() const { return *_mShape; } + inline PhysXSharedBody &getSharedBody() const { return *_mSharedBody; } + inline bool isTrigger() const { + return getShape().getFlags().isSet(physx::PxShapeFlag::eTRIGGER_SHAPE); + } + void updateFilterData(const physx::PxFilterData &data); + uint32_t getObjectID() const override { return _mObjectID; }; + +protected: + PhysXSharedBody *_mSharedBody{nullptr}; + physx::PxShape *_mShape{nullptr}; + physx::PxVec3 _mCenter; + physx::PxQuat _mRotation; + uint8_t _mFlag{0}; + bool _mEnabled{false}; + uint32_t _mObjectID{0}; + + virtual void updateCenter(); + virtual void onComponentSet() = 0; + virtual void insertToShapeMap(); + virtual void eraseFromShapeMap(); +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXSphere.cpp b/cocos/physics/physx/shapes/PhysXSphere.cpp new file mode 100644 index 0000000..daed28a --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXSphere.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/shapes/PhysXSphere.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +PhysXSphere::PhysXSphere() : _mRadius(0.5F) {} + +void PhysXSphere::setRadius(float r) { + _mRadius = r; + updateGeometry(); + getShape().setGeometry(getPxGeometry()); +} + +void PhysXSphere::onComponentSet() { + updateGeometry(); + _mShape = PxGetPhysics().createShape(getPxGeometry(), getDefaultMaterial(), true); +} + +void PhysXSphere::updateScale() { + updateGeometry(); + getShape().setGeometry(getPxGeometry()); + updateCenter(); +} + +void PhysXSphere::updateGeometry() { + physx::PxVec3 scale; + auto *node = getSharedBody().getNode(); + node->updateWorldTransform(); + pxSetVec3Ext(scale, node->getWorldScale()); + auto &geo = getPxGeometry(); + geo.radius = physx::PxMax(_mRadius * scale.abs().maxElement(), PX_NORMALIZATION_EPSILON); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXSphere.h b/cocos/physics/physx/shapes/PhysXSphere.h new file mode 100644 index 0000000..602e254 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXSphere.h @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +class PhysXSphere final : public PhysXShape, public ISphereShape { +public: + PhysXSphere(); + ~PhysXSphere() override = default; + void setRadius(float r) override; + void updateScale() override; + +private: + float _mRadius; + void updateGeometry(); + void onComponentSet() override; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXTerrain.cpp b/cocos/physics/physx/shapes/PhysXTerrain.cpp new file mode 100644 index 0000000..36e40e2 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXTerrain.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/shapes/PhysXTerrain.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +PhysXTerrain::PhysXTerrain() : _mTerrain(nullptr), + _mRowScale(1.F), + _mColScale(1.F), + _mHeightScale(1.F), + _mIsTrigger(false){}; + +void PhysXTerrain::setTerrain(uint32_t objectID, float rs, float cs, float hs) { + uintptr_t handle = PhysXWorld::getInstance().getPXPtrWithPXObjectID(objectID); + if (handle == 0) return; + if (_mShape) return; + if (reinterpret_cast(_mTerrain) == handle) return; + _mTerrain = reinterpret_cast(handle); + _mRowScale = rs; + _mColScale = cs; + _mHeightScale = hs; + if (_mSharedBody && _mTerrain) { + onComponentSet(); + insertToShapeMap(); + if (_mEnabled) getSharedBody().addShape(*this); + setAsTrigger(_mIsTrigger); + updateCenter(); + } +} + +void PhysXTerrain::onComponentSet() { + if (_mTerrain) { + physx::PxHeightFieldGeometry geom; + geom.rowScale = _mRowScale; + geom.columnScale = _mColScale; + geom.heightScale = _mHeightScale; + geom.heightField = _mTerrain; + _mShape = PxGetPhysics().createShape(geom, getDefaultMaterial(), true); + } +} + +void PhysXTerrain::updateScale() { + // updateCenter(); needed? +} + +void PhysXTerrain::updateCenter() { + if (!_mShape) return; + getShape().setLocalPose(physx::PxTransform{_mCenter, _mRotation}); +} + +void PhysXTerrain::setAsTrigger(bool v) { + _mIsTrigger = v; + if (_mShape) { + PhysXShape::setAsTrigger(v); + } +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXTerrain.h b/cocos/physics/physx/shapes/PhysXTerrain.h new file mode 100644 index 0000000..7bf77f6 --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXTerrain.h @@ -0,0 +1,51 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +class PhysXTerrain final : public PhysXShape, public ITerrainShape { +public: + PhysXTerrain(); + ~PhysXTerrain() override = default; + void setTerrain(uint32_t objectID, float rs, float cs, float hs) override; + void updateScale() override; + void updateCenter() override; + void setAsTrigger(bool v) override; + +private: + physx::PxHeightField *_mTerrain; + float _mRowScale; + float _mColScale; + float _mHeightScale; + bool _mIsTrigger; + void onComponentSet() override; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXTrimesh.cpp b/cocos/physics/physx/shapes/PhysXTrimesh.cpp new file mode 100644 index 0000000..e8f854d --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXTrimesh.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/physx/shapes/PhysXTrimesh.h" +#include "physics/physx/PhysXUtils.h" +#include "physics/physx/PhysXWorld.h" +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +PhysXTrimesh::PhysXTrimesh() : _mMeshHandle(0), + _mConvex(false), + _mIsTrigger(false){}; + +void PhysXTrimesh::setMesh(uint32_t objectID) { + uintptr_t handle = PhysXWorld::getInstance().getPXPtrWithPXObjectID(objectID); + if (handle == 0) return; + if (_mShape) { + getSharedBody().removeShape(*this); + eraseFromShapeMap(); + _mShape->release(); + _mShape = nullptr; + } + if (_mMeshHandle == handle) return; + _mMeshHandle = handle; + if (_mSharedBody && _mMeshHandle) { + onComponentSet(); + insertToShapeMap(); + if (_mEnabled) getSharedBody().addShape(*this); + setAsTrigger(_mIsTrigger); + updateCenter(); + } +} + +void PhysXTrimesh::useConvex(bool v) { + _mConvex = v; +} + +void PhysXTrimesh::onComponentSet() { + if (_mMeshHandle) { + const auto &mat = getDefaultMaterial(); + if (_mConvex) { + physx::PxConvexMeshGeometry geom; + geom.convexMesh = reinterpret_cast(_mMeshHandle); + // geom.meshFlags = PxConvexMeshGeometryFlags::eTIGHT_BOUNDS; + _mShape = PxGetPhysics().createShape(geom, mat, true); + } else { + physx::PxTriangleMeshGeometry geom; + geom.triangleMesh = reinterpret_cast(_mMeshHandle); + // geom.meshFlags = PxMeshGeometryFlag::eDOUBLE_SIDED; + _mShape = PxGetPhysics().createShape(geom, mat, true); + } + updateGeometry(); + } +} + +void PhysXTrimesh::updateScale() { + updateGeometry(); + updateCenter(); +} + +void PhysXTrimesh::updateGeometry() { + static physx::PxMeshScale scale; + scale.rotation = physx::PxQuat{physx::PxIdentity}; + auto *node = getSharedBody().getNode(); + node->updateWorldTransform(); + pxSetVec3Ext(scale.scale, node->getWorldScale()); + const auto &type = _mShape->getGeometryType(); + if (type == physx::PxGeometryType::eCONVEXMESH) { + physx::PxConvexMeshGeometry geom; + if (getShape().getConvexMeshGeometry(geom)) { + geom.scale = scale; + getShape().setGeometry(geom); + } + } else if (type == physx::PxGeometryType::eTRIANGLEMESH) { + physx::PxTriangleMeshGeometry geom; + if (getShape().getTriangleMeshGeometry(geom)) { + geom.scale = scale; + getShape().setGeometry(geom); + } + } +} + +void PhysXTrimesh::setAsTrigger(bool v) { + _mIsTrigger = v; + if (_mShape) { + PhysXShape::setAsTrigger(v); + } +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/physx/shapes/PhysXTrimesh.h b/cocos/physics/physx/shapes/PhysXTrimesh.h new file mode 100644 index 0000000..54a04ce --- /dev/null +++ b/cocos/physics/physx/shapes/PhysXTrimesh.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "physics/physx/shapes/PhysXShape.h" + +namespace cc { +namespace physics { + +class PhysXTrimesh final : public PhysXShape, public ITrimeshShape { +public: + PhysXTrimesh(); + ~PhysXTrimesh() override = default; + void setMesh(uint32_t objectID) override; + void useConvex(bool v) override; + void updateScale() override; + void setAsTrigger(bool v) override; + +private: + bool _mConvex; + uintptr_t _mMeshHandle; + bool _mIsTrigger; + void updateGeometry(); + void onComponentSet() override; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/sdk/CharacterController.cpp b/cocos/physics/sdk/CharacterController.cpp new file mode 100644 index 0000000..fc53ac4 --- /dev/null +++ b/cocos/physics/sdk/CharacterController.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/sdk/CharacterController.h" +#include +#include "physics/PhysicsSelector.h" + +#define CC_PHYSICS_CCT_DEFINITION(CLASS, WRAPPED) \ + \ +CLASS::CLASS() { \ + _impl.reset(ccnew WRAPPED()); \ +} \ + \ +CLASS::~CLASS() { \ + _impl.reset(nullptr); \ +} \ + \ +bool CLASS::initialize(Node *node) { \ + return _impl->initialize(node); \ +} \ + \ +void CLASS::onEnable() { \ + _impl->onEnable(); \ +} \ + \ +void CLASS::onDisable() { \ + _impl->onDisable(); \ +} \ + \ +void CLASS::onDestroy() { \ + _impl->onDestroy(); \ +} \ + \ +cc::Vec3 CLASS::getPosition() { \ + return _impl->getPosition(); \ +} \ + \ +void CLASS::setPosition(float x, float y, float z) { \ + _impl->setPosition(x, y, z); \ +} \ + \ +bool CLASS::onGround() { \ + return _impl->onGround(); \ +} \ + \ +void CLASS::move(float x, float y, float z, float minDist, float elapsedTime) { \ + _impl->move(x, y, z, minDist, elapsedTime); \ +} \ + \ +void CLASS::syncPhysicsToScene() { \ + _impl->syncPhysicsToScene(); \ +} \ + \ +void CLASS::setStepOffset(float v) { \ + _impl->setStepOffset(v); \ +} \ + \ +float CLASS::getStepOffset() { \ + return _impl->getStepOffset(); \ +} \ + \ +void CLASS::setSlopeLimit(float v) { \ + _impl->setSlopeLimit(v); \ +} \ + \ +float CLASS::getSlopeLimit() { \ + return _impl->getSlopeLimit(); \ +} \ + \ +void CLASS::setContactOffset(float v) { \ + _impl->setContactOffset(v); \ +} \ + \ +float CLASS::getContactOffset() { \ + return _impl->getContactOffset(); \ +} \ + \ +void CLASS::setDetectCollisions(bool v) { \ + _impl->setDetectCollisions(v); \ +} \ +void CLASS::setOverlapRecovery(bool v) { \ + _impl->setOverlapRecovery(v); \ +} \ +void CLASS::setCenter(float x, float y, float z) { \ + _impl->setCenter(x, y, z); \ +} \ +uint32_t CLASS::getGroup() { \ + return _impl->getGroup(); \ +} \ + \ +void CLASS::setGroup(uint32_t g) { \ + _impl->setGroup(g); \ +} \ + \ +uint32_t CLASS::getMask() { \ + return _impl->getMask(); \ +} \ + \ +void CLASS::setMask(uint32_t m) { \ + _impl->setMask(m); \ +} \ +void CLASS::updateEventListener(EShapeFilterFlag flag) { \ + _impl->updateEventListener(flag); \ +} \ +uint32_t CLASS::getObjectID()const { \ + return _impl->getObjectID(); \ +} \ + \ + +namespace cc { +namespace physics { + +/// COMMON /// +CC_PHYSICS_CCT_DEFINITION(CapsuleCharacterController, WrappedCapsuleCharacterController) +CC_PHYSICS_CCT_DEFINITION(BoxCharacterController, WrappedBoxCharacterController) + +/// EXTRAS /// + +void CapsuleCharacterController::setRadius(float v) { + _impl->setRadius(v); +} + +void CapsuleCharacterController::setHeight(float v) { + _impl->setHeight(v); +} + +void BoxCharacterController::setHalfHeight(float v) { + _impl->setHalfHeight(v); +} + +void BoxCharacterController::setHalfSideExtent(float v) { + _impl->setHalfSideExtent(v); +} + +void BoxCharacterController::setHalfForwardExtent(float v) { + _impl->setHalfForwardExtent(v); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/sdk/CharacterController.h b/cocos/physics/sdk/CharacterController.h new file mode 100644 index 0000000..e87e34d --- /dev/null +++ b/cocos/physics/sdk/CharacterController.h @@ -0,0 +1,80 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" +#include "core/scene-graph/Node.h" +#include "physics/spec/IShape.h" +#include "physics/spec/ICharacterController.h" + +#define CC_PHYSICS_CCT_CLASS(CLASS) \ + class CC_DLL CLASS final : public I##CLASS { \ + protected: \ + std::unique_ptr _impl; \ + \ + public: \ + CLASS(); \ + ~CLASS() override; \ + bool initialize(Node *node) override; \ + void onEnable() override; \ + void onDisable() override; \ + void onDestroy() override; \ + virtual cc::Vec3 getPosition() override; \ + virtual void setPosition(float x, float y, float z) override; \ + virtual bool onGround() override; \ + virtual void move(float x, float y, float z, float minDist, \ + float elapsedTime) override; \ + void syncPhysicsToScene() override; \ + virtual void setStepOffset(float v) override; \ + virtual float getStepOffset() override; \ + virtual void setSlopeLimit(float v) override; \ + virtual float getSlopeLimit() override; \ + virtual void setContactOffset(float v) override; \ + virtual float getContactOffset() override; \ + virtual void setDetectCollisions(bool v) override; \ + virtual void setOverlapRecovery(bool v) override; \ + virtual void setCenter(float x, float y, float z) override; \ + uint32_t getGroup() override; \ + void setGroup(uint32_t g) override; \ + uint32_t getMask() override; \ + void setMask(uint32_t m) override; \ + void updateEventListener(EShapeFilterFlag flag) override; \ + uint32_t getObjectID() const override; + +namespace cc { +namespace physics { + CC_PHYSICS_CCT_CLASS(CapsuleCharacterController) + void setRadius(float v) override; + void setHeight(float v) override; + }; + + CC_PHYSICS_CCT_CLASS(BoxCharacterController) + void setHalfHeight(float v) override; + void setHalfSideExtent(float v) override; + void setHalfForwardExtent(float v) override; +}; +}; // namespace physics +} // namespace cc diff --git a/cocos/physics/sdk/Joint.cpp b/cocos/physics/sdk/Joint.cpp new file mode 100644 index 0000000..373ed40 --- /dev/null +++ b/cocos/physics/sdk/Joint.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/sdk/Joint.h" +#include "base/memory/Memory.h" +#include "physics/PhysicsSelector.h" + +#define CC_PHYSICS_JOINT_DEFINITION(CLASS, WRAPPED) \ + \ + CLASS::CLASS() { \ + _impl.reset(ccnew WRAPPED()); \ + } \ + \ + CLASS::~CLASS() { \ + _impl.reset(nullptr); \ + } \ + \ + void CLASS::initialize(Node *node) { \ + _impl->initialize(node); \ + } \ + \ + void CLASS::onEnable() { \ + _impl->onEnable(); \ + } \ + \ + void CLASS::onDisable() { \ + _impl->onDisable(); \ + } \ + \ + void CLASS::onDestroy() { \ + _impl->onDestroy(); \ + } \ + \ + void CLASS::setConnectedBody(uint32_t rigidBodyID) { \ + _impl->setConnectedBody(rigidBodyID); \ + } \ + \ + void CLASS::setEnableCollision(bool v) { \ + _impl->setEnableCollision(v); \ + } \ + uint32_t CLASS::getObjectID() const { \ + return _impl->getObjectID(); \ + } + +namespace cc { +namespace physics { + +/// COMMON /// + +CC_PHYSICS_JOINT_DEFINITION(SphericalJoint, WrappedSphericalJoint) +CC_PHYSICS_JOINT_DEFINITION(RevoluteJoint, WrappedRevoluteJoint) +CC_PHYSICS_JOINT_DEFINITION(FixedJoint, WrappedFixedJoint) +CC_PHYSICS_JOINT_DEFINITION(GenericJoint, WrappedGenericJoint) + +/// EXTRAS /// + +void SphericalJoint::setPivotA(float x, float y, float z) { + _impl->setPivotA(x, y, z); +} + +void SphericalJoint::setPivotB(float x, float y, float z) { + _impl->setPivotB(x, y, z); +} + +void RevoluteJoint::setPivotA(float x, float y, float z) { + _impl->setPivotA(x, y, z); +} + +void RevoluteJoint::setPivotB(float x, float y, float z) { + _impl->setPivotB(x, y, z); +} + +void RevoluteJoint::setAxis(float x, float y, float z) { + _impl->setAxis(x, y, z); +} + +void RevoluteJoint::setLimitEnabled(bool v) { + _impl->setLimitEnabled(v); +} + +void RevoluteJoint::setLowerLimit(float v) { + _impl->setLowerLimit(v); +} + +void RevoluteJoint::setUpperLimit(float v) { + _impl->setUpperLimit(v); +} + +void RevoluteJoint::setMotorEnabled(bool v) { + _impl->setMotorEnabled(v); +} + +void RevoluteJoint::setMotorVelocity(float v) { + _impl->setMotorVelocity(v); +} + +void RevoluteJoint::setMotorForceLimit(float v) { + _impl->setMotorForceLimit(v); +} + +void FixedJoint::setBreakForce(float force) { + _impl->setBreakForce(force); +} + +void FixedJoint::setBreakTorque(float torque) { + _impl->setBreakTorque(torque); +} + +void GenericJoint::setConstraintMode(uint32_t index, uint32_t mode) { + _impl->setConstraintMode(index, mode); +} +void GenericJoint::setLinearLimit(uint32_t index, float lower, float upper) { + _impl->setLinearLimit(index, lower, upper); +} + +void GenericJoint::setAngularExtent(float twist, float swing1, float swing2) { + _impl->setAngularExtent(twist, swing1, swing2); +} +void GenericJoint::setLinearSoftConstraint(bool enable) { + _impl->setLinearSoftConstraint(enable); +} +void GenericJoint::setLinearStiffness(float stiffness) { + _impl->setLinearStiffness(stiffness); +} +void GenericJoint::setLinearDamping(float damping) { + _impl->setLinearDamping(damping); +} +void GenericJoint::setLinearRestitution(float restitution) { + _impl->setLinearRestitution(restitution); +} + +void GenericJoint::setSwingSoftConstraint(bool enable) { + _impl->setSwingSoftConstraint(enable); +} +void GenericJoint::setTwistSoftConstraint(bool enable) { + _impl->setTwistSoftConstraint(enable); +} +void GenericJoint::setSwingStiffness(float stiffness) { + _impl->setSwingStiffness(stiffness); +} +void GenericJoint::setSwingDamping(float damping) { + _impl->setSwingDamping(damping); +} +void GenericJoint::setSwingRestitution(float restitution) { + _impl->setSwingRestitution(restitution); +} +void GenericJoint::setTwistStiffness(float stiffness) { + _impl->setTwistStiffness(stiffness); +} +void GenericJoint::setTwistDamping(float damping) { + _impl->setTwistDamping(damping); +} +void GenericJoint::setTwistRestitution(float restitution) { + _impl->setTwistRestitution(restitution); +} + +void GenericJoint::setDriverMode(uint32_t index, uint32_t mode) { + _impl->setDriverMode(index, mode); +} +void GenericJoint::setLinearMotorTarget(float x, float y, float z) { + _impl->setLinearMotorTarget(x, y, z); +} +void GenericJoint::setLinearMotorVelocity(float x, float y, float z) { + _impl->setLinearMotorVelocity(x, y, z); +} +void GenericJoint::setLinearMotorForceLimit(float limit) { + _impl->setLinearMotorForceLimit(limit); +} +void GenericJoint::setAngularMotorTarget(float x, float y, float z) { + _impl->setAngularMotorTarget(x, y, z); +} +void GenericJoint::setAngularMotorVelocity(float x, float y, float z) { + _impl->setAngularMotorVelocity(x, y, z); +} +void GenericJoint::setAngularMotorForceLimit(float limit) { + _impl->setAngularMotorForceLimit(limit); +} +void GenericJoint::setPivotA(float x, float y, float z) { + _impl->setPivotA(x, y, z); +} +void GenericJoint::setPivotB(float x, float y, float z) { + _impl->setPivotB(x, y, z); +} +void GenericJoint::setAutoPivotB(bool enable) { + _impl->setAutoPivotB(enable); +} +void GenericJoint::setAxis(float x, float y, float z) { + _impl->setAxis(x, y, z); +} +void GenericJoint::setSecondaryAxis(float x, float y, float z) { + _impl->setSecondaryAxis(x, y, z); +} + +void GenericJoint::setBreakForce(float force) { + _impl->setBreakForce(force); +} +void GenericJoint::setBreakTorque(float torque) { + _impl->setBreakTorque(torque); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/sdk/Joint.h b/cocos/physics/sdk/Joint.h new file mode 100644 index 0000000..ade1e9e --- /dev/null +++ b/cocos/physics/sdk/Joint.h @@ -0,0 +1,114 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/Macros.h" +#include "core/scene-graph/Node.h" +#include "physics/spec/IJoint.h" + +#define CC_PHYSICS_JOINT_CLASS(CLASS) \ + class CC_DLL CLASS final : virtual public I##CLASS { \ + protected: \ + std::unique_ptr _impl; \ + \ + public: \ + CLASS(); \ + ~CLASS() override; \ + void initialize(Node *node) override; \ + void onEnable() override; \ + void onDisable() override; \ + void onDestroy() override; \ + void setEnableCollision(bool v) override; \ + void setConnectedBody(uint32_t rigidBodyID) override; \ + uint32_t getObjectID() const override; +namespace cc { +namespace physics { + +CC_PHYSICS_JOINT_CLASS(RevoluteJoint) +void setPivotA(float x, float y, float z) override; +void setPivotB(float x, float y, float z) override; +void setAxis(float x, float y, float z) override; +void setLimitEnabled(bool v) override; +void setLowerLimit(float v) override; +void setUpperLimit(float v) override; +void setMotorEnabled(bool v) override; +void setMotorVelocity(float v) override; +void setMotorForceLimit(float v) override; +}; // RevoluteJoint + +CC_PHYSICS_JOINT_CLASS(SphericalJoint) +void setPivotA(float x, float y, float z) override; +void setPivotB(float x, float y, float z) override; +}; // SphericalJoint + +CC_PHYSICS_JOINT_CLASS(FixedJoint) +void setBreakForce(float force) override; +void setBreakTorque(float torque) override; +} +; // FixedJoint + +CC_PHYSICS_JOINT_CLASS(GenericJoint) +void setConstraintMode(uint32_t index, uint32_t mode) override; +void setLinearLimit(uint32_t index, float lower, float upper) override; +void setAngularExtent(float twist, float swing1, float swing2) override; +void setLinearSoftConstraint(bool enable) override; +void setLinearStiffness(float stiffness) override; +void setLinearDamping(float damping) override; +void setLinearRestitution(float restitution) override; + +void setSwingSoftConstraint(bool enable) override; +void setTwistSoftConstraint(bool enable) override; +void setSwingStiffness(float stiffness) override; +void setSwingDamping(float damping) override; +void setSwingRestitution(float restitution) override; +void setTwistStiffness(float stiffness) override; +void setTwistDamping(float damping) override; +void setTwistRestitution(float restitution) override; + +virtual void setDriverMode(uint32_t index, uint32_t mode) override; + +void setLinearMotorTarget(float x, float y, float z) override; +void setLinearMotorVelocity(float x, float y, float z) override; +void setLinearMotorForceLimit(float limit) override; + +void setAngularMotorTarget(float x, float y, float z) override; +void setAngularMotorVelocity(float x, float y, float z) override; +void setAngularMotorForceLimit(float limit) override; + +void setPivotA(float x, float y, float z) override; +void setPivotB(float x, float y, float z) override; +void setAutoPivotB(bool enable) override; +void setAxis(float x, float y, float z) override; +void setSecondaryAxis(float x, float y, float z) override; + +void setBreakForce(float force) override; +void setBreakTorque(float torque) override; +} +; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/sdk/RigidBody.cpp b/cocos/physics/sdk/RigidBody.cpp new file mode 100644 index 0000000..6f7b557 --- /dev/null +++ b/cocos/physics/sdk/RigidBody.cpp @@ -0,0 +1,193 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/sdk/RigidBody.h" +#include +#include "physics/PhysicsSelector.h" + +namespace cc { +namespace physics { + +RigidBody::RigidBody() { + _impl = std::make_unique(); +} + +RigidBody::~RigidBody() { + _impl.reset(nullptr); +} + +void RigidBody::initialize(Node *node, const ERigidBodyType t, const uint32_t g) { + _impl->initialize(node, t, g); +} + +void RigidBody::onEnable() { + _impl->onEnable(); +} + +void RigidBody::onDisable() { + _impl->onDisable(); +} + +void RigidBody::onDestroy() { + _impl->onDestroy(); +} + +bool RigidBody::isAwake() { + return _impl->isAwake(); +} + +bool RigidBody::isSleepy() { + return _impl->isSleepy(); +} + +bool RigidBody::isSleeping() { + return _impl->isSleeping(); +} + +void RigidBody::setType(ERigidBodyType v) { + _impl->setType(v); +} + +void RigidBody::setMass(float v) { + _impl->setMass(v); +} + +void RigidBody::setLinearDamping(float v) { + _impl->setLinearDamping(v); +} + +void RigidBody::setAngularDamping(float v) { + _impl->setAngularDamping(v); +} + +void RigidBody::useCCD(bool v) { + _impl->useCCD(v); +} + +void RigidBody::useGravity(bool v) { + _impl->useGravity(v); +} + +void RigidBody::setLinearFactor(float x, float y, float z) { + _impl->setLinearFactor(x, y, z); +} + +void RigidBody::setAngularFactor(float x, float y, float z) { + _impl->setAngularFactor(x, y, z); +} + +void RigidBody::setAllowSleep(bool v) { + _impl->setAllowSleep(v); +} + +void RigidBody::wakeUp() { + _impl->wakeUp(); +} + +void RigidBody::sleep() { + _impl->sleep(); +} + +void RigidBody::clearState() { + _impl->clearState(); +} + +void RigidBody::clearForces() { + _impl->clearForces(); +} + +void RigidBody::clearVelocity() { + _impl->clearVelocity(); +} + +void RigidBody::setSleepThreshold(float v) { + _impl->setSleepThreshold(v); +} + +float RigidBody::getSleepThreshold() { + return _impl->getSleepThreshold(); +} + +cc::Vec3 RigidBody::getLinearVelocity() { + return _impl->getLinearVelocity(); +} + +void RigidBody::setLinearVelocity(float x, float y, float z) { + _impl->setLinearVelocity(x, y, z); +} + +cc::Vec3 RigidBody::getAngularVelocity() { + return _impl->getAngularVelocity(); +} + +void RigidBody::setAngularVelocity(float x, float y, float z) { + _impl->setAngularVelocity(x, y, z); +} + +void RigidBody::applyForce(float x, float y, float z, float rx, float ry, float rz) { + _impl->applyForce(x, y, z, rx, ry, rz); +} + +void RigidBody::applyLocalForce(float x, float y, float z, float rx, float ry, float rz) { + _impl->applyLocalForce(x, y, z, rx, ry, rz); +} + +void RigidBody::applyImpulse(float x, float y, float z, float rx, float ry, float rz) { + _impl->applyImpulse(x, y, z, rx, ry, rz); +} + +void RigidBody::applyLocalImpulse(float x, float y, float z, float rx, float ry, float rz) { + _impl->applyLocalImpulse(x, y, z, rx, ry, rz); +} + +void RigidBody::applyTorque(float x, float y, float z) { + _impl->applyTorque(x, y, z); +} + +void RigidBody::applyLocalTorque(float x, float y, float z) { + _impl->applyLocalTorque(x, y, z); +} + +uint32_t RigidBody::getGroup() { + return _impl->getGroup(); +} + +void RigidBody::setGroup(uint32_t g) { + _impl->setGroup(g); +} + +uint32_t RigidBody::getMask() { + return _impl->getMask(); +} + +void RigidBody::setMask(uint32_t m) { + _impl->setMask(m); +} + +uint32_t RigidBody::getObjectID() const { + return _impl->getObjectID(); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/sdk/RigidBody.h b/cocos/physics/sdk/RigidBody.h new file mode 100644 index 0000000..acc35e1 --- /dev/null +++ b/cocos/physics/sdk/RigidBody.h @@ -0,0 +1,80 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" +#include "physics/spec/IBody.h" + +namespace cc { +namespace physics { +class CC_DLL RigidBody final : public IRigidBody { +public: + RigidBody(); + ~RigidBody() override; + void initialize(Node *node, ERigidBodyType t, uint32_t g) override; + void onEnable() override; + void onDisable() override; + void onDestroy() override; + bool isAwake() override; + bool isSleepy() override; + bool isSleeping() override; + void setType(ERigidBodyType v) override; + void setMass(float v) override; + void setLinearDamping(float v) override; + void setAngularDamping(float v) override; + void useGravity(bool v) override; + void useCCD(bool v) override; + void setLinearFactor(float x, float y, float z) override; + void setAngularFactor(float x, float y, float z) override; + void setAllowSleep(bool v) override; + void wakeUp() override; + void sleep() override; + void clearState() override; + void clearForces() override; + void clearVelocity() override; + void setSleepThreshold(float v) override; + float getSleepThreshold() override; + cc::Vec3 getLinearVelocity() override; + void setLinearVelocity(float x, float y, float z) override; + cc::Vec3 getAngularVelocity() override; + void setAngularVelocity(float x, float y, float z) override; + void applyForce(float x, float y, float z, float rx, float ry, float rz) override; + void applyLocalForce(float x, float y, float z, float rx, float ry, float rz) override; + void applyImpulse(float x, float y, float z, float rx, float ry, float rz) override; + void applyLocalImpulse(float x, float y, float z, float rx, float ry, float rz) override; + void applyTorque(float x, float y, float z) override; + void applyLocalTorque(float x, float y, float z) override; + uint32_t getGroup() override; + void setGroup(uint32_t g) override; + uint32_t getMask() override; + void setMask(uint32_t m) override; + uint32_t getObjectID() const override; + +private: + std::unique_ptr _impl; +}; +} // namespace physics +} // namespace cc diff --git a/cocos/physics/sdk/Shape.cpp b/cocos/physics/sdk/Shape.cpp new file mode 100644 index 0000000..ff77b25 --- /dev/null +++ b/cocos/physics/sdk/Shape.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/sdk/Shape.h" +#include "base/memory/Memory.h" +#include "physics/PhysicsSelector.h" + +#define CC_PHYSICS_SHAPE_DEFINITION(CLASS, WRAPPED) \ + \ + CLASS::CLASS() { \ + _impl.reset(ccnew WRAPPED()); \ + } \ + \ + CLASS::~CLASS() { \ + _impl.reset(nullptr); \ + } \ + \ + void CLASS::initialize(Node *node) { \ + _impl->initialize(node); \ + } \ + \ + void CLASS::onEnable() { \ + _impl->onEnable(); \ + } \ + \ + void CLASS::onDisable() { \ + _impl->onDisable(); \ + } \ + \ + void CLASS::onDestroy() { \ + _impl->onDestroy(); \ + } \ + \ + void CLASS::setMaterial(uint16_t ID, float f, float df, float r, \ + uint8_t m0, uint8_t m1) { \ + _impl->setMaterial(ID, f, df, r, m0, m1); \ + } \ + \ + void CLASS::setAsTrigger(bool v) { \ + _impl->setAsTrigger(v); \ + } \ + \ + void CLASS::setCenter(float x, float y, float z) { \ + _impl->setCenter(x, y, z); \ + } \ + \ + void CLASS::setGroup(uint32_t g) { \ + _impl->setGroup(g); \ + } \ + \ + uint32_t CLASS::getGroup() { \ + return _impl->getGroup(); \ + } \ + \ + void CLASS::setMask(uint32_t m) { \ + _impl->setMask(m); \ + } \ + \ + uint32_t CLASS::getMask() { \ + return _impl->getMask(); \ + } \ + \ + void CLASS::updateEventListener(EShapeFilterFlag v) { \ + _impl->updateEventListener(v); \ + } \ + \ + geometry::AABB &CLASS::getAABB() { \ + return _impl->getAABB(); \ + } \ + \ + geometry::Sphere &CLASS::getBoundingSphere() { \ + return _impl->getBoundingSphere(); \ + } \ + \ + uint32_t CLASS::getObjectID() const { \ + return _impl->getObjectID(); \ + } + +namespace cc { +namespace physics { + +/// COMMON /// + +CC_PHYSICS_SHAPE_DEFINITION(SphereShape, WrappedSphereShape) +CC_PHYSICS_SHAPE_DEFINITION(BoxShape, WrappedBoxShape) +CC_PHYSICS_SHAPE_DEFINITION(PlaneShape, WrappedPlaneShape) +CC_PHYSICS_SHAPE_DEFINITION(CapsuleShape, WrappedCapsuleShape) +CC_PHYSICS_SHAPE_DEFINITION(CylinderShape, WrappedCylinderShape) +CC_PHYSICS_SHAPE_DEFINITION(ConeShape, WrappedConeShape) +CC_PHYSICS_SHAPE_DEFINITION(TrimeshShape, WrappedTrimeshShape) +CC_PHYSICS_SHAPE_DEFINITION(TerrainShape, WrappedTerrainShape) + +/// EXTRAS /// + +void SphereShape::setRadius(float v) { + _impl->setRadius(v); +} + +void BoxShape::setSize(float x, float y, float z) { + _impl->setSize(x, y, z); +} + +void CapsuleShape::setRadius(float v) { + _impl->setRadius(v); +} + +void CapsuleShape::setCylinderHeight(float v) { + _impl->setCylinderHeight(v); +} + +void CapsuleShape::setDirection(EAxisDirection v) { + _impl->setDirection(v); +} + +void PlaneShape::setConstant(float v) { + _impl->setConstant(v); +} + +void PlaneShape::setNormal(float x, float y, float z) { + _impl->setNormal(x, y, z); +} + +void TrimeshShape::setMesh(uint32_t objectID) { + _impl->setMesh(objectID); +} + +void TrimeshShape::useConvex(bool v) { + _impl->useConvex(v); +} + +void TerrainShape::setTerrain(uint32_t objectID, float rs, float cs, float hs) { + _impl->setTerrain(objectID, rs, cs, hs); +} + +void CylinderShape::setConvex(uint32_t objectID) { + _impl->setConvex(objectID); +} + +void CylinderShape::setCylinder(float r, float h, EAxisDirection d) { + _impl->setCylinder(r, h, d); +} + +void ConeShape::setConvex(uint32_t objectID) { + _impl->setConvex(objectID); +} + +void ConeShape::setCone(float r, float h, EAxisDirection d) { + _impl->setCone(r, h, d); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/sdk/Shape.h b/cocos/physics/sdk/Shape.h new file mode 100644 index 0000000..735b681 --- /dev/null +++ b/cocos/physics/sdk/Shape.h @@ -0,0 +1,107 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/Macros.h" +#include "core/geometry/AABB.h" +#include "core/geometry/Sphere.h" +#include "physics/spec/IShape.h" + +#define CC_PHYSICS_SHAPE_CLASS(CLASS) \ + class CC_DLL CLASS final : public I##CLASS { \ + protected: \ + std::unique_ptr _impl; \ + \ + public: \ + CLASS(); \ + ~CLASS() override; \ + void initialize(Node *node) override; \ + void onEnable() override; \ + void onDisable() override; \ + void onDestroy() override; \ + void setMaterial(uint16_t ID, float f, float df, float r, \ + uint8_t m0, uint8_t m1) override; \ + void setAsTrigger(bool v) override; \ + void setCenter(float x, float y, float z) override; \ + void updateEventListener(EShapeFilterFlag v) override; \ + geometry::AABB &getAABB() override; \ + geometry::Sphere &getBoundingSphere() override; \ + uint32_t getGroup() override; \ + void setGroup(uint32_t g) override; \ + uint32_t getMask() override; \ + void setMask(uint32_t m) override; \ + uint32_t getObjectID() const override; + +namespace cc { +namespace physics { + +CC_PHYSICS_SHAPE_CLASS(SphereShape) +void setRadius(float v) override; +}; // namespace physics + +CC_PHYSICS_SHAPE_CLASS(BoxShape) +void setSize(float x, float y, float z) override; +}; // namespace cc + +CC_PHYSICS_SHAPE_CLASS(CapsuleShape) +void setRadius(float v) override; +void setCylinderHeight(float v) override; +void setDirection(EAxisDirection v) override; +} +; + +CC_PHYSICS_SHAPE_CLASS(PlaneShape) +void setConstant(float v) override; +void setNormal(float x, float y, float z) override; +} +; + +CC_PHYSICS_SHAPE_CLASS(TrimeshShape) +void setMesh(uint32_t objectID) override; +void useConvex(bool v) override; +} +; + +CC_PHYSICS_SHAPE_CLASS(CylinderShape) +void setConvex(uint32_t objectID) override; +void setCylinder(float r, float h, EAxisDirection d) override; +} +; + +CC_PHYSICS_SHAPE_CLASS(ConeShape) +void setConvex(uint32_t objectID) override; +void setCone(float r, float h, EAxisDirection d) override; +} +; + +CC_PHYSICS_SHAPE_CLASS(TerrainShape) +void setTerrain(uint32_t objectID, float rs, float cs, float hs) override; +} +; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/sdk/World.cpp b/cocos/physics/sdk/World.cpp new file mode 100644 index 0000000..5bdbeeb --- /dev/null +++ b/cocos/physics/sdk/World.cpp @@ -0,0 +1,182 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "physics/sdk/World.h" +#include +#include "physics/PhysicsSelector.h" + +namespace cc { +namespace physics { + +World::World() { + _impl = std::make_unique(); +} + +World::~World() { + _impl.reset(nullptr); +} + +void World::emitEvents() { + _impl->emitEvents(); +} + +void World::step(float fixedTimeStep) { + _impl->step(fixedTimeStep); +} + +void World::setAllowSleep(bool v) { + _impl->setAllowSleep(v); +} + +void World::setGravity(float x, float y, float z) { + _impl->setGravity(x, y, z); +} + +bool World::createMaterial(uint16_t id, float f, float df, float r, + uint8_t m0, uint8_t m1) { + return _impl->createMaterial(id, f, df, r, m0, m1); +} + +void World::syncSceneToPhysics() { + _impl->syncSceneToPhysics(); +} + +void World::syncSceneWithCheck() { + _impl->syncSceneWithCheck(); +} + +void World::destroy() { + _impl->destroy(); +} + +ccstd::vector> &World::getTriggerEventPairs() { + return _impl->getTriggerEventPairs(); +} + +ccstd::vector> &World::getContactEventPairs() { + return _impl->getContactEventPairs(); +} + +ccstd::vector>& World::getCCTShapeEventPairs() { + return _impl->getCCTShapeEventPairs(); +} + +ccstd::vector> &World::getCCTTriggerEventPairs() { + return _impl->getCCTTriggerEventPairs(); +} + +void World::setDebugDrawFlags(EPhysicsDrawFlags flags) { + _impl->setDebugDrawFlags(flags); +} + +EPhysicsDrawFlags World::getDebugDrawFlags() { + return _impl->getDebugDrawFlags(); +} + +void World::setDebugDrawConstraintSize(float size) { + _impl->setDebugDrawConstraintSize(size); +} + +float World::getDebugDrawConstraintSize() { + return _impl->getDebugDrawConstraintSize(); +} + +void World::setCollisionMatrix(uint32_t i, uint32_t m) { + _impl->setCollisionMatrix(i, m); +} + +uint32_t World::createConvex(ConvexDesc &desc) { + return _impl->createConvex(desc); +} + +uint32_t World::createTrimesh(TrimeshDesc &desc) { + return _impl->createTrimesh(desc); +} + +uint32_t World::createHeightField(HeightFieldDesc &desc) { + return _impl->createHeightField(desc); +} + +bool World::raycast(RaycastOptions &opt) { + return _impl->raycast(opt); +} + +ccstd::vector &World::raycastResult() { + return _impl->raycastResult(); +} + +bool World::raycastClosest(RaycastOptions &opt) { + return _impl->raycastClosest(opt); +} + +RaycastResult &World::raycastClosestResult() { + return _impl->raycastClosestResult(); +} + +float World::getFixedTimeStep() const { + return _impl->getFixedTimeStep(); +} + +void World::setFixedTimeStep(float fixedTimeStep) { + _impl->setFixedTimeStep(fixedTimeStep); +} + +bool World::sweepBox(RaycastOptions &opt, float halfExtentX, float halfExtentY, float halfExtentZ, + float orientationW, float orientationX, float orientationY, float orientationZ){ + return _impl->sweepBox(opt, halfExtentX, halfExtentY, halfExtentZ, orientationW, orientationX, orientationY, orientationZ); +} + +bool World::sweepBoxClosest(RaycastOptions &opt, float halfExtentX, float halfExtentY, float halfExtentZ, + float orientationW, float orientationX, float orientationY, float orientationZ){ + return _impl->sweepBoxClosest(opt, halfExtentX, halfExtentY, halfExtentZ, orientationW, orientationX, orientationY, orientationZ); +} + +bool World::sweepSphere(RaycastOptions &opt, float radius) { + return _impl->sweepSphere(opt, radius); +} + +bool World::sweepSphereClosest(RaycastOptions &opt, float radius) { + return _impl->sweepSphereClosest(opt, radius); +} + +bool World::sweepCapsule(RaycastOptions &opt, float radius, float height, + float orientationW, float orientationX, float orientationY, float orientationZ) { + return _impl->sweepCapsule(opt, radius, height, orientationW, orientationX, orientationY, orientationZ); +} + +bool World::sweepCapsuleClosest(RaycastOptions &opt, float radius, float height, + float orientationW, float orientationX, float orientationY, float orientationZ) { + return _impl->sweepCapsuleClosest(opt, radius, height, orientationW, orientationX, orientationY, orientationZ); +} + +RaycastResult &World::sweepClosestResult() { + return _impl->sweepClosestResult(); +} + +ccstd::vector &World::sweepResult() { + return _impl->sweepResult(); +} + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/sdk/World.h b/cocos/physics/sdk/World.h new file mode 100644 index 0000000..dec4400 --- /dev/null +++ b/cocos/physics/sdk/World.h @@ -0,0 +1,83 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" +#include "physics/spec/IWorld.h" + +namespace cc { +namespace physics { +class CC_DLL World final : public IPhysicsWorld { +public: + World(); + ~World() override; + void setGravity(float x, float y, float z) override; + void setAllowSleep(bool v) override; + void step(float fixedTimeStep) override; + void emitEvents() override; + void syncSceneToPhysics() override; + void syncSceneWithCheck() override; + void setDebugDrawFlags(EPhysicsDrawFlags flags) override; + EPhysicsDrawFlags getDebugDrawFlags() override; + void setDebugDrawConstraintSize(float size) override; + float getDebugDrawConstraintSize() override; + void setCollisionMatrix(uint32_t i, uint32_t m) override; + ccstd::vector> &getTriggerEventPairs() override; + ccstd::vector>& getContactEventPairs() override; + ccstd::vector>& getCCTShapeEventPairs() override; + ccstd::vector>& getCCTTriggerEventPairs() override; + bool raycast(RaycastOptions &opt) override; + bool raycastClosest(RaycastOptions &opt) override; + ccstd::vector &raycastResult() override; + RaycastResult &raycastClosestResult() override; + bool sweepBox(RaycastOptions &opt, float halfExtentX, float halfExtentY, float halfExtentZ, + float orientationW, float orientationX, float orientationY, float orientationZ) override; + bool sweepBoxClosest(RaycastOptions &opt, float halfExtentX, float halfExtentY, float halfExtentZ, + float orientationW, float orientationX, float orientationY, float orientationZ) override; + bool sweepSphere(RaycastOptions &opt, float radius) override; + bool sweepSphereClosest(RaycastOptions &opt, float radius) override; + bool sweepCapsule(RaycastOptions &opt, float radius, float height, + float orientationW, float orientationX, float orientationY, float orientationZ) override; + bool sweepCapsuleClosest(RaycastOptions &opt, float radius, float height, + float orientationW, float orientationX, float orientationY, float orientationZ) override; + RaycastResult &sweepClosestResult() override; + ccstd::vector &sweepResult() override; + + uint32_t createConvex(ConvexDesc &desc) override; + uint32_t createTrimesh(TrimeshDesc &desc) override; + uint32_t createHeightField(HeightFieldDesc &desc) override; + bool createMaterial(uint16_t id, float f, float df, float r, + uint8_t m0, uint8_t m1) override; + float getFixedTimeStep() const override; + void setFixedTimeStep(float fixedTimeStep) override; + + void destroy() override; + +private: + std::unique_ptr _impl; +}; +} // namespace physics +} // namespace cc diff --git a/cocos/physics/spec/IBody.h b/cocos/physics/spec/IBody.h new file mode 100644 index 0000000..6e79161 --- /dev/null +++ b/cocos/physics/spec/IBody.h @@ -0,0 +1,81 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/scene-graph/Node.h" +#include "math/Vec3.h" +#include "physics/spec/ILifecycle.h" + +namespace cc { +namespace physics { + +enum class ERigidBodyType : uint8_t { + DYNAMIC = 1, + STATIC = 2, + KINEMATIC = 4, +}; + +class IRigidBody : public ILifecycle { +public: + ~IRigidBody() override = default; + virtual void initialize(Node *node, ERigidBodyType t, uint32_t g) = 0; + virtual bool isAwake() = 0; + virtual bool isSleepy() = 0; + virtual bool isSleeping() = 0; + virtual void setType(ERigidBodyType v) = 0; + virtual void setMass(float v) = 0; + virtual void setLinearDamping(float v) = 0; + virtual void setAngularDamping(float v) = 0; + virtual void useGravity(bool v) = 0; + virtual void useCCD(bool v) = 0; + virtual void setLinearFactor(float x, float y, float z) = 0; + virtual void setAngularFactor(float x, float y, float z) = 0; + virtual void setAllowSleep(bool v) = 0; + virtual void wakeUp() = 0; + virtual void sleep() = 0; + virtual void clearState() = 0; + virtual void clearForces() = 0; + virtual void clearVelocity() = 0; + virtual void setSleepThreshold(float v) = 0; + virtual float getSleepThreshold() = 0; + virtual cc::Vec3 getLinearVelocity() = 0; + virtual void setLinearVelocity(float x, float y, float z) = 0; + virtual cc::Vec3 getAngularVelocity() = 0; + virtual void setAngularVelocity(float x, float y, float z) = 0; + virtual void applyForce(float x, float y, float z, float rx, float ry, float rz) = 0; + virtual void applyLocalForce(float x, float y, float z, float rx, float ry, float rz) = 0; + virtual void applyImpulse(float x, float y, float z, float rx, float ry, float rz) = 0; + virtual void applyLocalImpulse(float x, float y, float z, float rx, float ry, float rz) = 0; + virtual void applyTorque(float x, float y, float z) = 0; + virtual void applyLocalTorque(float x, float y, float z) = 0; + virtual uint32_t getGroup() = 0; + virtual void setGroup(uint32_t g) = 0; + virtual uint32_t getMask() = 0; + virtual void setMask(uint32_t m) = 0; + virtual uint32_t getObjectID() const = 0; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/spec/ICharacterController.h b/cocos/physics/spec/ICharacterController.h new file mode 100644 index 0000000..26adcfb --- /dev/null +++ b/cocos/physics/spec/ICharacterController.h @@ -0,0 +1,81 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "core/scene-graph/Node.h" +#include "math/Vec3.h" +#include "physics/spec/ILifecycle.h" +#include "physics/spec/IShape.h" + +namespace cc { +namespace physics { + +class IBaseCharacterController : virtual public ILifecycle { +public: + ~IBaseCharacterController() override = default; + virtual bool initialize(Node *node) = 0; + + virtual void setStepOffset(float v) = 0; + virtual float getStepOffset() = 0; + virtual void setSlopeLimit(float v) = 0; + virtual float getSlopeLimit() = 0; + virtual void setContactOffset(float v) = 0; + virtual float getContactOffset() = 0; + virtual void setDetectCollisions(bool v) = 0; + virtual void setOverlapRecovery(bool v) = 0; + virtual void setCenter(float x, float y, float z) = 0; + + virtual cc::Vec3 getPosition() = 0; + virtual void setPosition(float x, float y, float z) = 0; + virtual bool onGround() = 0; + virtual void move(float x, float y, float z, float minDist, float elapsedTime) = 0; + virtual void syncPhysicsToScene() = 0; + + virtual uint32_t getGroup() = 0; + virtual void setGroup(uint32_t g) = 0; + virtual uint32_t getMask() = 0; + virtual void setMask(uint32_t m) = 0; + virtual void updateEventListener(EShapeFilterFlag flag) = 0; + + virtual uint32_t getObjectID() const = 0; +}; + +class ICapsuleCharacterController : virtual public IBaseCharacterController { +public: + ~ICapsuleCharacterController() override = default; + virtual void setRadius(float v) = 0; + virtual void setHeight(float v) = 0; +}; + +class IBoxCharacterController : virtual public IBaseCharacterController { +public: + ~IBoxCharacterController() override = default; + virtual void setHalfHeight(float v) = 0; + virtual void setHalfSideExtent(float v) = 0; + virtual void setHalfForwardExtent(float v) = 0; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/spec/IJoint.h b/cocos/physics/spec/IJoint.h new file mode 100644 index 0000000..9e25a2d --- /dev/null +++ b/cocos/physics/spec/IJoint.h @@ -0,0 +1,113 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "core/scene-graph/Node.h" +#include "physics/spec/ILifecycle.h" + +namespace cc { +namespace physics { + +class IBaseJoint : virtual public ILifecycle { +public: + ~IBaseJoint() override = default; + virtual void initialize(Node *node) = 0; + virtual void setEnableCollision(bool v) = 0; + virtual void setConnectedBody(uint32_t rigidBodyID) = 0; + virtual uint32_t getObjectID() const = 0; +}; + +class ISphericalJoint : virtual public IBaseJoint { +public: + ~ISphericalJoint() override = default; + virtual void setPivotA(float x, float y, float z) = 0; + virtual void setPivotB(float x, float y, float z) = 0; +}; + +class IRevoluteJoint : virtual public IBaseJoint { +public: + ~IRevoluteJoint() override = default; + virtual void setPivotA(float x, float y, float z) = 0; + virtual void setPivotB(float x, float y, float z) = 0; + virtual void setAxis(float x, float y, float z) = 0; + virtual void setLimitEnabled(bool v) = 0; + virtual void setLowerLimit(float v) = 0; + virtual void setUpperLimit(float v) = 0; + virtual void setMotorEnabled(bool v) = 0; + virtual void setMotorVelocity(float v) = 0; + virtual void setMotorForceLimit(float v) = 0; +}; + +class IFixedJoint : virtual public IBaseJoint { +public: + ~IFixedJoint() override = default; + virtual void setBreakForce(float force) = 0; + virtual void setBreakTorque(float torque) = 0; +}; + +class IGenericJoint : virtual public IBaseJoint { +public: + ~IGenericJoint() override = default; + + virtual void setConstraintMode(uint32_t index, uint32_t mode) = 0; + virtual void setLinearLimit(uint32_t index, float lower, float upper) = 0; + virtual void setAngularExtent(float twist, float swing1, float swing2) = 0; + virtual void setLinearSoftConstraint(bool enable) = 0; + virtual void setLinearStiffness(float stiffness) = 0; + virtual void setLinearDamping(float damping) = 0; + virtual void setLinearRestitution(float restitution) = 0; + + virtual void setSwingSoftConstraint(bool enable) = 0; + virtual void setTwistSoftConstraint(bool enable) = 0; + virtual void setSwingStiffness(float stiffness) = 0; + virtual void setSwingDamping(float damping) = 0; + virtual void setSwingRestitution(float restitution) = 0; + virtual void setTwistStiffness(float stiffness) = 0; + virtual void setTwistDamping(float damping) = 0; + virtual void setTwistRestitution(float restitution) = 0; + + // motor + virtual void setDriverMode(uint32_t index, uint32_t mode) = 0; + virtual void setLinearMotorTarget(float x, float y, float z) = 0; + virtual void setLinearMotorVelocity(float x, float y, float z) = 0; + virtual void setLinearMotorForceLimit(float limit) = 0; + + virtual void setAngularMotorTarget(float x, float y, float z) = 0; + virtual void setAngularMotorVelocity(float x, float y, float z) = 0; + virtual void setAngularMotorForceLimit(float limit) = 0; + + virtual void setPivotA(float x, float y, float z) = 0; + virtual void setPivotB(float x, float y, float z) = 0; + virtual void setAutoPivotB(bool enable) = 0; + virtual void setAxis(float x, float y, float z) = 0; + virtual void setSecondaryAxis(float x, float y, float z) = 0; + + virtual void setBreakForce(float force) = 0; + virtual void setBreakTorque(float torque) = 0; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/spec/ILifecycle.h b/cocos/physics/spec/ILifecycle.h new file mode 100644 index 0000000..5252264 --- /dev/null +++ b/cocos/physics/spec/ILifecycle.h @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/TypeDef.h" + +namespace cc { +namespace physics { +class ILifecycle { +public: + virtual ~ILifecycle() = default; + virtual void onEnable() = 0; + virtual void onDisable() = 0; + virtual void onDestroy() = 0; +}; +} // namespace physics +} // namespace cc diff --git a/cocos/physics/spec/IShape.h b/cocos/physics/spec/IShape.h new file mode 100644 index 0000000..b948a96 --- /dev/null +++ b/cocos/physics/spec/IShape.h @@ -0,0 +1,130 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "core/geometry/AABB.h" +#include "core/geometry/Sphere.h" +#include "core/scene-graph/Node.h" +#include "physics/spec/ILifecycle.h" + +namespace cc { +namespace physics { + +enum class EAxisDirection : uint8_t { + X_AXIS, + Y_AXIS, + Z_AXIS, +}; + +enum class EShapeFilterFlag : uint8_t { + NONE = 0, + IS_TRIGGER = 1 << 0, + NEED_EVENT = 1 << 1, + NEED_CONTACT_DATA = 1 << 2, + DETECT_CONTACT_CCD = 1 << 3, +}; + +class IBaseShape : virtual public ILifecycle { +public: + ~IBaseShape() override = default; + ; + virtual void initialize(Node *node) = 0; + virtual void setMaterial(uint16_t id, float f, float df, float r, + uint8_t m0, uint8_t m1) = 0; + virtual void setAsTrigger(bool v) = 0; + virtual void setCenter(float x, float y, float z) = 0; + virtual geometry::AABB &getAABB() = 0; + virtual geometry::Sphere &getBoundingSphere() = 0; + virtual void updateEventListener(EShapeFilterFlag flag) = 0; + virtual uint32_t getGroup() = 0; + virtual void setGroup(uint32_t g) = 0; + virtual uint32_t getMask() = 0; + virtual void setMask(uint32_t m) = 0; + virtual uint32_t getObjectID() const = 0; +}; + +class ISphereShape : virtual public IBaseShape { +public: + ~ISphereShape() override = default; + ; + virtual void setRadius(float v) = 0; +}; + +class IBoxShape : virtual public IBaseShape { +public: + ~IBoxShape() override = default; + ; + virtual void setSize(float x, float y, float z) = 0; +}; + +class ICapsuleShape : virtual public IBaseShape { +public: + ~ICapsuleShape() override = default; + ; + virtual void setRadius(float v) = 0; + virtual void setCylinderHeight(float v) = 0; + virtual void setDirection(EAxisDirection v) = 0; +}; + +class ICylinderShape : virtual public IBaseShape { +public: + ~ICylinderShape() override = default; + ; + virtual void setConvex(uint32_t ObjectID) = 0; + virtual void setCylinder(float r, float h, EAxisDirection d) = 0; +}; + +class IConeShape : virtual public IBaseShape { +public: + ~IConeShape() override = default; + ; + virtual void setConvex(uint32_t ObjectID) = 0; + virtual void setCone(float r, float h, EAxisDirection d) = 0; +}; + +class IPlaneShape : virtual public IBaseShape { +public: + ~IPlaneShape() override = default; + ; + virtual void setConstant(float v) = 0; + virtual void setNormal(float x, float y, float z) = 0; +}; + +class ITrimeshShape : virtual public IBaseShape { +public: + ~ITrimeshShape() override = default; + ; + virtual void setMesh(uint32_t ObjectID) = 0; + virtual void useConvex(bool v) = 0; +}; + +class ITerrainShape : virtual public IBaseShape { +public: + virtual void setTerrain(uint32_t ObjectID, float rs, float cs, float hs) = 0; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/physics/spec/IWorld.h b/cocos/physics/spec/IWorld.h new file mode 100644 index 0000000..e0c9d8c --- /dev/null +++ b/cocos/physics/spec/IWorld.h @@ -0,0 +1,190 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/TypeDef.h" +#include "base/std/container/vector.h" +#include "math/Vec3.h" + +namespace cc { +namespace physics { + +enum class ETouchState : uint8_t { + ENTER = 0, + STAY = 1, + EXIT = 2, +}; + +enum class EPhysicsDrawFlags : uint32_t { + NONE = 0, + WIRE_FRAME = 0x0001, + CONSTRAINT = 0x0002, + AABB = 0x0004 +}; + +struct TriggerEventPair { + uint32_t shapeA; //wrapper object ID + uint32_t shapeB; //wrapper object ID + ETouchState state; + static constexpr uint8_t COUNT = 3; + TriggerEventPair(const uint32_t a, const uint32_t b) + : shapeA(a), + shapeB(b), + state(ETouchState::ENTER) {} +}; + +struct ContactPoint { + Vec3 position; + float separation; + Vec3 normal; + uint32_t internalFaceIndex0; + Vec3 impulse; + uint32_t internalFaceIndex1; + static constexpr uint8_t COUNT = 12; +}; + +struct ContactEventPair { + uint32_t shapeA; //wrapper object ID + uint32_t shapeB; //wrapper object ID + ETouchState state; + ccstd::vector contacts; + static constexpr uint8_t COUNT = 4; + ContactEventPair(const uint32_t a, const uint32_t b) + : shapeA(a), + shapeB(b), + state(ETouchState::ENTER) {} +}; + +struct CharacterControllerContact { + Vec3 worldPosition; + Vec3 worldNormal; + Vec3 motionDirection; + float motionLength; + static constexpr uint8_t COUNT = 10; +}; +struct CCTShapeEventPair { + uint32_t cct; //wrapper object ID + uint32_t shape; //wrapper object ID + //ETouchState state; + ccstd::vector contacts; + static constexpr uint8_t COUNT = 3; + CCTShapeEventPair(const uint32_t cct, const uint32_t shape) + : cct(cct), shape(shape) { + } +}; + +struct CCTTriggerEventPair { + uint32_t cct; //wrapper object ID + uint32_t shape; //wrapper object ID + ETouchState state; + static constexpr uint8_t COUNT = 3; + CCTTriggerEventPair(const uint32_t cct, const uint32_t shape) + : cct(cct), + shape(shape), + state(ETouchState::ENTER) {} +}; + +struct ConvexDesc { + void *positions; + uint32_t positionLength; +}; + +struct TrimeshDesc : ConvexDesc { + void *triangles; + uint32_t triangleLength; + bool isU16; +}; + +struct HeightFieldDesc { + uint32_t rows; + uint32_t columns; + void *samples; +}; + +struct RaycastOptions { + Vec3 origin; + float distance; + Vec3 unitDir; + uint32_t mask; + bool queryTrigger; +}; + +struct RaycastResult { + uint32_t shape{0}; + Vec3 hitPoint; + float distance; + Vec3 hitNormal; + RaycastResult() = default; +}; + +class IPhysicsWorld { +public: + virtual ~IPhysicsWorld() = default; + ; + virtual void setGravity(float x, float y, float z) = 0; + virtual void setAllowSleep(bool v) = 0; + virtual void step(float s) = 0; + virtual void emitEvents() = 0; + virtual void syncSceneToPhysics() = 0; + virtual void syncSceneWithCheck() = 0; + virtual void destroy() = 0; + virtual void setDebugDrawFlags(EPhysicsDrawFlags f) = 0; + virtual EPhysicsDrawFlags getDebugDrawFlags() = 0; + virtual void setDebugDrawConstraintSize(float s) = 0; + virtual float getDebugDrawConstraintSize() = 0; + virtual void setCollisionMatrix(uint32_t i, uint32_t m) = 0; + virtual ccstd::vector> &getTriggerEventPairs() = 0; + virtual ccstd::vector>& getContactEventPairs() = 0; + virtual ccstd::vector>& getCCTShapeEventPairs() = 0; + virtual ccstd::vector> &getCCTTriggerEventPairs() = 0; + virtual bool raycast(RaycastOptions &opt) = 0; + virtual bool raycastClosest(RaycastOptions &opt) = 0; + virtual ccstd::vector &raycastResult() = 0; + virtual RaycastResult &raycastClosestResult() = 0; + virtual bool sweepBox(RaycastOptions &opt, float halfExtentX, float halfExtentY, float halfExtentZ, + float orientationW, float orientationX, float orientationY, float orientationZ) = 0; + virtual bool sweepBoxClosest(RaycastOptions &opt, float halfExtentX, float halfExtentY, float halfExtentZ, + float orientationW, float orientationX, float orientationY, float orientationZ) = 0; + virtual bool sweepSphere(RaycastOptions &opt, float radius) = 0; + virtual bool sweepSphereClosest(RaycastOptions &opt, float radius) = 0; + virtual bool sweepCapsule(RaycastOptions &opt, float radius, float height, + float orientationW, float orientationX, float orientationY, float orientationZ) = 0; + virtual bool sweepCapsuleClosest(RaycastOptions &opt, float radius, float height, + float orientationW, float orientationX, float orientationY, float orientationZ) = 0; + virtual RaycastResult &sweepClosestResult() = 0; + virtual ccstd::vector &sweepResult() = 0; + virtual uint32_t createConvex(ConvexDesc &desc) = 0; + virtual uint32_t createTrimesh(TrimeshDesc &desc) = 0; + virtual uint32_t createHeightField(HeightFieldDesc &desc) = 0; + virtual bool createMaterial(uint16_t id, float f, float df, float r, + uint8_t m0, uint8_t m1) = 0; + virtual void setFixedTimeStep(float v) = 0; + virtual float getFixedTimeStep() const = 0; +}; + +} // namespace physics +} // namespace cc diff --git a/cocos/platform/BasePlatform.cpp b/cocos/platform/BasePlatform.cpp new file mode 100644 index 0000000..1ae6443 --- /dev/null +++ b/cocos/platform/BasePlatform.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/BasePlatform.h" +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) + #include "platform/win32/WindowsPlatform.h" +#elif (CC_PLATFORM == CC_PLATFORM_ANDROID) + #include "platform/android/AndroidPlatform.h" +#elif (CC_PLATFORM == CC_PLATFORM_OHOS) + #include "platform/ohos/OhosPlatform.h" +#elif (CC_PLATFORM == CC_PLATFORM_MACOS) + #include "platform/mac/MacPlatform.h" +#elif (CC_PLATFORM == CC_PLATFORM_IOS) + #include "platform/ios/IOSPlatform.h" +#elif (CC_PLATFORM == CC_PLATFORM_LINUX) + #include "platform/linux/LinuxPlatform.h" +#elif (CC_PLATFORM == CC_PLATFORM_QNX) + #include "platform/qnx/QnxPlatform.h" +#elif (CC_PLATFORM == CC_PLATFORM_OPENHARMONY) + #include "platform/openharmony/OpenHarmonyPlatform.h" +#endif + +namespace cc { +BasePlatform* BasePlatform::_currentPlatform = nullptr; + +BasePlatform::BasePlatform() { + // Only one platform can be initialized. + CC_ASSERT_NULL(_currentPlatform); + _currentPlatform = this; +} + +BasePlatform::~BasePlatform() { + _currentPlatform = nullptr; +} + +BasePlatform* BasePlatform::createDefaultPlatform() { +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) + static WindowsPlatform platform; +#elif (CC_PLATFORM == CC_PLATFORM_ANDROID) + static AndroidPlatform platform; +#elif (CC_PLATFORM == CC_PLATFORM_OHOS) + static OhosPlatform platform; +#elif (CC_PLATFORM == CC_PLATFORM_MACOS) + static MacPlatform platform; +#elif (CC_PLATFORM == CC_PLATFORM_IOS) + static IOSPlatform platform; +#elif (CC_PLATFORM == CC_PLATFORM_LINUX) + static LinuxPlatform platform; +#elif (CC_PLATFORM == CC_PLATFORM_QNX) + static QnxPlatform platform; +#elif (CC_PLATFORM == CC_PLATFORM_OPENHARMONY) + static OpenHarmonyPlatform platform; +#endif + return &platform; +} + +BasePlatform* BasePlatform::getPlatform() { + if (_currentPlatform) { + return _currentPlatform; + } + createDefaultPlatform(); + CC_ASSERT_NOT_NULL(_currentPlatform); + return _currentPlatform; +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/BasePlatform.h b/cocos/platform/BasePlatform.h new file mode 100644 index 0000000..e9a7b05 --- /dev/null +++ b/cocos/platform/BasePlatform.h @@ -0,0 +1,181 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Log.h" +#include "base/Macros.h" + +#include "engine/EngineEvents.h" +#include "platform/interfaces/modules/ISystem.h" + +#include +#include +#include + +namespace cc { + +class OSInterface; +class ISystemWindow; + +class CC_DLL BasePlatform { +public: + BasePlatform(); + /** + * @brief Destructor of AbstratctPlatform. + */ + virtual ~BasePlatform(); + /** + * @brief Get default system platform. + */ + static BasePlatform *getPlatform(); + /** + * @brief Initialize system platform. + */ + virtual int32_t init() = 0; + /** + * @brief Run system platform. + */ + virtual int32_t run(int argc, const char **argv) = 0; + /** + * @brief Main business logic. + */ + virtual int32_t loop() = 0; + + /** + * @brief Polling event. + */ + virtual void pollEvent() = 0; + + /** + * @brief Exit platform. + */ + virtual void exit() = 0; + + /** + * @brief Get target system type. + */ + using OSType = ISystem::OSType; + + virtual OSType getOSType() const = 0; + + /** + * @brief Get the SDK version for Android.Other systems also have sdk versions, + but they are not currently used. + */ + virtual int getSdkVersion() const = 0; + /** + * @brief Run the task in the platform thread, + * @brief most platforms are the main thread, android is the non-main thread + * @param task : Tasks running in platform threads + * @param fps : Task call frequency + */ + using ThreadCallback = std::function; + virtual void runInPlatformThread(const ThreadCallback &task) = 0; + /** + * @brief Get task call frequency. + */ + virtual int32_t getFps() const = 0; + /** + * @brief Set task call frequency. + */ + virtual void setFps(int32_t fps) = 0; + + /** + * @brief Get target system interface(Non thread safe.). + */ + template + std::enable_if_t::value, T *> + getInterface() const { + for (const auto &it : _osInterfaces) { + T *intf = dynamic_cast(it.get()); + if (intf) { + return intf; + } + } + return nullptr; + } + + template + std::enable_if_t::value, T *> + getInterface() { + for (const auto &it : _osInterfaces) { + T *intf = dynamic_cast(it.get()); + if (intf) { + return intf; + } + } + return nullptr; + } + + /** + * @brief Registration system interface. + */ + bool registerInterface(const OSInterface::Ptr &osInterface) { + CC_ASSERT_NOT_NULL(osInterface); + auto it = std::find(_osInterfaces.begin(), _osInterfaces.end(), osInterface); + if (it != _osInterfaces.end()) { + CC_LOG_WARNING("Duplicate registration interface"); + return false; + } + _osInterfaces.push_back(osInterface); + return true; + } + /** + * @brief Unregistration system interface. + */ + void unregisterInterface(const OSInterface::Ptr &osInterface) { + CC_ASSERT_NOT_NULL(osInterface); + auto it = std::find(_osInterfaces.begin(), _osInterfaces.end(), osInterface); + if (it != _osInterfaces.end()) { + CC_LOG_WARNING("Interface is not registrated"); + return; + } + _osInterfaces.erase(it); + } + + void unregisterAllInterfaces() { + _osInterfaces.clear(); + } + + virtual ISystemWindow *createNativeWindow(uint32_t windowId, void *externalHandle) = 0; + +private: + static BasePlatform *createDefaultPlatform(); + + static BasePlatform *_currentPlatform; // NOLINT(readability-identifier-naming) + std::vector _osInterfaces; + CC_DISALLOW_COPY_MOVE_ASSIGN(BasePlatform); +}; +} // namespace cc + +#define START_PLATFORM(argc, argv) \ + do { \ + cc::BasePlatform *platform = cc::BasePlatform::getPlatform(); \ + if (platform->init()) { \ + CC_LOG_FATAL("Platform initialization failed"); \ + return -1; \ + } \ + return platform->run(argc, argv); \ + } while (0) diff --git a/cocos/platform/FileUtils.cpp b/cocos/platform/FileUtils.cpp new file mode 100644 index 0000000..4f2d084 --- /dev/null +++ b/cocos/platform/FileUtils.cpp @@ -0,0 +1,1175 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/FileUtils.h" + +#include +#include + +#include +#include +#include + +#ifdef MINIZIP_FROM_SYSTEM + #include +#else // from our embedded sources + #include "unzip/unzip.h" +#endif +#include +#include + +#include "base/Data.h" +#include "base/Log.h" +#include "base/memory/Memory.h" +#include "platform/SAXParser.h" + +#include "tinydir/tinydir.h" +#include "tinyxml2/tinyxml2.h" + +namespace cc { + +// Implement DictMaker + +#if (CC_PLATFORM != CC_PLATFORM_IOS) && (CC_PLATFORM != CC_PLATFORM_MACOS) + +using SAXState = enum { + SAX_NONE = 0, + SAX_KEY, + SAX_DICT, + SAX_INT, + SAX_REAL, + SAX_STRING, + SAX_ARRAY +}; + +using SAXResult = enum { + SAX_RESULT_NONE = 0, + SAX_RESULT_DICT, + SAX_RESULT_ARRAY +}; + +class DictMaker : public SAXDelegator { +public: + SAXResult _resultType{SAX_RESULT_NONE}; + ValueMap _rootDict; + ValueVector _rootArray; + + ccstd::string _curKey; ///< parsed key + ccstd::string _curValue; // parsed value + SAXState _state{SAX_NONE}; + + ValueMap *_curDict; + ValueVector *_curArray; + + std::stack _dictStack; + std::stack _arrayStack; + std::stack _stateStack; + + DictMaker() = default; + + ~DictMaker() override = default; + + ValueMap dictionaryWithContentsOfFile(const ccstd::string &fileName) { + _resultType = SAX_RESULT_DICT; + SAXParser parser; + + CC_ASSERT(parser.init("UTF-8")); + parser.setDelegator(this); + + parser.parse(fileName); + return _rootDict; + } + + ValueMap dictionaryWithDataOfFile(const char *filedata, int filesize) { + _resultType = SAX_RESULT_DICT; + SAXParser parser; + + CC_ASSERT(parser.init("UTF-8")); + parser.setDelegator(this); + + parser.parse(filedata, filesize); + return _rootDict; + } + + ValueVector arrayWithContentsOfFile(const ccstd::string &fileName) { + _resultType = SAX_RESULT_ARRAY; + SAXParser parser; + + CC_ASSERT(parser.init("UTF-8")); + parser.setDelegator(this); + + parser.parse(fileName); + return _rootArray; + } + + void startElement(void *ctx, const char *name, const char **atts) override { + CC_UNUSED_PARAM(ctx); + CC_UNUSED_PARAM(atts); + const ccstd::string sName(name); + if (sName == "dict") { + if (_resultType == SAX_RESULT_DICT && _rootDict.empty()) { + _curDict = &_rootDict; + } + + _state = SAX_DICT; + + SAXState preState = SAX_NONE; + if (!_stateStack.empty()) { + preState = _stateStack.top(); + } + + if (SAX_ARRAY == preState) { + // add a new dictionary into the array + _curArray->push_back(Value(ValueMap())); + _curDict = &(_curArray->rbegin())->asValueMap(); + } else if (SAX_DICT == preState) { + // add a new dictionary into the pre dictionary + CC_ASSERT(!_dictStack.empty()); // The state is wrong. + ValueMap *preDict = _dictStack.top(); + (*preDict)[_curKey] = Value(ValueMap()); + _curDict = &(*preDict)[_curKey].asValueMap(); + } + + // record the dict state + _stateStack.push(_state); + _dictStack.push(_curDict); + } else if (sName == "key") { + _state = SAX_KEY; + } else if (sName == "integer") { + _state = SAX_INT; + } else if (sName == "real") { + _state = SAX_REAL; + } else if (sName == "string") { + _state = SAX_STRING; + } else if (sName == "array") { + _state = SAX_ARRAY; + + if (_resultType == SAX_RESULT_ARRAY && _rootArray.empty()) { + _curArray = &_rootArray; + } + SAXState preState = SAX_NONE; + if (!_stateStack.empty()) { + preState = _stateStack.top(); + } + + if (preState == SAX_DICT) { + (*_curDict)[_curKey] = Value(ValueVector()); + _curArray = &(*_curDict)[_curKey].asValueVector(); + } else if (preState == SAX_ARRAY) { + CC_ASSERT(!_arrayStack.empty()); // The state is wrong! + ValueVector *preArray = _arrayStack.top(); + preArray->push_back(Value(ValueVector())); + _curArray = &(_curArray->rbegin())->asValueVector(); + } + // record the array state + _stateStack.push(_state); + _arrayStack.push(_curArray); + } else { + _state = SAX_NONE; + } + } + + void endElement(void *ctx, const char *name) override { + CC_UNUSED_PARAM(ctx); + SAXState curState = _stateStack.empty() ? SAX_DICT : _stateStack.top(); + const ccstd::string sName(const_cast(name)); + if (sName == "dict") { + _stateStack.pop(); + _dictStack.pop(); + if (!_dictStack.empty()) { + _curDict = _dictStack.top(); + } + } else if (sName == "array") { + _stateStack.pop(); + _arrayStack.pop(); + if (!_arrayStack.empty()) { + _curArray = _arrayStack.top(); + } + } else if (sName == "true") { + if (SAX_ARRAY == curState) { + _curArray->push_back(Value(true)); + } else if (SAX_DICT == curState) { + (*_curDict)[_curKey] = Value(true); + } + } else if (sName == "false") { + if (SAX_ARRAY == curState) { + _curArray->push_back(Value(false)); + } else if (SAX_DICT == curState) { + (*_curDict)[_curKey] = Value(false); + } + } else if (sName == "string" || sName == "integer" || sName == "real") { + if (SAX_ARRAY == curState) { + if (sName == "string") { + _curArray->push_back(Value(_curValue)); + } else if (sName == "integer") { + _curArray->push_back(Value(atoi(_curValue.c_str()))); + } else { + _curArray->push_back(Value(std::atof(_curValue.c_str()))); + } + } else if (SAX_DICT == curState) { + if (sName == "string") { + (*_curDict)[_curKey] = Value(_curValue); + } else if (sName == "integer") { + (*_curDict)[_curKey] = Value(atoi(_curValue.c_str())); + } else { + (*_curDict)[_curKey] = Value(std::atof(_curValue.c_str())); + } + } + + _curValue.clear(); + } + + _state = SAX_NONE; + } + + void textHandler(void *ctx, const char *ch, int len) override { + CC_UNUSED_PARAM(ctx); + if (_state == SAX_NONE) { + return; + } + + SAXState curState = _stateStack.empty() ? SAX_DICT : _stateStack.top(); + const ccstd::string text = ccstd::string(const_cast(ch), len); + + switch (_state) { + case SAX_KEY: + _curKey = text; + break; + case SAX_INT: + case SAX_REAL: + case SAX_STRING: { + if (curState == SAX_DICT) { + // "key not found : " + CC_ASSERT(!_curKey.empty()); + } + + _curValue.append(text); + } break; + default: + break; + } + } +}; + +ValueMap FileUtils::getValueMapFromFile(const ccstd::string &filename) { + const ccstd::string fullPath = fullPathForFilename(filename); + if (fullPath.empty()) { + ValueMap ret; + return ret; + } + + DictMaker tMaker; + return tMaker.dictionaryWithContentsOfFile(fullPath); +} + +ValueMap FileUtils::getValueMapFromData(const char *filedata, int filesize) { + DictMaker tMaker; + return tMaker.dictionaryWithDataOfFile(filedata, filesize); +} + +ValueVector FileUtils::getValueVectorFromFile(const ccstd::string &filename) { + const ccstd::string fullPath = fullPathForFilename(filename); + DictMaker tMaker; + return tMaker.arrayWithContentsOfFile(fullPath); +} + +/* + * forward statement + */ +static tinyxml2::XMLElement *generateElementForArray(const ValueVector &array, tinyxml2::XMLDocument *doc); +static tinyxml2::XMLElement *generateElementForDict(const ValueMap &dict, tinyxml2::XMLDocument *doc); + +/* + * Use tinyxml2 to write plist files + */ +bool FileUtils::writeToFile(const ValueMap &dict, const ccstd::string &fullPath) { + return writeValueMapToFile(dict, fullPath); +} + +bool FileUtils::writeValueMapToFile(const ValueMap &dict, const ccstd::string &fullPath) { + auto *doc = ccnew tinyxml2::XMLDocument(); + if (nullptr == doc) { + return false; + } + + tinyxml2::XMLDeclaration *declaration = doc->NewDeclaration(R"(xml version="1.0" encoding="UTF-8")"); + if (nullptr == declaration) { + delete doc; + return false; + } + + doc->LinkEndChild(declaration); + tinyxml2::XMLElement *docType = doc->NewElement(R"(!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd")"); + doc->LinkEndChild(docType); + + tinyxml2::XMLElement *rootEle = doc->NewElement("plist"); + if (nullptr == rootEle) { + delete doc; + return false; + } + rootEle->SetAttribute("version", "1.0"); + doc->LinkEndChild(rootEle); + + tinyxml2::XMLElement *innerDict = generateElementForDict(dict, doc); + if (nullptr == innerDict) { + delete doc; + return false; + } + rootEle->LinkEndChild(innerDict); + + bool ret = tinyxml2::XML_SUCCESS == doc->SaveFile(getSuitableFOpen(fullPath).c_str()); + + delete doc; + return ret; +} + +bool FileUtils::writeValueVectorToFile(const ValueVector &vecData, const ccstd::string &fullPath) { + auto *doc = ccnew tinyxml2::XMLDocument(); + if (nullptr == doc) { + return false; + } + + tinyxml2::XMLDeclaration *declaration = doc->NewDeclaration(R"(xml version="1.0" encoding="UTF-8")"); + if (nullptr == declaration) { + delete doc; + return false; + } + + doc->LinkEndChild(declaration); + tinyxml2::XMLElement *docType = doc->NewElement(R"(!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd")"); + doc->LinkEndChild(docType); + + tinyxml2::XMLElement *rootEle = doc->NewElement("plist"); + if (nullptr == rootEle) { + delete doc; + return false; + } + rootEle->SetAttribute("version", "1.0"); + doc->LinkEndChild(rootEle); + + tinyxml2::XMLElement *innerDict = generateElementForArray(vecData, doc); + if (nullptr == innerDict) { + delete doc; + return false; + } + rootEle->LinkEndChild(innerDict); + + bool ret = tinyxml2::XML_SUCCESS == doc->SaveFile(getSuitableFOpen(fullPath).c_str()); + + delete doc; + return ret; +} + +/* + * Generate tinyxml2::XMLElement for Object through a tinyxml2::XMLDocument + */ +static tinyxml2::XMLElement *generateElementForObject(const Value &value, tinyxml2::XMLDocument *doc) { // NOLINT(misc-no-recursion) + // object is String + if (value.getType() == Value::Type::STRING) { + tinyxml2::XMLElement *node = doc->NewElement("string"); + tinyxml2::XMLText *content = doc->NewText(value.asString().c_str()); + node->LinkEndChild(content); + return node; + } + + // object is integer + if (value.getType() == Value::Type::INTEGER) { + tinyxml2::XMLElement *node = doc->NewElement("integer"); + tinyxml2::XMLText *content = doc->NewText(value.asString().c_str()); + node->LinkEndChild(content); + return node; + } + + // object is real + if (value.getType() == Value::Type::FLOAT || value.getType() == Value::Type::DOUBLE) { + tinyxml2::XMLElement *node = doc->NewElement("real"); + tinyxml2::XMLText *content = doc->NewText(value.asString().c_str()); + node->LinkEndChild(content); + return node; + } + + // object is bool + if (value.getType() == Value::Type::BOOLEAN) { + tinyxml2::XMLElement *node = doc->NewElement(value.asString().c_str()); + return node; + } + + // object is Array + if (value.getType() == Value::Type::VECTOR) { + return generateElementForArray(value.asValueVector(), doc); + } + + // object is Dictionary + if (value.getType() == Value::Type::MAP) { + return generateElementForDict(value.asValueMap(), doc); + } + + CC_LOG_DEBUG("This type cannot appear in property list"); + return nullptr; +} + +/* + * Generate tinyxml2::XMLElement for Dictionary through a tinyxml2::XMLDocument + */ +static tinyxml2::XMLElement *generateElementForDict(const ValueMap &dict, tinyxml2::XMLDocument *doc) { // NOLINT(misc-no-recursion) + tinyxml2::XMLElement *rootNode = doc->NewElement("dict"); + + for (const auto &iter : dict) { + tinyxml2::XMLElement *tmpNode = doc->NewElement("key"); + rootNode->LinkEndChild(tmpNode); + tinyxml2::XMLText *content = doc->NewText(iter.first.c_str()); + tmpNode->LinkEndChild(content); + + tinyxml2::XMLElement *element = generateElementForObject(iter.second, doc); + if (element) { + rootNode->LinkEndChild(element); + } + } + return rootNode; +} + +/* + * Generate tinyxml2::XMLElement for Array through a tinyxml2::XMLDocument + */ +static tinyxml2::XMLElement *generateElementForArray(const ValueVector &array, tinyxml2::XMLDocument *pDoc) { // NOLINT(misc-no-recursion) + tinyxml2::XMLElement *rootNode = pDoc->NewElement("array"); + + for (const auto &value : array) { + tinyxml2::XMLElement *element = generateElementForObject(value, pDoc); + if (element) { + rootNode->LinkEndChild(element); + } + } + return rootNode; +} + +#else + +/* The subclass FileUtilsApple should override these two method. */ +ValueMap FileUtils::getValueMapFromFile(const ccstd::string &filename) { return ValueMap(); } +ValueMap FileUtils::getValueMapFromData(const char *filedata, int filesize) { return ValueMap(); } +ValueVector FileUtils::getValueVectorFromFile(const ccstd::string &filename) { return ValueVector(); } +bool FileUtils::writeToFile(const ValueMap &dict, const ccstd::string &fullPath) { return false; } + +#endif /* (CC_PLATFORM != CC_PLATFORM_IOS) && (CC_PLATFORM != CC_PLATFORM_MACOS) */ + +// Implement FileUtils +FileUtils *FileUtils::sharedFileUtils = nullptr; + +FileUtils *FileUtils::getInstance() { + return FileUtils::sharedFileUtils; +} + +void FileUtils::destroyInstance() { +} + +void FileUtils::setDelegate(FileUtils *delegate) { + delete FileUtils::sharedFileUtils; + FileUtils::sharedFileUtils = delegate; +} + +FileUtils::FileUtils() { + FileUtils::sharedFileUtils = this; +} + +FileUtils::~FileUtils() { + FileUtils::sharedFileUtils = nullptr; +} + +bool FileUtils::writeStringToFile(const ccstd::string &dataStr, const ccstd::string &fullPath) { + Data data; + auto *dataP = const_cast(dataStr.data()); + data.fastSet(reinterpret_cast(dataP), static_cast(dataStr.size())); + + bool rv = writeDataToFile(data, fullPath); + + // need to give up buffer ownership for temp using, or double free will occur + data.takeBuffer(); + return rv; +} + +bool FileUtils::writeDataToFile(const Data &data, const ccstd::string &fullPath) { + size_t size = 0; + const char *mode = "wb"; + + CC_ASSERT(!fullPath.empty() && data.getSize() != 0); + + auto *fileutils = FileUtils::getInstance(); + do { + // Read the file from hardware + FILE *fp = fopen(fileutils->getSuitableFOpen(fullPath).c_str(), mode); + CC_BREAK_IF(!fp); + size = data.getSize(); + + fwrite(data.getBytes(), size, 1, fp); + + fclose(fp); + + return true; + } while (false); + + return false; +} + +bool FileUtils::init() { + addSearchPath("Resources", true); + addSearchPath("data", true); + _searchPathArray.push_back(_defaultResRootPath); + return true; +} + +void FileUtils::purgeCachedEntries() { + _fullPathCache.clear(); +} + +ccstd::string FileUtils::getStringFromFile(const ccstd::string &filename) { + ccstd::string s; + getContents(filename, &s); + return s; +} + +Data FileUtils::getDataFromFile(const ccstd::string &filename) { + Data d; + getContents(filename, &d); + return d; +} + +FileUtils::Status FileUtils::getContents(const ccstd::string &filename, ResizableBuffer *buffer) { + if (filename.empty()) { + return Status::NOT_EXISTS; + } + + auto *fs = FileUtils::getInstance(); + + ccstd::string fullPath = fs->fullPathForFilename(filename); + if (fullPath.empty()) { + return Status::NOT_EXISTS; + } + + FILE *fp = fopen(fs->getSuitableFOpen(fullPath).c_str(), "rb"); + if (!fp) { + return Status::OPEN_FAILED; + } + +#if defined(_MSC_VER) + auto descriptor = _fileno(fp); +#else + auto descriptor = fileno(fp); +#endif + struct stat statBuf; + if (fstat(descriptor, &statBuf) == -1) { + fclose(fp); + return Status::READ_FAILED; + } + auto size = static_cast(statBuf.st_size); + + buffer->resize(size); + size_t readsize = fread(buffer->buffer(), 1, size, fp); + fclose(fp); + + if (readsize < size) { + buffer->resize(readsize); + return Status::READ_FAILED; + } + + return Status::OK; +} + +unsigned char *FileUtils::getFileDataFromZip(const ccstd::string &zipFilePath, const ccstd::string &filename, uint32_t *size) { + unsigned char *buffer = nullptr; + unzFile file = nullptr; + *size = 0; + + do { + CC_BREAK_IF(zipFilePath.empty()); + + file = unzOpen(FileUtils::getInstance()->getSuitableFOpen(zipFilePath).c_str()); + CC_BREAK_IF(!file); + + // minizip 1.2.0 is same with other platforms + int ret = unzLocateFile(file, filename.c_str(), nullptr); + CC_BREAK_IF(UNZ_OK != ret); + + char filePathA[260]; + unz_file_info fileInfo; + ret = unzGetCurrentFileInfo(file, &fileInfo, filePathA, sizeof(filePathA), nullptr, 0, nullptr, 0); + CC_BREAK_IF(UNZ_OK != ret); + + ret = unzOpenCurrentFile(file); + CC_BREAK_IF(UNZ_OK != ret); + + buffer = static_cast(malloc(fileInfo.uncompressed_size)); + int CC_UNUSED readedSize = unzReadCurrentFile(file, buffer, static_cast(fileInfo.uncompressed_size)); + CC_ASSERT(readedSize == 0 || readedSize == (int)fileInfo.uncompressed_size); + + *size = fileInfo.uncompressed_size; + unzCloseCurrentFile(file); + } while (false); + + if (file) { + unzClose(file); + } + + return buffer; +} + +ccstd::string FileUtils::getPathForFilename(const ccstd::string &filename, const ccstd::string &searchPath) const { + ccstd::string file{filename}; + ccstd::string filePath; + size_t pos = filename.find_last_of('/'); + if (pos != ccstd::string::npos) { + filePath = filename.substr(0, pos + 1); + file = filename.substr(pos + 1); + } + + // searchPath + file_path + ccstd::string path = searchPath; + path.append(filePath); + + path = getFullPathForDirectoryAndFilename(path, file); + + return path; +} + +ccstd::string FileUtils::fullPathForFilename(const ccstd::string &filename) const { + if (filename.empty()) { + return ""; + } + + if (isAbsolutePath(filename)) { + return normalizePath(filename); + } + + // Already Cached ? + auto cacheIter = _fullPathCache.find(filename); + if (cacheIter != _fullPathCache.end()) { + return cacheIter->second; + } + + ccstd::string fullpath; + + for (const auto &searchIt : _searchPathArray) { + fullpath = this->getPathForFilename(filename, searchIt); + + if (!fullpath.empty()) { + // Using the filename passed in as key. + _fullPathCache.emplace(filename, fullpath); + return fullpath; + } + } + + // The file wasn't found, return empty string. + return ""; +} + +ccstd::string FileUtils::fullPathFromRelativeFile(const ccstd::string &filename, const ccstd::string &relativeFile) { + return relativeFile.substr(0, relativeFile.rfind('/') + 1) + filename; +} + +const ccstd::vector &FileUtils::getSearchPaths() const { + return _searchPathArray; +} + +const ccstd::vector &FileUtils::getOriginalSearchPaths() const { + return _originalSearchPaths; +} + +void FileUtils::setWritablePath(const ccstd::string &writablePath) { + _writablePath = writablePath; +} + +const ccstd::string &FileUtils::getDefaultResourceRootPath() const { + return _defaultResRootPath; +} + +void FileUtils::setDefaultResourceRootPath(const ccstd::string &path) { + if (_defaultResRootPath != path) { + _fullPathCache.clear(); + _defaultResRootPath = path; + if (!_defaultResRootPath.empty() && _defaultResRootPath[_defaultResRootPath.length() - 1] != '/') { + _defaultResRootPath += '/'; + } + + // Updates search paths + setSearchPaths(_originalSearchPaths); + } +} + +void FileUtils::setSearchPaths(const ccstd::vector &searchPaths) { + bool existDefaultRootPath = false; + _originalSearchPaths = searchPaths; + + _fullPathCache.clear(); + _searchPathArray.clear(); + + for (const auto &path : _originalSearchPaths) { + ccstd::string prefix; + ccstd::string fullPath; + + if (!isAbsolutePath(path)) { // Not an absolute path + prefix = _defaultResRootPath; + } + fullPath = prefix + path; + if (!path.empty() && path[path.length() - 1] != '/') { + fullPath += "/"; + } + if (!existDefaultRootPath && path == _defaultResRootPath) { + existDefaultRootPath = true; + } + _searchPathArray.push_back(fullPath); + } + + if (!existDefaultRootPath) { + // CC_LOG_DEBUG("Default root path doesn't exist, adding it."); + _searchPathArray.push_back(_defaultResRootPath); + } +} + +void FileUtils::addSearchPath(const ccstd::string &searchpath, bool front) { + ccstd::string prefix; + if (!isAbsolutePath(searchpath)) { + prefix = _defaultResRootPath; + } + + ccstd::string path = prefix + searchpath; + if (!path.empty() && path[path.length() - 1] != '/') { + path += "/"; + } + if (front) { + _originalSearchPaths.insert(_originalSearchPaths.begin(), searchpath); + _searchPathArray.insert(_searchPathArray.begin(), path); + } else { + _originalSearchPaths.push_back(searchpath); + _searchPathArray.push_back(path); + } +} + +ccstd::string FileUtils::getFullPathForDirectoryAndFilename(const ccstd::string &directory, const ccstd::string &filename) const { + // get directory+filename, safely adding '/' as necessary + ccstd::string ret = directory; + if (!directory.empty() && directory[directory.size() - 1] != '/') { + ret += '/'; + } + ret += filename; + ret = normalizePath(ret); + + // if the file doesn't exist, return an empty string + if (!isFileExistInternal(ret)) { + ret = ""; + } + return ret; +} + +bool FileUtils::isFileExist(const ccstd::string &filename) const { + if (isAbsolutePath(filename)) { + return isFileExistInternal(normalizePath(filename)); + } + ccstd::string fullpath = fullPathForFilename(filename); + return !fullpath.empty(); +} + +bool FileUtils::isAbsolutePath(const ccstd::string &path) const { + return (path[0] == '/'); +} + +bool FileUtils::isDirectoryExist(const ccstd::string &dirPath) const { + CC_ASSERT(!dirPath.empty()); + + if (isAbsolutePath(dirPath)) { + return isDirectoryExistInternal(normalizePath(dirPath)); + } + + // Already Cached ? + auto cacheIter = _fullPathCache.find(dirPath); + if (cacheIter != _fullPathCache.end()) { + return isDirectoryExistInternal(cacheIter->second); + } + + ccstd::string fullpath; + for (const auto &searchIt : _searchPathArray) { + // searchPath + file_path + fullpath = fullPathForFilename(searchIt + dirPath); + if (isDirectoryExistInternal(fullpath)) { + _fullPathCache.emplace(dirPath, fullpath); + return true; + } + } + return false; +} + +ccstd::vector FileUtils::listFiles(const ccstd::string &dirPath) const { + ccstd::string fullpath = fullPathForFilename(dirPath); + ccstd::vector files; + if (isDirectoryExist(fullpath)) { + tinydir_dir dir; +#ifdef UNICODE + unsigned int length = MultiByteToWideChar(CP_UTF8, 0, &fullpath[0], (int)fullpath.size(), NULL, 0); + if (length != fullpath.size()) { + return files; + } + std::wstring fullpathstr(length, 0); + MultiByteToWideChar(CP_UTF8, 0, &fullpath[0], (int)fullpath.size(), &fullpathstr[0], length); +#else + ccstd::string fullpathstr = fullpath; +#endif + if (tinydir_open(&dir, &fullpathstr[0]) != -1) { + while (dir.has_next) { + tinydir_file file; + if (tinydir_readfile(&dir, &file) == -1) { + // Error getting file + break; + } + +#ifdef UNICODE + std::wstring path = file.path; + length = WideCharToMultiByte(CP_UTF8, 0, &path[0], (int)path.size(), NULL, 0, NULL, NULL); + ccstd::string filepath; + if (length > 0) { + filepath.resize(length); + WideCharToMultiByte(CP_UTF8, 0, &path[0], (int)path.size(), &filepath[0], length, NULL, NULL); + } +#else + ccstd::string filepath = file.path; +#endif + if (file.is_dir) { + filepath.append("/"); + } + files.push_back(filepath); + + if (tinydir_next(&dir) == -1) { + // Error getting next file + break; + } + } + } + tinydir_close(&dir); + } + return files; +} + +void FileUtils::listFilesRecursively(const ccstd::string &dirPath, ccstd::vector *files) const { // NOLINT(misc-no-recursion) + ccstd::string fullpath = fullPathForFilename(dirPath); + if (!fullpath.empty() && isDirectoryExist(fullpath)) { + tinydir_dir dir; +#ifdef UNICODE + unsigned int length = MultiByteToWideChar(CP_UTF8, 0, &fullpath[0], (int)fullpath.size(), NULL, 0); + if (length != fullpath.size()) { + return; + } + std::wstring fullpathstr(length, 0); + MultiByteToWideChar(CP_UTF8, 0, &fullpath[0], (int)fullpath.size(), &fullpathstr[0], length); +#else + ccstd::string fullpathstr = fullpath; +#endif + if (tinydir_open(&dir, &fullpathstr[0]) != -1) { + while (dir.has_next) { + tinydir_file file; + if (tinydir_readfile(&dir, &file) == -1) { + // Error getting file + break; + } + +#ifdef UNICODE + std::wstring path = file.path; + length = WideCharToMultiByte(CP_UTF8, 0, &path[0], (int)path.size(), NULL, 0, NULL, NULL); + ccstd::string filepath; + if (length > 0) { + filepath.resize(length); + WideCharToMultiByte(CP_UTF8, 0, &path[0], (int)path.size(), &filepath[0], length, NULL, NULL); + } +#else + ccstd::string filepath = file.path; +#endif + if (file.name[0] != '.') { + if (file.is_dir) { + filepath.append("/"); + files->push_back(filepath); + listFilesRecursively(filepath, files); + } else { + files->push_back(filepath); + } + } + + if (tinydir_next(&dir) == -1) { + // Error getting next file + break; + } + } + } + tinydir_close(&dir); + } +} + +#if (CC_PLATFORM == CC_PLATFORM_WINDOWS) || (CC_PLATFORM == CC_PLATFORM_WINRT) +// windows os implement should override in platform specific FileUtiles class +bool FileUtils::isDirectoryExistInternal(const ccstd::string &dirPath) const { + // FileUtils not support isDirectoryExistInternal. + CC_ABORT(); + return false; +} + +bool FileUtils::createDirectory(const ccstd::string &path) { + // FileUtils not support createDirectory. + CC_ABORT(); + return false; +} + +bool FileUtils::removeDirectory(const ccstd::string &path) { + // FileUtils not support removeDirectory. + CC_ABORT(); + return false; +} + +bool FileUtils::removeFile(const ccstd::string &path) { + // FileUtils not support removeFile. + CC_ABORT(); + return false; +} + +bool FileUtils::renameFile(const ccstd::string &oldfullpath, const ccstd::string &newfullpath) { + // FileUtils not support renameFile. + CC_ABORT(); + return false; +} + +bool FileUtils::renameFile(const ccstd::string &path, const ccstd::string &oldname, const ccstd::string &name) { + // FileUtils not support renameFile. + CC_ABORT(); + return false; +} + +ccstd::string FileUtils::getSuitableFOpen(const ccstd::string &filenameUtf8) const { + // getSuitableFOpen should be override by platform FileUtils + CC_ABORT(); + return filenameUtf8; +} + +long FileUtils::getFileSize(const ccstd::string &filepath) { + // getFileSize should be override by platform FileUtils + CC_ABORT(); + return 0; +} + +#else + // default implements for unix like os + #include + #include + #include + + // android doesn't have ftw.h + #if (CC_PLATFORM != CC_PLATFORM_ANDROID) + #include + #endif + +bool FileUtils::isDirectoryExistInternal(const ccstd::string &dirPath) const { + struct stat st; + if (stat(dirPath.c_str(), &st) == 0) { + return S_ISDIR(st.st_mode); + } + return false; +} + +bool FileUtils::createDirectory(const ccstd::string &path) { + CC_ASSERT(!path.empty()); + + if (isDirectoryExist(path)) { + return true; + } + + // Split the path + size_t start = 0; + size_t found = path.find_first_of("/\\", start); + ccstd::string subpath; + ccstd::vector dirs; + + if (found != ccstd::string::npos) { + while (true) { + subpath = path.substr(start, found - start + 1); + if (!subpath.empty()) { + dirs.push_back(subpath); + } + start = found + 1; + found = path.find_first_of("/\\", start); + if (found == ccstd::string::npos) { + if (start < path.length()) { + dirs.push_back(path.substr(start)); + } + break; + } + } + } + + DIR *dir = nullptr; + + // Create path recursively + subpath = ""; + for (const auto &iter : dirs) { + subpath += iter; + dir = opendir(subpath.c_str()); + + if (!dir) { + // directory doesn't exist, should create a new one + + int ret = mkdir(subpath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); + if (ret != 0 && (errno != EEXIST)) { + // current directory can not be created, sub directories can not be created too + // should return + return false; + } + } else { + // directory exists, should close opened dir + closedir(dir); + } + } + return true; +} + +namespace { + #if (CC_PLATFORM != CC_PLATFORM_ANDROID) +int unlinkCb(const char *fpath, const struct stat * /*sb*/, int /*typeflag*/, struct FTW * /*ftwbuf*/) { + int rv = remove(fpath); + + if (rv) { + perror(fpath); + } + + return rv; +} + #endif +} // namespace + +bool FileUtils::removeDirectory(const ccstd::string &path) { + #if (CC_PLATFORM != CC_PLATFORM_ANDROID) + return nftw(path.c_str(), unlinkCb, 64, FTW_DEPTH | FTW_PHYS) != -1; + #else + ccstd::string command = "rm -r "; + // Path may include space. + command += "\"" + path + "\""; + + return (system(command.c_str()) >= 0); + #endif // (CC_PLATFORM != CC_PLATFORM_ANDROID) +} + +bool FileUtils::removeFile(const ccstd::string &path) { + return remove(path.c_str()) == 0; +} + +bool FileUtils::renameFile(const ccstd::string &oldfullpath, const ccstd::string &newfullpath) { + CC_ASSERT(!oldfullpath.empty()); + CC_ASSERT(!newfullpath.empty()); + + int errorCode = rename(oldfullpath.c_str(), newfullpath.c_str()); + + if (0 != errorCode) { + CC_LOG_ERROR("Fail to rename file %s to %s !Error code is %d", oldfullpath.c_str(), newfullpath.c_str(), errorCode); + return false; + } + return true; +} + +bool FileUtils::renameFile(const ccstd::string &path, const ccstd::string &oldname, const ccstd::string &name) { + CC_ASSERT(!path.empty()); + ccstd::string oldPath = path + oldname; + ccstd::string newPath = path + name; + + return this->renameFile(oldPath, newPath); +} + +ccstd::string FileUtils::getSuitableFOpen(const ccstd::string &filenameUtf8) const { + return filenameUtf8; +} + +long FileUtils::getFileSize(const ccstd::string &filepath) { //NOLINT(google-runtime-int) + CC_ASSERT(!filepath.empty()); + + ccstd::string fullpath{filepath}; + if (!isAbsolutePath(filepath)) { + fullpath = fullPathForFilename(filepath); + if (fullpath.empty()) { + return 0; + } + } + + struct stat info; + // Get data associated with "crt_stat.c": + int result = stat(fullpath.c_str(), &info); + + // Check if statistics are valid: + if (result != 0) { + // Failed + return -1; + } + return static_cast(info.st_size); // NOLINT(google-runtime-int) +} +#endif + +ccstd::string FileUtils::getFileExtension(const ccstd::string &filePath) const { + ccstd::string fileExtension; + size_t pos = filePath.find_last_of('.'); + if (pos != ccstd::string::npos) { + fileExtension = filePath.substr(pos, filePath.length()); + + std::transform(fileExtension.begin(), fileExtension.end(), fileExtension.begin(), ::tolower); + } + + return fileExtension; +} + +void FileUtils::valueMapCompact(ValueMap &valueMap) { +} + +void FileUtils::valueVectorCompact(ValueVector &valueVector) { +} + +ccstd::string FileUtils::getFileDir(const ccstd::string &path) const { + ccstd::string ret; + size_t pos = path.rfind('/'); + if (pos != ccstd::string::npos) { + ret = path.substr(0, pos); + } + + normalizePath(ret); + + return ret; +} + +ccstd::string FileUtils::normalizePath(const ccstd::string &path) const { + ccstd::string ret; + // Normalize: remove . and .. + ret = std::regex_replace(path, std::regex("/\\./"), "/"); + ret = std::regex_replace(ret, std::regex("/\\.$"), ""); + + size_t pos; + while ((pos = ret.find("..")) != ccstd::string::npos && pos > 2) { + size_t prevSlash = ret.rfind('/', pos - 2); + if (prevSlash == ccstd::string::npos) { + break; + } + + ret = ret.replace(prevSlash, pos - prevSlash + 2, ""); + } + return ret; +} + +} // namespace cc diff --git a/cocos/platform/FileUtils.h b/cocos/platform/FileUtils.h new file mode 100644 index 0000000..3aec2fe --- /dev/null +++ b/cocos/platform/FileUtils.h @@ -0,0 +1,636 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Data.h" +#include "base/Macros.h" +#include "base/Value.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" + +namespace cc { + +class ResizableBuffer { +public: + ~ResizableBuffer() = default; + virtual void resize(size_t size) = 0; + virtual void *buffer() const = 0; +}; + +template +class ResizableBufferAdapter {}; + +template +class ResizableBufferAdapter> : public ResizableBuffer { + using BufferType = std::basic_string; + BufferType *_buffer; + +public: + explicit ResizableBufferAdapter(BufferType *buffer) : _buffer(buffer) {} + void resize(size_t size) override { + _buffer->resize((size + sizeof(CharT) - 1) / sizeof(CharT)); + } + void *buffer() const override { + // can not invoke string::front() if it is empty + return _buffer->empty() ? nullptr : &_buffer->front(); + } +}; + +template +class ResizableBufferAdapter> : public ResizableBuffer { + using BufferType = ccstd::vector; + BufferType *_buffer; + +public: + explicit ResizableBufferAdapter(BufferType *buffer) : _buffer(buffer) {} + void resize(size_t size) override { + _buffer->resize((size + sizeof(T) - 1) / sizeof(T)); + } + void *buffer() const override { + // can not invoke vector::front() if it is empty + return _buffer->empty() ? nullptr : &_buffer->front(); + } +}; + +template <> +class ResizableBufferAdapter : public ResizableBuffer { + using BufferType = Data; + BufferType *_buffer; + +public: + explicit ResizableBufferAdapter(BufferType *buffer) : _buffer(buffer) {} + void resize(size_t size) override { + size_t oldSize = _buffer->getSize(); + if (oldSize != size) { + // need to take buffer ownership for outer memory control + auto *old = _buffer->takeBuffer(); + void *buffer = realloc(old, size); + if (buffer) { + _buffer->fastSet(static_cast(buffer), static_cast(size)); + } + } + } + void *buffer() const override { + return _buffer->getBytes(); + } +}; + +/** Helper class to handle file operations. */ +class CC_DLL FileUtils { +public: + /** + * Gets the instance of FileUtils. + */ + static FileUtils *getInstance(); + + /** + * Destroys the instance of FileUtils. + */ + CC_DEPRECATED(3.6.0) + static void destroyInstance(); + + /** + * You can inherit from platform dependent implementation of FileUtils, such as FileUtilsAndroid, + * and use this function to set delegate, then FileUtils will invoke delegate's implementation. + * For example, your resources are encrypted, so you need to decrypt it after reading data from + * resources, then you can implement all getXXX functions, and engine will invoke your own getXX + * functions when reading data of resources. + * + * If you don't want to system default implementation after setting delegate, you can just pass nullptr + * to this function. + * + * @warning It will delete previous delegate + */ + static void setDelegate(FileUtils *delegate); + + /** + * The default constructor. + */ + FileUtils(); + + /** + * The destructor of FileUtils. + */ + virtual ~FileUtils(); + + /** + * Purges full path caches. + */ + virtual void purgeCachedEntries(); + + /** + * Gets string from a file. + */ + virtual ccstd::string getStringFromFile(const ccstd::string &filename); + + /** + * Creates binary data from a file. + * @return A data object. + */ + virtual Data getDataFromFile(const ccstd::string &filename); + + enum class Status { + OK = 0, + NOT_EXISTS = 1, // File not exists + OPEN_FAILED = 2, // Open file failed. + READ_FAILED = 3, // Read failed + NOT_INITIALIZED = 4, // FileUtils is not initializes + TOO_LARGE = 5, // The file is too large (great than 2^32-1) + OBTAIN_SIZE_FAILED = 6 // Failed to obtain the file size. + }; + + /** + * Gets whole file contents as string from a file. + * + * Unlike getStringFromFile, these getContents methods: + * - read file in binary mode (does not convert CRLF to LF). + * - does not truncate the string when '\0' is found (returned string of getContents may have '\0' in the middle.). + * + * The template version of can accept cc::Data, std::basic_string and ccstd::vector. + * + * @code + * ccstd::string sbuf; + * FileUtils::getInstance()->getContents("path/to/file", &sbuf); + * + * ccstd::vector vbuf; + * FileUtils::getInstance()->getContents("path/to/file", &vbuf); + * + * Data dbuf; + * FileUtils::getInstance()->getContents("path/to/file", &dbuf); + * @endcode + * + * Note: if you read to ccstd::vector and std::basic_string where T is not 8 bit type, + * you may get 0 ~ sizeof(T)-1 bytes padding. + * + * - To write a new buffer class works with getContents, just extend ResizableBuffer. + * - To write a adapter for existing class, write a specialized ResizableBufferAdapter for that class, see follow code. + * + * @code + * namespace cc { // ResizableBufferAdapter needed in cocos2d namespace. + * template<> + * class ResizableBufferAdapter : public ResizableBuffer { + * public: + * ResizableBufferAdapter(AlreadyExistsBuffer* buffer) { + * // your code here + * } + * virtual void resize(size_t size) override { + * // your code here + * } + * virtual void* buffer() const override { + * // your code here + * } + * }; + * } + * @endcode + * + * @param[in] filename The resource file name which contains the path. + * @param[out] buffer The buffer where the file contents are store to. + * @return Returns: + * - Status::OK when there is no error, the buffer is filled with the contents of file. + * - Status::NotExists when file not exists, the buffer will not changed. + * - Status::OpenFailed when cannot open file, the buffer will not changed. + * - Status::ReadFailed when read end up before read whole, the buffer will fill with already read bytes. + * - Status::NotInitialized when FileUtils is not initializes, the buffer will not changed. + * - Status::TooLarge when there file to be read is too large (> 2^32-1), the buffer will not changed. + * - Status::ObtainSizeFailed when failed to obtain the file size, the buffer will not changed. + */ + template < + typename T, + typename Enable = typename std::enable_if< + std::is_base_of>::value>::type> + Status getContents(const ccstd::string &filename, T *buffer) { + ResizableBufferAdapter buf(buffer); + return getContents(filename, &buf); + } + virtual Status getContents(const ccstd::string &filename, ResizableBuffer *buffer); + + /** + * Gets resource file data from a zip file. + * + * @param[in] filename The resource file name which contains the relative path of the zip file. + * @param[out] size If the file read operation succeeds, it will be the data size, otherwise 0. + * @return Upon success, a pointer to the data is returned, otherwise nullptr. + * @warning Recall: you are responsible for calling free() on any Non-nullptr pointer returned. + */ + virtual unsigned char *getFileDataFromZip(const ccstd::string &zipFilePath, const ccstd::string &filename, uint32_t *size); + + /** Returns the fullpath for a given filename. + + First it will try to get a new filename from the "filenameLookup" dictionary. + If a new filename can't be found on the dictionary, it will use the original filename. + Then it will try to obtain the full path of the filename using the FileUtils search rules: resolutions, and search paths. + The file search is based on the array element order of search paths and resolution directories. + + For instance: + + We set two elements("/mnt/sdcard/", "internal_dir/") to search paths vector by setSearchPaths, + and set three elements("resources-ipadhd/", "resources-ipad/", "resources-iphonehd") + to resolutions vector by setSearchResolutionsOrder. The "internal_dir" is relative to "Resources/". + + If we have a file named 'sprite.png', the mapping in fileLookup dictionary contains `key: sprite.png -> value: sprite.pvr.gz`. + Firstly, it will replace 'sprite.png' with 'sprite.pvr.gz', then searching the file sprite.pvr.gz as follows: + + /mnt/sdcard/resources-ipadhd/sprite.pvr.gz (if not found, search next) + /mnt/sdcard/resources-ipad/sprite.pvr.gz (if not found, search next) + /mnt/sdcard/resources-iphonehd/sprite.pvr.gz (if not found, search next) + /mnt/sdcard/sprite.pvr.gz (if not found, search next) + internal_dir/resources-ipadhd/sprite.pvr.gz (if not found, search next) + internal_dir/resources-ipad/sprite.pvr.gz (if not found, search next) + internal_dir/resources-iphonehd/sprite.pvr.gz (if not found, search next) + internal_dir/sprite.pvr.gz (if not found, return "sprite.png") + + If the filename contains relative path like "gamescene/uilayer/sprite.png", + and the mapping in fileLookup dictionary contains `key: gamescene/uilayer/sprite.png -> value: gamescene/uilayer/sprite.pvr.gz`. + The file search order will be: + + /mnt/sdcard/gamescene/uilayer/resources-ipadhd/sprite.pvr.gz (if not found, search next) + /mnt/sdcard/gamescene/uilayer/resources-ipad/sprite.pvr.gz (if not found, search next) + /mnt/sdcard/gamescene/uilayer/resources-iphonehd/sprite.pvr.gz (if not found, search next) + /mnt/sdcard/gamescene/uilayer/sprite.pvr.gz (if not found, search next) + internal_dir/gamescene/uilayer/resources-ipadhd/sprite.pvr.gz (if not found, search next) + internal_dir/gamescene/uilayer/resources-ipad/sprite.pvr.gz (if not found, search next) + internal_dir/gamescene/uilayer/resources-iphonehd/sprite.pvr.gz (if not found, search next) + internal_dir/gamescene/uilayer/sprite.pvr.gz (if not found, return "gamescene/uilayer/sprite.png") + + If the new file can't be found on the file system, it will return the parameter filename directly. + + This method was added to simplify multiplatform support. Whether you are using cocos2d-js or any cross-compilation toolchain like StellaSDK or Apportable, + you might need to load different resources for a given file in the different platforms. + + @since v2.1 + */ + virtual ccstd::string fullPathForFilename(const ccstd::string &filename) const; + + /** + * Gets full path from a file name and the path of the relative file. + * @param filename The file name. + * @param relativeFile The path of the relative file. + * @return The full path. + * e.g. filename: hello.png, pszRelativeFile: /User/path1/path2/hello.plist + * Return: /User/path1/path2/hello.pvr (If there a a key(hello.png)-value(hello.pvr) in FilenameLookup dictionary. ) + * + */ + virtual ccstd::string fullPathFromRelativeFile(const ccstd::string &filename, const ccstd::string &relativeFile); + + /** + * Sets the array of search paths. + * + * You can use this array to modify the search path of the resources. + * If you want to use "themes" or search resources in the "cache", you can do it easily by adding new entries in this array. + * + * @note This method could access relative path and absolute path. + * If the relative path was passed to the vector, FileUtils will add the default resource directory before the relative path. + * For instance: + * On Android, the default resource root path is "@assets/". + * If "/mnt/sdcard/" and "resources-large" were set to the search paths vector, + * "resources-large" will be converted to "@assets/resources-large" since it was a relative path. + * + * @param searchPaths The array contains search paths. + * @see fullPathForFilename(const char*) + * @since v2.1 + */ + virtual void setSearchPaths(const ccstd::vector &searchPaths); + + /** + * Get default resource root path. + */ + const ccstd::string &getDefaultResourceRootPath() const; + + /** + * Set default resource root path. + */ + void setDefaultResourceRootPath(const ccstd::string &path); + + /** + * Add search path. + * + * @since v2.1 + */ + void addSearchPath(const ccstd::string &path, bool front = false); + + /** + * Gets the array of search paths. + * + * @return The array of search paths which may contain the prefix of default resource root path. + * @note In best practise, getter function should return the value of setter function passes in. + * But since we should not break the compatibility, we keep using the old logic. + * Therefore, If you want to get the original search paths, please call 'getOriginalSearchPaths()' instead. + * @see fullPathForFilename(const char*). + */ + virtual const ccstd::vector &getSearchPaths() const; + + /** + * Gets the original search path array set by 'setSearchPaths' or 'addSearchPath'. + * @return The array of the original search paths + */ + virtual const ccstd::vector &getOriginalSearchPaths() const; + + /** + * Gets the writable path. + * @return The path that can be write/read a file in + */ + virtual ccstd::string getWritablePath() const = 0; + + /** + * Sets writable path. + */ + virtual void setWritablePath(const ccstd::string &writablePath); + + /** + * Converts the contents of a file to a ValueMap. + * @param filename The filename of the file to gets content. + * @return ValueMap of the file contents. + * @note This method is used internally. + */ + virtual ValueMap getValueMapFromFile(const ccstd::string &filename); + + /** Converts the contents of a file to a ValueMap. + * This method is used internally. + */ + virtual ValueMap getValueMapFromData(const char *filedata, int filesize); + + /** + * write a ValueMap into a plist file + * + *@param dict the ValueMap want to save + *@param fullPath The full path to the file you want to save a string + *@return bool + */ + virtual bool writeToFile(const ValueMap &dict, const ccstd::string &fullPath); + + /** + * write a string into a file + * + * @param dataStr the string want to save + * @param fullPath The full path to the file you want to save a string + * @return bool True if write success + */ + virtual bool writeStringToFile(const ccstd::string &dataStr, const ccstd::string &fullPath); + + /** + * write Data into a file + * + *@param data the data want to save + *@param fullPath The full path to the file you want to save a string + *@return bool + */ + virtual bool writeDataToFile(const Data &data, const ccstd::string &fullPath); + + /** + * write ValueMap into a plist file + * + *@param dict the ValueMap want to save + *@param fullPath The full path to the file you want to save a string + *@return bool + */ + virtual bool writeValueMapToFile(const ValueMap &dict, const ccstd::string &fullPath); + + /** + * write ValueVector into a plist file + * + *@param vecData the ValueVector want to save + *@param fullPath The full path to the file you want to save a string + *@return bool + */ + virtual bool writeValueVectorToFile(const ValueVector &vecData, const ccstd::string &fullPath); + + /** + * Windows fopen can't support UTF-8 filename + * Need convert all parameters fopen and other 3rd-party libs + * + * @param filenameUtf8 ccstd::string name file for conversion from utf-8 + * @return ccstd::string ansi filename in current locale + */ + virtual ccstd::string getSuitableFOpen(const ccstd::string &filenameUtf8) const; + + // Converts the contents of a file to a ValueVector. + // This method is used internally. + virtual ValueVector getValueVectorFromFile(const ccstd::string &filename); + + /** + * Checks whether a file exists. + * + * @note If a relative path was passed in, it will be inserted a default root path at the beginning. + * @param filename The path of the file, it could be a relative or absolute path. + * @return True if the file exists, false if not. + */ + virtual bool isFileExist(const ccstd::string &filename) const; + + /** + * Gets filename extension is a suffix (separated from the base filename by a dot) in lower case. + * Examples of filename extensions are .png, .jpeg, .exe, .dmg and .txt. + * @param filePath The path of the file, it could be a relative or absolute path. + * @return suffix for filename in lower case or empty if a dot not found. + */ + virtual ccstd::string getFileExtension(const ccstd::string &filePath) const; + + /** + * Checks whether the path is an absolute path. + * + * @note On Android, if the parameter passed in is relative to "@assets/", this method will treat it as an absolute path. + * Also on Blackberry, path starts with "app/native/Resources/" is treated as an absolute path. + * + * @param path The path that needs to be checked. + * @return True if it's an absolute path, false if not. + */ + virtual bool isAbsolutePath(const ccstd::string &path) const; + + /** + * Checks whether the path is a directory. + * + * @param dirPath The path of the directory, it could be a relative or an absolute path. + * @return True if the directory exists, false if not. + */ + virtual bool isDirectoryExist(const ccstd::string &dirPath) const; + + /** + * List all files in a directory. + * + * @param dirPath The path of the directory, it could be a relative or an absolute path. + * @return File paths in a string vector + */ + virtual ccstd::vector listFiles(const ccstd::string &dirPath) const; + + /** + * List all files recursively in a directory. + * + * @param dirPath The path of the directory, it could be a relative or an absolute path. + * @return File paths in a string vector + */ + virtual void listFilesRecursively(const ccstd::string &dirPath, ccstd::vector *files) const; + + /** + * Creates a directory. + * + * @param dirPath The path of the directory, it must be an absolute path. + * @return True if the directory have been created successfully, false if not. + */ + virtual bool createDirectory(const ccstd::string &dirPath); + + /** + * Removes a directory. + * + * @param dirPath The full path of the directory, it must be an absolute path. + * @return True if the directory have been removed successfully, false if not. + */ + virtual bool removeDirectory(const ccstd::string &dirPath); + + /** + * Removes a file. + * + * @param filepath The full path of the file, it must be an absolute path. + * @return True if the file have been removed successfully, false if not. + */ + virtual bool removeFile(const ccstd::string &filepath); + + /** + * Renames a file under the given directory. + * + * @param path The parent directory path of the file, it must be an absolute path. + * @param oldname The current name of the file. + * @param name The new name of the file. + * @return True if the file have been renamed successfully, false if not. + */ + virtual bool renameFile(const ccstd::string &path, const ccstd::string &oldname, const ccstd::string &name); + + /** + * Renames a file under the given directory. + * + * @param oldfullpath The current fullpath of the file. Includes path and name. + * @param newfullpath The new fullpath of the file. Includes path and name. + * @return True if the file have been renamed successfully, false if not. + */ + virtual bool renameFile(const ccstd::string &oldfullpath, const ccstd::string &newfullpath); + + /** + * Retrieve the file size. + * + * @note If a relative path was passed in, it will be inserted a default root path at the beginning. + * @param filepath The path of the file, it could be a relative or absolute path. + * @return The file size. + */ + virtual long getFileSize(const ccstd::string &filepath); //NOLINT(google-runtime-int) + + /** Returns the full path cache. */ + const ccstd::unordered_map &getFullPathCache() const { return _fullPathCache; } + + virtual ccstd::string normalizePath(const ccstd::string &path) const; + virtual ccstd::string getFileDir(const ccstd::string &path) const; + +protected: + /** + * Initializes the instance of FileUtils. It will set _searchPathArray and _searchResolutionsOrderArray to default values. + * + * @note When you are porting Cocos2d-x to a new platform, you may need to take care of this method. + * You could assign a default value to _defaultResRootPath in the subclass of FileUtils(e.g. FileUtilsAndroid). Then invoke the FileUtils::init(). + * @return true if succeed, otherwise it returns false. + * + */ + virtual bool init(); + + /** + * Checks whether a file exists without considering search paths and resolution orders. + * @param filename The file (with absolute path) to look up for + * @return Returns true if the file found at the given absolute path, otherwise returns false + */ + virtual bool isFileExistInternal(const ccstd::string &filename) const = 0; + + /** + * Checks whether a directory exists without considering search paths and resolution orders. + * @param dirPath The directory (with absolute path) to look up for + * @return Returns true if the directory found at the given absolute path, otherwise returns false + */ + virtual bool isDirectoryExistInternal(const ccstd::string &dirPath) const; + + /** + * Gets full path for filename, resolution directory and search path. + * + * @param filename The file name. + * @param searchPath The search path. + * @return The full path of the file. It will return an empty string if the full path of the file doesn't exist. + */ + virtual ccstd::string getPathForFilename(const ccstd::string &filename, const ccstd::string &searchPath) const; + + /** + * Gets full path for the directory and the filename. + * + * @note Only iOS and Mac need to override this method since they are using + * `[[NSBundle mainBundle] pathForResource: ofType: inDirectory:]` to make a full path. + * Other platforms will use the default implementation of this method. + * @param directory The directory contains the file we are looking for. + * @param filename The name of the file. + * @return The full path of the file, if the file can't be found, it will return an empty string. + */ + virtual ccstd::string getFullPathForDirectoryAndFilename(const ccstd::string &directory, const ccstd::string &filename) const; + + /** + * The vector contains search paths. + * The lower index of the element in this vector, the higher priority for this search path. + */ + ccstd::vector _searchPathArray; + + /** + * The search paths which was set by 'setSearchPaths' / 'addSearchPath'. + */ + ccstd::vector _originalSearchPaths; + + /** + * The default root path of resources. + * If the default root path of resources needs to be changed, do it in the `init` method of FileUtils's subclass. + * For instance: + * On Android, the default root path of resources will be assigned with "@assets/" in FileUtilsAndroid::init(). + * Similarly on Blackberry, we assign "app/native/Resources/" to this variable in FileUtilsBlackberry::init(). + */ + ccstd::string _defaultResRootPath; + + /** + * The full path cache. When a file is found, it will be added into this cache. + * This variable is used for improving the performance of file search. + */ + mutable ccstd::unordered_map _fullPathCache; + + /** + * Writable path. + */ + ccstd::string _writablePath; + + /** + * The singleton pointer of FileUtils. + */ + static FileUtils *sharedFileUtils; + + /** + * Remove null value key (for iOS) + */ + virtual void valueMapCompact(ValueMap &valueMap); + virtual void valueVectorCompact(ValueVector &valueVector); +}; + +// Can remove this function when refactoring file system. +FileUtils *createFileUtils(); + +} // namespace cc diff --git a/cocos/platform/Image.cpp b/cocos/platform/Image.cpp new file mode 100644 index 0000000..5a19500 --- /dev/null +++ b/cocos/platform/Image.cpp @@ -0,0 +1,1182 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2016-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Image.h" +#include +#include +#include "base/Config.h" // CC_USE_JPEG, CC_USE_WEBP +#include "base/std/container/string.h" +#include "gfx-base/GFXDef-common.h" + +#if CC_USE_JPEG + #include "jpeg/jpeglib.h" +#endif // CC_USE_JPEG + +#include "base/Data.h" +#include "base/Log.h" +#include "base/Utils.h" +#include "gfx-base/GFXDef.h" + +extern "C" { +#if CC_USE_PNG + #if __OHOS__ || __LINUX__ || __QNX__ + #include "png.h" + #else + #include "png/png.h" + #endif +#endif //CC_USE_PNG + +#include "base/etc1.h" +#include "base/etc2.h" +} + +#include "base/Compressed.h" +#include "base/astc.h" + +#if CC_USE_WEBP + #include "webp/decode.h" +#endif // CC_USE_WEBP + +#include "base/ZipUtils.h" +#include "platform/FileUtils.h" +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + #include "platform/android/FileUtils-android.h" +#endif + +#include "base/std/container/unordered_map.h" + +namespace cc { + +////////////////////////////////////////////////////////////////////////// +//struct and data for pvr structure + +namespace { +const int PVR_TEXTURE_FLAG_TYPE_MASK = 0xff; + +// Values taken from PVRTexture.h from http://www.imgtec.com +enum class PVR2TextureFlag { + MIPMAP = (1 << 8), // has mip map levels + TWIDDLE = (1 << 9), // is twiddled + BUMPMAP = (1 << 10), // has normals encoded for a bump map + TILING = (1 << 11), // is bordered for tiled pvr + CUBEMAP = (1 << 12), // is a cubemap/skybox + FALSE_MIP_COL = (1 << 13), // are there false colored MIP levels + VOLUME = (1 << 14), // is this a volume texture + ALPHA = (1 << 15), // v2.1 is there transparency info in the texture + VERTICAL_FLIP = (1 << 16), // v2.1 is the texture vertically flipped +}; + +const char G_PVR_TEX_IDENTIFIER[5] = "PVR!"; + +// v2 +enum class PVR2TexturePixelFormat : unsigned char { + RGBA4444 = 0x10, + RGBA5551, + RGBA8888, + RGB565, + RGB555, // unsupported + RGB888, + I8, + AI88, + PVRTC2BPP_RGBA, + PVRTC4BPP_RGBA, + BGRA8888, + A8, +}; + +// v3 +enum class PVR3TexturePixelFormat : uint64_t { + PVRTC2BPP_RGB = 0ULL, + PVRTC2BPP_RGBA = 1ULL, + PVRTC4BPP_RGB = 2ULL, + PVRTC4BPP_RGBA = 3ULL, + PVRTC2_2BPP_RGBA = 4ULL, + PVRTC2_4BPP_RGBA = 5ULL, + ETC1 = 6ULL, + DXT1 = 7ULL, + DXT2 = 8ULL, + DXT3 = 9ULL, + DXT4 = 10ULL, + DXT5 = 11ULL, + BC1 = 7ULL, + BC2 = 9ULL, + BC3 = 11ULL, + BC4 = 12ULL, + BC5 = 13ULL, + BC6 = 14ULL, + BC7 = 15ULL, + UYVY = 16ULL, + YUY2 = 17ULL, + B_W1BPP = 18ULL, + R9G9B9E5 = 19ULL, + RGBG8888 = 20ULL, + GRGB8888 = 21ULL, + ETC2_RGB = 22ULL, + ETC2_RGBA = 23ULL, + ETC2_RGBA1 = 24ULL, + EAC_R11_UNSIGNED = 25ULL, + EAC_R11_SIGNED = 26ULL, + EAC_R_G11_UNSIGNED = 27ULL, + EAC_R_G11_SIGNED = 28ULL, + + BGRA8888 = 0x0808080861726762ULL, + RGBA8888 = 0x0808080861626772ULL, + RGBA4444 = 0x0404040461626772ULL, + RGBA5551 = 0x0105050561626772ULL, + RGB565 = 0x0005060500626772ULL, + RGB888 = 0x0008080800626772ULL, + A8 = 0x0000000800000061ULL, + L8 = 0x000000080000006cULL, + LA88 = 0x000008080000616cULL, +}; + +// v2 +using _pixel2_formathash = const ccstd::unordered_map; + +const _pixel2_formathash::value_type V2_PIXEL_FORMATHASH_VALUE[] = { + _pixel2_formathash::value_type(PVR2TexturePixelFormat::BGRA8888, gfx::Format::BGRA8), + _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA8888, gfx::Format::RGBA8), + _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA4444, gfx::Format::RGBA4), + _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA5551, gfx::Format::RGB5A1), + _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGB565, gfx::Format::R5G6B5), + _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGB888, gfx::Format::RGB8), + _pixel2_formathash::value_type(PVR2TexturePixelFormat::A8, gfx::Format::A8), + _pixel2_formathash::value_type(PVR2TexturePixelFormat::I8, gfx::Format::L8), + _pixel2_formathash::value_type(PVR2TexturePixelFormat::AI88, gfx::Format::LA8), + + _pixel2_formathash::value_type(PVR2TexturePixelFormat::PVRTC2BPP_RGBA, gfx::Format::PVRTC_RGBA2), + _pixel2_formathash::value_type(PVR2TexturePixelFormat::PVRTC4BPP_RGBA, gfx::Format::PVRTC_RGBA4), +}; + +const int PVR2_MAX_TABLE_ELEMENTS = sizeof(V2_PIXEL_FORMATHASH_VALUE) / sizeof(V2_PIXEL_FORMATHASH_VALUE[0]); +const _pixel2_formathash V2_PIXEL_FORMATHASH(V2_PIXEL_FORMATHASH_VALUE, V2_PIXEL_FORMATHASH_VALUE + PVR2_MAX_TABLE_ELEMENTS); + +// v3 +using _pixel3_formathash = const ccstd::unordered_map; +_pixel3_formathash::value_type v3PixelFormathashValue[] = { + _pixel3_formathash::value_type(PVR3TexturePixelFormat::BGRA8888, gfx::Format::BGRA8), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA8888, gfx::Format::RGBA8), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA4444, gfx::Format::RGBA4), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA5551, gfx::Format::RGB5A1), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGB565, gfx::Format::R5G6B5), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGB888, gfx::Format::RGB8), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::A8, gfx::Format::A8), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::L8, gfx::Format::L8), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::LA88, gfx::Format::LA8), + + _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC2BPP_RGB, gfx::Format::PVRTC_RGB2), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC2BPP_RGBA, gfx::Format::PVRTC_RGBA2), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC4BPP_RGB, gfx::Format::PVRTC_RGB4), + _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC4BPP_RGBA, gfx::Format::PVRTC_RGBA4), + + _pixel3_formathash::value_type(PVR3TexturePixelFormat::ETC1, gfx::Format::ETC_RGB8), +}; + +const int PVR3_MAX_TABLE_ELEMENTS = sizeof(v3PixelFormathashValue) / sizeof(v3PixelFormathashValue[0]); + +const _pixel3_formathash V3_PIXEL_FORMATHASH(v3PixelFormathashValue, v3PixelFormathashValue + PVR3_MAX_TABLE_ELEMENTS); + +using PVRv2TexHeader = struct PvrTexHeader { + unsigned int headerLength; + unsigned int height; + unsigned int width; + unsigned int numMipmaps; + unsigned int flags; + unsigned int dataLength; + unsigned int bpp; + unsigned int bitmaskRed; + unsigned int bitmaskGreen; + unsigned int bitmaskBlue; + unsigned int bitmaskAlpha; + unsigned int pvrTag; + unsigned int numSurfs; +}; + +#ifdef _MSC_VER + #pragma pack(push, 1) +#endif +using PVRv3TexHeader = struct { + uint32_t version; + uint32_t flags; + uint64_t pixelFormat; + uint32_t colorSpace; + uint32_t channelType; + uint32_t height; + uint32_t width; + uint32_t depth; + uint32_t numberOfSurfaces; + uint32_t numberOfFaces; + uint32_t numberOfMipmaps; + uint32_t metadataLength; +#ifdef _MSC_VER +}; + #pragma pack(pop) +#else +} __attribute__((packed)); +#endif +} // namespace +//pvr structure end + +namespace { +using tImageSource = struct { + const unsigned char *data; + uint32_t size; + int offset; +}; + +#ifdef CC_USE_PNG +void pngReadCallback(png_structp pngPtr, png_bytep data, png_size_t length) { + auto *isource = static_cast(png_get_io_ptr(pngPtr)); + + if (static_cast(isource->offset + length) <= isource->size) { + memcpy(data, isource->data + isource->offset, length); + isource->offset += static_cast(length); + } else { + png_error(pngPtr, "pngReaderCallback failed"); + } +} +#endif //CC_USE_PNG +} // namespace + +////////////////////////////////////////////////////////////////////////// +// Implement Image +////////////////////////////////////////////////////////////////////////// + +Image::Image() : _renderFormat(gfx::Format::UNKNOWN) { +} + +Image::~Image() { + CC_SAFE_FREE(_data); +} + +bool Image::initWithImageFile(const ccstd::string &path) { + bool ret = false; + //NOTE: fullPathForFilename isn't threadsafe. we should make sure the parameter is a full path. + // _filePath = FileUtils::getInstance()->fullPathForFilename(path); + _filePath = path; + + const Data data = FileUtils::getInstance()->getDataFromFile(_filePath); + + if (!data.isNull()) { + ret = initWithImageData(data.getBytes(), data.getSize()); + } + + return ret; +} + +bool Image::initWithImageData(const unsigned char *data, uint32_t dataLen) { //NOLINT(misc-no-recursion) + bool ret = false; + do { + CC_BREAK_IF(!data || dataLen <= 0); + + unsigned char *unpackedData = nullptr; + uint32_t unpackedLen = 0; + + //detect and unzip the compress file + if (ZipUtils::isCCZBuffer(data, dataLen)) { + unpackedLen = ZipUtils::inflateCCZBuffer(data, dataLen, &unpackedData); + } else if (ZipUtils::isGZipBuffer(data, dataLen)) { + unpackedLen = ZipUtils::inflateMemory(const_cast(data), dataLen, &unpackedData); + } else { + unpackedData = const_cast(data); + unpackedLen = dataLen; + } + + _fileType = detectFormat(unpackedData, unpackedLen); + + switch (_fileType) { + case Format::PNG: + ret = initWithPngData(unpackedData, unpackedLen); + break; + case Format::JPG: + ret = initWithJpgData(unpackedData, unpackedLen); + break; +#if CC_USE_WEBP + case Format::WEBP: + ret = initWithWebpData(unpackedData, unpackedLen); + break; +#endif + case Format::PVR: + ret = initWithPVRData(unpackedData, unpackedLen); + break; + case Format::ETC: + ret = initWithETCData(unpackedData, unpackedLen); + break; + case Format::ETC2: + ret = initWithETC2Data(unpackedData, unpackedLen); + break; + case Format::ASTC: + ret = initWithASTCData(unpackedData, unpackedLen); + break; + case Format::COMPRESSED: + ret = initWithCompressedMipsData(unpackedData, unpackedLen); + break; + default: + break; + } + + if (unpackedData != data) { + free(unpackedData); + } + } while (false); + + return ret; +} + +bool Image::isPng(const unsigned char *data, uint32_t dataLen) { + if (dataLen <= 8) { + return false; + } + + static const unsigned char PNG_SIGNATURE[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}; + + return memcmp(PNG_SIGNATURE, data, sizeof(PNG_SIGNATURE)) == 0; +} + +bool Image::isEtc(const unsigned char *data, uint32_t /*dataLen*/) { + return etc1_pkm_is_valid(const_cast(data)) != 0; +} + +bool Image::isEtc2(const unsigned char *data, uint32_t /*dataLen*/) { + return etc2_pkm_is_valid(const_cast(data)) != 0; +} + +bool Image::isASTC(const unsigned char *data, uint32_t /*dataLen*/) { + return astcIsValid(const_cast(data)); +} + +bool Image::isCompressed(const unsigned char *data, uint32_t /*dataLen*/) { + return compressedIsValid(data); +} + +bool Image::isJpg(const unsigned char *data, uint32_t dataLen) { + if (dataLen <= 4) { + return false; + } + + static const unsigned char JPG_SOI[] = {0xFF, 0xD8}; + + return memcmp(data, JPG_SOI, 2) == 0; +} + +bool Image::isWebp(const unsigned char *data, uint32_t dataLen) { + if (dataLen <= 12) { + return false; + } + + static const char *webpRiff = "RIFF"; + static const char *webpWebp = "WEBP"; + + return memcmp(data, webpRiff, 4) == 0 && memcmp(static_cast(data) + 8, webpWebp, 4) == 0; +} + +bool Image::isPvr(const unsigned char *data, uint32_t dataLen) { + if (static_cast(dataLen) < sizeof(PVRv2TexHeader) || static_cast(dataLen) < sizeof(PVRv3TexHeader)) { + return false; + } + + const auto *headerv2 = static_cast(static_cast(data)); + const auto *headerv3 = static_cast(static_cast(data)); + + return memcmp(&headerv2->pvrTag, G_PVR_TEX_IDENTIFIER, strlen(G_PVR_TEX_IDENTIFIER)) == 0 || CC_SWAP_INT32_BIG_TO_HOST(headerv3->version) == 0x50565203; +} + +Image::Format Image::detectFormat(const unsigned char *data, uint32_t dataLen) { + if (isPng(data, dataLen)) { + return Format::PNG; + } + if (isJpg(data, dataLen)) { + return Format::JPG; + } + if (isWebp(data, dataLen)) { + return Format::WEBP; + } + if (isPvr(data, dataLen)) { + return Format::PVR; + } + if (isEtc(data, dataLen)) { + return Format::ETC; + } + if (isEtc2(data, dataLen)) { + return Format::ETC2; + } + if (isASTC(data, dataLen)) { + return Format::ASTC; + } + if (isCompressed(data, dataLen)) { + return Format::COMPRESSED; + } + return Format::UNKNOWN; +} + +gfx::Format Image::getASTCFormat(const unsigned char *pHeader) { + int xdim = pHeader[ASTC_HEADER_MAGIC]; + int ydim = pHeader[ASTC_HEADER_MAGIC + 1]; + + if (xdim == 4) return gfx::Format::ASTC_RGBA_4X4; + if (xdim == 5) { + if (ydim == 4) return gfx::Format::ASTC_RGBA_5X4; + return gfx::Format::ASTC_RGBA_5X5; + } + if (xdim == 6) { + if (ydim == 5) return gfx::Format::ASTC_RGBA_6X5; + return gfx::Format::ASTC_RGBA_6X6; + } + if (xdim == 8) { + if (ydim == 5) return gfx::Format::ASTC_RGBA_8X5; + if (ydim == 6) return gfx::Format::ASTC_RGBA_8X6; + return gfx::Format::ASTC_RGBA_8X8; + } + if (xdim == 10) { + if (ydim == 5) return gfx::Format::ASTC_RGBA_10X5; + if (ydim == 6) return gfx::Format::ASTC_RGBA_10X6; + if (ydim == 8) return gfx::Format::ASTC_RGBA_10X8; + return gfx::Format::ASTC_RGBA_10X10; + } + if (ydim == 10) return gfx::Format::ASTC_RGBA_12X10; + return gfx::Format::ASTC_RGBA_12X12; +} + +namespace { +/* + * ERROR HANDLING: + * + * The JPEG library's standard error handler (jerror.c) is divided into + * several "methods" which you can override individually. This lets you + * adjust the behavior without duplicating a lot of code, which you might + * have to update with each future release. + * + * We override the "error_exit" method so that control is returned to the + * library's caller when a fatal error occurs, rather than calling exit() + * as the standard error_exit method does. + * + * We use C's setjmp/longjmp facility to return control. This means that the + * routine which calls the JPEG library must first execute a setjmp() call to + * establish the return point. We want the replacement error_exit to do a + * longjmp(). But we need to make the setjmp buffer accessible to the + * error_exit routine. To do this, we make a private extension of the + * standard JPEG error handler object. (If we were using C++, we'd say we + * were making a subclass of the regular error handler.) + * + * Here's the extended error handler struct: + */ +#if CC_USE_JPEG +struct MyErrorMgr { + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ +}; + +using MyErrorPtr = struct MyErrorMgr *; + +/* + * Here's the routine that will replace the standard error_exit method: + */ + +void myErrorExit(j_common_ptr cinfo) { + /* cinfo->err really points to a MyErrorMgr struct, so coerce pointer */ + auto *myerr = reinterpret_cast(cinfo->err); + + /* Always display the message. */ + /* We could postpone this until after returning, if we chose. */ + /* internal message function can't show error message in some platforms, so we rewrite it here. + * edit it if has version conflict. + */ + //(*cinfo->err->output_message) (cinfo); + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + CC_LOG_DEBUG("jpeg error: %s", buffer); + + /* Return control to the setjmp point */ + longjmp(myerr->setjmp_buffer, 1); +} +#endif // CC_USE_JPEG +} // namespace + +bool Image::initWithJpgData(const unsigned char *data, uint32_t dataLen) { +#if CC_USE_JPEG + /* these are standard libjpeg structures for reading(decompression) */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct MyErrorMgr jerr; + /* libjpeg data structure for storing one row, that is, scanline of an image */ + JSAMPROW rowPointer[1] = {nullptr}; + uint32_t location = 0; + + bool ret = false; + do { + /* We set up the normal JPEG error routines, then override error_exit. */ + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = myErrorExit; + /* Establish the setjmp return context for MyErrorExit to use. */ + if (setjmp(jerr.setjmp_buffer)) { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + jpeg_destroy_decompress(&cinfo); + break; + } + + /* setup decompression process and source, then read JPEG header */ + jpeg_create_decompress(&cinfo); + + jpeg_mem_src(&cinfo, const_cast(data), dataLen); + + /* reading the image header which contains image information */ + #if (JPEG_LIB_VERSION >= 90) + // libjpeg 0.9 adds stricter types. + jpeg_read_header(&cinfo, TRUE); + #else + jpeg_read_header(&cinfo, TRUE); + #endif + + // we only support RGB or grayscale + if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { + _renderFormat = gfx::Format::L8; + } else { + cinfo.out_color_space = JCS_RGB; + _renderFormat = gfx::Format::RGB8; + } + + /* Start decompression jpeg here */ + jpeg_start_decompress(&cinfo); + + /* init image info */ + _isCompressed = false; + _width = cinfo.output_width; + _height = cinfo.output_height; + _dataLen = cinfo.output_width * cinfo.output_height * cinfo.output_components; + _data = static_cast(malloc(_dataLen * sizeof(unsigned char))); + CC_BREAK_IF(!_data); + + /* now actually read the jpeg into the raw buffer */ + /* read one scan line at a time */ + while (cinfo.output_scanline < cinfo.output_height) { + rowPointer[0] = _data + location; + location += cinfo.output_width * cinfo.output_components; + jpeg_read_scanlines(&cinfo, rowPointer, 1); + } + + /* When read image file with broken data, jpeg_finish_decompress() may cause error. + * Besides, jpeg_destroy_decompress() shall deallocate and release all memory associated + * with the decompression object. + * So it doesn't need to call jpeg_finish_decompress(). + */ + //jpeg_finish_decompress( &cinfo ); + jpeg_destroy_decompress(&cinfo); + /* wrap up decompression, destroy objects, free pointers and close open files */ + ret = true; + } while (false); + + return ret; +#endif // CC_USE_JPEG +} + +bool Image::initWithPngData(const unsigned char *data, uint32_t dataLen) { +#if CC_USE_PNG + // length of bytes to check if it is a valid png file + #define PNGSIGSIZE 8 + bool ret = false; + png_byte header[PNGSIGSIZE] = {0}; + png_structp pngPtr = nullptr; + png_infop infoPtr = nullptr; + + do { + // png header len is 8 bytes + CC_BREAK_IF(dataLen < PNGSIGSIZE); + + // check the data is png or not + memcpy(header, data, PNGSIGSIZE); + CC_BREAK_IF(png_sig_cmp(header, 0, PNGSIGSIZE)); + + // init png_struct + pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + CC_BREAK_IF(!pngPtr); + + // init png_info + infoPtr = png_create_info_struct(pngPtr); + CC_BREAK_IF(!infoPtr); + + CC_BREAK_IF(setjmp(png_jmpbuf(pngPtr))); + + // set the read call back function + tImageSource imageSource; + imageSource.data = const_cast(data); + imageSource.size = dataLen; + imageSource.offset = 0; + png_set_read_fn(pngPtr, &imageSource, pngReadCallback); + + // read png header info + + // read png file info + png_read_info(pngPtr, infoPtr); + + _isCompressed = false; + _width = png_get_image_width(pngPtr, infoPtr); + _height = png_get_image_height(pngPtr, infoPtr); + png_byte bitDepth = png_get_bit_depth(pngPtr, infoPtr); + png_uint_32 colorType = png_get_color_type(pngPtr, infoPtr); + + //CC_LOG_DEBUG("color type %u", color_type); + + // force palette images to be expanded to 24-bit RGB + // it may include alpha channel + if (colorType == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(pngPtr); + } + // low-bit-depth grayscale images are to be expanded to 8 bits + if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { + bitDepth = 8; + png_set_expand_gray_1_2_4_to_8(pngPtr); + } + // expand any tRNS chunk data into a full alpha channel + if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(pngPtr); + } + // reduce images with 16-bit samples to 8 bits + if (bitDepth == 16) { + png_set_strip_16(pngPtr); + } + + // Expanded earlier for grayscale, now take care of palette and rgb + if (bitDepth < 8) { + png_set_packing(pngPtr); + } + // update info + png_read_update_info(pngPtr, infoPtr); + colorType = png_get_color_type(pngPtr, infoPtr); + + switch (colorType) { + case PNG_COLOR_TYPE_GRAY: + _renderFormat = gfx::Format::L8; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + _renderFormat = gfx::Format::LA8; + break; + case PNG_COLOR_TYPE_RGB: + _renderFormat = gfx::Format::RGB8; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + _renderFormat = gfx::Format::RGBA8; + break; + default: + break; + } + + auto *rowPointers = static_cast(malloc(sizeof(png_bytep) * _height)); + + const png_size_t rowBytes = png_get_rowbytes(pngPtr, infoPtr); + + _dataLen = static_cast(rowBytes * _height); + _data = static_cast(malloc(_dataLen * sizeof(unsigned char))); + if (!_data) { + if (rowPointers != nullptr) { + free(rowPointers); + } + break; + } + + for (int i = 0; i < _height; ++i) { + rowPointers[i] = _data + i * rowBytes; + } + png_read_image(pngPtr, rowPointers); + png_read_end(pngPtr, nullptr); + + if (rowPointers != nullptr) { + free(rowPointers); + } + + ret = true; + } while (false); + + if (pngPtr) { + png_destroy_read_struct(&pngPtr, (infoPtr) ? &infoPtr : nullptr, nullptr); + } + return ret; +#endif //CC_USE_PNG +} + +bool Image::initWithPVRv2Data(const unsigned char *data, uint32_t dataLen) { + //Cast first sizeof(PVRTexHeader) bytes of data stream as PVRTexHeader + const auto *header = static_cast(static_cast(data)); + + //Make sure that tag is in correct formatting + if (memcmp(&header->pvrTag, G_PVR_TEX_IDENTIFIER, strlen(G_PVR_TEX_IDENTIFIER)) != 0) { + return false; + } + + unsigned int flags = CC_SWAP_INT32_LITTLE_TO_HOST(header->flags); + auto formatFlags = static_cast(flags & PVR_TEXTURE_FLAG_TYPE_MASK); + const bool flipped = (flags & static_cast(PVR2TextureFlag::VERTICAL_FLIP)) != 0; + if (flipped) { + CC_LOG_DEBUG("initWithPVRv2Data: WARNING: Image is flipped. Regenerate it using PVRTexTool"); + } + + if (V2_PIXEL_FORMATHASH.find(formatFlags) == V2_PIXEL_FORMATHASH.end()) { + CC_LOG_DEBUG("initWithPVRv2Data: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant", (int)formatFlags); + return false; + } + + const auto it = V2_PIXEL_FORMATHASH.find(formatFlags); + if (it == V2_PIXEL_FORMATHASH.end()) { + CC_LOG_DEBUG("initWithPVRv2Data: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant", (int)formatFlags); + return false; + } + + _renderFormat = it->second; + + //Get size of mipmap + _width = static_cast(CC_SWAP_INT32_LITTLE_TO_HOST(header->width)); + _height = static_cast(CC_SWAP_INT32_LITTLE_TO_HOST(header->height)); + _isCompressed = true; + + //Move by size of header + _dataLen = dataLen - sizeof(PVRv2TexHeader); + _data = static_cast(malloc(_dataLen * sizeof(unsigned char))); + memcpy(_data, data + sizeof(PVRv2TexHeader), _dataLen); + + return true; +} + +bool Image::initWithPVRv3Data(const unsigned char *data, uint32_t dataLen) { + if (static_cast(dataLen) < sizeof(PVRv3TexHeader)) { + return false; + } + + const auto *header = static_cast(static_cast(data)); + + // validate version + if (CC_SWAP_INT32_BIG_TO_HOST(header->version) != 0x50565203) { + CC_LOG_DEBUG("initWithPVRv3Data: WARNING: pvr file version mismatch"); + return false; + } + + // parse pixel format + auto pixelFormat = static_cast(header->pixelFormat); + + if (V3_PIXEL_FORMATHASH.find(pixelFormat) == V3_PIXEL_FORMATHASH.end()) { + CC_LOG_DEBUG("initWithPVRv3Data: WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format variant", + static_cast(pixelFormat)); + return false; + } + + const auto it = V3_PIXEL_FORMATHASH.find(pixelFormat); + if (it == V3_PIXEL_FORMATHASH.end()) { + CC_LOG_DEBUG("initWithPVRv3Data: WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format variant", + static_cast(pixelFormat)); + return false; + } + + _renderFormat = it->second; + + // sizing + _width = static_cast(CC_SWAP_INT32_LITTLE_TO_HOST(header->width)); + _height = static_cast(CC_SWAP_INT32_LITTLE_TO_HOST(header->height)); + _isCompressed = true; + + _dataLen = dataLen - (sizeof(PVRv3TexHeader) + header->metadataLength); + _data = static_cast(malloc(_dataLen * sizeof(unsigned char))); + memcpy(_data, data + sizeof(PVRv3TexHeader) + header->metadataLength, _dataLen); + + return true; +} + +bool Image::initWithETCData(const unsigned char *data, uint32_t dataLen) { + const auto *header = static_cast(data); + + //check the data + if (!etc1_pkm_is_valid(header)) { + return false; + } + + _width = etc1_pkm_get_width(header); + _height = etc1_pkm_get_height(header); + _isCompressed = true; + + if (0 == _width || 0 == _height) { + return false; + } + + _renderFormat = gfx::Format::ETC_RGB8; + _dataLen = dataLen - ETC_PKM_HEADER_SIZE; + _data = static_cast(malloc(_dataLen * sizeof(unsigned char))); + memcpy(_data, static_cast(data) + ETC_PKM_HEADER_SIZE, _dataLen); + return true; +} + +bool Image::initWithETC2Data(const unsigned char *data, uint32_t dataLen) { + const auto *header = static_cast(data); + + //check the data + if (!etc2_pkm_is_valid(header)) { + return false; + } + + _width = etc2_pkm_get_width(header); + _height = etc2_pkm_get_height(header); + _isCompressed = true; + + if (0 == _width || 0 == _height) { + return false; + } + + etc2_uint32 format = etc2_pkm_get_format(header); + if (format == ETC2_RGB_NO_MIPMAPS) { + _renderFormat = gfx::Format::ETC2_RGB8; + } else { + _renderFormat = gfx::Format::ETC2_RGBA8; + } + + _dataLen = dataLen - ETC2_PKM_HEADER_SIZE; + _data = static_cast(malloc(_dataLen * sizeof(unsigned char))); + memcpy(_data, static_cast(data) + ETC2_PKM_HEADER_SIZE, _dataLen); + return true; +} + +bool Image::initWithASTCData(const unsigned char *data, uint32_t dataLen) { + const auto *header = static_cast(data); + + //check the data + if (!astcIsValid(header)) { + return false; + } + + _width = astcGetWidth(header); + _height = astcGetHeight(header); + _isCompressed = true; + + if (0 == _width || 0 == _height) { + return false; + } + + _renderFormat = getASTCFormat(header); + + _dataLen = dataLen - ASTC_HEADER_SIZE; + _data = static_cast(malloc(_dataLen * sizeof(unsigned char))); + memcpy(_data, data + ASTC_HEADER_SIZE, _dataLen); + + return true; +} + +bool Image::initWithCompressedMipsData(const unsigned char *data, uint32_t /*dataLen*/) { //NOLINT(misc-no-recursion) + //check the data + if (!compressedIsValid(data)) { + return false; + } + + // unpack compressed chunks + int width = 0; + int height = 0; + bool ret = false; + const auto chunkNumbers = getChunkNumbers(data); + ccstd::vector dataBuffers; + dataBuffers.resize(chunkNumbers); + _mipmapLevelDataSize.resize(chunkNumbers); + uint32_t dstDataLen = 0; + for (uint32_t i = 0; i < chunkNumbers; ++i) { + const auto *chunk = getChunk(data, i); + const auto dataLength = getChunkSizes(data, i); + if (_data) free(_data); + ret = initWithImageData(chunk, dataLength); + + if (i == 0) { + width = _width; + height = _height; + } + + dstDataLen += _dataLen; + _mipmapLevelDataSize[i] = _dataLen; + dataBuffers[i] = static_cast(malloc(_dataLen * sizeof(unsigned char))); + memcpy(dataBuffers[i], _data, _dataLen); + + if (!ret) break; + } + + auto *dstData = static_cast(malloc(dstDataLen * sizeof(unsigned char))); + uint32_t byteOffset = 0; + for (uint32_t i = 0; i < chunkNumbers; ++i) { + memcpy(dstData + byteOffset, dataBuffers[i], _mipmapLevelDataSize[i]); + byteOffset += _mipmapLevelDataSize[i]; + free(dataBuffers[i]); + } + + _width = width; + _height = height; + if (_data) free(_data); + _data = dstData; + _dataLen = dstDataLen; + + return ret; +} + +bool Image::initWithPVRData(const unsigned char *data, uint32_t dataLen) { + return initWithPVRv2Data(data, dataLen) || initWithPVRv3Data(data, dataLen); +} + +#if CC_USE_WEBP +bool Image::initWithWebpData(const unsigned char *data, uint32_t dataLen) { + bool ret = false; + do { + WebPDecoderConfig config; + if (WebPInitDecoderConfig(&config) == 0) break; + if (WebPGetFeatures(static_cast(data), dataLen, &config.input) != VP8_STATUS_OK) break; + if (config.input.width == 0 || config.input.height == 0) break; + + config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB; + _renderFormat = config.input.has_alpha ? gfx::Format::RGBA8 : gfx::Format::RGB8; + _width = config.input.width; + _height = config.input.height; + _isCompressed = false; + + _dataLen = _width * _height * (config.input.has_alpha ? 4 : 3); + _data = static_cast(malloc(_dataLen * sizeof(unsigned char))); + + config.output.u.RGBA.rgba = static_cast(_data); + config.output.u.RGBA.stride = _width * (config.input.has_alpha ? 4 : 3); + config.output.u.RGBA.size = _dataLen; + config.output.is_external_memory = 1; + + if (WebPDecode(static_cast(data), dataLen, &config) != VP8_STATUS_OK) { + free(_data); + _data = nullptr; + break; + } + + ret = true; + } while (false); + return ret; +} +#endif // CC_USE_WEBP + +bool Image::initWithRawData(const unsigned char *data, uint32_t /*dataLen*/, int width, int height, int /*bitsPerComponent*/, bool /*preMulti*/) { + bool ret = false; + do { + CC_BREAK_IF(0 == width || 0 == height); + + _height = height; + _width = width; + _renderFormat = gfx::Format::RGBA8; + _isCompressed = false; + + // only RGBA8888 supported + int bytesPerComponent = 4; + _dataLen = height * width * bytesPerComponent; + _data = static_cast(malloc(_dataLen * sizeof(unsigned char))); + CC_BREAK_IF(!_data); + memcpy(_data, data, _dataLen); + + ret = true; + } while (false); + + return ret; +} + +bool Image::saveToFile(const std::string &filename, bool isToRGB) { + //only support for Image::PixelFormat::RGB888 or Image::PixelFormat::RGBA8888 uncompressed data + if (isCompressed() || (_renderFormat != gfx::Format::RGB8 && _renderFormat != gfx::Format::RGBA8)) { + CC_LOG_DEBUG("saveToFile: Image: saveToFile is only support for gfx::Format::RGB8 or gfx::Format::RGBA8 uncompressed data for now"); + return false; + } + + std::string fileExtension = FileUtils::getInstance()->getFileExtension(filename); + + if (fileExtension == ".png") { + return saveImageToPNG(filename, isToRGB); + } + if (fileExtension == ".jpg") { + return saveImageToJPG(filename); + } + CC_LOG_DEBUG("saveToFile: Image: saveToFile no support file extension(only .png or .jpg) for file: %s", filename.c_str()); + return false; +} + +bool Image::saveImageToPNG(const std::string &filePath, bool isToRGB) { + bool ret = false; + + FILE *fp{nullptr}; + png_structp pngPtr{nullptr}; + png_infop infoPtr{nullptr}; + png_colorp palette{nullptr}; + png_bytep *rowPointers{nullptr}; + const bool hasAlpha = gfx::GFX_FORMAT_INFOS[static_cast(_renderFormat)].hasAlpha; + do { + // Init png structure and png ptr + pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + CC_BREAK_IF(!pngPtr); + if (setjmp(png_jmpbuf(pngPtr))) { + break; + } + infoPtr = png_create_info_struct(pngPtr); + CC_BREAK_IF(!infoPtr); + // Start open file + fp = fopen(FileUtils::getInstance()->getSuitableFOpen(filePath).c_str(), "wb"); + CC_BREAK_IF(!fp); + png_init_io(pngPtr, fp); + const auto mask = (!isToRGB && hasAlpha) ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB; + png_set_IHDR(pngPtr, infoPtr, _width, _height, 8, mask, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + palette = static_cast(png_malloc(pngPtr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color))); + CC_BREAK_IF(!palette); + png_set_PLTE(pngPtr, infoPtr, palette, PNG_MAX_PALETTE_LENGTH); + + png_write_info(pngPtr, infoPtr); + + png_set_packing(pngPtr); + + //row_pointers = (png_bytep *)png_malloc(png_ptr, _height * sizeof(png_bytep)); + rowPointers = static_cast(CC_MALLOC(_height * sizeof(png_bytep))); + CC_BREAK_IF(!rowPointers); + + if (!hasAlpha) { + for (int i = 0; i < _height; i++) { + rowPointers[i] = static_cast(_data) + i * _width * 3; + } + png_write_image(pngPtr, rowPointers); + } else { + if (isToRGB) { + auto *tempData = static_cast(CC_MALLOC(_width * _height * 3 * sizeof(unsigned char))); + if (nullptr == tempData) { + break; + } + auto *dst = tempData; + auto *src = _data; + for (int t = 0; t < _width * _height; t++) { + memcpy(dst, src, 3); + dst += 3; + src += 4; + } + + for (int i = 0; i < _height; i++) { + rowPointers[i] = static_cast(tempData) + i * _width * 3; + } + if (tempData != nullptr) { + CC_FREE(tempData); + } + png_write_image(pngPtr, rowPointers); + } else { + for (int i = 0; i < _height; i++) { + rowPointers[i] = static_cast(_data) + i * _width * 4 /*Bytes per pixel*/; + } + png_write_image(pngPtr, rowPointers); + } + } + + png_write_end(pngPtr, infoPtr); + ret = true; + } while (false); + + /*Later free for all functions*/ + if (rowPointers) { + free(rowPointers); + rowPointers = nullptr; + } + if (palette) { + png_free(pngPtr, palette); + } + if (infoPtr) { + png_destroy_write_struct(&pngPtr, &infoPtr); + } + if (fp) { + fclose(fp); + } + if (pngPtr) { + png_destroy_write_struct(&pngPtr, nullptr); + } + return ret; +} + +bool Image::saveImageToJPG(const std::string &filePath) { + bool ret = false; + do { + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + FILE *outfile; /* target file */ + JSAMPROW rowPointer[1]; /* pointer to JSAMPLE row[s] */ + int rowStride; /* physical row width in image buffer */ + + cinfo.err = jpeg_std_error(&jerr); + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress(&cinfo); + + CC_BREAK_IF((outfile = fopen(FileUtils::getInstance()->getSuitableFOpen(filePath).c_str(), "wb")) == nullptr); + + jpeg_stdio_dest(&cinfo, outfile); + + cinfo.image_width = _width; /* image width and height, in pixels */ + cinfo.image_height = _height; + cinfo.input_components = 3; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, 90, TRUE); + + jpeg_start_compress(&cinfo, TRUE); + + rowStride = _width * 3; /* JSAMPLEs per row in image_buffer */ + const bool hasAlpha = gfx::GFX_FORMAT_INFOS[static_cast(_renderFormat)].hasAlpha; + + if (hasAlpha) { + auto *tempData = static_cast(CC_MALLOC(_width * _height * 3 * sizeof(unsigned char))); + if (nullptr == tempData) { + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + fclose(outfile); + break; + } + auto *dst = tempData; + auto *src = _data; + for (int t = 0; t < _width * _height; t++) { + memcpy(dst, src, 3); + dst += 3; + src += 4; + } + while (cinfo.next_scanline < cinfo.image_height) { + rowPointer[0] = &tempData[cinfo.next_scanline * rowStride]; + (void)jpeg_write_scanlines(&cinfo, rowPointer, 1); + } + if (tempData != nullptr) { + CC_FREE(tempData); + } + } else { + while (cinfo.next_scanline < cinfo.image_height) { + rowPointer[0] = &_data[cinfo.next_scanline * rowStride]; + (void)jpeg_write_scanlines(&cinfo, rowPointer, 1); + } + } + + jpeg_finish_compress(&cinfo); + fclose(outfile); + jpeg_destroy_compress(&cinfo); + + ret = true; + } while (false); + return ret; +} + +} // namespace cc diff --git a/cocos/platform/Image.h b/cocos/platform/Image.h new file mode 100644 index 0000000..17ae7e9 --- /dev/null +++ b/cocos/platform/Image.h @@ -0,0 +1,136 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2016-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/RefCounted.h" +#include "base/std/container/string.h" +#include "gfx-base/GFXDef.h" + +namespace cc { + +class Image : public RefCounted { +public: + Image(); + ~Image() override; + + /** Supported formats for Image */ + enum class Format { + //! JPEG + JPG, + //! PNG + PNG, + //! WebP + WEBP, + //! PVR + PVR, + //! ETC + ETC, + //! ETC2 + ETC2, + //! ASTC + ASTC, + //! Compressed Data + COMPRESSED, + //! Raw Data + RAW_DATA, + //! Unknown format + UNKNOWN + }; + + bool initWithImageFile(const ccstd::string &path); + bool initWithImageData(const unsigned char *data, uint32_t dataLen); + + // @warning kFmtRawData only support RGBA8888 + bool initWithRawData(const unsigned char *data, uint32_t dataLen, int width, int height, int bitsPerComponent, bool preMulti = false); + + // data will be free outside. + inline void takeData(unsigned char **outData) { + *outData = _data; + _data = nullptr; + } + + // Getters + inline unsigned char *getData() const { return _data; } + inline uint32_t getDataLen() const { return _dataLen; } + inline Format getFileType() const { return _fileType; } + inline gfx::Format getRenderFormat() const { return _renderFormat; } + inline int getWidth() const { return _width; } + inline int getHeight() const { return _height; } + inline ccstd::string getFilePath() const { return _filePath; } + inline bool isCompressed() const { return _isCompressed; } + inline const ccstd::vector &getMipmapLevelDataSize() const { return _mipmapLevelDataSize; } + + /** + @brief Save Image data to the specified file, with specified format. + @param filename the file's absolute path, including file suffix. + @param isToRGB whether the image is saved as RGB format. + */ + bool saveToFile(const std::string &filename, bool isToRGB = true); + +protected: + bool initWithJpgData(const unsigned char *data, uint32_t dataLen); + bool initWithPngData(const unsigned char *data, uint32_t dataLen); +#if CC_USE_WEBP + bool initWithWebpData(const unsigned char *data, uint32_t dataLen); +#endif + bool initWithPVRData(const unsigned char *data, uint32_t dataLen); + bool initWithPVRv2Data(const unsigned char *data, uint32_t dataLen); + bool initWithPVRv3Data(const unsigned char *data, uint32_t dataLen); + bool initWithETCData(const unsigned char *data, uint32_t dataLen); + bool initWithETC2Data(const unsigned char *data, uint32_t dataLen); + bool initWithASTCData(const unsigned char *data, uint32_t dataLen); + bool initWithCompressedMipsData(const unsigned char *data, uint32_t dataLen); + + bool saveImageToPNG(const std::string &filePath, bool isToRGB = true); + bool saveImageToJPG(const std::string &filePath); + + unsigned char *_data = nullptr; + uint32_t _dataLen = 0; + int _width = 0; + int _height = 0; + Format _fileType = Format::UNKNOWN; + gfx::Format _renderFormat; + ccstd::string _filePath; + bool _isCompressed = false; + ccstd::vector _mipmapLevelDataSize; + + static Format detectFormat(const unsigned char *data, uint32_t dataLen); + static bool isPng(const unsigned char *data, uint32_t dataLen); + static bool isJpg(const unsigned char *data, uint32_t dataLen); + static bool isWebp(const unsigned char *data, uint32_t dataLen); + static bool isPvr(const unsigned char *data, uint32_t dataLen); + static bool isEtc(const unsigned char *data, uint32_t dataLen); + static bool isEtc2(const unsigned char *data, uint32_t dataLen); + static bool isASTC(const unsigned char *data, uint32_t detaLen); + static bool isCompressed(const unsigned char *data, uint32_t detaLen); + + static gfx::Format getASTCFormat(const unsigned char *pHeader); + + friend class ImageUtils; +}; + +} //namespace cc diff --git a/cocos/platform/SAXParser.cpp b/cocos/platform/SAXParser.cpp new file mode 100644 index 0000000..57bf7d6 --- /dev/null +++ b/cocos/platform/SAXParser.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** + Copyright (c) 2010 Максим Аксенов + Copyright (c) 2010 cocos2d-x.org + Copyright (c) 2013 Martell Malone + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/SAXParser.h" +#include "platform/FileUtils.h" +#include "tinyxml2/tinyxml2.h" + +namespace cc { + +class XmlSaxHander : public tinyxml2::XMLVisitor { +public: + XmlSaxHander() : _ccsaxParserImp(0){}; + + virtual bool VisitEnter(const tinyxml2::XMLElement &element, const tinyxml2::XMLAttribute *firstAttribute); + virtual bool VisitExit(const tinyxml2::XMLElement &element); + virtual bool Visit(const tinyxml2::XMLText &text); + virtual bool Visit(const tinyxml2::XMLUnknown &) { return true; } + + void setSAXParserImp(SAXParser *parser) { + _ccsaxParserImp = parser; + } + +private: + SAXParser *_ccsaxParserImp; +}; + +bool XmlSaxHander::VisitEnter(const tinyxml2::XMLElement &element, const tinyxml2::XMLAttribute *firstAttribute) { + //log(" VisitEnter %s",element.Value()); + + ccstd::vector attsVector; + for (const tinyxml2::XMLAttribute *attrib = firstAttribute; attrib; attrib = attrib->Next()) { + //log("%s", attrib->Name()); + attsVector.push_back(attrib->Name()); + //log("%s",attrib->Value()); + attsVector.push_back(attrib->Value()); + } + + // nullptr is used in c++11 + //attsVector.push_back(nullptr); + attsVector.push_back(nullptr); + + SAXParser::startElement(_ccsaxParserImp, (const CC_XML_CHAR *)element.Value(), (const CC_XML_CHAR **)(&attsVector[0])); + return true; +} +bool XmlSaxHander::VisitExit(const tinyxml2::XMLElement &element) { + //log("VisitExit %s",element.Value()); + + SAXParser::endElement(_ccsaxParserImp, (const CC_XML_CHAR *)element.Value()); + return true; +} + +bool XmlSaxHander::Visit(const tinyxml2::XMLText &text) { + //log("Visit %s",text.Value()); + SAXParser::textHandler(_ccsaxParserImp, (const CC_XML_CHAR *)text.Value(), static_cast(strlen(text.Value()))); + return true; +} + +SAXParser::SAXParser() { + _delegator = nullptr; +} + +SAXParser::~SAXParser(void) { +} + +bool SAXParser::init(const char *encoding) { + CC_UNUSED_PARAM(encoding); + // nothing to do + return true; +} + +bool SAXParser::parse(const char *xmlData, size_t dataLength) { + tinyxml2::XMLDocument tinyDoc; + tinyDoc.Parse(xmlData, dataLength); + XmlSaxHander printer; + printer.setSAXParserImp(this); + + return tinyDoc.Accept(&printer); +} + +bool SAXParser::parse(const ccstd::string &filename) { + bool ret = false; + Data data = FileUtils::getInstance()->getDataFromFile(filename); + if (!data.isNull()) { + ret = parse((const char *)data.getBytes(), data.getSize()); + } + + return ret; +} + +void SAXParser::startElement(void *ctx, const CC_XML_CHAR *name, const CC_XML_CHAR **atts) { + ((SAXParser *)(ctx))->_delegator->startElement(ctx, (char *)name, (const char **)atts); +} + +void SAXParser::endElement(void *ctx, const CC_XML_CHAR *name) { + ((SAXParser *)(ctx))->_delegator->endElement(ctx, (char *)name); +} +void SAXParser::textHandler(void *ctx, const CC_XML_CHAR *name, int len) { + ((SAXParser *)(ctx))->_delegator->textHandler(ctx, (char *)name, len); +} +void SAXParser::setDelegator(SAXDelegator *delegator) { + _delegator = delegator; +} + +} // namespace cc diff --git a/cocos/platform/SAXParser.h b/cocos/platform/SAXParser.h new file mode 100644 index 0000000..d22734a --- /dev/null +++ b/cocos/platform/SAXParser.h @@ -0,0 +1,122 @@ +/**************************************************************************** + Copyright (c) 2010 cocos2d-x.org + Copyright (c) 2010 Максим Аксенов + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef __CCSAXPARSER_H__ +#define __CCSAXPARSER_H__ +/// @cond DO_NOT_SHOW + +#include "base/Macros.h" +#include "base/std/container/string.h" + +namespace cc { + +/** + * @addtogroup platform + * @{ + */ + +typedef unsigned char CC_XML_CHAR; + +class CC_DLL SAXDelegator { +public: + virtual ~SAXDelegator() {} + + /** + * @js NA + * @lua NA + */ + virtual void startElement(void *ctx, const char *name, const char **atts) = 0; + /** + * @js NA + * @lua NA + */ + virtual void endElement(void *ctx, const char *name) = 0; + /** + * @js NA + * @lua NA + */ + virtual void textHandler(void *ctx, const char *s, int len) = 0; +}; + +class CC_DLL SAXParser { + SAXDelegator *_delegator; + +public: + /** + * @js NA + * @lua NA + */ + SAXParser(); + /** + * @js NA + * @lua NA + */ + ~SAXParser(void); + /** + * @js NA + * @lua NA + */ + bool init(const char *encoding); + /** + * @js NA + * @lua NA + */ + bool parse(const char *xmlData, size_t dataLength); + /** + * @js NA + * @lua NA + */ + bool parse(const ccstd::string &filename); + /** + * @js NA + * @lua NA + */ + void setDelegator(SAXDelegator *delegator); + /** + * @js NA + * @lua NA + */ + static void startElement(void *ctx, const CC_XML_CHAR *name, const CC_XML_CHAR **atts); + /** + * @js NA + * @lua NA + */ + static void endElement(void *ctx, const CC_XML_CHAR *name); + /** + * @js NA + * @lua NA + */ + static void textHandler(void *ctx, const CC_XML_CHAR *name, int len); +}; + +// end of platform group +/// @} + +} // namespace cc + +/// @endcond +#endif //__CCSAXPARSER_H__ diff --git a/cocos/platform/SDLHelper.cpp b/cocos/platform/SDLHelper.cpp new file mode 100644 index 0000000..71896c2 --- /dev/null +++ b/cocos/platform/SDLHelper.cpp @@ -0,0 +1,423 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/SDLHelper.h" +#include "SDL2/SDL.h" +#include "SDL2/SDL_main.h" +#include "SDL2/SDL_syswm.h" +#include "base/Log.h" +#include "engine/EngineEvents.h" +#include "platform/BasePlatform.h" +#include "platform/interfaces/modules/IScreen.h" +#include "platform/interfaces/modules/ISystemWindow.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" + +namespace { +std::unordered_map gKeyMap = { + {SDLK_APPLICATION, cc::KeyCode::CONTEXT_MENU}, + {SDLK_SCROLLLOCK, cc::KeyCode::SCROLLLOCK}, + {SDLK_PAUSE, cc::KeyCode::PAUSE}, + {SDLK_PRINTSCREEN, cc::KeyCode::PRINT_SCREEN}, + {SDLK_INSERT, cc::KeyCode::INSERT}, + {SDLK_ESCAPE, cc::KeyCode::ESCAPE}, + {SDLK_MINUS, cc::KeyCode::MINUS}, + {SDLK_LSHIFT, cc::KeyCode::SHIFT_LEFT}, + {SDLK_RSHIFT, cc::KeyCode::SHIFT_RIGHT}, + {SDLK_EQUALS, cc::KeyCode::EQUAL}, + {SDLK_BACKSLASH, cc::KeyCode::BACKSLASH}, + {SDLK_BACKQUOTE, cc::KeyCode::BACKQUOTE}, + {SDLK_BACKSPACE, cc::KeyCode::BACKSPACE}, + {SDLK_RETURN, cc::KeyCode::ENTER}, + {SDLK_RETURN2, cc::KeyCode::ENTER}, + {SDLK_LEFTBRACKET, cc::KeyCode::BRACKET_LEFT}, + {SDLK_RIGHTBRACKET, cc::KeyCode::BRACKET_RIGHT}, + {SDLK_SEMICOLON, cc::KeyCode::SEMICOLON}, + {SDLK_QUOTE, cc::KeyCode::QUOTE}, + {SDLK_TAB, cc::KeyCode::TAB}, + {SDLK_LCTRL, cc::KeyCode::CONTROL_LEFT}, + {SDLK_RCTRL, cc::KeyCode::CONTROL_RIGHT}, + {SDLK_LALT, cc::KeyCode::ALT_LEFT}, + {SDLK_RALT, cc::KeyCode::ALT_RIGHT}, + {SDLK_LEFT, cc::KeyCode::ARROW_LEFT}, + {SDLK_RIGHT, cc::KeyCode::ARROW_RIGHT}, + {SDLK_UP, cc::KeyCode::ARROW_UP}, + {SDLK_DOWN, cc::KeyCode::ARROW_DOWN}, + {SDLK_KP_ENTER, cc::KeyCode::NUMPAD_ENTER}, + {SDLK_KP_PLUS, cc::KeyCode::NUMPAD_PLUS}, + {SDLK_KP_MULTIPLY, cc::KeyCode::NUMPAD_MULTIPLY}, + {SDLK_KP_DIVIDE, cc::KeyCode::NUMPAD_DIVIDE}, + {SDLK_KP_MINUS, cc::KeyCode::NUMPAD_MINUS}, + {SDLK_KP_PERIOD, cc::KeyCode::NUMPAD_DECIMAL}, + {SDLK_KP_BACKSPACE, cc::KeyCode::BACKSPACE}, + {SDLK_NUMLOCKCLEAR, cc::KeyCode::NUM_LOCK}, + {SDLK_HOME, cc::KeyCode::HOME}, + {SDLK_PAGEUP, cc::KeyCode::PAGE_UP}, + {SDLK_PAGEDOWN, cc::KeyCode::PAGE_DOWN}, + {SDLK_END, cc::KeyCode::END}, + {SDLK_COMMA, cc::KeyCode::COMMA}, + {SDLK_PERIOD, cc::KeyCode::PERIOD}, + {SDLK_SLASH, cc::KeyCode::SLASH}, + {SDLK_SPACE, cc::KeyCode::SPACE}, + {SDLK_DELETE, cc::KeyCode::DELETE_KEY}, + {SDLK_CAPSLOCK, cc::KeyCode::CAPS_LOCK}, + {SDLK_KP_0, cc::KeyCode::NUMPAD_0}, + {SDLK_KP_1, cc::KeyCode::NUMPAD_1}, + {SDLK_KP_2, cc::KeyCode::NUMPAD_2}, + {SDLK_KP_3, cc::KeyCode::NUMPAD_3}, + {SDLK_KP_4, cc::KeyCode::NUMPAD_4}, + {SDLK_KP_5, cc::KeyCode::NUMPAD_5}, + {SDLK_KP_6, cc::KeyCode::NUMPAD_6}, + {SDLK_KP_7, cc::KeyCode::NUMPAD_7}, + {SDLK_KP_8, cc::KeyCode::NUMPAD_8}, + {SDLK_KP_9, cc::KeyCode::NUMPAD_9}}; + +int sdlKeycodeToCocosCode(int sdlCode, int mode) { + auto it = gKeyMap.find(sdlCode); + if (it != gKeyMap.end()) { + return static_cast(it->second); + } + + if (sdlCode >= SDLK_F1 && sdlCode <= SDLK_F12) { + // F1 ~ F12 + return 112 + (sdlCode - SDLK_F1); + } else { + int code = sdlCode & (~(1 << 30)); + if (code >= SDLK_a && code <= SDLK_z) { + return 'A' + (code - SDLK_a); + } else { + return code; + } + } +} + +std::unordered_map gWindowFlagMap = { + {cc::ISystemWindow::CC_WINDOW_FULLSCREEN, SDL_WINDOW_FULLSCREEN}, + {cc::ISystemWindow::CC_WINDOW_OPENGL, SDL_WINDOW_OPENGL}, + {cc::ISystemWindow::CC_WINDOW_SHOWN, SDL_WINDOW_SHOWN}, + {cc::ISystemWindow::CC_WINDOW_HIDDEN, SDL_WINDOW_HIDDEN}, + {cc::ISystemWindow::CC_WINDOW_BORDERLESS, SDL_WINDOW_BORDERLESS}, + {cc::ISystemWindow::CC_WINDOW_RESIZABLE, SDL_WINDOW_RESIZABLE}, + {cc::ISystemWindow::CC_WINDOW_MINIMIZED, SDL_WINDOW_MINIMIZED}, + {cc::ISystemWindow::CC_WINDOW_MAXIMIZED, SDL_WINDOW_MAXIMIZED}, + {cc::ISystemWindow::CC_WINDOW_INPUT_GRABBED, SDL_WINDOW_INPUT_GRABBED}, + {cc::ISystemWindow::CC_WINDOW_INPUT_FOCUS, SDL_WINDOW_INPUT_FOCUS}, + {cc::ISystemWindow::CC_WINDOW_MOUSE_FOCUS, SDL_WINDOW_MOUSE_FOCUS}, + {cc::ISystemWindow::CC_WINDOW_FULLSCREEN_DESKTOP, SDL_WINDOW_FULLSCREEN_DESKTOP}, + {cc::ISystemWindow::CC_WINDOW_FOREIGN, SDL_WINDOW_FOREIGN}, + {cc::ISystemWindow::CC_WINDOW_ALLOW_HIGHDPI, SDL_WINDOW_ALLOW_HIGHDPI}, + {cc::ISystemWindow::CC_WINDOW_MOUSE_CAPTURE, SDL_WINDOW_MOUSE_CAPTURE}, + {cc::ISystemWindow::CC_WINDOW_ALWAYS_ON_TOP, SDL_WINDOW_ALWAYS_ON_TOP}, + {cc::ISystemWindow::CC_WINDOW_SKIP_TASKBAR, SDL_WINDOW_SKIP_TASKBAR}, + {cc::ISystemWindow::CC_WINDOW_UTILITY, SDL_WINDOW_UTILITY}, + {cc::ISystemWindow::CC_WINDOW_TOOLTIP, SDL_WINDOW_TOOLTIP}, + {cc::ISystemWindow::CC_WINDOW_POPUP_MENU, SDL_WINDOW_POPUP_MENU}, + {cc::ISystemWindow::CC_WINDOW_VULKAN, SDL_WINDOW_VULKAN}}; + +int windowFlagsToSDLWindowFlag(int flags) { + int sdlFlags = 0; + for (auto it : gWindowFlagMap) { + if ((it.first & flags) == it.first) { + sdlFlags |= it.second; + } + } + return sdlFlags; +} +} // namespace + +namespace cc { +SDLHelper::SDLHelper() {} + +SDLHelper::~SDLHelper() { + SDL_Quit(); +} + +int SDLHelper::init() { + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + // Display error message + CC_LOG_ERROR("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); + return -1; + } + // (1) Disable IDE on windows platform. + // (2) On mac platform, SDL has an internal implementation of textinput , + // which internally sends the SDL_TEXTINPUT event. Causing two events to be sent. + // So we need to stop the implementation of TextInput. + // (3) Other platforms do not use textinput in sdl. + stopTextInput(); + return 0; +} + +void SDLHelper::dispatchWindowEvent(uint32_t windowId, const SDL_WindowEvent &wevent) { + WindowEvent ev; + ev.windowId = windowId; + + switch (wevent.event) { + case SDL_WINDOWEVENT_SHOWN: { + ev.type = WindowEvent::Type::SHOW; + events::WindowEvent::broadcast(ev); + break; + } + case SDL_WINDOWEVENT_RESTORED: { + ev.type = WindowEvent::Type::RESTORED; + events::WindowEvent::broadcast(ev); + break; + } + case SDL_WINDOWEVENT_SIZE_CHANGED: { + auto *screen = BasePlatform::getPlatform()->getInterface(); + CC_ASSERT(screen != nullptr); + ev.type = WindowEvent::Type::SIZE_CHANGED; + ev.width = wevent.data1 * screen->getDevicePixelRatio(); + ev.height = wevent.data2 * screen->getDevicePixelRatio(); + events::WindowEvent::broadcast(ev); + break; + } + case SDL_WINDOWEVENT_RESIZED: { + auto *screen = BasePlatform::getPlatform()->getInterface(); + CC_ASSERT(screen != nullptr); + ev.type = WindowEvent::Type::RESIZED; + ev.width = wevent.data1 * screen->getDevicePixelRatio(); + ev.height = wevent.data2 * screen->getDevicePixelRatio(); + events::WindowEvent::broadcast(ev); + break; + } + case SDL_WINDOWEVENT_HIDDEN: { + SDL_Window *window = SDL_GetWindowFromID(windowId); + if (!isWindowMinimized(window)) { + int32_t v = SDL_GetWindowFlags(window); + ev.type = WindowEvent::Type::HIDDEN; + events::WindowEvent::broadcast(ev); + } + break; + } + case SDL_WINDOWEVENT_MINIMIZED: { + ev.type = WindowEvent::Type::MINIMIZED; + events::WindowEvent::broadcast(ev); + break; + } + case SDL_WINDOWEVENT_CLOSE: { + ev.type = WindowEvent::Type::CLOSE; + events::WindowEvent::broadcast(ev); + break; + } + } +} + +void SDLHelper::dispatchSDLEvent(uint32_t windowId, const SDL_Event &sdlEvent) { + cc::TouchEvent touch; + cc::MouseEvent mouse; + cc::KeyboardEvent keyboard; + + touch.windowId = windowId; + mouse.windowId = windowId; + keyboard.windowId = windowId; + + SDL_Window *window = SDL_GetWindowFromID(sdlEvent.window.windowID); + + switch (sdlEvent.type) { + case SDL_QUIT: { + WindowEvent ev; + ev.type = WindowEvent::Type::QUIT; + events::WindowEvent::broadcast(ev); + break; + } + case SDL_WINDOWEVENT: { + dispatchWindowEvent(windowId, sdlEvent.window); + break; + } + case SDL_MOUSEBUTTONDOWN: { + int width = 0; + int height = 0; + SDL_GetWindowSize(window, &width, &height); + const SDL_MouseButtonEvent &event = sdlEvent.button; + if (0 > event.x || event.x > width || 0 > event.y || event.y > height) { + break; + } + mouse.type = MouseEvent::Type::DOWN; + mouse.x = static_cast(event.x); + mouse.y = static_cast(event.y); + mouse.button = event.button - 1; + events::Mouse::broadcast(mouse); + break; + } + case SDL_MOUSEBUTTONUP: { + const SDL_MouseButtonEvent &event = sdlEvent.button; + mouse.type = MouseEvent::Type::UP; + mouse.x = static_cast(event.x); + mouse.y = static_cast(event.y); + mouse.button = event.button - 1; + events::Mouse::broadcast(mouse); + break; + } + case SDL_MOUSEMOTION: { + const SDL_MouseMotionEvent &event = sdlEvent.motion; + mouse.type = MouseEvent::Type::MOVE; + mouse.button = -1; // BUTTON_MISSING + // Needs to be consistent with event-mouse.ts definition + // Multiple button presses at the same time are not supported. + // if we are pressed at the same time, the result is indeterminate. + if (event.state & SDL_BUTTON_LMASK) { + mouse.button |= 0x00; // BUTTON_LEFT + } else if (event.state & SDL_BUTTON_RMASK) { + mouse.button |= 0x02; // BUTTON_RGIHT + } else if (event.state & SDL_BUTTON_MIDDLE) { + mouse.button |= 0x01; // BUTTON_MIDDLE + } + + mouse.x = static_cast(event.x); + mouse.y = static_cast(event.y); + mouse.xDelta = static_cast(event.xrel); + mouse.yDelta = static_cast(event.yrel); + events::Mouse::broadcast(mouse); + break; + } + case SDL_MOUSEWHEEL: { + const SDL_MouseWheelEvent &event = sdlEvent.wheel; + mouse.type = MouseEvent::Type::WHEEL; + mouse.x = static_cast(event.x); + mouse.y = static_cast(event.y); + mouse.button = 0; //TODO: direction + events::Mouse::broadcast(mouse); + break; + } + case SDL_FINGERUP: { + const SDL_TouchFingerEvent &event = sdlEvent.tfinger; + touch.type = TouchEvent::Type::ENDED; + touch.touches = {TouchInfo(event.x, event.y, (int)event.fingerId)}; + events::Touch::broadcast(touch); + break; + } + case SDL_FINGERDOWN: { + const SDL_TouchFingerEvent &event = sdlEvent.tfinger; + touch.type = TouchEvent::Type::BEGAN; + touch.touches = {TouchInfo(event.x, event.y, (int)event.fingerId)}; + events::Touch::broadcast(touch); + break; + } + case SDL_FINGERMOTION: { + const SDL_TouchFingerEvent &event = sdlEvent.tfinger; + touch.type = TouchEvent::Type::MOVED; + touch.touches = {TouchInfo(event.x, event.y, (int)event.fingerId)}; + events::Touch::broadcast(touch); + break; + } + case SDL_KEYDOWN: { + const SDL_KeyboardEvent &event = sdlEvent.key; + auto mode = event.keysym.mod; + keyboard.action = KeyboardEvent::Action::PRESS; + keyboard.key = sdlKeycodeToCocosCode(event.keysym.sym, mode); + keyboard.altKeyActive = mode & KMOD_ALT; + keyboard.ctrlKeyActive = mode & KMOD_CTRL; + keyboard.shiftKeyActive = mode & KMOD_SHIFT; + //CC_LOG_DEBUG("==> key %d -> code %d", event.keysym.sym, keyboard.key); + events::Keyboard::broadcast(keyboard); + break; + } + case SDL_KEYUP: { + const SDL_KeyboardEvent &event = sdlEvent.key; + auto mode = event.keysym.mod; + keyboard.action = KeyboardEvent::Action::RELEASE; + keyboard.key = sdlKeycodeToCocosCode(event.keysym.sym, mode); + keyboard.altKeyActive = mode & KMOD_ALT; + keyboard.ctrlKeyActive = mode & KMOD_CTRL; + keyboard.shiftKeyActive = mode & KMOD_SHIFT; + events::Keyboard::broadcast(keyboard); + break; + break; + } + default: + break; + } +} + +SDL_Window *SDLHelper::createWindow(const char *title, + int w, int h, int flags) { + SDL_Rect screenRect; + if (SDL_GetDisplayUsableBounds(0, &screenRect) != 0) { + return nullptr; + } + int x = screenRect.x; + int y = screenRect.y + screenRect.h - h; + return createWindow(title, x, y, w, h, flags); +} + +SDL_Window *SDLHelper::createWindow(const char *title, + int x, int y, int w, + int h, int flags) { + // Create window + int sdlFlags = windowFlagsToSDLWindowFlag(flags); + SDL_Window *handle = SDL_CreateWindow(title, x, y, w, h, sdlFlags); + if (handle == nullptr) { + // Display error message + CC_LOG_ERROR("Window could not be created! SDL_Error: %s\n", SDL_GetError()); + return nullptr; + } + + return handle; +} + +void SDLHelper::setCursorEnabled(bool value) { + SDL_SetRelativeMouseMode(value ? SDL_FALSE : SDL_TRUE); + events::PointerLock::broadcast(!value); +} + +#if (CC_PLATFORM == CC_PLATFORM_LINUX) +uintptr_t SDLHelper::getDisplay(SDL_Window *window) { + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); + return reinterpret_cast(wmInfo.info.x11.display); +} +#endif + +uintptr_t SDLHelper::getWindowHandle(SDL_Window *window) { + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); + +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + return reinterpret_cast(wmInfo.info.win.window); +#elif (CC_PLATFORM == CC_PLATFORM_LINUX) + return reinterpret_cast(wmInfo.info.x11.window); +#elif (CC_PLATFORM == CC_PLATFORM_MACOS) + return reinterpret_cast(wmInfo.info.cocoa.window); +#endif + CC_ABORT(); + return 0; +} + +Vec2 SDLHelper::getWindowPosition(SDL_Window *window) { + int x = 0; + int y = 0; + SDL_GetWindowPosition(window, &x, &y); + return Vec2(x, y); +} + +void SDLHelper::stopTextInput() { + SDL_StopTextInput(); +} + +bool SDLHelper::isWindowMinimized(SDL_Window *window) { + return SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED; +} + +} // namespace cc diff --git a/cocos/platform/SDLHelper.h b/cocos/platform/SDLHelper.h new file mode 100644 index 0000000..c51b133 --- /dev/null +++ b/cocos/platform/SDLHelper.h @@ -0,0 +1,64 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include +#include "engine/EngineEvents.h" +#include "math/Vec2.h" + +struct SDL_Window; +union SDL_Event; +struct SDL_WindowEvent; + +namespace cc { + +class SDLHelper { + friend class SystemWindowManager; + +public: + SDLHelper(); + ~SDLHelper(); + + static int init(); + + static SDL_Window* createWindow(const char* title, + int w, int h, int flags); + static SDL_Window* createWindow(const char* title, + int x, int y, int w, + int h, int flags); + + static uintptr_t getWindowHandle(SDL_Window* window); +#if (CC_PLATFORM == CC_PLATFORM_LINUX) + static uintptr_t getDisplay(SDL_Window* window); +#endif + static void setCursorEnabled(bool value); + static Vec2 getWindowPosition(SDL_Window* window); + static void stopTextInput(); + static bool isWindowMinimized(SDL_Window* window); + +private: + static void dispatchSDLEvent(uint32_t windowId, const SDL_Event& sdlEvent); + static void dispatchWindowEvent(uint32_t windowId, const SDL_WindowEvent& wevent); +}; +} // namespace cc diff --git a/cocos/platform/StdC.h b/cocos/platform/StdC.h new file mode 100644 index 0000000..967a143 --- /dev/null +++ b/cocos/platform/StdC.h @@ -0,0 +1,82 @@ +/**************************************************************************** + Copyright (c) 2020 cocos.com Xiamen Yaji Software Co., Ltd. + Copyright (c) undefined-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + + #define WIN32_LEAN_AND_MEAN + #include + + #if _MSC_VER < 1800 + #if !defined(isnan) + #define isnan _isnan + #endif + #endif + + #include + #include + #include + #include + #include + #include + + #if _MSC_VER >= 1600 + #include + #else + #include "platform/win32/compat/stdint.h" + #endif + + // Conflicted with ParticleSystem::PositionType::RELATIVE, so we need to undef it. + #ifdef RELATIVE + #undef RELATIVE + #endif + + // Conflicted with CCBReader::SizeType::RELATIVE and CCBReader::ScaleType::RELATIVE, so we need to undef it. + #ifdef ABSOLUTE + #undef ABSOLUTE + #endif + + // Conflicted with HttpRequest::Type::DELETE, so we need to undef it. + #ifdef DELETE + #undef DELETE + #endif + + #undef min + #undef max +#else + + #include + #include + #include + #include + #include + #include + #include + #include + #include + +#endif // CC_PLATFORM == CC_PLATFORM_WINDOWS diff --git a/cocos/platform/UniversalPlatform.cpp b/cocos/platform/UniversalPlatform.cpp new file mode 100644 index 0000000..36b149f --- /dev/null +++ b/cocos/platform/UniversalPlatform.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/UniversalPlatform.h" + +#include "platform/interfaces/OSInterface.h" + +extern int cocos_main(int argc, const char** argv); // NOLINT(readability-identifier-naming) +extern void cocos_destory(); // NOLINT(readability-identifier-naming) + +namespace cc { +UniversalPlatform::OSType UniversalPlatform::getOSType() const { + return getInterface()->getOSType(); +} + +int32_t UniversalPlatform::run(int argc, const char** argv) { + if (cocos_main(argc, argv) != 0) { + return -1; + } + return loop(); +} + +int UniversalPlatform::getSdkVersion() const { + return 0; +} + +void UniversalPlatform::runInPlatformThread(const ThreadCallback& task) { + _mainTask = task; +} + +void UniversalPlatform::runTask() { + if (_mainTask) { + _mainTask(); + } +} + +int32_t UniversalPlatform::getFps() const { + return _fps; +} + +void UniversalPlatform::setFps(int32_t fps) { + _fps = fps; +} + +void UniversalPlatform::pollEvent() { +} + +void UniversalPlatform::onPause() { +} + +void UniversalPlatform::onResume() { +} + +void UniversalPlatform::onClose() { +} + +void UniversalPlatform::onDestroy() { + cocos_destory(); +} + +void UniversalPlatform::exit() { + +} + +} // namespace cc diff --git a/cocos/platform/UniversalPlatform.h b/cocos/platform/UniversalPlatform.h new file mode 100644 index 0000000..0b6762c --- /dev/null +++ b/cocos/platform/UniversalPlatform.h @@ -0,0 +1,94 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/BasePlatform.h" + +namespace cc { +class CC_DLL UniversalPlatform : public BasePlatform { +public: + /** + * @brief Start base platform initialization. + */ + int32_t run(int argc, const char **argv) override; + + /** + * @brief Get targe platform type. + */ + OSType getOSType() const override; + /** + * @brief Get the SDK version for Android.Other systems also have sdk versions, + but they are not currently used. + */ + int getSdkVersion() const override; + /** + * @brief Polling event + */ + void pollEvent() override; + /** + * @brief Run the task in the platform thread, + * @brief most platforms are the main thread, android is the non-main thread + * @param task : Tasks running in platform threads + */ + void runInPlatformThread(const ThreadCallback &task) override; + /** + * @brief Get task call frequency. + */ + int32_t getFps() const override; + /** + * @brief Set task call frequency. + */ + void setFps(int32_t fps) override; + + virtual void runTask(); + /** + * @brief Processing pause message + */ + virtual void onPause(); + /** + * @brief Processing resume message + */ + virtual void onResume(); + /** + * @brief Processing close message + */ + virtual void onClose(); + /** + * @brief Processing destroy message + */ + virtual void onDestroy(); + + /** + * @brief Exit platform. + */ + void exit() override; + +private: + ThreadCallback _mainTask{nullptr}; + + int32_t _fps{60}; +}; + +} // namespace cc diff --git a/cocos/platform/android/AndroidKeyCodes.cpp b/cocos/platform/android/AndroidKeyCodes.cpp new file mode 100644 index 0000000..293105f --- /dev/null +++ b/cocos/platform/android/AndroidKeyCodes.cpp @@ -0,0 +1,133 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include +#include +#include +#include "base/std/container/unordered_map.h" + +namespace cc { +ccstd::unordered_map androidKeyCodes = { + {AKEYCODE_BACK, "Backspace"}, + {AKEYCODE_TAB, "Tab"}, + {AKEYCODE_ENTER, "Enter"}, + {AKEYCODE_SHIFT_LEFT, "ShiftLeft"}, + {AKEYCODE_CTRL_LEFT, "ControlLeft"}, + {AKEYCODE_ALT_LEFT, "AltLeft"}, + {AKEYCODE_SHIFT_RIGHT, "ShiftRight"}, + {AKEYCODE_CTRL_RIGHT, "ControlRight"}, + {AKEYCODE_ALT_RIGHT, "AltRight"}, + {AKEYCODE_MEDIA_PLAY_PAUSE, "Pause"}, + {AKEYCODE_CAPS_LOCK, "CapsLock"}, + {AKEYCODE_ESCAPE, "Escape"}, + {AKEYCODE_SPACE, "Space"}, + {AKEYCODE_PAGE_UP, "PageUp"}, + {AKEYCODE_PAGE_DOWN, "PageDown"}, + {AKEYCODE_MOVE_END, "End"}, + {AKEYCODE_MOVE_HOME, "Home"}, + {AKEYCODE_DPAD_LEFT, "ArrowLeft"}, + {AKEYCODE_DPAD_UP, "ArrowUp"}, + {AKEYCODE_DPAD_RIGHT, "ArrowRight"}, + {AKEYCODE_DPAD_DOWN, "ArrowDown"}, + {AKEYCODE_INSERT, "Insert"}, + {AKEYCODE_DEL, "Delete"}, + {AKEYCODE_0, "Digit0"}, + {AKEYCODE_1, "Digit1"}, + {AKEYCODE_2, "Digit2"}, + {AKEYCODE_3, "Digit3"}, + {AKEYCODE_4, "Digit4"}, + {AKEYCODE_5, "Digit5"}, + {AKEYCODE_6, "Digit6"}, + {AKEYCODE_7, "Digit7"}, + {AKEYCODE_8, "Digit8"}, + {AKEYCODE_9, "Digit9"}, + {AKEYCODE_A, "KeyA"}, + {AKEYCODE_B, "KeyB"}, + {AKEYCODE_C, "KeyC"}, + {AKEYCODE_D, "KeyD"}, + {AKEYCODE_E, "KeyE"}, + {AKEYCODE_F, "KeyF"}, + {AKEYCODE_G, "KeyG"}, + {AKEYCODE_H, "KeyH"}, + {AKEYCODE_I, "KeyI"}, + {AKEYCODE_J, "KeyJ"}, + {AKEYCODE_K, "KeyK"}, + {AKEYCODE_L, "KeyL"}, + {AKEYCODE_M, "KeyM"}, + {AKEYCODE_N, "KeyN"}, + {AKEYCODE_O, "KeyO"}, + {AKEYCODE_P, "KeyP"}, + {AKEYCODE_Q, "KeyQ"}, + {AKEYCODE_R, "KeyR"}, + {AKEYCODE_S, "KeyS"}, + {AKEYCODE_T, "KeyT"}, + {AKEYCODE_U, "KeyU"}, + {AKEYCODE_V, "KeyV"}, + {AKEYCODE_W, "KeyW"}, + {AKEYCODE_X, "KeyX"}, + {AKEYCODE_Y, "KeyY"}, + {AKEYCODE_Z, "KeyZ"}, + {AKEYCODE_NUMPAD_0, "Numpad0"}, + {AKEYCODE_NUMPAD_1, "Numpad1"}, + {AKEYCODE_NUMPAD_2, "Numpad2"}, + {AKEYCODE_NUMPAD_3, "Numpad3"}, + {AKEYCODE_NUMPAD_4, "Numpad4"}, + {AKEYCODE_NUMPAD_5, "Numpad5"}, + {AKEYCODE_NUMPAD_6, "Numpad6"}, + {AKEYCODE_NUMPAD_7, "Numpad7"}, + {AKEYCODE_NUMPAD_8, "Numpad8"}, + {AKEYCODE_NUMPAD_9, "Numpad9"}, + {AKEYCODE_NUMPAD_MULTIPLY, "NumpadMultiply"}, + {AKEYCODE_NUMPAD_ADD, "NumpadAdd"}, + {AKEYCODE_NUMPAD_SUBTRACT, "NumpadSubtract"}, + {AKEYCODE_NUMPAD_DOT, "NumpadDecimal"}, + {AKEYCODE_NUMPAD_DIVIDE, "NumpadDivide"}, + {AKEYCODE_NUMPAD_ENTER, "NumpadEnter"}, + {AKEYCODE_F1, "F1"}, + {AKEYCODE_F2, "F2"}, + {AKEYCODE_F3, "F3"}, + {AKEYCODE_F4, "F4"}, + {AKEYCODE_F5, "F5"}, + {AKEYCODE_F6, "F6"}, + {AKEYCODE_F7, "F7"}, + {AKEYCODE_F8, "F8"}, + {AKEYCODE_F9, "F9"}, + {AKEYCODE_F10, "F10"}, + {AKEYCODE_F11, "F11"}, + {AKEYCODE_F12, "F12"}, + {AKEYCODE_NUM_LOCK, "NumLock"}, + {AKEYCODE_SCROLL_LOCK, "ScrollLock"}, + {AKEYCODE_SEMICOLON, "Semicolon"}, + {AKEYCODE_EQUALS, "Equal"}, + {AKEYCODE_COMMA, "Comma"}, + {AKEYCODE_MINUS, "Minus"}, + {AKEYCODE_PERIOD, "Period"}, + {AKEYCODE_SLASH, "Slash"}, + {AKEYCODE_GRAVE, "Backquote"}, + {AKEYCODE_LEFT_BRACKET, "BracketLeft"}, + {AKEYCODE_BACKSLASH, "Backslash"}, + {AKEYCODE_RIGHT_BRACKET, "BracketRight"}, + {AKEYCODE_APOSTROPHE, "Quote"}, +}; +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/android/AndroidPlatform.cpp b/cocos/platform/android/AndroidPlatform.cpp new file mode 100644 index 0000000..8c2733e --- /dev/null +++ b/cocos/platform/android/AndroidPlatform.cpp @@ -0,0 +1,884 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/android/AndroidPlatform.h" +#include +#include +#include "application/ApplicationManager.h" +#include "base/Log.h" +#include "base/memory/Memory.h" +#include "base/std/container/unordered_map.h" +#include "game-activity/native_app_glue/android_native_app_glue.h" +#include "java/jni/JniHelper.h" +#include "modules/Screen.h" +#include "modules/System.h" +#include "platform/BasePlatform.h" +#include "platform/android/FileUtils-android.h" +#include "platform/java/jni/JniImp.h" +#include "platform/java/modules/Accelerometer.h" +#include "platform/java/modules/Battery.h" +#include "platform/java/modules/Network.h" +#include "platform/java/modules/SystemWindow.h" +#include "platform/java/modules/SystemWindowManager.h" +#include "platform/java/modules/Vibrator.h" + +#include "platform/interfaces/modules/IXRInterface.h" +#if CC_USE_XR +#include "platform/java/modules/XRInterface.h" +#endif + +#include "base/StringUtil.h" +#include "engine/EngineEvents.h" +#include "paddleboat.h" + +#define ABORT_GAME \ + { \ + CC_LOG_ERROR("*** GAME ABORTING."); \ + *((volatile char *)0) = 'a'; \ + } + +#define ABORT_IF(cond) \ + { \ + if (!(cond)) { \ + CC_LOG_ERROR("ASSERTION FAILED: %s", #cond); \ + ABORT_GAME; \ + } \ + } + +#define INPUT_ACTION_COUNT 6 + +// Interval time per frame, in milliseconds +#define LOW_FREQUENCY_TIME_INTERVAL 50 + +// Maximum runtime of game threads while in the background, in seconds +#define LOW_FREQUENCY_EXPIRED_DURATION_SECONDS 60 + +#define CC_ENABLE_SUSPEND_GAME_THREAD true + +extern int cocos_main(int argc, const char **argv); // NOLINT(readability-identifier-naming) + +namespace cc { + +struct cc::KeyboardEvent keyboardEvent; + +struct InputAction { + uint32_t buttonMask{0}; + int32_t actionCode{-1}; +}; + +extern ccstd::unordered_map androidKeyCodes; + +static const InputAction PADDLEBOAT_ACTIONS[INPUT_ACTION_COUNT] = { + {PADDLEBOAT_BUTTON_DPAD_UP, static_cast(KeyCode::DPAD_UP)}, + {PADDLEBOAT_BUTTON_DPAD_LEFT, static_cast(KeyCode::DPAD_LEFT)}, + {PADDLEBOAT_BUTTON_DPAD_DOWN, static_cast(KeyCode::DPAD_DOWN)}, + {PADDLEBOAT_BUTTON_DPAD_RIGHT, static_cast(KeyCode::DPAD_RIGHT)}, +}; + +struct ControllerKeyRemap { + Paddleboat_Buttons buttonMask; + StickKeyCode actionCode{StickKeyCode::UNDEFINE}; + const char *name; +}; + +#define REMAP_WITH_NAME(btn, key) \ + { btn, key, #btn } + +static const ControllerKeyRemap PADDLEBOAT_MAPKEY[] = { + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_A, StickKeyCode::A), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_B, StickKeyCode::B), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_X, StickKeyCode::X), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_Y, StickKeyCode::Y), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_L1, StickKeyCode::L1), + // REMAP_WITH_NAME(PADDLEBOAT_BUTTON_L2, StickKeyCode::TRIGGER_LEFT), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_L3, StickKeyCode::L3), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_R1, StickKeyCode::R1), + // REMAP_WITH_NAME(PADDLEBOAT_BUTTON_R2, StickKeyCode::TRIGGER_RIGHT), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_R3, StickKeyCode::R3), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_SELECT, StickKeyCode::MINUS), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_START, StickKeyCode::PLUS), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_SYSTEM, StickKeyCode::MENU), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_TOUCHPAD, StickKeyCode::UNDEFINE), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_AUX1, StickKeyCode::UNDEFINE), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_AUX2, StickKeyCode::UNDEFINE), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_AUX3, StickKeyCode::UNDEFINE), + REMAP_WITH_NAME(PADDLEBOAT_BUTTON_AUX4, StickKeyCode::UNDEFINE), +}; +#undef REMAP_WITH_NAME + +const int INPUT_ACTION_REMAP_COUNT = sizeof(PADDLEBOAT_MAPKEY) / sizeof(ControllerKeyRemap); + +static const InputAction INPUT_KEY_ACTIONS[] = { + {AKEYCODE_BACK, static_cast(KeyCode::MOBILE_BACK)}, + {AKEYCODE_ENTER, static_cast(KeyCode::ENTER)}, + {AKEYCODE_MENU, static_cast(KeyCode::ALT_LEFT)}, + {AKEYCODE_DPAD_UP, static_cast(KeyCode::DPAD_UP)}, + {AKEYCODE_DPAD_DOWN, static_cast(KeyCode::DPAD_DOWN)}, + {AKEYCODE_DPAD_LEFT, static_cast(KeyCode::DPAD_LEFT)}, + {AKEYCODE_DPAD_RIGHT, static_cast(KeyCode::DPAD_RIGHT)}, + {AKEYCODE_DPAD_CENTER, static_cast(KeyCode::DPAD_CENTER)}, +}; + +static bool keyState[INPUT_ACTION_COUNT] = {false}; + +extern void gameControllerStatusCallback(int32_t controllerIndex, + Paddleboat_ControllerStatus status, + void *userData); + +class GameInputProxy { +public: + explicit GameInputProxy(AndroidPlatform *platform) { + _androidPlatform = platform; + if (0 != platform->_app->activity->vm->AttachCurrentThread(&_jniEnv, nullptr)) { + CC_LOG_FATAL("*** FATAL ERROR: Failed to attach thread to JNI."); + ABORT_GAME + } + ABORT_IF(_jniEnv != nullptr) + + Paddleboat_init(_jniEnv, platform->_app->activity->javaGameActivity); + Paddleboat_setControllerStatusCallback(gameControllerStatusCallback, this); + // This is needed to allow controller events through to us. + // By default, only touch-screen events are passed through, to match the + // behaviour of NativeActivity. + android_app_set_motion_event_filter(platform->_app, nullptr); + + // Flags to control how the IME behaves. + static constexpr int INPUTTYPE_DOT_TYPE_CLASS_TEXT = 1; + static constexpr int IME_ACTION_NONE = 1; + static constexpr int IME_FLAG_NO_FULLSCREEN = 33554432; + + GameActivity_setImeEditorInfo(platform->_app->activity, INPUTTYPE_DOT_TYPE_CLASS_TEXT, + IME_ACTION_NONE, IME_FLAG_NO_FULLSCREEN); + } + + ~GameInputProxy() { + Paddleboat_setControllerStatusCallback(nullptr, nullptr); + Paddleboat_destroy(_jniEnv); + if (_jniEnv) { + CC_LOG_INFO("Detaching current thread from JNI."); + _androidPlatform->_app->activity->vm->DetachCurrentThread(); + CC_LOG_INFO("Current thread detached from JNI."); + _jniEnv = nullptr; + } + } + void checkForNewAxis() { + // Tell GameActivity about any new axis ids so it reports + // their events + const uint64_t activeAxisIds = Paddleboat_getActiveAxisMask(); + uint64_t newAxisIds = activeAxisIds ^ _activeAxisIds; + if (newAxisIds != 0) { + _activeAxisIds = activeAxisIds; + int32_t currentAxisId = 0; + while (newAxisIds != 0) { + if ((newAxisIds & 1) != 0) { + CC_LOG_INFO("Enable Axis: %d", currentAxisId); + GameActivityPointerAxes_enableAxis(currentAxisId); + } + ++currentAxisId; + newAxisIds >>= 1; + } + } + } + + void handleInput() { + checkForNewAxis(); + Paddleboat_update(_jniEnv); + // If we get any key or motion events that were handled by a game controller, + // read controller data and cook it into an event + bool controllerEvent = false; + + // Swap input buffers so we don't miss any events while processing inputBuffer. + android_input_buffer *inputBuffer = android_app_swap_input_buffers( + _androidPlatform->_app); + // Early exit if no events. + if (inputBuffer == nullptr) return; + + if (inputBuffer->keyEventsCount != 0) { + for (uint64_t i = 0; i < inputBuffer->keyEventsCount; ++i) { + GameActivityKeyEvent *keyEvent = &inputBuffer->keyEvents[i]; + if (_gameControllerIndex >= 0 && Paddleboat_processGameActivityKeyInputEvent(keyEvent, + sizeof(GameActivityKeyEvent))) { + controllerEvent = true; + } else { + cookGameActivityKeyEvent(keyEvent); + } + } + android_app_clear_key_events(inputBuffer); + } + if (inputBuffer->motionEventsCount != 0) { + for (uint64_t i = 0; i < inputBuffer->motionEventsCount; ++i) { + GameActivityMotionEvent *motionEvent = &inputBuffer->motionEvents[i]; + + if (_gameControllerIndex >= 0 && Paddleboat_processGameActivityMotionInputEvent(motionEvent, + sizeof(GameActivityMotionEvent))) { + controllerEvent = true; + } else { + // Didn't belong to a game controller, process it ourselves if it is a touch event + bool isMouseEvent = motionEvent->pointerCount > 0 && (motionEvent->pointers[0].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || motionEvent->pointers[0].toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE); + if (isMouseEvent) { + cookGameActivityMouseEvent(motionEvent); + } else { + cookGameActivityMotionEvent(motionEvent); + } + } + } + android_app_clear_motion_events(inputBuffer); + } + + if (controllerEvent) { + cookGameControllerEvent(_gameControllerIndex); + } + } + + struct ButtonState { + uint32_t buttonsDown; + uint32_t &prevState; +#define DEF_ATTR(name, code) \ + bool name##Pressed() const { return buttonsDown & PADDLEBOAT_BUTTON_##code; } \ + bool name##Rel() const { return !name##Pressed() && (prevState & PADDLEBOAT_BUTTON_##code); } \ + bool name() const { return name##Pressed() || name##Rel(); } + + DEF_ATTR(dpadLeft, DPAD_LEFT) + DEF_ATTR(dpadRight, DPAD_RIGHT) + DEF_ATTR(dpadUp, DPAD_UP) + DEF_ATTR(dpadDown, DPAD_DOWN) + + DEF_ATTR(l2, L2) + DEF_ATTR(r2, R2) + +#undef DEF_ATTR + }; + + bool cookGameControllerEvent(const int32_t gameControllerIndex) { + static std::vector prevStates{4}; + bool addedControllerEvent = false; + cc::ControllerInfo info; + if (gameControllerIndex >= 0) { + if (gameControllerIndex >= prevStates.size()) { + prevStates.resize(gameControllerIndex * 2 + 1); + } + uint32_t &prevButtonsDown = prevStates[gameControllerIndex]; + + info.napdId = gameControllerIndex; + Paddleboat_Controller_Data controllerData; + if (Paddleboat_getControllerData(gameControllerIndex, &controllerData) == + PADDLEBOAT_NO_ERROR) { + addedControllerEvent = true; + // Generate events from buttons + for (auto inputAction : PADDLEBOAT_ACTIONS) { + if (controllerData.buttonsDown & inputAction.buttonMask) { + reportKeyState(inputAction.actionCode, true); + } else if (prevButtonsDown & inputAction.buttonMask) { + reportKeyState(inputAction.actionCode, false); + } + } + for (auto remap : PADDLEBOAT_MAPKEY) { + auto code = remap.actionCode; + if (controllerData.buttonsDown & remap.buttonMask) { + if (code == StickKeyCode::UNDEFINE) { + CC_LOG_ERROR("key \"%s\" is unhandled", remap.name); + } + cc::ControllerInfo::ButtonInfo buttonInfo{code, true}; + info.buttonInfos.emplace_back(buttonInfo); + } else if (prevButtonsDown & remap.buttonMask) { + cc::ControllerInfo::ButtonInfo buttonInfo{code, false}; + buttonInfo.key = code; + buttonInfo.isPress = false; + info.buttonInfos.emplace_back(buttonInfo); + } + } + const ButtonState bts{controllerData.buttonsDown, prevButtonsDown}; + if (bts.dpadLeft() || bts.dpadRight()) { + float dLeft = bts.dpadLeftRel() ? 0.0F : (bts.dpadLeftPressed() ? -1.0F : 0.0F); + float dRight = bts.dpadRightRel() ? 0.0F : (bts.dpadRightPressed() ? 1.0F : 0.0F); + const ControllerInfo::AxisInfo axisInfo(StickAxisCode::X, dLeft + dRight); + info.axisInfos.emplace_back(axisInfo); + } + if (bts.dpadUp() || bts.dpadDown()) { + float dUp = bts.dpadUpRel() ? 0.0F : (bts.dpadUp() ? 1.0F : 0.0F); + float dDown = bts.dpadDownRel() ? 0.0F : (bts.dpadDown() ? -1.0F : 0.0F); + const ControllerInfo::AxisInfo axisInfo(StickAxisCode::Y, dUp + dDown); + info.axisInfos.emplace_back(axisInfo); + } + if (bts.l2()) { + const ControllerInfo::AxisInfo axisInfo(StickAxisCode::L2, bts.l2Rel() ? 0.0F : (bts.l2Pressed() ? controllerData.triggerL2 : 0.0F)); + info.axisInfos.emplace_back(axisInfo); + } + if (bts.r2()) { + const ControllerInfo::AxisInfo axisInfo(StickAxisCode::R2, bts.r2Rel() ? 0.0F : (bts.r2Pressed() ? controllerData.triggerR2 : 0.0F)); + info.axisInfos.emplace_back(axisInfo); + } + + auto lx = controllerData.leftStick.stickX; + auto ly = -controllerData.leftStick.stickY; + auto rx = controllerData.rightStick.stickX; + auto ry = -controllerData.rightStick.stickY; + + info.axisInfos.emplace_back(StickAxisCode::LEFT_STICK_X, lx); + info.axisInfos.emplace_back(StickAxisCode::LEFT_STICK_Y, ly); + info.axisInfos.emplace_back(StickAxisCode::RIGHT_STICK_X, rx); + info.axisInfos.emplace_back(StickAxisCode::RIGHT_STICK_Y, ry); + + ControllerEvent controllerEvent; + controllerEvent.type = ControllerEvent::Type::GAMEPAD; + controllerEvent.controllerInfos.emplace_back(std::make_unique(std::move(info))); + events::Controller::broadcast(controllerEvent); + + // Update our prev variable so we can det + // ect delta changes from down to up + prevButtonsDown = controllerData.buttonsDown; + } + } + return addedControllerEvent; + } + + // NOLINTNEXTLINE + bool cookGameActivityMouseEvent(GameActivityMotionEvent *motionEvent) { + cc::MouseEvent mouseEvent; + if (motionEvent->pointerCount > 0) { + mouseEvent.windowId = ISystemWindow::mainWindowId; // must be main window here + + int action = motionEvent->action; + int actionMasked = action & AMOTION_EVENT_ACTION_MASK; + int button = motionEvent->buttonState; + + setMousePosition(mouseEvent, motionEvent); + switch (button) { + case AMOTION_EVENT_BUTTON_PRIMARY: + mouseEvent.button = 0; + break; + case AMOTION_EVENT_BUTTON_SECONDARY: + mouseEvent.button = 2; + break; + case AMOTION_EVENT_BUTTON_TERTIARY: + mouseEvent.button = 1; + break; + case AMOTION_EVENT_BUTTON_BACK: + mouseEvent.button = 3; + break; + case AMOTION_EVENT_BUTTON_FORWARD: + mouseEvent.button = 4; + break; + default: + mouseEvent.button = 0; + } + + if (actionMasked == AMOTION_EVENT_ACTION_DOWN || + actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) { + mouseEvent.type = cc::MouseEvent::Type::DOWN; + } else if (actionMasked == AMOTION_EVENT_ACTION_UP || + actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) { + mouseEvent.type = cc::MouseEvent::Type::UP; + } else if (actionMasked == AMOTION_EVENT_ACTION_SCROLL) { + mouseEvent.type = cc::MouseEvent::Type::WHEEL; + // TODO(): wheel delta + } else if (actionMasked == AMOTION_EVENT_ACTION_MOVE) { + mouseEvent.type = cc::MouseEvent::Type::MOVE; + } else if (actionMasked == AMOTION_EVENT_ACTION_HOVER_MOVE) { + mouseEvent.type = cc::MouseEvent::Type::MOVE; + } else if (actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) { + return false; + } else if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT) { + return false; + } else { + return false; + } + + events::Mouse::broadcast(mouseEvent); + return true; + } + return false; + } + + // NOLINTNEXTLINE + bool cookGameActivityMotionEvent(GameActivityMotionEvent *motionEvent) { + cc::TouchEvent touchEvent; + if (motionEvent->pointerCount > 0) { + touchEvent.windowId = ISystemWindow::mainWindowId; // must be main window here + + int action = motionEvent->action; + int actionMasked = action & AMOTION_EVENT_ACTION_MASK; + int eventChangedIndex = -1; + + bool isMouseEvent = motionEvent->pointerCount > 0 && (motionEvent->pointers[0].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || motionEvent->pointers[0].toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE); + + if (actionMasked == AMOTION_EVENT_ACTION_DOWN || + actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) { + if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) { + eventChangedIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) + >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } else { + eventChangedIndex = 0; + } + touchEvent.type = cc::TouchEvent::Type::BEGAN; + } else if (actionMasked == AMOTION_EVENT_ACTION_UP || + actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) { + touchEvent.type = cc::TouchEvent::Type::ENDED; + if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) { + eventChangedIndex = ((action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) + >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } else { + eventChangedIndex = 0; + } + } else if (actionMasked == AMOTION_EVENT_ACTION_CANCEL) { + touchEvent.type = cc::TouchEvent::Type::CANCELLED; + } else if (actionMasked == AMOTION_EVENT_ACTION_MOVE) { + touchEvent.type = cc::TouchEvent::Type::MOVED; + } else { + return false; + } + + bool touchHandled = true; + if (eventChangedIndex >= 0) { + touchHandled = addTouchEvent(touchEvent, eventChangedIndex, motionEvent); + } else { + for (int i = 0; i < motionEvent->pointerCount; i++) { + addTouchEvent(touchEvent, i, motionEvent); + } + } + events::Touch::broadcast(touchEvent); + touchEvent.touches.clear(); + return touchHandled; + } + return false; + } + + // NOLINTNEXTLINE + bool cookGameActivityKeyEvent(GameActivityKeyEvent *keyEvent) { + if (_gameControllerIndex >= 0) { + for (const auto &action : INPUT_KEY_ACTIONS) { + if (action.buttonMask != keyEvent->keyCode) { + continue; + } + keyboardEvent.action = 0 == keyEvent->action ? cc::KeyboardEvent::Action::PRESS + : cc::KeyboardEvent::Action::RELEASE; + keyboardEvent.key = action.actionCode; + events::Keyboard::broadcast(keyboardEvent); + return true; + } + } + keyboardEvent.action = 0 == keyEvent->action ? cc::KeyboardEvent::Action::PRESS + : cc::KeyboardEvent::Action::RELEASE; + keyboardEvent.key = keyEvent->keyCode; + auto keyCodeItr = androidKeyCodes.find(keyEvent->keyCode); + if (keyCodeItr == androidKeyCodes.end()) { + keyboardEvent.code.clear(); + } else { + keyboardEvent.code = keyCodeItr->second; + } + events::Keyboard::broadcast(keyboardEvent); + return true; + } + + // NOLINTNEXTLINE + void reportKeyState(int keyCode, bool state) { + bool wentDown = !keyState[keyCode] && state; + bool wentUp = keyState[keyCode] && !state; + keyState[keyCode] = state; + + if (wentUp) { + keyboardEvent.key = keyCode; + keyboardEvent.action = cc::KeyboardEvent::Action::RELEASE; + events::Keyboard::broadcast(keyboardEvent); + } else if (wentDown) { + keyboardEvent.key = keyCode; + keyboardEvent.action = cc::KeyboardEvent::Action::PRESS; + events::Keyboard::broadcast(keyboardEvent); + } + } + + int32_t getActiveGameControllerIndex() const { + return _gameControllerIndex; + } + + void setActiveGameControllerIndex(const int32_t controllerIndex) { + _gameControllerIndex = controllerIndex; + } + + void handleAppCommand(int32_t cmd) { + switch (cmd) { + case APP_CMD_SAVE_STATE: + // The system has asked us to save our current state. + CC_LOG_DEBUG("AndroidPlatform: APP_CMD_SAVE_STATE"); + break; + case APP_CMD_INIT_WINDOW: { + _hasWindow = true; + ANativeWindow *nativeWindow = _androidPlatform->_app->window; + + // We have a window! + CC_LOG_DEBUG("AndroidPlatform: APP_CMD_INIT_WINDOW"); + if (!_launched) { + _launched = true; + + ISystemWindowInfo info; + info.width = ANativeWindow_getWidth(nativeWindow); + info.height = ANativeWindow_getHeight(nativeWindow); + info.externalHandle = nativeWindow; + _androidPlatform->getInterface()->createWindow(info); + + if (cocos_main(0, nullptr) != 0) { + CC_LOG_ERROR("AndroidPlatform: Launch game failed!"); + } else { + IXRInterface *xr = CC_GET_XR_INTERFACE(); + if (xr) { + xr->onRenderResume(); + } + } + } else { + IXRInterface *xr = CC_GET_XR_INTERFACE(); + if (xr) { + xr->onRenderResume(); + } + + auto *windowMgr = _androidPlatform->getInterface(); + auto *window = static_cast(windowMgr->getWindow(ISystemWindow::mainWindowId)); + window->setWindowHandle(nativeWindow); + events::WindowRecreated::broadcast(ISystemWindow::mainWindowId); + } + break; + } + case APP_CMD_TERM_WINDOW: { + _hasWindow = false; + // The window is going away -- kill the surface + CC_LOG_DEBUG("AndroidPlatform: APP_CMD_TERM_WINDOW"); + IXRInterface *xr = CC_GET_XR_INTERFACE(); + if (xr) { + xr->onRenderPause(); + } + // NOLINTNEXTLINE + events::WindowDestroy::broadcast(ISystemWindow::mainWindowId); + break; + } + case APP_CMD_GAINED_FOCUS: + _isActive = true; + CC_LOG_INFO("AndroidPlatform: APP_CMD_GAINED_FOCUS"); + break; + case APP_CMD_LOST_FOCUS: + _isActive = false; + CC_LOG_INFO("AndroidPlatform: APP_CMD_LOST_FOCUS"); + break; + case APP_CMD_PAUSE: + _isActive = false; + CC_LOG_INFO("AndroidPlatform: APP_CMD_PAUSE"); + break; + case APP_CMD_RESUME: { + _isActive = true; + CC_LOG_INFO("AndroidPlatform: APP_CMD_RESUME"); + break; + } + case APP_CMD_DESTROY: { + CC_LOG_INFO("AndroidPlatform: APP_CMD_DESTROY"); + IXRInterface *xr = CC_GET_XR_INTERFACE(); + if (xr) { + xr->onRenderDestroy(); + } + WindowEvent ev; + ev.type = WindowEvent::Type::CLOSE; + events::WindowEvent::broadcast(ev); + break; + } + case APP_CMD_STOP: { + CC_LOG_INFO("AndroidPlatform: APP_CMD_STOP"); + _isVisible = false; + Paddleboat_onStop(_jniEnv); + WindowEvent ev; + ev.type = WindowEvent::Type::HIDDEN; + events::WindowEvent::broadcast(ev); + break; + } + case APP_CMD_START: { + CC_LOG_INFO("AndroidPlatform: APP_CMD_START"); + _isVisible = true; + Paddleboat_onStart(_jniEnv); + WindowEvent ev; + ev.type = WindowEvent::Type::SHOW; + events::WindowEvent::broadcast(ev); + break; + } + case APP_CMD_WINDOW_RESIZED: { + CC_LOG_INFO("AndroidPlatform: APP_CMD_WINDOW_RESIZED"); + cc::WindowEvent ev; + ev.type = cc::WindowEvent::Type::SIZE_CHANGED; + ev.width = ANativeWindow_getWidth(_androidPlatform->_app->window); + ev.height = ANativeWindow_getHeight(_androidPlatform->_app->window); + ev.windowId = ISystemWindow::mainWindowId; + events::WindowEvent::broadcast(ev); + break; + } + case APP_CMD_CONFIG_CHANGED: + CC_LOG_INFO("AndroidPlatform: APP_CMD_CONFIG_CHANGED"); + // Window was resized or some other configuration changed. + // Note: we don't handle this event because we check the surface dimensions + // every frame, so that's how we know it was resized. If you are NOT doing that, + // then you need to handle this event! + break; + case APP_CMD_LOW_MEMORY: { + // system told us we have low memory. So if we are not visible, let's + // cooperate by deallocating all of our graphic resources. + CC_LOG_INFO("AndroidPlatform: APP_CMD_LOW_MEMORY"); + events::LowMemory::broadcast(); + break; + } + case APP_CMD_CONTENT_RECT_CHANGED: + CC_LOG_DEBUG("AndroidPlatform: APP_CMD_CONTENT_RECT_CHANGED"); + break; + case APP_CMD_WINDOW_REDRAW_NEEDED: + CC_LOG_INFO("AndroidPlatform: APP_CMD_WINDOW_REDRAW_NEEDED"); + break; + case APP_CMD_WINDOW_INSETS_CHANGED: + CC_LOG_DEBUG("AndroidPlatform: APP_CMD_WINDOW_INSETS_CHANGED"); + // ARect insets; + // // Log all the insets types + // for (int type = 0; type < GAMECOMMON_INSETS_TYPE_COUNT; ++type) { + // GameActivity_getWindowInsets(_app->activity, (GameCommonInsetsType)type, &insets); + // } + break; + default: + CC_LOG_INFO("AndroidPlatform: (unknown command)."); + break; + } + if (_eventCallback) { + _eventCallback(cmd); + } + } + + using AppEventCallback = std::function; + void registerAppEventCallback(AppEventCallback callback) { + _eventCallback = std::move(callback); + } + + inline bool isAnimating() const { + return _isVisible && _hasWindow; + } + + inline bool isActive() const { + return _isActive; + } + +private: + static bool addTouchEvent(cc::TouchEvent &touchEvent, int index, GameActivityMotionEvent *motionEvent) { + if (index < 0 || index >= motionEvent->pointerCount) { + return false; + } + int id = motionEvent->pointers[index].id; + float x = GameActivityPointerAxes_getX(&motionEvent->pointers[index]); + float y = GameActivityPointerAxes_getY(&motionEvent->pointers[index]); + touchEvent.touches.emplace_back(x, y, id); + return true; + } + + static void setMousePosition(cc::MouseEvent &mouseEvent, GameActivityMotionEvent *motionEvent) { + if (motionEvent->pointerCount == 0) { + ABORT_IF(false); + } + mouseEvent.x = GameActivityPointerAxes_getX(&motionEvent->pointers[0]); + mouseEvent.y = GameActivityPointerAxes_getY(&motionEvent->pointers[0]); + } + + AppEventCallback _eventCallback{nullptr}; + AndroidPlatform *_androidPlatform{nullptr}; + JNIEnv *_jniEnv{nullptr}; // JNI environment + uint64_t _activeAxisIds{0}; + int32_t _gameControllerIndex{-1}; // Most recently connected game controller index + bool _launched{false}; + bool _isVisible{false}; + bool _hasWindow{false}; + bool _isActive{false}; +}; + +static void handleCmdProxy(struct android_app *app, int32_t cmd) { + auto *proxy = static_cast(app->userData); + proxy->handleAppCommand(cmd); +} + +void gameControllerStatusCallback(const int32_t controllerIndex, + const Paddleboat_ControllerStatus status, + void *userData) { + auto *inputProxy = static_cast(userData); + if (inputProxy) { + // Always make the most recently connected controller the active one + if (status == PADDLEBOAT_CONTROLLER_JUST_CONNECTED) { + inputProxy->setActiveGameControllerIndex(controllerIndex); + } else if (status == PADDLEBOAT_CONTROLLER_JUST_DISCONNECTED) { + // We only care if the controller that disconnected was the one + // we are currently using + if (controllerIndex == inputProxy->getActiveGameControllerIndex()) { + // Default to no fallback controller, loop and look for another connected + // one + int32_t newControllerIndex = -1; + + for (int32_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { + if (i != controllerIndex && + Paddleboat_getControllerStatus(i) == PADDLEBOAT_CONTROLLER_ACTIVE) { + newControllerIndex = i; + break; + } + } + inputProxy->setActiveGameControllerIndex(newControllerIndex); + } + } + ControllerChangeEvent event; + for (int32_t i = 0; i < PADDLEBOAT_MAX_CONTROLLERS; ++i) { + if (Paddleboat_getControllerStatus(i) == PADDLEBOAT_CONTROLLER_ACTIVE) { + event.controllerIds.emplace_back(i); + } + } + events::ControllerChange::broadcast(event); + } +} + +AndroidPlatform::~AndroidPlatform() = default; + +int AndroidPlatform::init() { +#if CC_USE_XR + registerInterface(std::make_shared()); +#endif + IXRInterface *xr = CC_GET_XR_INTERFACE(); + if (xr) { + JniHelper::getEnv(); + xr->initialize(JniHelper::getJavaVM(), getActivity()); + } + cc::FileUtilsAndroid::setAssetManager(_app->activity->assetManager); + _inputProxy = ccnew GameInputProxy(this); + _inputProxy->registerAppEventCallback([this](int32_t cmd) { + IXRInterface *xr = CC_GET_XR_INTERFACE(); + if (xr) { + xr->handleAppCommand(cmd); + } + + if (APP_CMD_START == cmd || APP_CMD_INIT_WINDOW == cmd) { + if (_inputProxy->isAnimating()) { + _isLowFrequencyLoopEnabled = false; + _loopTimeOut = 0; + } + } else if (APP_CMD_STOP == cmd) { + _lowFrequencyTimer.reset(); + _loopTimeOut = LOW_FREQUENCY_TIME_INTERVAL; + _isLowFrequencyLoopEnabled = true; + if (xr && !xr->getXRConfig(xr::XRConfigKey::INSTANCE_CREATED).getBool()) { + // xr will sleep, -1 we will block forever waiting for events. + _loopTimeOut = -1; + _isLowFrequencyLoopEnabled = false; + } + } + }); + _app->userData = _inputProxy; + _app->onAppCmd = handleCmdProxy; + + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + + return 0; +} + +void AndroidPlatform::onDestroy() { + UniversalPlatform::onDestroy(); + unregisterAllInterfaces(); + JniHelper::onDestroy(); + CC_SAFE_DELETE(_inputProxy) +} + +cc::ISystemWindow *AndroidPlatform::createNativeWindow(uint32_t windowId, void *externalHandle) { + return ccnew SystemWindow(windowId, externalHandle); +} + +int AndroidPlatform::getSdkVersion() const { + return AConfiguration_getSdkVersion(_app->config); +} + +int32_t AndroidPlatform::run(int /*argc*/, const char ** /*argv*/) { + loop(); + return 0; +} + +void AndroidPlatform::exit() { + _app->destroyRequested = 1; +} + +int32_t AndroidPlatform::loop() { + IXRInterface *xr = CC_GET_XR_INTERFACE(); + while (true) { + int events; + struct android_poll_source *source; + + // suspend thread while _loopTimeOut set to -1 + while ((ALooper_pollAll(_loopTimeOut, nullptr, &events, + reinterpret_cast(&source))) >= 0) { + // process event + if (source != nullptr) { + source->process(_app, source); + } + + // Exit the game loop when the Activity is destroyed + if (_app->destroyRequested) { + break; + } + } + // Exit the game loop when the Activity is destroyed + if (_app->destroyRequested) { + break; + } + if (xr && !xr->platformLoopStart()) continue; + _inputProxy->handleInput(); + if (_inputProxy->isAnimating() && (xr ? xr->getXRConfig(xr::XRConfigKey::SESSION_RUNNING).getBool() : true)) { + runTask(); + if (_inputProxy->isActive()) { + flushTasksOnGameThreadAtForegroundJNI(); + } + } + flushTasksOnGameThreadJNI(); + +#if CC_ENABLE_SUSPEND_GAME_THREAD + if (_isLowFrequencyLoopEnabled) { + // Suspend a game thread after it has been running in the background for a specified amount of time + if (_lowFrequencyTimer.getSeconds() > LOW_FREQUENCY_EXPIRED_DURATION_SECONDS) { + _isLowFrequencyLoopEnabled = false; + _loopTimeOut = -1; + } + } +#endif + if (xr) xr->platformLoopEnd(); + } + onDestroy(); + return 0; +} + +void AndroidPlatform::pollEvent() { + // +} + +void *AndroidPlatform::getActivity() { // Dangerous + return _app->activity->javaGameActivity; +} + +void *AndroidPlatform::getEnv() { + return JniHelper::getEnv(); +} + +} // namespace cc diff --git a/cocos/platform/android/AndroidPlatform.h b/cocos/platform/android/AndroidPlatform.h new file mode 100644 index 0000000..de55002 --- /dev/null +++ b/cocos/platform/android/AndroidPlatform.h @@ -0,0 +1,72 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Timer.h" +#include "platform/UniversalPlatform.h" + +struct android_app; + +namespace cc { +class GameInputProxy; + +class CC_DLL AndroidPlatform : public UniversalPlatform { +public: + AndroidPlatform() = default; + + ~AndroidPlatform() override; + + int init() override; + + void pollEvent() override; + + int32_t run(int argc, const char **argv) override; + + int getSdkVersion() const override; + + int32_t loop() override; + void exit() override; + + void *getActivity(); + + static void *getEnv(); + + void onDestroy() override; + + inline void setAndroidApp(android_app *app) { + _app = app; + } + + ISystemWindow *createNativeWindow(uint32_t windowId, void *externalHandle) override; + +private: + bool _isLowFrequencyLoopEnabled{false}; + utils::Timer _lowFrequencyTimer; + int _loopTimeOut{-1}; + GameInputProxy *_inputProxy{nullptr}; + android_app *_app{nullptr}; + friend class GameInputProxy; +}; +} // namespace cc diff --git a/cocos/platform/android/FileUtils-android.cpp b/cocos/platform/android/FileUtils-android.cpp new file mode 100644 index 0000000..1a45c26 --- /dev/null +++ b/cocos/platform/android/FileUtils-android.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/android/FileUtils-android.h" +#include +#include +#include +#include "android/asset_manager.h" +#include "android/asset_manager_jni.h" +#include "base/Log.h" +#include "base/ZipUtils.h" +#include "base/memory/Memory.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/JniImp.h" + +#define LOG_TAG "FileUtils-android.cpp" +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + +#define ASSETS_FOLDER_NAME "@assets/" + +#ifndef JCLS_HELPER + #define JCLS_HELPER "com/cocos/lib/CocosHelper" +#endif + +namespace cc { + +AAssetManager *FileUtilsAndroid::assetmanager = nullptr; +ZipFile *FileUtilsAndroid::obbfile = nullptr; + +FileUtils *createFileUtils() { + return ccnew FileUtilsAndroid(); +} + +void FileUtilsAndroid::setAssetManager(AAssetManager *a) { + if (nullptr == a) { + LOGD("setAssetManager : received unexpected nullptr parameter"); + return; + } + + cc::FileUtilsAndroid::assetmanager = a; +} + +FileUtilsAndroid::FileUtilsAndroid() { + init(); +} + +FileUtilsAndroid::~FileUtilsAndroid() { + CC_SAFE_DELETE(obbfile); +} + +bool FileUtilsAndroid::init() { + _defaultResRootPath = ASSETS_FOLDER_NAME; + + ccstd::string assetsPath(getObbFilePathJNI()); + if (assetsPath.find("/obb/") != ccstd::string::npos) { + obbfile = ccnew ZipFile(assetsPath); + } + + return FileUtils::init(); +} + +bool FileUtilsAndroid::isFileExistInternal(const ccstd::string &strFilePath) const { + if (strFilePath.empty()) { + return false; + } + + bool bFound = false; + + // Check whether file exists in apk. + if (strFilePath[0] != '/') { + const char *s = strFilePath.c_str(); + + // Found "@assets/" at the beginning of the path and we don't want it + if (strFilePath.find(ASSETS_FOLDER_NAME) == 0) s += strlen(ASSETS_FOLDER_NAME); + if (obbfile && obbfile->fileExists(s)) { + bFound = true; + } else if (FileUtilsAndroid::assetmanager) { + AAsset *aa = AAssetManager_open(FileUtilsAndroid::assetmanager, s, AASSET_MODE_UNKNOWN); + if (aa) { + bFound = true; + AAsset_close(aa); + } else { + // CC_LOG_DEBUG("[AssetManager] ... in APK %s, found = false!", strFilePath.c_str()); + } + } + } else { + FILE *fp = fopen(strFilePath.c_str(), "r"); + if (fp) { + bFound = true; + fclose(fp); + } + } + return bFound; +} + +bool FileUtilsAndroid::isDirectoryExistInternal(const ccstd::string &testDirPath) const { + if (testDirPath.empty()) { + return false; + } + + ccstd::string dirPath = testDirPath; + if (dirPath[dirPath.length() - 1] == '/') { + dirPath[dirPath.length() - 1] = '\0'; + } + + // find absolute path in flash memory + if (dirPath[0] == '/') { + CC_LOG_DEBUG("find in flash memory dirPath(%s)", dirPath.c_str()); + struct stat st; + if (stat(dirPath.c_str(), &st) == 0) { + return S_ISDIR(st.st_mode); + } + } else { + // find it in apk's assets dir + // Found "@assets/" at the beginning of the path and we don't want it + CC_LOG_DEBUG("find in apk dirPath(%s)", dirPath.c_str()); + const char *s = dirPath.c_str(); + if (dirPath.find(_defaultResRootPath) == 0) { + s += _defaultResRootPath.length(); + } + if (FileUtilsAndroid::assetmanager) { + AAssetDir *aa = AAssetManager_openDir(FileUtilsAndroid::assetmanager, s); + if (aa && AAssetDir_getNextFileName(aa)) { + AAssetDir_close(aa); + return true; + } + } + } + + return false; +} + +bool FileUtilsAndroid::isAbsolutePath(const ccstd::string &strPath) const { + // On Android, there are two situations for full path. + // 1) Files in APK, e.g. assets/path/path/file.png + // 2) Files not in APK, e.g. /data/data/org.cocos2dx.hellocpp/cache/path/path/file.png, or /sdcard/path/path/file.png. + // So these two situations need to be checked on Android. + return strPath[0] == '/' || strPath.find(ASSETS_FOLDER_NAME) == 0; +} + +FileUtils::Status FileUtilsAndroid::getContents(const ccstd::string &filename, ResizableBuffer *buffer) { + if (filename.empty()) { + return FileUtils::Status::NOT_EXISTS; + } + + ccstd::string fullPath = fullPathForFilename(filename); + if (fullPath.empty()) { + return FileUtils::Status::NOT_EXISTS; + } + + if (fullPath[0] == '/') { + return FileUtils::getContents(fullPath, buffer); + } + + ccstd::string relativePath; + size_t position = fullPath.find(ASSETS_FOLDER_NAME); + if (0 == position) { + // "@assets/" is at the beginning of the path and we don't want it + relativePath += fullPath.substr(strlen(ASSETS_FOLDER_NAME)); + } else { + relativePath = fullPath; + } + + if (obbfile) { + if (obbfile->getFileData(relativePath, buffer)) { + return FileUtils::Status::OK; + } + } + + if (nullptr == assetmanager) { + LOGD("... FileUtilsAndroid::assetmanager is nullptr"); + return FileUtils::Status::NOT_INITIALIZED; + } + + AAsset *asset = AAssetManager_open(assetmanager, relativePath.data(), AASSET_MODE_UNKNOWN); + if (nullptr == asset) { + LOGD("asset (%s) is nullptr", filename.c_str()); + return FileUtils::Status::OPEN_FAILED; + } + + auto size = AAsset_getLength(asset); + buffer->resize(size); + + int readsize = AAsset_read(asset, buffer->buffer(), size); + AAsset_close(asset); + + if (readsize < size) { + if (readsize >= 0) { + buffer->resize(readsize); + } + return FileUtils::Status::READ_FAILED; + } + + return FileUtils::Status::OK; +} + +ccstd::string FileUtilsAndroid::getWritablePath() const { + if (!_writablePath.empty()) { + return _writablePath; + } + // Fix for Nexus 10 (Android 4.2 multi-user environment) + // the path is retrieved through Java Context.getCacheDir() method + ccstd::string dir; + ccstd::string tmp = JniHelper::callStaticStringMethod(JCLS_HELPER, "getWritablePath"); + + if (tmp.length() > 0) { + dir.append(tmp).append("/"); + return dir; + } + return ""; +} + +} // namespace cc diff --git a/cocos/platform/android/FileUtils-android.h b/cocos/platform/android/FileUtils-android.h new file mode 100644 index 0000000..2b8db4e --- /dev/null +++ b/cocos/platform/android/FileUtils-android.h @@ -0,0 +1,74 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "android/asset_manager.h" +#include "base/Macros.h" +#include "base/std/container/string.h" +#include "jni.h" +#include "platform/FileUtils.h" + +namespace cc { + +class ZipFile; + +/** + * @addtogroup platform + * @{ + */ + +//! @brief Helper class to handle file operations +class CC_DLL FileUtilsAndroid : public FileUtils { + friend class FileUtils; + +public: + FileUtilsAndroid(); + ~FileUtilsAndroid() override; + + static void setAssetManager(AAssetManager *a); + static AAssetManager *getAssetManager() { return assetmanager; } + static ZipFile *getObbFile() { return obbfile; } + + /* override functions */ + bool init() override; + FileUtils::Status getContents(const ccstd::string &filename, ResizableBuffer *buffer) override; + + ccstd::string getWritablePath() const override; + bool isAbsolutePath(const ccstd::string &strPath) const override; + +private: + bool isFileExistInternal(const ccstd::string &strFilePath) const override; + bool isDirectoryExistInternal(const ccstd::string &dirPath) const override; + + static AAssetManager *assetmanager; + static ZipFile *obbfile; +}; + +// end of platform group +/// @} + +} // namespace cc diff --git a/cocos/platform/android/adpf_manager.cpp b/cocos/platform/android/adpf_manager.cpp new file mode 100644 index 0000000..e663160 --- /dev/null +++ b/cocos/platform/android/adpf_manager.cpp @@ -0,0 +1,272 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "adpf_manager.h" +#include "platform/BasePlatform.h" + +#if CC_PLATFORM == CC_PLATFORM_ANDROID && __ANDROID_API__ >= 30 + + #include + #include + #include + #include + #include + #include + #include "java/jni/JniHelper.h" + + #define ALOGI(...) + #define ALOGE(...) + +// Invoke the method periodically (once a frame) to monitor +// the device's thermal throttling status. +void ADPFManager::Monitor() { + auto currentClock = std::chrono::high_resolution_clock::now(); + auto past = currentClock - last_clock_; + auto pastMS = std::chrono::duration_cast(past).count(); + + // if (current_clock - last_clock_ >= kThermalHeadroomUpdateThreshold) { + if (past > kThermalHeadroomUpdateThreshold) { + // Update thermal headroom. + // CC_LOG_INFO(" Monitor past %d ms", static_cast(pastMS)); + UpdateThermalStatusHeadRoom(); + last_clock_ = currentClock; + } +} + +float ADPFManager::GetThermalStatusNormalized() const { + if (thermal_manager_ == nullptr) { + return 0; + } + auto level = AThermal_getCurrentThermalStatus(thermal_manager_); + auto levelValue = (static_cast(level) - static_cast(ATHERMAL_STATUS_NONE)) * 1.0f / + static_cast(ATHERMAL_STATUS_SHUTDOWN); + return levelValue; +} + +// Invoke the API first to set the android_app instance. +void ADPFManager::Initialize() { + // Initialize PowerManager reference. + InitializePowerManager(); + + // Initialize PowerHintManager reference. + InitializePerformanceHintManager(); + + beforeTick.bind([&]() { + this->BeginPerfHintSession(); + this->Monitor(); + }); + + afterTick.bind([&]() { + auto fps = cc::BasePlatform::getPlatform()->getFps(); + auto frameDurationNS = 1000000000LL / fps; + this->EndPerfHintSession(frameDurationNS); + }); + + if (thermal_manager_) { + auto ret = AThermal_registerThermalStatusListener( + thermal_manager_, +[](void *data, AThermalStatus status) { + ADPFManager::getInstance().SetThermalStatus(status); + CC_LOG_INFO("Thermal Status :%d", static_cast(status)); + }, + nullptr); + ALOGI("Thermal Status callback registerred to:%d", ret); + } +} + +// Initialize JNI calls for the powermanager. +bool ADPFManager::InitializePowerManager() { + #if __ANDROID_API__ >= 31 + if (android_get_device_api_level() >= 31) { + // Initialize the powermanager using NDK API. + thermal_manager_ = AThermal_acquireManager(); + return true; + } + #endif + + JNIEnv *env = cc::JniHelper::getEnv(); + auto *javaGameActivity = cc::JniHelper::getActivity(); + + // Retrieve class information + jclass context = env->FindClass("android/content/Context"); + + // Get the value of a constant + jfieldID fid = + env->GetStaticFieldID(context, "POWER_SERVICE", "Ljava/lang/String;"); + jobject str_svc = env->GetStaticObjectField(context, fid); + + // Get the method 'getSystemService' and call it + jmethodID mid_getss = env->GetMethodID( + context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + jobject obj_power_service = env->CallObjectMethod(javaGameActivity, mid_getss, str_svc); + + // Add global reference to the power service object. + obj_power_service_ = env->NewGlobalRef(obj_power_service); + + jclass cls_power_service = env->GetObjectClass(obj_power_service_); + get_thermal_headroom_ = + env->GetMethodID(cls_power_service, "getThermalHeadroom", "(I)F"); + + // Free references + env->DeleteLocalRef(cls_power_service); + env->DeleteLocalRef(obj_power_service); + env->DeleteLocalRef(str_svc); + env->DeleteLocalRef(context); + + if (get_thermal_headroom_ == 0) { + // The API is not supported in the platform version. + return false; + } + + return true; +} + +// Retrieve current thermal headroom using JNI call. +float ADPFManager::UpdateThermalStatusHeadRoom() { + #if __ANDROID_API__ >= 31 + if (android_get_device_api_level() >= 31) { + // Use NDK API to retrieve thermal status headroom. + auto seconds = kThermalHeadroomUpdateThreshold.count(); + thermal_headroom_ = AThermal_getThermalHeadroom( + thermal_manager_, seconds); + if (!std::isnan(thermal_headroom_)) { + thermal_headroom_valid_ = thermal_headroom_; + } + return thermal_headroom_; + } + #endif + + if (get_thermal_headroom_ == 0) { + return 0.f; + } + JNIEnv *env = cc::JniHelper::getEnv(); + + // Get thermal headroom! + thermal_headroom_ = + env->CallFloatMethod(obj_power_service_, get_thermal_headroom_, + kThermalHeadroomUpdateThreshold); + ALOGE("Current thermal Headroom %f", thermal_headroom_); + return thermal_headroom_; +} + +// Initialize JNI calls for the PowerHintManager. +bool ADPFManager::InitializePerformanceHintManager() { + JNIEnv *env = cc::JniHelper::getEnv(); + auto *javaGameActivity = cc::JniHelper::getActivity(); + + // Retrieve class information + jclass context = env->FindClass("android/content/Context"); + + // Get the value of a constant + jfieldID fid = env->GetStaticFieldID(context, "PERFORMANCE_HINT_SERVICE", + "Ljava/lang/String;"); + jobject str_svc = env->GetStaticObjectField(context, fid); + + // Get the method 'getSystemService' and call it + jmethodID mid_getss = env->GetMethodID( + context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + jobject obj_perfhint_service = env->CallObjectMethod( + javaGameActivity, mid_getss, str_svc); + + // Add global reference to the power service object. + obj_perfhint_service_ = env->NewGlobalRef(obj_perfhint_service); + + // Retrieve methods IDs for the APIs. + jclass cls_perfhint_service = env->GetObjectClass(obj_perfhint_service_); + jmethodID mid_createhintsession = + env->GetMethodID(cls_perfhint_service, "createHintSession", + "([IJ)Landroid/os/PerformanceHintManager$Session;"); + jmethodID mid_preferedupdaterate = env->GetMethodID( + cls_perfhint_service, "getPreferredUpdateRateNanos", "()J"); + + // Create int array which contain current tid. + jintArray array = env->NewIntArray(1); + int32_t tid = getpid(); + env->SetIntArrayRegion(array, 0, 1, &tid); + const jlong DEFAULT_TARGET_NS = 16666666; + + // Create Hint session for the thread. + jobject obj_hintsession = env->CallObjectMethod( + obj_perfhint_service_, mid_createhintsession, array, DEFAULT_TARGET_NS); + if (obj_hintsession == nullptr) { + ALOGI("Failed to create a perf hint session."); + } else { + obj_perfhint_session_ = env->NewGlobalRef(obj_hintsession); + preferred_update_rate_ = + env->CallLongMethod(obj_perfhint_service_, mid_preferedupdaterate); + + // Retrieve mid of Session APIs. + jclass cls_perfhint_session = env->GetObjectClass(obj_perfhint_session_); + report_actual_work_duration_ = env->GetMethodID( + cls_perfhint_session, "reportActualWorkDuration", "(J)V"); + update_target_work_duration_ = env->GetMethodID( + cls_perfhint_session, "updateTargetWorkDuration", "(J)V"); + } + + // Free local references + env->DeleteLocalRef(obj_hintsession); + env->DeleteLocalRef(array); + env->DeleteLocalRef(cls_perfhint_service); + env->DeleteLocalRef(obj_perfhint_service); + env->DeleteLocalRef(str_svc); + env->DeleteLocalRef(context); + + if (report_actual_work_duration_ == 0 || update_target_work_duration_ == 0) { + // The API is not supported in the platform version. + return false; + } + + return true; +} + +thermalStateChangeListener ADPFManager::thermalListener = NULL; + +void ADPFManager::SetThermalStatus(int32_t i) { + int32_t prev_status_ = thermal_status_; + int32_t current_status_ = i; + thermal_status_ = i; + if (thermalListener != NULL) { + thermalListener(prev_status_, current_status_); + } +} + +void ADPFManager::SetThermalListener(thermalStateChangeListener listener) { + thermalListener = listener; +} + +// Indicates the start and end of the performance intensive task. +// The methods call performance hint API to tell the performance +// hint to the system. +void ADPFManager::BeginPerfHintSession() { perfhintsession_start_ = std::chrono::high_resolution_clock::now(); } + +void ADPFManager::EndPerfHintSession(jlong target_duration_ns) { + auto current_clock = std::chrono::high_resolution_clock::now(); + auto duration = current_clock - perfhintsession_start_; + frame_time_ns_ = std::chrono::duration_cast(duration).count(); + if (obj_perfhint_session_) { + jlong duration_ns = std::chrono::duration_cast( + duration * 100000000) + .count(); + auto *env = cc::JniHelper::getEnv(); + + // Report and update the target work duration using JNI calls. + env->CallVoidMethod(obj_perfhint_session_, report_actual_work_duration_, + duration_ns); + env->CallVoidMethod(obj_perfhint_session_, update_target_work_duration_, + target_duration_ns); + } +} + +#endif diff --git a/cocos/platform/android/adpf_manager.h b/cocos/platform/android/adpf_manager.h new file mode 100644 index 0000000..9ef9bc8 --- /dev/null +++ b/cocos/platform/android/adpf_manager.h @@ -0,0 +1,161 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ADPF_MANAGER_H_ +#define ADPF_MANAGER_H_ + +#if CC_PLATFORM == CC_PLATFORM_ANDROID && __ANDROID_API__ >= 30 + #include + #include + #include + #include + + #include + #include + #include "3d/models/SkinningModel.h" + #include "engine/EngineEvents.h" + #include "platform/java/jni/JniHelper.h" + +// Forward declarations of functions that need to be in C decl. + +typedef void (*thermalStateChangeListener)(int32_t, int32_t); + +/* + * ADPFManager class anages the ADPF APIs. + */ +class ADPFManager { +public: + // Singleton function. + static ADPFManager &getInstance() { + static ADPFManager instance; + return instance; + } + + // Dtor. + ~ADPFManager() { + // Remove global reference. + auto env = cc::JniHelper::getEnv(); + if (env != nullptr) { + if (obj_power_service_ != nullptr) { + env->DeleteGlobalRef(obj_power_service_); + } + if (obj_perfhint_service_ != nullptr) { + env->DeleteGlobalRef(obj_perfhint_service_); + } + if (obj_perfhint_session_ != nullptr) { + env->DeleteGlobalRef(obj_perfhint_session_); + } + if (thermal_manager_ != nullptr) { + AThermal_releaseManager(thermal_manager_); + } + } + } + + // Delete copy constructor since the class is used as a singleton. + ADPFManager(ADPFManager const &) = delete; + + void operator=(ADPFManager const &) = delete; + + // Invoke the method periodically (once a frame) to monitor + // the device's thermal throttling status. + void Monitor(); + + // Invoke the API first to set the android_app instance. + + // Method to set thermal status. Need to be public since the method + // is called from C native listener. + void SetThermalStatus(int32_t i); + + // Get current thermal status and headroom. + int32_t GetThermalStatus() { return thermal_status_; } + float GetThermalStatusNormalized() const; + + float GetFrameTimeMS() const { return frame_time_ns_ / 1000000.0F; } + + float GetThermalHeadroom() { return thermal_headroom_; } + + void SetThermalListener(thermalStateChangeListener listener); + + // Indicates the start and end of the performance intensive task. + // The methods call performance hint API to tell the performance + // hint to the system. + void BeginPerfHintSession(); + + void EndPerfHintSession(jlong target_duration_ns); + + // Method to retrieve thermal manager. The API is used to register/unregister + // callbacks from C API. + AThermalManager *GetThermalManager() { return thermal_manager_; } + + void Initialize(); + +private: + // Update thermal headroom each sec. + static constexpr auto kThermalHeadroomUpdateThreshold = std::chrono::seconds(1); + + // Function pointer from the game, will be invoked when we receive state changed event from Thermal API + static thermalStateChangeListener thermalListener; + + // Ctor. It's private since the class is designed as a singleton. + ADPFManager() + : thermal_manager_(nullptr), + thermal_status_(0), + thermal_headroom_(0.f), + obj_power_service_(nullptr), + get_thermal_headroom_(0), + obj_perfhint_service_(nullptr), + obj_perfhint_session_(nullptr), + report_actual_work_duration_(0), + update_target_work_duration_(0), + preferred_update_rate_(0) { + last_clock_ = std::chrono::high_resolution_clock::now(); + perfhintsession_start_ = std::chrono::high_resolution_clock::now(); + } + + // Functions to initialize ADPF API's calls. + bool InitializePowerManager(); + + float UpdateThermalStatusHeadRoom(); + + bool InitializePerformanceHintManager(); + + AThermalManager *thermal_manager_ = nullptr; + int32_t thermal_status_; + float thermal_headroom_ = 0; + float thermal_headroom_valid_ = 0; + std::chrono::time_point last_clock_; + jobject obj_power_service_; + jmethodID get_thermal_headroom_; + + jobject obj_perfhint_service_; + jobject obj_perfhint_session_; + jmethodID report_actual_work_duration_; + jmethodID update_target_work_duration_; + jlong preferred_update_rate_; + + cc::events::BeforeTick::Listener beforeTick; + cc::events::AfterTick::Listener afterTick; + + std::chrono::time_point perfhintsession_start_; + int64_t frame_time_ns_{0}; +}; + + #define CC_SUPPORT_ADPF 1 // NOLINT +#else + #define CC_SUPPORT_ADPF 0 // NOLINT +#endif // ADPF_MANAGER_H_ + +#endif diff --git a/cocos/platform/android/java/.classpath b/cocos/platform/android/java/.classpath new file mode 100644 index 0000000..5176974 --- /dev/null +++ b/cocos/platform/android/java/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/cocos/platform/android/java/.project b/cocos/platform/android/java/.project new file mode 100644 index 0000000..9a978af --- /dev/null +++ b/cocos/platform/android/java/.project @@ -0,0 +1,33 @@ + + + libcocos2dx + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/cocos/platform/android/java/AndroidManifest.xml b/cocos/platform/android/java/AndroidManifest.xml new file mode 100644 index 0000000..339f389 --- /dev/null +++ b/cocos/platform/android/java/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/cocos/platform/android/java/build.xml b/cocos/platform/android/java/build.xml new file mode 100644 index 0000000..885e6ee --- /dev/null +++ b/cocos/platform/android/java/build.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cocos/platform/android/java/libs/com.android.vending.expansion.zipfile.jar b/cocos/platform/android/java/libs/com.android.vending.expansion.zipfile.jar new file mode 100644 index 0000000..bbdeb8a Binary files /dev/null and b/cocos/platform/android/java/libs/com.android.vending.expansion.zipfile.jar differ diff --git a/cocos/platform/android/java/libs/game-sdk.jar b/cocos/platform/android/java/libs/game-sdk.jar new file mode 100644 index 0000000..5bed1ab Binary files /dev/null and b/cocos/platform/android/java/libs/game-sdk.jar differ diff --git a/cocos/platform/android/java/libs/okhttp-3.12.14.jar b/cocos/platform/android/java/libs/okhttp-3.12.14.jar new file mode 100644 index 0000000..780477e Binary files /dev/null and b/cocos/platform/android/java/libs/okhttp-3.12.14.jar differ diff --git a/cocos/platform/android/java/libs/okio-1.15.0.jar b/cocos/platform/android/java/libs/okio-1.15.0.jar new file mode 100644 index 0000000..4e0e47a Binary files /dev/null and b/cocos/platform/android/java/libs/okio-1.15.0.jar differ diff --git a/cocos/platform/android/java/proguard-project.txt b/cocos/platform/android/java/proguard-project.txt new file mode 100644 index 0000000..786b01b --- /dev/null +++ b/cocos/platform/android/java/proguard-project.txt @@ -0,0 +1,13 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: diff --git a/cocos/platform/android/java/project.properties b/cocos/platform/android/java/project.properties new file mode 100755 index 0000000..88ca83f --- /dev/null +++ b/cocos/platform/android/java/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +android.library=true +# Project target. +target=android-10 diff --git a/cocos/platform/android/java/src/com/cocos/lib/CanvasRenderingContext2DImpl.java b/cocos/platform/android/java/src/com/cocos/lib/CanvasRenderingContext2DImpl.java new file mode 100644 index 0000000..0eae825 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CanvasRenderingContext2DImpl.java @@ -0,0 +1,529 @@ +/**************************************************************************** + * Copyright (c) 2018 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Typeface; +import android.os.Build; +import android.text.TextPaint; +import android.util.Log; + +import java.lang.ref.WeakReference; +import java.util.HashMap; + +public class CanvasRenderingContext2DImpl { + + private static final String TAG = "CanvasContext2D"; + + private static final int TEXT_ALIGN_LEFT = 0; + private static final int TEXT_ALIGN_CENTER = 1; + private static final int TEXT_ALIGN_RIGHT = 2; + + private static final int TEXT_BASELINE_TOP = 0; + private static final int TEXT_BASELINE_MIDDLE = 1; + private static final int TEXT_BASELINE_BOTTOM = 2; + private static final int TEXT_BASELINE_ALPHABETIC = 3; + + private static WeakReference sContext; + private TextPaint mTextPaint; + private Paint mLinePaint; + private Path mLinePath; + private Canvas mCanvas = new Canvas(); + private Bitmap mBitmap; + private int mTextAlign = TEXT_ALIGN_LEFT; + private int mTextBaseline = TEXT_BASELINE_BOTTOM; + private int mFillStyleR = 0; + private int mFillStyleG = 0; + private int mFillStyleB = 0; + private int mFillStyleA = 255; + + private int mStrokeStyleR = 0; + private int mStrokeStyleG = 0; + private int mStrokeStyleB = 0; + private int mStrokeStyleA = 255; + + private String mFontName = "Arial"; + private float mFontSize = 40.0f; + private float mLineWidth = 0.0f; + private float mShadowBlur = 0.0f; + private float mShadowOffsetX = 0.0f; + private float mShadowOffsetY = 0.0f; + private int mShadowColorA = 0; + private int mShadowColorB = 0; + private int mShadowColorG = 0; + private int mShadowColorR = 0; + + private static float _sApproximatingOblique = -0.25f;//please check paint api documentation + private boolean mIsBoldFont = false; + private boolean mIsItalicFont = false; + private boolean mIsObliqueFont = false; + private boolean mIsSmallCapsFontVariant = false; + private String mLineCap = "butt"; + private String mLineJoin = "miter"; + + private class Size { + Size(float w, float h) { + this.width = w; + this.height = h; + } + + Size() { + this.width = 0; + this.height = 0; + } + public float width; + public float height; + } + + private class Point { + Point(float x, float y) { + this.x = x; + this.y = y; + } + + Point() { + this.x = this.y = 0.0f; + } + + Point(Point pt) { + this.x = pt.x; + this.y = pt.y; + } + + void set(float x, float y) { + this.x = x; + this.y = y; + } + + public float x; + public float y; + } + + static void init(Context context) { + sContext = new WeakReference<>(context); + } + + static void destroy() { + sContext = null; + } + + private static HashMap sTypefaceCache = new HashMap<>(); + + // url is a full path started with '@assets/' + private static void loadTypeface(String familyName, String url) { + if (!sTypefaceCache.containsKey(familyName)) { + try { + Typeface typeface = null; + if (url.startsWith("/")) { + typeface = Typeface.createFromFile(url); + } else if (sContext.get() != null) { + final String prefix = "@assets/"; + if (url.startsWith(prefix)) { + url = url.substring(prefix.length()); + } + typeface = Typeface.createFromAsset(sContext.get().getAssets(), url); + } + + if (typeface != null) { + sTypefaceCache.put(familyName, typeface); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + // REFINE:: native should clear font cache before exiting game. + private static void clearTypefaceCache() { + sTypefaceCache.clear(); + } + + private static TextPaint newPaint(String fontName, int fontSize, boolean enableBold, boolean enableItalic, boolean obliqueFont, boolean smallCapsFontVariant) { + TextPaint paint = new TextPaint(); + paint.setTextSize(fontSize); + paint.setAntiAlias(true); + paint.setSubpixelText(true); + + int style = Typeface.NORMAL; + if (enableBold && enableItalic) { + paint.setFakeBoldText(true); + style = Typeface.BOLD_ITALIC; + } else if (enableBold) { + paint.setFakeBoldText(true); + style = Typeface.BOLD; + } else if (enableItalic) { + style = Typeface.ITALIC; + } + + Typeface typeFace = null; + if (sTypefaceCache.containsKey(fontName)) { + typeFace = sTypefaceCache.get(fontName); + typeFace = Typeface.create(typeFace, style); + } else { + typeFace = Typeface.create(fontName, style); + } + paint.setTypeface(typeFace); + if(obliqueFont) { + paint.setTextSkewX(_sApproximatingOblique); + } + if(smallCapsFontVariant && Build.VERSION.SDK_INT >= 21) { + CocosReflectionHelper.invokeInstanceMethod(paint, + "setFontFeatureSettings", + new Class[]{String.class}, + new Object[]{"smcp"}); + } + return paint; + } + + private CanvasRenderingContext2DImpl() { + // Log.d(TAG, "constructor"); + } + + private void recreateBuffer(float w, float h) { + // Log.d(TAG, "recreateBuffer:" + w + ", " + h); + if (mBitmap != null) { + mBitmap.recycle(); + } + mBitmap = Bitmap.createBitmap((int)Math.ceil(w), (int)Math.ceil(h), Bitmap.Config.ARGB_8888); + // FIXME: in MIX 2S, its API level is 28, but can not find invokeInstanceMethod. It seems + // devices may not obey the specification, so comment the codes. +// if (Build.VERSION.SDK_INT >= 19) { +// CocosReflectionHelper.invokeInstanceMethod(mBitmap, +// "setPremultiplied", +// new Class[]{Boolean.class}, +// new Object[]{Boolean.FALSE}); +// } + mCanvas.setBitmap(mBitmap); + } + + private void beginPath() { + if (mLinePath == null) { + mLinePath = new Path(); + } + mLinePath.reset(); + } + + private void closePath() { + mLinePath.close(); + } + + private void moveTo(float x, float y) { + mLinePath.moveTo(x, y); + } + + private void lineTo(float x, float y) { + mLinePath.lineTo(x, y); + } + + private void stroke() { + if (mLinePaint == null) { + mLinePaint = new Paint(); + mLinePaint.setAntiAlias(true); + } + + if(mLinePath == null) { + mLinePath = new Path(); + } + + mLinePaint.setARGB(mStrokeStyleA, mStrokeStyleR, mStrokeStyleG, mStrokeStyleB); + mLinePaint.setStyle(Paint.Style.STROKE); + mLinePaint.setStrokeWidth(mLineWidth); + this.setStrokeCap(mLinePaint); + this.setStrokeJoin(mLinePaint); + mCanvas.drawPath(mLinePath, mLinePaint); + } + + private void setStrokeCap(Paint paint) { + switch (mLineCap) { + case "butt": + paint.setStrokeCap(Paint.Cap.BUTT); + break; + case "round": + paint.setStrokeCap(Paint.Cap.ROUND); + break; + case "square": + paint.setStrokeCap(Paint.Cap.SQUARE); + break; + } + } + + private void setStrokeJoin(Paint paint) { + switch (mLineJoin) { + case "bevel": + paint.setStrokeJoin(Paint.Join.BEVEL); + break; + case "round": + paint.setStrokeJoin(Paint.Join.ROUND); + break; + case "miter": + paint.setStrokeJoin(Paint.Join.MITER); + break; + } + } + + private void fill() { + if (mLinePaint == null) { + mLinePaint = new Paint(); + } + + if(mLinePath == null) { + mLinePath = new Path(); + } + + mLinePaint.setARGB(mFillStyleA, mFillStyleR, mFillStyleG, mFillStyleB); + mLinePaint.setStyle(Paint.Style.FILL); + mCanvas.drawPath(mLinePath, mLinePaint); + // workaround: draw a hairline to cover the border + mLinePaint.setStrokeWidth(0); + this.setStrokeCap(mLinePaint); + this.setStrokeJoin(mLinePaint); + mLinePaint.setStyle(Paint.Style.STROKE); + mCanvas.drawPath(mLinePath, mLinePaint); + mLinePaint.setStrokeWidth(mLineWidth); + } + + private void setLineCap(String lineCap) { + mLineCap = lineCap; + } + + private void setLineJoin(String lineJoin) { + mLineJoin = lineJoin; + } + + private void setShadowBlur(float blur) { + mShadowBlur = blur * 0.5f; + } + + private void setShadowColor(int r, int g, int b, int a) { + mShadowColorR = r; + mShadowColorG = g; + mShadowColorB = b; + mShadowColorA = a; + } + + private void setShadowOffsetX(float offsetX) { + mShadowOffsetX = offsetX; + } + + private void setShadowOffsetY(float offsetY) { + mShadowOffsetY = offsetY; + } + + private void saveContext() { + mCanvas.save(); + } + + private void restoreContext() { + // If there is no saved state, this method should do nothing. + if (mCanvas.getSaveCount() > 1){ + mCanvas.restore(); + } + } + + private void rect(float x, float y, float w, float h) { + // Log.d(TAG, "this: " + this + ", rect: " + x + ", " + y + ", " + w + ", " + h); + beginPath(); + moveTo(x, y); + lineTo(x, y + h); + lineTo(x + w, y + h); + lineTo(x + w, y); + closePath(); + } + + private void clearRect(float x, float y, float w, float h) { + // Log.d(TAG, "this: " + this + ", clearRect: " + x + ", " + y + ", " + w + ", " + h); + int clearSize = (int)(w * h); + int[] clearColor = new int[clearSize]; + for (int i = 0; i < clearSize; ++i) { + clearColor[i] = Color.TRANSPARENT; + } + mBitmap.setPixels(clearColor, 0, (int) w, (int) x, (int) y, (int) w, (int) h); + } + + private void createTextPaintIfNeeded() { + if (mTextPaint == null) { + mTextPaint = newPaint(mFontName, (int) mFontSize, mIsBoldFont, mIsItalicFont, mIsObliqueFont, mIsSmallCapsFontVariant); + } + } + + private void fillRect(float x, float y, float w, float h) { + // Log.d(TAG, "fillRect: " + x + ", " + y + ", " + ", " + w + ", " + h); + int pixelValue = (mFillStyleA & 0xff) << 24 | (mFillStyleR & 0xff) << 16 | (mFillStyleG & 0xff) << 8 | (mFillStyleB & 0xff); + int fillSize = (int)(w * h); + int[] fillColors = new int[fillSize]; + for (int i = 0; i < fillSize; ++i) { + fillColors[i] = pixelValue; + } + mBitmap.setPixels(fillColors, 0, (int) w, (int)x, (int)y, (int)w, (int)h); + } + + private void scaleX(TextPaint textPaint, String text, float maxWidth) { + if(maxWidth < Float.MIN_VALUE) return; + float measureWidth = this.measureText(text); + if((measureWidth - maxWidth) < Float.MIN_VALUE) return; + float scaleX = maxWidth/measureWidth; + textPaint.setTextScaleX(scaleX); + } + + private void fillText(String text, float x, float y, float maxWidth) { +// Log.d(TAG, "this: " + this + ", fillText: " + text + ", " + x + ", " + y + ", " + ", " + maxWidth); + createTextPaintIfNeeded(); + configShadow(mTextPaint); + mTextPaint.setARGB(mFillStyleA, mFillStyleR, mFillStyleG, mFillStyleB); + mTextPaint.setStyle(Paint.Style.FILL); + scaleX(mTextPaint, text, maxWidth); + Point pt = convertDrawPoint(new Point(x, y), text); + mCanvas.drawText(text, pt.x, pt.y, mTextPaint); + } + + private void strokeText(String text, float x, float y, float maxWidth) { + // Log.d(TAG, "strokeText: " + text + ", " + x + ", " + y + ", " + ", " + maxWidth); + createTextPaintIfNeeded(); + configShadow(mTextPaint); + mTextPaint.setARGB(mStrokeStyleA, mStrokeStyleR, mStrokeStyleG, mStrokeStyleB); + mTextPaint.setStyle(Paint.Style.STROKE); + mTextPaint.setStrokeWidth(mLineWidth); + scaleX(mTextPaint, text, maxWidth); + Point pt = convertDrawPoint(new Point(x, y), text); + mCanvas.drawText(text, pt.x, pt.y, mTextPaint); + } + + private void configShadow(Paint paint) { + if ((Math.abs(mShadowOffsetX) > Float.MIN_VALUE || Math.abs(mShadowOffsetY) > Float.MIN_VALUE)) { + if (mShadowBlur < 0) { + return; + } + if (mShadowBlur < Float.MIN_VALUE) { + mShadowBlur = 0.001f;//If shadowBlur is 0, the shadow effect is not consistent with the web. + } + paint.setShadowLayer(mShadowBlur, mShadowOffsetX, mShadowOffsetY, + Color.argb(mShadowColorA, mShadowColorR, mShadowColorG, mShadowColorB)); + } + } + + private float measureText(String text) { + createTextPaintIfNeeded(); + float ret = mTextPaint.measureText(text); + // Log.d(TAG, "measureText: " + text + ", return: " + ret); + return ret; + } + + private void updateFont(String fontName, float fontSize, boolean bold, boolean italic, boolean oblique, boolean smallCaps) { + // Log.d(TAG, "updateFont: " + fontName + ", " + fontSize); + mFontName = fontName; + mFontSize = fontSize; + mIsBoldFont = bold; + mIsItalicFont = italic; + mIsObliqueFont = oblique; + mIsSmallCapsFontVariant = smallCaps; + mTextPaint = null; // Reset paint to re-create paint object in createTextPaintIfNeeded + } + + private void setTextAlign(int align) { + // Log.d(TAG, "setTextAlign: " + align); + mTextAlign = align; + } + + private void setTextBaseline(int baseline) { + // Log.d(TAG, "setTextBaseline: " + baseline); + mTextBaseline = baseline; + } + + private void setFillStyle(int r, int g, int b, int a) { + // Log.d(TAG, "setFillStyle: " + r + ", " + g + ", " + b + ", " + a); + mFillStyleR = r; + mFillStyleG = g; + mFillStyleB = b; + mFillStyleA = a; + } + + private void setStrokeStyle(int r, int g, int b, int a) { + // Log.d(TAG, "setStrokeStyle: " + r + ", " + g + ", " + b + ", " + a); + mStrokeStyleR = r; + mStrokeStyleG = g; + mStrokeStyleB = b; + mStrokeStyleA = a; + } + + private void setLineWidth(float lineWidth) { + mLineWidth = lineWidth; + } + + private void _fillImageData(int[] imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) { + Log.d(TAG, "_fillImageData: "); + int fillSize = (int) (imageWidth * imageHeight); + int[] fillColors = new int[fillSize]; + int r, g, b, a; + for (int i = 0; i < fillSize; ++i) { + // imageData Pixel (RGBA) -> fillColors int (ARGB) + fillColors[i] = Integer.rotateRight(imageData[i], 8); + } + mBitmap.setPixels(fillColors, 0, (int) imageWidth, (int) offsetX, (int) offsetY, (int) imageWidth, (int) imageHeight); + } + + private Point convertDrawPoint(final Point point, String text) { + // The parameter 'point' is located at left-bottom position. + // Need to adjust 'point' according 'text align' & 'text base line'. + Point ret = new Point(point); + createTextPaintIfNeeded(); + Paint.FontMetrics fm = mTextPaint.getFontMetrics(); + float width = measureText(text); + + if (mTextAlign == TEXT_ALIGN_CENTER) + { + ret.x -= width / 2; + } + else if (mTextAlign == TEXT_ALIGN_RIGHT) + { + ret.x -= width; + } + + // Canvas.drawText accepts the y parameter as the baseline position, not the most bottom + if (mTextBaseline == TEXT_BASELINE_TOP) + { + ret.y += -fm.ascent; + } + else if (mTextBaseline == TEXT_BASELINE_MIDDLE) + { + ret.y += (fm.descent - fm.ascent) / 2 - fm.descent; + } + else if (mTextBaseline == TEXT_BASELINE_BOTTOM) + { + ret.y += -fm.descent; + } + + return ret; + } + + @SuppressWarnings("unused") + private Bitmap getBitmap() { + return mBitmap; + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosActivity.java b/cocos/platform/android/java/src/com/cocos/lib/CocosActivity.java new file mode 100644 index 0000000..a20829e --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosActivity.java @@ -0,0 +1,222 @@ +/**************************************************************************** + * Copyright (c) 2018-2022 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.media.AudioManager; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import com.google.androidgamesdk.GameActivity; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +public class CocosActivity extends GameActivity { + private static final String TAG = "CocosActivity"; + private CocosWebViewHelper mWebViewHelper = null; + private CocosVideoHelper mVideoHelper = null; + + private CocosSensorHandler mSensorHandler; + private List mSurfaceViewArray; + private FrameLayout mRootLayout; + + + + private native void onCreateNative(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + onLoadNativeLibraries(); + onCreateNative(); + + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + super.onCreate(savedInstanceState); + + // GlobalObject.init should be initialized at first. + GlobalObject.init(this, this); + + CocosHelper.registerBatteryLevelReceiver(this); + CocosHelper.init(); + CocosAudioFocusManager.registerAudioFocusListener(this); + CanvasRenderingContext2DImpl.init(this); + this.setVolumeControlStream(AudioManager.STREAM_MUSIC); + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); + initView(); + + + mSensorHandler = new CocosSensorHandler(this); + + setImmersiveMode(); + + Utils.hideVirtualButton(); + + mSurfaceView.setOnTouchListener((v, event) -> processMotionEvent(event)); + } + + private void setImmersiveMode() { + WindowManager.LayoutParams lp = getWindow().getAttributes(); + try { + Field field = lp.getClass().getField("layoutInDisplayCutoutMode"); + //Field constValue = lp.getClass().getDeclaredField("LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER"); + Field constValue = lp.getClass().getDeclaredField("LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES"); + field.setInt(lp, constValue.getInt(null)); + + // https://developer.android.com/training/system-ui/immersive + int flag = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; + + flag |= View.class.getDeclaredField("SYSTEM_UI_FLAG_IMMERSIVE_STICKY").getInt(null); + View view = getWindow().getDecorView(); + view.setSystemUiVisibility(flag); + + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + protected void initView() { + mRootLayout = findViewById(contentViewId); + if (mWebViewHelper == null) { + mWebViewHelper = new CocosWebViewHelper(mRootLayout); + } + + if (mVideoHelper == null) { + mVideoHelper = new CocosVideoHelper(this, mRootLayout); + } + } + + + + public SurfaceView getSurfaceView() { + return this.mSurfaceView; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + CocosHelper.unregisterBatteryLevelReceiver(this); + CocosAudioFocusManager.unregisterAudioFocusListener(this); + CanvasRenderingContext2DImpl.destroy(); + GlobalObject.destroy(); + } + + @Override + protected void onPause() { + super.onPause(); + mSensorHandler.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + mSensorHandler.onResume(); + Utils.hideVirtualButton(); + if (CocosAudioFocusManager.isAudioFocusLoss()) { + CocosAudioFocusManager.registerAudioFocusListener(this); + } + } + + @Override + protected void onStop() { + super.onStop(); + mSurfaceView.setVisibility(View.INVISIBLE); + if (null != mSurfaceViewArray) { + for (CocosSurfaceView surfaceView : mSurfaceViewArray) { + surfaceView.setVisibility(View.INVISIBLE); + } + } + } + + @Override + protected void onStart() { + super.onStart(); + mSurfaceView.setVisibility(View.VISIBLE); + if (null != mSurfaceViewArray) { + for (CocosSurfaceView surfaceView : mSurfaceViewArray) { + surfaceView.setVisibility(View.VISIBLE); + } + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus && CocosAudioFocusManager.isAudioFocusLoss()) { + CocosAudioFocusManager.registerAudioFocusListener(this); + } + } + + // invoke from native code + @SuppressWarnings({"UnusedDeclaration"}) + private void createSurface(int x, int y, int width, int height, int windowId) { + runOnUiThread(new Runnable() { + @Override + public void run() { + CocosSurfaceView view = new CocosSurfaceView(CocosActivity.this, windowId); + view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height); + params.leftMargin = x; + params.topMargin = y; + //mSubsurfaceView.setBackgroundColor(Color.BLUE); + mRootLayout.addView(view, params); + if (null == mSurfaceViewArray) { + mSurfaceViewArray = new ArrayList<>(); + } + mSurfaceViewArray.add(view); + } + }); + } + + private void onLoadNativeLibraries() { + try { + ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA); + + Bundle bundle = ai.metaData; + String libName = bundle.getString("android.app.lib_name"); + if (TextUtils.isEmpty(libName)) { + Log.e(TAG, "can not find library, please config android.app.lib_name at AndroidManifest.xml"); + } + assert libName != null; + System.loadLibrary(libName); + getIntent().putExtra(GameActivity.META_DATA_LIB_NAME, libName); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosAudioFocusManager.java b/cocos/platform/android/java/src/com/cocos/lib/CocosAudioFocusManager.java new file mode 100644 index 0000000..7ffa087 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosAudioFocusManager.java @@ -0,0 +1,121 @@ +/**************************************************************************** + Copyright (c) 2018-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioFocusRequest; +import android.media.AudioManager; +import android.os.Build; +import android.util.Log; + +class CocosAudioFocusManager { + + private static final String _TAG = "CocosAudioFocusManager"; + private static boolean isAudioFocusLost = true; + + private final static AudioManager.OnAudioFocusChangeListener sAfChangeListener = focusChange -> { + Log.d(_TAG, "onAudioFocusChange: " + focusChange + ", thread: " + Thread.currentThread().getName()); + + if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { + // Permanent loss of audio focus + // Pause playback immediately + Log.d(_TAG, "Pause music by AUDIOFOCUS_LOSS"); + isAudioFocusLost = true; + CocosHelper.runOnGameThreadAtForeground(() -> nativeSetAudioVolumeFactor(0)); + } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { + // Pause playback + Log.d(_TAG, "Pause music by AUDIOFOCUS_LOSS_TRANSILENT"); + isAudioFocusLost = true; + CocosHelper.runOnGameThreadAtForeground(() -> nativeSetAudioVolumeFactor(0)); + } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { + // Lower the volume, keep playing + Log.d(_TAG, "Lower the volume, keep playing by AUDIOFOCUS_LOSS_TRANSILENT_CAN_DUCK"); + isAudioFocusLost = false; + CocosHelper.runOnGameThreadAtForeground(() -> nativeSetAudioVolumeFactor(0.1f)); + } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { + // Your app has been granted audio focus again + // Raise volume to normal, restart playback if necessary + Log.d(_TAG, "Resume music by AUDIOFOCUS_GAIN"); + isAudioFocusLost = false; + CocosHelper.runOnGameThreadAtForeground(() -> nativeSetAudioVolumeFactor(1.0f)); + } + }; + + static void registerAudioFocusListener(Context context) { + AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + assert am != null; + int result; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + AudioAttributes playbackAttributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_GAME) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build(); + + // set the playback attributes for the focus requester + AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) + .setAudioAttributes(playbackAttributes) + .setWillPauseWhenDucked(true) + .setOnAudioFocusChangeListener(sAfChangeListener) + .build(); + + result = am.requestAudioFocus(focusRequest); + } else { + // Request audio focus for playback + result = am.requestAudioFocus(sAfChangeListener, + // Use the music stream. + AudioManager.STREAM_MUSIC, + // Request permanent focus. + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); + } + + if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + Log.d(_TAG, "requestAudioFocus succeed"); + isAudioFocusLost = false; + CocosHelper.runOnGameThreadAtForeground(() -> nativeSetAudioVolumeFactor(1.0f)); + return; + } + + Log.e(_TAG, "requestAudioFocus failed!"); + } + + static void unregisterAudioFocusListener(Context context) { + AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + assert am != null; + int result = am.abandonAudioFocus(sAfChangeListener); + if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + Log.d(_TAG, "abandonAudioFocus succeed!"); + } else { + Log.e(_TAG, "abandonAudioFocus failed!"); + } + } + + static boolean isAudioFocusLoss() { + return isAudioFocusLost; + } + + private static native void nativeSetAudioVolumeFactor(float focus); +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosDownloader.java b/cocos/platform/android/java/src/com/cocos/lib/CocosDownloader.java new file mode 100644 index 0000000..c5f98b8 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosDownloader.java @@ -0,0 +1,406 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import org.cocos2dx.okhttp3.Call; +import org.cocos2dx.okhttp3.Callback; +import org.cocos2dx.okhttp3.Dispatcher; +import org.cocos2dx.okhttp3.OkHttpClient; +import org.cocos2dx.okhttp3.Protocol; +import org.cocos2dx.okhttp3.Request; +import org.cocos2dx.okhttp3.Response; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +// Rename package okhttp3 to org.cocos2dx.okhttp3 +// Github repo: https://github.com/PatriceJiang/okhttp/tree/cocos2dx-rename-3.12.x +// and https://github.com/PatriceJiang/okio/tree/cocos2dx-rename-1.15.0 + +public class CocosDownloader { + + private int _id; + private OkHttpClient _httpClient = null; + private static Dispatcher dispatcher = null; + + private String _tempFileNameSuffix; + private int _countOfMaxProcessingTasks; + private ConcurrentHashMap _taskMap = new ConcurrentHashMap<>(); + private Queue _taskQueue = new LinkedList<>(); + private int _runningTaskCount = 0; + + private void onProgress(final int id, final long downloadBytes, final long downloadNow, final long downloadTotal) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + nativeOnProgress(_id, id, downloadBytes, downloadNow, downloadTotal); + } + }); + } + + private void onFinish(final int id, final int errCode, final String errStr, final byte[] data) { + Call task =_taskMap.get(id); + if (null == task) return; + _taskMap.remove(id); + _runningTaskCount -= 1; + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + nativeOnFinish(_id, id, errCode, errStr, data); + } + }); + runNextTaskIfExists(); + } + + public static CocosDownloader createDownloader(int id, int timeoutInSeconds, String tempFileSuffix, int maxProcessingTasks) { + CocosDownloader downloader = new CocosDownloader(); + downloader._id = id; + + if(dispatcher == null) { + dispatcher = new Dispatcher(); + } + + if (timeoutInSeconds > 0) { + downloader._httpClient = new OkHttpClient().newBuilder() + .dispatcher(dispatcher) + .followRedirects(true) + .followSslRedirects(true) + .connectTimeout(timeoutInSeconds, TimeUnit.SECONDS) + .protocols(Collections.singletonList(Protocol.HTTP_1_1)) + .build(); + } else { + downloader._httpClient = new OkHttpClient().newBuilder() + .dispatcher(dispatcher) + .followRedirects(true) + .followSslRedirects(true) + .protocols(Collections.singletonList(Protocol.HTTP_1_1)) + .build(); + } + + + downloader._tempFileNameSuffix = tempFileSuffix; + downloader._countOfMaxProcessingTasks = maxProcessingTasks; + return downloader; + } + + public static void createTask(final CocosDownloader downloader, int id_, String url_, String path_, String []header_) { + final int id = id_; + final String url = url_; + final String path = path_; + final String pathWithUrlHash = path + url.hashCode(); + final String tempFilePath = pathWithUrlHash + downloader._tempFileNameSuffix; + final String[] header = header_; + + Runnable taskRunnable = new Runnable() { + String domain = null; + String host = null; + File tempFile = null; + File finalFile = null; + long downloadStart = 0; + + @Override + public void run() { + Call task = null; + + do { + if (path.length() > 0) { + try { + URI uri = new URI(url); + domain = uri.getHost(); + } catch (URISyntaxException e) { + e.printStackTrace(); + break; + } catch (NullPointerException e) { + e.printStackTrace(); + break; + } + + // file task + tempFile = new File(tempFilePath); + if (tempFile.isDirectory()) break; + + File parent = tempFile.getParentFile(); + if (parent == null) { + String errStr = "Invalid path " + path + " : The current path is inaccessible."; + Log.e("CocosDownloader", errStr); + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + downloader.nativeOnFinish(downloader._id, id, 0, errStr, null); + } + }); + break; + } + if (!parent.isDirectory() && !parent.mkdirs()) break; + + finalFile = new File(path); + if (finalFile.isDirectory()) break; + long fileLen = tempFile.length(); + + host = domain.startsWith("www.") ? domain.substring(4) : domain; + if (fileLen > 0) { + SharedPreferences sharedPreferences = GlobalObject.getContext().getSharedPreferences("breakpointDownloadSupport", Context.MODE_PRIVATE); + if (sharedPreferences.contains(host) && sharedPreferences.getBoolean(host, false)) { + downloadStart = fileLen; + } else { + // Remove previous downloaded context + try { + PrintWriter writer = new PrintWriter(tempFile); + writer.print(""); + writer.close(); + } + // Not found then nothing to do + catch (FileNotFoundException e) { + } + } + } + } + + final Request.Builder builder = new Request.Builder().url(url); + for (int i = 0; i < header.length / 2; i++) { + builder.addHeader(header[i * 2], header[(i * 2) + 1]); + } + if (downloadStart > 0) { + builder.addHeader("RANGE", "bytes=" + downloadStart + "-"); + } + + final Request request = builder.build(); + task = downloader._httpClient.newCall(request); + if (null == task) { + final String errStr = "Can't create DownloadTask for " + url; + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + downloader.nativeOnFinish(downloader._id, id, 0, errStr, null); + } + }); + } else { + downloader._taskMap.put(id, task); + } + task.enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + downloader.onFinish(id, 0, e.toString(), null); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + InputStream is = null; + byte[] buf = new byte[4096]; + FileOutputStream fos = null; + + try { + + if(!(response.code() >= 200 && response.code() <= 206)) { + // it is encourage to delete the tmp file when requested range not satisfiable. + if (response.code() == 416) { + File file = new File(tempFilePath); + if (file.exists() && file.isFile()) { + file.delete(); + } + } + downloader.onFinish(id, -2, response.message(), null); + return; + } + + // save breakpointDownloadSupport Data to SharedPreferences storage + Context context = GlobalObject.getContext(); + SharedPreferences sharedPreferences = context.getSharedPreferences("breakpointDownloadSupport", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + long total = response.body().contentLength() + downloadStart; + if (path.length() > 0 && !sharedPreferences.contains(host)) { + if (total > 0) { + editor.putBoolean(host, true); + } else { + editor.putBoolean(host, false); + } + editor.commit(); + } + + long current = downloadStart; + is = response.body().byteStream(); + + if (path.length() > 0) { + if (downloadStart > 0) { + fos = new FileOutputStream(tempFile, true); + } else { + fos = new FileOutputStream(tempFile, false); + } + + int len; + while ((len = is.read(buf)) != -1) { + current += len; + fos.write(buf, 0, len); + downloader.onProgress(id, len, current, total); + } + fos.flush(); + + String errStr = null; + do { + // rename temp file to final file, if final file exist, remove it + if (finalFile.exists()) { + if (finalFile.isDirectory()) { + break; + } + if (!finalFile.delete()) { + errStr = "Can't remove old file:" + finalFile.getAbsolutePath(); + break; + } + } + tempFile.renameTo(finalFile); + } while (false); + + if (errStr == null) { + downloader.onFinish(id, 0, null, null); + downloader.runNextTaskIfExists(); + } else { + downloader.onFinish(id, 0, errStr, null); + } + if (sharedPreferences.contains(host)) { + editor.remove(host); + editor.commit(); + } + } else { + // non-file + ByteArrayOutputStream buffer; + if(total > 0) { + buffer = new ByteArrayOutputStream((int) total); + } else { + buffer = new ByteArrayOutputStream(4096); + } + + int len; + while ((len = is.read(buf)) != -1) { + current += len; + buffer.write(buf, 0, len); + downloader.onProgress(id, len, current, total); + } + downloader.onFinish(id, 0, null, buffer.toByteArray()); + downloader.runNextTaskIfExists(); + } + } catch (IOException e) { + e.printStackTrace(); + downloader.onFinish(id, 0, e.toString(), null); + } finally { + try { + if (is != null) { + is.close(); + } + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + Log.e("CocosDownloader", e.toString()); + } + } + } + }); + } while (false); + } + }; + downloader.enqueueTask(taskRunnable); + } + + public static void abort(final CocosDownloader downloader, final int id) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + Iterator iter = downloader._taskMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter.next(); + Object key = entry.getKey(); + Call task = (Call) entry.getValue(); + if (null != task && Integer.parseInt(key.toString()) == id) { + task.cancel(); + downloader._taskMap.remove(id); + downloader.runNextTaskIfExists(); + break; + } + } + } + }); + } + + public static void cancelAllRequests(final CocosDownloader downloader) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + for (Object o : downloader._taskMap.entrySet()) { + Map.Entry entry = (Map.Entry) o; + Call task = (Call) entry.getValue(); + if (null != task) { + task.cancel(); + } + } + } + }); + } + + + private void enqueueTask(Runnable taskRunnable) { + synchronized (_taskQueue) { + if (_runningTaskCount < _countOfMaxProcessingTasks) { + GlobalObject.runOnUiThread(taskRunnable); + _runningTaskCount++; + } else { + _taskQueue.add(taskRunnable); + } + } + } + + private void runNextTaskIfExists() { + synchronized (_taskQueue) { + while (_runningTaskCount < _countOfMaxProcessingTasks && + CocosDownloader.this._taskQueue.size() > 0) { + + Runnable taskRunnable = CocosDownloader.this._taskQueue.poll(); + GlobalObject.runOnUiThread(taskRunnable); + _runningTaskCount += 1; + } + } + } + + native void nativeOnProgress(int id, int taskId, long dl, long dlnow, long dltotal); + native void nativeOnFinish(int id, int taskId, int errCode, String errStr, final byte[] data); +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosEditBoxActivity.java b/cocos/platform/android/java/src/com/cocos/lib/CocosEditBoxActivity.java new file mode 100644 index 0000000..c992433 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosEditBoxActivity.java @@ -0,0 +1,486 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.StateListDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.os.Build; +import android.os.Bundle; +import android.text.Editable; +import android.text.InputFilter; +import android.text.InputType; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +public class CocosEditBoxActivity extends Activity { + + // a color of dark green, was used for confirm button background + private static final int DARK_GREEN = Color.parseColor("#1fa014"); + private static final int DARK_GREEN_PRESS = Color.parseColor("#008e26"); + + private static CocosEditBoxActivity sThis = null; + private Cocos2dxEditText mEditText = null; + private Button mButton = null; + private String mButtonTitle = null; + private boolean mConfirmHold = true; + private int mEditTextID = 1; + private int mButtonLayoutID = 2; + + /*************************************************************************************** + Inner class. + **************************************************************************************/ + class Cocos2dxEditText extends EditText { + private final String TAG = "Cocos2dxEditBox"; + private boolean mIsMultiLine = false; + private TextWatcher mTextWatcher = null; + private boolean keyboardVisible = false; + private int mScreenHeight; + private boolean mCheckKeyboardShowNormally = false; + + public Cocos2dxEditText(Activity context){ + super(context); + this.setTextColor(Color.BLACK); + mScreenHeight = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)). + getDefaultDisplay().getHeight(); + + mTextWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + // Pass text to c++. + CocosEditBoxActivity.this.onKeyboardInput(s.toString()); + } + }; + registKeyboardVisible(); + } + + /*************************************************************************************** + Public functions. + **************************************************************************************/ + + public void show(String defaultValue, int maxLength, boolean isMultiline, boolean confirmHold, String confirmType, String inputType) { + mIsMultiLine = isMultiline; + this.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength) }); + this.setText(defaultValue); + if (this.getText().length() >= defaultValue.length()) { + this.setSelection(defaultValue.length()); + } else { + this.setSelection(this.getText().length()); + } + this.setConfirmType(confirmType); + this.setInputType(inputType, mIsMultiLine); + this.setVisibility(View.VISIBLE); + + // Open soft keyboard manually. Should request focus to open soft keyboard. + this.requestFocus(); + + this.addListeners(); + } + + public void hide() { + mEditText.setVisibility(View.INVISIBLE); + this.removeListeners(); + } + + /*************************************************************************************** + Private functions. + **************************************************************************************/ + + private void setConfirmType(final String confirmType) { + if (confirmType.contentEquals("done")) { + this.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + mButtonTitle = getResources().getString(R.string.done); + } else if (confirmType.contentEquals("next")) { + this.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + mButtonTitle = getResources().getString(R.string.next); + } else if (confirmType.contentEquals("search")) { + this.setImeOptions(EditorInfo.IME_ACTION_SEARCH | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + mButtonTitle = getResources().getString(R.string.search); + } else if (confirmType.contentEquals("go")) { + this.setImeOptions(EditorInfo.IME_ACTION_GO | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + mButtonTitle = getResources().getString(R.string.go); + } else if (confirmType.contentEquals("send")) { + this.setImeOptions(EditorInfo.IME_ACTION_SEND | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + mButtonTitle = getResources().getString(R.string.send); + } else{ + mButtonTitle = null; + Log.e(TAG, "unknown confirm type " + confirmType); + } + } + + private void setInputType(final String inputType, boolean isMultiLine){ + mCheckKeyboardShowNormally = false; + if (inputType.contentEquals("text")) { + if (isMultiLine) + this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + else + this.setInputType(InputType.TYPE_CLASS_TEXT); + } + else if (inputType.contentEquals("email")) + this.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + else if (inputType.contentEquals("number")) + this.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED); + else if (inputType.contentEquals("phone")) + this.setInputType(InputType.TYPE_CLASS_PHONE); + else if (inputType.contentEquals("password")) { + if (Build.BRAND.equalsIgnoreCase("oppo")) { + mCheckKeyboardShowNormally = true; + } + this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } + else + Log.e(TAG, "unknown input type " + inputType); + } + + private void addListeners() { + + this.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (! mIsMultiLine) { + CocosEditBoxActivity.this.hide(); + } + + return false; // pass on to other listeners. + } + }); + + + this.addTextChangedListener(mTextWatcher); + } + + private void removeListeners() { + this.setOnEditorActionListener(null); + this.removeTextChangedListener(mTextWatcher); + } + + private boolean isSystemAdjustUIWhenPopKeyboard(int bottom) { + int bottomOffset = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + bottomOffset = getWindow().getDecorView().getRootWindowInsets().getSystemWindowInsetBottom(); + } + // view will be scrolled to the target position by system, + if (Math.abs(bottom - bottomOffset) < 10) { + return true; + } + return false; + } + + private void registKeyboardVisible() { + getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + Rect r = new Rect(); + getWindowVisibleDisplayFrame(r); + int heightDiff = getRootView().getHeight() - (r.bottom - r.top); + // if more than a quarter of the screen, its probably a keyboard + if (heightDiff > mScreenHeight/4) { + if (!keyboardVisible) { + keyboardVisible = true; + } + if (!isSystemAdjustUIWhenPopKeyboard(heightDiff)) { + getRootView().scrollTo(0, heightDiff); + } + } else { + getRootView().scrollTo(0, 0); + if (mCheckKeyboardShowNormally && !keyboardVisible) { + Toast.makeText(CocosEditBoxActivity.this, R.string.tip_disable_safe_input_type, Toast.LENGTH_SHORT).show(); + } + if (keyboardVisible) { + keyboardVisible = false; + CocosEditBoxActivity.this.hide(); + } + } + } + }); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + CocosEditBoxActivity.sThis = this; + + ViewGroup.LayoutParams frameLayoutParams = + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + RelativeLayout frameLayout = new RelativeLayout(this); + frameLayout.setLayoutParams(frameLayoutParams); + frameLayout.setClickable(true); + frameLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + CocosEditBoxActivity.this.hide(); + } + }); + setContentView(frameLayout); + + this.addItems(frameLayout); + + Intent intent = getIntent(); + Bundle extras = null; + if (null != intent) { + extras = intent.getExtras(); + } + if(extras == null){ + show("", + 10, + false, + false, + "done", + "text" + ); + } else { + show(extras.getString("defaultValue"), + extras.getInt("maxLength"), + extras.getBoolean("isMultiline"), + extras.getBoolean("confirmHold"), + extras.getString("confirmType"), + extras.getString("inputType")); + } + } + + /*************************************************************************************** + Public functions. + **************************************************************************************/ + + /*************************************************************************************** + Private functions. + **************************************************************************************/ + private void addItems(RelativeLayout layout) { + RelativeLayout myLayout = new RelativeLayout(this); + myLayout.setBackgroundColor(Color.argb(255, 244, 244, 244)); + + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + layout.addView(myLayout, layoutParams); + + this.addEditText(myLayout); + this.addButton(myLayout); + } + + private int dpToPixel(int dp) { + final float scale = getResources().getDisplayMetrics().density; + int px = (int) (dp * scale + 0.5f); + return px; + } + private void addEditText(RelativeLayout layout) { + mEditText = new Cocos2dxEditText(this); + mEditText.setVisibility(View.INVISIBLE); + mEditText.setGravity(Gravity.CENTER_VERTICAL); + mEditText.setBackground(getRoundRectShape(18, Color.WHITE, Color.WHITE)); + mEditText.setId(mEditTextID); + int bottomPadding = dpToPixel(4); + int leftPadding = dpToPixel(3); + mEditText.setPadding(leftPadding,bottomPadding,leftPadding,bottomPadding); + + RelativeLayout.LayoutParams editParams = new RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + editParams.addRule(RelativeLayout.CENTER_VERTICAL); + editParams.addRule(RelativeLayout.LEFT_OF, mButtonLayoutID); + int bottomMargin = dpToPixel(5); + int leftMargin = dpToPixel(4); + editParams.setMargins(leftMargin, bottomMargin, bottomMargin, bottomMargin); + layout.addView(mEditText, editParams); + } + + private void addButton(RelativeLayout layout) { + mButton = new Button(this); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mButton.setTextColor(Color.WHITE); + mButton.setTextSize(16); + mButton.setBackground(getRoundRectShape(18, DARK_GREEN, DARK_GREEN_PRESS)); + int paddingLeft = dpToPixel(5); + mButton.setPadding(paddingLeft,0,paddingLeft,0); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + layoutParams.addRule(RelativeLayout.ALIGN_TOP, mEditTextID); + layoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mEditTextID); + layoutParams.rightMargin = dpToPixel(4); + layout.addView(mButton, layoutParams); + mButton.setGravity(Gravity.CENTER); + mButton.setId(mButtonLayoutID); + + mButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CocosEditBoxActivity.this.onKeyboardConfirm(mEditText.getText().toString()); + + if (!CocosEditBoxActivity.this.mConfirmHold) + CocosEditBoxActivity.this.hide(); + } + }); + } + + private Drawable getRoundRectShape(int radius, int normalColor, int pressColor) { + float[] outerRadii = new float[]{radius, radius, radius, radius, radius, radius, radius, radius}; + RoundRectShape roundRectShape = new RoundRectShape(outerRadii, null, null); + ShapeDrawable shapeDrawableNormal = new ShapeDrawable(); + shapeDrawableNormal.setShape(roundRectShape); + shapeDrawableNormal.getPaint().setStyle(Paint.Style.FILL); + shapeDrawableNormal.getPaint().setColor(normalColor); + ShapeDrawable shapeDrawablePress = new ShapeDrawable(); + shapeDrawablePress.setShape(roundRectShape); + shapeDrawablePress.getPaint().setStyle(Paint.Style.FILL); + shapeDrawablePress.getPaint().setColor(pressColor); + StateListDrawable drawable = new StateListDrawable(); + drawable.addState(new int[]{android.R.attr.state_pressed}, shapeDrawablePress); + drawable.addState(new int[]{}, shapeDrawableNormal); + return drawable; + } + + + private void hide() { + Utils.hideVirtualButton(); + this.closeKeyboard(); + finish(); + } + + public void show(String defaultValue, int maxLength, boolean isMultiline, boolean confirmHold, String confirmType, String inputType) { + mConfirmHold = confirmHold; + mEditText.show(defaultValue, maxLength, isMultiline, confirmHold, confirmType, inputType); + mButton.setText(mButtonTitle); + if (TextUtils.isEmpty(mButtonTitle)) { + mButton.setVisibility(View.INVISIBLE); + } else { + mButton.setVisibility(View.VISIBLE); + } + + this.openKeyboard(); + } + + private void closeKeyboard() { + InputMethodManager imm = (InputMethodManager) getSystemService(this.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + + this.onKeyboardComplete(mEditText.getText().toString()); + } + + private void openKeyboard() { + InputMethodManager imm = (InputMethodManager) getSystemService(this.INPUT_METHOD_SERVICE); + imm.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT); + } + + /*************************************************************************************** + Functions invoked by CPP. + **************************************************************************************/ + + private static void showNative(String defaultValue, int maxLength, boolean isMultiline, boolean confirmHold, String confirmType, String inputType) { + + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + Intent i = new Intent(GlobalObject.getActivity(), CocosEditBoxActivity.class); + i.putExtra("defaultValue", defaultValue); + i.putExtra("maxLength", maxLength); + i.putExtra("isMultiline", isMultiline); + i.putExtra("confirmHold", confirmHold); + i.putExtra("confirmType", confirmType); + i.putExtra("inputType", inputType); + GlobalObject.getActivity().startActivity(i); + } + }); + } + + private static void hideNative() { + if (null != CocosEditBoxActivity.sThis) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosEditBoxActivity.sThis.hide(); + } + }); + } + } + + /*************************************************************************************** + Native functions invoked by UI. + **************************************************************************************/ + private void onKeyboardInput(String text) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosEditBoxActivity.onKeyboardInputNative(text); + } + }); + } + + private void onKeyboardComplete(String text) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosEditBoxActivity.onKeyboardCompleteNative(text); + } + }); + } + + private void onKeyboardConfirm(String text) { + CocosHelper.runOnGameThread(new Runnable() { + @Override + public void run() { + CocosEditBoxActivity.onKeyboardConfirmNative(text); + } + }); + } + + private static native void onKeyboardInputNative(String text); + private static native void onKeyboardCompleteNative(String text); + private static native void onKeyboardConfirmNative(String text); +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosHandler.java b/cocos/platform/android/java/src/com/cocos/lib/CocosHandler.java new file mode 100644 index 0000000..1c37198 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosHandler.java @@ -0,0 +1,105 @@ +/**************************************************************************** +Copyright (c) 2010-2013 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.Handler; +import android.os.Message; +import android.app.Activity; + +import java.lang.ref.WeakReference; + +public class CocosHandler extends Handler { + // =========================================================== + // Constants + // =========================================================== + public final static int HANDLER_SHOW_DIALOG = 1; + + // =========================================================== + // Fields + // =========================================================== + private WeakReference mActivity; + + // =========================================================== + // Constructors + // =========================================================== + public CocosHandler(Activity activity) { + this.mActivity = new WeakReference(activity); + } + + // =========================================================== + // Getter & Setter + // =========================================================== + + // =========================================================== + // Methods for/from SuperClass/Interfaces + // =========================================================== + + // =========================================================== + // Methods + // =========================================================== + + public void handleMessage(Message msg) { + switch (msg.what) { + case CocosHandler.HANDLER_SHOW_DIALOG: + showDialog(msg); + break; + } + } + + private void showDialog(Message msg) { + Activity theActivity = this.mActivity.get(); + DialogMessage dialogMessage = (DialogMessage)msg.obj; + new AlertDialog.Builder(theActivity) + .setTitle(dialogMessage.title) + .setMessage(dialogMessage.message) + .setPositiveButton("Ok", + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + // REFINE: Auto-generated method stub + + } + }).create().show(); + } + + // =========================================================== + // Inner and Anonymous Classes + // =========================================================== + + public static class DialogMessage { + public String title; + public String message; + + public DialogMessage(String title, String message) { + this.title = title; + this.message = message; + } + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosHelper.java b/cocos/platform/android/java/src/com/cocos/lib/CocosHelper.java new file mode 100644 index 0000000..382b24d --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosHelper.java @@ -0,0 +1,435 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetFileDescriptor; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.os.BatteryManager; +import android.os.Build; +import android.os.Environment; +import android.os.LocaleList; +import android.os.ParcelFileDescriptor; +import android.os.Vibrator; +import android.util.Log; +import android.view.Display; +import android.view.Surface; +import android.view.WindowInsets; +import android.view.WindowManager; + +import com.android.vending.expansion.zipfile.APKExpansionSupport; +import com.android.vending.expansion.zipfile.ZipResourceFile; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Queue; +import java.util.concurrent.locks.ReentrantLock; + + +public class CocosHelper { + // =========================================================== + // Constants + // =========================================================== + private static final String TAG = CocosHelper.class.getSimpleName(); + + // =========================================================== + // Fields + // =========================================================== + + private static Vibrator sVibrateService; + private static BatteryReceiver sBatteryReceiver = new BatteryReceiver(); + + public static final int NETWORK_TYPE_NONE = 0; + public static final int NETWORK_TYPE_LAN = 1; + public static final int NETWORK_TYPE_WWAN = 2; + + // The absolute path to the OBB if it exists. + private static String sObbFilePath = ""; + + // The OBB file + private static ZipResourceFile sOBBFile = null; + + static class LockedTaskQ { + private final Object readMtx = new Object(); + private Queue sTaskQ = new LinkedList<>(); + public void addTask(Runnable runnable) { + synchronized (readMtx) { + sTaskQ.add(runnable); + } + } + public void runTasks(){ + Queue tmp; + synchronized (readMtx) { + tmp = sTaskQ; + sTaskQ = new LinkedList<>(); + } + for(Runnable runnable : tmp){ + runnable.run(); + } + } + } + + private static LockedTaskQ sTaskQOnGameThread = new LockedTaskQ(); + private static LockedTaskQ sForegroundTaskQOnGameThread = new LockedTaskQ(); + /** + * Battery receiver to getting battery level. + */ + static class BatteryReceiver extends BroadcastReceiver { + public float sBatteryLevel = 0.0f; + + @Override + public void onReceive(Context context, Intent intent) { + setBatteryLevelByIntent(intent); + } + + public void setBatteryLevelByIntent(Intent intent) { + if (null != intent) { + int current = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); + int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1); + float level = current * 1.0f / total; + // clamp to 0~1 + sBatteryLevel = Math.min(Math.max(level, 0.0f), 1.0f); + } + } + } + + static void registerBatteryLevelReceiver(Context context) { + Intent intent = context.registerReceiver(sBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + sBatteryReceiver.setBatteryLevelByIntent(intent); + } + + static void unregisterBatteryLevelReceiver(Context context) { + context.unregisterReceiver(sBatteryReceiver); + } + + //Run on game thread forever, no matter foreground or background + public static void runOnGameThread(final Runnable runnable) { + sTaskQOnGameThread.addTask(runnable); + } + + static void flushTasksOnGameThread() { + sTaskQOnGameThread.runTasks(); + } + public static void runOnGameThreadAtForeground(final Runnable runnable) { + sForegroundTaskQOnGameThread.addTask(runnable); + } + + static void flushTasksOnGameThreadAtForeground() { + sForegroundTaskQOnGameThread.runTasks(); + } + + public static int getNetworkType() { + int status = NETWORK_TYPE_NONE; + NetworkInfo networkInfo; + try { + ConnectivityManager connMgr = (ConnectivityManager) GlobalObject.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); + networkInfo = connMgr.getActiveNetworkInfo(); + } catch (Exception e) { + e.printStackTrace(); + return status; + } + if (networkInfo == null) { + return status; + } + int nType = networkInfo.getType(); + if (nType == ConnectivityManager.TYPE_MOBILE) { + status = NETWORK_TYPE_WWAN; + } else if (nType == ConnectivityManager.TYPE_WIFI) { + status = NETWORK_TYPE_LAN; + } + return status; + } + + // =========================================================== + // Constructors + // =========================================================== + + private static boolean sInited = false; + + public static void init() { + if (!sInited) { + CocosHelper.sVibrateService = (Vibrator) GlobalObject.getContext().getSystemService(Context.VIBRATOR_SERVICE); + CocosHelper.initObbFilePath(); + CocosHelper.initializeOBBFile(); + + sInited = true; + } + } + + public static float getBatteryLevel() { + return sBatteryReceiver.sBatteryLevel; + } + + public static String getObbFilePath() { + return CocosHelper.sObbFilePath; + } + + public static String getWritablePath() { + return GlobalObject.getContext().getFilesDir().getAbsolutePath(); + } + + public static String getCurrentLanguage() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return LocaleList.getDefault().get(0).getLanguage(); + } else { + return Locale.getDefault().getLanguage(); + } + } + + public static String getCurrentLanguageCode() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return LocaleList.getDefault().get(0).toString(); + } else { + return Locale.getDefault().toString(); + } + } + + public static String getDeviceModel() { + return Build.MODEL; + } + + public static String getSystemVersion() { + return Build.VERSION.RELEASE; + } + + public static void vibrate(float duration) { + try { + if (sVibrateService != null && sVibrateService.hasVibrator()) { + if (android.os.Build.VERSION.SDK_INT >= 26) { + Class vibrationEffectClass = Class.forName("android.os.VibrationEffect"); + if (vibrationEffectClass != null) { + final int DEFAULT_AMPLITUDE = CocosReflectionHelper.getConstantValue(vibrationEffectClass, + "DEFAULT_AMPLITUDE"); + //VibrationEffect.createOneShot(long milliseconds, int amplitude) + final Method method = vibrationEffectClass.getMethod("createOneShot", + new Class[]{Long.TYPE, Integer.TYPE}); + Class type = method.getReturnType(); + + Object effect = method.invoke(vibrationEffectClass, + new Object[]{(long) (duration * 1000), DEFAULT_AMPLITUDE}); + //sVibrateService.vibrate(VibrationEffect effect); + CocosReflectionHelper.invokeInstanceMethod(sVibrateService, "vibrate", + new Class[]{type}, new Object[]{(effect)}); + } + } else { + sVibrateService.vibrate((long) (duration * 1000)); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static boolean openURL(String url) { + if (GlobalObject.getActivity() == null) { + Log.e(TAG, "activity is null"); + return false; + } + + boolean ret = false; + try { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + GlobalObject.getActivity().startActivity(i); + ret = true; + } catch (Exception e) { + } + return ret; + } + + public static void copyTextToClipboard(final String text) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + ClipboardManager myClipboard = (ClipboardManager) GlobalObject.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData myClip = ClipData.newPlainText("text", text); + myClipboard.setPrimaryClip(myClip); + } + }); + } + + public static long[] getObbAssetFileDescriptor(final String path) { + long[] array = new long[3]; + if (CocosHelper.sOBBFile != null) { + AssetFileDescriptor descriptor = CocosHelper.sOBBFile.getAssetFileDescriptor(path); + if (descriptor != null) { + try { + ParcelFileDescriptor parcel = descriptor.getParcelFileDescriptor(); + Method method = parcel.getClass().getMethod("getFd", new Class[]{}); + array[0] = (Integer) method.invoke(parcel); + array[1] = descriptor.getStartOffset(); + array[2] = descriptor.getLength(); + } catch (NoSuchMethodException e) { + Log.e(CocosHelper.TAG, "Accessing file descriptor directly from the OBB is only supported from Android 3.1 (API level 12) and above."); + } catch (IllegalAccessException e) { + Log.e(CocosHelper.TAG, e.toString()); + } catch (InvocationTargetException e) { + Log.e(CocosHelper.TAG, e.toString()); + } + } + } + return array; + } + + public static int getDeviceRotation() { + try { + Display display = ((WindowManager) GlobalObject.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + return display.getRotation(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + return Surface.ROTATION_0; + } + + // =========================================================== + // Private functions. + // =========================================================== + + // Initialize asset path: + // - absolute path to the OBB if it exists, + // - else empty string. + private static void initObbFilePath() { + int versionCode = 1; + final ApplicationInfo applicationInfo = GlobalObject.getContext().getApplicationInfo(); + try { + versionCode = GlobalObject.getContext().getPackageManager().getPackageInfo(applicationInfo.packageName, 0).versionCode; + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + String pathToOBB = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/obb/" + applicationInfo.packageName + "/main." + versionCode + "." + applicationInfo.packageName + ".obb"; + File obbFile = new File(pathToOBB); + if (obbFile.exists()) + CocosHelper.sObbFilePath = pathToOBB; + } + + private static void initializeOBBFile() { + int versionCode = 1; + final ApplicationInfo applicationInfo = GlobalObject.getContext().getApplicationInfo(); + try { + versionCode = GlobalObject.getContext().getPackageManager().getPackageInfo(applicationInfo.packageName, 0).versionCode; + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + try { + CocosHelper.sOBBFile = APKExpansionSupport.getAPKExpansionZipFile(GlobalObject.getContext(), versionCode, 0); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static float[] getSafeArea() { + if (GlobalObject.getActivity() == null) { + Log.e(TAG, "activity is null"); + return new float[]{0, 0, 0, 0}; + } + if (android.os.Build.VERSION.SDK_INT >= 28) { + do { + Object windowInsectObj = GlobalObject.getActivity().getWindow().getDecorView().getRootWindowInsets(); + + if (windowInsectObj == null) break; + + Class windowInsets = WindowInsets.class; + try { + Method wiGetDisplayCutout = windowInsets.getMethod("getDisplayCutout"); + Object cutout = wiGetDisplayCutout.invoke(windowInsectObj); + + if (cutout == null) break; + + Class displayCutout = cutout.getClass(); + Method dcGetLeft = displayCutout.getMethod("getSafeInsetLeft"); + Method dcGetRight = displayCutout.getMethod("getSafeInsetRight"); + Method dcGetBottom = displayCutout.getMethod("getSafeInsetBottom"); + Method dcGetTop = displayCutout.getMethod("getSafeInsetTop"); + + if (dcGetLeft != null && dcGetRight != null && dcGetBottom != null && dcGetTop != null) { + int left = (Integer) dcGetLeft.invoke(cutout); + int right = (Integer) dcGetRight.invoke(cutout); + int top = (Integer) dcGetTop.invoke(cutout); + int bottom = (Integer) dcGetBottom.invoke(cutout); + return new float[]{top, left, bottom, right}; + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } while (false); + } + return new float[]{0, 0, 0, 0}; + } + public static void finishActivity() { + if (GlobalObject.getActivity() == null) { + Log.e(TAG, "activity is null"); + return; + } + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + GlobalObject.getActivity().finish(); + } + }); + } + public static void setKeepScreenOn(boolean keepScreenOn) { + if (GlobalObject.getActivity() == null) { + Log.e(TAG, "activity is null"); + return; + } + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + if (keepScreenOn) { + GlobalObject.getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + GlobalObject.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + }); + } + public static boolean supportHPE() { + PackageManager pm = GlobalObject.getContext().getPackageManager(); + return pm.hasSystemFeature("com.google.android.play.feature.HPE_EXPERIENCE"); + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosHttpURLConnection.java b/cocos/platform/android/java/src/com/cocos/lib/CocosHttpURLConnection.java new file mode 100644 index 0000000..2212986 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosHttpURLConnection.java @@ -0,0 +1,414 @@ +/**************************************************************************** +Copyright (c) 2010-2014 cocos2d-x.org +Copyright (c) 2014-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URL; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +public class CocosHttpURLConnection +{ + private static String TAG = "CocosHttpURLConnection"; + private static final String POST_METHOD = "POST" ; + private static final String PUT_METHOD = "PUT" ; + private static final String PATCH_METHOD = "PATCH" ; + + static HttpURLConnection createHttpURLConnection(String linkURL) { + URL url; + HttpURLConnection urlConnection; + try { + url = new URL(linkURL); + urlConnection = (HttpURLConnection) url.openConnection(); + //Accept-Encoding + urlConnection.setRequestProperty("Accept-Encoding", "identity"); + urlConnection.setDoInput(true); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "createHttpURLConnection:" + e.toString()); + return null; + } + + return urlConnection; + } + + static void setReadAndConnectTimeout(HttpURLConnection urlConnection, int readMiliseconds, int connectMiliseconds) { + urlConnection.setReadTimeout(readMiliseconds); + urlConnection.setConnectTimeout(connectMiliseconds); + } + + static void setRequestMethod(HttpURLConnection urlConnection, String method){ + try { + urlConnection.setRequestMethod(method); + if(method.equalsIgnoreCase(POST_METHOD) || method.equalsIgnoreCase(PUT_METHOD) || method.equalsIgnoreCase(PATCH_METHOD)) { + urlConnection.setDoOutput(true); + } + } catch (ProtocolException e) { + Log.e(TAG, "setRequestMethod:" + e.toString()); + } + + } + + static void setVerifySSL(HttpURLConnection urlConnection, String sslFilename) { + if(!(urlConnection instanceof HttpsURLConnection)) + return; + + + HttpsURLConnection httpsURLConnection = (HttpsURLConnection)urlConnection; + + try { + InputStream caInput = null; + if (sslFilename.startsWith("/")) { + caInput = new BufferedInputStream(new FileInputStream(sslFilename)); + }else { + String assetString = "assets/"; + String assetsfilenameString = sslFilename.substring(assetString.length()); + caInput = new BufferedInputStream(GlobalObject.getContext().getAssets().open(assetsfilenameString)); + } + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate ca; + ca = cf.generateCertificate(caInput); + System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); + caInput.close(); + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca", ca); + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + + // Create an SSLContext that uses our TrustManager + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + + httpsURLConnection.setSSLSocketFactory(context.getSocketFactory()); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "setVerifySSL:" + e.toString()); + } + } + + //Add header + static void addRequestHeader(HttpURLConnection urlConnection, String key, String value) { + urlConnection.setRequestProperty(key, value); + } + + static int connect(HttpURLConnection http) { + int suc = 0; + + try { + http.connect(); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "connect" + e.toString()); + suc = 1; + } + + return suc; + } + + static void disconnect(HttpURLConnection http) { + http.disconnect(); + } + + static void sendRequest(HttpURLConnection http, byte[] byteArray) { + try { + OutputStream out = http.getOutputStream(); + if(null != byteArray) { + out.write(byteArray); + out.flush(); + } + out.close(); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "sendRequest:" + e.toString()); + } + } + + static String getResponseHeaders(HttpURLConnection http) { + Map> headers = http.getHeaderFields(); + if (null == headers) { + return null; + } + + String header = ""; + + for (Entry> entry: headers.entrySet()) { + String key = entry.getKey(); + if (null == key) { + header += listToString(entry.getValue(), ",") + "\n"; + } else { + header += key + ":" + listToString(entry.getValue(), ",") + "\n"; + } + } + + return header; + } + + static String getResponseHeaderByIdx(HttpURLConnection http, int idx) { + Map> headers = http.getHeaderFields(); + if (null == headers) { + return null; + } + + String header = null; + + int counter = 0; + for (Entry> entry: headers.entrySet()) { + if (counter == idx) { + String key = entry.getKey(); + if (null == key) { + header = listToString(entry.getValue(), ",") + "\n"; + } else { + header = key + ":" + listToString(entry.getValue(), ",") + "\n"; + } + break; + } + counter++; + } + + return header; + } + + static String getResponseHeaderByKey(HttpURLConnection http, String key) { + if (null == key) { + return null; + } + + Map> headers = http.getHeaderFields(); + if (null == headers) { + return null; + } + + String header = null; + + for (Entry> entry: headers.entrySet()) { + if (key.equalsIgnoreCase(entry.getKey())) { + if ("set-cookie".equalsIgnoreCase(key)) { + header = combinCookies(entry.getValue(), http.getURL().getHost()); + } else { + header = listToString(entry.getValue(), ","); + } + break; + } + } + + return header; + } + + static int getResponseHeaderByKeyInt(HttpURLConnection http, String key) { + String value = http.getHeaderField(key); + + if (null == value) { + return 0; + } else { + return Integer.parseInt(value); + } + } + + static byte[] getResponseContent(HttpURLConnection http) { + InputStream in; + try { + in = http.getInputStream(); + String contentEncoding = http.getContentEncoding(); + if (contentEncoding != null) { + if(contentEncoding.equalsIgnoreCase("gzip")){ + in = new GZIPInputStream(http.getInputStream()); //reads 2 bytes to determine GZIP stream! + } + else if(contentEncoding.equalsIgnoreCase("deflate")){ + in = new InflaterInputStream(http.getInputStream()); + } + } + } catch (IOException e) { + in = http.getErrorStream(); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "1 getResponseContent: " + e.toString()); + return null; + } + + try { + byte[] buffer = new byte[1024]; + int size = 0; + ByteArrayOutputStream bytestream = new ByteArrayOutputStream(); + while((size = in.read(buffer, 0 , 1024)) != -1) + { + bytestream.write(buffer, 0, size); + } + byte retbuffer[] = bytestream.toByteArray(); + bytestream.close(); + return retbuffer; + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "2 getResponseContent:" + e.toString()); + } + + return null; + } + + static int getResponseCode(HttpURLConnection http) { + int code = 0; + try { + code = http.getResponseCode(); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "getResponseCode:" + e.toString()); + } + return code; + } + + static String getResponseMessage(HttpURLConnection http) { + String msg; + try { + msg = http.getResponseMessage(); + } catch (Exception e) { + e.printStackTrace(); + msg = e.toString(); + Log.e(TAG, "getResponseMessage: " + msg); + } + + return msg; + } + + public static String listToString(List list, String strInterVal) { + if (list == null) { + return null; + } + StringBuilder result = new StringBuilder(); + boolean flag = false; + for (String str : list) { + if (flag) { + result.append(strInterVal); + } + if (null == str) { + str = ""; + } + result.append(str); + flag = true; + } + return result.toString(); + } + + public static String combinCookies(List list, String hostDomain) { + StringBuilder sbCookies = new StringBuilder(); + String domain = hostDomain; + String tailmatch = "FALSE"; + String path = "/"; + String secure = "FALSE"; + String key = null; + String value = null; + String expires = null; + for (String str : list) { + String[] parts = str.split(";"); + for (String part : parts) { + int firstIndex = part.indexOf("="); + if (-1 == firstIndex) + continue; + + String[] item = {part.substring(0, firstIndex), part.substring(firstIndex + 1)}; + if ("expires".equalsIgnoreCase(item[0].trim())) { + expires = str2Seconds(item[1].trim()); + } else if("path".equalsIgnoreCase(item[0].trim())) { + path = item[1]; + } else if("secure".equalsIgnoreCase(item[0].trim())) { + secure = item[1]; + } else if("domain".equalsIgnoreCase(item[0].trim())) { + domain = item[1]; + } else if("version".equalsIgnoreCase(item[0].trim()) || "max-age".equalsIgnoreCase(item[0].trim())) { + //do nothing + } else { + key = item[0]; + value = item[1]; + } + } + + if (null == domain) { + domain = "none"; + } + + sbCookies.append(domain); + sbCookies.append('\t'); + sbCookies.append(tailmatch); //access + sbCookies.append('\t'); + sbCookies.append(path); //path + sbCookies.append('\t'); + sbCookies.append(secure); //secure + sbCookies.append('\t'); + sbCookies.append(expires); //expires + sbCookies.append("\t"); + sbCookies.append(key); //key + sbCookies.append("\t"); + sbCookies.append(value); //value + sbCookies.append('\n'); + } + + return sbCookies.toString(); + } + + private static String str2Seconds(String strTime) { + Calendar c = Calendar.getInstance(); + long milliseconds = 0; + + try { + c.setTime(new SimpleDateFormat("EEE, dd-MMM-yy hh:mm:ss zzz", Locale.US).parse(strTime)); + milliseconds = c.getTimeInMillis() / 1000; + } catch (ParseException e) { + Log.e(TAG, "str2Seconds: " + e.toString()); + } + + return Long.toString(milliseconds); + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosJavascriptJavaBridge.java b/cocos/platform/android/java/src/com/cocos/lib/CocosJavascriptJavaBridge.java new file mode 100644 index 0000000..5415417 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosJavascriptJavaBridge.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013-2016 Chukong Technologies Inc. + * Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.cocos.lib; + +public class CocosJavascriptJavaBridge { + public static native int evalString(String value); +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosKeyCodeHandler.java b/cocos/platform/android/java/src/com/cocos/lib/CocosKeyCodeHandler.java new file mode 100644 index 0000000..834d117 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosKeyCodeHandler.java @@ -0,0 +1,87 @@ +/**************************************************************************** + Copyright (c) 2010-2013 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import android.view.KeyEvent; + +public class CocosKeyCodeHandler { + private CocosActivity mAct; + + public native void handleKeyDown(final int keyCode); + + public native void handleKeyUp(final int keyCode); + + public CocosKeyCodeHandler(CocosActivity act) { + mAct = act; + } + + public boolean onKeyDown(final int keyCode, final KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: +// CocosVideoHelper.mVideoHandler.sendEmptyMessage(CocosVideoHelper.KeyEventBack); + case KeyEvent.KEYCODE_MENU: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_DPAD_CENTER: + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleKeyDown(keyCode); + } + }); + return true; + default: + return false; + } + } + + public boolean onKeyUp(final int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_MENU: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_DPAD_CENTER: + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleKeyUp(keyCode); + } + }); + return true; + default: + return false; + } + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosLocalStorage.java b/cocos/platform/android/java/src/com/cocos/lib/CocosLocalStorage.java new file mode 100644 index 0000000..79fdb0d --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosLocalStorage.java @@ -0,0 +1,170 @@ +/**************************************************************************** +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +package com.cocos.lib; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +public class CocosLocalStorage { + + private static final String TAG = "CocosLocalStorage"; + + private static String DATABASE_NAME = "jsb.sqlite"; + private static String TABLE_NAME = "data"; + private static final int DATABASE_VERSION = 1; + + private static DBOpenHelper mDatabaseOpenHelper = null; + private static SQLiteDatabase mDatabase = null; + + public static boolean init(String dbName, String tableName) { + if (GlobalObject.getContext() != null) { + DATABASE_NAME = dbName; + TABLE_NAME = tableName; + mDatabaseOpenHelper = new DBOpenHelper(GlobalObject.getContext()); + mDatabase = mDatabaseOpenHelper.getWritableDatabase(); + return true; + } + return false; + } + + public static void destroy() { + if (mDatabase != null) { + mDatabase.close(); + } + } + + public static void setItem(String key, String value) { + try { + String sql = "replace into "+TABLE_NAME+"(key,value)values(?,?)"; + mDatabase.execSQL(sql, new Object[] { key, value }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static String getItem(String key) { + String ret = null; + try { + String sql = "select value from "+TABLE_NAME+" where key=?"; + Cursor c = mDatabase.rawQuery(sql, new String[]{key}); + while (c.moveToNext()) { + // only return the first value + if (ret != null) + { + Log.e(TAG, "The key contains more than one value."); + break; + } + ret = c.getString(c.getColumnIndex("value")); + } + c.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return ret; + } + + public static void removeItem(String key) { + try { + String sql = "delete from "+TABLE_NAME+" where key=?"; + mDatabase.execSQL(sql, new Object[] {key}); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void clear() { + try { + String sql = "delete from "+TABLE_NAME; + mDatabase.execSQL(sql); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static String getKey(int nIndex) { + String ret = null; + try { + int nCount = 0; + String sql = "select key from "+TABLE_NAME + " order by rowid asc"; + Cursor c = mDatabase.rawQuery(sql, null); + if(nIndex < 0 || nIndex >= c.getCount()) { + return null; + } + + while (c.moveToNext()) { + if(nCount == nIndex) { + ret = c.getString(c.getColumnIndex("key")); + break; + } + nCount++; + } + c.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return ret; + } + + public static int getLength() { + int res = 0; + try { + String sql = "select count(*) as nums from "+TABLE_NAME; + Cursor c = mDatabase.rawQuery(sql, null); + if (c.moveToNext()){ + res = c.getInt(c.getColumnIndex("nums")); + } + c.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return res; + } + + /** + * This creates/opens the database. + */ + private static class DBOpenHelper extends SQLiteOpenHelper { + + DBOpenHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS "+TABLE_NAME+"(key TEXT PRIMARY KEY,value TEXT);"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + + newVersion + ", which will destroy all old data"); + //db.execSQL("DROP TABLE IF EXISTS " + VIRTUAL_TABLE); + //onCreate(db); + } + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosOrientationHelper.java b/cocos/platform/android/java/src/com/cocos/lib/CocosOrientationHelper.java new file mode 100644 index 0000000..43c4b3d --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosOrientationHelper.java @@ -0,0 +1,62 @@ +/**************************************************************************** + * Copyright (c) 2020 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import android.content.Context; +import android.view.OrientationEventListener; + +public class CocosOrientationHelper extends OrientationEventListener { + + private int mCurrentOrientation; + + public CocosOrientationHelper(Context context) { + super(context); + mCurrentOrientation = CocosHelper.getDeviceRotation(); + } + + public void onPause() { + this.disable(); + } + + public void onResume() { + this.enable(); + } + + @Override + public void onOrientationChanged(int orientation) { + int curOrientation = CocosHelper.getDeviceRotation(); + if (curOrientation != mCurrentOrientation) { + mCurrentOrientation = CocosHelper.getDeviceRotation(); + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + nativeOnOrientationChanged(mCurrentOrientation); + } + }); + } + } + + private static native void nativeOnOrientationChanged(int rotation); +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosReflectionHelper.java b/cocos/platform/android/java/src/com/cocos/lib/CocosReflectionHelper.java new file mode 100644 index 0000000..eef98e4 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosReflectionHelper.java @@ -0,0 +1,75 @@ +/**************************************************************************** +Copyright (c) 2016 cocos2d-x.org +Copyright (c) 2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import android.util.Log; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class CocosReflectionHelper { + public static T getConstantValue(final Class aClass, final String constantName) { + try { + return (T)aClass.getDeclaredField(constantName).get(null); + } catch (NoSuchFieldException e) { + Log.e("error", "can not find " + constantName + " in " + aClass.getName()); + } + catch (IllegalAccessException e) { + Log.e("error", constantName + " is not accessable"); + } + catch (IllegalArgumentException e) { + Log.e("error", "arguments error when get " + constantName); + } + catch (Exception e) { + Log.e("error", "can not get constant" + constantName); + } + + return null; + } + + public static T invokeInstanceMethod(final Object instance, final String methodName, + final Class[] parameterTypes, final Object[] parameters) { + + final Class aClass = instance.getClass(); + try { + final Method method = aClass.getMethod(methodName, parameterTypes); + return (T)method.invoke(instance, parameters); + } catch (NoSuchMethodException e) { + Log.e("error", "can not find " + methodName + " in " + aClass.getName()); + } + catch (IllegalAccessException e) { + Log.e("error", methodName + " is not accessible"); + } + catch (IllegalArgumentException e) { + Log.e("error", "arguments are error when invoking " + methodName); + } + catch (InvocationTargetException e) { + Log.e("error", "an exception was thrown by the invoked method when invoking " + methodName); + } + + return null; + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosSensorHandler.java b/cocos/platform/android/java/src/com/cocos/lib/CocosSensorHandler.java new file mode 100644 index 0000000..a32746e --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosSensorHandler.java @@ -0,0 +1,145 @@ +/**************************************************************************** + Copyright (c) 2010-2013 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +public class CocosSensorHandler implements SensorEventListener { + // =========================================================== + // Constants + // =========================================================== + + private static final String TAG = "CocosSensorHandler"; + private static CocosSensorHandler mSensorHandler; + private static boolean mEnableSensor = false; + + private final Context mContext; + private SensorManager mSensorManager; + private Sensor mAcceleration; + private Sensor mAccelerationIncludingGravity; + private Sensor mGyroscope; + private int mSamplingPeriodUs = SensorManager.SENSOR_DELAY_GAME; + + private static float[] sDeviceMotionValues = new float[9]; + + // =========================================================== + // Constructors + // =========================================================== + + public CocosSensorHandler(final Context context) { + mContext = context; + mSensorHandler = this; + } + + // =========================================================== + // Getter & Setter + // =========================================================== + public void enable() { + if (mEnableSensor) { + if (null == mSensorManager) { + mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); + mAcceleration = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + mAccelerationIncludingGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION); + mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); + } + + mSensorManager.registerListener(this, mAcceleration, mSamplingPeriodUs); + mSensorManager.registerListener(this, mAccelerationIncludingGravity, mSamplingPeriodUs); + mSensorManager.registerListener(this, mGyroscope, mSamplingPeriodUs); + } + } + + public void disable() { + if (mEnableSensor && null != mSensorManager) { + this.mSensorManager.unregisterListener(this); + } + } + + public void setInterval(float interval) { + if (android.os.Build.VERSION.SDK_INT >= 11) { + mSamplingPeriodUs = (int) (interval * 1000000); + } + disable(); + enable(); + } + + // =========================================================== + // Methods for/from SuperClass/Interfaces + // =========================================================== + @Override + public void onSensorChanged(final SensorEvent sensorEvent) { + int type = sensorEvent.sensor.getType(); + if (type == Sensor.TYPE_ACCELEROMETER) { + sDeviceMotionValues[0] = sensorEvent.values[0]; + sDeviceMotionValues[1] = sensorEvent.values[1]; + // Issue https://github.com/cocos-creator/2d-tasks/issues/2532 + // use negative event.acceleration.z to match iOS value + sDeviceMotionValues[2] = -sensorEvent.values[2]; + } else if (type == Sensor.TYPE_LINEAR_ACCELERATION) { + sDeviceMotionValues[3] = sensorEvent.values[0]; + sDeviceMotionValues[4] = sensorEvent.values[1]; + sDeviceMotionValues[5] = sensorEvent.values[2]; + } else if (type == Sensor.TYPE_GYROSCOPE) { + // The unit is rad/s, need to be converted to deg/s + sDeviceMotionValues[6] = (float) Math.toDegrees(sensorEvent.values[0]); + sDeviceMotionValues[7] = (float) Math.toDegrees(sensorEvent.values[1]); + sDeviceMotionValues[8] = (float) Math.toDegrees(sensorEvent.values[2]); + } + } + + @Override + public void onAccuracyChanged(final Sensor sensor, final int accuracy) { + } + + public void onPause() { + disable(); + } + + public void onResume() { + enable(); + } + + public static void setAccelerometerInterval(float interval) { + mSensorHandler.setInterval(interval); + } + + public static void setAccelerometerEnabled(boolean enabled) { + mEnableSensor = enabled; + if (enabled) { + mSensorHandler.enable(); + } else { + mSensorHandler.disable(); + } + } + + public static float[] getDeviceMotionValue() { + return sDeviceMotionValues; + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosSurfaceView.java b/cocos/platform/android/java/src/com/cocos/lib/CocosSurfaceView.java new file mode 100644 index 0000000..62b5486 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosSurfaceView.java @@ -0,0 +1,99 @@ +/**************************************************************************** + * Copyright (c) 2020 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import android.content.Context; +import android.util.Log; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +public class CocosSurfaceView extends SurfaceView implements android.view.SurfaceHolder.Callback2 { + private CocosTouchHandler mTouchHandler; + private long mNativeHandle; + private int mWindowId; + + public CocosSurfaceView(Context context, int windowId) { + super(context); + mWindowId = windowId; + mNativeHandle = constructNative(windowId); + mTouchHandler = new CocosTouchHandler(mWindowId); + getHolder().addCallback(this); + } + + private native long constructNative(int windowId); + private native void destructNative(long handle); + private native void onSizeChangedNative(int windowId, int width, final int height); + private native void onSurfaceRedrawNeededNative(long handle); + private native void onSurfaceCreatedNative(long handle, Surface surface); + private native void onSurfaceChangedNative(long handle, Surface surface, int format, int width, int height); + private native void onSurfaceDestroyedNative(long handle); + + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + onSizeChangedNative(mWindowId, w, h); + } + }); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return mTouchHandler.onTouchEvent(event); + } + + @Override + public void surfaceRedrawNeeded(SurfaceHolder holder) { + onSurfaceRedrawNeededNative(mNativeHandle); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + onSurfaceCreatedNative(mNativeHandle, holder.getSurface()); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + onSurfaceChangedNative(mNativeHandle, holder.getSurface(), format, width, height); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + onSurfaceDestroyedNative(mNativeHandle); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + destructNative(mNativeHandle); + mNativeHandle = 0; + } + +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosTouchHandler.java b/cocos/platform/android/java/src/com/cocos/lib/CocosTouchHandler.java new file mode 100644 index 0000000..729aba1 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosTouchHandler.java @@ -0,0 +1,184 @@ +/**************************************************************************** + Copyright (c) 2010-2013 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import android.util.Log; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceView; + +public class CocosTouchHandler { + public final static String TAG = "CocosTouchHandler"; + private boolean mStopHandleTouchAndKeyEvents = false; + private int mWindowId; + + public CocosTouchHandler(int windowId) { + mWindowId = windowId; + } + + boolean onTouchEvent(MotionEvent pMotionEvent) { + // these data are used in ACTION_MOVE and ACTION_CANCEL + final int pointerNumber = pMotionEvent.getPointerCount(); + final int[] ids = new int[pointerNumber]; + final float[] xs = new float[pointerNumber]; + final float[] ys = new float[pointerNumber]; + + for (int i = 0; i < pointerNumber; i++) { + ids[i] = pMotionEvent.getPointerId(i); + xs[i] = pMotionEvent.getX(i); + ys[i] = pMotionEvent.getY(i); + } + + switch (pMotionEvent.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_POINTER_DOWN: + if (mStopHandleTouchAndKeyEvents) { +// Cocos2dxEditBox.complete(); + return true; + } + + final int indexPointerDown = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int idPointerDown = pMotionEvent.getPointerId(indexPointerDown); + final float xPointerDown = pMotionEvent.getX(indexPointerDown); + final float yPointerDown = pMotionEvent.getY(indexPointerDown); + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleActionDown(mWindowId, idPointerDown, xPointerDown, yPointerDown); + } + }); + break; + + case MotionEvent.ACTION_DOWN: + if (mStopHandleTouchAndKeyEvents) { +// Cocos2dxEditBox.complete(); + return true; + } + + // there are only one finger on the screen + final int idDown = pMotionEvent.getPointerId(0); + final float xDown = xs[0]; + final float yDown = ys[0]; + + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleActionDown(mWindowId, idDown, xDown, yDown); + } + }); + + break; + + case MotionEvent.ACTION_MOVE: + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleActionMove(mWindowId, ids, xs, ys); + } + }); + + break; + + case MotionEvent.ACTION_POINTER_UP: + final int indexPointUp = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int idPointerUp = pMotionEvent.getPointerId(indexPointUp); + final float xPointerUp = pMotionEvent.getX(indexPointUp); + final float yPointerUp = pMotionEvent.getY(indexPointUp); + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleActionUp(mWindowId, idPointerUp, xPointerUp, yPointerUp); + } + }); + + break; + + case MotionEvent.ACTION_UP: + // there are only one finger on the screen + final int idUp = pMotionEvent.getPointerId(0); + final float xUp = xs[0]; + final float yUp = ys[0]; + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleActionUp(mWindowId, idUp, xUp, yUp); + } + }); + + break; + + case MotionEvent.ACTION_CANCEL: + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleActionCancel(mWindowId, ids, xs, ys); + } + }); + break; + } + + if (BuildConfig.DEBUG) { +// CocosTouchHandler.dumpMotionEvent(pMotionEvent); + } + return true; + } + + public void setStopHandleTouchAndKeyEvents(boolean value) { + mStopHandleTouchAndKeyEvents = value; + } + + private static void dumpMotionEvent(final MotionEvent event) { + final String names[] = {"DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE", "POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?"}; + final StringBuilder sb = new StringBuilder(); + final int action = event.getAction(); + final int actionCode = action & MotionEvent.ACTION_MASK; + sb.append("event ACTION_").append(names[actionCode]); + if (actionCode == MotionEvent.ACTION_POINTER_DOWN || actionCode == MotionEvent.ACTION_POINTER_UP) { + sb.append("(pid ").append(action >> MotionEvent.ACTION_POINTER_INDEX_SHIFT); + sb.append(")"); + } + sb.append("["); + for (int i = 0; i < event.getPointerCount(); i++) { + sb.append("#").append(i); + sb.append("(pid ").append(event.getPointerId(i)); + sb.append(")=").append((int) event.getX(i)); + sb.append(",").append((int) event.getY(i)); + if (i + 1 < event.getPointerCount()) { + sb.append(";"); + } + } + sb.append("]"); + Log.d(TAG, sb.toString()); + } + + private native void handleActionDown(int windowId, final int id, final float x, final float y); + + private native void handleActionMove(int windowId, final int[] ids, final float[] xPointerList, final float[] yPointerList); + + private native void handleActionUp(int windowId, final int id, final float x, final float y); + + private native void handleActionCancel(int windowId, final int[] ids, final float[] xPointerList, final float[] yPointerList); + +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosVideoHelper.java b/cocos/platform/android/java/src/com/cocos/lib/CocosVideoHelper.java new file mode 100644 index 0000000..676c319 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosVideoHelper.java @@ -0,0 +1,509 @@ +/**************************************************************************** +Copyright (c) 2014-2016 Chukong Technologies Inc. +Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.util.SparseArray; +import android.view.View; +import android.widget.FrameLayout; +import android.app.Activity; + +import com.cocos.lib.CocosVideoView.OnVideoEventListener; + +import java.lang.ref.WeakReference; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +public class CocosVideoHelper { + + private FrameLayout mLayout = null; + private Activity mActivity = null; + private static SparseArray sVideoViews = null; + static VideoHandler mVideoHandler = null; + private static Handler sHandler = null; + + CocosVideoHelper(Activity activity, FrameLayout layout) + { + mActivity = activity; + mLayout = layout; + + mVideoHandler = new VideoHandler(this); + sVideoViews = new SparseArray(); + sHandler = new Handler(Looper.myLooper()); + } + + private static int videoTag = 0; + private final static int VideoTaskCreate = 0; + private final static int VideoTaskRemove = 1; + private final static int VideoTaskSetSource = 2; + private final static int VideoTaskSetRect = 3; + private final static int VideoTaskStart = 4; + private final static int VideoTaskPause = 5; + private final static int VideoTaskResume = 6; + private final static int VideoTaskStop = 7; + private final static int VideoTaskSeek = 8; + private final static int VideoTaskSetVisible = 9; + private final static int VideoTaskRestart = 10; + private final static int VideoTaskKeepRatio = 11; + private final static int VideoTaskFullScreen = 12; + private final static int VideoTaskSetVolume = 13; + private final static int VideoTaskSetPlaybackRate = 14; + private final static int VideoTaskSetMute = 15; + private final static int VideoTaskSetLoop = 16; + + + final static int KeyEventBack = 1000; + + static class VideoHandler extends Handler{ + WeakReference mReference; + + VideoHandler(CocosVideoHelper helper){ + mReference = new WeakReference(helper); + } + + @Override + public void handleMessage(Message msg) { + CocosVideoHelper helper = mReference.get(); + switch (msg.what) { + case VideoTaskCreate: { + helper._createVideoView(msg.arg1); + break; + } + case VideoTaskRemove: { + helper._removeVideoView(msg.arg1); + break; + } + case VideoTaskSetSource: { + helper._setVideoURL(msg.arg1, msg.arg2, (String)msg.obj); + break; + } + case VideoTaskStart: { + helper._startVideo(msg.arg1); + break; + } + case VideoTaskSetRect: { + Rect rect = (Rect)msg.obj; + helper._setVideoRect(msg.arg1, rect.left, rect.top, rect.right, rect.bottom); + break; + } + case VideoTaskFullScreen:{ + if (msg.arg2 == 1) { + helper._setFullScreenEnabled(msg.arg1, true); + } else { + helper._setFullScreenEnabled(msg.arg1, false); + } + break; + } + case VideoTaskPause: { + helper._pauseVideo(msg.arg1); + break; + } + + case VideoTaskStop: { + helper._stopVideo(msg.arg1); + break; + } + case VideoTaskSeek: { + helper._seekVideoTo(msg.arg1, msg.arg2); + break; + } + case VideoTaskSetVisible: { + if (msg.arg2 == 1) { + helper._setVideoVisible(msg.arg1, true); + } else { + helper._setVideoVisible(msg.arg1, false); + } + break; + } + case VideoTaskKeepRatio: { + if (msg.arg2 == 1) { + helper._setVideoKeepRatio(msg.arg1, true); + } else { + helper._setVideoKeepRatio(msg.arg1, false); + } + break; + } + case KeyEventBack: { + helper.onBackKeyEvent(); + break; + } + case VideoTaskSetVolume: { + float volume = (float) msg.arg2 / 10; + helper._setVolume(msg.arg1, volume); + break; + } + case VideoTaskSetPlaybackRate: { + float rate = (float) msg.arg2 / 10; + helper._setPlaybackRate(msg.arg1, rate); + break; + } + case VideoTaskSetMute: { + if (msg.arg2 == 1) { + helper._setMute(msg.arg1, true); + } else { + helper._setMute(msg.arg1, false); + } + break; + } + case VideoTaskSetLoop: { + if (msg.arg2 == 1) { + helper._setLoop(msg.arg1, true); + } else { + helper._setLoop(msg.arg1, false); + } + break; + } + default: + break; + } + + super.handleMessage(msg); + } + } + + public static native void nativeExecuteVideoCallback(int index,int event); + + OnVideoEventListener videoEventListener = new OnVideoEventListener() { + + @Override + public void onVideoEvent(int tag,int event) { + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + nativeExecuteVideoCallback(tag, event); + } + }); + } + }; + + public static int createVideoWidget() { + Message msg = new Message(); + msg.what = VideoTaskCreate; + msg.arg1 = videoTag; + mVideoHandler.sendMessage(msg); + + return videoTag++; + } + + private void _createVideoView(int index) { + CocosVideoView videoView = new CocosVideoView(mActivity,index); + sVideoViews.put(index, videoView); + FrameLayout.LayoutParams lParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + mLayout.addView(videoView, lParams); + + videoView.setZOrderOnTop(true); + videoView.setVideoViewEventListener(videoEventListener); + } + + public static void removeVideoWidget(int index){ + Message msg = new Message(); + msg.what = VideoTaskRemove; + msg.arg1 = index; + mVideoHandler.sendMessage(msg); + } + + private void _removeVideoView(int index) { + CocosVideoView view = sVideoViews.get(index); + if (view != null) { + view.stopPlayback(); + sVideoViews.remove(index); + mLayout.removeView(view); + } + } + + public static void setVideoUrl(int index, int videoSource, String videoUrl) { + Message msg = new Message(); + msg.what = VideoTaskSetSource; + msg.arg1 = index; + msg.arg2 = videoSource; + msg.obj = videoUrl; + mVideoHandler.sendMessage(msg); + } + + private void _setVideoURL(int index, int videoSource, String videoUrl) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + switch (videoSource) { + case 0: + videoView.setVideoFileName(videoUrl); + break; + case 1: + videoView.setVideoURL(videoUrl); + break; + default: + break; + } + } + } + + public static void setVideoRect(int index, int left, int top, int maxWidth, int maxHeight) { + Message msg = new Message(); + msg.what = VideoTaskSetRect; + msg.arg1 = index; + msg.obj = new Rect(left, top, maxWidth, maxHeight); + mVideoHandler.sendMessage(msg); + } + + private void _setVideoRect(int index, int left, int top, int maxWidth, int maxHeight) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.setVideoRect(left, top, maxWidth, maxHeight); + } + } + + public static void setFullScreenEnabled(int index, boolean enabled) { + Message msg = new Message(); + msg.what = VideoTaskFullScreen; + msg.arg1 = index; + if (enabled) { + msg.arg2 = 1; + } else { + msg.arg2 = 0; + } + mVideoHandler.sendMessage(msg); + } + + private void _setFullScreenEnabled(int index, boolean enabled) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.setFullScreenEnabled(enabled); + } + } + + private void onBackKeyEvent() { + int viewCount = sVideoViews.size(); + for (int i = 0; i < viewCount; i++) { + int key = sVideoViews.keyAt(i); + CocosVideoView videoView = sVideoViews.get(key); + if (videoView != null) { + videoView.setFullScreenEnabled(false); + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + nativeExecuteVideoCallback(key, KeyEventBack); + } + }); + } + } + } + + public static void startVideo(int index) { + Message msg = new Message(); + msg.what = VideoTaskStart; + msg.arg1 = index; + mVideoHandler.sendMessage(msg); + } + + private void _startVideo(int index) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.start(); + } + } + + public static void pauseVideo(int index) { + Message msg = new Message(); + msg.what = VideoTaskPause; + msg.arg1 = index; + mVideoHandler.sendMessage(msg); + } + + private void _pauseVideo(int index) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.pause(); + } + } + + public static void stopVideo(int index) { + Message msg = new Message(); + msg.what = VideoTaskStop; + msg.arg1 = index; + mVideoHandler.sendMessage(msg); + } + + private void _stopVideo(int index) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.stop(); + } + } + + public static void seekVideoTo(int index,int msec) { + Message msg = new Message(); + msg.what = VideoTaskSeek; + msg.arg1 = index; + msg.arg2 = msec; + mVideoHandler.sendMessage(msg); + } + + private void _seekVideoTo(int index,int msec) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.seekTo(msec); + } + } + + public static float getCurrentTime(final int index) { + CocosVideoView video = sVideoViews.get(index); + float currentPosition = -1; + if (video != null) { + currentPosition = video.getCurrentPosition() / 1000.0f; + } + return currentPosition; + } + + public static float getDuration(final int index) { + CocosVideoView video = sVideoViews.get(index); + float duration = -1; + if (video != null) { + duration = video.getDuration() / 1000.0f; + } + if (duration <= 0) { + Log.w("CocosVideoHelper", "Video player's duration is not ready to get now!"); + } + return duration; + } + + public static void setVideoVisible(int index, boolean visible) { + Message msg = new Message(); + msg.what = VideoTaskSetVisible; + msg.arg1 = index; + if (visible) { + msg.arg2 = 1; + } else { + msg.arg2 = 0; + } + + mVideoHandler.sendMessage(msg); + } + + private void _setVideoVisible(int index, boolean visible) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + if (visible) { + videoView.fixSize(); + videoView.setVisibility(View.VISIBLE); + } else { + videoView.setVisibility(View.INVISIBLE); + } + } + } + + public static void setVideoKeepRatioEnabled(int index, boolean enable) { + Message msg = new Message(); + msg.what = VideoTaskKeepRatio; + msg.arg1 = index; + if (enable) { + msg.arg2 = 1; + } else { + msg.arg2 = 0; + } + mVideoHandler.sendMessage(msg); + } + + public static void setPlaybackRate(final int index, final float value) { + Message msg = new Message(); + msg.what = VideoTaskSetPlaybackRate; + msg.arg1 = index; + msg.arg2 = (int) (value * 10); + mVideoHandler.sendMessage(msg); + } + + private void _setPlaybackRate(final int index, final float value) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.playbackRate(value); + } + } + + public static void setMute(int index, boolean enable) { + Message msg = new Message(); + msg.what = VideoTaskSetMute; + msg.arg1 = index; + if (enable) { + msg.arg2 = 1; + } else { + msg.arg2 = 0; + } + mVideoHandler.sendMessage(msg); + } + + private void _setMute(int index, boolean enable) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.setMute(enable); + } + } + + public static void setLoop(int index, boolean enable) { + Message msg = new Message(); + msg.what = VideoTaskSetLoop; + msg.arg1 = index; + if (enable) { + msg.arg2 = 1; + } else { + msg.arg2 = 0; + } + mVideoHandler.sendMessage(msg); + } + + private void _setLoop(int index, boolean enable) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.setLoop(enable); + } + } + + private void _setVideoKeepRatio(int index, boolean enable) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.setKeepRatio(enable); + } + } + + private void _setVolume(final int index, final float volume) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.setVolume(volume); + } + } + + public static void setVolume(final int index, final float volume) { + Message msg = new Message(); + msg.what = VideoTaskSetVolume; + msg.arg1 = index; + msg.arg2 = (int) (volume * 10); + mVideoHandler.sendMessage(msg); + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosVideoView.java b/cocos/platform/android/java/src/com/cocos/lib/CocosVideoView.java new file mode 100644 index 0000000..663fa23 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosVideoView.java @@ -0,0 +1,637 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * Copyright (c) 2014-2016 Chukong Technologies Inc. + * Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cocos.lib; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.AssetFileDescriptor; +import android.content.res.Resources; +import android.graphics.Point; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.PlaybackParams; +import android.net.Uri; +import android.os.Build; +import android.util.Log; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.widget.FrameLayout; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Map; + +public class CocosVideoView extends SurfaceView { + + // =========================================================== + // Internal classes and interfaces. + // =========================================================== + + public interface OnVideoEventListener { + void onVideoEvent(int tag,int event); + } + + private enum State { + IDLE, + ERROR, + INITIALIZED, + PREPARING, + PREPARED, + STARTED, + PAUSED, + STOPPED, + PLAYBACK_COMPLETED, + } + + // =========================================================== + // Constants + // =========================================================== + private static final String AssetResourceRoot = "@assets/"; + + // =========================================================== + // Fields + // =========================================================== + + private String TAG = "CocosVideoView"; + + private Uri mVideoUri; + private int mDuration; + private int mPosition; + + private State mCurrentState = State.IDLE; + + // All the stuff we need for playing and showing a video + private SurfaceHolder mSurfaceHolder = null; + private MediaPlayer mMediaPlayer = null; + private int mVideoWidth = 0; + private int mVideoHeight = 0; + + private OnVideoEventListener mOnVideoEventListener; + + // recording the seek position while preparing + private int mSeekWhenPrepared = 0; + + protected Activity mActivity = null; + + protected int mViewLeft = 0; + protected int mViewTop = 0; + protected int mViewWidth = 0; + protected int mViewHeight = 0; + + protected int mVisibleLeft = 0; + protected int mVisibleTop = 0; + protected int mVisibleWidth = 0; + protected int mVisibleHeight = 0; + + protected boolean mFullScreenEnabled = false; + + private boolean mIsAssetResource = false; + private String mVideoFilePath = null; + + private int mViewTag = 0; + private boolean mKeepRatio = false; + private boolean mMetaUpdated = false; + + // MediaPlayer will be released when surface view is destroyed, so should record the position, + // and use it to play after MedialPlayer is created again. + private int mPositionBeforeRelease = 0; + // also need to record play state when surface is destroyed. + private State mStateBeforeRelease = State.IDLE; + + // =========================================================== + // Constructors + // =========================================================== + + public CocosVideoView(Activity activity, int tag) { + super(activity); + + mViewTag = tag; + mActivity = activity; + initVideoView(); + } + + // =========================================================== + // Getter & Setter + // =========================================================== + + public void setVideoRect(int left, int top, int maxWidth, int maxHeight) { + if (mViewLeft == left && mViewTop == top && mViewWidth == maxWidth && mViewHeight == maxHeight) + return; + + mViewLeft = left; + mViewTop = top; + mViewWidth = maxWidth; + mViewHeight = maxHeight; + + fixSize(mViewLeft, mViewTop, mViewWidth, mViewHeight); + } + + public void setFullScreenEnabled(boolean enabled) { + if (mFullScreenEnabled != enabled) { + mFullScreenEnabled = enabled; + fixSize(); + } + } + + public void setVolume (float volume) { + if (mMediaPlayer != null) { + mMediaPlayer.setVolume(volume, volume); + } + } + + public void setKeepRatio(boolean enabled) { + mKeepRatio = enabled; + fixSize(); + } + + public void playbackRate(float value) { + if (mMediaPlayer != null) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + PlaybackParams params = new PlaybackParams(); + params.setSpeed(value); + mMediaPlayer.setPlaybackParams(params); + } + } + } + + public void setMute(boolean enabled) { + if (mMediaPlayer != null) { + if (enabled) { + mMediaPlayer.setVolume(0f, 0f); + } else { + mMediaPlayer.setVolume(1f, 1f); + } + } + } + + public void setLoop(boolean enabled) { + if (mMediaPlayer != null) { + mMediaPlayer.setLooping(enabled); + } + } + + + public void setVideoURL(String url) { + mIsAssetResource = false; + setVideoURI(Uri.parse(url), null); + } + + public void setVideoFileName(String path) { + if (path.startsWith(AssetResourceRoot)) { + path = path.substring(AssetResourceRoot.length()); + } + + if (path.startsWith("/")) { + mIsAssetResource = false; + setVideoURI(Uri.parse(path),null); + } + else { + + mVideoFilePath = path; + mIsAssetResource = true; + setVideoURI(Uri.parse(path), null); + } + } + + public int getCurrentPosition() { + if (! (mCurrentState == State.IDLE || + mCurrentState == State.ERROR || + mCurrentState == State.INITIALIZED || + mCurrentState == State.STOPPED || + mMediaPlayer == null) ) { + mPosition = mMediaPlayer.getCurrentPosition(); + } + return mPosition; + } + + public int getDuration() { + if (! (mCurrentState == State.IDLE || + mCurrentState == State.ERROR || + mCurrentState == State.INITIALIZED || + mCurrentState == State.STOPPED || + mMediaPlayer == null) ) { + mDuration = mMediaPlayer.getDuration(); + } + + return mDuration; + } + + /** + * Register a callback to be invoked when some video event triggered. + * + * @param l The callback that will be run + */ + public void setVideoViewEventListener(OnVideoEventListener l) + { + mOnVideoEventListener = l; + } + + // =========================================================== + // Overrides + // =========================================================== + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(mVisibleWidth, mVisibleHeight); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { + this.sendEvent(EVENT_CLICKED); + } + return true; + } + + // =========================================================== + // Public functions + // =========================================================== + + public void stop() { + if (!(mCurrentState == State.IDLE || mCurrentState == State.INITIALIZED || mCurrentState == State.ERROR || mCurrentState == State.STOPPED) + && mMediaPlayer != null) { + mCurrentState = State.STOPPED; + mMediaPlayer.stop(); + this.sendEvent(EVENT_STOPPED); + + // after the video is stop, it shall prepare to be playable again + try { + mMediaPlayer.reset(); // reset to avoid some problems on start. + loadDataSource(); + mMediaPlayer.prepare(); + this.showFirstFrame(); + } catch (Exception ex) {} + } + } + + public void stopPlayback() { + this.release(); + } + + public void start() { + if ((mCurrentState == State.PREPARED || + mCurrentState == State.PAUSED || + mCurrentState == State.PLAYBACK_COMPLETED) && + mMediaPlayer != null) { + + mCurrentState = State.STARTED; + mMediaPlayer.start(); + this.sendEvent(EVENT_PLAYING); + } + } + + public void pause() { + if ((mCurrentState == State.STARTED || mCurrentState == State.PLAYBACK_COMPLETED) && + mMediaPlayer != null) { + mCurrentState = State.PAUSED; + mMediaPlayer.pause(); + this.sendEvent(EVENT_PAUSED); + } + } + + public void seekTo(int ms) { + if (mCurrentState == State.IDLE || mCurrentState == State.INITIALIZED || + mCurrentState == State.STOPPED || mCurrentState == State.ERROR || + mMediaPlayer == null) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try { + Method seekTo = mMediaPlayer.getClass().getMethod("seekTo", long.class, int.class); + // The mode argument added in API level 26, 3 = MediaPlayer.SEEK_CLOSEST + // https://developer.android.com/reference/android/media/MediaPlayer#seekTo(int) + seekTo.invoke(mMediaPlayer, ms, 3); + } catch (Exception e) { + mMediaPlayer.seekTo(ms); + } + } + else { + mMediaPlayer.seekTo(ms); + } + } + + public void fixSize() { + if (mFullScreenEnabled) { + Point screenSize = new Point(); + mActivity.getWindowManager().getDefaultDisplay().getSize(screenSize); + fixSize(0, 0, screenSize.x, screenSize.y); + } else { + fixSize(mViewLeft, mViewTop, mViewWidth, mViewHeight); + } + } + + public void fixSize(int left, int top, int width, int height) { + if (mVideoWidth == 0 || mVideoHeight == 0) { + mVisibleLeft = left; + mVisibleTop = top; + mVisibleWidth = width; + mVisibleHeight = height; + } + else if (width != 0 && height != 0) { + if (mKeepRatio && !mFullScreenEnabled) { + if ( mVideoWidth * height > width * mVideoHeight ) { + mVisibleWidth = width; + mVisibleHeight = width * mVideoHeight / mVideoWidth; + } else if ( mVideoWidth * height < width * mVideoHeight ) { + mVisibleWidth = height * mVideoWidth / mVideoHeight; + mVisibleHeight = height; + } + mVisibleLeft = left + (width - mVisibleWidth) / 2; + mVisibleTop = top + (height - mVisibleHeight) / 2; + } else { + mVisibleLeft = left; + mVisibleTop = top; + mVisibleWidth = width; + mVisibleHeight = height; + } + } + else { + mVisibleLeft = left; + mVisibleTop = top; + mVisibleWidth = mVideoWidth; + mVisibleHeight = mVideoHeight; + } + + getHolder().setFixedSize(mVisibleWidth, mVisibleHeight); + + FrameLayout.LayoutParams lParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT); + lParams.leftMargin = mVisibleLeft; + lParams.topMargin = mVisibleTop; + setLayoutParams(lParams); + } + + public int resolveAdjustedSize(int desiredSize, int measureSpec) { + int result = desiredSize; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + /* Parent says we can be as big as we want. Just don't be larger + * than max size imposed on ourselves. + */ + result = desiredSize; + break; + + case MeasureSpec.AT_MOST: + /* Parent says we can be as big as we want, up to specSize. + * Don't be larger than specSize, and don't be larger than + * the max size imposed on ourselves. + */ + result = Math.min(desiredSize, specSize); + break; + + case MeasureSpec.EXACTLY: + // No choice. Do what we are told. + result = specSize; + break; + } + + return result; + } + + // =========================================================== + // Private functions + // =========================================================== + + private void initVideoView() { + mVideoWidth = 0; + mVideoHeight = 0; + getHolder().addCallback(mSHCallback); + //Fix issue#11516:Can't play video on Android 2.3.x + getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + setFocusable(true); + setFocusableInTouchMode(true); + mCurrentState = State.IDLE; + } + + /** + * @hide + */ + private void setVideoURI(Uri uri, Map headers) { + mVideoUri = uri; + mVideoWidth = 0; + mVideoHeight = 0; + } + + private void loadDataSource() throws IOException { + if (mIsAssetResource) { + AssetFileDescriptor afd = mActivity.getAssets().openFd(mVideoFilePath); + mMediaPlayer.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getLength()); + } else { + mMediaPlayer.setDataSource(mVideoUri.toString()); + } + } + + private void openVideo() { + if (mSurfaceHolder == null) { + // not ready for playback just yet, will try again later + return; + } + if (mIsAssetResource) { + if(mVideoFilePath == null) + return; + } else if(mVideoUri == null) { + return; + } + + this.pausePlaybackService(); + + try { + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnPreparedListener(mPreparedListener); + mMediaPlayer.setOnCompletionListener(mCompletionListener); + mMediaPlayer.setOnErrorListener(mErrorListener); + mMediaPlayer.setDisplay(mSurfaceHolder); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mMediaPlayer.setScreenOnWhilePlaying(true); + + loadDataSource(); + mCurrentState = State.INITIALIZED; + + // Use Prepare() instead of PrepareAsync to make things easy. + mMediaPlayer.prepare(); + this.showFirstFrame(); + +// mMediaPlayer.prepareAsync(); + } catch (IOException ex) { + Log.w(TAG, "Unable to open content: " + mVideoUri, ex); + mCurrentState = State.ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } catch (IllegalArgumentException ex) { + Log.w(TAG, "Unable to open content: " + mVideoUri, ex); + mCurrentState = State.ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } + } + + MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { + public void onPrepared(MediaPlayer mp) { + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + + if (mVideoWidth != 0 && mVideoHeight != 0) { + fixSize(); + } + + if(!mMetaUpdated) { + CocosVideoView.this.sendEvent(EVENT_META_LOADED); + CocosVideoView.this.sendEvent(EVENT_READY_TO_PLAY); + mMetaUpdated = true; + } + + mCurrentState = State.PREPARED; + + if (mStateBeforeRelease == State.STARTED) { + CocosVideoView.this.start(); + } + + if (mPositionBeforeRelease > 0) { + CocosVideoView.this.seekTo(mPositionBeforeRelease); + } + + mStateBeforeRelease = State.IDLE; + mPositionBeforeRelease = 0; + } + }; + + private MediaPlayer.OnCompletionListener mCompletionListener = + new MediaPlayer.OnCompletionListener() { + public void onCompletion(MediaPlayer mp) { + mCurrentState = State.PLAYBACK_COMPLETED; + CocosVideoView.this.sendEvent(EVENT_COMPLETED); + //make it playable again. + CocosVideoView.this.showFirstFrame(); + } + }; + + + private static final int EVENT_PLAYING = 0; + private static final int EVENT_PAUSED = 1; + private static final int EVENT_STOPPED = 2; + private static final int EVENT_COMPLETED = 3; + private static final int EVENT_META_LOADED = 4; + private static final int EVENT_CLICKED = 5; + private static final int EVENT_READY_TO_PLAY = 6; + + private MediaPlayer.OnErrorListener mErrorListener = + new MediaPlayer.OnErrorListener() { + public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { + Log.d(TAG, "Error: " + framework_err + "," + impl_err); + mCurrentState = State.ERROR; + + /* Otherwise, pop up an error dialog so the user knows that + * something bad has happened. Only try and pop up the dialog + * if we're attached to a window. When we're going away and no + * longer have a window, don't bother showing the user an error. + */ + if (getWindowToken() != null) { + Resources r = mActivity.getResources(); + int messageId; + + if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { + // messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback; + messageId = r.getIdentifier("VideoView_error_text_invalid_progressive_playback", "string", "android"); + } else { + // messageId = com.android.internal.R.string.VideoView_error_text_unknown; + messageId = r.getIdentifier("VideoView_error_text_unknown", "string", "android"); + } + + int titleId = r.getIdentifier("VideoView_error_title", "string", "android"); + int buttonStringId = r.getIdentifier("VideoView_error_button", "string", "android"); + + new AlertDialog.Builder(mActivity) + .setTitle(r.getString(titleId)) + .setMessage(messageId) + .setPositiveButton(r.getString(buttonStringId), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + /* If we get here, there is no onError listener, so + * at least inform them that the video is over. + */ + CocosVideoView.this.sendEvent(EVENT_COMPLETED); + } + }) + .setCancelable(false) + .show(); + } + return true; + } + }; + + SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() + { + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + } + + public void surfaceCreated(SurfaceHolder holder) { + mSurfaceHolder = holder; + CocosVideoView.this.openVideo(); + } + + public void surfaceDestroyed(SurfaceHolder holder) { + // after we return from this we can't use the surface any more + mSurfaceHolder = null; + mPositionBeforeRelease = getCurrentPosition(); + mStateBeforeRelease = mCurrentState; + CocosVideoView.this.release(); + } + }; + + /* + * release the media player in any state + */ + private void release() { + if (mMediaPlayer != null) { + mMediaPlayer.release(); + mMediaPlayer = null; + mCurrentState = State.IDLE; + } + } + + private void showFirstFrame() { + mMediaPlayer.seekTo(1); + } + + private void sendEvent(int event) { + if (this.mOnVideoEventListener != null) { + this.mOnVideoEventListener.onVideoEvent(this.mViewTag, event); + } + } + + // Tell the music playback service to pause + // REFINE: these constants need to be published somewhere in the framework. + private void pausePlaybackService() { + Intent i = new Intent("com.android.music.musicservicecommand"); + i.putExtra("command", "pause"); + mActivity.sendBroadcast(i); + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosWebView.java b/cocos/platform/android/java/src/com/cocos/lib/CocosWebView.java new file mode 100755 index 0000000..c752c88 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosWebView.java @@ -0,0 +1,173 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.FrameLayout; + +import java.lang.reflect.Method; +import java.net.URI; +import java.util.concurrent.CountDownLatch; + + class ShouldStartLoadingWorker implements Runnable { + private CountDownLatch mLatch; + private boolean[] mResult; + private final int mViewTag; + private final String mUrlString; + + ShouldStartLoadingWorker(CountDownLatch latch, boolean[] result, int viewTag, String urlString) { + this.mLatch = latch; + this.mResult = result; + this.mViewTag = viewTag; + this.mUrlString = urlString; + } + + @Override + public void run() { + this.mResult[0] = CocosWebViewHelper._shouldStartLoading(mViewTag, mUrlString); + this.mLatch.countDown(); // notify that result is ready + } + } + + public class CocosWebView extends WebView { + private static final String TAG = CocosWebViewHelper.class.getSimpleName(); + + private int mViewTag; + private String mJSScheme; + + public CocosWebView(Context context) { + this(context, -1); + } + + @SuppressLint("SetJavaScriptEnabled") + public CocosWebView(Context context, int viewTag) { + super(context); + this.mViewTag = viewTag; + this.mJSScheme = ""; + + this.setFocusable(true); + this.setFocusableInTouchMode(true); + + this.getSettings().setSupportZoom(false); + + this.getSettings().setDomStorageEnabled(true); + this.getSettings().setJavaScriptEnabled(true); + + // `searchBoxJavaBridge_` has big security risk. http://jvn.jp/en/jp/JVN53768697 + try { + Method method = this.getClass().getMethod("removeJavascriptInterface", new Class[]{String.class}); + method.invoke(this, "searchBoxJavaBridge_"); + } catch (Exception e) { + Log.d(TAG, "This API level do not support `removeJavascriptInterface`"); + } + + this.setWebViewClient(new Cocos2dxWebViewClient()); + this.setWebChromeClient(new WebChromeClient()); + } + + public void setJavascriptInterfaceScheme(String scheme) { + this.mJSScheme = scheme != null ? scheme : ""; + } + + public void setScalesPageToFit(boolean scalesPageToFit) { + this.getSettings().setSupportZoom(scalesPageToFit); + } + + class Cocos2dxWebViewClient extends WebViewClient { + @Override + public boolean shouldOverrideUrlLoading(WebView view, final String urlString) { + try { + URI uri = URI.create(urlString); + if (uri != null && uri.getScheme().equals(mJSScheme)) { + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + CocosWebViewHelper._onJsCallback(mViewTag, urlString); + } + }); + return true; + } + } catch (Exception e) { + Log.d(TAG, "Failed to create URI from url"); + } + + boolean[] result = new boolean[] { true }; + CountDownLatch latch = new CountDownLatch(1); + + // run worker on cocos thread + GlobalObject.runOnUiThread(new ShouldStartLoadingWorker(latch, result, mViewTag, urlString)); + + // wait for result from cocos thread + try { + latch.await(); + } catch (InterruptedException ex) { + Log.d(TAG, "'shouldOverrideUrlLoading' failed"); + } + + return result[0]; + } + + @Override + public void onPageFinished(WebView view, final String url) { + super.onPageFinished(view, url); + + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + CocosWebViewHelper._didFinishLoading(mViewTag, url); + } + }); + } + + @Override + public void onReceivedError(WebView view, int errorCode, String description, final String failingUrl) { + super.onReceivedError(view, errorCode, description, failingUrl); + + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + CocosWebViewHelper._didFailLoading(mViewTag, failingUrl); + } + }); + } + } + + public void setWebViewRect(int left, int top, int maxWidth, int maxHeight) { + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT); + layoutParams.leftMargin = left; + layoutParams.topMargin = top; + layoutParams.width = maxWidth; + layoutParams.height = maxHeight; + this.setLayoutParams(layoutParams); + } + } diff --git a/cocos/platform/android/java/src/com/cocos/lib/CocosWebViewHelper.java b/cocos/platform/android/java/src/com/cocos/lib/CocosWebViewHelper.java new file mode 100755 index 0000000..43debe9 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/CocosWebViewHelper.java @@ -0,0 +1,310 @@ +/**************************************************************************** + Copyright (c) 2010-2011 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import android.graphics.Color; +import android.os.Handler; +import android.os.Looper; +import android.util.SparseArray; +import android.view.View; +import android.webkit.WebView; +import android.widget.FrameLayout; +import android.widget.PopupWindow; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + + +public class CocosWebViewHelper { + private static final String TAG = CocosWebViewHelper.class.getSimpleName(); + private static Handler sHandler; + private static PopupWindow sPopUp; + private static FrameLayout sLayout; + + private static SparseArray webViews; + private static int viewTag = 0; + + public CocosWebViewHelper(FrameLayout layout) { + + CocosWebViewHelper.sLayout = layout; + CocosWebViewHelper.sHandler = new Handler(Looper.myLooper()); + + CocosWebViewHelper.webViews = new SparseArray(); + } + + private static native boolean shouldStartLoading(int index, String message); + private static native void didFinishLoading(int index, String message); + private static native void didFailLoading(int index, String message); + private static native void onJsCallback(int index, String message); + + public static boolean _shouldStartLoading(int index, String message) { return !shouldStartLoading(index, message); } + public static void _didFinishLoading(int index, String message) { didFinishLoading(index, message); } + public static void _didFailLoading(int index, String message) { didFailLoading(index, message); } + public static void _onJsCallback(int index, String message) { onJsCallback(index, message); } + + public static int createWebView() { + final int index = viewTag; + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = new CocosWebView(GlobalObject.getContext(), index); + FrameLayout.LayoutParams lParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + sLayout.addView(webView, lParams); + + webViews.put(index, webView); + } + }); + return viewTag++; + } + + public static void removeWebView(final int index) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webViews.remove(index); + sLayout.removeView(webView); + webView.destroy(); + webView = null; + } + } + }); + } + + public static void setVisible(final int index, final boolean visible) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + }); + } + + public static void setWebViewRect(final int index, final int left, final int top, final int maxWidth, final int maxHeight) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.setWebViewRect(left, top, maxWidth, maxHeight); + } + } + }); + } + + public static void setBackgroundTransparent(final int index, final boolean isTransparent) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.setBackgroundColor(isTransparent ? Color.TRANSPARENT : Color.WHITE); + webView.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null); + } + } + }); + } + + public static void setJavascriptInterfaceScheme(final int index, final String scheme) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.setJavascriptInterfaceScheme(scheme); + } + } + }); + } + + public static void loadData(final int index, final String data, final String mimeType, final String encoding, final String baseURL) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.loadDataWithBaseURL(baseURL, data, mimeType, encoding, null); + } + } + }); + } + + public static void loadHTMLString(final int index, final String data, final String baseUrl) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.loadDataWithBaseURL(baseUrl, data, null, null, null); + } + } + }); + } + + public static void loadUrl(final int index, final String url) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.loadUrl(url); + } + } + }); + } + + public static void loadFile(final int index, final String filePath) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.loadUrl(filePath); + } + } + }); + } + + public static void stopLoading(final int index) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.stopLoading(); + } + } + }); + + } + + public static void reload(final int index) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.reload(); + } + } + }); + } + + public static T callInMainThread(Callable call) throws ExecutionException, InterruptedException { + FutureTask task = new FutureTask(call); + sHandler.post(task); + return task.get(); + } + + public static boolean canGoBack(final int index) { + Callable callable = new Callable() { + @Override + public Boolean call() throws Exception { + CocosWebView webView = webViews.get(index); + return webView != null && webView.canGoBack(); + } + }; + try { + return callInMainThread(callable); + } catch (ExecutionException e) { + return false; + } catch (InterruptedException e) { + return false; + } + } + + public static boolean canGoForward(final int index) { + Callable callable = new Callable() { + @Override + public Boolean call() throws Exception { + CocosWebView webView = webViews.get(index); + return webView != null && webView.canGoForward(); + } + }; + try { + return callInMainThread(callable); + } catch (ExecutionException e) { + return false; + } catch (InterruptedException e) { + return false; + } + } + + public static void goBack(final int index) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.goBack(); + } + } + }); + } + + public static void goForward(final int index) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.goForward(); + } + } + }); + } + + public static void evaluateJS(final int index, final String js) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.loadUrl("javascript:" + js); + } + } + }); + } + + public static void setScalesPageToFit(final int index, final boolean scalesPageToFit) { + GlobalObject.runOnUiThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.setScalesPageToFit(scalesPageToFit); + } + } + }); + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/GlobalObject.java b/cocos/platform/android/java/src/com/cocos/lib/GlobalObject.java new file mode 100644 index 0000000..ea9e94a --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/GlobalObject.java @@ -0,0 +1,80 @@ +/**************************************************************************** + Copyright (c) 2020 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import android.app.Activity; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; + +public class GlobalObject { + private static Context sContext = null; + private static Activity sActivity = null; + private static Handler sHandler = null; + private static Thread sUiThread = null; + + // Should be invoked in UI thread. The parameter `context` and `activity` could be the same value. + public static void init(Context context, Activity activity) { + sContext = context; + sActivity = activity; + sHandler = new Handler(Looper.getMainLooper()); + sUiThread = Thread.currentThread(); + if (sUiThread != Looper.getMainLooper().getThread()) { + throw new RuntimeException("GlobalObject.init should be invoked in UI thread"); + } + } + + public static void destroy() { + sContext = null; + sActivity = null; + if (sHandler != null) { + sHandler.removeCallbacksAndMessages(null); + } + sHandler = null; + } + + public static Activity getActivity() { + return sActivity; + } + + public static Context getContext() { + return sContext; + } + + /** + * Runs the specified action on the UI thread. If the current thread is the UI + * thread, then the action is executed immediately. If the current thread is + * not the UI thread, the action is posted to the event queue of the UI thread. + * This method keeps the same logic as which in Activity.runOnUiThread. + * https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-13.0.0_r37/core/java/android/app/Activity.java#7364 + * @param action the action to run on the UI thread + */ + public static void runOnUiThread(Runnable action) { + if (Thread.currentThread() != sUiThread) { + sHandler.post(action); + } else { + action.run(); + } + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/JsbBridge.java b/cocos/platform/android/java/src/com/cocos/lib/JsbBridge.java new file mode 100644 index 0000000..375ae8e --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/JsbBridge.java @@ -0,0 +1,61 @@ +/**************************************************************************** + Copyright (c) 2018-2021 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +public class JsbBridge { + public interface ICallback{ + /** + * Applies this callback to the given argument. + * + * @param arg0 as input + * @param arg1 as input + */ + void onScript(String arg0, String arg1); + } + private static ICallback callback; + + private static void callByScript(String arg0, String arg1){ + if(JsbBridge.callback != null) + callback.onScript(arg0, arg1); + } + + /**Add a callback which you would like to apply + * @param f ICallback, the method which will be actually applied. multiple calls will override + * */ + public static void setCallback(ICallback f){ + JsbBridge.callback = f; + } + /** + * Java dispatch Js event, use native c++ code + * @param arg0 input values + */ + private static native void nativeSendToScript(String arg0, String arg1); + public static void sendToScript(String arg0, String arg1){ + nativeSendToScript(arg0, arg1); + } + public static void sendToScript(String arg0){ + nativeSendToScript(arg0, null); + } +} \ No newline at end of file diff --git a/cocos/platform/android/java/src/com/cocos/lib/JsbBridgeWrapper.java b/cocos/platform/android/java/src/com/cocos/lib/JsbBridgeWrapper.java new file mode 100644 index 0000000..6bc71bb --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/JsbBridgeWrapper.java @@ -0,0 +1,109 @@ +/**************************************************************************** + Copyright (c) 2018-2021 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import java.util.ArrayList; +import java.util.HashMap; + +public class JsbBridgeWrapper { + //Interface for listener, should be implemented and dispatched + public interface OnScriptEventListener { + void onScriptEvent(String arg); + } + /** + * Get the instance of JsbBridgetWrapper + */ + public static JsbBridgeWrapper getInstance() { + if (instance == null) { + instance = new JsbBridgeWrapper(); + } + return instance; + } + /** + * Add a listener to specified event, if the event does not exist, the wrapper will create one. Concurrent listener will be ignored + */ + public void addScriptEventListener(String eventName, OnScriptEventListener listener) { + if (eventMap.get(eventName) == null) { + eventMap.put(eventName, new ArrayList()); + } + eventMap.get(eventName).add(listener); + } + /** + * Remove listener for specified event, concurrent event will be deleted. Return false only if the event does not exist + */ + public boolean removeScriptEventListener(String eventName, OnScriptEventListener listener) { + ArrayList arr = eventMap.get(eventName); + if (arr == null) { + return false; + } + arr.remove(listener); + return true; + } + /** + * Remove all listener for event specified. + */ + public void removeAllListenersForEvent(String eventName) { + this.eventMap.remove(eventName); + } + /** + * Remove all event registered. Use it carefully! + */ + public void removeAllListeners() { + this.eventMap.clear(); + } + /** + * Dispatch the event with argument, the event should be registered in javascript, or other script language in future. + */ + public void dispatchEventToScript(String eventName, String arg) { + JsbBridge.sendToScript(eventName, arg); + } + /** + * Dispatch the event which is registered in javascript, or other script language in future. + */ + public void dispatchEventToScript(String eventName) { + JsbBridge.sendToScript(eventName); + } + + private JsbBridgeWrapper() { + JsbBridge.setCallback(new JsbBridge.ICallback() { + @Override + public void onScript(String arg0, String arg1) { + triggerEvents(arg0, arg1); + } + }); + } + + private final HashMap> eventMap = new HashMap<>(); + private static JsbBridgeWrapper instance; + + private void triggerEvents(String eventName, String arg) { + ArrayList arr = eventMap.get(eventName); + if (arr == null) + return; + for (OnScriptEventListener m : arr) { + m.onScriptEvent(arg); + } + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/Utils.java b/cocos/platform/android/java/src/com/cocos/lib/Utils.java new file mode 100644 index 0000000..5c74872 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/Utils.java @@ -0,0 +1,57 @@ +/**************************************************************************** + Copyright (c) 2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import android.os.Build; +import android.view.View; + +public class Utils { + + public static void hideVirtualButton() { + if (Build.VERSION.SDK_INT >= 19 && + null != GlobalObject.getActivity()) { + // use reflection to remove dependence of API level + + Class viewClass = View.class; + final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = CocosReflectionHelper.getConstantValue(viewClass, "SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION"); + final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = CocosReflectionHelper.getConstantValue(viewClass, "SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN"); + final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = CocosReflectionHelper.getConstantValue(viewClass, "SYSTEM_UI_FLAG_HIDE_NAVIGATION"); + final int SYSTEM_UI_FLAG_FULLSCREEN = CocosReflectionHelper.getConstantValue(viewClass, "SYSTEM_UI_FLAG_FULLSCREEN"); + final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = CocosReflectionHelper.getConstantValue(viewClass, "SYSTEM_UI_FLAG_IMMERSIVE_STICKY"); + final int SYSTEM_UI_FLAG_LAYOUT_STABLE = CocosReflectionHelper.getConstantValue(viewClass, "SYSTEM_UI_FLAG_LAYOUT_STABLE"); + + // getWindow().getDecorView().setSystemUiVisibility(); + final Object[] parameters = new Object[]{SYSTEM_UI_FLAG_LAYOUT_STABLE + | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar + | SYSTEM_UI_FLAG_FULLSCREEN // hide status bar + | SYSTEM_UI_FLAG_IMMERSIVE_STICKY}; + CocosReflectionHelper.invokeInstanceMethod(GlobalObject.getActivity().getWindow().getDecorView(), + "setSystemUiVisibility", + new Class[]{Integer.TYPE}, + parameters); + } + } +} \ No newline at end of file diff --git a/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosDelegatingSSLSocketFactory.java b/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosDelegatingSSLSocketFactory.java new file mode 100644 index 0000000..4b2d6b2 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosDelegatingSSLSocketFactory.java @@ -0,0 +1,65 @@ +package com.cocos.lib.websocket; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +/** + * An SSL socket factory that forwards all calls to a delegate. Override {@link + * #configureSocket} to customize a created socket before it is returned. + */ +class CocosDelegatingSSLSocketFactory extends SSLSocketFactory { + protected final SSLSocketFactory delegate; + + CocosDelegatingSSLSocketFactory(SSLSocketFactory delegate) { + this.delegate = delegate; + } + + @Override + public String[] getDefaultCipherSuites() { + return delegate.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket(Socket socket, String host, int port, + boolean autoClose) throws IOException { + return configureSocket( + (SSLSocket)delegate.createSocket(socket, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return configureSocket((SSLSocket)delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, + int localPort) throws IOException { + return configureSocket( + (SSLSocket)delegate.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return configureSocket((SSLSocket)delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, + InetAddress localAddress, int localPort) + throws IOException { + return configureSocket((SSLSocket)delegate.createSocket( + address, port, localAddress, localPort)); + } + + protected SSLSocket configureSocket(SSLSocket socket) throws IOException { + return socket; + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosGzipRequestInterceptor.java b/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosGzipRequestInterceptor.java new file mode 100644 index 0000000..29cc9aa --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosGzipRequestInterceptor.java @@ -0,0 +1,50 @@ +package com.cocos.lib.websocket; + +import org.cocos2dx.okhttp3.Interceptor; +import org.cocos2dx.okhttp3.MediaType; +import org.cocos2dx.okhttp3.Request; +import org.cocos2dx.okhttp3.RequestBody; +import org.cocos2dx.okhttp3.Response; +import org.cocos2dx.okio.BufferedSink; +import org.cocos2dx.okio.GzipSink; +import org.cocos2dx.okio.Okio; +import java.io.IOException; + +public class CocosGzipRequestInterceptor implements Interceptor { + @Override + public Response intercept(Chain chain) throws IOException { + Request originalRequest = chain.request(); + if (originalRequest.body() == null || + originalRequest.header("Content-Encoding") != null) { + return chain.proceed(originalRequest); + } + + Request compressedRequest = + originalRequest.newBuilder() + .header("Content-Encoding", "gzip") + .method(originalRequest.method(), gzip(originalRequest.body())) + .build(); + return chain.proceed(compressedRequest); + } + + private RequestBody gzip(final RequestBody body) { + return new RequestBody() { + @Override + public MediaType contentType() { + return body.contentType(); + } + + @Override + public long contentLength() { + return -1; // 无法提前知道压缩后的数据大小 + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); + body.writeTo(gzipSink); + gzipSink.close(); + } + }; + } +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosWebSocket.java b/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosWebSocket.java new file mode 100644 index 0000000..36082fd --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosWebSocket.java @@ -0,0 +1,346 @@ +package com.cocos.lib.websocket; + +import android.os.Build; +import android.util.Log; + +import com.cocos.lib.GlobalObject; + +import org.cocos2dx.okhttp3.CipherSuite; +import org.cocos2dx.okhttp3.Dispatcher; +import org.cocos2dx.okhttp3.OkHttpClient; +import org.cocos2dx.okhttp3.Protocol; +import org.cocos2dx.okhttp3.Request; +import org.cocos2dx.okhttp3.Response; +import org.cocos2dx.okhttp3.WebSocketListener; +import org.cocos2dx.okio.ByteString; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.security.GeneralSecurityException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +@SuppressWarnings("unused") +public class CocosWebSocket extends WebSocketListener { + private final static String _TAG = "cocos-websocket"; + + static { + NativeInit(); + } + + private static class _WebSocketContext { + long identifier; + long handlerPtr; + } + private static Dispatcher dispatcher = null; + + private final long _timeout; + private final boolean _perMessageDeflate; + private final boolean _tcpNoDelay; + private final String[] _header; + private final _WebSocketContext _wsContext = + new _WebSocketContext(); + + private org.cocos2dx.okhttp3.WebSocket _webSocket; + private OkHttpClient _client; + + CocosWebSocket(long ptr, long handler, String[] header, boolean tcpNoDelay, + boolean perMessageDeflate, long timeout) { + _wsContext.identifier = ptr; + _wsContext.handlerPtr = handler; + _header = header; + _tcpNoDelay = tcpNoDelay; + _perMessageDeflate = perMessageDeflate; + _timeout = timeout; + } + + private void _removeHandler() { + synchronized (_wsContext) { + _wsContext.identifier = 0; + _wsContext.handlerPtr = 0; + } + } + + private void _send(final byte[] msg) { + // Log.d(_TAG, "try sending binary msg"); + if (null == _webSocket) { + Log.e(_TAG, "WebSocket hasn't connected yet"); + return; + } + + ByteString byteString = ByteString.of(msg); + _webSocket.send(byteString); + } + + private void _send(final String msg) { + // Log.d(_TAG, "try sending string msg: " + msg); + if (null == _webSocket) { + Log.e(_TAG, "WebSocket hasn't connected yet"); + return; + } + + _webSocket.send(msg); + } + + /** + * Returns the VM's default SSL socket factory, using {@code trustManager} for + * trusted root certificates. + */ + private SSLSocketFactory + defaultSslSocketFactory(X509TrustManager trustManager) + throws NoSuchAlgorithmException, KeyManagementException { + SSLContext sslContext = SSLContext.getInstance("TLS"); + SecureRandom random; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + random = SecureRandom.getInstanceStrong(); + } else { + random = SecureRandom.getInstance("SHA1PRNG"); + } + sslContext.init(null, new TrustManager[] {trustManager}, random); + return sslContext.getSocketFactory(); + } + + private String[] javaNames(List cipherSuites) { + if (cipherSuites == null) { + return new String[0]; + } else { + String[] result = new String[cipherSuites.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = cipherSuites.get(i).javaName(); + } + return result; + } + } + + private void _connect(final String url, final String protocols, + final String caFilePath) { + Log.d(_TAG, "connect ws url: '" + url + "' ,protocols: '" + protocols + "' ,ca_: '" + caFilePath + "'"); + Request.Builder requestBuilder = new Request.Builder().url(url); + URI uriObj = null; + try { + requestBuilder = requestBuilder.url(url.trim()); + uriObj = URI.create(url); + } catch (NullPointerException | IllegalArgumentException e) { + synchronized (_wsContext) { + nativeOnError("invalid url", _wsContext.identifier, + _wsContext.handlerPtr); + } + return; + } + if (!protocols.isEmpty()) { + requestBuilder.addHeader("Sec-WebSocket-Protocol", protocols); + } + if (_header != null) { + for (int index = 0; index < _header.length; index += 2) { + requestBuilder.header(_header[index], _header[index + 1]); + } + } + + String originProtocol =uriObj.getScheme().toLowerCase(); + String uriScheme = (originProtocol.equals("wss") || originProtocol.equals("https"))? "https" : "http"; + requestBuilder.addHeader("Origin", uriScheme + "://" + uriObj.getHost() + (uriObj.getPort() < 0 ? "" : ":" + uriObj.getPort())); + + Request request = requestBuilder.build(); + + if(dispatcher == null) { + dispatcher = new Dispatcher(); + } + + OkHttpClient.Builder builder = + new OkHttpClient.Builder() + .dispatcher(dispatcher) + .protocols(Collections.singletonList(Protocol.HTTP_1_1)) + .readTimeout(_timeout, TimeUnit.MILLISECONDS) + .writeTimeout(_timeout, TimeUnit.MILLISECONDS) + .connectTimeout(_timeout, TimeUnit.MILLISECONDS); + + if (_perMessageDeflate) { + // 开启压缩扩展, 开启 Gzip 压缩 + builder.addInterceptor(new CocosGzipRequestInterceptor()); + } + KeyStore keyStore = null; + if (url.toLowerCase().startsWith("wss://") && !caFilePath.isEmpty()) { + try { + InputStream caInput = null; + + if (caFilePath.startsWith("assets/")) { + caInput = GlobalObject.getContext().getResources().getAssets().open(caFilePath); + } else { + caInput = new FileInputStream(caFilePath); + } + if (caFilePath.toLowerCase().endsWith(".pem")) { + keyStore = CocosWebSocketUtils.GetPEMKeyStore(caInput); + } else { + keyStore = CocosWebSocketUtils.GetCERKeyStore(caInput); + } + } catch (Exception e) { + e.printStackTrace(); + String errMsg = e.getMessage(); + if (errMsg == null) { + errMsg = "unknown error"; + } + synchronized (_wsContext) { + nativeOnError(errMsg, _wsContext.identifier, _wsContext.handlerPtr); + } + return; + } + + builder.hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + Log.d(_TAG, "ca hostname: " + hostname); + HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); + return hv.verify(hostname, session); + } + }); + } + if (url.toLowerCase().startsWith("wss://") || _tcpNoDelay) { + try { + X509TrustManager trustManager = + CocosWebSocketUtils.GetTrustManager(keyStore); + SSLContext sslContext = SSLContext.getInstance("TLS"); + SecureRandom random; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + random = SecureRandom.getInstanceStrong(); + } else { + random = SecureRandom.getInstance("SHA1PRNG"); + } + sslContext.init(null, new TrustManager[] {trustManager}, random); + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + SSLSocketFactory customSslSocketFactory = + new CocosDelegatingSSLSocketFactory(sslSocketFactory) { + @Override + protected SSLSocket configureSocket(SSLSocket socket) + throws IOException { + socket.setTcpNoDelay(_tcpNoDelay); + // TLSv1.2 is disabled default below API20---- + // https://developer.android.com/reference/javax/net/ssl/SSLSocket + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH) { + socket.setEnabledProtocols(new String[] {"TLSv1.2"}); + } + return socket; + } + }; + builder.sslSocketFactory(customSslSocketFactory, trustManager); + } catch (GeneralSecurityException e) { + e.printStackTrace(); + String errMsg = e.getMessage(); + if (errMsg == null) { + errMsg = "unknown error"; + } + synchronized (_wsContext) { + nativeOnError(errMsg, _wsContext.identifier, _wsContext.handlerPtr); + } + return; + } + } + _client = builder.build(); + _webSocket = _client.newWebSocket(request, this); + } + + private void _close(final int code, final String reason) { + _webSocket.close(code, reason); + // _client.dispatcher().executorService().shutdown(); + } + + private long _getBufferedAmountID() { + return _webSocket.queueSize(); + } + + private void output(final String content) { + Log.w(_TAG, content); + } + + @Override + public void onOpen(org.cocos2dx.okhttp3.WebSocket _webSocket, Response response) { + output("WebSocket onOpen _client: " + _client); + synchronized (_wsContext) { + nativeOnOpen(response.protocol().toString(), + response.headers().toString(), _wsContext.identifier, + _wsContext.handlerPtr); + } + } + + @Override + public void onMessage(org.cocos2dx.okhttp3.WebSocket _webSocket, String text) { + // output("Receiving string msg: " + text); + synchronized (_wsContext) { + nativeOnStringMessage(text, _wsContext.identifier, _wsContext.handlerPtr); + } + } + + @Override + public void onMessage(org.cocos2dx.okhttp3.WebSocket _webSocket, ByteString bytes) { + // output("Receiving binary msg"); + synchronized (_wsContext) { + nativeOnBinaryMessage(bytes.toByteArray(), _wsContext.identifier, + _wsContext.handlerPtr); + } + } + + @Override + public void onClosing(org.cocos2dx.okhttp3.WebSocket _webSocket, int code, + String reason) { + output("Closing : " + code + " / " + reason); + if (_webSocket != null) { + _webSocket.close(code, reason); + } + } + + @Override + public void onFailure(org.cocos2dx.okhttp3.WebSocket _webSocket, Throwable t, + Response response) { + String msg = ""; + if (t != null) { + msg = t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage(); + } + output("onFailure Error : " + msg); + synchronized (_wsContext) { + nativeOnError(msg, _wsContext.identifier, _wsContext.handlerPtr); + } + } + + @Override + public void onClosed(org.cocos2dx.okhttp3.WebSocket _webSocket, int code, + String reason) { + output("onClosed : " + code + " / " + reason); + synchronized (_wsContext) { + nativeOnClosed(code, reason, _wsContext.identifier, + _wsContext.handlerPtr); + } + } + + private static native void NativeInit(); + + private native void nativeOnStringMessage(final String msg, long identifier, + long handler); + + private native void nativeOnBinaryMessage(final byte[] msg, long identifier, + long handler); + + private native void nativeOnOpen(final String protocol, + final String headerString, long identifier, + long handler); + + private native void nativeOnClosed(final int code, final String reason, + long identifier, long handler); + + private native void nativeOnError(final String msg, long identifier, + long handler); +} diff --git a/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosWebSocketUtils.java b/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosWebSocketUtils.java new file mode 100644 index 0000000..cd31d04 --- /dev/null +++ b/cocos/platform/android/java/src/com/cocos/lib/websocket/CocosWebSocketUtils.java @@ -0,0 +1,86 @@ +package com.cocos.lib.websocket; + +import android.util.Base64; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.Arrays; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +class CocosWebSocketUtils { + static X509TrustManager GetTrustManager(KeyStore keyStore) + throws GeneralSecurityException { + String algorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(algorithm); + trustManagerFactory.init(keyStore); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || + !(trustManagers[0] instanceof X509TrustManager)) { + String prefixErrorMessage = "Unexpected default trust managers:"; + throw new IllegalStateException(prefixErrorMessage + + Arrays.toString(trustManagers)); + } + return (X509TrustManager)trustManagers[0]; + } + + static KeyStore GetCERKeyStore(InputStream inputStream) + throws CertificateException, KeyStoreException, IOException, + NoSuchAlgorithmException { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + Certificate certificate = factory.generateCertificate(inputStream); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null); + keyStore.setCertificateEntry("0", certificate); + return keyStore; + } + + static KeyStore GetPEMKeyStore(InputStream inputStream) throws Exception { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null); + int index = 0; + + BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(inputStream)); + String carBegin; + while ((carBegin = bufferedReader.readLine()) != null) { + if (carBegin.contains("BEGIN CERTIFICATE")) { + StringBuilder stringBuilder = new StringBuilder(); + while ((carBegin = bufferedReader.readLine()) != null) { + if (carBegin.contains("END CERTIFICATE")) { + String hexString = stringBuilder.toString(); + byte[] bytes = Base64.decode(hexString, Base64.DEFAULT); + Certificate certificate = _GenerateCertificateFromDER(bytes); + keyStore.setCertificateEntry(Integer.toString(index++), + certificate); + break; + } else { + stringBuilder.append(carBegin); + } + } + } + } + bufferedReader.close(); + if (index == 0) { + throw new IllegalArgumentException("No CERTIFICATE found"); + } + return keyStore; + } + + private static Certificate _GenerateCertificateFromDER(byte[] certBytes) + throws CertificateException { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + return factory.generateCertificate(new ByteArrayInputStream(certBytes)); + } +} diff --git a/cocos/platform/android/jni/JniCocosEntry.cpp b/cocos/platform/android/jni/JniCocosEntry.cpp new file mode 100644 index 0000000..9334f01 --- /dev/null +++ b/cocos/platform/android/jni/JniCocosEntry.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include + +#include "game-activity/native_app_glue/android_native_app_glue.h" +#include "platform/android/AndroidPlatform.h" +#include "platform/java/jni/JniHelper.h" + +extern "C" { + +void android_main(struct android_app *app) { + auto *platform = cc::BasePlatform::getPlatform(); + auto *androidPlatform = static_cast(platform); + androidPlatform->setAndroidApp(app); + androidPlatform->init(); + androidPlatform->run(0, nullptr); +} + +//NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosActivity_onCreateNative(JNIEnv *env, jobject activity) { + cc::JniHelper::init(env, activity); +} +} diff --git a/cocos/platform/android/jni/JniCocosSurfaceView.cpp b/cocos/platform/android/jni/JniCocosSurfaceView.cpp new file mode 100644 index 0000000..d4508f5 --- /dev/null +++ b/cocos/platform/android/jni/JniCocosSurfaceView.cpp @@ -0,0 +1,263 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include +#include +#include +#include +#include +#include "application/ApplicationManager.h" +#include "base/memory/Memory.h" +#include "engine/EngineEvents.h" +#include "platform/android/AndroidPlatform.h" +#include "platform/java/modules/SystemWindow.h" +#include "platform/java/modules/SystemWindowManager.h" + +namespace { +struct cc::TouchEvent touchEvent; + +class NativeWindowCache { +public: + explicit NativeWindowCache(int windowId) : _windowId{windowId} { + } + + ~NativeWindowCache() { + setSurface(nullptr); + } + + void setSurface(jobject surface) { + if (_nativeWindow != nullptr) { + ANativeWindow_release(_nativeWindow); + } + if (surface != nullptr) { + _nativeWindow = ANativeWindow_fromSurface(env, surface); + } else { + _nativeWindow = nullptr; + } + } + + ANativeWindow *getNativeWindow() { + return _nativeWindow; + } + + int getWindowId() const { + return _windowId; + } + + JNIEnv *env; + +private: + int _windowId{0}; + ANativeWindow *_nativeWindow{nullptr}; +}; +} // namespace + +//#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "JniCocosSurfaceView JNI", __VA_ARGS__) + +extern "C" { + +JNIEXPORT jlong Java_com_cocos_lib_CocosSurfaceView_constructNative(JNIEnv *env, jobject /*thiz*/, jint windowId) { // NOLINT JNI function name + auto *cache = ccnew NativeWindowCache(windowId); + cache->env = env; + return reinterpret_cast(cache); +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_destructNative(JNIEnv * /*env*/, jobject /*thiz*/, jlong handle) { // NOLINT JNI function name + auto *windowCache = reinterpret_cast(handle); + CC_SAFE_DELETE(windowCache); +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_onSizeChangedNative(JNIEnv * /*env*/, jobject /*thiz*/, jint windowId, jint width, jint height) { // NOLINT JNI function name + auto func = [width, height, windowId]() -> void { + cc::events::Resize::broadcast(width, height, windowId); + }; + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func); +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_onSurfaceRedrawNeededNative(JNIEnv * /*env*/, jobject /*thiz*/, jlong handle) { // NOLINT JNI function name + // +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_onSurfaceCreatedNative(JNIEnv *env, jobject /*thiz*/, jlong handle, jobject surface) { // NOLINT JNI function name + CC_UNUSED_PARAM(env); + auto *windowCache = reinterpret_cast(handle); + ANativeWindow *oldNativeWindow = windowCache->getNativeWindow(); + windowCache->setSurface(surface); + ANativeWindow *nativeWindow = windowCache->getNativeWindow(); + + auto *platform = static_cast(cc::BasePlatform::getPlatform()); + auto *windowMgr = platform->getInterface(); + auto *iSysWindow = windowMgr->getWindow(windowCache->getWindowId()); + auto *sysWindow = static_cast(iSysWindow); + sysWindow->setWindowHandle(nativeWindow); + if (oldNativeWindow) { + auto func = [sysWindow]() -> void { + cc::events::WindowRecreated::broadcast(sysWindow->getWindowId()); + }; + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func); + } +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_onSurfaceChangedNative(JNIEnv *env, // NOLINT JNI function name + jobject /*thiz*/, + jlong handle, + jobject surface, + jint format, + jint width, + jint height) { + CC_UNUSED_PARAM(env); + CC_UNUSED_PARAM(format); + CC_UNUSED_PARAM(width); + CC_UNUSED_PARAM(height); + if (handle != 0) { + auto *windowCache = (NativeWindowCache *)handle; + ANativeWindow *oldNativeWindow = windowCache->getNativeWindow(); + // Fix for window being destroyed behind the scenes on older Android + // versions. + if (oldNativeWindow != nullptr) { + ANativeWindow_acquire(oldNativeWindow); + } + + windowCache->setSurface(surface); + ANativeWindow *newNativeWindow = windowCache->getNativeWindow(); + if (oldNativeWindow != newNativeWindow) { + auto *iSysWindow = CC_GET_PLATFORM_INTERFACE(cc::SystemWindowManager)->getWindow(windowCache->getWindowId()); + auto *sysWindow = static_cast(iSysWindow); + sysWindow->setWindowHandle(newNativeWindow); + + auto func = [sysWindow]() -> void { + cc::events::WindowRecreated::broadcast(sysWindow->getWindowId()); + }; + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func); + } + // Release the window we acquired earlier. + if (oldNativeWindow != nullptr) { + ANativeWindow_release(oldNativeWindow); + } + } +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosSurfaceView_onSurfaceDestroyedNative(JNIEnv *env, jobject /*thiz*/, jlong handle) { // NOLINT JNI function name + auto *windowCache = (NativeWindowCache *)handle; + ANativeWindow *nativeWindow = windowCache->getNativeWindow(); + + // todo: destroy gfx surface + auto func = [nativeWindow]() -> void { + auto *platform = static_cast(cc::BasePlatform::getPlatform()); + auto *windowMgr = platform->getInterface(); + cc::ISystemWindow *window = windowMgr->getWindowFromANativeWindow(nativeWindow); + + cc::events::WindowDestroy::broadcast(window->getWindowId()); + }; + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread(func); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL +Java_com_cocos_lib_CocosTouchHandler_handleActionDown(JNIEnv *env, // NOLINT JNI function name + jobject obj, + jint windowId, + jint id, + jfloat x, + jfloat y) { + CC_UNUSED_PARAM(env); + CC_UNUSED_PARAM(obj); + + touchEvent.windowId = windowId; + touchEvent.type = cc::TouchEvent::Type::BEGAN; + touchEvent.touches.emplace_back(x, y, id); + cc::events::Touch::broadcast(touchEvent); + touchEvent.touches.clear(); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosTouchHandler_handleActionUp(JNIEnv *env, // NOLINT JNI function name + jobject obj, + jint windowId, + jint id, + jfloat x, + jfloat y) { + CC_UNUSED_PARAM(env); + CC_UNUSED_PARAM(obj); + + touchEvent.windowId = windowId; + touchEvent.type = cc::TouchEvent::Type::ENDED; + touchEvent.touches.emplace_back(x, y, id); + cc::events::Touch::broadcast(touchEvent); + touchEvent.touches.clear(); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosTouchHandler_handleActionMove(JNIEnv *env, // NOLINT JNI function name + jobject obj, + jint windowId, + jintArray ids, + jfloatArray xs, + jfloatArray ys) { + CC_UNUSED_PARAM(obj); + + touchEvent.windowId = windowId; + touchEvent.type = cc::TouchEvent::Type::MOVED; + int size = env->GetArrayLength(ids); + jint id[size]; + jfloat x[size]; + jfloat y[size]; + + env->GetIntArrayRegion(ids, 0, size, id); + env->GetFloatArrayRegion(xs, 0, size, x); + env->GetFloatArrayRegion(ys, 0, size, y); + for (int i = 0; i < size; i++) { + touchEvent.touches.emplace_back(x[i], y[i], id[i]); + } + + cc::events::Touch::broadcast(touchEvent); + touchEvent.touches.clear(); +} + +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosTouchHandler_handleActionCancel(JNIEnv *env, // NOLINT JNI function name + jobject obj, + jint windowId, + jintArray ids, + jfloatArray xs, + jfloatArray ys) { + CC_UNUSED_PARAM(obj); + + touchEvent.windowId = windowId; + touchEvent.type = cc::TouchEvent::Type::CANCELLED; + int size = env->GetArrayLength(ids); + jint id[size]; + jfloat x[size]; + jfloat y[size]; + + env->GetIntArrayRegion(ids, 0, size, id); + env->GetFloatArrayRegion(xs, 0, size, x); + env->GetFloatArrayRegion(ys, 0, size, y); + for (int i = 0; i < size; i++) { + touchEvent.touches.emplace_back(x[i], y[i], id[i]); + } + cc::events::Touch::broadcast(touchEvent); + touchEvent.touches.clear(); +} +} diff --git a/cocos/platform/android/libcocos2dx/AndroidManifest.xml b/cocos/platform/android/libcocos2dx/AndroidManifest.xml new file mode 100644 index 0000000..b6ea80d --- /dev/null +++ b/cocos/platform/android/libcocos2dx/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/cocos/platform/android/libcocos2dx/build.gradle b/cocos/platform/android/libcocos2dx/build.gradle new file mode 100644 index 0000000..5c47318 --- /dev/null +++ b/cocos/platform/android/libcocos2dx/build.gradle @@ -0,0 +1,40 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger() + namespace 'com.cocos.lib' + defaultConfig { + minSdkVersion PROP_MIN_SDK_VERSION + targetSdkVersion PROP_TARGET_SDK_VERSION + versionCode 1 + versionName "1.0" + consumerProguardFiles 'proguard-rules.pro' + } + + buildFeatures { + buildConfig = true + } + + sourceSets.main { + aidl.srcDir "../java/src" + java.srcDir "../java/src" + manifest.srcFile "AndroidManifest.xml" + } + + buildDir = new File(rootProject.buildDir, project.name) + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + api fileTree(include: ['*.jar'], dir: '../java/libs') +} diff --git a/cocos/platform/android/libcocos2dx/proguard-rules.pro b/cocos/platform/android/libcocos2dx/proguard-rules.pro new file mode 100644 index 0000000..031a158 --- /dev/null +++ b/cocos/platform/android/libcocos2dx/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\developSoftware\Android\SDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: +-keep public class com.google.** { *; } +-keep public class androidx.** { *; } +-keep class com.cocos.lib.CocosActivity { + public ; + protected ; + private void createSurface(...); +} diff --git a/cocos/platform/android/libcocos2dx/src/main/res/values-zh-rCN/strings.xml b/cocos/platform/android/libcocos2dx/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000..0c0c264 --- /dev/null +++ b/cocos/platform/android/libcocos2dx/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,10 @@ + + + + 完成 + 下一个 + 搜索 + 前往 + 发送 + 请到系统设置关闭安全键盘 + diff --git a/cocos/platform/android/libcocos2dx/src/main/res/values/strings.xml b/cocos/platform/android/libcocos2dx/src/main/res/values/strings.xml new file mode 100644 index 0000000..97d6073 --- /dev/null +++ b/cocos/platform/android/libcocos2dx/src/main/res/values/strings.xml @@ -0,0 +1,10 @@ + + + + Done + Next + Search + Go + Send + Please go to the system settings to turn off the security keyboard! + diff --git a/cocos/platform/android/libcocosxr/AndroidManifest.xml b/cocos/platform/android/libcocosxr/AndroidManifest.xml new file mode 100644 index 0000000..2af5e78 --- /dev/null +++ b/cocos/platform/android/libcocosxr/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/cocos/platform/android/libcocosxr/build.gradle b/cocos/platform/android/libcocosxr/build.gradle new file mode 100644 index 0000000..18b2350 --- /dev/null +++ b/cocos/platform/android/libcocosxr/build.gradle @@ -0,0 +1,37 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger() + + defaultConfig { + minSdkVersion PROP_MIN_SDK_VERSION + targetSdkVersion PROP_TARGET_SDK_VERSION + versionCode 1 + versionName "1.0" + } + + sourceSets.main { + java.srcDirs "src" + res.srcDirs 'res' + jniLibs.srcDirs 'libs' + manifest.srcFile "AndroidManifest.xml" + } + + buildDir = new File(rootProject.buildDir, project.name) + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) + implementation project(':libcocos') +} diff --git a/cocos/platform/android/libcocosxr/proguard-rules.pro b/cocos/platform/android/libcocosxr/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/cocos/platform/android/libcocosxr/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRApi.java b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRApi.java new file mode 100644 index 0000000..a8e97a9 --- /dev/null +++ b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRApi.java @@ -0,0 +1,212 @@ +/**************************************************************************** + * Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib.xr; + +import android.app.Activity; +import android.app.Application; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.util.Log; + +import java.lang.ref.WeakReference; + +public class CocosXRApi { + private static final String TAG = "CocosXRApi"; + private final static String ACTION_ADB_CMD = "com.cocosxr.adb.cmd"; + private enum ActivityLifecycleType { + UnKnown, + Created, + Started, + Resumed, + Paused, + Stopped, + SaveInstanceState, + Destroyed + } + + private final static CocosXRApi instance = new CocosXRApi(); + + /** + * adb shell am broadcast -a com.cocosxr.adb.cmd --es CMD_KEY LOG --ei CMD_VALUE 1 + * adb shell am broadcast -a com.cocosxr.adb.cmd --es CMD_KEY LOG --es CMD_VALUE abc + */ + private class CocosXRActionReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_ADB_CMD.equals(intent.getAction())) { + // adb cmd + if (intent.getExtras() == null) { + Log.w(TAG, "[CocosXRActionReceiver] intent.getExtras() == null"); + return; + } + Object cmdKey = intent.getExtras().get("CMD_KEY"); + String key = cmdKey == null ? "" : cmdKey.toString(); + Object cmdValue = intent.getExtras().get("CMD_VALUE"); + String valueStr = null; + if (cmdValue instanceof Integer) { + valueStr = String.valueOf(intent.getIntExtra("CMD_VALUE", Integer.MIN_VALUE)); + } else if (cmdValue instanceof String) { + valueStr = intent.getStringExtra("CMD_VALUE"); + } + + try { + onAdbCmd(key, valueStr); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + } + + private CocosXRApi() { + } + + public static CocosXRApi getInstance() { + return instance; + } + + private Application application; + private WeakReference activityWeakReference; + private Context applicationContext; + private Application.ActivityLifecycleCallbacks activityLifecycleCallbacks; + private CocosXRActionReceiver actionReceiver; + private CocosXRWebViewManager webViewManager; + + public void onCreate(Activity activity) { + activityWeakReference = new WeakReference<>(activity); + application = activity.getApplication(); + applicationContext = activity.getApplicationContext(); + webViewManager = new CocosXRWebViewManager(); + if (activityLifecycleCallbacks == null) { + activityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + try { + onActivityLifecycleCallback(ActivityLifecycleType.Created.ordinal(), activity.getLocalClassName()); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @Override + public void onActivityStarted(Activity activity) { + webViewManager.onCreate(activity); + try { + onActivityLifecycleCallback(ActivityLifecycleType.Started.ordinal(), activity.getLocalClassName()); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @Override + public void onActivityResumed(Activity activity) { + try { + onActivityLifecycleCallback(ActivityLifecycleType.Resumed.ordinal(), activity.getLocalClassName()); + } catch (Throwable e) { + e.printStackTrace(); + } + webViewManager.onResume(); + } + + @Override + public void onActivityPaused(Activity activity) { + try { + onActivityLifecycleCallback(ActivityLifecycleType.Paused.ordinal(), activity.getLocalClassName()); + } catch (Throwable e) { + e.printStackTrace(); + } + webViewManager.onPause(); + } + + @Override + public void onActivityStopped(Activity activity) { + try { + onActivityLifecycleCallback(ActivityLifecycleType.Stopped.ordinal(), activity.getLocalClassName()); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + try { + onActivityLifecycleCallback(ActivityLifecycleType.SaveInstanceState.ordinal(), activity.getLocalClassName()); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @Override + public void onActivityDestroyed(Activity activity) { + try { + onActivityLifecycleCallback(ActivityLifecycleType.Destroyed.ordinal(), activity.getLocalClassName()); + } catch (Throwable e) { + e.printStackTrace(); + } + webViewManager.onDestroy(); + } + }; + } + application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks); + + if(actionReceiver != null) { + applicationContext.unregisterReceiver(actionReceiver); + actionReceiver =null; + } + + actionReceiver = new CocosXRActionReceiver(); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_ADB_CMD); + applicationContext.registerReceiver(actionReceiver, intentFilter); + } + + public void onDestroy() { + if (application != null && activityLifecycleCallbacks != null) { + application.unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks); + activityLifecycleCallbacks = null; + } + + if(applicationContext != null && actionReceiver != null) { + applicationContext.unregisterReceiver(actionReceiver); + actionReceiver = null; + } + } + + public Context getContext() { + return applicationContext; + } + + public Activity getActivity() { + return activityWeakReference.get(); + } + + // native + private native void onActivityLifecycleCallback(int id, String activityClassName); + private native void onAdbCmd(String key, String value); +} diff --git a/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRGLHelper.java b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRGLHelper.java new file mode 100644 index 0000000..19a411f --- /dev/null +++ b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRGLHelper.java @@ -0,0 +1,225 @@ +/**************************************************************************** + * Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + + +package com.cocos.lib.xr; + +import android.opengl.GLES11Ext; +import android.opengl.GLES30; +import android.util.Log; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +public class CocosXRGLHelper { + private static final String TAG = "CocosXRGLHelper"; + + public static int loadShader(int shaderType, String source) { + int shader = GLES30.glCreateShader(shaderType); + if (shader != 0) { + GLES30.glShaderSource(shader, source); + GLES30.glCompileShader(shader); + int[] compiled = new int[1]; + GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + Log.e(TAG, "Could not compile shader " + shaderType + ":"); + Log.e(TAG, GLES30.glGetShaderInfoLog(shader)); + GLES30.glDeleteShader(shader); + shader = 0; + } + } + return shader; + } + + public static int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + return 0; + } + checkGLError("vertex shader"); + + int pixelShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentSource); + if (pixelShader == 0) { + return 0; + } + checkGLError("fragment shader"); + int program = GLES30.glCreateProgram(); + if (program != 0) { + GLES30.glAttachShader(program, vertexShader); + checkGLError("glAttachShader vertexShader"); + GLES30.glAttachShader(program, pixelShader); + checkGLError("glAttachShader pixelShader"); + GLES30.glLinkProgram(program); + GLES30.glDetachShader(program, vertexShader); + GLES30.glDetachShader(program, pixelShader); + int[] linkStatus = new int[1]; + GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES30.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES30.glGetProgramInfoLog(program)); + GLES30.glDeleteProgram(program); + program = 0; + } + } + return program; + } + + private static final int SIZE_OF_FLOAT = 4; + private static final int SIZE_OF_SHORT = 2; + public static FloatBuffer createFloatBuffer(float[] array) { + ByteBuffer bb = ByteBuffer.allocateDirect(array.length * SIZE_OF_FLOAT); + bb.order(ByteOrder.nativeOrder()); + FloatBuffer fb = bb.asFloatBuffer(); + fb.put(array); + fb.position(0); + return fb; + } + + public static ShortBuffer createShortBuffer(short[] array) { + ByteBuffer bb = ByteBuffer.allocateDirect(array.length * SIZE_OF_SHORT); + bb.order(ByteOrder.nativeOrder()); + ShortBuffer fb = bb.asShortBuffer(); + fb.put(array); + fb.position(0); + return fb; + } + + public static void checkGLError(String operation) { + int errerCode = GLES30.glGetError(); + if (errerCode != GLES30.GL_NO_ERROR) { + String msg = operation + ":error" + errerCode; + Log.e(TAG, msg); + throw new RuntimeException(msg); + } + } + + public static int createOESTexture() { + int[] oesTex = new int[1]; + GLES30.glGenTextures(1, oesTex, 0); + GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTex[0]); + GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR); + GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR); + GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE); + GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE); + GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); + return oesTex[0]; + } + + public static class GLQuadScreen { + final String quadMeshVertexShader_EXT = + " #version 310 es\n in vec4 vertexPosition; \n " + + "in vec2 vertexTexCoord; \n " + + "out vec2 texCoord; \n " + + "uniform mat4 textureMatrix;\n" + + "void main() \n " + + "{" + + " gl_Position = vertexPosition; \n " + + " vec4 temp = vec4(vertexTexCoord.x, vertexTexCoord.y, 0, 1); \n" + + " texCoord = (textureMatrix * temp).xy; \n " + + "}"; + + final String quadFragmentShader_EXT = + "#version 310 es\n #extension GL_OES_EGL_image_external_essl3 : require \n" + + "precision mediump float; \n" + + "in vec2 texCoord; \n" + + "uniform samplerExternalOES texSampler2D; \n" + + "out vec4 frag_color;\n" + + "void main() \n" + + "{ \n" + + " frag_color = texture(texSampler2D, texCoord); \n" + + "}"; + float[] orthoQuadVertices = { + -1.0f, -1.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 1.0f}; + + float[] orthoQuadTexCoords_EXT = { + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 0.0f + }; + + short[] orthoQuadIndices = {0, 1, 2, 2, 3, 0}; + + ShortBuffer indexBuffer; + FloatBuffer vetexBuffer; + FloatBuffer textureCoordBuffer; + + int program = -1; + int vertexHandle = 0; + int textureCoordHandle = 0; + int textureMatrixHandle = 0; + + public GLQuadScreen() { + } + + public void initShader() { + if(program == -1) { + vetexBuffer = createFloatBuffer(orthoQuadVertices); + textureCoordBuffer = createFloatBuffer(orthoQuadTexCoords_EXT); + indexBuffer = createShortBuffer(orthoQuadIndices); + program = createProgram(quadMeshVertexShader_EXT, quadFragmentShader_EXT); + GLES30.glUseProgram(program); + vertexHandle = GLES30.glGetAttribLocation(program, "vertexPosition"); + textureCoordHandle = GLES30.glGetAttribLocation(program, "vertexTexCoord"); + textureMatrixHandle = GLES30.glGetUniformLocation(program, "textureMatrix"); + GLES30.glUseProgram(0); + } + Log.d(TAG, "GLQuadScreen Shader Info:" + program + "," + vertexHandle + "," + textureCoordHandle); + } + + public void release() { + GLES30.glDeleteProgram(program); + program = 0; + } + + public void draw(int oesTextureId, float[] videoTransformMatrix) { + if(program == -1) { + initShader(); + return; + } + + GLES30.glUseProgram(program); + GLES30.glVertexAttribPointer(vertexHandle, 4, GLES30.GL_FLOAT, false, 0, vetexBuffer); + GLES30.glVertexAttribPointer(textureCoordHandle, 2, GLES30.GL_FLOAT, false, 0, textureCoordBuffer); + + GLES30.glEnableVertexAttribArray(vertexHandle); + GLES30.glEnableVertexAttribArray(textureCoordHandle); + + GLES30.glActiveTexture(GLES30.GL_TEXTURE0); + GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTextureId); + + GLES30.glUniformMatrix4fv(textureMatrixHandle, 1, false, videoTransformMatrix, 0); + + GLES30.glDrawElements(GLES30.GL_TRIANGLES, indexBuffer.capacity(), GLES30.GL_UNSIGNED_SHORT, indexBuffer); + + GLES30.glDisableVertexAttribArray(vertexHandle); + GLES30.glDisableVertexAttribArray(textureCoordHandle); + GLES30.glUseProgram(0); + } + } +} diff --git a/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRVideoManager.java b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRVideoManager.java new file mode 100644 index 0000000..47dacde --- /dev/null +++ b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRVideoManager.java @@ -0,0 +1,484 @@ +/**************************************************************************** + * Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + + +package com.cocos.lib.xr; + +import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION; + +import android.app.Activity; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.opengl.GLES30; +import android.util.Log; +import com.cocos.lib.JsbBridgeWrapper; +import com.cocos.lib.xr.permission.CocosXRPermissionHelper; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +public class CocosXRVideoManager { + private static final String TAG = "CocosXRVideoManager"; + private final static CocosXRVideoManager instance = new CocosXRVideoManager(); + + class VideoEventData { + public String headTag; + public int eventId; + public String videoPlayerHandleKey; + public int videoSourceType; + public String videoSourceUrl; + public int videoWidth; + public int videoHeight; + public int videoTextureId; + public boolean isLoop; + public int seekToMsec; + public String eventName; + public float volume; + public float playbackSpeed = 1.0f; + + public VideoEventData(String data) { + String[] dataArray = data.split("&"); + if (dataArray[0].equals(XR_VIDEO_EVENT_TAG)) { + this.eventId = Integer.parseInt(dataArray[1]); + this.eventName = dataArray[2]; + this.videoPlayerHandleKey = dataArray[3]; + switch (this.eventId) { + case VIDEO_EVENT_PREPARE: { + this.videoSourceType = Integer.parseInt(dataArray[4]); + this.videoSourceUrl = dataArray[5]; + this.isLoop = Integer.parseInt(dataArray[6]) == 1; + this.volume = Float.parseFloat(dataArray[7]); + this.playbackSpeed = Float.parseFloat(dataArray[8]); + break; + } + + case VIDEO_EVENT_SEEK_TO: { + this.seekToMsec = Integer.parseInt(dataArray[4]); + break; + } + + case VIDEO_EVENT_SET_LOOP: { + this.isLoop = Integer.parseInt(dataArray[4]) == 1; + break; + } + + case VIDEO_EVENT_SET_VOLUME: { + this.volume = Float.parseFloat(dataArray[4]); + break; + } + + case VIDEO_EVENT_SET_TEXTURE_INFO: { + this.videoWidth = Integer.parseInt(dataArray[4]); + this.videoHeight = Integer.parseInt(dataArray[5]); + this.videoTextureId = Integer.parseInt(dataArray[6]); + break; + } + + case VIDEO_EVENT_SET_SPEED: { + this.playbackSpeed = Float.parseFloat(dataArray[4]); + break; + } + } + } + } + } + + private CocosXRVideoManager() { + } + + public static CocosXRVideoManager getInstance() { + return instance; + } + + final int MAX_COUNT = 3; + public static final int VIDEO_SOURCE_TYPE_LOCAL = 1; + public static final int VIDEO_SOURCE_TYPE_REMOTE = 2; + + public static final int VIDEO_EVENT_INVALID = 1; + //xr-event&id&handle& + public static final int VIDEO_EVENT_PREPARE = 2; + public static final int VIDEO_EVENT_PLAY = 3; + public static final int VIDEO_EVENT_PAUSE = 4; + public static final int VIDEO_EVENT_STOP = 5; + public static final int VIDEO_EVENT_RESET = 6; + public static final int VIDEO_EVENT_DESTROY = 7; + + public static final int VIDEO_EVENT_GET_POSITION = 30; + public static final int VIDEO_EVENT_GET_DURATION = 31; + public static final int VIDEO_EVENT_GET_IS_PALYING = 32; + public static final int VIDEO_EVENT_GET_IS_LOOPING = 33; + + public static final int VIDEO_EVENT_SET_LOOP = 50; + public static final int VIDEO_EVENT_SEEK_TO = 51; + public static final int VIDEO_EVENT_SET_VOLUME = 52; + public static final int VIDEO_EVENT_SET_TEXTURE_INFO = 53; + public static final int VIDEO_EVENT_SET_SPEED = 54; + + public static final int VIDEO_EVENT_MEDIA_PLAYER_PREPARED = 100; + public static final int VIDEO_EVENT_MEDIA_PLAYER_PLAY_COMPLETE = 101; + public static final int VIDEO_EVENT_MEDIA_PLAYER_SEEK_COMPLETE = 102; + public static final int VIDEO_EVENT_MEDIA_PLAYER_ERROR = 103; + public static final int VIDEO_EVENT_MEDIA_PLAYER_VIDEO_SIZE = 104; + public static final int VIDEO_EVENT_MEDIA_PLAYER_ON_INFO = 105; + + private WeakReference activityWeakReference; + final String XR_VIDEO_PLAYER_EVENT_NAME = "xr-video-player:"; + //xr-event&handle&id&url&512&512&1 + final String XR_VIDEO_EVENT_TAG = "xr-event"; + + HashMap xrVideoPlayerHashMap = new HashMap<>(); + HashMap> cachedScriptEventHashMap = new HashMap<>(); + CocosXRVideoGLThread videoGLThread = null; + boolean isPaused = false; + public void onCreate(Activity activity) { + activityWeakReference = new WeakReference<>(activity); + for (int i = 0; i < MAX_COUNT; i++) { + JsbBridgeWrapper.getInstance().addScriptEventListener(XR_VIDEO_PLAYER_EVENT_NAME + i, eventData -> { + if(isPaused) { + return; + } + processVideoEvent(eventData); + }); + } + JsbBridgeWrapper.getInstance().addScriptEventListener(CocosXRPermissionHelper.XR_PERMISSION_EVENT_NAME, CocosXRPermissionHelper::onScriptEvent); + + CocosXRApi.getInstance().onCreate(activity); + } + + public void onResume() { + Log.d(TAG, "onResume"); + isPaused = false; + if(videoGLThread != null) { + videoGLThread.onResume(); + } + Set>> entrySets = cachedScriptEventHashMap.entrySet(); + for (Map.Entry> entrySet : entrySets) { + if (entrySet.getKey() != null && entrySet.getValue() != null) { + for (String data : entrySet.getValue()) { + Log.d(TAG, "onResume.dispatchEventToScript:" + entrySet.getKey() + ":" + data); + JsbBridgeWrapper.getInstance().dispatchEventToScript(entrySet.getKey(), data); + } + } + } + cachedScriptEventHashMap.clear(); + } + + public void onPause() { + Log.d(TAG, "onPause"); + isPaused = true; + if(videoGLThread != null) { + videoGLThread.onPause(); + } + + Set> entrySets = xrVideoPlayerHashMap.entrySet(); + for (Map.Entry entrySet : entrySets) { + if(entrySet.getValue() != null) { + entrySet.getValue().pause(); + } + } + } + + public void sendVideoEvent(VideoEventData videoEventData, String... eventData) { + sendVideoEvent(videoEventData.eventId, videoEventData.eventName, videoEventData.videoPlayerHandleKey, eventData); + } + + public void sendVideoEvent(int eventId, String eventName, String videoPlayerHandleKey, String... eventData) { + StringBuilder data = new StringBuilder("xr-event&" + eventId + "&" + eventName + '&' + videoPlayerHandleKey); + if (eventData.length > 0) { + for (String evtData : eventData) { + data.append("&").append(evtData); + } + } + + if(videoGLThread == null) return; + if(isPaused) { + Log.e(TAG, "sendVideoEvent failed, because is paused !!! [" + data + "]"); + if (!cachedScriptEventHashMap.containsKey(eventName)) { + cachedScriptEventHashMap.put(eventName, new ArrayList<>()); + } + Objects.requireNonNull(cachedScriptEventHashMap.get(eventName)).add(data.toString()); + return; + } + JsbBridgeWrapper.getInstance().dispatchEventToScript(eventName, data.toString()); + } + + private void processVideoEvent(String eventData) { + VideoEventData videoEventData = new VideoEventData(eventData); + if (videoEventData.eventId == VIDEO_EVENT_PREPARE) { + CocosXRVideoPlayer videoPlayer = xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey); + if (videoPlayer == null) { + videoPlayer = new CocosXRVideoPlayer(activityWeakReference, videoEventData.videoPlayerHandleKey, videoEventData.eventName); + videoPlayer.prepare(videoEventData); + xrVideoPlayerHashMap.put(videoEventData.videoPlayerHandleKey, videoPlayer); + } else { + videoPlayer.prepare(videoEventData); + } + + if (videoGLThread == null) { + videoGLThread = new CocosXRVideoGLThread(); + videoGLThread.start(); + } + } else if (videoEventData.eventId == VIDEO_EVENT_PLAY) { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).play(); + } else if (videoEventData.eventId == VIDEO_EVENT_PAUSE) { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).pause(); + } else if (videoEventData.eventId == VIDEO_EVENT_STOP) { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).stop(); + } else if (videoEventData.eventId == VIDEO_EVENT_RESET) { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).reset(); + } else if (videoEventData.eventId == VIDEO_EVENT_DESTROY) { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).release(); + videoGLThread.queueEvent(() -> { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).onGLDestroy(); + xrVideoPlayerHashMap.remove(videoEventData.videoPlayerHandleKey); + }); + } else if (videoEventData.eventId == VIDEO_EVENT_GET_POSITION) { + int position = Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).getCurrentPosition(); + sendVideoEvent(videoEventData, String.valueOf(position)); + } else if (videoEventData.eventId == VIDEO_EVENT_GET_DURATION) { + int duration = Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).getDuration(); + sendVideoEvent(videoEventData, String.valueOf(duration)); + } else if (videoEventData.eventId == VIDEO_EVENT_GET_IS_PALYING) { + boolean isPlaying = Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).isPlaying(); + sendVideoEvent(videoEventData, String.valueOf(isPlaying)); + } else if (videoEventData.eventId == VIDEO_EVENT_GET_IS_LOOPING) { + boolean isLooping = Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).isLooping(); + sendVideoEvent(videoEventData, String.valueOf(isLooping)); + } else if (videoEventData.eventId == VIDEO_EVENT_SET_LOOP) { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).setLooping(videoEventData.isLoop); + } else if (videoEventData.eventId == VIDEO_EVENT_SEEK_TO) { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).seekTo(videoEventData.seekToMsec); + } else if(videoEventData.eventId == VIDEO_EVENT_SET_VOLUME) { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).setVolume(videoEventData.volume); + } else if(videoEventData.eventId == VIDEO_EVENT_SET_TEXTURE_INFO) { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).setTextureInfo(videoEventData.videoWidth, videoEventData.videoHeight, videoEventData.videoTextureId); + } else if(videoEventData.eventId == VIDEO_EVENT_SET_SPEED) { + Objects.requireNonNull(xrVideoPlayerHashMap.get(videoEventData.videoPlayerHandleKey)).setPlaybackSpeed(videoEventData.playbackSpeed); + } + } + + public void onDestroy() { + Log.d(TAG, "onDestroy:" + xrVideoPlayerHashMap.size()); + CocosXRApi.getInstance().onDestroy(); + if(videoGLThread != null) { + videoGLThread.onDestroy(); + videoGLThread = null; + } + for (int i = 0; i < MAX_COUNT; i++) { + JsbBridgeWrapper.getInstance().removeAllListenersForEvent(XR_VIDEO_PLAYER_EVENT_NAME + i); + } + JsbBridgeWrapper.getInstance().removeAllListenersForEvent(CocosXRPermissionHelper.XR_PERMISSION_EVENT_NAME); + Set> entrySets = xrVideoPlayerHashMap.entrySet(); + for (Map.Entry entrySet : entrySets) { + entrySet.getValue().release(); + } + xrVideoPlayerHashMap.clear(); + cachedScriptEventHashMap.clear(); + } + + class CocosXRVideoGLThread extends Thread { + private final ReentrantLock lockObj = new ReentrantLock(true); + private final Condition pauseCondition = lockObj.newCondition(); + private boolean requestPaused = false; + private boolean requestExited = false; + long lastTickTime = System.nanoTime(); + private final ArrayList mEventQueue = new ArrayList<>(); + + EGLContext eglContext; + EGLDisplay eglDisplay; + EGLSurface pBufferSurface; + EGLContext parentContext; + int renderTargetFboId; + + CocosXRVideoGLThread() { + parentContext = EGL14.eglGetCurrentContext(); + } + + @Override + public void run() { + init(); + while (true) { + lockObj.lock(); + try { + if (requestExited) { + break; + } + + if (requestPaused) { + pauseCondition.await(); + } + + if (requestExited) { + break; + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + lockObj.unlock(); + } + + synchronized (this) { + if (!mEventQueue.isEmpty()) { + Runnable event = mEventQueue.remove(0); + if (event != null) { + event.run(); + continue; + } + } + } + + tick(); + } + + exit(); + } + + void init() { + eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE}; + int[] attrList = new int[] {EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, + EGL14.EGL_RENDERABLE_TYPE, 0x00000040, + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_DEPTH_SIZE, 8, + EGL14.EGL_SAMPLE_BUFFERS, 1, + EGL14.EGL_SAMPLES, 1, + EGL14.EGL_STENCIL_SIZE, 0, + EGL14.EGL_NONE}; + EGLConfig[] configOut = new EGLConfig[1]; + int[] configNumOut = new int[1]; + EGL14.eglChooseConfig(eglDisplay, attrList, 0, configOut, 0, 1, + configNumOut, 0); + eglContext = EGL14.eglCreateContext(eglDisplay, configOut[0], parentContext, attrib_list, 0); + + int[] sur_attrib_list = {EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE}; + pBufferSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configOut[0], sur_attrib_list, 0); + + EGL14.eglMakeCurrent(eglDisplay, pBufferSurface, pBufferSurface, eglContext); + GLES30.glDisable(GLES30.GL_DEPTH_TEST); + GLES30.glDisable(GLES30.GL_BLEND); + GLES30.glDisable(GLES30.GL_CULL_FACE); + + int[] tmpFboId = new int[1]; + GLES30.glGenFramebuffers(1, tmpFboId, 0); + renderTargetFboId = tmpFboId[0]; + CocosXRGLHelper.checkGLError("fbo"); + + lastTickTime = System.nanoTime(); + + Set> entrySets = xrVideoPlayerHashMap.entrySet(); + for (Map.Entry entrySet : entrySets) { + entrySet.getValue().onGLReady(); + } + Log.d(TAG, "CocosXRVideoGLThread init"); + } + + void tick() { + // draw + lastTickTime = System.nanoTime(); + Set> entrySets = xrVideoPlayerHashMap.entrySet(); + for (Map.Entry entrySet : entrySets) { + entrySet.getValue().onBeforeGLDrawFrame(); + if(entrySet.getValue().isStopped() || entrySet.getValue().getVideoTextureWidth() == 0 || entrySet.getValue().getVideoTextureHeight() == 0) { + continue; + } + GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, renderTargetFboId); + GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, entrySet.getValue().getTargetTextureId(), 0); + int offsetX = (entrySet.getValue().getVideoTextureWidth() - entrySet.getValue().getVideoSourceWidth()) / 2; + int offsetY = (entrySet.getValue().getVideoTextureHeight() - entrySet.getValue().getVideoSourceHeight()) / 2; + GLES30.glViewport(offsetX, offsetY, entrySet.getValue().getVideoSourceWidth(), entrySet.getValue().getVideoSourceHeight()); + GLES30.glScissor(0, 0, entrySet.getValue().getVideoTextureWidth(), entrySet.getValue().getVideoTextureHeight()); + entrySet.getValue().onGLDrawFrame(); + } + EGL14.eglSwapBuffers(eglDisplay, pBufferSurface); + + if (System.nanoTime() - lastTickTime < 16666666) { + try { + long sleepTimeNS = 16666666 - (System.nanoTime() - lastTickTime); + Thread.sleep(sleepTimeNS / 1000000); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + void exit() { + Set> entrySets = xrVideoPlayerHashMap.entrySet(); + for (Map.Entry entrySet : entrySets) { + entrySet.getValue().onGLDestroy(); + } + GLES30.glDeleteFramebuffers(1, new int[] {renderTargetFboId}, 0); + EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + EGL14.eglDestroySurface(eglDisplay, pBufferSurface); + EGL14.eglDestroyContext(eglDisplay, eglContext); + Log.d(TAG, "CocosXRVideoGLThread exit"); + } + + public void onPause() { + lockObj.lock(); + requestPaused = true; + lockObj.unlock(); + Log.d(TAG, "CocosXRVideoGLThread onPause"); + } + + public void onResume() { + lockObj.lock(); + requestPaused = false; + pauseCondition.signalAll(); + lockObj.unlock(); + Log.d(TAG, "CocosXRVideoGLThread onResume"); + } + + public void onDestroy() { + lockObj.lock(); + requestExited = true; + pauseCondition.signalAll(); + lockObj.unlock(); + + try { + videoGLThread.join(); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, e.getLocalizedMessage()); + } + Log.d(TAG, "CocosXRVideoGLThread onDestroy"); + } + + public void queueEvent(Runnable r) { + synchronized (this) { + mEventQueue.add(r); + } + } + } +} diff --git a/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRVideoPlayer.java b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRVideoPlayer.java new file mode 100644 index 0000000..175fec4 --- /dev/null +++ b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRVideoPlayer.java @@ -0,0 +1,368 @@ +/**************************************************************************** + * Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib.xr; + +import android.app.Activity; +import android.content.res.AssetFileDescriptor; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; +import android.view.Surface; + +import java.io.IOException; +import java.lang.ref.WeakReference; + +public class CocosXRVideoPlayer { + enum MediaPlayerState { + IDLE, + INITIALIZED, + READY_PREPARE, + PREPARING, + PREPARED, + STARTED, + STOPPED, + PAUSED, + END, + ERROR, + COMPLETED + } + + private static final String TAG = "CocosXRVideoPlayer"; + private String uniqueKey; + private String eventName; + private CocosXRVideoTexture videoTexture; + private String videoSourceUrl; + private int videoSourceType; + private int videoTextureId = 0; + private boolean isGLInitialized = false; + private int videoSourceSizeWidth = 0; + private int videoSourceSizeHeight = 0; + private int videoTextureWidth = 0; + private int videoTextureHeight = 0; + private MediaPlayerState mediaPlayerState = MediaPlayerState.IDLE; + + CocosXRGLHelper.GLQuadScreen quadScreen; + MediaPlayer mediaPlayer; + WeakReference atyWeakReference; + + public CocosXRVideoPlayer(WeakReference activityWeakReference, String key, String eventName) { + this.atyWeakReference = activityWeakReference; + this.uniqueKey = key; + this.eventName = eventName; + this.quadScreen = new CocosXRGLHelper.GLQuadScreen(); + this.mediaPlayer = new MediaPlayer(); + this.mediaPlayer.setOnErrorListener((mp, what, extra) -> { + mediaPlayerState = MediaPlayerState.ERROR; + Log.e(TAG, "onError " + what + "," + extra + "." + mp.toString()); + CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_ERROR, eventName, uniqueKey); + return false; + }); + this.mediaPlayer.setOnInfoListener((mp, what, extra) -> { + // Log.d(TAG, "onInfo " + what + "," + extra + "." + mp.toString()); + CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_ON_INFO, eventName, uniqueKey, String.valueOf(what)); + return false; + }); + this.mediaPlayer.setOnPreparedListener(mp -> { + mediaPlayerState = MediaPlayerState.PREPARED; + CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_PREPARED, eventName, uniqueKey); + CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_GET_DURATION, eventName, uniqueKey, String.valueOf(mp.getDuration())); + Log.d(TAG, "onPrepared." + mp+ ", getDuration." + mp.getDuration() + "," + mp.getVideoWidth() + "X" + mp.getVideoHeight() + "," + mp.isPlaying()); + }); + this.mediaPlayer.setOnCompletionListener(mp -> { + Log.d(TAG, "onCompletion." + mp.toString()); + mediaPlayerState = MediaPlayerState.COMPLETED; + CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_PLAY_COMPLETE, eventName, uniqueKey); + }); + this.mediaPlayer.setOnSeekCompleteListener(mp -> { + // Log.d(TAG, "onSeekComplete." + mp.toString()); + CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_SEEK_COMPLETE, eventName, uniqueKey); + }); + this.mediaPlayer.setOnVideoSizeChangedListener((mp, width, height) -> { + Log.d(TAG, "onVideoSizeChanged " + width + "x" + height + "." + mp.toString() + ", isPlaying." + mp.isPlaying()); + if(videoSourceSizeWidth != width || videoSourceSizeHeight != height) { + videoSourceSizeWidth = width; + videoSourceSizeHeight = height; + CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_GET_IS_PALYING, eventName, uniqueKey, String.valueOf(mp.isPlaying() ? 1 : 0)); + CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_MEDIA_PLAYER_VIDEO_SIZE, eventName, uniqueKey, width + "&" + height); + } + }); + this.mediaPlayer.setAudioAttributes(new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build()); + mediaPlayerState = MediaPlayerState.INITIALIZED; + Log.d(TAG, "constructor() " + key + "|" + eventName + "|" + quadScreen.toString()); + } + + public void runOnUIThread(Runnable runnable) { + if (atyWeakReference != null && atyWeakReference.get() != null) { + atyWeakReference.get().runOnUiThread(runnable); + } else { + Log.e(TAG, "runOnUIThread failed, activity not exist !!!"); + } + } + + public void setTextureInfo(int textureWidth, int textureHeight, int videoTextureId) { + this.videoTextureId = videoTextureId; + this.videoTextureWidth = textureWidth; + this.videoTextureHeight = textureHeight; + Log.d(TAG, "setTextureInfo." + textureWidth + "x" + textureHeight + ":" + videoTextureId); + } + + public void prepare(CocosXRVideoManager.VideoEventData data) { + if (this.videoTexture == null) { + this.videoTexture = new CocosXRVideoTexture(); + } + this.videoSourceType = data.videoSourceType; + this.videoSourceUrl = data.videoSourceUrl; + if(TextUtils.isEmpty(this.videoSourceUrl)) { + Log.w(TAG, "prepare failed, because video source is empty !!!"); + return; + } + + try { + if (data.videoSourceType == CocosXRVideoManager.VIDEO_SOURCE_TYPE_LOCAL) { + AssetFileDescriptor afd = atyWeakReference.get().getResources().getAssets().openFd(data.videoSourceUrl); + mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); + } else { + mediaPlayer.setDataSource(data.videoSourceUrl); + } + } catch (IOException e) { + e.printStackTrace(); + Log.e(TAG, e.getLocalizedMessage()); + } + + mediaPlayer.setLooping(data.isLoop); + mediaPlayer.setVolume(data.volume, data.volume); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + mediaPlayer.setPlaybackParams(mediaPlayer.getPlaybackParams().setSpeed(data.playbackSpeed)); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "prepare:" + e.getLocalizedMessage()); + } + } + mediaPlayerState = MediaPlayerState.READY_PREPARE; + if (isGLInitialized) { + runOnUIThread(() -> { + if(mediaPlayerState == MediaPlayerState.READY_PREPARE) { + mediaPlayerState = MediaPlayerState.PREPARING; + mediaPlayer.prepareAsync(); + } + }); + } + Log.d(TAG, "prepare"); + } + + public int getTargetTextureId() { + return videoTextureId; + } + + public int getVideoTextureWidth() { + return videoTextureWidth; + } + + public int getVideoTextureHeight() { + return videoTextureHeight; + } + + public int getVideoSourceWidth() { + return videoSourceSizeWidth; + } + + public int getVideoSourceHeight() { + return videoSourceSizeHeight; + } + + public String getVideoSourceUrl() { + return videoSourceUrl; + } + + public int getVideoSourceType() { + return videoSourceType; + } + + public void onGLReady() { + Log.d(TAG, "onGLReady." + this.hashCode()); + videoTexture.createSurfaceTexture(); + quadScreen.initShader(); + Surface surface = new Surface(videoTexture.getSurfaceTexture()); + mediaPlayer.setSurface(surface); + surface.release(); + isGLInitialized = true; + if(mediaPlayerState == MediaPlayerState.READY_PREPARE) { + runOnUIThread(() -> { + if(mediaPlayerState == MediaPlayerState.READY_PREPARE) { + mediaPlayerState = MediaPlayerState.PREPARING; + mediaPlayer.prepareAsync(); + } + }); + } + } + + public void onBeforeGLDrawFrame() { + if (!isGLInitialized) { + onGLReady(); + } + } + + public void onGLDrawFrame() { + if (videoTextureId == 0 || videoTextureWidth == 0 || videoTextureHeight == 0) { + return; + } + videoTexture.updateTexture(); + quadScreen.draw(videoTexture.getOESTextureId(), videoTexture.getVideoMatrix()); + } + + public void onGLDestroy() { + if (videoTexture != null) { + videoTexture.release(); + videoTexture = null; + } + + if (quadScreen != null) { + quadScreen.release(); + quadScreen = null; + } + } + + public void play() { + runOnUIThread(() -> { + Log.d(TAG, "- start"); + mediaPlayer.start(); + mediaPlayerState = MediaPlayerState.STARTED; + CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_GET_IS_PALYING, eventName, uniqueKey, String.valueOf(mediaPlayer.isPlaying() ? 1 : 0)); + }); + } + + public void pause() { + if(!mediaPlayer.isPlaying()) return; + runOnUIThread(() -> { + Log.d(TAG, "- pause"); + mediaPlayer.pause(); + mediaPlayerState = MediaPlayerState.PAUSED; + CocosXRVideoManager.getInstance().sendVideoEvent(CocosXRVideoManager.VIDEO_EVENT_GET_IS_PALYING, eventName, uniqueKey, String.valueOf(mediaPlayer.isPlaying() ? 1 : 0)); + }); + } + + public void stop() { + runOnUIThread(() -> { + Log.d(TAG, "- stop"); + mediaPlayer.stop(); + mediaPlayerState = MediaPlayerState.STOPPED; + }); + } + + public void reset() { + runOnUIThread(() -> { + Log.d(TAG, "- reset"); + mediaPlayer.reset(); + mediaPlayerState = MediaPlayerState.IDLE; + }); + } + + public void release() { + runOnUIThread(() -> { + if (mediaPlayer != null) { + try { + if (mediaPlayer.isPlaying()) { + mediaPlayer.pause(); + } + mediaPlayer.stop(); + mediaPlayer.release(); + mediaPlayer = null; + mediaPlayerState = MediaPlayerState.END; + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, e.getLocalizedMessage()); + } + Log.d(TAG, "- release"); + } + }); + } + + public boolean isPlaying() { + return mediaPlayer != null && mediaPlayer.isPlaying(); + } + + public boolean isStopped() { + return mediaPlayerState == MediaPlayerState.STOPPED; + } + + public boolean isLooping() { + return mediaPlayer.isLooping(); + } + + public void setLooping(boolean looping) { + runOnUIThread(() -> { + Log.d(TAG, "- setLooping." + looping); + mediaPlayer.setLooping(looping); + }); + } + + public void setVolume(float volume) { + runOnUIThread(() -> mediaPlayer.setVolume(volume, volume)); + } + + public int getDuration() { + return mediaPlayer.getDuration(); + } + + public int getCurrentPosition() { + return mediaPlayer.getCurrentPosition(); + } + + public void seekTo(int mSec) { + runOnUIThread(() -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mediaPlayer.seekTo(mSec, MediaPlayer.SEEK_CLOSEST); + } else { + mediaPlayer.seekTo(mSec); + } + }); + } + + public float getPlaybackSpeed() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return mediaPlayer.getPlaybackParams().getSpeed(); + } else { + return 1; + } + } + + public void setPlaybackSpeed(float speed) { + runOnUIThread(() -> { + Log.d(TAG, "- setPlaybackSpeed." + speed); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + mediaPlayer.setPlaybackParams(mediaPlayer.getPlaybackParams().setSpeed(Math.max(speed, 0.1f))); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, e.getLocalizedMessage()); + } + } + }); + } +} diff --git a/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRVideoTexture.java b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRVideoTexture.java new file mode 100644 index 0000000..c8b53fa --- /dev/null +++ b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRVideoTexture.java @@ -0,0 +1,103 @@ +/**************************************************************************** + * Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + + +package com.cocos.lib.xr; + +import android.graphics.SurfaceTexture; +import android.opengl.GLES30; +import android.opengl.Matrix; + +public class CocosXRVideoTexture implements SurfaceTexture.OnFrameAvailableListener { + SurfaceTexture surfaceTexture; + private boolean surfaceNeedsUpdate = false; + private long videoTimestampNs = -1; + private final float[] videoSTMatrix = new float[16]; + private long lastFrameAvailableTime = 0; + + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + surfaceNeedsUpdate = true; + lastFrameAvailableTime = System.currentTimeMillis(); + } + + private int videoOESTextureId; + + public CocosXRVideoTexture() { + Matrix.setIdentityM(videoSTMatrix, 0); + } + + public SurfaceTexture createSurfaceTexture() { + videoOESTextureId = CocosXRGLHelper.createOESTexture(); + surfaceTexture = new SurfaceTexture(videoOESTextureId); + surfaceTexture.setOnFrameAvailableListener(this); + return surfaceTexture; + } + + public SurfaceTexture getSurfaceTexture() { + return surfaceTexture; + } + + public int getOESTextureId() { + return videoOESTextureId; + } + + public float[] getVideoMatrix() { + return videoSTMatrix; + } + + public long getVideoTimestampNs() { + return videoTimestampNs; + } + + public synchronized boolean updateTexture() { + if (!surfaceNeedsUpdate && System.currentTimeMillis() - lastFrameAvailableTime > 30) { + surfaceNeedsUpdate = true; + lastFrameAvailableTime = System.currentTimeMillis(); + } + + if (surfaceNeedsUpdate) { + surfaceTexture.updateTexImage(); + surfaceTexture.getTransformMatrix(videoSTMatrix); + videoTimestampNs = surfaceTexture.getTimestamp(); + surfaceNeedsUpdate = false; + return true; + } + return false; + } + + public boolean isFrameAvailable() { + return surfaceNeedsUpdate; + } + + public void release() { + if (surfaceTexture != null) { + surfaceTexture.release(); + } + if (videoOESTextureId != 0) { + GLES30.glDeleteTextures(1, new int[] {videoOESTextureId}, 0); + videoOESTextureId = 0; + } + } +} diff --git a/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRWebViewContainer.java b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRWebViewContainer.java new file mode 100644 index 0000000..9625ac8 --- /dev/null +++ b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRWebViewContainer.java @@ -0,0 +1,476 @@ +/**************************************************************************** + * Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib.xr; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.net.http.SslError; +import android.opengl.GLES30; +import android.os.Build; +import android.os.Message; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.webkit.ClientCertRequest; +import android.webkit.CookieManager; +import android.webkit.HttpAuthHandler; +import android.webkit.PermissionRequest; +import android.webkit.SafeBrowsingResponse; +import android.webkit.SslErrorHandler; +import android.webkit.WebChromeClient; +import android.webkit.WebResourceError; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.FrameLayout; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +public class CocosXRWebViewContainer extends FrameLayout { + private static final String TAG = "CocosXRWebViewContainer"; + WebView webView; + private long lastDrawTime = 0; + private CocosXRVideoTexture videoTexture; + private Surface webViewSurface; + private CocosXRGLHelper.GLQuadScreen quadScreen; + private boolean isGLInitialized = false; + private int renderTargetFboId; + private int videoTextureId = 0; + private int videoTextureWidth = 0; + private int videoTextureHeight = 0; + + private boolean isKeyDown = false; + private long keyDownTime = 0; + + public CocosXRWebViewContainer(Context context) { + super(context); + init(); + } + + public CocosXRWebViewContainer(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CocosXRWebViewContainer(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + void init() { + webView = new WebView(getContext()); + webView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT, Gravity.NO_GRAVITY)); + webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + + webView.getSettings().setSupportZoom(false); + webView.getSettings().setBuiltInZoomControls(true); + webView.getSettings().setTextZoom(100); + + webView.getSettings().setDisplayZoomControls(false); + webView.getSettings().setLoadWithOverviewMode(true); + webView.getSettings().setUseWideViewPort(true); + webView.getSettings().setMediaPlaybackRequiresUserGesture(false); + + webView.getSettings().setJavaScriptEnabled(true); + webView.getSettings().setDomStorageEnabled(true); + webView.getSettings().setDefaultTextEncodingName("UTF-8"); + webView.getSettings().setAllowFileAccess(true); + webView.getSettings().setAllowContentAccess(true); + webView.getSettings().setAllowFileAccessFromFileURLs(true); + webView.getSettings().setDatabaseEnabled(true); + webView.getSettings().setAppCacheEnabled(true); + webView.getSettings().setLoadsImagesAutomatically(true); + webView.getSettings().setSupportMultipleWindows(false); + webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); + webView.getSettings().setBlockNetworkImage(false); + webView.getSettings().setGeolocationEnabled(true); + webView.getSettings().setPluginState(WebSettings.PluginState.ON); + webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); + } else { + webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + webView.getSettings().setSafeBrowsingEnabled(false); + } + CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true); + webView.setWebViewClient(new WebViewClient() { + + @Override + public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback) { + Log.d(TAG, "onSafeBrowsingHit:" + threatType + "," + request.getUrl().toString()); + } + + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + return super.shouldInterceptRequest(view, request); + } + + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + Log.d(TAG, "onReceivedSslError:" + error.toString()); + } + + @Override + public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { + Log.d(TAG, "onReceivedClientCertRequest:" + request.toString()); + } + + @Override + public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { + Log.d(TAG, "onReceivedHttpAuthRequest:" + host + ":" + realm); + } + + @Override + public void onReceivedLoginRequest(WebView view, String realm, String account, String args) { + Log.d(TAG, "onReceivedLoginRequest:" + account + ":" + realm + ":" + args); + } + + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (!url.startsWith("http://") && !url.startsWith("https://")) { + Log.e(TAG, "shouldOverrideUrlLoading failed:" + url); + view.reload(); + return false; + } + + view.loadUrl(url); + return true; + } + + @Override + public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Log.e(TAG, "onReceivedError:" + error.getDescription() + "," + error.getErrorCode() + "," + view.getTitle()); + } + super.onReceivedError(view, request, error); + } + + @Override + public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { + Log.e(TAG, "onReceivedHttpError:" + errorResponse.toString()); + super.onReceivedHttpError(view, request, errorResponse); + } + }); + webView.setWebChromeClient(new WebChromeClient() { + + @Override + public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { + Log.d(TAG, "onCreateWindow:" + resultMsg.toString()); + return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg); + } + + @Override + public void onCloseWindow(WebView window) { + Log.d(TAG, "onCreateWindowonCloseWindow"); + super.onCloseWindow(window); + } + + @Override + public void onReceivedTitle(WebView view, String title) { + } + + @Override + public void onProgressChanged(WebView view, int newProgress) { + } + + @Override + public void onShowCustomView(View view, CustomViewCallback callback) { + Log.d(TAG, "onShowCustomView"); + if(callback != null) { + view.setVisibility(View.GONE); + callback.onCustomViewHidden(); + } + /*ViewParent viewParent = webView.getParent(); + if (viewParent instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup)viewParent; + if (viewGroup.getChildCount() > 1) { + callback.onCustomViewHidden(); + return; + } + viewGroup.getChildAt(0).setVisibility(View.GONE); + view.setBackgroundColor(0); + viewGroup.addView(view); + view.setVisibility(View.VISIBLE); + }*/ + } + + @Override + public void onHideCustomView() { + Log.d(TAG, "onHideCustomView"); + /*ViewParent viewParent = webView.getParent(); + if (viewParent instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup)viewParent; + while (viewGroup.getChildCount() > 1) + viewGroup.removeViewAt(1); + viewGroup.getChildAt(0).setVisibility(View.VISIBLE); + }*/ + } + + @Override + public void onPermissionRequest(PermissionRequest request) { + String[] resources = request.getResources(); + ArrayList permissions = new ArrayList<>(); + for (String resource : resources) { + Log.d(TAG, "onPermissionRequest.resource:" + resource); + if (resource.equals("android.webkit.resource.AUDIO_CAPTURE")) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (getContext().checkSelfPermission("android.permission.RECORD_AUDIO") != PackageManager.PERMISSION_GRANTED) { + permissions.add(resource); + } + } + } else if (resource.equals("android.webkit.resource.PROTECTED_MEDIA_ID")) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (getContext().checkSelfPermission("android.permission.PROTECTED_MEDIA_ID") != PackageManager.PERMISSION_GRANTED) { + permissions.add(resource); + } + } + } + } + + if(permissions.size() > 0) { + String[] names = new String[permissions.size()]; + permissions.toArray(names); + Log.d(TAG, "acquirePermissions:" + Arrays.toString(names)); + request.grant(names); + } + + } + }); + String userAgentString = webView.getSettings().getUserAgentString(); + Log.d(TAG, "ua:" + userAgentString); + + + this.quadScreen = new CocosXRGLHelper.GLQuadScreen(); + videoTexture = new CocosXRVideoTexture(); + webView.clearFocus(); + webView.setFocusableInTouchMode(false); + addView(webView); + } + + @Override + public void draw(Canvas canvas) { + lastDrawTime = System.currentTimeMillis(); + if (webViewSurface == null && canvas != null) { + super.draw(canvas); + return; + } else if(webViewSurface == null) { + return; + } + //returns canvas attached to gl texture to draw on + Canvas glAttachedCanvas; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + glAttachedCanvas = webViewSurface.lockHardwareCanvas(); + } else { + glAttachedCanvas = webViewSurface.lockCanvas(null); + } + if (glAttachedCanvas != null) { + glAttachedCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + glAttachedCanvas.scale(1.0F, 1.0F); + glAttachedCanvas.translate(-getScrollX(), -getScrollY()); + //draw the view to provided canvas + super.draw(glAttachedCanvas); + } + webViewSurface.unlockCanvasAndPost(glAttachedCanvas); + if (videoTexture != null) { + videoTexture.onFrameAvailable(null); + } + } + + public void onDrawCheck() { + if (webViewSurface != null && System.currentTimeMillis() - lastDrawTime > 16) { + post(() -> draw(null)); + } + } + + CocosXRVideoTexture getVideoTexture() { + return videoTexture; + } + + public void onDestroy() { + Log.d(TAG, "destroy." + hashCode()); + if(webView != null) { + webView.removeAllViews(); + webView.destroy(); + webView = null; + } + + if (webViewSurface != null) { + webViewSurface.release(); + webViewSurface = null; + } + } + + public void setTextureInfo(int textureWidth, int textureHeight, int videoTextureId) { + this.videoTextureId = videoTextureId; + this.videoTextureWidth = textureWidth; + this.videoTextureHeight = textureHeight; + } + + public int getTargetTextureId() { + return videoTextureId; + } + + public int getVideoTextureWidth() { + return videoTextureWidth; + } + + public int getVideoTextureHeight() { + return videoTextureHeight; + } + + public void onGLReady() { + videoTexture.createSurfaceTexture(); + videoTexture.getSurfaceTexture().setDefaultBufferSize(this.videoTextureWidth, this.videoTextureHeight); + quadScreen.initShader(); + webViewSurface = new Surface(videoTexture.getSurfaceTexture()); + isGLInitialized = true; + + int[] tmpFboId = new int[1]; + GLES30.glGenFramebuffers(1, tmpFboId, 0); + renderTargetFboId = tmpFboId[0]; + CocosXRGLHelper.checkGLError("fbo"); + Log.d(TAG, "onGLReady." + this.hashCode() + ",oes." + videoTexture.getOESTextureId() + ",fbo." + tmpFboId[0] + + "," + this.videoTextureWidth + "x" + this.videoTextureHeight); + } + + public void onBeforeGLDrawFrame() { + if (videoTextureId == 0 || videoTextureWidth == 0 || videoTextureHeight == 0) { + return; + } + + if (!isGLInitialized) { + onGLReady(); + } + } + + public void onGLDrawFrame() { + if (videoTextureId == 0 || videoTextureWidth == 0 || videoTextureHeight == 0) { + return; + } + + GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, renderTargetFboId); + GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, videoTextureId, 0); + GLES30.glViewport(0, 0, videoTextureWidth, videoTextureHeight); + GLES30.glScissor(0, 0, videoTextureWidth, videoTextureHeight); + onDrawCheck(); + videoTexture.updateTexture(); + quadScreen.draw(videoTexture.getOESTextureId(), videoTexture.getVideoMatrix()); + GLES30.glFlush(); + } + + public void onGLDestroy() { + Log.d(TAG, "onGLDestroy." + this.hashCode()); + if(renderTargetFboId > 0) { + GLES30.glDeleteFramebuffers(1, new int[]{renderTargetFboId}, 0); + renderTargetFboId = 0; + } + + if (videoTexture != null) { + videoTexture.release(); + videoTexture = null; + } + + if (quadScreen != null) { + quadScreen.release(); + quadScreen = null; + } + } + + public void simulateTouchDown(float ux, float uy) { + isKeyDown = true; + keyDownTime = SystemClock.uptimeMillis(); + float touchX = ux * getWidth(); + float touchY = getHeight() - uy * getHeight(); + post(() -> { + MotionEvent motionEvent = MotionEvent.obtain(keyDownTime, SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_DOWN, touchX, touchY, 0); + motionEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); + dispatchTouchEvent(motionEvent); + }); + } + + public void simulateTouchMove(float ux, float uy) { + if (isKeyDown) { + post(() -> { + float touchX = ux * getWidth(); + float touchY = getHeight() - uy * getHeight(); + MotionEvent motionEvent = MotionEvent.obtain(keyDownTime, SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_MOVE, touchX, touchY, 0); + motionEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); + dispatchTouchEvent(motionEvent); + }); + } + } + + public void simulateTouchUp(float ux, float uy) { + isKeyDown = false; + float touchX = ux * getWidth(); + float touchY = getHeight() - uy * getHeight(); + post(() -> { + MotionEvent motionEvent = MotionEvent.obtain(keyDownTime, SystemClock.uptimeMillis() + 100, MotionEvent.ACTION_UP, touchX, touchY, 0); + motionEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); + dispatchTouchEvent(motionEvent); + }); + } + + public void loadUrl(String url) { + if(webView != null) { + HashMap hashMap = new HashMap<>(); + hashMap.put("Referer", "https://www.cocos.com/"); + webView.loadUrl(url, hashMap); + } + } + + public WebSettings getSettings() { + return webView.getSettings(); + } + + public void goForward() { + webView.goForward(); + } + + public void goBack() { + webView.goBack(); + } + + public void reload() { + webView.reload(); + } + + +} diff --git a/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRWebViewManager.java b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRWebViewManager.java new file mode 100644 index 0000000..2753c4b --- /dev/null +++ b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/CocosXRWebViewManager.java @@ -0,0 +1,450 @@ +/**************************************************************************** + * Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib.xr; + +import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION; + +import android.app.Activity; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.opengl.GLES30; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.FrameLayout; + +import com.cocos.lib.JsbBridgeWrapper; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +public class CocosXRWebViewManager { + private static final String TAG = "CocosXRWebViewManager"; + public static final String XR_WEBVIEW_EVENT_NAME = "xr-webview-"; + public static final String XR_WEBVIEW_EVENT_TAG_TO_ADD = "to-add"; + public static final String XR_WEBVIEW_EVENT_TAG_TO_REMOVE = "to-remove"; + public static final String XR_WEBVIEW_EVENT_TAG_ADDED = "added"; + public static final String XR_WEBVIEW_EVENT_TAG_REMOVED = "removed"; + public static final String XR_WEBVIEW_EVENT_TAG_TEXTUREINFO = "textureinfo"; + public static final String XR_WEBVIEW_EVENT_TAG_HOVER = "hover"; + public static final String XR_WEBVIEW_EVENT_TAG_CLICK_DOWN = "click-down"; + public static final String XR_WEBVIEW_EVENT_TAG_CLICK_UP = "click-up"; + public static final String XR_WEBVIEW_EVENT_TAG_GOFORWARD = "go-forward"; + public static final String XR_WEBVIEW_EVENT_TAG_GOBACK = "go-back"; + public static final String XR_WEBVIEW_EVENT_TAG_LOADURL = "load-url"; + public static final String XR_WEBVIEW_EVENT_TAG_RELOAD = "reload"; + final int MAX_COUNT = 3; + + ConcurrentHashMap xrWebViewHashMap = new ConcurrentHashMap<>(); + private WeakReference activityWeakReference; + CocosXRWebViewGLThread webViewGLThread; + + private final boolean isMobileUA = false; + + public void createWebView(int webviewId, int textureWidth, int textureHeight, String url) { + Log.d(TAG, "createWebView:" + webviewId + "," + url); + if (activityWeakReference.get() != null) { + activityWeakReference.get().runOnUiThread(() -> { + CocosXRWebViewContainer xrWebViewContainer = new CocosXRWebViewContainer(activityWeakReference.get()); + View decorView = activityWeakReference.get().getWindow().getDecorView(); + if(decorView instanceof FrameLayout) { + FrameLayout parentLayout = (FrameLayout) decorView; + FrameLayout.LayoutParams frameLayoutParams = new FrameLayout.LayoutParams(textureWidth, textureHeight); + parentLayout.addView(xrWebViewContainer, frameLayoutParams); + xrWebViewContainer.setZ(-100 + webviewId); + } + if (url != null) { + if (isMobileUA) { + xrWebViewContainer.getSettings().setUserAgentString("Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.82 Mobile Safari/537.36"); + } else { + xrWebViewContainer.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36"); + } + xrWebViewContainer.loadUrl(url); + } + + xrWebViewHashMap.put(String.valueOf(webviewId), xrWebViewContainer); + // notify + JsbBridgeWrapper.getInstance().dispatchEventToScript(XR_WEBVIEW_EVENT_NAME.concat(String.valueOf(webviewId)), XR_WEBVIEW_EVENT_TAG_ADDED); + }); + } + } + + public void removeWebView(int webviewId) { + if (!xrWebViewHashMap.containsKey(String.valueOf(webviewId))) { + return; + } + Log.d(TAG, "removeWebView:" + webviewId); + if (activityWeakReference.get() != null) { + activityWeakReference.get().runOnUiThread(() -> { + CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId)); + if (xrWebViewContainer != null) { + View decorView = activityWeakReference.get().getWindow().getDecorView(); + if(decorView instanceof FrameLayout) { + FrameLayout parentLayout = (FrameLayout) decorView; + parentLayout.removeView(xrWebViewContainer); + } + + xrWebViewContainer.onDestroy(); + webViewGLThread.queueEvent(() -> { + xrWebViewContainer.onGLDestroy(); + xrWebViewHashMap.remove(String.valueOf(webviewId)); + }); + // notify + JsbBridgeWrapper.getInstance().dispatchEventToScript(XR_WEBVIEW_EVENT_TAG_REMOVED.concat(String.valueOf(webviewId))); + } + }); + } + } + + private void goForward(int webviewId) { + if (activityWeakReference.get() != null) { + activityWeakReference.get().runOnUiThread(() -> { + CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId)); + if (xrWebViewContainer != null) { + xrWebViewContainer.goForward(); + } + }); + } + } + + private void goBack(int webviewId) { + if (activityWeakReference.get() != null) { + activityWeakReference.get().runOnUiThread(() -> { + CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId)); + if (xrWebViewContainer != null) { + xrWebViewContainer.goBack(); + } + }); + } + } + + private void reload(int webviewId) { + if (activityWeakReference.get() != null) { + activityWeakReference.get().runOnUiThread(() -> { + CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId)); + if (xrWebViewContainer != null) { + xrWebViewContainer.reload(); + } + }); + } + } + + private void loadUrl(int webviewId, String url) { + if (activityWeakReference.get() != null) { + activityWeakReference.get().runOnUiThread(() -> { + CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId)); + if (xrWebViewContainer != null) { + if (isMobileUA) { + xrWebViewContainer.getSettings().setUserAgentString("Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.82 Mobile Safari/537.36"); + } else { + xrWebViewContainer.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36"); + } + xrWebViewContainer.loadUrl(url); + } + }); + } + } + + public void setTextureInfo(int webViewId, int textureId, int textureWidth, int textureHeight) { + Log.d(TAG, "setTextureInfo:" + webViewId + "," + textureWidth + "x" + textureHeight + ":" + textureId); + CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webViewId)); + if (xrWebViewContainer != null) { + xrWebViewContainer.setTextureInfo(textureWidth, textureHeight, textureId); + } + + if (webViewGLThread == null) { + webViewGLThread = new CocosXRWebViewGLThread(); + webViewGLThread.start(); + } + } + + public void onCreate(Activity activity) { + Log.d(TAG, "onCreate"); + activityWeakReference = new WeakReference<>(activity); + for (int i = 0; i < MAX_COUNT; i++) { + String eventName = XR_WEBVIEW_EVENT_NAME.concat(String.valueOf(i)); + int webviewId = i; + JsbBridgeWrapper.getInstance().addScriptEventListener(eventName, arg -> { + if (arg == null) { + Log.e(TAG, "Invalid arg is null !!!"); + return; + } + String[] dataArray = arg.split("&"); + if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_TEXTUREINFO)) { + // id&w&h + setTextureInfo(webviewId, Integer.parseInt(dataArray[1]), Integer.parseInt(dataArray[2]), Integer.parseInt(dataArray[3])); + } else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_TO_ADD)) { + int textureWidth = Integer.parseInt(dataArray[1]); + int textureHeight = Integer.parseInt(dataArray[2]); + String url = dataArray.length > 3 ? dataArray[3] : null; + createWebView(webviewId, textureWidth, textureHeight, url); + } else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_TO_REMOVE)) { + removeWebView(webviewId); + } else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_HOVER)) { + CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId)); + if (xrWebViewContainer != null) { + xrWebViewContainer.simulateTouchMove(Float.parseFloat(dataArray[1]), Float.parseFloat(dataArray[2])); + } + } else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_CLICK_DOWN)) { + CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId)); + if (xrWebViewContainer != null) { + xrWebViewContainer.simulateTouchDown(Float.parseFloat(dataArray[1]), Float.parseFloat(dataArray[2])); + } + } else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_CLICK_UP)) { + CocosXRWebViewContainer xrWebViewContainer = xrWebViewHashMap.get(String.valueOf(webviewId)); + if (xrWebViewContainer != null) { + xrWebViewContainer.simulateTouchUp(Float.parseFloat(dataArray[1]), Float.parseFloat(dataArray[2])); + } + } else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_LOADURL)) { + loadUrl(webviewId, dataArray[1]); + } else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_RELOAD)) { + reload(webviewId); + } else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_GOBACK)) { + goBack(webviewId); + } else if (TextUtils.equals(dataArray[0], XR_WEBVIEW_EVENT_TAG_GOFORWARD)) { + goForward(webviewId); + } + }); + } + } + + public void onResume() { + Log.d(TAG, "onResume"); + if (webViewGLThread != null) { + webViewGLThread.onResume(); + } + } + + public void onPause() { + Log.d(TAG, "onPause"); + if (webViewGLThread != null) { + webViewGLThread.onPause(); + } + } + + public void onDestroy() { + Log.d(TAG, "onDestroy"); + if (webViewGLThread != null) { + webViewGLThread.onDestroy(); + webViewGLThread = null; + } + for (int i = 0; i < MAX_COUNT; i++) { + JsbBridgeWrapper.getInstance().removeAllListenersForEvent(XR_WEBVIEW_EVENT_NAME.concat(String.valueOf(i))); + } + Set> entrySets = xrWebViewHashMap.entrySet(); + for (Map.Entry entrySet : entrySets) { + CocosXRWebViewContainer xrWebViewContainer = entrySet.getValue(); + if (activityWeakReference.get() != null) { + View decorView = activityWeakReference.get().getWindow().getDecorView(); + if(decorView instanceof FrameLayout) { + FrameLayout parentLayout = (FrameLayout) decorView; + parentLayout.removeView(xrWebViewContainer); + } + } + xrWebViewContainer.onDestroy(); + } + xrWebViewHashMap.clear(); + } + + class CocosXRWebViewGLThread extends Thread { + private final ReentrantLock lockObj = new ReentrantLock(true); + private final Condition pauseCondition = lockObj.newCondition(); + private boolean running = false; + private boolean requestPaused = false; + private boolean requestExited = false; + long lastTickTime = System.nanoTime(); + private final ArrayList mEventQueue = new ArrayList<>(); + + EGLContext eglContext; + EGLDisplay eglDisplay; + EGLSurface pBufferSurface; + EGLContext parentContext; + + CocosXRWebViewGLThread() { + parentContext = EGL14.eglGetCurrentContext(); + } + + public boolean isRunning() { + return running; + } + + @Override + public void run() { + init(); + while (true) { + lockObj.lock(); + try { + if (requestExited) { + running = false; + break; + } + + if (requestPaused) { + running = false; + pauseCondition.await(); + running = true; + } + + if (requestExited) { + running = false; + break; + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + lockObj.unlock(); + } + + synchronized (this) { + if (!mEventQueue.isEmpty()) { + Runnable event = mEventQueue.remove(0); + if (event != null) { + event.run(); + continue; + } + } + } + + tick(); + } + + exit(); + } + + void init() { + running = true; + eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE}; + int[] attrList = new int[]{EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, + EGL14.EGL_RENDERABLE_TYPE, 0x00000040, + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_DEPTH_SIZE, 0, + EGL14.EGL_SAMPLE_BUFFERS, 1, + EGL14.EGL_SAMPLES, 1, + EGL14.EGL_STENCIL_SIZE, 0, + EGL14.EGL_NONE}; + EGLConfig[] configOut = new EGLConfig[1]; + int[] configNumOut = new int[1]; + EGL14.eglChooseConfig(eglDisplay, attrList, 0, configOut, 0, 1, + configNumOut, 0); + eglContext = EGL14.eglCreateContext(eglDisplay, configOut[0], parentContext, attrib_list, 0); + + int[] sur_attrib_list = {EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE}; + pBufferSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configOut[0], sur_attrib_list, 0); + + EGL14.eglMakeCurrent(eglDisplay, pBufferSurface, pBufferSurface, eglContext); + GLES30.glDisable(GLES30.GL_DEPTH_TEST); + GLES30.glDisable(GLES30.GL_BLEND); + GLES30.glDisable(GLES30.GL_CULL_FACE); + + lastTickTime = System.nanoTime(); + Log.d(TAG, "CocosXRWebViewGLThread init"); + } + + void tick() { + // draw + lastTickTime = System.nanoTime(); + + Set> entrySets = xrWebViewHashMap.entrySet(); + for (Map.Entry entrySet : entrySets) { + entrySet.getValue().onBeforeGLDrawFrame(); + if(entrySet.getValue().getVideoTextureWidth() == 0 || entrySet.getValue().getVideoTextureHeight() == 0) { + continue; + } + entrySet.getValue().onGLDrawFrame(); + } + + EGL14.eglSwapBuffers(eglDisplay, pBufferSurface); + + if (System.nanoTime() - lastTickTime < 16666666) { + // lock 60fps + try { + long sleepTimeNS = 16666666 - (System.nanoTime() - lastTickTime); + Thread.sleep(sleepTimeNS / 1000000); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + void exit() { + running = false; + Set> entrySets = xrWebViewHashMap.entrySet(); + for (Map.Entry entrySet : entrySets) { + entrySet.getValue().onGLDestroy(); + } + EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + EGL14.eglDestroySurface(eglDisplay, pBufferSurface); + EGL14.eglDestroyContext(eglDisplay, eglContext); + Log.d(TAG, "CocosXRWebViewGLThread exit"); + } + + public void onPause() { + lockObj.lock(); + requestPaused = true; + lockObj.unlock(); + Log.d(TAG, "CocosXRWebViewGLThread onPause"); + } + + public void onResume() { + lockObj.lock(); + requestPaused = false; + pauseCondition.signalAll(); + lockObj.unlock(); + Log.d(TAG, "CocosXRWebViewGLThread onResume"); + } + + public void onDestroy() { + lockObj.lock(); + requestExited = true; + pauseCondition.signalAll(); + lockObj.unlock(); + + try { + join(); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, e.getLocalizedMessage()); + } + Log.d(TAG, "CocosXRWebViewGLThread onDestroy"); + } + + public void queueEvent(Runnable r) { + synchronized (this) { + mEventQueue.add(r); + } + } + } +} diff --git a/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/permission/CocosXRPermissionFragment.java b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/permission/CocosXRPermissionFragment.java new file mode 100644 index 0000000..0f86041 --- /dev/null +++ b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/permission/CocosXRPermissionFragment.java @@ -0,0 +1,106 @@ +/**************************************************************************** + * Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib.xr.permission; + +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import java.util.Arrays; + +public class CocosXRPermissionFragment extends Fragment { + private static final int PERMISSION_REQUEST_CODE = 1101; + private static final String TAG = "CocosPermissionFragment"; + private static final String PERMISSION_TAG = "TAG_PermissionFragment"; + + public static CocosXRPermissionFragment getInstance(Activity activity) { + FragmentManager fm = activity.getFragmentManager(); + CocosXRPermissionFragment fragment = (CocosXRPermissionFragment) fm.findFragmentByTag(PERMISSION_TAG); + if (fragment == null) { + try { + Log.d(TAG, "Creating CocosXRPermissionFragment"); + fragment = new CocosXRPermissionFragment(); + FragmentTransaction trans = fm.beginTransaction(); + trans.add(fragment, PERMISSION_TAG); + trans.commit(); + fm.executePendingTransactions(); + } catch (Throwable th) { + Log.e(TAG, "Cannot launch PermissionFragment:" + th.getMessage(), th); + return null; + } + } + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + Log.d(TAG, "onCreate"); + } + + public void acquirePermissions(String[] permissions) { + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(() -> { + final int[] grantResults = new int[permissions.length]; + Activity context = getActivity(); + if (context != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PackageManager packageManager = context.getPackageManager(); + String packageName = context.getPackageName(); + for (String permission : permissions) { + if (PackageManager.PERMISSION_DENIED == packageManager.checkPermission(permission, packageName)) { + CocosXRPermissionFragment.this.requestPermissions(permissions, PERMISSION_REQUEST_CODE); + break; + } + } + Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED); + onRequestPermissionsResult(PERMISSION_REQUEST_CODE, permissions, grantResults); + } else { + Log.e(TAG, "acquirePermissions failed !"); + Arrays.fill(grantResults, PackageManager.PERMISSION_DENIED); + onRequestPermissionsResult(PERMISSION_REQUEST_CODE, permissions, grantResults); + } + } else { + Arrays.fill(grantResults, PackageManager.PERMISSION_DENIED); + onRequestPermissionsResult(PERMISSION_REQUEST_CODE, permissions, grantResults); + } + }); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == PERMISSION_REQUEST_CODE && permissions.length > 0) { + CocosXRPermissionHelper.onAcquirePermissions(permissions, grantResults); + } + } +} diff --git a/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/permission/CocosXRPermissionHelper.java b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/permission/CocosXRPermissionHelper.java new file mode 100644 index 0000000..edf9718 --- /dev/null +++ b/cocos/platform/android/libcocosxr/src/com/cocos/lib/xr/permission/CocosXRPermissionHelper.java @@ -0,0 +1,108 @@ +/**************************************************************************** + * Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib.xr.permission; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Process; +import android.text.TextUtils; +import android.util.Log; +import com.cocos.lib.CocosHelper; +import com.cocos.lib.JsbBridgeWrapper; +import com.cocos.lib.xr.CocosXRApi; + +import java.util.Arrays; + +public class CocosXRPermissionHelper { + private static final String LOG_TAG = "CocosXRPermissionHelper"; + public static final String XR_PERMISSION_EVENT_NAME = "xr-permission"; + public static final String XR_PERMISSION_TAG_CHECK = "check"; + public static final String XR_PERMISSION_TAG_REQUEST = "request"; + public interface PermissionCallback { + void onRequestPermissionsResult(String[] permissions, int[] grantResults); + } + + private static PermissionCallback permissionCallback = null; + public static void onScriptEvent(String arg) { + String[] array = arg.split(":"); + if (TextUtils.equals(array[0], XR_PERMISSION_TAG_CHECK)) { + int result = checkPermission(array[1]) ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED; + CocosHelper.runOnGameThread(() -> JsbBridgeWrapper.getInstance().dispatchEventToScript(XR_PERMISSION_EVENT_NAME, XR_PERMISSION_TAG_CHECK + ":" + array[1] + ":" + result)); + } else if (TextUtils.equals(array[0], XR_PERMISSION_TAG_REQUEST)) { + String[] permissionNames = array[1].split("&"); + acquirePermissions(permissionNames, (PermissionCallback) null); + } + } + + public static boolean checkPermission(String permission) { + Context context = CocosXRApi.getInstance().getContext(); + if (context == null) + return false; + if (context.checkPermission(permission, android.os.Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED) { + Log.d(LOG_TAG, "checkPermission: " + permission + " has granted"); + return true; + } else { + Log.d(LOG_TAG, "checkPermission: " + permission + " has not granted"); + return false; + } + } + + public static void acquirePermissions(String[] permissions, PermissionCallback callback) { + permissionCallback = callback; + CocosXRPermissionHelper.acquirePermissions(permissions, CocosXRApi.getInstance().getActivity()); + } + + public static void acquirePermissions(String[] permissions, Activity InActivity) { + if (InActivity == null) + return; + final Activity activity = InActivity; + activity.runOnUiThread(() -> { + CocosXRPermissionFragment fragment = CocosXRPermissionFragment.getInstance(activity); + if (fragment != null) { + fragment.acquirePermissions(permissions); + } + }); + } + + public static void onAcquirePermissions(String[] permissions, int[] grantResults) { + Log.d(LOG_TAG, "onAcquirePermissions:" + Arrays.toString(permissions) + "|" + Arrays.toString(grantResults)); + // + CocosHelper.runOnGameThread(() -> { + StringBuilder stringBuilder = new StringBuilder(XR_PERMISSION_TAG_REQUEST); + stringBuilder.append(":"); + for (int i = 0; i < permissions.length; i++) { + stringBuilder.append(permissions[i]).append("#").append(grantResults[i]); + if (i != permissions.length - 1) { + stringBuilder.append("&"); + } + } + JsbBridgeWrapper.getInstance().dispatchEventToScript(XR_PERMISSION_EVENT_NAME, stringBuilder.toString()); + }); + if (permissionCallback != null) { + permissionCallback.onRequestPermissionsResult(permissions, grantResults); + } + } +} diff --git a/cocos/platform/android/modules/Screen.cpp b/cocos/platform/android/modules/Screen.cpp new file mode 100644 index 0000000..41e6750 --- /dev/null +++ b/cocos/platform/android/modules/Screen.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/android/modules/Screen.h" +#include +#include +#include +#include "platform/java/jni/JniHelper.h" + +namespace cc { + +int Screen::getDPI() const { + static int dpi = -1; + if (dpi == -1) { + AConfiguration *config = AConfiguration_new(); + //AConfiguration_fromAssetManager(config, cocosApp.assetManager); + int32_t density = AConfiguration_getDensity(config); + AConfiguration_delete(config); + const int stdDpi = 160; + dpi = density * stdDpi; + } + return dpi; +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/android/modules/Screen.h b/cocos/platform/android/modules/Screen.h new file mode 100644 index 0000000..d359c45 --- /dev/null +++ b/cocos/platform/android/modules/Screen.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/java/modules/CommonScreen.h" + +namespace cc { + +class CC_DLL Screen : public CommonScreen { +public: + int getDPI() const override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/android/modules/System.cpp b/cocos/platform/android/modules/System.cpp new file mode 100644 index 0000000..97ea8ef --- /dev/null +++ b/cocos/platform/android/modules/System.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/android/modules/System.h" + +namespace cc { +System::System() = default; + +System::~System() = default; + +System::OSType System::getOSType() const { + return OSType::ANDROIDOS; +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/android/modules/System.h b/cocos/platform/android/modules/System.h new file mode 100644 index 0000000..ba23e57 --- /dev/null +++ b/cocos/platform/android/modules/System.h @@ -0,0 +1,41 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/java/modules/CommonSystem.h" + +namespace cc { + +class CC_DLL System : public CommonSystem { +public: + System(); + ~System() override; + + OSType getOSType() const override; + +private: +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/apple/FileUtils-apple.h b/cocos/platform/apple/FileUtils-apple.h new file mode 100644 index 0000000..49be63d --- /dev/null +++ b/cocos/platform/apple/FileUtils-apple.h @@ -0,0 +1,68 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2011 Zynga Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/std/container/string.h" + +#include "base/Macros.h" +#include "platform/FileUtils.h" + +namespace cc { + +//! @brief Helper class to handle file operations +class CC_DLL FileUtilsApple : public FileUtils { +public: + FileUtilsApple(); + ~FileUtilsApple() override = default; + /* override functions */ + ccstd::string getWritablePath() const override; + ccstd::string getFullPathForDirectoryAndFilename(const ccstd::string &directory, const ccstd::string &filename) const override; + + ValueMap getValueMapFromFile(const ccstd::string &filename) override; + ValueMap getValueMapFromData(const char *filedata, int filesize) override; + bool writeToFile(const ValueMap &dict, const ccstd::string &fullPath) override; + + ValueVector getValueVectorFromFile(const ccstd::string &filename) override; +#if CC_FILEUTILS_APPLE_ENABLE_OBJC + void setBundle(NSBundle *bundle); +#endif + + bool createDirectory(const ccstd::string &path) override; + +private: + bool isFileExistInternal(const ccstd::string &filePath) const override; + bool removeDirectory(const ccstd::string &dirPath) override; + void valueMapCompact(ValueMap &valueMap) override; + void valueVectorCompact(ValueVector &valueVector) override; + + struct IMPL; + std::unique_ptr pimpl_; +}; + +} // namespace cc diff --git a/cocos/platform/apple/FileUtils-apple.mm b/cocos/platform/apple/FileUtils-apple.mm new file mode 100644 index 0000000..631af26 --- /dev/null +++ b/cocos/platform/apple/FileUtils-apple.mm @@ -0,0 +1,438 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cc-x.org + Copyright (c) 2011 Zynga Inc. + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#import + +#include "platform/apple/FileUtils-apple.h" + +#include +#include +#include "base/Log.h" +#include "base/memory/Memory.h" +#include "base/std/container/string.h" +#include "platform/FileUtils.h" +#include "platform/SAXParser.h" + +namespace cc { + +struct FileUtilsApple::IMPL { + IMPL(NSBundle *bundle) : bundle_([NSBundle mainBundle]) {} + void setBundle(NSBundle *bundle) { + bundle_ = bundle; + } + NSBundle *getBundle() const { + return bundle_; + } + +private: + NSBundle *bundle_; +}; + +static id convertCCValueToNSObject(const cc::Value &value); +static cc::Value convertNSObjectToCCValue(id object); + +static void addNSObjectToCCMap(id nsKey, id nsValue, ValueMap &dict); +static void addCCValueToNSDictionary(const ccstd::string &key, const Value &value, NSMutableDictionary *dict); +static void addNSObjectToCCVector(id item, ValueVector &array); +static void addCCValueToNSArray(const Value &value, NSMutableArray *array); + +static id convertCCValueToNSObject(const cc::Value &value) { + switch (value.getType()) { + case Value::Type::NONE: + return [NSNull null]; + + case Value::Type::STRING: + return [NSString stringWithCString:value.asString().c_str() encoding:NSUTF8StringEncoding]; + + case Value::Type::BYTE: + return [NSNumber numberWithInt:value.asByte()]; + + case Value::Type::INTEGER: + return [NSNumber numberWithInt:value.asInt()]; + + case Value::Type::UNSIGNED: + return [NSNumber numberWithUnsignedInt:value.asUnsignedInt()]; + + case Value::Type::FLOAT: + return [NSNumber numberWithFloat:value.asFloat()]; + + case Value::Type::DOUBLE: + return [NSNumber numberWithDouble:value.asDouble()]; + + case Value::Type::BOOLEAN: + return [NSNumber numberWithBool:value.asBool()]; + + case Value::Type::VECTOR: { + NSMutableArray *array = [NSMutableArray array]; + const ValueVector &vector = value.asValueVector(); + for (const auto &e : vector) { + addCCValueToNSArray(e, array); + } + return array; + } + + case Value::Type::MAP: { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + const ValueMap &map = value.asValueMap(); + for (auto iter = map.begin(); iter != map.end(); ++iter) { + addCCValueToNSDictionary(iter->first, iter->second, dictionary); + } + return dictionary; + } + + case Value::Type::INT_KEY_MAP: + break; + } + + return [NSNull null]; +} + +static cc::Value convertNSObjectToCCValue(id item) { + // add string value into array + if ([item isKindOfClass:[NSString class]]) { + return Value([item UTF8String]); + } + + // add number value into array(such as int, float, bool and so on) + // the value is a number + if ([item isKindOfClass:[NSNumber class]]) { + NSNumber *num = item; + const char *numType = [num objCType]; + if (num == (void *)kCFBooleanFalse || num == (void *)kCFBooleanTrue) { + bool v = [num boolValue]; + return Value(v); + } else if (strcmp(numType, @encode(float)) == 0) { + return Value([num floatValue]); + } else if (strcmp(numType, @encode(double)) == 0) { + return Value([num doubleValue]); + } else { + return Value([num intValue]); + } + } + + // add dictionary value into array + if ([item isKindOfClass:[NSDictionary class]]) { + ValueMap dict; + for (id subKey in [item allKeys]) { + id subValue = [item objectForKey:subKey]; + addNSObjectToCCMap(subKey, subValue, dict); + } + + return Value(dict); + } + + // add array value into array + if ([item isKindOfClass:[NSArray class]]) { + ValueVector subArray; + for (id subItem in item) { + addNSObjectToCCVector(subItem, subArray); + } + return Value(subArray); + } + + return Value::VALUE_NULL; +} + +static void addNSObjectToCCVector(id item, ValueVector &array) { + array.push_back(convertNSObjectToCCValue(item)); +} + +static void addCCValueToNSArray(const Value &value, NSMutableArray *array) { + [array addObject:convertCCValueToNSObject(value)]; +} + +static void addNSObjectToCCMap(id nsKey, id nsValue, ValueMap &dict) { + // the key must be a string + CC_ASSERT([nsKey isKindOfClass:[NSString class]]); + ccstd::string key = [nsKey UTF8String]; + dict[key] = convertNSObjectToCCValue(nsValue); +} + +static void addCCValueToNSDictionary(const ccstd::string &key, const Value &value, NSMutableDictionary *dict) { + NSString *NSkey = [NSString stringWithCString:key.c_str() encoding:NSUTF8StringEncoding]; + [dict setObject:convertCCValueToNSObject(value) forKey:NSkey]; +} + +FileUtils *createFileUtils() { + // TODO(qgh):In the simulator, it will be called twice. So the judgment here is to prevent memory leaks. + // But this is equivalent to using a singleton pattern, + // which is not consistent with the current design and will be optimized later. + if (!FileUtils::getInstance()) { + return ccnew FileUtilsApple(); + } + return FileUtils::getInstance(); +} + +FileUtilsApple::FileUtilsApple() : pimpl_(ccnew IMPL([NSBundle mainBundle])) { + init(); +} + +#if CC_FILEUTILS_APPLE_ENABLE_OBJC +void FileUtilsApple::setBundle(NSBundle *bundle) { + pimpl_->setBundle(bundle); +} +#endif + +#pragma mark - FileUtils + +static NSFileManager *s_fileManager = [NSFileManager defaultManager]; + +ccstd::string FileUtilsApple::getWritablePath() const { + if (_writablePath.length()) { + return _writablePath; + } + + // save to document folder + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + ccstd::string strRet = [documentsDirectory UTF8String]; + strRet.append("/"); + return strRet; +} + +bool FileUtilsApple::isFileExistInternal(const ccstd::string &filePath) const { + if (filePath.empty()) { + return false; + } + + bool ret = false; + + if (filePath[0] != '/') { + ccstd::string path; + ccstd::string file; + size_t pos = filePath.find_last_of("/"); + if (pos != ccstd::string::npos) { + file = filePath.substr(pos + 1); + path = filePath.substr(0, pos + 1); + } else { + file = filePath; + } + + NSString *fullpath = [pimpl_->getBundle() pathForResource:[NSString stringWithUTF8String:file.c_str()] + ofType:nil + inDirectory:[NSString stringWithUTF8String:path.c_str()]]; + if (fullpath != nil) { + ret = true; + } + } else { + // Search path is an absolute path. + if ([s_fileManager fileExistsAtPath:[NSString stringWithUTF8String:filePath.c_str()]]) { + ret = true; + } + } + + return ret; +} + +static int unlink_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { + auto ret = remove(fpath); + if (ret) { + CC_LOG_INFO("Fail to remove: %s ", fpath); + } + + return ret; +} + +bool FileUtilsApple::removeDirectory(const ccstd::string &path) { + if (path.empty()) { + CC_LOG_ERROR("Fail to remove directory, path is empty!"); + return false; + } + + if (nftw(path.c_str(), unlink_cb, 64, FTW_DEPTH | FTW_PHYS)) + return false; + else + return true; +} + +ccstd::string FileUtilsApple::getFullPathForDirectoryAndFilename(const ccstd::string &directory, const ccstd::string &filename) const { + if (directory[0] != '/') { + NSString *dirStr = [NSString stringWithUTF8String:directory.c_str()]; + // The following logic is used for remove the "../" in the directory + // Because method "pathForResource" will return nil if the directory contains "../". + auto theIdx = directory.find(".."); + if (theIdx != ccstd::string::npos && theIdx > 0) { + NSMutableArray *pathComps = [NSMutableArray arrayWithArray:[dirStr pathComponents]]; + NSUInteger idx = [pathComps indexOfObject:@".."]; + while (idx != NSNotFound && idx > 0) { // if found ".." & it's not at the beginning of the string + [pathComps removeObjectAtIndex:idx]; // remove the item ".." + [pathComps removeObjectAtIndex:idx - 1]; // remove the item before ".." + idx = [pathComps indexOfObject:@".."]; // find ".." again + } + dirStr = [NSString pathWithComponents:pathComps]; + } + + NSString *fullpath = [pimpl_->getBundle() pathForResource:[NSString stringWithUTF8String:filename.c_str()] + ofType:nil + inDirectory:dirStr]; + if (fullpath != nil) { + return [fullpath UTF8String]; + } + } else { + ccstd::string fullPath = directory + filename; + // Search path is an absolute path. + if ([s_fileManager fileExistsAtPath:[NSString stringWithUTF8String:fullPath.c_str()]]) { + return fullPath; + } + } + return ""; +} + +ValueMap FileUtilsApple::getValueMapFromFile(const ccstd::string &filename) { + auto d(FileUtils::getInstance()->getDataFromFile(filename)); + return getValueMapFromData(reinterpret_cast(d.getBytes()), static_cast(d.getSize())); +} + +ValueMap FileUtilsApple::getValueMapFromData(const char *filedata, int filesize) { + NSData *file = [NSData dataWithBytes:filedata length:filesize]; + NSPropertyListFormat format; + NSError *error; + NSDictionary *dict = [NSPropertyListSerialization propertyListWithData:file options:NSPropertyListImmutable format:&format error:&error]; + + ValueMap ret; + + if (dict != nil) { + for (id key in [dict allKeys]) { + id value = [dict objectForKey:key]; + addNSObjectToCCMap(key, value, ret); + } + } + return ret; +} + +bool FileUtilsApple::writeToFile(const ValueMap &dict, const ccstd::string &fullPath) { + return writeValueMapToFile(dict, fullPath); +} + +bool FileUtils::writeValueMapToFile(const ValueMap &dict, const ccstd::string &fullPath) { + valueMapCompact(const_cast(dict)); + //CC_LOG_DEBUG("iOS||Mac Dictionary %d write to file %s", dict->_ID, fullPath.c_str()); + NSMutableDictionary *nsDict = [NSMutableDictionary dictionary]; + + for (auto iter = dict.begin(); iter != dict.end(); ++iter) { + addCCValueToNSDictionary(iter->first, iter->second, nsDict); + } + + NSString *file = [NSString stringWithUTF8String:fullPath.c_str()]; + // do it atomically + return [nsDict writeToFile:file atomically:YES]; +} + +void FileUtilsApple::valueMapCompact(ValueMap &valueMap) { + auto itr = valueMap.begin(); + while (itr != valueMap.end()) { + auto vtype = itr->second.getType(); + switch (vtype) { + case Value::Type::NONE: { + itr = valueMap.erase(itr); + continue; + } break; + case Value::Type::MAP: { + valueMapCompact(itr->second.asValueMap()); + } break; + case Value::Type::VECTOR: { + valueVectorCompact(itr->second.asValueVector()); + } break; + default: + break; + } + itr++; + } +} + +void FileUtilsApple::valueVectorCompact(ValueVector &valueVector) { + auto itr = valueVector.begin(); + while (itr != valueVector.end()) { + auto vtype = (*itr).getType(); + switch (vtype) { + case Value::Type::NONE: { + itr = valueVector.erase(itr); + continue; + } break; + case Value::Type::MAP: { + valueMapCompact((*itr).asValueMap()); + } break; + case Value::Type::VECTOR: { + valueVectorCompact((*itr).asValueVector()); + } break; + default: + break; + } + itr++; + } +} + +bool FileUtils::writeValueVectorToFile(const ValueVector &vecData, const ccstd::string &fullPath) { + NSString *path = [NSString stringWithUTF8String:fullPath.c_str()]; + NSMutableArray *array = [NSMutableArray array]; + + for (const auto &e : vecData) { + addCCValueToNSArray(e, array); + } + + [array writeToFile:path atomically:YES]; + + return true; +} +ValueVector FileUtilsApple::getValueVectorFromFile(const ccstd::string &filename) { + // NSString* pPath = [NSString stringWithUTF8String:pFileName]; + // NSString* pathExtension= [pPath pathExtension]; + // pPath = [pPath stringByDeletingPathExtension]; + // pPath = [[NSBundle mainBundle] pathForResource:pPath ofType:pathExtension]; + // fixing cannot read data using Array::createWithContentsOfFile + ccstd::string fullPath = fullPathForFilename(filename); + NSString *path = [NSString stringWithUTF8String:fullPath.c_str()]; + NSArray *array = [NSArray arrayWithContentsOfFile:path]; + + ValueVector ret; + + for (id value in array) { + addNSObjectToCCVector(value, ret); + } + + return ret; +} + +bool FileUtilsApple::createDirectory(const ccstd::string &path) { + CC_ASSERT(!path.empty()); + + if (isDirectoryExist(path)) + return true; + + NSError *error; + + bool result = [s_fileManager createDirectoryAtPath:[NSString stringWithUTF8String:path.c_str()] withIntermediateDirectories:YES attributes:nil error:&error]; + + if (!result && error != nil) { + CC_LOG_ERROR("Fail to create directory \"%s\": %s", path.c_str(), [error.localizedDescription UTF8String]); + } + + return result; +} + +} // namespace cc diff --git a/cocos/platform/apple/JsbBridge.h b/cocos/platform/apple/JsbBridge.h new file mode 100644 index 0000000..ec0e4c9 --- /dev/null +++ b/cocos/platform/apple/JsbBridge.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#import + +typedef void (^ICallback)(NSString*, NSString*); + +@interface JsbBridge : NSObject ++ (instancetype)sharedInstance; +- (void)setCallback:(ICallback)cb; +- (bool)callByScript:(NSString*)arg0 arg1:(NSString*)arg1; +- (void)sendToScript:(NSString*)arg0 arg1:(NSString*)arg1; +- (void)sendToScript:(NSString*)arg0; +@end diff --git a/cocos/platform/apple/JsbBridge.mm b/cocos/platform/apple/JsbBridge.mm new file mode 100644 index 0000000..9181037 --- /dev/null +++ b/cocos/platform/apple/JsbBridge.mm @@ -0,0 +1,98 @@ +/**************************************************************************** + Copyright (c) 2018-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "JsbBridge.h" +#import +#include "base/std/container/string.h" +#include "cocos/bindings/manual/JavaScriptObjCBridge.h" +#include "engine/EngineEvents.h" + +bool callPlatformStringMethod(const ccstd::string &arg0, const ccstd::string &arg1) { + NSString *oc_arg0 = [NSString stringWithCString:arg0.c_str() encoding:NSUTF8StringEncoding]; + NSString *oc_arg1 = [NSString stringWithCString:arg1.c_str() encoding:NSUTF8StringEncoding]; + JsbBridge *m = [JsbBridge sharedInstance]; + [m callByScript:oc_arg0 arg1:oc_arg1]; + return true; +} + +@implementation JsbBridge { + ICallback callback; + cc::events::Close::Listener closeListener; +} + +static JsbBridge *instance = nil; + ++ (instancetype)sharedInstance { + static dispatch_once_t pred = 0; + dispatch_once(&pred, ^{ + instance = [[super allocWithZone:NULL] init]; + NSAssert(instance != nil, @"alloc or init failed"); + }); + return instance; +} + ++ (id)allocWithZone:(struct _NSZone *)zone { + return [JsbBridge sharedInstance]; +} + +- (id)copyWithZone:(struct _NSZone *)zone { + return [JsbBridge sharedInstance]; +} + +- (id)init { + if (self = [super init]) { + closeListener.bind([&](){ + if ([JsbBridge sharedInstance] != nil) { + [[JsbBridge sharedInstance] release]; + } + }); + } + return self; +} + +- (void)setCallback:(ICallback)cb { + callback = cb; +} + +- (bool)callByScript:(NSString *)arg0 arg1:(NSString *)arg1 { + if (callback != nil) { + callback(arg0, arg1); + return true; + } + return false; +} + +- (void)sendToScript:(NSString *)arg0 arg1:(NSString *)arg1 { + const ccstd::string c_arg0{[arg0 UTF8String]}; + const ccstd::string c_arg1{[arg1 UTF8String]}; + callScript(c_arg0, c_arg1); +} + +- (void)sendToScript:(NSString *)arg0 { + const ccstd::string c_arg0{[arg0 UTF8String]}; + callScript(c_arg0, ""); +} + +@end diff --git a/cocos/platform/apple/JsbBridgeWrapper.h b/cocos/platform/apple/JsbBridgeWrapper.h new file mode 100644 index 0000000..ad887ae --- /dev/null +++ b/cocos/platform/apple/JsbBridgeWrapper.h @@ -0,0 +1,59 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#import + +typedef void (^OnScriptEventListener)(NSString*); + +@interface JsbBridgeWrapper : NSObject +/** + * Get the instance of JsbBridgetWrapper + */ ++ (instancetype)sharedInstance; +/** + * Add a listener to specified event, if the event does not exist, the wrapper will create one. Concurrent listener will be ignored + */ +- (void)addScriptEventListener:(NSString*)eventName listener:(OnScriptEventListener)listener; +/** + * Remove listener for specified event, concurrent event will be deleted. Return false only if the event does not exist + */ +- (bool)removeScriptEventListener:(NSString*)eventName listener:(OnScriptEventListener)listener; +/** + * Remove all listener for event specified. + */ +- (void)removeAllListenersForEvent:(NSString*)eventName; +/** + * Remove all event registered. Use it carefully! + */ +- (void)removeAllListeners; +/** + * Dispatch the event with argument, the event should be registered in javascript, or other script language in future. + */ +- (void)dispatchEventToScript:(NSString*)eventName arg:(NSString*)arg; +/** + * Dispatch the event which is registered in javascript, or other script language in future. + */ +- (void)dispatchEventToScript:(NSString*)eventName; +@end diff --git a/cocos/platform/apple/JsbBridgeWrapper.mm b/cocos/platform/apple/JsbBridgeWrapper.mm new file mode 100644 index 0000000..906ffa9 --- /dev/null +++ b/cocos/platform/apple/JsbBridgeWrapper.mm @@ -0,0 +1,134 @@ +/**************************************************************************** + Copyright (c) 2018-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include +#include "base/std/container/string.h" +#include "JsbBridge.h" +#include "JsbBridgeWrapper.h" +#include "engine/EngineEvents.h" + +@implementation JsbBridgeWrapper { + JsbBridge* jb; + NSMutableDictionary*>* cbDictionnary; + cc::events::Close::Listener closeListener; +} + +static JsbBridgeWrapper* instance = nil; +static ICallback cb = ^void(NSString* _event, NSString* _arg) { + [[JsbBridgeWrapper sharedInstance] triggerEvent:_event arg:_arg]; +}; ++ (instancetype)sharedInstance { + static dispatch_once_t pred = 0; + dispatch_once(&pred, ^{ + instance = [[super allocWithZone:NULL] init]; + NSAssert(instance != nil, @"alloc or init failed"); + }); + return instance; +} + ++ (id)allocWithZone:(struct _NSZone*)zone { + return [JsbBridgeWrapper sharedInstance]; +} + +- (id)copyWithZone:(struct _NSZone*)zone { + return [JsbBridgeWrapper sharedInstance]; +} + +- (void)addScriptEventListener:(NSString*)eventName listener:(OnScriptEventListener)listener { + if (![cbDictionnary objectForKey:eventName]) { + NSMutableArray *newArr = [[NSMutableArray alloc] init]; + [cbDictionnary setValue:newArr forKey:eventName]; + [newArr release]; + } + NSMutableArray* arr = [cbDictionnary objectForKey:eventName]; + if (![arr containsObject:listener]) { + [arr addObject:listener]; + } + [listener release]; +} + +- (void)triggerEvent:(NSString*)eventName arg:(NSString*)arg { + NSMutableArray* arr = [cbDictionnary objectForKey:eventName]; + if (!arr) { + return; + } + for (OnScriptEventListener listener : arr) { + listener(arg); + } +} +- (void)removeAllListenersForEvent:(NSString*)eventName { + if (![cbDictionnary objectForKey:eventName]) { + return; + } + //same as release all listeners. + [cbDictionnary removeObjectForKey:eventName]; +} + +- (bool)removeScriptEventListener:(NSString*)eventName listener:(OnScriptEventListener)listener { + NSMutableArray* arr = [cbDictionnary objectForKey:eventName]; + if (!arr) { + return false; + } + [arr removeObject:listener]; + return true; +} +- (void)removeAllListeners { + [cbDictionnary removeAllObjects]; +} +- (void)dispatchEventToScript:(NSString*)eventName arg:(NSString*)arg { + [jb sendToScript:eventName arg1:arg]; +} + +- (void)dispatchEventToScript:(NSString*)eventName { + [jb sendToScript:eventName]; +} +- (id)init { + if (self = [super init]) { + cbDictionnary = [NSMutableDictionary new]; + if (cbDictionnary == nil) { + [self release]; + return nil; + } + jb = [JsbBridge sharedInstance]; + if (jb == nil) { + [self release]; + return nil; + } + [jb setCallback:cb]; + closeListener.bind([&](){ + if ([JsbBridgeWrapper sharedInstance] != nil) { + [[JsbBridgeWrapper sharedInstance] release]; + } + }); + } + return self; +} +- (void)dealloc { + for (NSMutableArray* arr : cbDictionnary) { + [arr release]; + } + [cbDictionnary release]; + [super dealloc]; +} +@end diff --git a/cocos/platform/apple/Reachability.cpp b/cocos/platform/apple/Reachability.cpp new file mode 100644 index 0000000..fc4ea45 --- /dev/null +++ b/cocos/platform/apple/Reachability.cpp @@ -0,0 +1,218 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "Reachability.h" +#include +#include +#include +#include +#include +#include +#include "base/DeferredReleasePool.h" +#include "base/Macros.h" +#include "base/memory/Memory.h" + +namespace { + +#define ShouldPrintReachabilityFlags 0 + +static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char *comment) { +#if ShouldPrintReachabilityFlags + + printf("Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", + #if CC_PLATFORM == CC_PLATFORM_IOS + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + #else + '-', + #endif + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', + comment); +#endif +} + +cc::Reachability::NetworkStatus getNetworkStatusForFlags(SCNetworkReachabilityFlags flags) { + PrintReachabilityFlags(flags, "networkStatusForFlags"); + if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) { + // The target host is not reachable. + return cc::Reachability::NetworkStatus::NOT_REACHABLE; + } + + cc::Reachability::NetworkStatus returnValue = cc::Reachability::NetworkStatus::NOT_REACHABLE; + + if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) { + /* + If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi... + */ + returnValue = cc::Reachability::NetworkStatus::REACHABLE_VIA_WIFI; + } + + if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0) || + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) { + /* + ... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs... + */ + + if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) { + /* + ... and no [user] intervention is needed... + */ + returnValue = cc::Reachability::NetworkStatus::REACHABLE_VIA_WIFI; + } + } + +#if CC_PLATFORM == CC_PLATFORM_IOS + if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) { + /* + ... but WWAN connections are OK if the calling application is using the CFNetwork APIs. + */ + returnValue = cc::Reachability::NetworkStatus::REACHABLE_VIA_WWAN; + } +#endif + + return returnValue; +} +} // namespace + +namespace cc { + +Reachability *Reachability::createWithHostName(const ccstd::string &hostName) { + Reachability *returnValue = nullptr; + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(nullptr, hostName.c_str()); + if (reachability != nullptr) { + returnValue = ccnew Reachability(); + returnValue->addRef(); + if (returnValue != nullptr) { + cc::DeferredReleasePool::add(returnValue); + returnValue->_reachabilityRef = reachability; + } else { + CFRelease(reachability); + } + } + return returnValue; +} + +Reachability *Reachability::createWithAddress(const struct sockaddr *hostAddress) { + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, hostAddress); + + Reachability *returnValue = nullptr; + + if (reachability != nullptr) { + returnValue = ccnew Reachability(); + returnValue->addRef(); + if (returnValue != nullptr) { + cc::DeferredReleasePool::add(returnValue); + returnValue->_reachabilityRef = reachability; + } else { + CFRelease(reachability); + } + } + return returnValue; +} + +Reachability *Reachability::createForInternetConnection() { + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + return createWithAddress((const struct sockaddr *)&zeroAddress); +} + +Reachability::Reachability() +: _callback(nullptr), + _userData(nullptr), + _reachabilityRef(nullptr) { +} + +Reachability::~Reachability() { + stopNotifier(); + if (_reachabilityRef != nullptr) { + CFRelease(_reachabilityRef); + } +} + +void Reachability::onReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) { + CC_ASSERT_NOT_NULL(info); + + cc::Reachability *thiz = reinterpret_cast(info); + if (thiz->_callback != nullptr) { + NetworkStatus status = getNetworkStatusForFlags(flags); + thiz->_callback(thiz, status, thiz->_userData); + } +} + +bool Reachability::startNotifier(const ReachabilityCallback &cb, void *userData) { + _callback = cb; + _userData = userData; + + bool returnValue = false; + SCNetworkReachabilityContext context = {0, this, nullptr, nullptr, nullptr}; + + if (SCNetworkReachabilitySetCallback(_reachabilityRef, onReachabilityCallback, &context)) { + if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) { + returnValue = true; + } + } + + return returnValue; +} + +void Reachability::stopNotifier() { + if (_reachabilityRef != nullptr) { + SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + } +} + +bool Reachability::isConnectionRequired() const { + CC_ASSERT_NOT_NULL(_reachabilityRef); + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + + return false; +} + +Reachability::NetworkStatus Reachability::getCurrentReachabilityStatus() const { + CC_ASSERT_NOT_NULL(_reachabilityRef); + NetworkStatus returnValue = NetworkStatus::NOT_REACHABLE; + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) { + returnValue = getNetworkStatusForFlags(flags); + } + + return returnValue; +} + +} // namespace cc diff --git a/cocos/platform/apple/Reachability.h b/cocos/platform/apple/Reachability.h new file mode 100644 index 0000000..64b93f1 --- /dev/null +++ b/cocos/platform/apple/Reachability.h @@ -0,0 +1,86 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/RefCounted.h" + +#include +#include +#include "base/std/container/string.h" + +struct sockaddr; + +namespace cc { + +class Reachability final : public RefCounted { +public: + enum class NetworkStatus : uint8_t { + NOT_REACHABLE, + REACHABLE_VIA_WIFI, + REACHABLE_VIA_WWAN + }; + + /*! + * Use to check the reachability of a given host name. + */ + static Reachability *createWithHostName(const ccstd::string &hostName); + + /*! + * Use to check the reachability of a given IP address. + */ + static Reachability *createWithAddress(const struct sockaddr *hostAddress); + + /*! + * Checks whether the default route is available. Should be used by applications that do not connect to a particular host. + */ + static Reachability *createForInternetConnection(); + + using ReachabilityCallback = std::function; + + /*! + * Start listening for reachability notifications on the current run loop. + */ + bool startNotifier(const ReachabilityCallback &cb, void *userData); + void stopNotifier(); + + NetworkStatus getCurrentReachabilityStatus() const; + + /*! + * WWAN may be available, but not active until a connection has been established. WiFi may require a connection for VPN on Demand. + */ + bool isConnectionRequired() const; + +private: + Reachability(); + ~Reachability(); + + static void onReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info); + + ReachabilityCallback _callback; + void *_userData; + SCNetworkReachabilityRef _reachabilityRef; +}; + +} // namespace cc diff --git a/cocos/platform/apple/modules/CanvasRenderingContext2DDelegate.h b/cocos/platform/apple/modules/CanvasRenderingContext2DDelegate.h new file mode 100644 index 0000000..e3fea7c --- /dev/null +++ b/cocos/platform/apple/modules/CanvasRenderingContext2DDelegate.h @@ -0,0 +1,89 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/csscolorparser.h" +#include "base/std/container/array.h" +#include "bindings/jswrapper/config.h" +#include "math/Math.h" +#include "platform/interfaces/modules/canvas/ICanvasRenderingContext2D.h" + +#ifdef __OBJC__ +@class CanvasRenderingContext2DDelegateImpl; +#else +class CanvasRenderingContext2DDelegateImpl; +#endif + +namespace cc { + +class CanvasRenderingContext2DDelegate : public ICanvasRenderingContext2D::Delegate { +public: + using Size = ccstd::array; + using TextAlign = ICanvasRenderingContext2D::TextAlign; + using TextBaseline = ICanvasRenderingContext2D::TextBaseline; + CanvasRenderingContext2DDelegate(); + ~CanvasRenderingContext2DDelegate() override; + + void recreateBuffer(float w, float h) override; + void beginPath() override; + void closePath() override; + void moveTo(float x, float y) override; + void lineTo(float x, float y) override; + void stroke() override; + void saveContext() override; + void restoreContext() override; + void clearRect(float /*x*/, float /*y*/, float w, float h) override; + void fill() override; + void setLineCap(const ccstd::string &lineCap) override; + void setLineJoin(const ccstd::string &lineJoin) override; + void rect(float x, float y, float w, float h) override; + void fillRect(float x, float y, float w, float h) override; + void fillText(const ccstd::string &text, float x, float y, float /*maxWidth*/) override; + void strokeText(const ccstd::string &text, float /*x*/, float /*y*/, float /*maxWidth*/) override; + Size measureText(const ccstd::string &text) override; + void updateFont(const ccstd::string &fontName, float fontSize, bool bold, bool italic, bool oblique, bool smallCaps) override; + void setTextAlign(TextAlign align) override; + void setTextBaseline(TextBaseline baseline) override; + void setFillStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override; + void setStrokeStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override; + void setLineWidth(float lineWidth) override; + const cc::Data &getDataRef() const override; + void fillImageData(const Data &imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) override; + void updateData() override {} + void setShadowBlur(float blur) override; + void setShadowColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override; + void setShadowOffsetX(float offsetX) override; + void setShadowOffsetY(float offsetY) override; + +private: + void fillData(); + void unMultiplyAlpha(unsigned char *ptr, uint32_t size) const; + +public: +private: + CanvasRenderingContext2DDelegateImpl *_impl; +}; + +} // namespace cc diff --git a/cocos/platform/apple/modules/CanvasRenderingContext2DDelegate.mm b/cocos/platform/apple/modules/CanvasRenderingContext2DDelegate.mm new file mode 100644 index 0000000..0048396 --- /dev/null +++ b/cocos/platform/apple/modules/CanvasRenderingContext2DDelegate.mm @@ -0,0 +1,714 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/apple/modules/CanvasRenderingContext2DDelegate.h" +#include "base/UTF8.h" +#include "base/csscolorparser.h" +#include "math/Math.h" + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_platform.h" + +#import + +#if CC_PLATFORM == CC_PLATFORM_MACOS + #import +#else + #import + #import + + #define NSBezierPath UIBezierPath + #define NSFont UIFont + #define NSColor UIColor + #define NSSize CGSize + #define NSZeroSize CGSizeZero + #define NSPoint CGPoint + #define NSMakePoint CGPointMake + +#endif + +#include + +@interface CanvasRenderingContext2DDelegateImpl : NSObject { + NSFont *_font; + NSMutableDictionary *_tokenAttributesDict; + NSString *_fontName; + CGFloat _fontSize; + CGFloat _width; + CGFloat _height; + CGContextRef _context; + +#if CC_PLATFORM == CC_PLATFORM_MACOS + NSGraphicsContext *_currentGraphicsContext; + NSGraphicsContext *_oldGraphicsContext; +#else + CGContextRef _oldContext; +#endif + + CGColorSpaceRef _colorSpace; + cc::Data _imageData; + NSBezierPath *_path; + + cc::ICanvasRenderingContext2D::TextAlign _textAlign; + cc::ICanvasRenderingContext2D::TextBaseline _textBaseLine; + ccstd::array _fillStyle; + ccstd::array _strokeStyle; + NSColor *_shadowColor; + float _lineWidth; + bool _bold; + bool _italic; +} + +@property (nonatomic, strong) NSFont *font; +@property (nonatomic, strong) NSMutableDictionary *tokenAttributesDict; +@property (nonatomic, strong) NSString *fontName; +@property (nonatomic, assign) cc::ICanvasRenderingContext2D::TextAlign textAlign; +@property (nonatomic, assign) cc::ICanvasRenderingContext2D::TextBaseline textBaseLine; +@property (nonatomic, assign) float lineWidth; +@property (nonatomic, assign) float shadowBlur; +@property (nonatomic, assign) float shadowOffsetX; +@property (nonatomic, assign) float shadowOffsetY; + +@end + +@implementation CanvasRenderingContext2DDelegateImpl + +@synthesize font = _font; +@synthesize tokenAttributesDict = _tokenAttributesDict; +@synthesize fontName = _fontName; +@synthesize textAlign = _textAlign; +@synthesize textBaseLine = _textBaseLine; +@synthesize lineWidth = _lineWidth; + +- (id)init { + if (self = [super init]) { + _lineWidth = 0; + _textAlign = cc::ICanvasRenderingContext2D::TextAlign::LEFT; + _textBaseLine = cc::ICanvasRenderingContext2D::TextBaseline::BOTTOM; + _width = _height = 0; + _shadowBlur = _shadowOffsetX = _shadowOffsetY = 0; + _shadowColor = nil; + _context = nil; + _colorSpace = nil; + +#if CC_PLATFORM == CC_PLATFORM_MACOS + _currentGraphicsContext = nil; + _oldGraphicsContext = nil; +#endif + _path = [NSBezierPath bezierPath]; + [_path retain]; + [self updateFontWithName:@"Arial" fontSize:30 bold:false italic:false]; + } + + return self; +} + +- (void)dealloc { + self.font = nil; + self.tokenAttributesDict = nil; + self.fontName = nil; + if (_shadowColor) { + [_shadowColor release]; + } + _shadowColor = nil; + CGColorSpaceRelease(_colorSpace); + // release the context + CGContextRelease(_context); + [_path release]; +#if CC_PLATFORM == CC_PLATFORM_MACOS + [_currentGraphicsContext release]; +#endif + [super dealloc]; +} + +#if CC_PLATFORM == CC_PLATFORM_MACOS + +- (NSFont *)_createSystemFont { + NSFontTraitMask mask = NSUnitalicFontMask; + if (_italic) { + mask = NSItalicFontMask; + } + if (_bold) { + mask |= NSBoldFontMask; + } else { + mask |= NSUnboldFontMask; + } + + NSFont *font = [[NSFontManager sharedFontManager] + fontWithFamily:_fontName + traits:mask + weight:0 + size:_fontSize]; + + if (font == nil) { + const auto &familyMap = getFontFamilyNameMap(); + auto iter = familyMap.find([_fontName UTF8String]); + if (iter != familyMap.end()) { + font = [[NSFontManager sharedFontManager] + fontWithFamily:[NSString stringWithUTF8String:iter->second.c_str()] + traits:mask + weight:0 + size:_fontSize]; + } + } + + if (font == nil) { + font = [[NSFontManager sharedFontManager] + fontWithFamily:@"Arial" + traits:mask + weight:0 + size:_fontSize]; + } + return font; +} + +#else + +- (UIFont *)_createSystemFont { + UIFont *font = nil; + + if (_bold) { + font = [UIFont fontWithName:[_fontName stringByAppendingString:@"-Bold"] size:_fontSize]; + } else { + font = [UIFont fontWithName:_fontName size:_fontSize]; + } + + if (font == nil) { + const auto &familyMap = getFontFamilyNameMap(); + auto iter = familyMap.find([_fontName UTF8String]); + if (iter != familyMap.end()) { + font = [UIFont fontWithName:[NSString stringWithUTF8String:iter->second.c_str()] size:_fontSize]; + } + } + + if (font == nil) { + if (_bold) { + font = [UIFont boldSystemFontOfSize:_fontSize]; + } else { + font = [UIFont systemFontOfSize:_fontSize]; + } + } + return font; +} + +#endif + +- (void)updateFontWithName:(NSString *)fontName fontSize:(CGFloat)fontSize bold:(bool)bold italic:(bool)italic { + _fontSize = fontSize; + _bold = bold; + _italic = italic; + + self.fontName = fontName; + self.font = [self _createSystemFont]; + + NSMutableParagraphStyle *paragraphStyle = [[[NSMutableParagraphStyle alloc] init] autorelease]; + paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail; + [paragraphStyle setAlignment:NSTextAlignmentCenter]; + + // color + NSColor *foregroundColor = [NSColor colorWithRed:1.0f + green:1.0f + blue:1.0f + alpha:1.0f]; + + // attribute + if (_italic) { + self.tokenAttributesDict = [NSMutableDictionary dictionaryWithObjectsAndKeys: + foregroundColor, NSForegroundColorAttributeName, + _font, NSFontAttributeName, + @(0.25f), NSObliquenessAttributeName, + paragraphStyle, NSParagraphStyleAttributeName, nil]; + } else { + self.tokenAttributesDict = [NSMutableDictionary dictionaryWithObjectsAndKeys: + foregroundColor, NSForegroundColorAttributeName, + _font, NSFontAttributeName, + paragraphStyle, NSParagraphStyleAttributeName, nil]; + } + +} + +- (void)recreateBufferWithWidth:(NSInteger)width height:(NSInteger)height { + _width = width = width > 0 ? width : 1; + _height = height = height > 0 ? height : 1; + NSUInteger textureSize = width * height * 4; + unsigned char *data = (unsigned char *)malloc(sizeof(unsigned char) * textureSize); + memset(data, 0, textureSize); + _imageData.fastSet(data, static_cast(textureSize)); + + if (_context != nil) { + CGContextRelease(_context); + _context = nil; + } + +#if CC_PLATFORM == CC_PLATFORM_MACOS + if (_currentGraphicsContext != nil) { + [_currentGraphicsContext release]; + _currentGraphicsContext = nil; + } +#endif + + // draw text + _colorSpace = CGColorSpaceCreateDeviceRGB(); + _context = CGBitmapContextCreate(data, + width, + height, + 8, + width * 4, + _colorSpace, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + if (nil == _context) { + CGColorSpaceRelease(_colorSpace); //REFINE: HOWTO RELEASE? + _colorSpace = nil; + } + +#if CC_PLATFORM == CC_PLATFORM_MACOS + _currentGraphicsContext = [NSGraphicsContext graphicsContextWithCGContext:_context flipped:NO]; + [_currentGraphicsContext retain]; +#else + // move Y rendering to the top of the image + CGContextTranslateCTM(_context, 0.0f, _height); + + //NOTE: NSString draws in UIKit referential i.e. renders upside-down compared to CGBitmapContext referential + CGContextScaleCTM(_context, 1.0f, -1.0f); +#endif +} + +- (NSSize)measureText:(NSString *)text { + NSAttributedString *stringWithAttributes = [[[NSAttributedString alloc] initWithString:text + attributes:_tokenAttributesDict] autorelease]; + + NSSize textRect = NSZeroSize; + textRect.width = CGFLOAT_MAX; + textRect.height = CGFLOAT_MAX; + + NSSize dim = [stringWithAttributes boundingRectWithSize:textRect options:(NSStringDrawingOptions)(NSStringDrawingUsesLineFragmentOrigin)context:nil].size; + + dim.width = ceilf(dim.width); + dim.height = ceilf(dim.height); + + return dim; +} + +- (NSPoint)convertDrawPoint:(NSPoint)point text:(NSString *)text { + // The parameter 'point' is located at left-bottom position. + // Need to adjust 'point' according 'text align' & 'text base line'. + NSSize textSize = [self measureText:text]; + + if (_textAlign == cc::ICanvasRenderingContext2D::TextAlign::CENTER) { + point.x -= textSize.width / 2.0f; + } else if (_textAlign == cc::ICanvasRenderingContext2D::TextAlign::RIGHT) { + point.x -= textSize.width; + } + +#if CC_PLATFORM == CC_PLATFORM_MACOS + // The origin on macOS is bottom-left by default, + // so we need to convert y from top-left origin to bottom-left origin. + point.y = _height - point.y; + if (_textBaseLine == cc::ICanvasRenderingContext2D::TextBaseline::TOP) { + point.y += -textSize.height; + } else if (_textBaseLine == cc::ICanvasRenderingContext2D::TextBaseline::MIDDLE) { + point.y += -textSize.height / 2.0f; + } else if (_textBaseLine == cc::ICanvasRenderingContext2D::TextBaseline::BOTTOM) { + // drawAtPoint default + } else if (_textBaseLine == cc::ICanvasRenderingContext2D::TextBaseline::ALPHABETIC) { + point.y += _font.descender; + } +#else + if (_textBaseLine == cc::ICanvasRenderingContext2D::TextBaseline::TOP) { + // drawAtPoint default + } else if (_textBaseLine == cc::ICanvasRenderingContext2D::TextBaseline::MIDDLE) { + point.y += -textSize.height / 2.0f; + } else if (_textBaseLine == cc::ICanvasRenderingContext2D::TextBaseline::BOTTOM) { + point.y += -textSize.height; + } else if (_textBaseLine == cc::ICanvasRenderingContext2D::TextBaseline::ALPHABETIC) { + point.y -= _font.ascender; + } +#endif + + return point; +} + +- (bool) isShadowEnabled { + if (_shadowColor && (_shadowBlur > 0 || _shadowOffsetX > 0 || _shadowOffsetY > 0)) { + return true; + } + return false; +} + +- (void)fillText:(NSString *)text x:(CGFloat)x y:(CGFloat)y maxWidth:(CGFloat)maxWidth { + if (text.length == 0) + return; + + NSPoint drawPoint = [self convertDrawPoint:NSMakePoint(x, y) text:text]; + + NSMutableParagraphStyle *paragraphStyle = [[[NSMutableParagraphStyle alloc] init] autorelease]; + paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail; + + [_tokenAttributesDict removeObjectForKey:NSStrokeColorAttributeName]; + + [_tokenAttributesDict setObject:paragraphStyle forKey:NSParagraphStyleAttributeName]; + [_tokenAttributesDict setObject:[NSColor colorWithRed:_fillStyle[0] green:_fillStyle[1] blue:_fillStyle[2] alpha:_fillStyle[3]] + forKey:NSForegroundColorAttributeName]; + + [self saveContext]; + + // text color + CGContextSetRGBFillColor(_context, _fillStyle[0], _fillStyle[1], _fillStyle[2], _fillStyle[3]); + CGContextSetShouldSubpixelQuantizeFonts(_context, false); + CGContextBeginTransparencyLayerWithRect(_context, CGRectMake(0, 0, _width, _height), nullptr); + CGContextSetTextDrawingMode(_context, kCGTextFill); + if ([self isShadowEnabled]) { + // https://developer.apple.com/library/archive/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html + /* Core Graphics uses lower-left-origin coordinate system; + but the api CGContextSetShadowWithColor's parameter 'offset' accept base space(relative to view: upper-left-origin coordinate system), + and _shadowOffsetY is edited by lower-left-origin coordinate system, so here we need to multiply the change in y direction by -1 + */ + CGContextSetShadowWithColor(_context, CGSizeMake(_shadowOffsetX, -_shadowOffsetY), _shadowBlur, _shadowColor.CGColor); + } + + NSAttributedString *stringWithAttributes = [[[NSAttributedString alloc] initWithString:text + attributes:_tokenAttributesDict] autorelease]; + + [stringWithAttributes drawAtPoint:drawPoint]; + + CGContextEndTransparencyLayer(_context); + + [self restoreContext]; +} + +- (void)strokeText:(NSString *)text x:(CGFloat)x y:(CGFloat)y maxWidth:(CGFloat)maxWidth { + if (text.length == 0) + return; + + NSPoint drawPoint = [self convertDrawPoint:NSMakePoint(x, y) text:text]; + + NSMutableParagraphStyle *paragraphStyle = [[[NSMutableParagraphStyle alloc] init] autorelease]; + paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail; + + [_tokenAttributesDict removeObjectForKey:NSForegroundColorAttributeName]; + + [_tokenAttributesDict setObject:paragraphStyle forKey:NSParagraphStyleAttributeName]; + [_tokenAttributesDict setObject:[NSColor colorWithRed:_strokeStyle[0] + green:_strokeStyle[1] + blue:_strokeStyle[2] + alpha:_strokeStyle[3]] + forKey:NSStrokeColorAttributeName]; + + [self saveContext]; + + // text color + CGContextSetRGBStrokeColor(_context, _strokeStyle[0], _strokeStyle[1], _strokeStyle[2], _strokeStyle[3]); + CGContextSetRGBFillColor(_context, _fillStyle[0], _fillStyle[1], _fillStyle[2], _fillStyle[3]); + CGContextSetLineWidth(_context, _lineWidth); + CGContextSetLineJoin(_context, kCGLineJoinRound); + CGContextSetShouldSubpixelQuantizeFonts(_context, false); + CGContextBeginTransparencyLayerWithRect(_context, CGRectMake(0, 0, _width, _height), nullptr); + + CGContextSetTextDrawingMode(_context, kCGTextStroke); + if ([self isShadowEnabled]) { + // https://developer.apple.com/library/archive/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html + /* Core Graphics uses lower-left-origin coordinate system; + but the api CGContextSetShadowWithColor's parameter 'offset' accept base space(relative to view: upper-left-origin coordinate system), + and _shadowOffsetY is edited by lower-left-origin coordinate system, so here we need to multiply the change in y direction by -1 + */ + CGContextSetShadowWithColor(_context, CGSizeMake(_shadowOffsetX, -_shadowOffsetY), _shadowBlur, _shadowColor.CGColor); + } + + NSAttributedString *stringWithAttributes = [[[NSAttributedString alloc] initWithString:text + attributes:_tokenAttributesDict] autorelease]; + + [stringWithAttributes drawAtPoint:drawPoint]; + + CGContextEndTransparencyLayer(_context); + + [self restoreContext]; +} + +- (void)setFillStyleWithRed:(CGFloat)r green:(CGFloat)g blue:(CGFloat)b alpha:(CGFloat)a { + _fillStyle[0] = r; + _fillStyle[1] = g; + _fillStyle[2] = b; + _fillStyle[3] = a; +} + +- (void)setStrokeStyleWithRed:(CGFloat)r green:(CGFloat)g blue:(CGFloat)b alpha:(CGFloat)a { + _strokeStyle[0] = r; + _strokeStyle[1] = g; + _strokeStyle[2] = b; + _strokeStyle[3] = a; +} + +- (void)setShadowColorWithRed:(CGFloat)r green:(CGFloat)g blue:(CGFloat)b alpha:(CGFloat)a { + _shadowColor = [NSColor colorWithRed:r green:g blue:b alpha:a]; + [_shadowColor retain]; +} + +- (const cc::Data &)getDataRef { + return _imageData; +} + +- (void)clearRect:(CGRect)rect { + if (_imageData.isNull()) + return; + + rect.origin.x = floor(rect.origin.x); + rect.origin.y = floor(rect.origin.y); + rect.size.width = floor(rect.size.width); + rect.size.height = floor(rect.size.height); + + if (rect.origin.x < 0) rect.origin.x = 0; + if (rect.origin.y < 0) rect.origin.y = 0; + + if (rect.size.width < 1 || rect.size.height < 1) + return; + //REFINE: + // CC_ASSERT(rect.origin.x == 0 && rect.origin.y == 0); + memset((void *)_imageData.getBytes(), 0x00, _imageData.getSize()); +} + +- (void)fillRect:(CGRect)rect { + [self saveContext]; + + NSColor *color = [NSColor colorWithRed:_fillStyle[0] green:_fillStyle[1] blue:_fillStyle[2] alpha:_fillStyle[3]]; + [color setFill]; +#if CC_PLATFORM == CC_PLATFORM_MACOS + CGRect tmpRect = CGRectMake(rect.origin.x, _height - rect.origin.y - rect.size.height, rect.size.width, rect.size.height); + [NSBezierPath fillRect:tmpRect]; +#else + NSBezierPath *path = [NSBezierPath bezierPathWithRect:rect]; + [path fill]; +#endif + [self restoreContext]; +} + +- (void)saveContext { +#if CC_PLATFORM == CC_PLATFORM_MACOS + // save the old graphics context + _oldGraphicsContext = [NSGraphicsContext currentContext]; + // store the current context + [NSGraphicsContext setCurrentContext:_currentGraphicsContext]; + // push graphics state to stack + [NSGraphicsContext saveGraphicsState]; + [[NSGraphicsContext currentContext] setShouldAntialias:YES]; +#else + // save the old graphics context + _oldContext = UIGraphicsGetCurrentContext(); + // store the current context + UIGraphicsPushContext(_context); + CGContextSaveGState(_context); +#endif +} + +- (void)restoreContext { +#if CC_PLATFORM == CC_PLATFORM_MACOS + // pop the context + [NSGraphicsContext restoreGraphicsState]; + // reset the old graphics context + [NSGraphicsContext setCurrentContext:_oldGraphicsContext]; + _oldGraphicsContext = nil; +#else + // pop the context + CGContextRestoreGState(_context); + // reset the old graphics context + UIGraphicsPopContext(); + _oldContext = nil; +#endif +} + +- (void)beginPath { +} + +- (void)stroke { + NSColor *color = [NSColor colorWithRed:_strokeStyle[0] green:_strokeStyle[1] blue:_strokeStyle[2] alpha:_strokeStyle[3]]; + [color setStroke]; + [_path setLineWidth:_lineWidth]; + [_path stroke]; +} + +- (void)moveToX:(float)x y:(float)y { +#if CC_PLATFORM == CC_PLATFORM_MACOS + [_path moveToPoint:NSMakePoint(x, _height - y)]; +#else + [_path moveToPoint:NSMakePoint(x, y)]; +#endif +} + +- (void)lineToX:(float)x y:(float)y { +#if CC_PLATFORM == CC_PLATFORM_MACOS + [_path lineToPoint:NSMakePoint(x, _height - y)]; +#else + [_path addLineToPoint:NSMakePoint(x, y)]; +#endif +} + +@end + +namespace cc { +CanvasRenderingContext2DDelegate::CanvasRenderingContext2DDelegate() { + _impl = [[CanvasRenderingContext2DDelegateImpl alloc] init]; +} + +CanvasRenderingContext2DDelegate::~CanvasRenderingContext2DDelegate() { + [_impl release]; +} + +void CanvasRenderingContext2DDelegate::recreateBuffer(float w, float h) { + [_impl recreateBufferWithWidth:w height:h]; +} + +const cc::Data &CanvasRenderingContext2DDelegate::getDataRef() const { + static Data data; + data = [_impl getDataRef]; + unMultiplyAlpha(data.getBytes(), data.getSize()); + return data; +} + +void CanvasRenderingContext2DDelegate::beginPath() { + [_impl beginPath]; +} + +void CanvasRenderingContext2DDelegate::closePath() { +} + +void CanvasRenderingContext2DDelegate::moveTo(float x, float y) { + [_impl moveToX:x y:y]; +} + +void CanvasRenderingContext2DDelegate::lineTo(float x, float y) { + [_impl lineToX:x y:y]; +} + +void CanvasRenderingContext2DDelegate::stroke() { + [_impl stroke]; +} + +void CanvasRenderingContext2DDelegate::saveContext() { + [_impl saveContext]; +} + +void CanvasRenderingContext2DDelegate::restoreContext() { + [_impl restoreContext]; +} + +void CanvasRenderingContext2DDelegate::clearRect(float x, float y, float w, float h) { + [_impl clearRect:CGRectMake(x, y, w, h)]; +} + +void CanvasRenderingContext2DDelegate::fill() { +} + +void CanvasRenderingContext2DDelegate::setLineCap(const ccstd::string &lineCap) { +} + +void CanvasRenderingContext2DDelegate::setLineJoin(const ccstd::string &lineJoin) { +} + +void CanvasRenderingContext2DDelegate::rect(float x, float y, float w, float h) { +} + +void CanvasRenderingContext2DDelegate::fillRect(float x, float y, float w, float h) { + [_impl fillRect:CGRectMake(x, y, w, h)]; +} + +void CanvasRenderingContext2DDelegate::fillText(const ccstd::string &text, float x, float y, float maxWidth) { + [_impl fillText:[NSString stringWithUTF8String:text.c_str()] x:x y:y maxWidth:maxWidth]; +} + +void CanvasRenderingContext2DDelegate::strokeText(const ccstd::string &text, float x, float y, float maxWidth) { + [_impl strokeText:[NSString stringWithUTF8String:text.c_str()] x:x y:y maxWidth:maxWidth]; +} + +CanvasRenderingContext2DDelegate::Size CanvasRenderingContext2DDelegate::measureText(const ccstd::string &text) { + NSString *str = [NSString stringWithUTF8String:text.c_str()]; + if (str == nil) { + ccstd::string textNew; + cc::StringUtils::UTF8LooseFix(text, textNew); + str = [NSString stringWithUTF8String:textNew.c_str()]; + } + CGSize size = [_impl measureText:str]; + return CanvasRenderingContext2DDelegate::Size{(float)size.width, (float)size.height}; +} + +void CanvasRenderingContext2DDelegate::updateFont(const ccstd::string &fontName, float fontSize, bool bold, bool italic, bool oblique, bool smallCaps) { + CGFloat gfloatFontSize = fontSize; + [_impl updateFontWithName:[NSString stringWithUTF8String:fontName.c_str()] fontSize:gfloatFontSize bold:bold italic:italic]; +} + +void CanvasRenderingContext2DDelegate::setTextAlign(TextAlign align) { + _impl.textAlign = align; +} + +void CanvasRenderingContext2DDelegate::setTextBaseline(TextBaseline baseline) { + _impl.textBaseLine = baseline; +} + +void CanvasRenderingContext2DDelegate::setFillStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + [_impl setFillStyleWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:a / 255.0f]; +} + +void CanvasRenderingContext2DDelegate::setStrokeStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + [_impl setStrokeStyleWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:a / 255.0f]; +} + +void CanvasRenderingContext2DDelegate::setLineWidth(float lineWidth) { + _impl.lineWidth = lineWidth; +} + +void CanvasRenderingContext2DDelegate::fillImageData(const Data &imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) { +} + +void CanvasRenderingContext2DDelegate::fillData() { +} + +#define CLAMP(V, HI) std::min((V), (HI)) +void CanvasRenderingContext2DDelegate::unMultiplyAlpha(unsigned char *ptr, uint32_t size) const { + float alpha; + for (int i = 0; i < size; i += 4) { + alpha = (float)ptr[i + 3]; + if (alpha > 0) { + ptr[i] = CLAMP((int)((float)ptr[i] / alpha * 255), 255); + ptr[i + 1] = CLAMP((int)((float)ptr[i + 1] / alpha * 255), 255); + ptr[i + 2] = CLAMP((int)((float)ptr[i + 2] / alpha * 255), 255); + } + } +} + +void CanvasRenderingContext2DDelegate::setShadowBlur(float blur) { + _impl.shadowBlur = blur * 0.5f; +} + +void CanvasRenderingContext2DDelegate::setShadowColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + [_impl setShadowColorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:a / 255.0f]; +} + +void CanvasRenderingContext2DDelegate::setShadowOffsetX(float offsetX) { + _impl.shadowOffsetX = offsetX; +} + +void CanvasRenderingContext2DDelegate::setShadowOffsetY(float offsetY) { + _impl.shadowOffsetY = offsetY; +} + +} // namespace cc diff --git a/cocos/platform/empty/EmptyPlatform.cpp b/cocos/platform/empty/EmptyPlatform.cpp new file mode 100644 index 0000000..7b247e8 --- /dev/null +++ b/cocos/platform/empty/EmptyPlatform.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/empty/EmptyPlatform.h" +#include "modules/Accelerometer.h" +#include "modules/Battery.h" +#include "modules/Network.h" +#include "modules/Screen.h" +#include "modules/System.h" +#include "modules/SystemWindow.h" +#include "modules/Vibrator.h" +#include "platform/interfaces/OSInterface.h" + +#include +#include + +namespace cc { +EmptyPlatform::EmptyPlatform() = default; +EmptyPlatform::~EmptyPlatform() { +} + +int32_t EmptyPlatform::init() { + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + return 0; +} + +int32_t EmptyPlatform::loop() { + while (!_quit) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + return 0; +} + +} // namespace cc diff --git a/cocos/platform/empty/EmptyPlatform.h b/cocos/platform/empty/EmptyPlatform.h new file mode 100644 index 0000000..15fbe25 --- /dev/null +++ b/cocos/platform/empty/EmptyPlatform.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/UniversalPlatform.h" +#include "platform/empty/modules/SystemWindow.h" + +namespace cc { + +class CC_DLL EmptyPlatform : public UniversalPlatform { +public: + EmptyPlatform(); + /** + * Destructor of WindowPlatform. + */ + ~EmptyPlatform() override; + /** + * Implementation of Windows platform initialization. + */ + int32_t init() override; + + int32_t loop() override; + +private: + bool _quit{false}; +}; + +} // namespace cc diff --git a/cocos/platform/empty/modules/Accelerometer.cpp b/cocos/platform/empty/modules/Accelerometer.cpp new file mode 100644 index 0000000..611ccd6 --- /dev/null +++ b/cocos/platform/empty/modules/Accelerometer.cpp @@ -0,0 +1,39 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/empty/modules/Accelerometer.h" + +namespace cc { +void Accelerometer::setAccelerometerEnabled(bool isEnabled) { +} + +void Accelerometer::setAccelerometerInterval(float interval) { +} + +const Accelerometer::MotionValue &Accelerometer::getDeviceMotionValue() { + static MotionValue motionValue; + return motionValue; +} + +} // namespace cc diff --git a/cocos/platform/empty/modules/Accelerometer.h b/cocos/platform/empty/modules/Accelerometer.h new file mode 100644 index 0000000..8a577f4 --- /dev/null +++ b/cocos/platform/empty/modules/Accelerometer.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IAccelerometer.h" + +namespace cc { + +class CC_DLL Accelerometer : public IAccelerometer { +public: + /** + * To enable or disable accelerometer. + */ + void setAccelerometerEnabled(bool isEnabled) override; + + /** + * Sets the interval of accelerometer. + */ + void setAccelerometerInterval(float interval) override; + + /** + * Gets the motion value of current device. + */ + const MotionValue &getDeviceMotionValue() override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/empty/modules/Battery.cpp b/cocos/platform/empty/modules/Battery.cpp new file mode 100644 index 0000000..a99b1a6 --- /dev/null +++ b/cocos/platform/empty/modules/Battery.cpp @@ -0,0 +1,32 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "platform/empty/modules/Battery.h" + +namespace cc { + +float Battery::getBatteryLevel() const { + return 1.0F; +} + +} // namespace cc diff --git a/cocos/platform/empty/modules/Battery.h b/cocos/platform/empty/modules/Battery.h new file mode 100644 index 0000000..31a428a --- /dev/null +++ b/cocos/platform/empty/modules/Battery.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IBattery.h" + +namespace cc { + +class CC_DLL Battery : public IBattery { +public: + float getBatteryLevel() const override; +}; + +} // namespace cc diff --git a/cocos/platform/empty/modules/CanvasRenderingContext2DDelegate.cpp b/cocos/platform/empty/modules/CanvasRenderingContext2DDelegate.cpp new file mode 100644 index 0000000..d648e1a --- /dev/null +++ b/cocos/platform/empty/modules/CanvasRenderingContext2DDelegate.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/empty/modules/CanvasRenderingContext2DDelegate.h" +#include "platform/empty/EmptyPlatform.h" + +namespace { +#define RGB(r, g, b) (int)((int)r | (((int)g) << 8) | (((int)b) << 16)) +#define RGBA(r, g, b, a) (int)((int)r | (((int)g) << 8) | (((int)b) << 16) | (((int)a) << 24)) +} // namespace + +namespace cc { + +CanvasRenderingContext2DDelegate::CanvasRenderingContext2DDelegate() { +} + +CanvasRenderingContext2DDelegate::~CanvasRenderingContext2DDelegate() { +} + +void CanvasRenderingContext2DDelegate::recreateBuffer(float w, float h) { +} + +void CanvasRenderingContext2DDelegate::beginPath() { +} + +void CanvasRenderingContext2DDelegate::closePath() { +} + +void CanvasRenderingContext2DDelegate::moveTo(float x, float y) { + // +} + +void CanvasRenderingContext2DDelegate::lineTo(float x, float y) { +} + +void CanvasRenderingContext2DDelegate::stroke() { +} + +void CanvasRenderingContext2DDelegate::saveContext() { +} + +void CanvasRenderingContext2DDelegate::restoreContext() { +} + +void CanvasRenderingContext2DDelegate::clearRect(float x, float y, float w, float h) { +} + +void CanvasRenderingContext2DDelegate::fillRect(float x, float y, float w, float h) { +} + +void CanvasRenderingContext2DDelegate::fillText(const ccstd::string &text, float x, float y, float /*maxWidth*/) { +} + +void CanvasRenderingContext2DDelegate::strokeText(const ccstd::string &text, float /*x*/, float /*y*/, float /*maxWidth*/) const { +} + +CanvasRenderingContext2DDelegate::Size CanvasRenderingContext2DDelegate::measureText(const ccstd::string &text) { + return Size{0, 0}; +} + +void CanvasRenderingContext2DDelegate::updateFont(const ccstd::string &fontName, + float fontSize, + bool bold, + bool italic, + bool oblique, + bool /* smallCaps */) { +} + +void CanvasRenderingContext2DDelegate::setTextAlign(TextAlign align) { +} + +void CanvasRenderingContext2DDelegate::setTextBaseline(TextBaseline baseline) { + // +} + +void CanvasRenderingContext2DDelegate::setFillStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + // +} + +void CanvasRenderingContext2DDelegate::setStrokeStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + // +} + +void CanvasRenderingContext2DDelegate::setLineWidth(float lineWidth) { + // +} + +const cc::Data &CanvasRenderingContext2DDelegate::getDataRef() const { + return _imageData; +} + +void CanvasRenderingContext2DDelegate::fill() { +} + +void CanvasRenderingContext2DDelegate::setLineCap(const ccstd::string &lineCap) { +} + +void CanvasRenderingContext2DDelegate::setLineJoin(const ccstd::string &lineJoin) { +} + +void CanvasRenderingContext2DDelegate::fillImageData(const Data & /* imageData */, + float /* imageWidth */, + float /* imageHeight */, + float /* offsetX */, + float /* offsetY */) { + //XCreateImage(display, visual, DefaultDepth(display,DefaultScreen(display)), ZPixmap, 0, image32, width, height, 32, 0); + //XPutImage(dpy, w, gc, image, 0, 0, 50, 60, 40, 30); +} + +void CanvasRenderingContext2DDelegate::strokeText(const ccstd::string & /* text */, + float /* x */, + float /* y */, + float /* maxWidth */) { +} + +void CanvasRenderingContext2DDelegate::rect(float /* x */, + float /* y */, + float /* w */, + float /* h */) { +} + +} // namespace cc diff --git a/cocos/platform/empty/modules/CanvasRenderingContext2DDelegate.h b/cocos/platform/empty/modules/CanvasRenderingContext2DDelegate.h new file mode 100644 index 0000000..b69a595 --- /dev/null +++ b/cocos/platform/empty/modules/CanvasRenderingContext2DDelegate.h @@ -0,0 +1,86 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/canvas/ICanvasRenderingContext2D.h" + +#include +#include +#include "base/csscolorparser.h" +#include "base/std/container/array.h" +#include "cocos/bindings/manual/jsb_platform.h" +#include "math/Math.h" +#include "platform/FileUtils.h" + +namespace cc { + +class CC_DLL CanvasRenderingContext2DDelegate : public ICanvasRenderingContext2D::Delegate { +public: + using Point = ccstd::array; + using Vec2 = ccstd::array; + using Size = ccstd::array; + using Color4F = ccstd::array; + using TextAlign = ICanvasRenderingContext2D::TextAlign; + using TextBaseline = ICanvasRenderingContext2D::TextBaseline; + CanvasRenderingContext2DDelegate(); + ~CanvasRenderingContext2DDelegate() override; + + void recreateBuffer(float w, float h) override; + void beginPath() override; + void closePath() override; + void moveTo(float x, float y) override; + void lineTo(float x, float y) override; + void stroke() override; + void saveContext() override; + void restoreContext() override; + void clearRect(float /*x*/, float /*y*/, float w, float h) override; + void fillRect(float x, float y, float w, float h) override; + void fillText(const ccstd::string &text, float x, float y, float /*maxWidth*/) override; + void strokeText(const ccstd::string &text, float /*x*/, float /*y*/, float /*maxWidth*/) const; + Size measureText(const ccstd::string &text) override; + void updateFont(const ccstd::string &fontName, float fontSize, bool bold, bool italic, bool oblique, bool smallCaps) override; + void setTextAlign(TextAlign align) override; + void setTextBaseline(TextBaseline baseline) override; + void setFillStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override; + void setStrokeStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override; + void setLineWidth(float lineWidth) override; + const cc::Data &getDataRef() const override; + void fill() override; + void setLineCap(const ccstd::string &lineCap) override; + void setLineJoin(const ccstd::string &lineCap) override; + void fillImageData(const Data &imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) override; + void strokeText(const ccstd::string &text, float /*x*/, float /*y*/, float /*maxWidth*/) override; + void rect(float x, float y, float w, float h) override; + void updateData() override {} + void setShadowBlur(float blur) override {} + void setShadowColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override {} + void setShadowOffsetX(float offsetX) override {} + void setShadowOffsetY(float offsetY) override {} + +private: + cc::Data _imageData; +}; + +} // namespace cc diff --git a/cocos/platform/empty/modules/Network.cpp b/cocos/platform/empty/modules/Network.cpp new file mode 100644 index 0000000..790bda9 --- /dev/null +++ b/cocos/platform/empty/modules/Network.cpp @@ -0,0 +1,32 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ +#include "platform/empty/modules/Network.h" + +namespace cc { + +INetwork::NetworkType Network::getNetworkType() const { + return INetwork::NetworkType::LAN; +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/empty/modules/Network.h b/cocos/platform/empty/modules/Network.h new file mode 100644 index 0000000..27b36dd --- /dev/null +++ b/cocos/platform/empty/modules/Network.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/INetwork.h" + +namespace cc { + +class CC_DLL Network : public INetwork { +public: + NetworkType getNetworkType() const override; +}; + +} // namespace cc diff --git a/cocos/platform/empty/modules/Screen.cpp b/cocos/platform/empty/modules/Screen.cpp new file mode 100644 index 0000000..8772bc3 --- /dev/null +++ b/cocos/platform/empty/modules/Screen.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/empty/modules/Screen.h" +#include "base/Macros.h" +#include "cocos/bindings/jswrapper/SeApi.h" + +namespace cc { + +int Screen::getDPI() const { + static int dpi = -1; + return dpi; +} + +float Screen::getDevicePixelRatio() const { + return 1; +} + +void Screen::setKeepScreenOn(bool value) { + CC_UNUSED_PARAM(value); +} + +Screen::Orientation Screen::getDeviceOrientation() const { + return Orientation::PORTRAIT; +} + +Vec4 Screen::getSafeAreaEdge() const { + return cc::Vec4(); +} + +bool Screen::isDisplayStats() { + se::AutoHandleScope hs; + se::Value ret; + char commandBuf[100] = "cc.profiler.isShowingStats();"; + se::ScriptEngine::getInstance()->evalString(commandBuf, 100, &ret); + return ret.toBoolean(); +} + +void Screen::setDisplayStats(bool isShow) { + se::AutoHandleScope hs; + char commandBuf[100] = {0}; + sprintf(commandBuf, isShow ? "cc.profiler.showStats();" : "cc.profiler.hideStats();"); + se::ScriptEngine::getInstance()->evalString(commandBuf); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/empty/modules/Screen.h b/cocos/platform/empty/modules/Screen.h new file mode 100644 index 0000000..40628aa --- /dev/null +++ b/cocos/platform/empty/modules/Screen.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IScreen.h" + +namespace cc { + +class CC_DLL Screen : public IScreen { +public: + int getDPI() const override; + float getDevicePixelRatio() const override; + void setKeepScreenOn(bool value) override; + Orientation getDeviceOrientation() const override; + Vec4 getSafeAreaEdge() const override; + /** + @brief Get current display stats. + @return bool, is displaying stats or not. + */ + bool isDisplayStats() override; + + /** + @brief set display stats information. + */ + void setDisplayStats(bool isShow) override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/empty/modules/System.cpp b/cocos/platform/empty/modules/System.cpp new file mode 100644 index 0000000..c9fe0af --- /dev/null +++ b/cocos/platform/empty/modules/System.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/empty/modules/System.h" +#include + +namespace cc { +using OSType = System::OSType; + +OSType System::getOSType() const { + return OSType::LINUX; +} + +ccstd::string System::getDeviceModel() const { + return "Empty"; +} + +System::LanguageType System::getCurrentLanguage() const { + return LanguageType::ENGLISH; +} + +ccstd::string System::getCurrentLanguageCode() const { + return "en"; +} + +ccstd::string System::getSystemVersion() const { + return "empty"; +} + +bool System::openURL(const ccstd::string &url) { + return true; +} + +System::LanguageType System::getLanguageTypeByISO2(const char *code) const { + // this function is used by all platforms to get system language + // except windows: cocos/platform/win32/CCApplication-win32.cpp + LanguageType ret = LanguageType::ENGLISH; + return ret; +} + +void System::copyTextToClipboard(const ccstd::string &text) { + // TODO +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/empty/modules/System.h b/cocos/platform/empty/modules/System.h new file mode 100644 index 0000000..68c7799 --- /dev/null +++ b/cocos/platform/empty/modules/System.h @@ -0,0 +1,68 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/ISystem.h" + +namespace cc { + +class CC_DLL System : public ISystem { +public: + /** + @brief Get target system type. + */ + OSType getOSType() const override; + /** + @brief Get target device model. + */ + ccstd::string getDeviceModel() const override; + /** + @brief Get current language config. + @return Current language config. + */ + LanguageType getCurrentLanguage() const override; + /** + @brief Get current language iso 639-1 code. + @return Current language iso 639-1 code. + */ + ccstd::string getCurrentLanguageCode() const override; + /** + @brief Get system version. + @return system version. + */ + ccstd::string getSystemVersion() const override; + /** + @brief Open url in default browser. + @param String with url to open. + @return True if the resource located by the URL was successfully opened; otherwise false. + */ + bool openURL(const ccstd::string &url) override; + void copyTextToClipboard(const ccstd::string &text) override; + +private: + LanguageType getLanguageTypeByISO2(const char *code) const; +}; + +} // namespace cc diff --git a/cocos/platform/empty/modules/SystemWindow.cpp b/cocos/platform/empty/modules/SystemWindow.cpp new file mode 100644 index 0000000..e40c9ba --- /dev/null +++ b/cocos/platform/empty/modules/SystemWindow.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/empty/modules/SystemWindow.h" +#include +#include "base/Log.h" +#include "base/Macros.h" +#include "engine/EngineEvents.h" +#include "platform/empty/EmptyPlatform.h" + +namespace { + +} // namespace + +namespace cc { +SystemWindow::SystemWindow(uint32_t windowId, void* externalHandle) +: _windowId(windowId) { + if (externalHandle) { + _windowHandle = reinterpret_cast(externalHandle); + } +} + +SystemWindow::~SystemWindow() = default; + +uintptr_t SystemWindow::getWindowHandle() const { + return _windowHandle; +} + +uint32_t SystemWindow::getWindowId() const { + return _windowId; +} + +void SystemWindow::setCursorEnabled(bool value) { +} + +int SystemWindow::init() { + return 0; +} + +void SystemWindow::pollEvent(bool* quit) { + return; +} + +SystemWindow::Size SystemWindow::getViewSize() const { + return Size{static_cast(_width), static_cast(_height)}; +} + +} // namespace cc diff --git a/cocos/platform/empty/modules/SystemWindow.h b/cocos/platform/empty/modules/SystemWindow.h new file mode 100644 index 0000000..dd1b14a --- /dev/null +++ b/cocos/platform/empty/modules/SystemWindow.h @@ -0,0 +1,58 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +#include "platform/interfaces/modules/ISystemWindow.h" + +namespace cc { + +class CC_DLL SystemWindow : public ISystemWindow { +public: + explicit SystemWindow(uint32_t windowId, void* externalHandle); + ~SystemWindow() override; + + uintptr_t getWindowHandle() const override; + uint32_t getWindowId() const override; + + Size getViewSize() const override; + /* + @brief enable/disable(lock) the cursor, default is enabled + */ + void setCursorEnabled(bool value) override; + + int init(); + void pollEvent(bool* quit); + +private: + int _width{0}; + int _height{0}; + + uint32_t _windowId{0}; + uintptr_t _windowHandle{0}; +}; + +} // namespace cc diff --git a/cocos/platform/empty/modules/SystemWindowManager.cpp b/cocos/platform/empty/modules/SystemWindowManager.cpp new file mode 100644 index 0000000..e2025b5 --- /dev/null +++ b/cocos/platform/empty/modules/SystemWindowManager.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "SystemWindowManager.h" +#include "platform/BasePlatform.h" +#include "platform/empty/modules/SystemWindow.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" + +namespace cc { + +int SystemWindowManager::init() { + return 0; +} + +void SystemWindowManager::processEvent() { +} + +ISystemWindow *SystemWindowManager::getWindow(uint32_t windowId) const { + if (windowId == 0) { + return nullptr; + } + + auto iter = _windows.find(windowId); + if (iter != _windows.end()) { + return iter->second.get(); + } + return nullptr; +} + +ISystemWindow *SystemWindowManager::createWindow(const cc::ISystemWindowInfo &info) { + ISystemWindow *window = BasePlatform::getPlatform()->createNativeWindow(_nextWindowId, info.externalHandle); + if (window) { + if (!info.externalHandle) { + window->createWindow(info.title.c_str(), info.x, info.y, info.width, info.height, info.flags); + } + _windows[_nextWindowId] = std::shared_ptr(window); + _nextWindowId++; + } + return window; +} + +} // namespace cc diff --git a/cocos/platform/empty/modules/SystemWindowManager.h b/cocos/platform/empty/modules/SystemWindowManager.h new file mode 100644 index 0000000..f7d9768 --- /dev/null +++ b/cocos/platform/empty/modules/SystemWindowManager.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/unordered_map.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" + +namespace cc { + +class ISystemWindow; + +class SystemWindowManager : public ISystemWindowManager { +public: + explicit SystemWindowManager() = default; + + int init() override; + void processEvent() override; + + ISystemWindow *createWindow(const ISystemWindowInfo &info) override; + ISystemWindow *getWindow(uint32_t windowId) const override; + const SystemWindowMap &getWindows() const override { return _windows; } + +private: + uint32_t _nextWindowId{1}; // start from 1, 0 means an invalid ID + SystemWindowMap _windows; +}; +} // namespace cc diff --git a/cocos/platform/empty/modules/Vibrator.cpp b/cocos/platform/empty/modules/Vibrator.cpp new file mode 100644 index 0000000..77c2807 --- /dev/null +++ b/cocos/platform/empty/modules/Vibrator.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/empty/modules/Vibrator.h" + +#include "base/Macros.h" + +namespace cc { + +void Vibrator::vibrate(float duration) { + CC_UNUSED_PARAM(duration); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/empty/modules/Vibrator.h b/cocos/platform/empty/modules/Vibrator.h new file mode 100644 index 0000000..08f1449 --- /dev/null +++ b/cocos/platform/empty/modules/Vibrator.h @@ -0,0 +1,43 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IVibrator.h" + +namespace cc { + +class CC_DLL Vibrator : public IVibrator { +public: + /** + * Vibrator for the specified amount of time. + * If Vibrator is not supported, then invoking this method has no effect. + * Some platforms limit to a maximum duration of 5 seconds. + * Duration is ignored on iOS due to API limitations. + * @param duration The duration in seconds. + */ + void vibrate(float duration) override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/interfaces/OSInterface.h b/cocos/platform/interfaces/OSInterface.h new file mode 100644 index 0000000..c62b596 --- /dev/null +++ b/cocos/platform/interfaces/OSInterface.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" +#include "engine/EngineEvents.h" + +namespace cc { + +class CC_DLL OSInterface { +public: + using Ptr = std::shared_ptr; + /** + @brief Constructor of OSAbstractInterface. + */ + OSInterface() = default; + + /** + @brief Destructor of OSAbstractInterface. + */ + virtual ~OSInterface() = default; + +private: + CC_DISALLOW_COPY_MOVE_ASSIGN(OSInterface); +}; + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/Device.cpp b/cocos/platform/interfaces/modules/Device.cpp new file mode 100644 index 0000000..d8bb672 --- /dev/null +++ b/cocos/platform/interfaces/modules/Device.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/interfaces/modules/Device.h" + +#include "application/ApplicationManager.h" +#include "platform/interfaces/modules/IAccelerometer.h" +#include "platform/interfaces/modules/IBattery.h" +#include "platform/interfaces/modules/INetwork.h" +#include "platform/interfaces/modules/IScreen.h" +#include "platform/interfaces/modules/IVibrator.h" +#include "platform/interfaces/modules/ISystemWindow.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" + +namespace cc { + +int Device::getDPI() { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(IScreen)); + return CC_GET_PLATFORM_INTERFACE(IScreen)->getDPI(); +} + +float Device::getDevicePixelRatio() { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(IScreen)); + return CC_GET_PLATFORM_INTERFACE(IScreen)->getDevicePixelRatio(); +} + +void Device::setKeepScreenOn(bool keepScreenOn) { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(IScreen)); + return CC_GET_PLATFORM_INTERFACE(IScreen)->setKeepScreenOn(keepScreenOn); +} + +void Device::setAccelerometerEnabled(bool isEnabled) { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(IAccelerometer)); + return CC_GET_PLATFORM_INTERFACE(IAccelerometer)->setAccelerometerEnabled(isEnabled); +} + +void Device::setAccelerometerInterval(float interval) { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(IAccelerometer)); + return CC_GET_PLATFORM_INTERFACE(IAccelerometer)->setAccelerometerInterval(interval); +} + +const IAccelerometer::MotionValue &Device::getDeviceMotionValue() { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(IAccelerometer)); + return CC_GET_PLATFORM_INTERFACE(IAccelerometer)->getDeviceMotionValue(); +} + +IScreen::Orientation Device::getDeviceOrientation() { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(IScreen)); + return CC_GET_PLATFORM_INTERFACE(IScreen)->getDeviceOrientation(); +} + +ccstd::string Device::getDeviceModel() { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(ISystem)); + return CC_GET_PLATFORM_INTERFACE(ISystem)->getDeviceModel(); +} + +void Device::vibrate(float duration) { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(IVibrator)); + return CC_GET_PLATFORM_INTERFACE(IVibrator)->vibrate(duration); +} + +float Device::getBatteryLevel() { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(IBattery)); + return CC_GET_PLATFORM_INTERFACE(IBattery)->getBatteryLevel(); +} + +INetwork::NetworkType Device::getNetworkType() { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(INetwork)); + return CC_GET_PLATFORM_INTERFACE(INetwork)->getNetworkType(); +} + +Vec4 Device::getSafeAreaEdge() { + CC_ASSERT_NOT_NULL(CC_GET_PLATFORM_INTERFACE(IScreen)); + return CC_GET_PLATFORM_INTERFACE(IScreen)->getSafeAreaEdge(); +} + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/Device.h b/cocos/platform/interfaces/modules/Device.h new file mode 100644 index 0000000..7fb9a6d --- /dev/null +++ b/cocos/platform/interfaces/modules/Device.h @@ -0,0 +1,129 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Data.h" +#include "base/Macros.h" +#include "math/Vec4.h" + +#include "platform/interfaces/modules/IAccelerometer.h" +#include "platform/interfaces/modules/INetwork.h" +#include "platform/interfaces/modules/IScreen.h" + +namespace cc { + +struct FontDefinition; + +/** + * @addtogroup platform + * @{ + */ + +/** + * @class Device + * @brief + */ +class CC_DLL Device { +public: + using Orientation = IScreen::Orientation; + using MotionValue = IAccelerometer::MotionValue; + + /** + * Gets the DPI of device + * @return The DPI of device. + */ + static int getDPI(); + + /** + * Get device pixel ratio. + */ + static float getDevicePixelRatio(); + + /** + * To enable or disable accelerometer. + */ + static void setAccelerometerEnabled(bool isEnabled); + + /** + * Sets the interval of accelerometer. + */ + static void setAccelerometerInterval(float interval); + + /** + * Gets the motion value of current device. + */ + static const IAccelerometer::MotionValue &getDeviceMotionValue(); + + /** + * Gets the orientation of device. + */ + static IScreen::Orientation getDeviceOrientation(); + + /** + * Gets device model information. + */ + static ccstd::string getDeviceModel(); + + /** + * Controls whether the screen should remain on. + * + * @param keepScreenOn One flag indicating that the screen should remain on. + */ + static void setKeepScreenOn(bool keepScreenOn); + + /** + * Vibrate for the specified amount of time. + * If vibrate is not supported, then invoking this method has no effect. + * Some platforms limit to a maximum duration of 5 seconds. + * Duration is ignored on iOS due to API limitations. + * @param duration The duration in seconds. + */ + static void vibrate(float duration); + + /** + * Gets battery level, only avaiable on iOS and Android. + * @return 0.0 ~ 1.0 + */ + static float getBatteryLevel(); + + static INetwork::NetworkType getNetworkType(); + + /* + * Gets the SafeArea edge. + * Vec4(x, y, z, w) means Edge(top, left, bottom, right) + */ + static Vec4 getSafeAreaEdge(); + +private: + Device(); + CC_DISALLOW_COPY_MOVE_ASSIGN(Device) +}; + +// end group +/// @} + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/IAccelerometer.h b/cocos/platform/interfaces/modules/IAccelerometer.h new file mode 100644 index 0000000..68cad46 --- /dev/null +++ b/cocos/platform/interfaces/modules/IAccelerometer.h @@ -0,0 +1,63 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/OSInterface.h" + +namespace cc { + +class CC_DLL IAccelerometer : public OSInterface { +public: + struct MotionValue { + float accelerationX = 0.0F; + float accelerationY = 0.0F; + float accelerationZ = 0.0F; + + float accelerationIncludingGravityX = 0.0F; + float accelerationIncludingGravityY = 0.0F; + float accelerationIncludingGravityZ = 0.0F; + + float rotationRateAlpha = 0.0F; + float rotationRateBeta = 0.0F; + float rotationRateGamma = 0.0F; + }; + + /** + * To enable or disable accelerometer. + */ + virtual void setAccelerometerEnabled(bool isEnabled) = 0; + + /** + * Sets the interval of accelerometer. + */ + virtual void setAccelerometerInterval(float interval) = 0; + + /** + * Gets the motion value of current device. + */ + virtual const MotionValue &getDeviceMotionValue() = 0; +}; + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/IBattery.h b/cocos/platform/interfaces/modules/IBattery.h new file mode 100644 index 0000000..621e471 --- /dev/null +++ b/cocos/platform/interfaces/modules/IBattery.h @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/OSInterface.h" + +namespace cc { + +class CC_DLL IBattery : public OSInterface { +public: + /** + * Gets battery level, only avaiable on iOS and Android. + * @return 0.0 ~ 1.0 + */ + virtual float getBatteryLevel() const = 0; +}; + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/INetwork.h b/cocos/platform/interfaces/modules/INetwork.h new file mode 100644 index 0000000..3458fe0 --- /dev/null +++ b/cocos/platform/interfaces/modules/INetwork.h @@ -0,0 +1,41 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/OSInterface.h" + +namespace cc { + +class CC_DLL INetwork : public OSInterface { +public: + enum class NetworkType { + NONE, + LAN, + WWAN + }; + virtual NetworkType getNetworkType() const = 0; +}; + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/IScreen.h b/cocos/platform/interfaces/modules/IScreen.h new file mode 100644 index 0000000..b3d8086 --- /dev/null +++ b/cocos/platform/interfaces/modules/IScreen.h @@ -0,0 +1,65 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "math/Vec4.h" +#include "platform/interfaces/OSInterface.h" + +namespace cc { +class CC_DLL IScreen : public OSInterface { +public: + virtual int getDPI() const = 0; + virtual float getDevicePixelRatio() const = 0; + + // https://developer.mozilla.org/en-US/docs/Web/API/Window/orientation + enum class Orientation { + PORTRAIT = 0, + LANDSCAPE_LEFT = -90, + PORTRAIT_UPSIDE_DOWN = 180, + LANDSCAPE_RIGHT = 90 + }; + virtual Orientation getDeviceOrientation() const = 0; + + /** + @brief Get current display stats. + @return bool, is displaying stats or not. + */ + virtual bool isDisplayStats() = 0; + + /** + @brief set display stats information. + */ + virtual void setDisplayStats(bool isShow) = 0; + + /** + * Controls whether the screen should remain on. + * + * @param keepScreenOn One flag indicating that the screen should remain on. + */ + virtual void setKeepScreenOn(bool keepScreenOn) = 0; + + virtual Vec4 getSafeAreaEdge() const = 0; +}; +} // namespace cc diff --git a/cocos/platform/interfaces/modules/ISystem.cpp b/cocos/platform/interfaces/modules/ISystem.cpp new file mode 100644 index 0000000..1efff07 --- /dev/null +++ b/cocos/platform/interfaces/modules/ISystem.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/interfaces/modules/ISystem.h" + +namespace cc { +ISystem::~ISystem() = default; + +ccstd::string ISystem::getCurrentLanguageToString() { + LanguageType language = getCurrentLanguage(); + ccstd::string languageStr = ""; // NOLINT + switch (language) { + case ISystem::LanguageType::ENGLISH: + languageStr = "en"; + break; + case ISystem::LanguageType::CHINESE: + languageStr = "zh"; + break; + case ISystem::LanguageType::FRENCH: + languageStr = "fr"; + break; + case ISystem::LanguageType::ITALIAN: + languageStr = "it"; + break; + case ISystem::LanguageType::GERMAN: + languageStr = "de"; + break; + case ISystem::LanguageType::SPANISH: + languageStr = "es"; + break; + case ISystem::LanguageType::DUTCH: + languageStr = "du"; + break; + case ISystem::LanguageType::RUSSIAN: + languageStr = "ru"; + break; + case ISystem::LanguageType::KOREAN: + languageStr = "ko"; + break; + case ISystem::LanguageType::JAPANESE: + languageStr = "ja"; + break; + case ISystem::LanguageType::HUNGARIAN: + languageStr = "hu"; + break; + case ISystem::LanguageType::PORTUGUESE: + languageStr = "pt"; + break; + case ISystem::LanguageType::ARABIC: + languageStr = "ar"; + break; + case ISystem::LanguageType::NORWEGIAN: + languageStr = "no"; + break; + case ISystem::LanguageType::POLISH: + languageStr = "pl"; + break; + case ISystem::LanguageType::TURKISH: + languageStr = "tr"; + break; + case ISystem::LanguageType::UKRAINIAN: + languageStr = "uk"; + break; + case ISystem::LanguageType::ROMANIAN: + languageStr = "ro"; + break; + case ISystem::LanguageType::BULGARIAN: + languageStr = "bg"; + break; + case ISystem::LanguageType::HINDI: + languageStr = "hi"; + break; + default: + languageStr = "unknown"; + break; + } + return languageStr; +} + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/ISystem.h b/cocos/platform/interfaces/modules/ISystem.h new file mode 100644 index 0000000..0f9d68d --- /dev/null +++ b/cocos/platform/interfaces/modules/ISystem.h @@ -0,0 +1,91 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/OSInterface.h" + +#include + +namespace cc { + +class CC_DLL ISystem : public OSInterface { +public: + ~ISystem() override; + enum class LanguageType { + ENGLISH = 0, + CHINESE, + FRENCH, + ITALIAN, + GERMAN, + SPANISH, + DUTCH, + RUSSIAN, + KOREAN, + JAPANESE, + HUNGARIAN, + PORTUGUESE, + ARABIC, + NORWEGIAN, + POLISH, + TURKISH, + UKRAINIAN, + ROMANIAN, + BULGARIAN, + HINDI + }; + enum class OSType { + WINDOWS, /**< Windows */ + LINUX, /**< Linux */ + MAC, /**< Mac OS X*/ + ANDROIDOS, /**< Android, because ANDROID is a macro, so use ANDROIDOS instead */ + IPHONE, /**< iPhone */ + IPAD, /**< iPad */ + OHOS, /**< HarmonyOS> */ + OPENHARMONY, /**< OpenHarmony> */ + QNX, /**< QNX */ + }; + /** + @brief Get target system type. + */ + virtual OSType getOSType() const = 0; + + // + virtual ccstd::string getDeviceModel() const = 0; + virtual LanguageType getCurrentLanguage() const = 0; + virtual ccstd::string getCurrentLanguageCode() const = 0; + virtual ccstd::string getSystemVersion() const = 0; + + virtual ccstd::string getCurrentLanguageToString(); + + virtual void copyTextToClipboard(const ccstd::string& text) = 0; + /** + @brief Open url in default browser. + @param String with url to open. + @return True if the resource located by the URL was successfully opened; otherwise false. + */ + virtual bool openURL(const ccstd::string& url) = 0; +}; + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/ISystemWindow.h b/cocos/platform/interfaces/modules/ISystemWindow.h new file mode 100644 index 0000000..021baa7 --- /dev/null +++ b/cocos/platform/interfaces/modules/ISystemWindow.h @@ -0,0 +1,107 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/std/container/array.h" +#include "math/Geometry.h" +#include "platform/interfaces/OSInterface.h" + +namespace cc { + +class CC_DLL ISystemWindow : public OSInterface { +public: + static constexpr uint32_t mainWindowId = 1; + + using Size = cc::Size; + enum WindowFlags { + /* !!! FIXME: change this to name = (1<>; + +/** + * Responsible for creating, finding ISystemWindow object and message handling + */ +class ISystemWindowManager : public OSInterface { +public: + /** + * Initialize the NativeWindow environment + * @return 0 Succeed -1 Failed + */ + virtual int init() = 0; + + /** + * Process messages at the PAL layer + */ + virtual void processEvent() = 0; + + /** + * Create an ISystemWindow object + * @param info window description + * @return The created ISystemWindow object,if failed then return nullptr + */ + virtual ISystemWindow *createWindow(const ISystemWindowInfo &info) = 0; + + /** + * Find an ISystemWindow object + * @param windowId unique ID of window + */ + virtual ISystemWindow *getWindow(uint32_t windowId) const = 0; + + /** + * Retrive all windows + */ + virtual const SystemWindowMap &getWindows() const = 0; +}; +} // namespace cc diff --git a/cocos/platform/interfaces/modules/IVibrator.h b/cocos/platform/interfaces/modules/IVibrator.h new file mode 100644 index 0000000..0cf966d --- /dev/null +++ b/cocos/platform/interfaces/modules/IVibrator.h @@ -0,0 +1,44 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/OSInterface.h" + +namespace cc { + +class CC_DLL IVibrator : public OSInterface { +public: + IVibrator() = default; + /** + * Vibrate for the specified amount of time. + * If vibrate is not supported, then invoking this method has no effect. + * Some platforms limit to a maximum duration of 5 seconds. + * Duration is ignored on iOS due to API limitations. + * @param duration The duration in seconds. + */ + virtual void vibrate(float duration) = 0; +}; + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/IXRInterface.h b/cocos/platform/interfaces/modules/IXRInterface.h new file mode 100644 index 0000000..42933ca --- /dev/null +++ b/cocos/platform/interfaces/modules/IXRInterface.h @@ -0,0 +1,340 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/std/container/array.h" +#include "gfx-base/GFXDef-common.h" +#include "math/Vec2.h" +#include "platform/interfaces/OSInterface.h" +#if CC_USE_VULKAN + #include "vulkan/vulkan_core.h" +#endif +#include "XRCommon.h" +namespace cc { +// forward declare +namespace gfx { +class GLES3GPUContext; +} +namespace scene { +class Camera; +} + +enum class EGLSurfaceType { + NONE, + WINDOW, + PBUFFER +}; + +/** + * @en Mainly responsible for the docking work of the engine and the xr library + * @zh 主要负责引擎与xr库的对接工作 + */ +class CC_DLL IXRInterface : public OSInterface { +public: + /** + * @en get xr device vendor + * @zh 获取XR设备品牌 + * @return xr::XRVendor + */ + virtual xr::XRVendor getVendor() = 0; + + /** + * @en get xr config parameter + * @zh 获取XR配置参数 + */ + virtual xr::XRConfigValue getXRConfig(xr::XRConfigKey key) = 0; + + /** + * @en set xr config parameter + * @zh 设置XR配置参数 + * @param key + * @param value + */ + virtual void setXRConfig(xr::XRConfigKey key, xr::XRConfigValue value) = 0; + + /** + * @en get XR runtime version + * @zh 获取XR运行时版本号 + * @return + */ + virtual uint32_t getRuntimeVersion() = 0; + /** + * @en initialize xr runtime envirment + * @zh 初始化XR运行环境 + * @param javaVM android JVM + * @param activity android activity + */ + virtual void initialize(void *javaVM, void *activity) = 0; + + // render thread lifecycle + /** + * @en call when render pause + * @zh 渲染暂停时调用 + */ + virtual void onRenderPause() = 0; + /** + * @en call when render resume + * @zh 渲染恢复时调用 + */ + virtual void onRenderResume() = 0; + /** + * @en call when render destroy + * @zh 渲染结束退出时调用 + */ + virtual void onRenderDestroy() = 0; + // render thread lifecycle + + // gfx + /** + * @en call before gfx device initialize + * @zh GFX设备初始化前调用 + * @param gfxApi + */ + virtual void preGFXDeviceInitialize(gfx::API gfxApi) = 0; + /** + * @en call after gfx device initialize + * @zh GFX设备初始化后调用 + * @param gfxApi + */ + virtual void postGFXDeviceInitialize(gfx::API gfxApi) = 0; + /** + * @en call when gfx device acquire + * @zh GFX设备请求渲染 + * @param gfxApi + * @return + */ + virtual const xr::XRSwapchain &doGFXDeviceAcquire(gfx::API gfxApi) = 0; + /** + * @en call when gfx device present + * @zh GFX设备是否需要展示操作 + */ + virtual bool isGFXDeviceNeedsPresent(gfx::API gfxApi) = 0; + /** + * @en call after gfx device present + * @zh GFX设备展示操作执行之后调用 + * @param gfxApi + */ + virtual void postGFXDevicePresent(gfx::API gfxApi) = 0; + /** + * @en call when create gfx device's swapchain + * @zh 创建GFX交换链时调用 + */ + virtual void createXRSwapchains() = 0; + /** + * @en get xr swapchain list + * @zh 获取XR交换链列表 + * @return + */ + virtual const std::vector &getXRSwapchains() = 0; + /** + * @en get xr swapchain's format + * @zh 获取XR交换链格式 + * @return + */ + virtual gfx::Format getXRSwapchainFormat() = 0; + /** + * @en bind engine's swapchain with xr swapchain + * @zh 绑定引擎交换链与XR交换链对应关系 + * @param typedID engine swapchain's type id + */ + virtual void updateXRSwapchainTypedID(uint32_t typedID) = 0; + // gfx + + // vulkan +#ifdef CC_USE_VULKAN + /** + * @en get the vk version required by XR + * @zh 获取XR要求的VK版本信息 + * @param engineVkApiVersion engine's vk version + */ + virtual uint32_t getXRVkApiVersion(uint32_t engineVkApiVersion) = 0; + /** + * @en initialize vulkan envirment + * @zh 初始化vk运行环境 + * @param addr + */ + virtual void initializeVulkanData(const PFN_vkGetInstanceProcAddr &addr) = 0; + /** + * @en create vulkan instance + * @zh 创建vk实例 + * @param instInfo + * @return + */ + virtual VkInstance createXRVulkanInstance(const VkInstanceCreateInfo &instInfo) = 0; + /** + * @en create vulkan device + * @zh 创建vk逻辑设备 + * @param deviceInfo + * @return + */ + virtual VkDevice createXRVulkanDevice(const VkDeviceCreateInfo *deviceInfo) = 0; + /** + * @en get vulkan physical device + * @zh 获取vk物理设备 + * @return + */ + virtual VkPhysicalDevice getXRVulkanGraphicsDevice() = 0; + /** + * @en get vkImage list from xrswapchain + * @zh 获取xr交换链中vkimage列表 + * @param vkImages + * @param eye + */ + virtual void getXRSwapchainVkImages(std::vector &vkImages, uint32_t eye) = 0; +#endif + // vulkan + + // gles3 +#ifdef CC_USE_GLES3 + /** + * @en initialize gles envirment + * @zh 初始化gles运行环境 + * @param gles3wLoadFuncProc + * @param gpuContext + */ + virtual void initializeGLESData(xr::PFNGLES3WLOADPROC gles3wLoadFuncProc, gfx::GLES3GPUContext *gpuContext) = 0; + /** + * @en attach current texture id to fbo + * @zh 绑定当前纹理到帧缓冲区 + */ + virtual void attachGLESFramebufferTexture2D() = 0; + /** + * @en acquire EGLSurfaceType by engine swapchain's type id + * @zh 根据引擎交换链获取对应需要创建的EGLSurface类型 + * @param typedID + * @return + */ + virtual EGLSurfaceType acquireEGLSurfaceType(uint32_t typedID) = 0; +#endif + // gles3 + + // stereo render loop + /** + * @en call when platform loop start + * @zh 平台循环开始时调用 + * @return + */ + virtual bool platformLoopStart() = 0; + /** + * @en call when frame render begin + * @zh 一帧开始时调用 + * @return + */ + virtual bool beginRenderFrame() = 0; + /** + * @en whether the current frame allows rendering + * @zh 当前帧是否允许渲染 + * @return + */ + virtual bool isRenderAllowable() = 0; + /** + * @en call when single eye render begin + * @zh 单眼渲染开始时调用 + * @param eye + * @return + */ + virtual bool beginRenderEyeFrame(uint32_t eye) = 0; + /** + * @en call when single eye render end + * @zh 单眼渲染结束时调用 + * @param eye + * @return + */ + virtual bool endRenderEyeFrame(uint32_t eye) = 0; + /** + * @en call when frame render end + * @zh 一帧结束时调用 + * @return + */ + virtual bool endRenderFrame() = 0; + /** + * @en call when platform loop end + * @zh 平台循环结束时调用 + * @return + */ + virtual bool platformLoopEnd() = 0; + // stereo render loop + + /** + * @en get hmd view position data + * @zh 获取hmd双眼位置坐标 + * @param eye + * @return + */ + virtual ccstd::vector getHMDViewPosition(uint32_t eye, int trackingType) = 0; + + /** + * @en get xr view projection data + * @zh 获取xr双眼投影矩阵数据 + * @param eye + * @param near + * @param far + * @return + */ + virtual ccstd::vector getXRViewProjectionData(uint32_t eye, float near, float far) = 0; + + /** + * @en get xr eye's fov + * @zh 获取xr双眼视场角 + * @param eye + * @return + */ + virtual ccstd::vector getXREyeFov(uint32_t eye) = 0; + + // renderwindow + /** + * @en get renderwindow's xreye type + * @zh 获取当前RenderWindow的XREye类型 + * @param window + * @return + */ + virtual xr::XREye getXREyeByRenderWindow(void *window) = 0; + /** + * @en bind renderwindow with xr eye + * @zh 建立RenderWindow与XREye的对应关系 + * @param window + * @param eye + */ + virtual void bindXREyeWithRenderWindow(void *window, xr::XREye eye) = 0; + + /** + * @en app's lifecycle callback + * @zh 应用程序生命周期回调 + * @param appCmd + */ + virtual void handleAppCommand(int appCmd) = 0; + + /** + * @en adapt orthographic matrix(projection and view) + * @zh 适配正交相机 + * @param camera + * @param preTransform + * @param proj + * @param view + */ + virtual void adaptOrthographicMatrix(cc::scene::Camera *camera, const ccstd::array &preTransform, Mat4 &proj, Mat4 &view) = 0; +}; +} // namespace cc diff --git a/cocos/platform/interfaces/modules/XRCommon.h b/cocos/platform/interfaces/modules/XRCommon.h new file mode 100644 index 0000000..d33f211 --- /dev/null +++ b/cocos/platform/interfaces/modules/XRCommon.h @@ -0,0 +1,428 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#ifndef XR_COMMON_H_ +#define XR_COMMON_H_ +#include +#include +#include +#include +#include + +namespace cc { +namespace xr { +#define XR_INTERFACE_RUNTIME_VERSION_1_0 1 + +enum class XREye { + NONE = -1, + LEFT = 0, + RIGHT = 1, + MONO = 2 +}; + +enum class XRVendor { + MONADO, + META, + HUAWEIVR, + PICO, + ROKID, + SEED, + SPACESXR, + GSXR, + YVR, + HTC, + IQIYI, + SKYWORTH, + FFALCON, + NREAL, + INMO, + LENOVO +}; + +enum class XRConfigKey { + MULTI_SAMPLES = 0, + RENDER_SCALE = 1, + SESSION_RUNNING = 2, + INSTANCE_CREATED = 3, + VK_QUEUE_FAMILY_INDEX = 4, + METRICS_STATE = 5, + VIEW_COUNT = 6, + SWAPCHAIN_WIDTH = 7, + SWAPCHAIN_HEIGHT = 8, + SWAPCHAIN_FORMAT = 9, + MULTITHREAD_MODE = 10, + LOGIC_THREAD_ID = 11, + RENDER_THREAD_ID = 12, + DEVICE_VENDOR = 13, + RUNTIME_VERSION = 14, + PRESENT_ENABLE = 15, + RENDER_EYE_FRAME_LEFT = 16, + RENDER_EYE_FRAME_RIGHT = 17, + FEATURE_PASSTHROUGH = 18, + IMAGE_TRACKING = 19, + IMAGE_TRACKING_CANDIDATEIMAGE = 20, + IMAGE_TRACKING_DATA = 21, + IMAGE_TRACKING_SUPPORT_STATUS = 22, + HIT_TESTING = 23, + HIT_TESTING_DATA = 24, + HIT_TESTING_SUPPORT_STATUS = 25, + PLANE_DETECTION = 26, + PLANE_DETECTION_DATA = 27, + PLANE_DETECTION_SUPPORT_STATUS = 28, + SPATIAL_ANCHOR = 29, + SPATIAL_ANCHOR_DATA = 30, + SPATIAL_ANCHOR_SUPPORT_STATUS = 31, + HAND_TRACKING = 32, + HAND_TRACKING_DATA = 33, + HAND_TRACKING_SUPPORT_STATUS = 34, + APPLY_HAPTIC_CONTROLLER = 35, + STOP_HAPTIC_CONTROLLER = 36, + DEVICE_IPD = 37, + APP_COMMAND = 38, + ADB_COMMAND = 39, + ACTIVITY_LIFECYCLE = 40, + NATIVE_WINDOW = 41, + SPLIT_AR_GLASSES = 42, + ENGINE_VERSION = 43, + RECENTER_HMD = 44, + RECENTER_CONTROLLER = 45, + FOVEATION_LEVEL = 46, + DISPLAY_REFRESH_RATE = 47, + CAMERA_ACCESS = 48, + CAMERA_ACCESS_DATA = 49, + CAMERA_ACCESS_SUPPORT_STATUS = 50, + SPATIAL_MESHING = 51, + SPATIAL_MESHING_DATA = 52, + SPATIAL_MESHING_SUPPORT_STATUS = 53, + EYE_RENDER_JS_CALLBACK = 54, + ASYNC_LOAD_ASSETS_IMAGE = 55, + ASYNC_LOAD_ASSETS_IMAGE_RESULTS = 56, + LEFT_CONTROLLER_ACTIVE = 57, + RIGHT_CONTROLLER_ACTIVE= 58, + TS_EVENT_CALLBACK = 59, + MAX_COUNT +}; + +enum class XRConfigValueType { + UNKNOWN, + INT, + FLOAT, + BOOL, + STRING, + VOID_POINTER +}; + +struct XRConfigValue { + int vInt = 0; + float vFloat = 0; + bool vBool = false; + void *vPtr = nullptr; + std::string vString; + XRConfigValueType valueType = XRConfigValueType::UNKNOWN; + bool isInt() { + return valueType == XRConfigValueType::INT; + } + + bool isFloat() { + return valueType == XRConfigValueType::FLOAT; + } + + bool isBool() { + return valueType == XRConfigValueType::BOOL; + } + + bool isPointer() { + return valueType == XRConfigValueType::VOID_POINTER; + } + + bool isString() { + return valueType == XRConfigValueType::STRING; + } + + bool getBool() { + return vBool; + } + + int getInt() { + return vInt; + } + + float getFloat() { + return vFloat; + } + + std::string getString() { + return vString; + } + + void *getPointer() { + return vPtr; + } + + XRConfigValue() { + } + + XRConfigValue(int value) { + valueType = XRConfigValueType::INT; + vInt = value; + } + + XRConfigValue(float value) { + valueType = XRConfigValueType::FLOAT; + vFloat = value; + } + + XRConfigValue(std::string value) { + valueType = XRConfigValueType::STRING; + vString = value; + } + + XRConfigValue(bool value) { + valueType = XRConfigValueType::BOOL; + vBool = value; + } + + XRConfigValue(void *value) { + valueType = XRConfigValueType::VOID_POINTER; + vPtr = value; + } +}; + +enum class XREventType { + CLICK, + STICK, + GRAB, + POSE, + TOUCH, + UNKNOWN +}; + +struct XRControllerInfo { + virtual ~XRControllerInfo() = default; + virtual XREventType getXREventType() const = 0; +}; + +struct XRClick : public XRControllerInfo { + enum class Type { + TRIGGER_LEFT, + SHOULDER_LEFT, + THUMBSTICK_LEFT, + X, + Y, + MENU, + TRIGGER_RIGHT, + SHOULDER_RIGHT, + THUMBSTICK_RIGHT, + A, + B, + HOME, + BACK, + START, + DPAD_UP, + DPAD_DOWN, + DPAD_LEFT, + DPAD_RIGHT, + UNKNOWN + }; + + bool isPress = false; + Type type = Type::UNKNOWN; + + XRClick(Type type, bool isPress) + : type(type), + isPress(isPress) {} + + XREventType getXREventType() const override { + return XREventType::CLICK; + } +}; + +struct XRStick : public XRControllerInfo { + enum class Type { + STICK_LEFT, + STICK_RIGHT, + UNKNOWN + }; + + bool isActive = false; + float x = 0.F; + float y = 0.F; + Type type = Type::UNKNOWN; + + XRStick(Type type, bool isActive) + : type(type), + isActive(isActive) {} + + XRStick(Type type, float x, float y) + : type(type), + isActive(true), + x(x), + y(y) {} + + XREventType getXREventType() const override { + return XREventType::STICK; + } +}; + +struct XRGrab : public XRControllerInfo { + enum class Type { + TRIGGER_LEFT, + GRIP_LEFT, + TRIGGER_RIGHT, + GRIP_RIGHT, + UNKNOWN + }; + + bool isActive = false; + float value = 0.F; + Type type = Type::UNKNOWN; + + XRGrab(Type type, bool isActive) + : type(type), + isActive(isActive) {} + + XRGrab(Type type, float value) + : type(type), + isActive(true), + value(value) {} + + XREventType getXREventType() const override { + return XREventType::GRAB; + } +}; + +struct XRTouch : public XRControllerInfo { + enum class Type { + TOUCH_A, + TOUCH_B, + TOUCH_X, + TOUCH_Y, + TOUCH_TRIGGER_LEFT, + TOUCH_TRIGGER_RIGHT, + TOUCH_THUMBSTICK_LEFT, + TOUCH_THUMBSTICK_RIGHT, + UNKNOWN + }; + + bool isActive = false; + float value = 0.F; + Type type = Type::UNKNOWN; + + XRTouch(Type type, bool isActive) + : type(type), + isActive(isActive) {} + + XRTouch(Type type, float value) + : type(type), + isActive(true), + value(value) {} + + XREventType getXREventType() const override { + return XREventType::TOUCH; + } +}; + +struct XRPose : public XRControllerInfo { + enum class Type { + VIEW_LEFT, + HAND_LEFT, + AIM_LEFT, + VIEW_RIGHT, + HAND_RIGHT, + AIM_RIGHT, + HEAD_MIDDLE, + AR_MOBILE, + UNKNOWN + }; + + bool isActive = false; + float px = 0.F; + float py = 0.F; + float pz = 0.F; + float qx = 0.F; + float qy = 0.F; + float qz = 0.F; + float qw = 1.F; + Type type = Type::UNKNOWN; + + XRPose(Type type, bool isActive) + : type(type), + isActive(isActive) {} + + XRPose(Type type, float position[3], float quaternion[4]) + : type(type), isActive(true), px(position[0]), py(position[1]), pz(position[2]), qx(quaternion[0]), qy(quaternion[1]), qz(quaternion[2]), qw(quaternion[3]) {} + + XREventType getXREventType() const override { + return XREventType::POSE; + } +}; + +struct XRControllerEvent { + std::vector> xrControllerInfos; +}; +typedef std::function XRControllerCallback; +using PFNGLES3WLOADPROC = void *(*)(const char *); + +typedef std::function XRConfigChangeCallback; + +struct XRSwapchain { + void *xrSwapchainHandle = nullptr; + uint32_t width = 0; + uint32_t height = 0; + uint32_t glDrawFramebuffer = 0; + uint32_t swapchainImageIndex = 0; + uint32_t eye = 0; +}; + +struct XRTrackingImageData { + std::string friendlyName; + uint32_t id; + uint8_t *buffer; + uint32_t bufferSize; + float physicalWidth; + float physicalHeight; + uint32_t pixelSizeWidth; + uint32_t pixelSizeHeight; + float posePosition[3]; + float poseQuaternion[4]; +}; + +#define GraphicsApiOpenglES "OpenGLES" +#define GraphicsApiVulkan_1_0 "Vulkan1" +#define GraphicsApiVulkan_1_1 "Vulkan2" + +enum class XRActivityLifecycleType { + UnKnown, + Created, + Started, + Resumed, + Paused, + Stopped, + SaveInstanceState, + Destroyed +}; + +} // namespace xr +} // namespace cc +#endif // XR_COMMON_H_ diff --git a/cocos/platform/interfaces/modules/canvas/CanvasRenderingContext2D.cpp b/cocos/platform/interfaces/modules/canvas/CanvasRenderingContext2D.cpp new file mode 100644 index 0000000..7d68e27 --- /dev/null +++ b/cocos/platform/interfaces/modules/canvas/CanvasRenderingContext2D.cpp @@ -0,0 +1,452 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/interfaces/modules/canvas/CanvasRenderingContext2D.h" + +#include +#include +#include "base/csscolorparser.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/bindings/manual/jsb_platform.h" +#include "math/Math.h" +#include "platform/FileUtils.h" + +#if defined(CC_SERVER_MODE) + #include "platform/empty/modules/CanvasRenderingContext2DDelegate.h" +#elif (CC_PLATFORM == CC_PLATFORM_WINDOWS) + #include "platform/win32/modules/CanvasRenderingContext2DDelegate.h" +#elif (CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS) + #include "platform/java/modules/CanvasRenderingContext2DDelegate.h" +#elif (CC_PLATFORM == CC_PLATFORM_MACOS || CC_PLATFORM == CC_PLATFORM_IOS) + #include "platform/apple/modules/CanvasRenderingContext2DDelegate.h" +#elif (CC_PLATFORM == CC_PLATFORM_LINUX) + #include "platform/linux/modules/CanvasRenderingContext2DDelegate.h" +#elif (CC_PLATFORM == CC_PLATFORM_QNX) + #include "platform/qnx/modules/CanvasRenderingContext2DDelegate.h" +#elif (CC_PLATFORM == CC_PLATFORM_OPENHARMONY) + #include "platform/openharmony/modules/CanvasRenderingContext2DDelegate.h" +#endif + +using Vec2 = ccstd::array; +using Color4F = ccstd::array; + +namespace cc { +//using Size = ccstd::array; +CanvasGradient::CanvasGradient() = default; + +CanvasGradient::~CanvasGradient() = default; + +void CanvasGradient::addColorStop(float offset, const ccstd::string &color) { + //SE_LOGD("CanvasGradient::addColorStop: %p\n", this); +} + +// CanvasRenderingContext2D + +CanvasRenderingContext2D::CanvasRenderingContext2D(float width, float height) +: _width(width), + _height(height) { + _delegate = ccnew CanvasRenderingContext2DDelegate(); + //SE_LOGD("CanvasRenderingContext2D constructor: %p, width: %f, height: %f\n", this, width, height); +} + +CanvasRenderingContext2D::~CanvasRenderingContext2D() { + delete _delegate; +} + +void CanvasRenderingContext2D::clearRect(float x, float y, float width, float height) { + //SE_LOGD("CanvasRenderingContext2D::clearRect: %p, %f, %f, %f, %f\n", this, x, y, width, height); + recreateBufferIfNeeded(); + _delegate->clearRect(x, y, width, height); +} + +void CanvasRenderingContext2D::fillRect(float x, float y, float width, float height) { + recreateBufferIfNeeded(); + _delegate->fillRect(x, y, width, height); +} + +void CanvasRenderingContext2D::fillText(const ccstd::string &text, float x, float y, float maxWidth) { + //SE_LOGD("CanvasRenderingContext2D::fillText: %s, offset: (%f, %f), %f\n", text.c_str(), x, y, maxWidth); + if (text.empty()) { + return; + } + recreateBufferIfNeeded(); + _delegate->fillText(text, x, y, maxWidth); +} + +void CanvasRenderingContext2D::strokeText(const ccstd::string &text, float x, float y, float maxWidth) { + //SE_LOGD("CanvasRenderingContext2D::strokeText: %s, %f, %f, %f\n", text.c_str(), x, y, maxWidth); + if (text.empty()) { + return; + } + recreateBufferIfNeeded(); + _delegate->strokeText(text, x, y, maxWidth); +} + +cc::Size CanvasRenderingContext2D::measureText(const ccstd::string &text) { + //SE_LOGD("CanvasRenderingContext2D::measureText: %s\n", text.c_str()); + auto s = _delegate->measureText(text); + return cc::Size(s[0], s[1]); +} + +ICanvasGradient *CanvasRenderingContext2D::createLinearGradient(float /*x0*/, float /*y0*/, float /*x1*/, float /*y1*/) { + return nullptr; +} + +void CanvasRenderingContext2D::save() { + //SE_LOGD("CanvasRenderingContext2D::save\n"); + _delegate->saveContext(); +} + +void CanvasRenderingContext2D::beginPath() { + //SE_LOGD("\n-----------begin------------------\nCanvasRenderingContext2D::beginPath\n"); + recreateBufferIfNeeded(); + _delegate->beginPath(); +} + +void CanvasRenderingContext2D::closePath() { + //SE_LOGD("CanvasRenderingContext2D::closePath\n"); + _delegate->closePath(); +} + +void CanvasRenderingContext2D::moveTo(float x, float y) { + //SE_LOGD("CanvasRenderingContext2D::moveTo\n"); + _delegate->moveTo(x, y); +} + +void CanvasRenderingContext2D::lineTo(float x, float y) { + //SE_LOGD("CanvasRenderingContext2D::lineTo\n"); + _delegate->lineTo(x, y); +} + +void CanvasRenderingContext2D::stroke() { + //SE_LOGD("CanvasRenderingContext2D::stroke\n"); + recreateBufferIfNeeded(); + _delegate->stroke(); +} + +void CanvasRenderingContext2D::restore() { + //SE_LOGD("CanvasRenderingContext2D::restore\n"); + _delegate->restoreContext(); +} + +void CanvasRenderingContext2D::setCanvasBufferUpdatedCallback(const CanvasBufferUpdatedCallback &cb) { + _canvasBufferUpdatedCB = cb; +} + +void CanvasRenderingContext2D::fetchData() { + recreateBufferIfNeeded(); + _delegate->updateData(); + if (_canvasBufferUpdatedCB != nullptr) { + _canvasBufferUpdatedCB(_delegate->getDataRef()); + } +} + +void CanvasRenderingContext2D::setWidth(float width) { + //SE_LOGD("CanvasRenderingContext2D::set__width: %f\n", width); + if (math::isEqualF(width, _width)) return; + _width = width; + _isBufferSizeDirty = true; +} + +void CanvasRenderingContext2D::setHeight(float height) { + //SE_LOGD("CanvasRenderingContext2D::set__height: %f\n", height); + if (math::isEqualF(height, _height)) return; + _height = height; + _isBufferSizeDirty = true; +} + +void CanvasRenderingContext2D::setLineWidth(float lineWidth) { + //SE_LOGD("CanvasRenderingContext2D::set_lineWidth %d\n", lineWidth); + _lineWidth = lineWidth; + _delegate->setLineWidth(lineWidth); +} + +void CanvasRenderingContext2D::setLineCap(const ccstd::string &lineCap) { + //SE_LOGE("%s isn't implemented!\n", __FUNCTION__); +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + +#elif CC_PLATFORM == CC_PLATFORM_ANDROID + if (lineCap.empty()) return; + _delegate->setLineCap(lineCap); +#endif +} + +void CanvasRenderingContext2D::setLineJoin(const ccstd::string &lineJoin) { + //SE_LOGE("%s isn't implemented!\n", __FUNCTION__); + _delegate->setLineJoin(lineJoin); +} + +void CanvasRenderingContext2D::fill() { + // SE_LOGE("%s isn't implemented!\n", __FUNCTION__); +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + +#elif CC_PLATFORM == CC_PLATFORM_ANDROID + _delegate->fill(); +#endif +} + +void CanvasRenderingContext2D::rect(float x, float y, float w, float h) { + recreateBufferIfNeeded(); +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + // SE_LOGE("%s isn't implemented!\n", __FUNCTION__); +#elif CC_PLATFORM == CC_PLATFORM_ANDROID + // SE_LOGD("CanvasRenderingContext2D::rect: %p, %f, %f, %f, %f\n", this, x, y, width, height); + _delegate->rect(x, y, w, h); +#endif +} + +void CanvasRenderingContext2D::setFont(const ccstd::string &font) { + recreateBufferIfNeeded(); +#if CC_PLATFORM == CC_PLATFORM_WINDOWS + if (_font != font) { + _font = font; + + ccstd::string boldStr; + ccstd::string fontName = "Arial"; + ccstd::string fontSizeStr = "30"; + + std::regex re(R"(\s*((\d+)([\.]\d+)?)px\s+([^\r\n]*))"); + std::match_results results; + if (std::regex_search(_font.cbegin(), _font.cend(), results, re)) { + fontSizeStr = results[2].str(); + // support get font name from `60px American` or `60px "American abc-abc_abc"` + // support get font name contain space,example `times new roman` + // if regex rule that does not conform to the rules,such as Chinese,it defaults to Arial + std::match_results fontResults; + std::regex fontRe(R"(([\w\s-]+|"[\w\s-]+"$))"); + ccstd::string tmp(results[4].str()); + if (std::regex_match(tmp, fontResults, fontRe)) { + fontName = results[4].str(); + } + } + + auto fontSize = static_cast(atof(fontSizeStr.c_str())); + bool isBold = !boldStr.empty() || font.find("bold", 0) != ccstd::string::npos || font.find("Bold", 0) != ccstd::string::npos; + bool isItalic = font.find("italic", 0) != ccstd::string::npos || font.find("Italic", 0) != ccstd::string::npos; + _delegate->updateFont(fontName, static_cast(fontSize), isBold, isItalic, false, false); + } +#elif CC_PLATFORM == CC_PLATFORM_QNX + if (_font != font) { + _font = font; + + ccstd::string boldStr; + ccstd::string fontName = "Arial"; + ccstd::string fontSizeStr = "30"; + + // support get font name from `60px American` or `60px "American abc-abc_abc"` + std::regex re("(bold)?\\s*((\\d+)([\\.]\\d+)?)px\\s+([\\w-]+|\"[\\w -]+\"$)"); + std::match_results results; + if (std::regex_search(_font.cbegin(), _font.cend(), results, re)) { + boldStr = results[1].str(); + fontSizeStr = results[2].str(); + fontName = results[5].str(); + } + bool isItalic = font.find("italic", 0) != ccstd::string::npos || font.find("Italic", 0) != ccstd::string::npos; + auto fontSize = static_cast(atof(fontSizeStr.c_str())); + bool isBold = !boldStr.empty() || font.find("bold", 0) != ccstd::string::npos || font.find("Bold", 0) != ccstd::string::npos; + //SE_LOGD("CanvasRenderingContext2D::set_font: %s, Size: %f, isBold: %b\n", fontName.c_str(), fontSize, isBold); + _delegate->updateFont(fontName, fontSize, isBold, isItalic, false, false); + } +#elif CC_PLATFORM == CC_PLATFORM_LINUX + if (_font != font) { + _font = font; + + ccstd::string fontName = "sans-serif"; + ccstd::string fontSizeStr = "30"; + std::regex re(R"(\s*((\d+)([\.]\d+)?)px\s+([^\r\n]*))"); + std::match_results results; + if (std::regex_search(_font.cbegin(), _font.cend(), results, re)) { + fontSizeStr = results[2].str(); + // support get font name from `60px American` or `60px "American abc-abc_abc"` + // support get font name contain space,example `times new roman` + // if regex rule that does not conform to the rules,such as Chinese,it defaults to sans-serif + std::match_results fontResults; + std::regex fontRe(R"(([\w\s-]+|"[\w\s-]+"$))"); + ccstd::string tmp(results[4].str()); + if (std::regex_match(tmp, fontResults, fontRe)) { + //fontName = results[4].str(); + } + } + + bool isBold = font.find("bold", 0) != ccstd::string::npos || font.find("Bold", 0) != ccstd::string::npos; + bool isItalic = font.find("italic", 0) != ccstd::string::npos || font.find("Italic", 0) != ccstd::string::npos; + float fontSize = static_cast(atof(fontSizeStr.c_str())); + //SE_LOGD("CanvasRenderingContext2D::set_font: %s, Size: %f, isBold: %b\n", fontName.c_str(), fontSize, isBold); + _delegate->updateFont(fontName, fontSize, isBold, isItalic, false, false); + } +#elif CC_PLATFORM == CC_PLATFORM_ANDROID || CC_PLATFORM == CC_PLATFORM_OHOS || CC_PLATFORM == CC_PLATFORM_OPENHARMONY + if (_font != font) { + _font = font; + ccstd::string fontName = "sans-serif"; + ccstd::string fontSizeStr = "30"; + std::regex re(R"(\s*((\d+)([\.]\d+)?)px\s+([^\r\n]*))"); + std::match_results results; + if (std::regex_search(_font.cbegin(), _font.cend(), results, re)) { + fontSizeStr = results[2].str(); + // support get font name from `60px American` or `60px "American abc-abc_abc"` + // support get font name contain space,example `times new roman` + // if regex rule that does not conform to the rules,such as Chinese,it defaults to sans-serif + std::match_results fontResults; + std::regex fontRe(R"(([\w\s-]+|"[\w\s-]+"$))"); + ccstd::string tmp(results[4].str()); + if (std::regex_match(tmp, fontResults, fontRe)) { + fontName = results[4].str(); + } + } + + double fontSize = atof(fontSizeStr.c_str()); + bool isBold = font.find("bold", 0) != ccstd::string::npos || font.find("Bold", 0) != ccstd::string::npos; + bool isItalic = font.find("italic", 0) != ccstd::string::npos || font.find("Italic", 0) != ccstd::string::npos; + bool isSmallCaps = font.find("small-caps", 0) != ccstd::string::npos || font.find("Small-Caps") != ccstd::string::npos; + bool isOblique = font.find("oblique", 0) != ccstd::string::npos || font.find("Oblique", 0) != ccstd::string::npos; + //font-style: italic, oblique, normal + //font-weight: normal, bold + //font-variant: normal, small-caps + _delegate->updateFont(fontName, static_cast(fontSize), isBold, isItalic, isOblique, isSmallCaps); + } +#elif CC_PLATFORM == CC_PLATFORM_MACOS || CC_PLATFORM == CC_PLATFORM_IOS + if (_font != font) { + _font = font; + + ccstd::string boldStr; + ccstd::string fontName = "Arial"; + ccstd::string fontSizeStr = "30"; + bool isItalic = font.find("italic", 0) != ccstd::string::npos || font.find("Italic", 0) != ccstd::string::npos; + + // support get font name from `60px American` or `60px "American abc-abc_abc"` + std::regex re("(bold)?\\s*((\\d+)([\\.]\\d+)?)px\\s+([\\w-]+|\"[\\w -]+\"$)"); + std::match_results results; + if (std::regex_search(_font.cbegin(), _font.cend(), results, re)) { + boldStr = results[1].str(); + fontSizeStr = results[2].str(); + fontName = results[5].str(); + } + float fontSize = atof(fontSizeStr.c_str()); + bool isBold = !boldStr.empty() || font.find("bold", 0) != ccstd::string::npos || font.find("Bold", 0) != ccstd::string::npos; + _delegate->updateFont(fontName, static_cast(fontSize), isBold, isItalic, false, false); + } +#endif +} + +void CanvasRenderingContext2D::setTextAlign(const ccstd::string &textAlign) { + //SE_LOGD("CanvasRenderingContext2D::set_textAlign: %s\n", textAlign.c_str()); + if (textAlign == "left") { + _delegate->setTextAlign(TextAlign::LEFT); + } else if (textAlign == "center" || textAlign == "middle") { + _delegate->setTextAlign(TextAlign::CENTER); + } else if (textAlign == "right") { + _delegate->setTextAlign(TextAlign::RIGHT); + } else { + CC_ABORT(); + } +} + +void CanvasRenderingContext2D::setTextBaseline(const ccstd::string &textBaseline) { + //SE_LOGD("CanvasRenderingContext2D::set_textBaseline: %s\n", textBaseline.c_str()); + if (textBaseline == "top") { + _delegate->setTextBaseline(TextBaseline::TOP); + } else if (textBaseline == "middle") { + _delegate->setTextBaseline(TextBaseline::MIDDLE); + } else if (textBaseline == "bottom") //REFINE:, how to deal with alphabetic, currently we handle it as bottom mode. + { + _delegate->setTextBaseline(TextBaseline::BOTTOM); + } else if (textBaseline == "alphabetic") { + _delegate->setTextBaseline(TextBaseline::ALPHABETIC); + } else { + CC_ABORT(); + } +} + +void CanvasRenderingContext2D::setFillStyle(const ccstd::string &fillStyle) { + CSSColorParser::Color color = CSSColorParser::parse(fillStyle); + _delegate->setFillStyle(color.r, color.g, color.b, static_cast(color.a * 255)); + //SE_LOGD("CanvasRenderingContext2D::set_fillStyle: %s, (%d, %d, %d, %f)\n", fillStyle.c_str(), color.r, color.g, color.b, color.a); +} + +void CanvasRenderingContext2D::setStrokeStyle(const ccstd::string &strokeStyle) { + CSSColorParser::Color color = CSSColorParser::parse(strokeStyle); + _delegate->setStrokeStyle(color.r, color.g, color.b, static_cast(color.a * 255)); +} + +void CanvasRenderingContext2D::setShadowBlur(float blur) { + _delegate->setShadowBlur(blur); +} + +void CanvasRenderingContext2D::setShadowColor(const ccstd::string &shadowColor) { + CSSColorParser::Color color = CSSColorParser::parse(shadowColor); + _delegate->setShadowColor(color.r, color.g, color.b, static_cast(color.a * 255)); +} + +void CanvasRenderingContext2D::setShadowOffsetX(float offsetX) { + _delegate->setShadowOffsetX(offsetX); +} + +void CanvasRenderingContext2D::setShadowOffsetY(float offsetY) { + _delegate->setShadowOffsetY(offsetY); +} + +void CanvasRenderingContext2D::setGlobalCompositeOperation(const ccstd::string &globalCompositeOperation) { + //SE_LOGE("%s isn't implemented!\n", __FUNCTION__); +} + +void CanvasRenderingContext2D::fillImageData(const Data &imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) { + recreateBufferIfNeeded(); + //SE_LOGE("%s isn't implemented!\n", __FUNCTION__); +#if CC_PLATFORM == CC_PLATFORM_WINDOWS +#elif CC_PLATFORM == CC_PLATFORM_ANDROID + _delegate->fillImageData(imageData, imageWidth, imageHeight, offsetX, offsetY); +#endif +} +// transform +//REFINE: + +void CanvasRenderingContext2D::translate(float x, float y) { + //SE_LOGE("%s isn't implemented!\n", __FUNCTION__); +} + +void CanvasRenderingContext2D::scale(float x, float y) { + //SE_LOGE("%s isn't implemented!\n", __FUNCTION__); +} + +void CanvasRenderingContext2D::rotate(float angle) { + //SE_LOGE("%s isn't implemented!\n", __FUNCTION__); +} + +void CanvasRenderingContext2D::transform(float a, float b, float c, float d, float e, float f) { + //SE_LOGE("%s isn't implemented!\n", __FUNCTION__); +} + +void CanvasRenderingContext2D::setTransform(float a, float b, float c, float d, float e, float f) { + //SE_LOGE("%s isn't implemented!\n", __FUNCTION__); +} + +void CanvasRenderingContext2D::recreateBufferIfNeeded() { + if (_isBufferSizeDirty) { + _isBufferSizeDirty = false; + //SE_LOGD("Recreate buffer %p, w: %f, h:%f\n", this, __width, __height); + _delegate->recreateBuffer(_width, _height); + } +} + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/canvas/CanvasRenderingContext2D.h b/cocos/platform/interfaces/modules/canvas/CanvasRenderingContext2D.h new file mode 100644 index 0000000..76b235b --- /dev/null +++ b/cocos/platform/interfaces/modules/canvas/CanvasRenderingContext2D.h @@ -0,0 +1,128 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/canvas/ICanvasRenderingContext2D.h" + +namespace cc { + +class CC_DLL CanvasGradient : public ICanvasGradient { +public: + CanvasGradient(); + ~CanvasGradient() override; // NOLINT(performance-trivially-destructible) + void addColorStop(float offset, const ccstd::string &color) override; +}; + +class CC_DLL CanvasRenderingContext2D : public ICanvasRenderingContext2D { +public: + CanvasRenderingContext2D(float width, float height); + ~CanvasRenderingContext2D() override; + + // Rect + void rect(float x, float y, float width, float height) override; + void clearRect(float x, float y, float width, float height) override; + void fillRect(float x, float y, float width, float height) override; + + void fillText(const ccstd::string &text, float x, float y, float maxWidth) override; + void strokeText(const ccstd::string &text, float x, float y, float maxWidth) override; + Size measureText(const ccstd::string &text) override; + ICanvasGradient *createLinearGradient(float x0, float y0, float x1, float y1) override; + void save() override; + // Paths + void beginPath() override; + void closePath() override; + void moveTo(float x, float y) override; + void lineTo(float x, float y) override; + void fill() override; + void stroke() override; + void restore() override; + + // callback + using CanvasBufferUpdatedCallback = std::function; + void setCanvasBufferUpdatedCallback(const CanvasBufferUpdatedCallback &cb) override; + void fetchData() override; + + // functions for properties + void setWidth(float width) override; + void setHeight(float height) override; + void setLineWidth(float lineWidth) override; + void setLineJoin(const ccstd::string &lineJoin) override; + void setLineCap(const ccstd::string &lineCap) override; + void setFont(const ccstd::string &font) override; + void setTextAlign(const ccstd::string &textAlign) override; + void setTextBaseline(const ccstd::string &textBaseline) override; + void setFillStyle(const ccstd::string &fillStyle) override; + void setStrokeStyle(const ccstd::string &strokeStyle) override; + void setGlobalCompositeOperation(const ccstd::string &globalCompositeOperation) override; + void setShadowBlur(float blur) override; + void setShadowColor(const ccstd::string &shadowColor) override; + void setShadowOffsetX(float offsetX) override; + void setShadowOffsetY(float offsetY) override; + + // fill image data into Context2D + void fillImageData(const Data &imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) override; + + // transform + void translate(float x, float y) override; + void scale(float x, float y) override; + void rotate(float angle) override; + + void transform(float a, float b, float c, float d, float e, float f) override; + void setTransform(float a, float b, float c, float d, float e, float f) override; + +private: + void recreateBufferIfNeeded() override; + +public: + float _width = 0.0F; + float _height = 0.0F; + + // Line styles + float _lineWidth = 1.0F; + ccstd::string _lineJoin = "miter"; + ccstd::string _lineCap = "butt"; + + // Text styles + ccstd::string _font = "10px sans-serif"; + ccstd::string _textAlign = "start"; + ccstd::string _textBaseline = "alphabetic"; + + // Fill and stroke styles + ccstd::string _fillStyle = "#000"; + + ccstd::string _strokeStyle = "#000"; + + // Compositing + ccstd::string _globalCompositeOperation = "source-over"; + +private: + ICanvasRenderingContext2D::Delegate *_delegate; + + CanvasBufferUpdatedCallback _canvasBufferUpdatedCB = nullptr; + + bool _isBufferSizeDirty = true; +}; + +} // namespace cc diff --git a/cocos/platform/interfaces/modules/canvas/ICanvasRenderingContext2D.h b/cocos/platform/interfaces/modules/canvas/ICanvasRenderingContext2D.h new file mode 100644 index 0000000..0650bc0 --- /dev/null +++ b/cocos/platform/interfaces/modules/canvas/ICanvasRenderingContext2D.h @@ -0,0 +1,150 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Data.h" +#include "base/std/container/array.h" +#include "base/std/container/string.h" +#include "math/Geometry.h" +#include "platform/interfaces/OSInterface.h" + +namespace cc { + +class CC_DLL ICanvasGradient { +public: + ICanvasGradient() = default; + virtual ~ICanvasGradient() = default; // NOLINT(performance-trivially-destructible) + virtual void addColorStop(float offset, const ccstd::string &color) = 0; +}; + +class CC_DLL ICanvasRenderingContext2D : public OSInterface { +public: + enum class TextAlign { + LEFT, + CENTER, + RIGHT + }; + + enum class TextBaseline { + TOP, + MIDDLE, + BOTTOM, + ALPHABETIC + }; + + class Delegate { + public: + using Size = ccstd::array; + virtual ~Delegate() = default; + virtual void recreateBuffer(float w, float h) = 0; + virtual void beginPath() = 0; + virtual void closePath() = 0; + virtual void moveTo(float x, float y) = 0; + virtual void lineTo(float x, float y) = 0; + virtual void stroke() = 0; + virtual void saveContext() = 0; + virtual void restoreContext() = 0; + virtual void clearRect(float /*x*/, float /*y*/, float w, float h) = 0; + virtual void fill() = 0; + virtual void rect(float x, float y, float w, float h) = 0; + virtual void setLineCap(const ccstd::string &lineCap) = 0; + virtual void setLineJoin(const ccstd::string &lineCap) = 0; + virtual void fillImageData(const Data &imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) = 0; + virtual void fillRect(float x, float y, float w, float h) = 0; + virtual void fillText(const ccstd::string &text, float x, float y, float /*maxWidth*/) = 0; + virtual void strokeText(const ccstd::string &text, float /*x*/, float /*y*/, float /*maxWidth*/) = 0; + virtual Size measureText(const ccstd::string &text) = 0; + virtual void updateFont(const ccstd::string &fontName, float fontSize, bool bold, bool italic, bool oblique, bool smallCaps) = 0; + virtual void setTextAlign(TextAlign align) = 0; + virtual void setTextBaseline(TextBaseline baseline) = 0; + virtual void setFillStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) = 0; + virtual void setStrokeStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) = 0; + virtual void setLineWidth(float lineWidth) = 0; + virtual void setShadowBlur(float blur) = 0; + virtual void setShadowColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) = 0; + virtual void setShadowOffsetX(float offsetX) = 0; + virtual void setShadowOffsetY(float offsetY) = 0; + virtual const cc::Data &getDataRef() const = 0; + virtual void updateData() = 0; + }; + //static OSInterface::Ptr getInterface(); + // Rect + virtual void rect(float x, float y, float width, float height) = 0; + virtual void clearRect(float x, float y, float width, float height) = 0; + virtual void fillRect(float x, float y, float width, float height) = 0; + + virtual void fillText(const ccstd::string &text, float x, float y, float maxWidth) = 0; + virtual void strokeText(const ccstd::string &text, float x, float y, float maxWidth) = 0; + virtual Size measureText(const ccstd::string &text) = 0; + virtual ICanvasGradient *createLinearGradient(float x0, float y0, float x1, float y1) = 0; + virtual void save() = 0; + // Paths + virtual void beginPath() = 0; + virtual void closePath() = 0; + virtual void moveTo(float x, float y) = 0; + virtual void lineTo(float x, float y) = 0; + virtual void fill() = 0; + virtual void stroke() = 0; + virtual void restore() = 0; + + // callback + using CanvasBufferUpdatedCallback = std::function; + virtual void setCanvasBufferUpdatedCallback(const CanvasBufferUpdatedCallback &cb) = 0; + + // functions for properties + virtual void setWidth(float width) = 0; + virtual void setHeight(float height) = 0; + virtual void setLineWidth(float lineWidth) = 0; + virtual void setLineJoin(const ccstd::string &lineJoin) = 0; + virtual void setLineCap(const ccstd::string &lineCap) = 0; + virtual void setFont(const ccstd::string &font) = 0; + virtual void setTextAlign(const ccstd::string &textAlign) = 0; + virtual void setTextBaseline(const ccstd::string &textBaseline) = 0; + virtual void setFillStyle(const ccstd::string &fillStyle) = 0; + virtual void setStrokeStyle(const ccstd::string &strokeStyle) = 0; + virtual void setGlobalCompositeOperation(const ccstd::string &globalCompositeOperation) = 0; + virtual void setShadowBlur(float blur) = 0; + virtual void setShadowColor(const ccstd::string &shadowColor) = 0; + virtual void setShadowOffsetX(float offsetX) = 0; + virtual void setShadowOffsetY(float offsetY) = 0; + + // fill image data into Context2D + virtual void fillImageData(const Data &imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) = 0; + + // transform + virtual void translate(float x, float y) = 0; + virtual void scale(float x, float y) = 0; + virtual void rotate(float angle) = 0; + + virtual void transform(float a, float b, float c, float d, float e, float f) = 0; + virtual void setTransform(float a, float b, float c, float d, float e, float f) = 0; + + virtual void fetchData() = 0; + +private: + virtual void recreateBufferIfNeeded() = 0; +}; + +} // namespace cc diff --git a/cocos/platform/ios/AppDelegateBridge.h b/cocos/platform/ios/AppDelegateBridge.h new file mode 100644 index 0000000..e2515c9 --- /dev/null +++ b/cocos/platform/ios/AppDelegateBridge.h @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#import +#import +#include "platform/UniversalPlatform.h" + +@interface AppDelegateBridge : NSObject +- (float)getPixelRatio; +- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; +- (void)applicationDidBecomeActive:(UIApplication *)application; +- (void)applicationWillResignActive:(UIApplication *)application; +- (void)applicationWillTerminate:(UIApplication *)application; +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application; +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator; +- (void)dispatchTouchEvent:(cc::TouchEvent::Type)type touches:(NSSet *)touches withEvent:(UIEvent *)event; +@end diff --git a/cocos/platform/ios/AppDelegateBridge.mm b/cocos/platform/ios/AppDelegateBridge.mm new file mode 100644 index 0000000..b3647dc --- /dev/null +++ b/cocos/platform/ios/AppDelegateBridge.mm @@ -0,0 +1,121 @@ +/**************************************************************************** + Copyright (c) 2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#import "platform/ios/AppDelegateBridge.h" +#include "platform/ios/IOSPlatform.h" +#include "platform/interfaces/modules/IScreen.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" + +@implementation AppDelegateBridge +cc::IOSPlatform *_platform = nullptr; +- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + _platform = dynamic_cast(cc::BasePlatform::getPlatform()); + CC_ASSERT_NOT_NULL(_platform); + + // Create main system window + CGRect bounds = [[UIScreen mainScreen] bounds]; + cc::ISystemWindowInfo info; + info.width = static_cast(bounds.size.width); + info.height = static_cast(bounds.size.height); + auto *windowMgr = _platform->getInterface(); + windowMgr->createWindow(info); + + _platform->loop(); +} + +- (void)applicationWillResignActive:(UIApplication *)application { + _platform->onPause(); +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + _platform->onResume(); +} + +- (void)applicationWillTerminate:(UIApplication *)application { + _platform->onClose(); + _platform->onDestroy(); + _platform = nullptr; +} + + +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { + cc::events::LowMemory::broadcast(); +} + +- (float)getPixelRatio { + cc::BasePlatform *platform = cc::BasePlatform::getPlatform(); + cc::IScreen *screenIntf = platform->getInterface(); + return (float)screenIntf->getDevicePixelRatio(); +} + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + cc::IScreen::Orientation orientation; + // reference: https://developer.apple.com/documentation/uikit/uiinterfaceorientation?language=objc + // UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft + // UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight + switch ([UIDevice currentDevice].orientation) { + case UIDeviceOrientationPortrait: + orientation = cc::IScreen::Orientation::PORTRAIT; + break; + case UIDeviceOrientationLandscapeRight: + orientation = cc::IScreen::Orientation::LANDSCAPE_LEFT; + break; + case UIDeviceOrientationPortraitUpsideDown: + orientation = cc::IScreen::Orientation::PORTRAIT_UPSIDE_DOWN; + break; + case UIDeviceOrientationLandscapeLeft: + orientation = cc::IScreen::Orientation::LANDSCAPE_RIGHT; + break; + default: + break; + } + cc::DeviceEvent ev; + cc::BasePlatform *platform = cc::BasePlatform::getPlatform(); + cc::IScreen *screenIntf = platform->getInterface(); + cc::events::Orientation::broadcast((int)screenIntf->getDeviceOrientation()); + + float pixelRatio = screenIntf->getDevicePixelRatio(); + cc::WindowEvent resizeEv; + resizeEv.windowId = 1; + resizeEv.type = cc::WindowEvent::Type::RESIZED; + resizeEv.width = size.width * pixelRatio; + resizeEv.height = size.height * pixelRatio; + cc::events::WindowEvent::broadcast(resizeEv); +} + +- (void)dispatchTouchEvent:(cc::TouchEvent::Type)type touches:(NSSet *)touches withEvent:(UIEvent *)event { + cc::TouchEvent touchEvent; + touchEvent.windowId = 1; + touchEvent.type = type; + for (UITouch *touch in touches) { + touchEvent.touches.push_back({static_cast([touch locationInView:[touch view]].x), + static_cast([touch locationInView:[touch view]].y), + static_cast((intptr_t)touch)}); + } + cc::events::Touch::broadcast(touchEvent); + touchEvent.touches.clear(); +} + +@end diff --git a/cocos/platform/ios/IOSPlatform.h b/cocos/platform/ios/IOSPlatform.h new file mode 100644 index 0000000..dd6ce26 --- /dev/null +++ b/cocos/platform/ios/IOSPlatform.h @@ -0,0 +1,71 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/UniversalPlatform.h" + +namespace cc { + +class IOSPlatform : public UniversalPlatform { +public: + IOSPlatform() = default; + /** + * @brief Destructor of WindowPlatform. + */ + ~IOSPlatform() override; + /** + * @brief Implementation of Windows platform initialization. + */ + int32_t init() override; + + /** + * @brief Start base platform initialization. + */ + int32_t run(int argc, const char **argv) override; + + void requestExit(); + + void exit() override; + /** + * @brief Implement the main logic of the base platform. + */ + int32_t loop() override; + void setFps(int32_t fps) override; + + int32_t getFps() const override; + + void onPause() override; + void onResume() override; + void onClose() override; + void onDestroy() override; + ISystemWindow *createNativeWindow(uint32_t windowId, void *externalHandle) override; + +private: + bool _requestExit{false}; + bool _quitLoop{false}; + ThreadCallback _cb; +}; + +} // namespace cc diff --git a/cocos/platform/ios/IOSPlatform.mm b/cocos/platform/ios/IOSPlatform.mm new file mode 100644 index 0000000..ec3f477 --- /dev/null +++ b/cocos/platform/ios/IOSPlatform.mm @@ -0,0 +1,183 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/ios/IOSPlatform.h" +#include "platform/interfaces/OSInterface.h" +#include "platform/interfaces/modules/ISystemWindow.h" + +#import +#include "base/memory/Memory.h" +#include "modules/Accelerometer.h" +#include "modules/Battery.h" +#include "modules/Network.h" +#include "modules/Screen.h" +#include "modules/System.h" +#include "modules/SystemWindow.h" +#include "modules/SystemWindowManager.h" +#include "modules/Vibrator.h" + +extern int cocos_main(int argc, const char **argv); + +@interface MyTimer : NSObject { + cc::IOSPlatform *_platform; + CADisplayLink *_displayLink; + int _fps; +} +- (instancetype)initWithApp:(cc::IOSPlatform *)platform fps:(int)fps; +- (void)start; +- (void)changeFPS:(int)fps; +- (void)pause; +- (void)resume; +@end + +@implementation MyTimer + +- (instancetype)initWithApp:(cc::IOSPlatform *)platform fps:(int)fps { + if (self = [super init]) { + _fps = fps; + _platform = platform; + _displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(renderScene:)]; + _displayLink.preferredFramesPerSecond = _fps; + } + return self; +} + +- (void)start { + [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; +} + +- (void)pause { + _displayLink.paused = TRUE; +} + +- (void)resume { + _displayLink.paused = FALSE; +} + +- (void)changeFPS:(int)fps { + _displayLink.preferredFramesPerSecond = fps; +} + +- (int)getFPS { + return (int)_displayLink.preferredFramesPerSecond; +} + +- (void)renderScene:(id)sender { + _platform->runTask(); +} + +@end + +namespace { +MyTimer *_timer; +} + +namespace cc { + +IOSPlatform::~IOSPlatform() = default; + +int32_t IOSPlatform::init() { + _timer = [[MyTimer alloc] initWithApp:this fps:60]; + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + return 0; +} + +void IOSPlatform::exit() { + if(_requestExit) { + // Manual quit requires a call to onDestory. + onDestroy(); + ::exit(0); + } else { + _quitLoop = true; + } +} + +int32_t IOSPlatform::loop() { + cocos_main(0, nullptr); + [_timer start]; + return 0; +} + +int32_t IOSPlatform::run(int argc, const char **argv) { + return 0; +} + +void IOSPlatform::setFps(int32_t fps) { + [_timer changeFPS:fps]; +} + +int32_t IOSPlatform::getFps() const { + return [_timer getFPS]; +} + +void IOSPlatform::onPause() { + [_timer pause]; + + cc::WindowEvent ev; + ev.type = cc::WindowEvent::Type::HIDDEN; + cc::events::WindowEvent::broadcast(ev); +} + +void IOSPlatform::onResume() { + [_timer resume]; + + cc::WindowEvent ev; + ev.type = cc::WindowEvent::Type::SHOW; + cc::events::WindowEvent::broadcast(ev); +} + +void IOSPlatform::onClose() { + cc::WindowEvent ev; + ev.type = cc::WindowEvent::Type::CLOSE; + cc::events::WindowEvent::broadcast(ev); +} + +void IOSPlatform::requestExit() { + _requestExit = true; + onClose(); +} + +void IOSPlatform::onDestroy() { + if(!_requestExit) { + // ios exit process is special because it needs to wait for ts layer to destroy resources. + // The timer cannot be used here. + while (!_quitLoop) { + runTask(); + } + } + UniversalPlatform::onDestroy(); +} + +ISystemWindow *IOSPlatform::createNativeWindow(uint32_t windowId, void *externalHandle) { + return ccnew SystemWindow(windowId, externalHandle); +} + +} // namespace cc diff --git a/cocos/platform/ios/Untitled.xcworkspace/contents.xcworkspacedata b/cocos/platform/ios/Untitled.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..94b2795 --- /dev/null +++ b/cocos/platform/ios/Untitled.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,4 @@ + + + diff --git a/cocos/platform/ios/Untitled.xcworkspace/xcuserdata/gem.xcuserdatad/UserInterfaceState.xcuserstate b/cocos/platform/ios/Untitled.xcworkspace/xcuserdata/gem.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..84efaf4 Binary files /dev/null and b/cocos/platform/ios/Untitled.xcworkspace/xcuserdata/gem.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/cocos/platform/ios/View.h b/cocos/platform/ios/View.h new file mode 100644 index 0000000..4794d33 --- /dev/null +++ b/cocos/platform/ios/View.h @@ -0,0 +1,42 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#import + +#ifdef CC_USE_METAL + #import +#endif + +@interface View : UIView + +@property (nonatomic, assign) BOOL preventTouch; +#ifdef CC_USE_METAL +@property (nonatomic, assign) id device; +#endif + +- (void)setPreventTouchEvent:(BOOL)flag; + +@end diff --git a/cocos/platform/ios/View.mm b/cocos/platform/ios/View.mm new file mode 100644 index 0000000..109c627 --- /dev/null +++ b/cocos/platform/ios/View.mm @@ -0,0 +1,121 @@ +/**************************************************************************** + Copyright (c) 2020-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#import "View.h" + +#include +#import "platform/ios/AppDelegateBridge.h" +#include "platform/ios/IOSPlatform.h" + +namespace { +} // namespace + +@implementation View { + cc::IOSPlatform *_platform; +} + +@synthesize preventTouch; + +#ifdef CC_USE_METAL ++ (Class)layerClass { + return [CAMetalLayer class]; +} +#else ++ (Class)layerClass { + return [CAEAGLLayer class]; +} +#endif + +- (void)dispatchTouchEvent:(cc::TouchEvent::Type)type withEvent:(NSSet *)touches { + cc::TouchEvent touchEvent; + touchEvent.windowId = 1; + touchEvent.type = type; + for (UITouch *touch in touches) { + touchEvent.touches.push_back({static_cast([touch locationInView:[touch view]].x), + static_cast([touch locationInView:[touch view]].y), + static_cast((intptr_t)touch)}); + } + CC_ASSERT_NOT_NULL(_platform); + cc::events::Touch::broadcast(touchEvent); +} + +- (id)initWithFrame:(CGRect)frame { + _platform = reinterpret_cast(cc::BasePlatform::getPlatform()); +#ifdef CC_USE_METAL + if (self = [super initWithFrame:frame]) { + self.preventTouch = FALSE; + + float pixelRatio = [[UIScreen mainScreen] nativeScale]; + CGSize size = CGSizeMake(static_cast(frame.size.width * pixelRatio), + static_cast(frame.size.height * pixelRatio)); + self.contentScaleFactor = pixelRatio; + // Config metal layer + CAMetalLayer *layer = (CAMetalLayer *)self.layer; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + layer.device = self.device = MTLCreateSystemDefaultDevice(); + layer.drawableSize = size; + } +#else + if (self = [super initWithFrame:frame]) { + self.preventTouch = FALSE; + } +#endif + + return self; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + if (self.preventTouch) + return; + + [self dispatchTouchEvent:cc::TouchEvent::Type::BEGAN withEvent:touches]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + if (self.preventTouch) + return; + + [self dispatchTouchEvent:cc::TouchEvent::Type::MOVED withEvent:touches]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + if (self.preventTouch) + return; + + [self dispatchTouchEvent:cc::TouchEvent::Type::ENDED withEvent:touches]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + if (self.preventTouch) + return; + + [self dispatchTouchEvent:cc::TouchEvent::Type::CANCELLED withEvent:touches]; +} + +- (void)setPreventTouchEvent:(BOOL)flag { + self.preventTouch = flag; +} + +@end diff --git a/cocos/platform/ios/WarpCocosContent.h b/cocos/platform/ios/WarpCocosContent.h new file mode 100644 index 0000000..6d1b8d8 --- /dev/null +++ b/cocos/platform/ios/WarpCocosContent.h @@ -0,0 +1,24 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface WarpCocosContent : NSObject + +// 渲染视图 +@property (nonatomic, assign) UIView *renderView; + +// 用于动态创建游戏视图控制器的 Block +@property (nonatomic, copy) UIViewController* (^gameVCBlock)(void); + +// 单例方法 ++ (instancetype)shareInstance; + +// 设置渲染视图 +- (void)setRenderView:(UIView *)renderView; + +// 获取当前游戏视图控制器 +- (UIViewController *)getGameViewController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/cocos/platform/ios/WarpCocosContent.mm b/cocos/platform/ios/WarpCocosContent.mm new file mode 100644 index 0000000..468f75a --- /dev/null +++ b/cocos/platform/ios/WarpCocosContent.mm @@ -0,0 +1,28 @@ +#import "WarpCocosContent.h" + +@implementation WarpCocosContent + +// 单例对象 ++ (instancetype)shareInstance { + static WarpCocosContent *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[WarpCocosContent alloc] init]; + }); + return instance; +} + +// 设置渲染视图 +- (void)setRenderView:(UIView *)renderView { + _renderView = renderView; +} + +// 获取当前游戏视图控制器 +- (UIViewController *)getGameViewController { + if (self.gameVCBlock) { + return self.gameVCBlock(); + } + return nil; +} + +@end diff --git a/cocos/platform/ios/modules/Accelerometer.h b/cocos/platform/ios/modules/Accelerometer.h new file mode 100644 index 0000000..8de5dc1 --- /dev/null +++ b/cocos/platform/ios/modules/Accelerometer.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IAccelerometer.h" + +namespace cc { + +class Accelerometer : public IAccelerometer { +public: + /** + * @brief To enable or disable accelerometer. + */ + void setAccelerometerEnabled(bool isEnabled) override; + + /** + * @brief Sets the interval of accelerometer. + */ + void setAccelerometerInterval(float interval) override; + + /** + * @brief Gets the motion value of current device. + */ + const MotionValue &getDeviceMotionValue() override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/ios/modules/Accelerometer.mm b/cocos/platform/ios/modules/Accelerometer.mm new file mode 100644 index 0000000..e673aaf --- /dev/null +++ b/cocos/platform/ios/modules/Accelerometer.mm @@ -0,0 +1,159 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/ios/modules/Accelerometer.h" +#import +// Accelerometer +#include +#import +#include +#include "platform/interfaces/modules/IAccelerometer.h" + +static const float g = 9.80665; +static const float radToDeg = (180 / M_PI); + +@interface CCMotionDispatcher : NSObject { + CMMotionManager *_motionManager; + cc::IAccelerometer::MotionValue _motionValue; + float _interval; // unit: seconds + bool _enabled; +} + ++ (id)sharedMotionDispatcher; +- (id)init; +- (void)setMotionEnabled:(bool)isEnabled; +- (void)setMotionInterval:(float)interval; + +@end + +@implementation CCMotionDispatcher + +static CCMotionDispatcher *__motionDispatcher = nullptr; + ++ (id)sharedMotionDispatcher { + if (__motionDispatcher == nil) { + __motionDispatcher = [[CCMotionDispatcher alloc] init]; + } + + return __motionDispatcher; +} + +- (id)init { + if ((self = [super init])) { + _enabled = false; + _interval = 1.0f / 60.0f; + _motionManager = [[CMMotionManager alloc] init]; + } + return self; +} + +- (void)dealloc { + __motionDispatcher = nullptr; + [_motionManager release]; + [super dealloc]; +} + +- (void)setMotionEnabled:(bool)enabled { + if (_enabled == enabled) + return; + + bool isDeviceMotionAvailable = _motionManager.isDeviceMotionAvailable; + if (enabled) { + // Has Gyro? (iPhone4 and newer) + if (isDeviceMotionAvailable) { + [_motionManager startDeviceMotionUpdates]; + _motionManager.deviceMotionUpdateInterval = _interval; + } + // Only basic accelerometer data + else { + [_motionManager startAccelerometerUpdates]; + _motionManager.accelerometerUpdateInterval = _interval; + } + } else { + // Has Gyro? (iPhone4 and newer) + if (isDeviceMotionAvailable) { + [_motionManager stopDeviceMotionUpdates]; + } + // Only basic accelerometer data + else { + [_motionManager stopAccelerometerUpdates]; + } + } + _enabled = enabled; +} + +- (void)setMotionInterval:(float)interval { + _interval = interval; + if (_enabled) { + if (_motionManager.isDeviceMotionAvailable) { + _motionManager.deviceMotionUpdateInterval = _interval; + } else { + _motionManager.accelerometerUpdateInterval = _interval; + } + } +} + +- (const cc::IAccelerometer::MotionValue &)getMotionValue { + if (_motionManager.isDeviceMotionAvailable) { + CMDeviceMotion *motion = _motionManager.deviceMotion; + _motionValue.accelerationX = motion.userAcceleration.x * g; + _motionValue.accelerationY = motion.userAcceleration.y * g; + _motionValue.accelerationZ = motion.userAcceleration.z * g; + + _motionValue.accelerationIncludingGravityX = (motion.userAcceleration.x + motion.gravity.x) * g; + _motionValue.accelerationIncludingGravityY = (motion.userAcceleration.y + motion.gravity.y) * g; + _motionValue.accelerationIncludingGravityZ = (motion.userAcceleration.z + motion.gravity.z) * g; + + _motionValue.rotationRateAlpha = motion.rotationRate.x * radToDeg; + _motionValue.rotationRateBeta = motion.rotationRate.y * radToDeg; + _motionValue.rotationRateGamma = motion.rotationRate.z * radToDeg; + } else { + CMAccelerometerData *acc = _motionManager.accelerometerData; + _motionValue.accelerationIncludingGravityX = acc.acceleration.x * g; + _motionValue.accelerationIncludingGravityY = acc.acceleration.y * g; + _motionValue.accelerationIncludingGravityZ = acc.acceleration.z * g; + } + + return _motionValue; +} + +@end + +// + +namespace cc { +void Accelerometer::setAccelerometerEnabled(bool isEnabled) { + [[CCMotionDispatcher sharedMotionDispatcher] setMotionEnabled:isEnabled]; +} + +void Accelerometer::setAccelerometerInterval(float interval) { + [[CCMotionDispatcher sharedMotionDispatcher] setMotionInterval:interval]; +} + +const Accelerometer::MotionValue &Accelerometer::getDeviceMotionValue() { + return [[CCMotionDispatcher sharedMotionDispatcher] getMotionValue]; +} + +} // namespace cc diff --git a/cocos/platform/ios/modules/Battery.h b/cocos/platform/ios/modules/Battery.h new file mode 100644 index 0000000..802b19f --- /dev/null +++ b/cocos/platform/ios/modules/Battery.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IBattery.h" + +namespace cc { + +class Battery : public IBattery { +public: + Battery() = default; + float getBatteryLevel() const override; +}; + +} // namespace cc diff --git a/cocos/platform/ios/modules/Battery.mm b/cocos/platform/ios/modules/Battery.mm new file mode 100644 index 0000000..0228ee2 --- /dev/null +++ b/cocos/platform/ios/modules/Battery.mm @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/ios/modules/Battery.h" +#import + +namespace cc { + +float Battery::getBatteryLevel() const { + // set batteryMonitoringEnabled value to YES otherwise batteryLevel is always -1 + [UIDevice currentDevice].batteryMonitoringEnabled = YES; + return [UIDevice currentDevice].batteryLevel; +} + +} // namespace cc diff --git a/cocos/platform/ios/modules/Network.h b/cocos/platform/ios/modules/Network.h new file mode 100644 index 0000000..dea5b74 --- /dev/null +++ b/cocos/platform/ios/modules/Network.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/INetwork.h" + +namespace cc { + +class Network : public INetwork { +public: + NetworkType getNetworkType() const override; +}; + +} // namespace cc diff --git a/cocos/platform/ios/modules/Network.mm b/cocos/platform/ios/modules/Network.mm new file mode 100644 index 0000000..1911fd2 --- /dev/null +++ b/cocos/platform/ios/modules/Network.mm @@ -0,0 +1,57 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/ios/modules/Network.h" +#include +#import +#include "platform/apple/Reachability.h" + +namespace cc { + +INetwork::NetworkType Network::getNetworkType() const { + static Reachability *__reachability = nullptr; + if (__reachability == nullptr) { + __reachability = Reachability::createForInternetConnection(); + __reachability->addRef(); + } + + NetworkType ret = NetworkType::NONE; + Reachability::NetworkStatus status = __reachability->getCurrentReachabilityStatus(); + switch (status) { + case Reachability::NetworkStatus::REACHABLE_VIA_WIFI: + ret = NetworkType::LAN; + break; + case Reachability::NetworkStatus::REACHABLE_VIA_WWAN: + ret = NetworkType::WWAN; + break; + default: + ret = NetworkType::NONE; + break; + } + + return ret; +} + +} // namespace cc diff --git a/cocos/platform/ios/modules/Screen.h b/cocos/platform/ios/modules/Screen.h new file mode 100644 index 0000000..e00ff05 --- /dev/null +++ b/cocos/platform/ios/modules/Screen.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IScreen.h" + +namespace cc { + +class Screen : public IScreen { +public: + int getDPI() const override; + float getDevicePixelRatio() const override; + void setKeepScreenOn(bool value) override; + Orientation getDeviceOrientation() const override; + Vec4 getSafeAreaEdge() const override; + /** + * @brief Get current display stats. + * @return bool, is displaying stats or not. + */ + bool isDisplayStats() override; + + /** + * @brief set display stats information. + */ + void setDisplayStats(bool isShow) override; +}; + +} // namespace cc diff --git a/cocos/platform/ios/modules/Screen.mm b/cocos/platform/ios/modules/Screen.mm new file mode 100644 index 0000000..8a38566 --- /dev/null +++ b/cocos/platform/ios/modules/Screen.mm @@ -0,0 +1,117 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/ios/modules/Screen.h" + +#include +#import +#include +#import + +#include "base/Macros.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "cocos/platform/ios/WarpCocosContent.h" + +namespace cc { + +int Screen::getDPI() const { + static int dpi = -1; + + if (dpi == -1) { + float scale = [[UIScreen mainScreen] scale]; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + dpi = 132 * scale; + } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + dpi = 163 * scale; + } else { + dpi = 160 * scale; + } + } + return dpi; +} + +float Screen::getDevicePixelRatio() const { + return [[UIScreen mainScreen] nativeScale]; +} + +void Screen::setKeepScreenOn(bool value) { + [[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)value]; +} + +Screen::Orientation Screen::getDeviceOrientation() const { + Orientation orientation = Orientation::PORTRAIT; + switch ([[UIApplication sharedApplication] statusBarOrientation]) { + case UIInterfaceOrientationLandscapeRight: + orientation = Orientation::LANDSCAPE_RIGHT; + break; + + case UIInterfaceOrientationLandscapeLeft: + orientation = Orientation::LANDSCAPE_LEFT; + break; + + case UIInterfaceOrientationPortraitUpsideDown: + orientation = Orientation::PORTRAIT_UPSIDE_DOWN; + break; + + case UIInterfaceOrientationPortrait: + orientation = Orientation::PORTRAIT; + break; + default: + CC_ABORT(); + break; + } + + return orientation; +} + +Vec4 Screen::getSafeAreaEdge() const { + //UIView *screenView = UIApplication.sharedApplication.delegate.window.rootViewController.view; + + UIView * screenView = WarpCocosContent.shareInstance.renderView; + if (@available(iOS 11.0, *)) { + UIEdgeInsets safeAreaEdge = screenView.safeAreaInsets; + return cc::Vec4(safeAreaEdge.top, safeAreaEdge.left, safeAreaEdge.bottom, safeAreaEdge.right); + } + // If running on iOS devices lower than 11.0, return ZERO Vec4. + return cc::Vec4(); +} + +bool Screen::isDisplayStats() { + se::AutoHandleScope hs; + se::Value ret; + char commandBuf[100] = "cc.debug.isDisplayStats();"; + se::ScriptEngine::getInstance()->evalString(commandBuf, 100, &ret); + return ret.toBoolean(); +} + +void Screen::setDisplayStats(bool isShow) { + se::AutoHandleScope hs; + char commandBuf[100] = {0}; + sprintf(commandBuf, "cc.debug.setDisplayStats(%s);", isShow ? "true" : "false"); + se::ScriptEngine::getInstance()->evalString(commandBuf); +} + +} // namespace cc diff --git a/cocos/platform/ios/modules/System.h b/cocos/platform/ios/modules/System.h new file mode 100644 index 0000000..e78a493 --- /dev/null +++ b/cocos/platform/ios/modules/System.h @@ -0,0 +1,65 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/ISystem.h" + +namespace cc { + +class System : public ISystem { +public: + /** + * @brief Get target system type. + */ + OSType getOSType() const override; + /** + * @brief Get target device model. + */ + ccstd::string getDeviceModel() const override; + /** + * @brief Get current language config. + * @return Current language config. + */ + LanguageType getCurrentLanguage() const override; + /** + * @brief Get current language iso 639-1 code. + * @return Current language iso 639-1 code. + */ + ccstd::string getCurrentLanguageCode() const override; + /** + * @brief Get system version. + * @return system version. + */ + ccstd::string getSystemVersion() const override; + /** + * @brief Open url in default browser. + * @param String with url to open. + * @return True if the resource located by the URL was successfully opened; otherwise false. + */ + bool openURL(const ccstd::string& url) override; + void copyTextToClipboard(const std::string& text) override; +}; + +} // namespace cc diff --git a/cocos/platform/ios/modules/System.mm b/cocos/platform/ios/modules/System.mm new file mode 100644 index 0000000..d2c253f --- /dev/null +++ b/cocos/platform/ios/modules/System.mm @@ -0,0 +1,109 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/ios/modules/System.h" +#import + +#include +#include +#include +#include + +namespace cc { +using OSType = System::OSType; + +OSType System::getOSType() const { + return OSType::IPHONE; +} + +ccstd::string System::getDeviceModel() const { + struct utsname systemInfo; + uname(&systemInfo); + return systemInfo.machine; +} + +System::LanguageType System::getCurrentLanguage() const { + // get the current language and country config + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSArray *languages = [defaults objectForKey:@"AppleLanguages"]; + NSString *currentLanguage = [languages objectAtIndex:0]; + + // get the current language code.(such as English is "en", Chinese is "zh" and so on) + NSDictionary *temp = [NSLocale componentsFromLocaleIdentifier:currentLanguage]; + NSString *languageCode = [temp objectForKey:NSLocaleLanguageCode]; + + if ([languageCode isEqualToString:@"zh"]) return LanguageType::CHINESE; + if ([languageCode isEqualToString:@"en"]) return LanguageType::ENGLISH; + if ([languageCode isEqualToString:@"fr"]) return LanguageType::FRENCH; + if ([languageCode isEqualToString:@"it"]) return LanguageType::ITALIAN; + if ([languageCode isEqualToString:@"de"]) return LanguageType::GERMAN; + if ([languageCode isEqualToString:@"es"]) return LanguageType::SPANISH; + if ([languageCode isEqualToString:@"nl"]) return LanguageType::DUTCH; + if ([languageCode isEqualToString:@"ru"]) return LanguageType::RUSSIAN; + if ([languageCode isEqualToString:@"ko"]) return LanguageType::KOREAN; + if ([languageCode isEqualToString:@"ja"]) return LanguageType::JAPANESE; + if ([languageCode isEqualToString:@"hu"]) return LanguageType::HUNGARIAN; + if ([languageCode isEqualToString:@"pt"]) return LanguageType::PORTUGUESE; + if ([languageCode isEqualToString:@"ar"]) return LanguageType::ARABIC; + if ([languageCode isEqualToString:@"nb"]) return LanguageType::NORWEGIAN; + if ([languageCode isEqualToString:@"pl"]) return LanguageType::POLISH; + if ([languageCode isEqualToString:@"tr"]) return LanguageType::TURKISH; + if ([languageCode isEqualToString:@"uk"]) return LanguageType::UKRAINIAN; + if ([languageCode isEqualToString:@"ro"]) return LanguageType::ROMANIAN; + if ([languageCode isEqualToString:@"bg"]) return LanguageType::BULGARIAN; + if ([languageCode isEqualToString:@"hi"]) return LanguageType::HINDI; + return LanguageType::ENGLISH; +} + +ccstd::string System::getCurrentLanguageCode() const { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSArray *languages = [defaults objectForKey:@"AppleLanguages"]; + NSString *currentLanguage = [languages objectAtIndex:0]; + return [currentLanguage UTF8String]; +} + +ccstd::string System::getSystemVersion() const { + NSString *systemVersion = [UIDevice currentDevice].systemVersion; + return [systemVersion UTF8String]; +} + +bool System::openURL(const ccstd::string &url) { + NSString *msg = [NSString stringWithCString:url.c_str() encoding:NSUTF8StringEncoding]; + NSURL *nsUrl = [NSURL URLWithString:msg]; + __block BOOL flag = false; + [[UIApplication sharedApplication] openURL:nsUrl + options:@{} + completionHandler:^(BOOL success) { + flag = success; + }]; + return flag; +} + +void System::copyTextToClipboard(const std::string& text) { + UIPasteboard* pasteboard = [UIPasteboard generalPasteboard]; + pasteboard.string = [NSString stringWithCString:text.c_str() encoding:NSUTF8StringEncoding]; +} + +} // namespace cc diff --git a/cocos/platform/ios/modules/SystemWindow.h b/cocos/platform/ios/modules/SystemWindow.h new file mode 100644 index 0000000..0871b9f --- /dev/null +++ b/cocos/platform/ios/modules/SystemWindow.h @@ -0,0 +1,58 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "platform/interfaces/modules/ISystemWindow.h" + +@class UIWindow; + +namespace cc { +class SystemWindow : public ISystemWindow { +public: + SystemWindow(uint32_t windowId, void* externalHandle); + ~SystemWindow() override; + + void closeWindow() override; + uintptr_t getWindowHandle() const override; + + Size getViewSize() const override; + /* + @brief enable/disable(lock) the cursor, default is enabled + */ + void setCursorEnabled(bool value) override; + + uint32_t getWindowId() const override { return _windowId; } + UIWindow* getUIWindow() const { return _window; } + +private: + int32_t _width{0}; + int32_t _height{0}; + + uint32_t _windowId{0}; + void* _externalHandle{nullptr}; + UIWindow* _window{nullptr}; +}; +} // namespace cc diff --git a/cocos/platform/ios/modules/SystemWindow.mm b/cocos/platform/ios/modules/SystemWindow.mm new file mode 100644 index 0000000..8eece38 --- /dev/null +++ b/cocos/platform/ios/modules/SystemWindow.mm @@ -0,0 +1,62 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/ios/modules/SystemWindow.h" +#import +#include "platform/BasePlatform.h" +#include "platform/ios/IOSPlatform.h" +#include "platform/interfaces/modules/IScreen.h" +#include "platform/ios/WarpCocosContent.h" + +namespace cc { + +SystemWindow::SystemWindow(uint32_t windowId, void *externalHandle) + : _windowId(windowId) + , _externalHandle(externalHandle) { +} + +SystemWindow::~SystemWindow() = default; + +void SystemWindow::setCursorEnabled(bool value) { +} + +void SystemWindow::closeWindow() { + // Force quit as there's no API to exit UIApplication + IOSPlatform* platform = dynamic_cast(BasePlatform::getPlatform()); + platform->requestExit(); +} + +uintptr_t SystemWindow::getWindowHandle() const { + //return reinterpret_cast(UIApplication.sharedApplication.delegate.window.rootViewController.view); + return reinterpret_cast(WarpCocosContent.shareInstance.renderView); +} + +SystemWindow::Size SystemWindow::getViewSize() const { + auto dpr = BasePlatform::getPlatform()->getInterface()->getDevicePixelRatio(); + CGRect bounds = [[UIScreen mainScreen] bounds]; + return Size{static_cast(bounds.size.width * dpr), static_cast(bounds.size.height * dpr)}; +} + +} // namespace cc diff --git a/cocos/platform/ios/modules/SystemWindowManager.h b/cocos/platform/ios/modules/SystemWindowManager.h new file mode 100644 index 0000000..1580d3c --- /dev/null +++ b/cocos/platform/ios/modules/SystemWindowManager.h @@ -0,0 +1,53 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/unordered_map.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" + +@class UIWindow; + +namespace cc { + +class ISystemWindow; + +class SystemWindowManager : public ISystemWindowManager { +public: + explicit SystemWindowManager() = default; + + int init() override { return 0; } + void processEvent() override {} + + ISystemWindow *createWindow(const ISystemWindowInfo &info) override; + ISystemWindow *getWindow(uint32_t windowId) const override; + const SystemWindowMap &getWindows() const override { return _windows; } + + ISystemWindow *getWindowFromUIWindow(UIWindow *window) const; + +private: + uint32_t _nextWindowId{1}; // start from 1, 0 means an invalid ID + SystemWindowMap _windows; +}; +} // namespace cc diff --git a/cocos/platform/ios/modules/SystemWindowManager.mm b/cocos/platform/ios/modules/SystemWindowManager.mm new file mode 100644 index 0000000..8c01430 --- /dev/null +++ b/cocos/platform/ios/modules/SystemWindowManager.mm @@ -0,0 +1,67 @@ +/**************************************************************************** + Copyright (c) 2021 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#include "SystemWindowManager.h" +#include "platform/BasePlatform.h" +#include "platform/SDLHelper.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" +#include "platform/ios/modules/SystemWindow.h" + +namespace cc { + +ISystemWindow *SystemWindowManager::getWindow(uint32_t windowId) const { + if (windowId == 0) { + return nullptr; + } + + auto iter = _windows.find(windowId); + if (iter != _windows.end()) { + return iter->second.get(); + } + return nullptr; +} + +ISystemWindow *SystemWindowManager::createWindow(const cc::ISystemWindowInfo &info) { + ISystemWindow *window = BasePlatform::getPlatform()->createNativeWindow(_nextWindowId, info.externalHandle); + if (window) { + window->createWindow(info.title.c_str(), info.x, info.y, info.width, info.height, info.flags); + _windows[_nextWindowId] = std::shared_ptr(window); + _nextWindowId++; + } + return window; +} + +ISystemWindow *SystemWindowManager::getWindowFromUIWindow(UIWindow *window) const { + for (const auto &iter : _windows) { + SystemWindow *sysWindow = static_cast(iter.second.get()); + UIWindow *nsWindow = sysWindow->getUIWindow(); + if (nsWindow == window) { + return sysWindow; + } + } + return nullptr; +} + +} // namespace cc diff --git a/cocos/platform/ios/modules/Vibrator.h b/cocos/platform/ios/modules/Vibrator.h new file mode 100644 index 0000000..d0ee615 --- /dev/null +++ b/cocos/platform/ios/modules/Vibrator.h @@ -0,0 +1,43 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IVibrator.h" + +namespace cc { + +class Vibrator : public IVibrator { +public: + /** + * Vibrate for the specified amount of time. + * If vibrate is not supported, then invoking this method has no effect. + * Some platforms limit to a maximum duration of 5 seconds. + * Duration is ignored on iOS due to API limitations. + * @param duration The duration in seconds. + */ + void vibrate(float duration) override; +}; + +} // namespace cc diff --git a/cocos/platform/ios/modules/Vibrator.mm b/cocos/platform/ios/modules/Vibrator.mm new file mode 100644 index 0000000..8152623 --- /dev/null +++ b/cocos/platform/ios/modules/Vibrator.mm @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/ios/modules/Vibrator.h" + +#include "base/Macros.h" + +namespace cc { + +void Vibrator::vibrate(float duration) { + CC_UNUSED_PARAM(duration); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/jni/JniCocosKeyCodeHandler.cpp b/cocos/platform/java/jni/JniCocosKeyCodeHandler.cpp new file mode 100644 index 0000000..052a0a2 --- /dev/null +++ b/cocos/platform/java/jni/JniCocosKeyCodeHandler.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/jni/JniHelper.h" +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include + #include +#elif CC_PLATFORM == CC_PLATFORM_OHOS + #include +#endif + +#include +#include "engine/EngineEvents.h" +#include "platform/java/jni/glue/JniNativeGlue.h" +#include "platform/java/modules/SystemWindow.h" + +namespace { +struct cc::KeyboardEvent keyboardEvent; + +// key values in web, refer to http://docs.cocos.com/creator/api/en/enums/KEY.html +#if CC_PLATFORM == CC_PLATFORM_ANDROID +ccstd::unordered_map keyCodeMap = { + {AKEYCODE_BACK, 6}, + {AKEYCODE_ENTER, 13}, + {AKEYCODE_MENU, 18}, + {AKEYCODE_DPAD_UP, 1003}, + {AKEYCODE_DPAD_DOWN, 1004}, + {AKEYCODE_DPAD_LEFT, 1000}, + {AKEYCODE_DPAD_RIGHT, 1001}, + {AKEYCODE_DPAD_CENTER, 1005}}; +#elif CC_PLATFORM == CC_PLATFORM_OHOS +ccstd::unordered_map keyCodeMap = {}; +#endif +//NOLINTNEXTLINE +static void dispatchKeyCodeEvent(int keyCode, cc::KeyboardEvent &event) { + if (keyCodeMap.count(keyCode) > 0) { + keyCode = keyCodeMap[keyCode]; + } else { + keyCode = 0; + } + event.windowId = cc::ISystemWindow::mainWindowId; + event.key = keyCode; + cc::events::Keyboard::broadcast(event); +} +} // namespace + +extern "C" { +//NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosKeyCodeHandler_handleKeyDown(JNIEnv *env, jobject obj, jint keyCode) { + keyboardEvent.action = cc::KeyboardEvent::Action::PRESS; + dispatchKeyCodeEvent(keyCode, keyboardEvent); +} + +//NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosKeyCodeHandler_handleKeyUp(JNIEnv *env, jobject obj, jint keyCode) { + keyboardEvent.action = cc::KeyboardEvent::Action::RELEASE; + dispatchKeyCodeEvent(keyCode, keyboardEvent); +} +} diff --git a/cocos/platform/java/jni/JniCocosOrientationHelper.cpp b/cocos/platform/java/jni/JniCocosOrientationHelper.cpp new file mode 100644 index 0000000..804be6d --- /dev/null +++ b/cocos/platform/java/jni/JniCocosOrientationHelper.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include +#include "engine/EngineEvents.h" +#include "platform/interfaces/modules/Device.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/glue/JniNativeGlue.h" +#include "platform/java/modules/SystemWindow.h" + +extern "C" { +// NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosOrientationHelper_nativeOnOrientationChanged(JNIEnv *env, jclass thiz, jint rotation) { + int orientation; + switch (rotation) { + case 0: // ROTATION_0 + orientation = (int)cc::Device::Orientation::PORTRAIT; + case 1: // ROTATION_90 + orientation = (int)cc::Device::Orientation::LANDSCAPE_RIGHT; + case 2: // ROTATION_180 + orientation = (int)cc::Device::Orientation::PORTRAIT_UPSIDE_DOWN; + case 3: // ROTATION_270 + orientation = (int)cc::Device::Orientation::LANDSCAPE_LEFT; + } + // run callbacks in game thread? + cc::events::Orientation::broadcast(orientation); +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosAbilitySlice_onOrientationChangedNative(JNIEnv *env, jobject obj, jint orientation, jint width, jint height) { //NOLINT JNI function name + static jint pOrientation = 0; + static jint pWidth = 0; + static jint pHeight = 0; + if (pOrientation != orientation || pWidth != width || pHeight != height) { + cc::WindowEvent ev; + // TODO(qgh):The windows ID needs to be passed down from the jave, which is currently using the main window. + ev.windowId = cc::ISystemWindow::mainWindowId; + ev.type = cc::WindowEvent::Type::SIZE_CHANGED; + ev.width = width; + ev.height = height; + cc::events::WindowEvent::broadcast(ev); + pOrientation = orientation; + pHeight = height; + pWidth = width; + } +} +} diff --git a/cocos/platform/java/jni/JniCocosTouchHandler.cpp b/cocos/platform/java/jni/JniCocosTouchHandler.cpp new file mode 100644 index 0000000..e67fa74 --- /dev/null +++ b/cocos/platform/java/jni/JniCocosTouchHandler.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include +#include "engine/EngineEvents.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/glue/JniNativeGlue.h" +#include "platform/java/modules/SystemWindow.h" +#include "platform/java/modules/SystemWindowManager.h" + +namespace { +cc::TouchEvent touchEvent; +} +extern "C" { +//NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosTouchHandler_handleActionDown(JNIEnv *env, jobject obj, jint id, + jfloat x, jfloat y) { + // TODO(qgh):The windows ID needs to be passed down from the jave, which is currently using the main window. + touchEvent.windowId = cc::ISystemWindow::mainWindowId; + touchEvent.type = cc::TouchEvent::Type::BEGAN; + touchEvent.touches.emplace_back(x, y, id); + cc::events::Touch::broadcast(touchEvent); + touchEvent.touches.clear(); +} + +//NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosTouchHandler_handleActionUp(JNIEnv *env, jobject obj, jint id, jfloat x, + jfloat y) { + touchEvent.windowId = cc::ISystemWindow::mainWindowId; + touchEvent.type = cc::TouchEvent::Type::ENDED; + touchEvent.touches.emplace_back(x, y, id); + cc::events::Touch::broadcast(touchEvent); + touchEvent.touches.clear(); +} + +//NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosTouchHandler_handleActionMove(JNIEnv *env, jobject obj, + jintArray ids, + jfloatArray xs, + jfloatArray ys) { + touchEvent.type = cc::TouchEvent::Type::MOVED; + touchEvent.windowId = cc::ISystemWindow::mainWindowId; + int size = env->GetArrayLength(ids); + jint id[size]; + jfloat x[size]; + jfloat y[size]; + + env->GetIntArrayRegion(ids, 0, size, id); + env->GetFloatArrayRegion(xs, 0, size, x); + env->GetFloatArrayRegion(ys, 0, size, y); + for (int i = 0; i < size; i++) { + touchEvent.touches.emplace_back(x[i], y[i], id[i]); + } + cc::events::Touch::broadcast(touchEvent); + touchEvent.touches.clear(); +} + +//NOLINTNEXTLINE +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosTouchHandler_handleActionCancel(JNIEnv *env, jobject obj, + jintArray ids, + jfloatArray xs, + jfloatArray ys) { + touchEvent.type = cc::TouchEvent::Type::CANCELLED; + touchEvent.windowId = cc::ISystemWindow::mainWindowId; + int size = env->GetArrayLength(ids); + jint id[size]; + jfloat x[size]; + jfloat y[size]; + + env->GetIntArrayRegion(ids, 0, size, id); + env->GetFloatArrayRegion(xs, 0, size, x); + env->GetFloatArrayRegion(ys, 0, size, y); + for (int i = 0; i < size; i++) { + touchEvent.touches.emplace_back(x[i], y[i], id[i]); + } + cc::events::Touch::broadcast(touchEvent); + touchEvent.touches.clear(); +} +} diff --git a/cocos/platform/java/jni/JniHelper.cpp b/cocos/platform/java/jni/JniHelper.cpp new file mode 100644 index 0000000..8e7eff2 --- /dev/null +++ b/cocos/platform/java/jni/JniHelper.cpp @@ -0,0 +1,447 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/jni/JniHelper.h" +#if ANDROID + #include +#else + #include "cocos/base/Log.h" +#endif +#include +#include + +#include "base/UTF8.h" + +#define CC_CACHE_CLASS_ID 0 + +#if ANDROID + #define LOG_TAG "JniHelper" + #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#else + #define LOGD(...) CC_LOG_DEBUG(__VA_ARGS__) + #define LOGE(...) CC_LOG_ERROR(__VA_ARGS__) +#endif + +namespace { +pthread_key_t g_key; // NOLINT(readability-identifier-naming) +class JClassWrapper final { +public: + explicit JClassWrapper(jclass kls) { + if (kls) { + _globalKls = static_cast(cc::JniHelper::getEnv()->NewGlobalRef(static_cast(kls))); + } + } + ~JClassWrapper() { + if (_globalKls) { + cc::JniHelper::getEnv()->DeleteGlobalRef(_globalKls); + } + _globalKls = nullptr; + } + jclass operator*() const { + return _globalKls; + } + +private: + jclass _globalKls = {}; +}; +#if CC_CACHE_CLASS_ID +ccstd::unordered_map _cachedJClasses; +#endif +}; // namespace + +jclass _getClassID(const char *className) { // NOLINT + if (nullptr == className) { + return nullptr; + } +#if CC_CACHE_CLASS_ID + auto it = _cachedJClasses.find(className); + if (it != _cachedJClasses.end()) { + return *it->second; + } +#endif + + JNIEnv *env = cc::JniHelper::getEnv(); + + jstring jstrClassName = env->NewStringUTF(className); + + auto *klassObj = static_cast(env->CallObjectMethod(cc::JniHelper::classloader, + cc::JniHelper::loadclassMethodMethodId, + jstrClassName)); + + if (nullptr == klassObj || env->ExceptionCheck()) { + LOGE("Classloader failed to find class of %s", className); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + env->ExceptionClear(); + klassObj = nullptr; + } + + ccDeleteLocalRef(env, jstrClassName); + // LOGE("1. delete 0x%p", jstrClassName); +#if CC_CACHE_CLASS_ID + if (klassObj) { + _cachedJClasses.emplace(className, klassObj); + } +#endif + return klassObj; +} + +void cbDetachCurrentThread(void * /*a*/) { + cc::JniHelper::getJavaVM()->DetachCurrentThread(); +} + +namespace cc { +jmethodID JniHelper::loadclassMethodMethodId = nullptr; +jobject JniHelper::classloader = nullptr; +std::function JniHelper::classloaderCallback = nullptr; +jobject JniHelper::sContext = nullptr; +JavaVM *JniHelper::sJavaVM = nullptr; + +JavaVM *JniHelper::getJavaVM() { + pthread_t thisthread = pthread_self(); + LOGD("JniHelper::getJavaVM(), pthread_self() = %ld", thisthread); + return JniHelper::sJavaVM; +} + +void JniHelper::init(JNIEnv *env, jobject context) { + env->GetJavaVM(&JniHelper::sJavaVM); + JniHelper::sContext = context; + + pthread_key_create(&g_key, cbDetachCurrentThread); + auto ok = JniHelper::setClassLoaderFrom(context); + CC_ASSERT(ok); +} + +void JniHelper::onDestroy() { + if (JniHelper::sJavaVM) { + if (JniHelper::sContext) { + cc::JniHelper::getEnv()->DeleteGlobalRef(JniHelper::sContext); + JniHelper::sContext = nullptr; + } + LOGD("JniHelper::onDestroy"); + } +} + +JNIEnv *JniHelper::cacheEnv() { + JavaVM *jvm = JniHelper::sJavaVM; + JNIEnv *env = nullptr; + // get jni environment + jint ret = jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_4); + + switch (ret) { + case JNI_OK: + // Success! + pthread_setspecific(g_key, env); + return env; + + case JNI_EDETACHED: + // Thread not attached +#if CC_PLATFORM == CC_PLATFORM_ANDROID + if (jvm->AttachCurrentThread(&env, nullptr) < 0) { +#else + if (jvm->AttachCurrentThread(reinterpret_cast(&env), nullptr) < 0) { +#endif + LOGE("Failed to get the environment using AttachCurrentThread()"); + + return nullptr; + } else { + // Success : Attached and obtained JNIEnv! + pthread_setspecific(g_key, env); + return env; + } + + case JNI_EVERSION: + // Cannot recover from this error + LOGE("JNI interface version 1.4 not supported"); + default: + LOGE("Failed to get the environment using GetEnv()"); + return nullptr; + } +} + +JNIEnv *JniHelper::getEnv() { + auto *env = static_cast(pthread_getspecific(g_key)); + if (env == nullptr) { + env = JniHelper::cacheEnv(); + } + return env; +} + +jobject JniHelper::getContext() { + return sContext; +} + +jobject JniHelper::getActivity() { + // TODO(cjh): In normal mode, sContext is Activity itself, but in surface-less mode, we need to + // returns nullptr. + return sContext; +} + +#if CC_PLATFORM == CC_PLATFORM_OHOS +bool JniHelper::setClassLoaderFrom(jobject contextInstance) { + if (!JniHelper::classloader) { + JniMethodInfo getclassloaderMethod; + if (!JniHelper::getMethodInfoDefaultClassLoader(getclassloaderMethod, + "ohos/app/AbilityContext", + "getClassloader", // typo ? + "()Ljava/lang/ClassLoader;")) { + return false; + } + + jobject klassLoader = cc::JniHelper::getEnv()->CallObjectMethod(contextInstance, + getclassloaderMethod.methodID); + + if (nullptr == klassLoader) { + return false; + } + + JniMethodInfo loadClass; + if (!JniHelper::getMethodInfoDefaultClassLoader(loadClass, + "java/lang/ClassLoader", + "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;")) { + return false; + } + + JniHelper::classloader = cc::JniHelper::getEnv()->NewGlobalRef(klassLoader); + JniHelper::loadclassMethodMethodId = loadClass.methodID; + } + + JniHelper::sContext = cc::JniHelper::getEnv()->NewGlobalRef(contextInstance); + if (JniHelper::classloaderCallback != nullptr) { + JniHelper::classloaderCallback(); + } + + return true; +} +#elif CC_PLATFORM == CC_PLATFORM_ANDROID +bool JniHelper::setClassLoaderFrom(jobject contextInstance) { + if (!JniHelper::classloader) { + JniMethodInfo getClassloaderMethod; + if (!JniHelper::getMethodInfoDefaultClassLoader(getClassloaderMethod, + "android/content/Context", + "getClassLoader", + "()Ljava/lang/ClassLoader;")) { + return false; + } + + jobject classLoader = cc::JniHelper::getEnv()->CallObjectMethod(contextInstance, + getClassloaderMethod.methodID); + + if (nullptr == classLoader) { + return false; + } + + JniMethodInfo loadClass; + if (!JniHelper::getMethodInfoDefaultClassLoader(loadClass, + "java/lang/ClassLoader", + "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;")) { + return false; + } + + JniHelper::classloader = cc::JniHelper::getEnv()->NewGlobalRef(classLoader); + JniHelper::loadclassMethodMethodId = loadClass.methodID; + } + + JniHelper::sContext = cc::JniHelper::getEnv()->NewGlobalRef(contextInstance); + if (JniHelper::classloaderCallback != nullptr) { + JniHelper::classloaderCallback(); + } + + return true; +} +#endif + +bool JniHelper::getStaticMethodInfo(JniMethodInfo &methodinfo, + const char *className, + const char *methodName, + const char *paramCode) { + if ((nullptr == className) || + (nullptr == methodName) || + (nullptr == paramCode)) { + return false; + } + + JNIEnv *env = JniHelper::getEnv(); + if (!env) { + LOGE("Failed to get JNIEnv"); + return false; + } + + jclass classID = _getClassID(className); + if (!classID) { + LOGE("Failed to find class %s", className); + env->ExceptionClear(); + return false; + } + + jmethodID methodID = env->GetStaticMethodID(classID, methodName, paramCode); + if (!methodID) { + LOGE("Failed to find static method id of %s", methodName); + env->ExceptionClear(); + return false; + } + + methodinfo.classID = classID; + methodinfo.env = env; + methodinfo.methodID = methodID; + return true; +} + +//NOLINTNEXTLINE +bool JniHelper::getMethodInfoDefaultClassLoader(JniMethodInfo &methodinfo, + const char *className, + const char *methodName, + const char *paramCode) { + if ((nullptr == className) || + (nullptr == methodName) || + (nullptr == paramCode)) { + return false; + } + + JNIEnv *env = JniHelper::getEnv(); + if (!env) { + return false; + } + + jclass classID = env->FindClass(className); + if (!classID) { + LOGE("Failed to find class %s", className); + env->ExceptionClear(); + return false; + } + + jmethodID methodID = env->GetMethodID(classID, methodName, paramCode); + if (!methodID) { + LOGE("Failed to find method id of %s", methodName); + env->ExceptionClear(); + return false; + } + + methodinfo.classID = classID; + methodinfo.env = env; + methodinfo.methodID = methodID; + + return true; +} + +bool JniHelper::getMethodInfo(JniMethodInfo &methodinfo, + const char *className, + const char *methodName, + const char *paramCode) { + if ((nullptr == className) || + (nullptr == methodName) || + (nullptr == paramCode)) { + return false; + } + + JNIEnv *env = JniHelper::getEnv(); + if (!env) { + return false; + } + + jclass classID = _getClassID(className); + if (!classID) { + LOGE("Failed to find class %s", className); + env->ExceptionClear(); + return false; + } + + jmethodID methodID = env->GetMethodID(classID, methodName, paramCode); + if (!methodID) { + LOGE("Failed to find method id of %s", methodName); + env->ExceptionClear(); + return false; + } + + methodinfo.classID = classID; + methodinfo.env = env; + methodinfo.methodID = methodID; + + return true; +} + +ccstd::string JniHelper::jstring2string(jstring jstr) { + if (jstr == nullptr) { + return ""; + } + + JNIEnv *env = JniHelper::getEnv(); + if (!env) { + return ""; + } + + ccstd::string strValue = cc::StringUtils::getStringUTFCharsJNI(env, jstr); + + return strValue; +} + +jstring JniHelper::convert(JniHelper::LocalRefMapType *localRefs, cc::JniMethodInfo *t, const char *x) { + jstring ret = nullptr; + if (x) { + ret = cc::StringUtils::newStringUTFJNI(t->env, x); + } + + (*localRefs)[t->env].push_back(ret); + return ret; +} + +jstring JniHelper::convert(JniHelper::LocalRefMapType *localRefs, cc::JniMethodInfo *t, const ccstd::string &x) { + return convert(localRefs, t, x.c_str()); +} + +jobject JniHelper::convert(JniHelper::LocalRefMapType *localRefs, cc::JniMethodInfo *t, const std::vector &x) { + jclass stringClass = _getClassID("java/lang/String"); + jobjectArray ret = t->env->NewObjectArray(x.size(), stringClass, nullptr); + for (auto i = 0; i < x.size(); i++) { + jstring jstr = cc::StringUtils::newStringUTFJNI(t->env, x[i]); + t->env->SetObjectArrayElement(ret, i, jstr); + t->env->DeleteLocalRef(jstr); + } + (*localRefs)[t->env].push_back(ret); + return ret; +} + +void JniHelper::deleteLocalRefs(JNIEnv *env, JniHelper::LocalRefMapType *localRefs) { + if (!env) { + return; + } + + for (const auto &ref : (*localRefs)[env]) { + ccDeleteLocalRef(env, ref); + // LOGE("2. delete 0x%p", ref); + } + (*localRefs)[env].clear(); +} + +void JniHelper::reportError(const ccstd::string &className, const ccstd::string &methodName, const ccstd::string &signature) { + LOGE("Failed to find static java method. Class name: %s, method name: %s, signature: %s ", className.c_str(), methodName.c_str(), signature.c_str()); +} + +} //namespace cc diff --git a/cocos/platform/java/jni/JniHelper.h b/cocos/platform/java/jni/JniHelper.h new file mode 100644 index 0000000..e69c5f0 --- /dev/null +++ b/cocos/platform/java/jni/JniHelper.h @@ -0,0 +1,556 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cc-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "base/Log.h" +#include "base/Macros.h" +#include "base/std/container/string.h" +#include "base/std/container/unordered_map.h" +#include "base/std/container/vector.h" +#include "math/Vec3.h" + +// The macro must be used this way to find the native method. The principle is not well understood. +#define JNI_METHOD2(CLASS2, FUNC2) Java_##CLASS2##_##FUNC2 +#define JNI_METHOD1(CLASS1, FUNC1) JNI_METHOD2(CLASS1, FUNC1) + +// ccDeleteLocalRef with classid produces warning?? +#if 0 + #define ccDeleteLocalRef(jenv, ref) \ + do { \ + jenv->DeleteLocalRef(ref); \ + CC_LOG_DEBUG("deleteLocalRef file: %s, func: %s, line: %d", __FILE__, __FUNCTION__, __LINE__); \ + } while (0) +#else + #define ccDeleteLocalRef(jenv, ref) jenv->DeleteLocalRef(ref); // NOLINT +#endif +#define CLEAR_EXCEPTON(env) \ + do { \ + if (env->ExceptionCheck()) { \ + env->ExceptionDescribe(); \ + env->ExceptionClear(); \ + } \ + } while (false) + +struct android_app; + +namespace cc { + +using JniMethodInfo = struct JniMethodInfo_ { // NOLINT(readability-identifier-naming) + JNIEnv *env; + jclass classID; + jmethodID methodID; +}; + +class CC_DLL JniHelper { +public: + using LocalRefMapType = ccstd::unordered_map>; + + static JavaVM *getJavaVM(); + static JNIEnv *getEnv(); + static jobject getActivity(); + static jobject getContext(); + + static void init(JNIEnv *env, jobject context); + static void onDestroy(); + + // NOLINTNEXTLINE + static bool getStaticMethodInfo(JniMethodInfo &methodInfo, + const char *className, + const char *methodName, + const char *paramCode); + + // NOLINTNEXTLINE + static bool getMethodInfo(JniMethodInfo &methodInfo, + const char *className, + const char *methodName, + const char *paramCode); + + static ccstd::string jstring2string(jstring str); + + static jmethodID loadclassMethodMethodId; + static jobject classloader; + static std::function classloaderCallback; + + template + static jobject newObject(const ccstd::string &className, Ts... xs) { + jobject ret = nullptr; + static const char *methodName = ""; + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")V"; + if (cc::JniHelper::getMethodInfo(t, className.c_str(), methodName, signature.c_str())) { + LocalRefMapType localRefs; + ret = t.env->NewObject(t.classID, t.methodID, convert(&localRefs, &t, xs)...); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + return ret; + } + + template + static void callObjectVoidMethod(jobject object, + const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")V"; + if (cc::JniHelper::getMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + t.env->CallVoidMethod(object, t.methodID, convert(&localRefs, &t, xs)...); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + CLEAR_EXCEPTON(t.env); + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + } + + template + static float callObjectFloatMethod(jobject object, + const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + float ret = 0.0F; + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")F"; + if (cc::JniHelper::getMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + ret = t.env->CallFloatMethod(object, t.methodID, convert(&localRefs, &t, xs)...); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + CLEAR_EXCEPTON(t.env); + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + return ret; + } + + template + static jlong callObjectLongMethod(jobject object, + const std::string &className, + const std::string &methodName, + Ts... xs) { + jlong ret = 0; + cc::JniMethodInfo t; + std::string signature = "(" + std::string(getJNISignature(xs...)) + ")J"; + if (cc::JniHelper::getMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + ret = t.env->CallLongMethod(object, t.methodID, convert(&localRefs, &t, xs)...); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + CLEAR_EXCEPTON(t.env); + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + return ret; + } + + template + static jbyteArray callObjectByteArrayMethod(jobject object, + const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + jbyteArray ret = nullptr; + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")[B"; + if (cc::JniHelper::getMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + ret = static_cast(t.env->CallObjectMethod(object, t.methodID, convert(&localRefs, &t, xs)...)); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + CLEAR_EXCEPTON(t.env); + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + return ret; + } + + template + static void callStaticVoidMethod(const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")V"; + if (cc::JniHelper::getStaticMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + t.env->CallStaticVoidMethod(t.classID, t.methodID, convert(&localRefs, &t, xs)...); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + CLEAR_EXCEPTON(t.env); + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + } + + template + static bool callStaticBooleanMethod(const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + jboolean jret = JNI_FALSE; + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")Z"; + if (cc::JniHelper::getStaticMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + jret = t.env->CallStaticBooleanMethod(t.classID, t.methodID, convert(&localRefs, &t, xs)...); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + CLEAR_EXCEPTON(t.env); + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + return (jret == JNI_TRUE); + } + + template + static int callStaticIntMethod(const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + jint ret = 0; + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")I"; + if (cc::JniHelper::getStaticMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + ret = t.env->CallStaticIntMethod(t.classID, t.methodID, convert(&localRefs, &t, xs)...); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + CLEAR_EXCEPTON(t.env); + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + return ret; + } + + template + static float callStaticFloatMethod(const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + jfloat ret = 0.0; + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")F"; + if (cc::JniHelper::getStaticMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + ret = t.env->CallStaticFloatMethod(t.classID, t.methodID, convert(&localRefs, &t, xs)...); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + CLEAR_EXCEPTON(t.env); + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + return ret; + } + + template + static float *callStaticFloatArrayMethod(const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + static float ret[32]; + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")[F"; + if (cc::JniHelper::getStaticMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + auto *array = static_cast(t.env->CallStaticObjectMethod(t.classID, t.methodID, convert(&localRefs, &t, xs)...)); + CLEAR_EXCEPTON(t.env); + jsize len = t.env->GetArrayLength(array); + if (len <= 32) { + jfloat *elems = t.env->GetFloatArrayElements(array, nullptr); + if (elems) { + memcpy(ret, elems, sizeof(float) * len); + t.env->ReleaseFloatArrayElements(array, elems, 0); + }; + } + CLEAR_EXCEPTON(t.env); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + ccDeleteLocalRef(t.env, array); + deleteLocalRefs(t.env, &localRefs); + return &ret[0]; + } + reportError(className, methodName, signature); + return nullptr; + } + + template + static Vec3 callStaticVec3Method(const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + Vec3 ret; + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")[F"; + if (cc::JniHelper::getStaticMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + auto *array = static_cast(t.env->CallStaticObjectMethod(t.classID, t.methodID, convert(&localRefs, &t, xs)...)); + CLEAR_EXCEPTON(t.env); + jsize len = t.env->GetArrayLength(array); + if (len == 3) { + jfloat *elems = t.env->GetFloatArrayElements(array, nullptr); + ret.x = elems[0]; + ret.y = elems[1]; + ret.z = elems[2]; + t.env->ReleaseFloatArrayElements(array, elems, 0); + } + CLEAR_EXCEPTON(t.env); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + ccDeleteLocalRef(t.env, array); + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + return ret; + } + + template + static double callStaticDoubleMethod(const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + jdouble ret = 0.0; + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")D"; + if (cc::JniHelper::getStaticMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + ret = t.env->CallStaticDoubleMethod(t.classID, t.methodID, convert(&localRefs, &t, xs)...); + CLEAR_EXCEPTON(t.env); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + return ret; + } + + template + static ccstd::string callStaticStringMethod(const ccstd::string &className, + const ccstd::string &methodName, + Ts... xs) { + ccstd::string ret; + + cc::JniMethodInfo t; + ccstd::string signature = "(" + ccstd::string(getJNISignature(xs...)) + ")Ljava/lang/String;"; + if (cc::JniHelper::getStaticMethodInfo(t, className.c_str(), methodName.c_str(), signature.c_str())) { + LocalRefMapType localRefs; + auto *jret = static_cast(t.env->CallStaticObjectMethod(t.classID, t.methodID, convert(&localRefs, &t, xs)...)); + CLEAR_EXCEPTON(t.env); + ret = cc::JniHelper::jstring2string(jret); + ccDeleteLocalRef(t.env, jret); +#ifndef __OHOS__ + ccDeleteLocalRef(t.env, t.classID); +#endif + deleteLocalRefs(t.env, &localRefs); + } else { + reportError(className, methodName, signature); + } + return ret; + } + static bool setClassLoaderFrom(jobject contextInstance); + +private: + static jobject sContext; + static JavaVM *sJavaVM; + + static JNIEnv *cacheEnv(); + // NOLINTNEXTLINE + static bool getMethodInfoDefaultClassLoader(JniMethodInfo &methodinfo, + const char *className, + const char *methodName, + const char *paramCode); + + static jstring convert(LocalRefMapType *localRefs, cc::JniMethodInfo *t, const char *x); + + static jstring convert(LocalRefMapType *localRefs, cc::JniMethodInfo *t, const ccstd::string &x); + + static jobject convert(LocalRefMapType *localRefs, cc::JniMethodInfo *t, const std::vector &x); + + template + static T convert(LocalRefMapType * /*localRefs*/, cc::JniMethodInfo * /*t*/, T x) { + return x; + } + + template + static jobject convert(LocalRefMapType *localRefs, cc::JniMethodInfo *t, std::pair data) { + jobject ret = nullptr; + +#define JNI_SET_TYPED_ARRAY(lowercase, camelCase) \ + j##lowercase##Array array = t->env->New##camelCase##Array(data.second); \ + t->env->Set##camelCase##ArrayRegion(array, 0, data.second, reinterpret_cast(data.first)); \ + if (array) { \ + (*localRefs)[t->env].push_back(array); \ + } \ + ret = static_cast(array); + + using U = typename std::remove_cv::type>::type; + + if (sizeof(U) == 1) { + JNI_SET_TYPED_ARRAY(byte, Byte) + } else if (sizeof(U) == 4 && std::is_integral::value) { + JNI_SET_TYPED_ARRAY(int, Int) + } else if (sizeof(U) == 8 && std::is_integral::value) { + JNI_SET_TYPED_ARRAY(long, Long); + } else if (sizeof(U) == 4 && std::is_floating_point::value) { + JNI_SET_TYPED_ARRAY(float, Float); + } else if (sizeof(U) == 8 && std::is_floating_point::value) { + JNI_SET_TYPED_ARRAY(double, Double); + } + +#undef JNI_SET_TYPED_ARRAY + return ret; + } + + template + static typename std::enable_if::value, jobject>::type convert(LocalRefMapType *localRefs, cc::JniMethodInfo *t, const std::vector &data) { + jobject ret = nullptr; + +#define JNI_SET_TYPED_ARRAY(lowercase, camelCase) \ + j##lowercase##Array array = t->env->New##camelCase##Array(data.size()); \ + t->env->Set##camelCase##ArrayRegion(array, 0, data.size(), reinterpret_cast(data.data())); \ + if (array) { \ + (*localRefs)[t->env].push_back(array); \ + } \ + ret = static_cast(array); + + if (sizeof(T) == 1) { + JNI_SET_TYPED_ARRAY(byte, Byte) + } else if (sizeof(T) == 4 && std::is_integral::value) { + JNI_SET_TYPED_ARRAY(int, Int) + } else if (sizeof(T) == 8 && std::is_integral::value) { + JNI_SET_TYPED_ARRAY(long, Long); + } else if (sizeof(T) == 4 && std::is_floating_point::value) { + JNI_SET_TYPED_ARRAY(float, Float); + } else if (sizeof(T) == 8 && std::is_floating_point::value) { + JNI_SET_TYPED_ARRAY(double, Double); + } + +#undef JNI_SET_TYPED_ARRAY + return ret; + } + + static void deleteLocalRefs(JNIEnv *env, LocalRefMapType *localRefs); + + static ccstd::string getJNISignature() { + return ""; + } + + static ccstd::string getJNISignature(bool /*unused*/) { + return "Z"; + } + + static ccstd::string getJNISignature(char /*unused*/) { + return "C"; + } + + static ccstd::string getJNISignature(unsigned char /*unused*/) { + return "B"; // same as jbyte + } + + static ccstd::string getJNISignature(int16_t /*unused*/) { + return "S"; + } + + static ccstd::string getJNISignature(int32_t /*unused*/) { + return "I"; + } + + static ccstd::string getJNISignature(int64_t /*unused*/) { + return "J"; + } + + static ccstd::string getJNISignature(float /*unused*/) { + return "F"; + } + + static ccstd::string getJNISignature(double /*unused*/) { + return "D"; + } + + static ccstd::string getJNISignature(jbyteArray /*unused*/) { + return "[B"; + } + + static ccstd::string getJNISignature(jintArray /*unused*/) { + return "[I"; + } + + static ccstd::string getJNISignature(const char * /*unused*/) { + return "Ljava/lang/String;"; + } + + static ccstd::string getJNISignature(const ccstd::string & /*unused*/) { + return "Ljava/lang/String;"; + } + + template + static ccstd::string getJNISignature(T x) { + // This template should never be instantiated + static_assert(sizeof(x) == 0, "Unsupported argument type"); + return ""; + } + + template + static std::string getJNISignature(std::pair /*x*/) { + typename std::remove_pointer::type>::type m; + return std::string("[") + getJNISignature(m); + } + + template + static std::string getJNISignature(const std::vector & /*x*/) { + T m; + return std::string("[") + getJNISignature(m); + } + + template + static ccstd::string getJNISignature(T x, Ts... xs) { + return getJNISignature(x) + getJNISignature(xs...); + } + + static void reportError(const ccstd::string &className, const ccstd::string &methodName, const ccstd::string &signature); +}; + +} // namespace cc diff --git a/cocos/platform/java/jni/JniImp.cpp b/cocos/platform/java/jni/JniImp.cpp new file mode 100644 index 0000000..cc37a74 --- /dev/null +++ b/cocos/platform/java/jni/JniImp.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "JniImp.h" + +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include +#else + #include +#endif + +#include +#include "JniHelper.h" +#include "cocos/audio/include/AudioEngine.h" + +#ifndef JCLS_HELPER + #define JCLS_HELPER "com/cocos/lib/CocosHelper" +#endif + +#ifndef JCLS_SENSOR + #define JCLS_SENSOR "com/cocos/lib/CocosSensorHandler" +#endif + +#ifndef COM_AUDIOFOCUS_CLASS_NAME + #define COM_AUDIOFOCUS_CLASS_NAME com_cocos_lib_CocosAudioFocusManager +#endif +#define JNI_AUDIO(FUNC) JNI_METHOD1(COM_AUDIOFOCUS_CLASS_NAME, FUNC) + + +/*********************************************************** + * Functions invoke from cpp to Java. + ***********************************************************/ +namespace cc { +ccstd::string getObbFilePathJNI() { + return JniHelper::callStaticStringMethod(JCLS_HELPER, "getObbFilePath"); +} + +int getObbAssetFileDescriptorJNI(const ccstd::string &path, int64_t *startOffset, int64_t *size) { + JniMethodInfo methodInfo; + int fd = 0; + + if (JniHelper::getStaticMethodInfo(methodInfo, JCLS_HELPER, "getObbAssetFileDescriptor", "(Ljava/lang/String;)[J")) { + jstring stringArg = methodInfo.env->NewStringUTF(path.c_str()); + auto *newArray = static_cast(methodInfo.env->CallStaticObjectMethod(methodInfo.classID, methodInfo.methodID, stringArg)); + jsize theArrayLen = methodInfo.env->GetArrayLength(newArray); + + if (3 == theArrayLen) { + jboolean copy = JNI_FALSE; + jlong *array = methodInfo.env->GetLongArrayElements(newArray, ©); + fd = static_cast(array[0]); + *startOffset = array[1]; + *size = array[2]; + methodInfo.env->ReleaseLongArrayElements(newArray, array, 0); + } + + ccDeleteLocalRef(methodInfo.env, methodInfo.classID); + ccDeleteLocalRef(methodInfo.env, stringArg); + } + + return fd; +} + +ccstd::string getCurrentLanguageJNI() { + return JniHelper::callStaticStringMethod(JCLS_HELPER, "getCurrentLanguage"); +} + +ccstd::string getCurrentLanguageCodeJNI() { + return JniHelper::callStaticStringMethod(JCLS_HELPER, "getCurrentLanguageCode"); +} + +ccstd::string getSystemVersionJNI() { + return JniHelper::callStaticStringMethod(JCLS_HELPER, "getSystemVersion"); +} + +bool openURLJNI(const ccstd::string &url) { + return JniHelper::callStaticBooleanMethod(JCLS_HELPER, "openURL", url); +} + +void copyTextToClipboardJNI(const ccstd::string &text) { + JniHelper::callStaticVoidMethod(JCLS_HELPER, "copyTextToClipboard", text); +} + +ccstd::string getDeviceModelJNI() { + return JniHelper::callStaticStringMethod(JCLS_HELPER, "getDeviceModel"); +} + +int getDPIJNI() { + return JniHelper::callStaticIntMethod(JCLS_HELPER, "getDPI"); +} + +void setVibrateJNI(float duration) { + JniHelper::callStaticVoidMethod(JCLS_HELPER, "vibrate", duration); +} + +void setKeepScreenOnJNI(bool isEnabled) { + return JniHelper::callStaticVoidMethod(JCLS_HELPER, "setKeepScreenOn", isEnabled); +} + +void finishActivity() { + JniHelper::callStaticVoidMethod(JCLS_HELPER, "finishActivity"); +} +int getNetworkTypeJNI() { + return JniHelper::callStaticIntMethod(JCLS_HELPER, "getNetworkType"); +} + +float *getSafeAreaEdgeJNI() { + return JniHelper::callStaticFloatArrayMethod(JCLS_HELPER, "getSafeArea"); +} + +int getDeviceRotationJNI() { + return JniHelper::callStaticIntMethod(JCLS_HELPER, "getDeviceRotation"); +} + +float getBatteryLevelJNI() { + return JniHelper::callStaticFloatMethod(JCLS_HELPER, "getBatteryLevel"); +} + +void flushTasksOnGameThreadJNI() { + JniHelper::callStaticVoidMethod(JCLS_HELPER, + "flushTasksOnGameThread"); +} + +void flushTasksOnGameThreadAtForegroundJNI() { + JniHelper::callStaticVoidMethod(JCLS_HELPER, + "flushTasksOnGameThreadAtForeground"); +} + +void setAccelerometerEnabledJNI(bool isEnabled) { + JniHelper::callStaticVoidMethod(JCLS_SENSOR, "setAccelerometerEnabled", isEnabled); +} + +void setAccelerometerIntervalJNI(float interval) { + JniHelper::callStaticVoidMethod(JCLS_SENSOR, "setAccelerometerInterval", interval); +} + +float *getDeviceMotionValueJNI() { + return JniHelper::callStaticFloatArrayMethod(JCLS_SENSOR, "getDeviceMotionValue"); +} + +bool getSupportHPE() { + return JniHelper::callStaticBooleanMethod(JCLS_HELPER, "supportHPE"); +} + +} // namespace cc +extern "C" { +JNIEXPORT void JNICALL JNI_AUDIO(nativeSetAudioVolumeFactor)(JNIEnv * /*env*/, jclass /* thiz*/, jfloat volumeFactor) { +#if CC_USE_AUDIO + cc::AudioEngine::setVolumeFactor(volumeFactor); +#endif +} +} diff --git a/cocos/platform/java/jni/JniImp.h b/cocos/platform/java/jni/JniImp.h new file mode 100644 index 0000000..8176b7b --- /dev/null +++ b/cocos/platform/java/jni/JniImp.h @@ -0,0 +1,55 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/string.h" + +namespace cc { +ccstd::string getObbFilePathJNI(); +int getObbAssetFileDescriptorJNI(const ccstd::string &path, int64_t *startOffset, int64_t *size); +ccstd::string getCurrentLanguageJNI(); +ccstd::string getCurrentLanguageCodeJNI(); +ccstd::string getSystemVersionJNI(); +bool openURLJNI(const ccstd::string &url); +void copyTextToClipboardJNI(const ccstd::string &text); +ccstd::string getDeviceModelJNI(); +int getDPIJNI(); +void setVibrateJNI(float duration); +void setKeepScreenOnJNI(bool isEnabled); +int getNetworkTypeJNI(); +float *getSafeAreaEdgeJNI(); +int getDeviceRotationJNI(); +float getBatteryLevelJNI(); +void flushTasksOnGameThreadJNI(); +void flushTasksOnGameThreadAtForegroundJNI(); +void setAccelerometerEnabledJNI(bool isEnabled); +void setAccelerometerIntervalJNI(float interval); +float *getDeviceMotionValueJNI(); +void finishActivity(); +/** + * support for High Performance Emulator + */ +bool getSupportHPE(); +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/jni/glue/JniNativeGlue.cpp b/cocos/platform/java/jni/glue/JniNativeGlue.cpp new file mode 100644 index 0000000..8e1df8d --- /dev/null +++ b/cocos/platform/java/jni/glue/JniNativeGlue.cpp @@ -0,0 +1,298 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/jni/glue/JniNativeGlue.h" +#include +#include +#include "application/ApplicationManager.h" +#include "engine/EngineEvents.h" +#include "platform/BasePlatform.h" +#include "platform/java/jni/JniImp.h" +#include "platform/java/jni/glue/MessagePipe.h" +#include "platform/java/jni/log.h" +#include "platform/java/modules/SystemWindow.h" +#include "platform/java/modules/SystemWindowManager.h" + +namespace cc { +JniNativeGlue::~JniNativeGlue() = default; + +JniNativeGlue* JniNativeGlue::getInstance() { + static JniNativeGlue jniNativeGlue; + return &jniNativeGlue; +} + +void JniNativeGlue::start(int argc, const char** argv) { + _messagePipe = std::make_unique(); + + BasePlatform* platform = cc::BasePlatform::getPlatform(); + if (platform->init()) { + LOGV("Platform initialization failed"); + } + platform->run(argc, argv); +} + +void JniNativeGlue::setWindowHandle(NativeWindowType* window) { + if (_pendingWindow) { + writeCommandSync(JniCommand::JNI_CMD_TERM_WINDOW); + } + _pendingWindow = window; + if (window) { + writeCommandSync(JniCommand::JNI_CMD_INIT_WINDOW); + } +} + +void JniNativeGlue::setActivityGetter(std::function getter) { + _activityGetter = std::move(getter); +} + +void* JniNativeGlue::getActivity() { + return _activityGetter ? _activityGetter() : nullptr; +} + +void JniNativeGlue::setEnvGetter(std::function getter) { + _envGetter = std::move(getter); +} + +void* JniNativeGlue::getEnv() { + return _envGetter ? _envGetter() : nullptr; +} + +void JniNativeGlue::setResourceManager(ResourceManagerType* resourceManager) { + _resourceManager = resourceManager; +} + +ResourceManagerType* JniNativeGlue::getResourceManager() { + return _resourceManager; +} + +NativeWindowType* JniNativeGlue::getWindowHandle() { + return _window; +} + +void JniNativeGlue::setSdkVersion(int sdkVersion) { + _sdkVersion = sdkVersion; +} + +int JniNativeGlue::getSdkVersion() const { + return _sdkVersion; +} + +void JniNativeGlue::setObbPath(const std::string& path) { + _obbPath = path; +} + +std::string JniNativeGlue::getObbPath() const { + return _obbPath; +} + +void JniNativeGlue::setRunning(bool isRunning) { + _threadPromise.set_value(); + _running = isRunning; +} + +void JniNativeGlue::waitRunning() { + _threadPromise.get_future().get(); +} + +bool JniNativeGlue::isRunning() const { + return _running; +} + +void JniNativeGlue::flushTasksOnGameThread() const { + // Handle java events send by UI thread. Input events are handled here too. + + flushTasksOnGameThreadJNI(); + if (_animating) { + flushTasksOnGameThreadAtForegroundJNI(); + } +} + +void JniNativeGlue::writeCommandAsync(JniCommand cmd) { + CommandMsg msg{.cmd = cmd, .callback = nullptr}; + _messagePipe->writeCommand(&msg, sizeof(msg)); +} + +void JniNativeGlue::writeCommandSync(JniCommand cmd) { + std::promise fu; + CommandMsg msg{.cmd = cmd, .callback = [&fu]() { + fu.set_value(); + }}; + _messagePipe->writeCommand(&msg, sizeof(msg)); + fu.get_future().get(); +} + +int JniNativeGlue::readCommand(CommandMsg* msg) { + return _messagePipe->readCommand(msg, sizeof(CommandMsg)); +} + +int JniNativeGlue::readCommandWithTimeout(CommandMsg* cmd, int delayMS) { + return _messagePipe->readCommandWithTimeout(cmd, sizeof(CommandMsg), delayMS); +} + +bool JniNativeGlue::isPause() const { + if (!_animating) { + return true; + } + if (_appState == JniCommand::JNI_CMD_PAUSE) { + return true; + } + return false; +} + +void JniNativeGlue::onPause() { + writeCommandAsync(JniCommand::JNI_CMD_PAUSE); +} + +void JniNativeGlue::onResume() { + writeCommandAsync(JniCommand::JNI_CMD_RESUME); +} + +void JniNativeGlue::onLowMemory() { + writeCommandAsync(JniCommand::JNI_CMD_LOW_MEMORY); +} + +void JniNativeGlue::execCommand() { + static CommandMsg msg; + static bool runInLowRate{false}; + runInLowRate = !_animating || JniCommand::JNI_CMD_PAUSE == _appState; + + if (readCommandWithTimeout(&msg, runInLowRate ? 50 : 0) > 0) { + preExecCmd(msg.cmd); + engineHandleCmd(msg.cmd); + postExecCmd(msg.cmd); + if (msg.callback) { + msg.callback(); + } + } +} + +void JniNativeGlue::preExecCmd(JniCommand cmd) { + switch (cmd) { + case JniCommand::JNI_CMD_INIT_WINDOW: { + LOGV("JNI_CMD_INIT_WINDOW"); + _animating = true; + _window = _pendingWindow; + } break; + case JniCommand::JNI_CMD_TERM_WINDOW: + LOGV("JNI_CMD_TERM_WINDOW"); + _animating = false; + break; + case JniCommand::JNI_CMD_RESUME: + LOGV("JNI_CMD_RESUME"); + _appState = cmd; + break; + case JniCommand::JNI_CMD_PAUSE: + LOGV("JNI_CMD_PAUSE"); + _appState = cmd; + break; + default: + break; + } +} + +void JniNativeGlue::engineHandleCmd(JniCommand cmd) { + static bool isWindowInitialized = false; + // Handle CMD here if needed. + switch (cmd) { + case JniCommand::JNI_CMD_INIT_WINDOW: { + if (isWindowInitialized) { + return; + } + isWindowInitialized = true; + // cc::CustomEvent event; + // event.name = EVENT_RECREATE_WINDOW; + // event.args->ptrVal = reinterpret_cast(getWindowHandle()); + ISystemWindowInfo info; + info.width = getWidth(); + info.height = getHeight(); + info.externalHandle = getWindowHandle(); + BasePlatform* platform = cc::BasePlatform::getPlatform(); + auto* windowMgr = platform->getInterface(); + CC_ASSERT(windowMgr != nullptr); + windowMgr->createWindow(info); + events::WindowRecreated::broadcast(ISystemWindow::mainWindowId); + } break; + case JniCommand::JNI_CMD_TERM_WINDOW: { + events::WindowDestroy::broadcast(ISystemWindow::mainWindowId); + } break; + case JniCommand::JNI_CMD_RESUME: { + events::WindowChanged::broadcast(WindowEvent::Type::SHOW); + } break; + case JniCommand::JNI_CMD_PAUSE: { + events::WindowChanged::broadcast(WindowEvent::Type::HIDDEN); + } break; + case JniCommand::JNI_CMD_DESTROY: { + LOGV("APP_CMD_DESTROY"); + events::WindowChanged::broadcast(WindowEvent::Type::CLOSE); + setRunning(false); + } break; + case JniCommand::JNI_CMD_LOW_MEMORY: { + events::LowMemory::broadcast(); + break; + } + default: + break; + } +} + +void JniNativeGlue::postExecCmd(JniCommand cmd) { + switch (cmd) { + case JniCommand::JNI_CMD_TERM_WINDOW: { +#if CC_PLATFORM == CC_PLATFORM_ANDROID + if (_window) { + ANativeWindow_release(_window); + } +#endif + _window = nullptr; + } break; + default: + break; + } +} + +int32_t JniNativeGlue::getWidth() const { + int32_t width = 0; + if (_window) { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + width = ANativeWindow_getWidth(_window); +#elif (CC_PLATFORM == CC_PLATFORM_OHOS) + width = NativeLayerHandle(_window, NativeLayerOps::GET_WIDTH); +#endif + } + return width; +} + +int32_t JniNativeGlue::getHeight() const { + int32_t height = 0; + if (_window) { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + height = ANativeWindow_getHeight(_window); +#elif (CC_PLATFORM == CC_PLATFORM_OHOS) + height = NativeLayerHandle(_window, NativeLayerOps::GET_HEIGHT); +#endif + } + return height; +} + +} // namespace cc diff --git a/cocos/platform/java/jni/glue/JniNativeGlue.h b/cocos/platform/java/jni/glue/JniNativeGlue.h new file mode 100644 index 0000000..cba7c2a --- /dev/null +++ b/cocos/platform/java/jni/glue/JniNativeGlue.h @@ -0,0 +1,142 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include "base/Macros.h" +#include "platform/java/jni/glue/MessagePipe.h" + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + #include + #include + +using ResourceManagerType = AAssetManager; +using NativeWindowType = ANativeWindow; + +#elif (CC_PLATFORM == CC_PLATFORM_OHOS) + #include + #include + +using ResourceManagerType = ResourceManager; +using NativeWindowType = NativeLayer; +#endif + +using NativeActivity = void*; //jobject +using NativeEnv = void*; //jnienv + +namespace cc { + +class IEventDispatch; +class TouchEvent; + +class CC_DLL JniNativeGlue { +public: + enum class JniCommand { + JNI_CMD_TERM_WINDOW = 0, + JNI_CMD_INIT_WINDOW, + JNI_CMD_RESUME, + JNI_CMD_PAUSE, + JNI_CMD_DESTROY, + JNI_CMD_LOW_MEMORY, + JNI_CMD_UNKNOW, + }; + virtual ~JniNativeGlue(); + static JniNativeGlue* getInstance(); + + virtual void start(int argc, const char** argv); + + void setWindowHandle(NativeWindowType* window); + NativeWindowType* getWindowHandle(); + + void setActivityGetter(std::function); + void* getActivity(); + + void setEnvGetter(std::function); + void* getEnv(); + + void setResourceManager(ResourceManagerType* resourceManager); + ResourceManagerType* getResourceManager(); + + void setSdkVersion(int sdkVersion); + int getSdkVersion() const; + + void setObbPath(const std::string& path); + std::string getObbPath() const; + + bool isRunning() const; + void setRunning(bool isRunning); + void waitRunning(); + + void flushTasksOnGameThread() const; + + struct CommandMsg { + JniCommand cmd; + std::function callback; + }; + void writeCommandAsync(JniCommand cmd); + void writeCommandSync(JniCommand cmd); + int readCommand(CommandMsg* msg); + int readCommandWithTimeout(CommandMsg* cmd, int delayMS); + + void onPause(); + void onResume(); + void onLowMemory(); + + bool isPause() const; + + void execCommand(); + + int32_t getWidth() const; + int32_t getHeight() const; + + bool isAnimating() const { return _animating; } + +private: + void preExecCmd(JniCommand cmd); + void engineHandleCmd(JniCommand cmd); + void postExecCmd(JniCommand cmd); + + bool _running{false}; + int _sdkVersion{0}; + bool _animating{false}; + + std::promise _threadPromise; + std::string _obbPath; + ResourceManagerType* _resourceManager{nullptr}; + NativeWindowType* _window{nullptr}; + NativeWindowType* _pendingWindow{nullptr}; + JniCommand _appState{JniCommand::JNI_CMD_UNKNOW}; + IEventDispatch* _eventDispatcher{nullptr}; + std::unique_ptr _messagePipe{nullptr}; + + std::function _envGetter; + std::function _activityGetter; +}; + +} // namespace cc + +#define JNI_NATIVE_GLUE() cc::JniNativeGlue::getInstance() diff --git a/cocos/platform/java/jni/glue/MessagePipe.cpp b/cocos/platform/java/jni/glue/MessagePipe.cpp new file mode 100644 index 0000000..b5d5678 --- /dev/null +++ b/cocos/platform/java/jni/glue/MessagePipe.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/jni/glue/MessagePipe.h" +#include +#include +#include "platform/java/jni/log.h" +//#include +//#include "platform/android/jni/JniCocosActivity.h" + +namespace cc { +MessagePipe::MessagePipe() { + int messagePipe[2] = {0}; + if (pipe(messagePipe)) { + LOGV("Can not create pipe: %s", strerror(errno)); + } + + _pipeRead = messagePipe[0]; + _pipeWrite = messagePipe[1]; + + if (fcntl(_pipeRead, F_SETFL, O_NONBLOCK) < 0) { + LOGV("Can not make pipe read to non blocking mode."); + } +} + +MessagePipe::~MessagePipe() { + close(_pipeRead); + close(_pipeWrite); +} + +void MessagePipe::writeCommand(int8_t cmd) const { + write(_pipeWrite, &cmd, sizeof(cmd)); +} + +int MessagePipe::readCommand(int8_t &cmd) const { + return read(_pipeRead, &cmd, sizeof(cmd)); +} + +int MessagePipe::readCommandWithTimeout(void *msg, int32_t size, int delayMS) { + if (delayMS > 0) { + static fd_set fdSet; + static timeval timeout; + + timeout = {delayMS / 1000, (delayMS % 1000) * 1000}; + FD_ZERO(&fdSet); + FD_SET(_pipeRead, &fdSet); + + auto ret = select(_pipeRead + 1, &fdSet, nullptr, nullptr, &timeout); + if (ret < 0) { + LOGV("failed to run select(..): %s\n", strerror(errno)); + return ret; + } + + if (ret == 0) { + return 0; + } + } + return readCommand(msg, size); +} + +void MessagePipe::writeCommand(void *msg, int32_t size) const { + write(_pipeWrite, msg, size); +} + +int MessagePipe::readCommand(void *msg, int32_t size) const { + return read(_pipeRead, msg, size); +} +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/jni/glue/MessagePipe.h b/cocos/platform/java/jni/glue/MessagePipe.h new file mode 100644 index 0000000..2b4b29e --- /dev/null +++ b/cocos/platform/java/jni/glue/MessagePipe.h @@ -0,0 +1,48 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include "base/Macros.h" + +namespace cc { + +class CC_DLL MessagePipe { +public: + MessagePipe(); + ~MessagePipe(); + + void writeCommand(int8_t cmd) const; + int readCommand(int8_t &cmd) const; + void writeCommand(void *msg, int32_t size) const; + int readCommand(void *msg, int32_t size) const; + int readCommandWithTimeout(void *msg, int32_t size, int delayMS); + +private: + int _pipeRead = 0; + int _pipeWrite = 0; +}; + +} // namespace cc diff --git a/cocos/platform/java/jni/log.h b/cocos/platform/java/jni/log.h new file mode 100644 index 0000000..2abdb48 --- /dev/null +++ b/cocos/platform/java/jni/log.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#define JNI_IMP_LOG_TAG "JniImp" + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + #include + #define LOGV(...) __android_log_print(ANDROID_LOG_INFO, "CocosActivity JNI", __VA_ARGS__) + #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, JNI_IMP_LOG_TAG, __VA_ARGS__) +#elif (CC_PLATFORM == CC_PLATFORM_OHOS) + #include + #define LOGV(...) HILOG_INFO(LOG_APP, __VA_ARGS__) + #define LOGD(...) +#endif \ No newline at end of file diff --git a/cocos/platform/java/modules/Accelerometer.cpp b/cocos/platform/java/modules/Accelerometer.cpp new file mode 100644 index 0000000..62665af --- /dev/null +++ b/cocos/platform/java/modules/Accelerometer.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/modules/Accelerometer.h" +#include "platform/java/jni/JniImp.h" + +namespace cc { + +void Accelerometer::setAccelerometerEnabled(bool isEnabled) { + setAccelerometerEnabledJNI(isEnabled); +} + +void Accelerometer::setAccelerometerInterval(float interval) { + setAccelerometerIntervalJNI(interval); +} + +const Accelerometer::MotionValue &Accelerometer::getDeviceMotionValue() { + static MotionValue motionValue; + float *v = getDeviceMotionValueJNI(); + + if (v) { + motionValue.accelerationIncludingGravityX = v[0]; + motionValue.accelerationIncludingGravityY = v[1]; + motionValue.accelerationIncludingGravityZ = v[2]; + + motionValue.accelerationX = v[3]; + motionValue.accelerationY = v[4]; + motionValue.accelerationZ = v[5]; + + motionValue.rotationRateAlpha = v[6]; + motionValue.rotationRateBeta = v[7]; + motionValue.rotationRateGamma = v[8]; + } else { + memset(&motionValue, 0, sizeof(motionValue)); + } + return motionValue; +} + +} // namespace cc diff --git a/cocos/platform/java/modules/Accelerometer.h b/cocos/platform/java/modules/Accelerometer.h new file mode 100644 index 0000000..727914c --- /dev/null +++ b/cocos/platform/java/modules/Accelerometer.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IAccelerometer.h" + +namespace cc { + +class Accelerometer : public IAccelerometer { +public: + /** + * To enable or disable accelerometer. + */ + void setAccelerometerEnabled(bool isEnabled) override; + + /** + * Sets the interval of accelerometer. + */ + void setAccelerometerInterval(float interval) override; + + /** + * Gets the motion value of current device. + */ + const MotionValue &getDeviceMotionValue() override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/modules/Battery.cpp b/cocos/platform/java/modules/Battery.cpp new file mode 100644 index 0000000..9e7acb8 --- /dev/null +++ b/cocos/platform/java/modules/Battery.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/modules/Battery.h" +#include "platform/java/jni/JniImp.h" + +namespace cc { + +float Battery::getBatteryLevel() const { + return getBatteryLevelJNI(); +} + +} // namespace cc diff --git a/cocos/platform/java/modules/Battery.h b/cocos/platform/java/modules/Battery.h new file mode 100644 index 0000000..d14def1 --- /dev/null +++ b/cocos/platform/java/modules/Battery.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IBattery.h" + +namespace cc { + +class Battery : public IBattery { +public: + float getBatteryLevel() const override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/modules/CanvasRenderingContext2DDelegate.cpp b/cocos/platform/java/modules/CanvasRenderingContext2DDelegate.cpp new file mode 100644 index 0000000..9b362fc --- /dev/null +++ b/cocos/platform/java/modules/CanvasRenderingContext2DDelegate.cpp @@ -0,0 +1,343 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/modules/CanvasRenderingContext2DDelegate.h" + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + #include +#else + #include +#endif + +namespace { + +} // namespace + +#define CLAMP(V, HI) std::min((V), (HI)) + +namespace cc { +CanvasRenderingContext2DDelegate::CanvasRenderingContext2DDelegate() { + jobject obj = JniHelper::newObject(JCLS_CANVASIMPL); + _obj = JniHelper::getEnv()->NewGlobalRef(obj); + ccDeleteLocalRef(JniHelper::getEnv(), obj); +} + +CanvasRenderingContext2DDelegate::~CanvasRenderingContext2DDelegate() { + JniHelper::getEnv()->DeleteGlobalRef(_obj); +} + +void CanvasRenderingContext2DDelegate::recreateBuffer(float w, float h) { + _bufferWidth = w; + _bufferHeight = h; + if (_bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "recreateBuffer", w, h); +} + +void CanvasRenderingContext2DDelegate::beginPath() { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "beginPath"); +} + +void CanvasRenderingContext2DDelegate::closePath() { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "closePath"); +} + +void CanvasRenderingContext2DDelegate::moveTo(float x, float y) { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "moveTo", x, y); +} + +void CanvasRenderingContext2DDelegate::lineTo(float x, float y) { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "lineTo", x, y); +} + +void CanvasRenderingContext2DDelegate::stroke() { + if (_bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "stroke"); +} + +void CanvasRenderingContext2DDelegate::fill() { + if (_bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "fill"); +} + +void CanvasRenderingContext2DDelegate::rect(float x, float y, float w, float h) { + if (_bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "rect", x, y, w, h); +} + +void CanvasRenderingContext2DDelegate::saveContext() { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "saveContext"); +} + +void CanvasRenderingContext2DDelegate::restoreContext() { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "restoreContext"); +} + +void CanvasRenderingContext2DDelegate::clearRect(float x, float y, float w, float h) { + if (_bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + if (x >= _bufferWidth || y >= _bufferHeight) { + return; + } + if (x + w > _bufferWidth) { + w = _bufferWidth - x; + } + if (y + h > _bufferHeight) { + h = _bufferHeight - y; + } + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "clearRect", x, y, w, h); +} + +void CanvasRenderingContext2DDelegate::fillRect(float x, float y, float w, float h) { + if (_bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + if (x >= _bufferWidth || y >= _bufferHeight) { + return; + } + if (x + w > _bufferWidth) { + w = _bufferWidth - x; + } + if (y + h > _bufferHeight) { + h = _bufferHeight - y; + } + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "fillRect", x, y, w, h); +} + +void CanvasRenderingContext2DDelegate::fillText(const ccstd::string &text, float x, float y, float maxWidth) { + if (text.empty() || _bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "fillText", text, x, y, maxWidth); +} + +void CanvasRenderingContext2DDelegate::strokeText(const ccstd::string &text, float x, float y, float maxWidth) { + if (text.empty() || _bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "strokeText", text, x, y, maxWidth); +} + +CanvasRenderingContext2DDelegate::Size CanvasRenderingContext2DDelegate::measureText(const ccstd::string &text) { + if (text.empty()) { + return ccstd::array{0.0F, 0.0F}; + } + float measureText1 = JniHelper::callObjectFloatMethod(_obj, JCLS_CANVASIMPL, "measureText", text); + Size size{measureText1, 0.0F}; + return size; +} + +void CanvasRenderingContext2DDelegate::updateFont(const ccstd::string &fontName, float fontSize, bool bold, bool italic, bool oblique, bool smallCaps) { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "updateFont", fontName, fontSize, bold, italic, oblique, smallCaps); +} + +void CanvasRenderingContext2DDelegate::setLineCap(const ccstd::string &lineCap) { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setLineCap", lineCap); +} + +void CanvasRenderingContext2DDelegate::setLineJoin(const ccstd::string &lineJoin) { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setLineJoin", lineJoin); +} + +void CanvasRenderingContext2DDelegate::setTextAlign(TextAlign align) { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setTextAlign", static_cast(align)); +} + +void CanvasRenderingContext2DDelegate::setTextBaseline(TextBaseline baseline) { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setTextBaseline", static_cast(baseline)); +} + +void CanvasRenderingContext2DDelegate::setFillStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setFillStyle", + static_cast(r), + static_cast(g), + static_cast(b), + static_cast(a)); +} + +void CanvasRenderingContext2DDelegate::setStrokeStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setStrokeStyle", + static_cast(r), + static_cast(g), + static_cast(b), + static_cast(a)); +} + +void CanvasRenderingContext2DDelegate::setLineWidth(float lineWidth) { + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setLineWidth", lineWidth); +} + +const cc::Data &CanvasRenderingContext2DDelegate::getDataRef() const { + return _data; +} + +void CanvasRenderingContext2DDelegate::fillImageData(const Data &imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) { + if (_bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + + auto *arr = JniHelper::getEnv()->NewIntArray(imageData.getSize() / 4); + JniHelper::getEnv()->SetIntArrayRegion(arr, 0, imageData.getSize() / 4, + reinterpret_cast(imageData.getBytes())); + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "_fillImageData", arr, imageWidth, + imageHeight, offsetX, offsetY); + ccDeleteLocalRef(JniHelper::getEnv(), arr); +} + +void CanvasRenderingContext2DDelegate::updateData() { + jobject bmpObj = nullptr; + JniMethodInfo methodInfo; +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + if (JniHelper::getMethodInfo(methodInfo, JCLS_CANVASIMPL, "getBitmap", "()Landroid/graphics/Bitmap;")) { + bmpObj = methodInfo.env->CallObjectMethod(_obj, methodInfo.methodID); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } +#else + if (JniHelper::getMethodInfo(methodInfo, JCLS_CANVASIMPL, "getBitmap", "()Lohos/media/image/PixelMap;")) { + bmpObj = methodInfo.env->CallObjectMethod(_obj, methodInfo.methodID); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } +#endif + JNIEnv *env = JniHelper::getEnv(); + do { + if (nullptr == bmpObj) { + break; + } +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + AndroidBitmapInfo bmpInfo; + if (AndroidBitmap_getInfo(env, bmpObj, &bmpInfo) != ANDROID_BITMAP_RESULT_SUCCESS) { + CC_LOG_ERROR("AndroidBitmap_getInfo() failed ! error"); + break; + } + if (bmpInfo.width < 1 || bmpInfo.height < 1) { + break; + } + + void *pixelData; + if (AndroidBitmap_lockPixels(env, bmpObj, &pixelData) != ANDROID_BITMAP_RESULT_SUCCESS) { + CC_LOG_ERROR("AndroidBitmap_lockPixels() failed ! error"); + break; + } + + uint32_t size = bmpInfo.stride * bmpInfo.height; +#else + OhosPixelMapInfo bmpInfo; + void *pixelData = nullptr; + if (GetImageInfo(env, bmpObj, bmpInfo) == + OHOS_IMAGE_RESULT_SUCCESS && + bmpInfo.width > 0 && + bmpInfo.height > 0 && + bmpInfo.pixelFormat == OHOS_PIXEL_MAP_FORMAT_RGBA_8888) { + if (AccessPixels(env, bmpObj, &pixelData) != OHOS_IMAGE_RESULT_SUCCESS) { + CC_LOG_ERROR("AccessPixels() failed ! error"); + break; + } + } else { + break; + } + uint32_t size = bmpInfo.rowSize * bmpInfo.height; +#endif + auto *bmpData = static_cast(malloc(size)); + memcpy(bmpData, pixelData, size); +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + unMultiplyAlpha(reinterpret_cast(bmpData), size); +#endif + _data.fastSet(reinterpret_cast(bmpData), size); + +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + AndroidBitmap_unlockPixels(env, bmpObj); +#else + UnAccessPixels(env, bmpObj); +#endif + } while (false); + if (bmpObj) { + env->DeleteLocalRef(bmpObj); + } +} + +void CanvasRenderingContext2DDelegate::unMultiplyAlpha(unsigned char *ptr, uint32_t size) { // NOLINT(readability-convert-member-functions-to-static) + // Android source data is not premultiplied alpha when API >= 19 + // please refer CanvasRenderingContext2DImpl::recreateBuffer(float w, float h) + // in CanvasRenderingContext2DImpl.java + // if (getAndroidSDKInt() >= 19) + // return; + + float alpha; + for (int i = 0; i < size; i += 4) { + alpha = static_cast(ptr[i + 3]); + if (alpha > 0) { + ptr[i] = CLAMP((int)((float)ptr[i] / alpha * 255), 255); + ptr[i + 1] = CLAMP((int)((float)ptr[i + 1] / alpha * 255), 255); + ptr[i + 2] = CLAMP((int)((float)ptr[i + 2] / alpha * 255), 255); + } + } +} + +void CanvasRenderingContext2DDelegate::setShadowBlur(float blur) { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setShadowBlur", blur); +#else + CC_LOG_WARNING("shadowBlur not implemented"); +#endif +} + +void CanvasRenderingContext2DDelegate::setShadowColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setShadowColor", + static_cast(r), + static_cast(g), + static_cast(b), + static_cast(a)); +#else + CC_LOG_WARNING("shadowColor not implemented"); +#endif +} + +void CanvasRenderingContext2DDelegate::setShadowOffsetX(float offsetX) { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setShadowOffsetX", offsetX); +#else + CC_LOG_WARNING("shadowOffsetX not implemented"); +#endif +} + +void CanvasRenderingContext2DDelegate::setShadowOffsetY(float offsetY) { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + JniHelper::callObjectVoidMethod(_obj, JCLS_CANVASIMPL, "setShadowOffsetY", offsetY); +#else + CC_LOG_WARNING("shadowOffsetY not implemented"); +#endif +} + +} // namespace cc diff --git a/cocos/platform/java/modules/CanvasRenderingContext2DDelegate.h b/cocos/platform/java/modules/CanvasRenderingContext2DDelegate.h new file mode 100644 index 0000000..3148df7 --- /dev/null +++ b/cocos/platform/java/modules/CanvasRenderingContext2DDelegate.h @@ -0,0 +1,101 @@ +/**************************************************************************** + Copyright (c) 2018-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/canvas/ICanvasRenderingContext2D.h" + +#include "base/csscolorparser.h" +#include "bindings/jswrapper/config.h" +#include "math/Math.h" + +#include "cocos/bindings/jswrapper/SeApi.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/JniImp.h" + +#if __OHOS__ + #include +#endif + +#include + +#ifndef JCLS_CANVASIMPL + #define JCLS_CANVASIMPL "com/cocos/lib/CanvasRenderingContext2DImpl" +#endif +#include "base/std/container/array.h" + +namespace cc { + +class CanvasRenderingContext2DDelegate : public ICanvasRenderingContext2D::Delegate { +public: + using Size = ccstd::array; + using TextAlign = ICanvasRenderingContext2D::TextAlign; + using TextBaseline = ICanvasRenderingContext2D::TextBaseline; + + CanvasRenderingContext2DDelegate(); + ~CanvasRenderingContext2DDelegate() override; + + void recreateBuffer(float w, float h) override; + void beginPath() override; + void closePath() override; + void moveTo(float x, float y) override; + void lineTo(float x, float y) override; + void stroke() override; + void saveContext() override; + void restoreContext() override; + void clearRect(float /*x*/, float /*y*/, float w, float h) override; + void fill() override; + void setLineCap(const ccstd::string &lineCap) override; + void setLineJoin(const ccstd::string &lineJoin) override; + void rect(float x, float y, float w, float h) override; + void fillRect(float x, float y, float w, float h) override; + void fillText(const ccstd::string &text, float x, float y, float /*maxWidth*/) override; + void strokeText(const ccstd::string &text, float /*x*/, float /*y*/, float /*maxWidth*/) override; + Size measureText(const ccstd::string &text) override; + void updateFont(const ccstd::string &fontName, float fontSize, bool bold, bool italic, bool oblique, bool smallCaps) override; + void setTextAlign(TextAlign align) override; + void setTextBaseline(TextBaseline baseline) override; + void setFillStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override; + void setStrokeStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override; + void setLineWidth(float lineWidth) override; + const cc::Data &getDataRef() const override; + void fillImageData(const Data &imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) override; + void updateData() override; + void setShadowBlur(float blur) override; + void setShadowColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override; + void setShadowOffsetX(float offsetX) override; + void setShadowOffsetY(float offsetY) override; + +private: + void unMultiplyAlpha(unsigned char *ptr, uint32_t size); + +public: +private: + jobject _obj = nullptr; + Data _data; + float _bufferWidth = 0.0F; + float _bufferHeight = 0.0F; +}; + +} // namespace cc diff --git a/cocos/platform/java/modules/CommonScreen.cpp b/cocos/platform/java/modules/CommonScreen.cpp new file mode 100644 index 0000000..8e9304c --- /dev/null +++ b/cocos/platform/java/modules/CommonScreen.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/modules/CommonScreen.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "platform/java/jni/JniImp.h" + +namespace { +// constant from Android API: +// reference: https://developer.android.com/reference/android/view/Surface#ROTATION_0 +enum Rotation { + ROTATION_0 = 0, + ROTATION_90, + ROTATION_180, + ROTATION_270 +}; +} // namespace + +namespace cc { + +// int Screen::getDPI() { +// static int dpi = -1; +// if (dpi == -1) { +// AConfiguration *config = AConfiguration_new(); +// //AConfiguration_fromAssetManager(config, cocosApp.assetManager); +// int32_t density = AConfiguration_getDensity(config); +// AConfiguration_delete(config); +// const int stdDpi = 160; +// dpi = density * stdDpi; +// } +// return dpi; +// } + +float CommonScreen::getDevicePixelRatio() const { + return 1; +} + +void CommonScreen::setKeepScreenOn(bool keepScreenOn) { + // JniHelper::callStaticVoidMethod(JCLS_HELPER, "setKeepScreenOn", value); + // ANativeActivity_setWindowFlags(JniHelper::getAndroidApp()->activity, AWINDOW_FLAG_KEEP_SCREEN_ON, 0); + //CC_UNUSED_PARAM(keepScreenOn); + return setKeepScreenOnJNI(keepScreenOn); +} + +IScreen::Orientation CommonScreen::getDeviceOrientation() const { + int rotation = getDeviceRotationJNI(); + switch (rotation) { + case ROTATION_0: + return Orientation::PORTRAIT; + case ROTATION_90: + return Orientation::LANDSCAPE_RIGHT; + case ROTATION_180: + return Orientation::PORTRAIT_UPSIDE_DOWN; + case ROTATION_270: + return Orientation::LANDSCAPE_LEFT; + default: + break; + } + return Orientation::PORTRAIT; +} + +Vec4 CommonScreen::getSafeAreaEdge() const { + float *data = getSafeAreaEdgeJNI(); + return cc::Vec4(data[0], data[1], data[2], data[3]); +} + +bool CommonScreen::isDisplayStats() { //NOLINT + se::AutoHandleScope hs; + se::Value ret; + char commandBuf[100] = "cc.debug.isDisplayStats();"; + se::ScriptEngine::getInstance()->evalString(commandBuf, 100, &ret); + return ret.toBoolean(); +} + +void CommonScreen::setDisplayStats(bool isShow) { //NOLINT + se::AutoHandleScope hs; + char commandBuf[100] = {0}; + sprintf(commandBuf, "cc.debug.setDisplayStats(%s);", isShow ? "true" : "false"); + se::ScriptEngine::getInstance()->evalString(commandBuf); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/modules/CommonScreen.h b/cocos/platform/java/modules/CommonScreen.h new file mode 100644 index 0000000..1bc58dd --- /dev/null +++ b/cocos/platform/java/modules/CommonScreen.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IScreen.h" + +namespace cc { + +class CommonScreen : public IScreen { +public: + float getDevicePixelRatio() const override; + void setKeepScreenOn(bool keepScreenOn) override; + Orientation getDeviceOrientation() const override; + Vec4 getSafeAreaEdge() const override; + /** + @brief Get current display stats. + @return bool, is displaying stats or not. + */ + bool isDisplayStats() override; + + /** + @brief set display stats information. + */ + void setDisplayStats(bool isShow) override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/modules/CommonSystem.cpp b/cocos/platform/java/modules/CommonSystem.cpp new file mode 100644 index 0000000..b5d44dd --- /dev/null +++ b/cocos/platform/java/modules/CommonSystem.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/modules/CommonSystem.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/JniImp.h" + +namespace cc { +CommonSystem::CommonSystem() = default; +CommonSystem::~CommonSystem() = default; + +ccstd::string CommonSystem::getDeviceModel() const { + return getDeviceModelJNI(); +} + +CommonSystem::LanguageType CommonSystem::getCurrentLanguage() const { + ccstd::string languageName = getCurrentLanguageJNI(); + const char *pLanguageName = languageName.c_str(); + LanguageType ret = LanguageType::ENGLISH; + + if (0 == strcmp("zh", pLanguageName)) { + ret = LanguageType::CHINESE; + } else if (0 == strcmp("en", pLanguageName)) { + ret = LanguageType::ENGLISH; + } else if (0 == strcmp("fr", pLanguageName)) { + ret = LanguageType::FRENCH; + } else if (0 == strcmp("it", pLanguageName)) { + ret = LanguageType::ITALIAN; + } else if (0 == strcmp("de", pLanguageName)) { + ret = LanguageType::GERMAN; + } else if (0 == strcmp("es", pLanguageName)) { + ret = LanguageType::SPANISH; + } else if (0 == strcmp("ru", pLanguageName)) { + ret = LanguageType::RUSSIAN; + } else if (0 == strcmp("nl", pLanguageName)) { + ret = LanguageType::DUTCH; + } else if (0 == strcmp("ko", pLanguageName)) { + ret = LanguageType::KOREAN; + } else if (0 == strcmp("ja", pLanguageName)) { + ret = LanguageType::JAPANESE; + } else if (0 == strcmp("hu", pLanguageName)) { + ret = LanguageType::HUNGARIAN; + } else if (0 == strcmp("pt", pLanguageName)) { + ret = LanguageType::PORTUGUESE; + } else if (0 == strcmp("ar", pLanguageName)) { + ret = LanguageType::ARABIC; + } else if (0 == strcmp("nb", pLanguageName)) { + ret = LanguageType::NORWEGIAN; + } else if (0 == strcmp("pl", pLanguageName)) { + ret = LanguageType::POLISH; + } else if (0 == strcmp("tr", pLanguageName)) { + ret = LanguageType::TURKISH; + } else if (0 == strcmp("uk", pLanguageName)) { + ret = LanguageType::UKRAINIAN; + } else if (0 == strcmp("ro", pLanguageName)) { + ret = LanguageType::ROMANIAN; + } else if (0 == strcmp("bg", pLanguageName)) { + ret = LanguageType::BULGARIAN; + } else if (0 == strcmp("hi", pLanguageName)) { + ret = LanguageType::HINDI; + } + return ret; +} + +ccstd::string CommonSystem::getCurrentLanguageCode() const { + return getCurrentLanguageCodeJNI(); +} + +ccstd::string CommonSystem::getSystemVersion() const { + return getSystemVersionJNI(); +} + +bool CommonSystem::openURL(const ccstd::string &url) { + return openURLJNI(url); +} + +void CommonSystem::copyTextToClipboard(const ccstd::string &text) { + copyTextToClipboardJNI(text); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/modules/CommonSystem.h b/cocos/platform/java/modules/CommonSystem.h new file mode 100644 index 0000000..1ec7e08 --- /dev/null +++ b/cocos/platform/java/modules/CommonSystem.h @@ -0,0 +1,64 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/ISystem.h" + +namespace cc { + +class CommonSystem : public ISystem { +public: + CommonSystem(); + ~CommonSystem() override; + /** + @brief Get target device model. + */ + ccstd::string getDeviceModel() const override; + /** + @brief Get current language config. + @return Current language config. + */ + LanguageType getCurrentLanguage() const override; + /** + @brief Get current language iso 639-1 code. + @return Current language iso 639-1 code. + */ + ccstd::string getCurrentLanguageCode() const override; + /** + @brief Get system version. + @return system version. + */ + ccstd::string getSystemVersion() const override; + /** + @brief Open url in default browser. + @param String with url to open. + @return True if the resource located by the URL was successfully opened; otherwise false. + */ + bool openURL(const ccstd::string& url) override; + + void copyTextToClipboard(const std::string& text) override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/modules/Network.cpp b/cocos/platform/java/modules/Network.cpp new file mode 100644 index 0000000..fe13872 --- /dev/null +++ b/cocos/platform/java/modules/Network.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/modules/Network.h" +#include "platform/java/jni/JniImp.h" + +namespace cc { + +INetwork::NetworkType Network::getNetworkType() const { + return static_cast(getNetworkTypeJNI()); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/modules/Network.h b/cocos/platform/java/modules/Network.h new file mode 100644 index 0000000..a4eed0e --- /dev/null +++ b/cocos/platform/java/modules/Network.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/INetwork.h" + +namespace cc { + +class Network : public INetwork { +public: + NetworkType getNetworkType() const override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/modules/SystemWindow.cpp b/cocos/platform/java/modules/SystemWindow.cpp new file mode 100644 index 0000000..b2f3773 --- /dev/null +++ b/cocos/platform/java/modules/SystemWindow.cpp @@ -0,0 +1,126 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/modules/SystemWindow.h" +#include +#include +#include +#include +#include +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + #include +#endif + +#include "BasePlatform.h" +#include "base/Log.h" +#include "base/Macros.h" +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/JniImp.h" +#include "platform/java/jni/glue/JniNativeGlue.h" + +namespace { +#ifndef JCLS_COCOSACTIVITY + #define JCLS_COCOSACTIVITY "com/cocos/lib/CocosActivity" +#endif +} // namespace + +namespace cc { +SystemWindow::SystemWindow(uint32_t windowId, void *externalHandle) +: _windowHandle(externalHandle), _windowId(windowId) { +} + +void SystemWindow::setCursorEnabled(bool value) { +} + +void SystemWindow::setWindowHandle(void *handle) { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + //The getWindowHandle interface may have been called earlier, causing _handleMutex to be occupied all the time. + bool lockSuccess = _handleMutex.try_lock(); + bool needNotify = _windowHandle == nullptr; + _windowHandle = handle; + if (needNotify) { + _windowHandlePromise.set_value(); + } + if (lockSuccess) { + _handleMutex.unlock(); + } +#else + _windowHandle = handle; +#endif +} + +uintptr_t SystemWindow::getWindowHandle() const { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + std::lock_guard lock(const_cast(_handleMutex)); + if (!_windowHandle) { + auto &future = const_cast &>(_windowHandlePromise); + future.get_future().get(); + } + CC_ASSERT(_windowHandle); + return reinterpret_cast(_windowHandle); +#else + return reinterpret_cast( + JNI_NATIVE_GLUE()->getWindowHandle()); +#endif +} + +SystemWindow::Size SystemWindow::getViewSize() const { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + CC_ASSERT(_windowHandle); + auto *nativeWindow = static_cast(_windowHandle); + return Size{static_cast(ANativeWindow_getWidth(nativeWindow)), + static_cast(ANativeWindow_getHeight(nativeWindow))}; +#else + return Size{static_cast(JNI_NATIVE_GLUE()->getWidth()), + static_cast(JNI_NATIVE_GLUE()->getHeight())}; +#endif +} +void SystemWindow::closeWindow() { +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + finishActivity(); +#else + events::Close::broadcast(); + exit(0); //TODO(cc): better exit for ohos +#endif +} + +bool SystemWindow::createWindow(const char *title, int x, int y, int w, int h, int flags) { + CC_UNUSED_PARAM(title); + CC_UNUSED_PARAM(flags); +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + cc::JniHelper::callObjectVoidMethod(cc::JniHelper::getActivity(), JCLS_COCOSACTIVITY, "createSurface", x, y, w, h, static_cast(_windowId)); +#endif + return true; +} + +bool SystemWindow::createWindow(const char *title, int w, int h, int flags) { + CC_UNUSED_PARAM(title); + CC_UNUSED_PARAM(flags); +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) + cc::JniHelper::callObjectVoidMethod(cc::JniHelper::getActivity(), JCLS_COCOSACTIVITY, "createSurface", 0, 0, w, h, static_cast(_windowId)); +#endif + return true; +} + +} // namespace cc diff --git a/cocos/platform/java/modules/SystemWindow.h b/cocos/platform/java/modules/SystemWindow.h new file mode 100644 index 0000000..6933acb --- /dev/null +++ b/cocos/platform/java/modules/SystemWindow.h @@ -0,0 +1,61 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "platform/interfaces/modules/ISystemWindow.h" + +namespace cc { + +class SystemWindow : public ISystemWindow { +public: + SystemWindow(uint32_t windowId, void *externalHandle); + + bool createWindow(const char *title, int x, int y, int w, int h, int flags) override; + + bool createWindow(const char *title, int w, int h, int flags) override; + + /** + @brief enable/disable(lock) the cursor, default is enabled + */ + void setCursorEnabled(bool value) override; + + void setWindowHandle(void *handle); + uintptr_t getWindowHandle() const override; + uint32_t getWindowId() const override { return _windowId; } + + Size getViewSize() const override; + + void closeWindow() override; + +private: + std::mutex _handleMutex; + std::promise _windowHandlePromise; + uint32_t _windowId{0}; + void *_windowHandle{nullptr}; +}; + +} // namespace cc diff --git a/cocos/platform/java/modules/SystemWindowManager.cpp b/cocos/platform/java/modules/SystemWindowManager.cpp new file mode 100644 index 0000000..3591c1e --- /dev/null +++ b/cocos/platform/java/modules/SystemWindowManager.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "SystemWindowManager.h" +#include "BasePlatform.h" +#include "platform/java/modules/SystemWindow.h" + +namespace cc { + +int SystemWindowManager::init() { + return 0; +} + +void SystemWindowManager::processEvent() { +} + +ISystemWindow *SystemWindowManager::createWindow(const ISystemWindowInfo &info) { + if (!isExternalHandleExist(info.externalHandle)) { + ISystemWindow *window = BasePlatform::getPlatform()->createNativeWindow(_nextWindowId, info.externalHandle); + if (window) { + if (!info.externalHandle) { + window->createWindow(info.title.c_str(), info.x, info.y, info.width, info.height, info.flags); + } + _windows[_nextWindowId] = std::shared_ptr(window); + _nextWindowId++; + } + return window; + } + return getWindowFromANativeWindow(static_cast(info.externalHandle)); +} + +ISystemWindow *SystemWindowManager::getWindow(uint32_t windowId) const { + if (windowId == 0) { + return nullptr; + } + + auto iter = _windows.find(windowId); + if (iter != _windows.end()) { + return iter->second.get(); + } + return nullptr; +} + +ISystemWindow *SystemWindowManager::getWindowFromANativeWindow(ANativeWindow *window) const { + if (!window) { + return nullptr; + } + for (const auto &pair : _windows) { + ISystemWindow *sysWindow = pair.second.get(); + auto *nativeWindow = reinterpret_cast(sysWindow->getWindowHandle()); + if (nativeWindow == window) { + return sysWindow; + } + } + return nullptr; +} + +bool SystemWindowManager::isExternalHandleExist(void *externalHandle) const { + return std::any_of(_windows.begin(), _windows.end(), [externalHandle](const auto &pair) { + auto *handle = reinterpret_cast(pair.second->getWindowHandle()); + return handle == externalHandle; + }); +} +} // namespace cc diff --git a/cocos/platform/java/modules/SystemWindowManager.h b/cocos/platform/java/modules/SystemWindowManager.h new file mode 100644 index 0000000..4c760c1 --- /dev/null +++ b/cocos/platform/java/modules/SystemWindowManager.h @@ -0,0 +1,52 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/unordered_map.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" + +struct ANativeWindow; + +namespace cc { +class ISystemWindow; + +class SystemWindowManager : public ISystemWindowManager { +public: + SystemWindowManager() = default; + + int init() override; + void processEvent() override; + ISystemWindow *createWindow(const ISystemWindowInfo &info) override; + ISystemWindow *getWindow(uint32_t windowId) const override; + const SystemWindowMap &getWindows() const override { return _windows; } + + ISystemWindow *getWindowFromANativeWindow(ANativeWindow *window) const; + bool isExternalHandleExist(void *handle) const; + +private: + uint32_t _nextWindowId{1}; // start from 1, 0 means an invalid ID + SystemWindowMap _windows; +}; +} // namespace cc diff --git a/cocos/platform/java/modules/Vibrator.cpp b/cocos/platform/java/modules/Vibrator.cpp new file mode 100644 index 0000000..caf8d74 --- /dev/null +++ b/cocos/platform/java/modules/Vibrator.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/java/modules/Vibrator.h" +#include "platform/java/jni/JniImp.h" + +namespace cc { + +void Vibrator::vibrate(float duration) { + setVibrateJNI(duration); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/modules/Vibrator.h b/cocos/platform/java/modules/Vibrator.h new file mode 100644 index 0000000..6208eb7 --- /dev/null +++ b/cocos/platform/java/modules/Vibrator.h @@ -0,0 +1,43 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IVibrator.h" + +namespace cc { + +class Vibrator : public IVibrator { +public: + /** + * Vibrate for the specified amount of time. + * If vibrate is not supported, then invoking this method has no effect. + * Some platforms limit to a maximum duration of 5 seconds. + * Duration is ignored on iOS due to API limitations. + * @param duration The duration in seconds. + */ + void vibrate(float duration) override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/java/modules/XRInterface.cpp b/cocos/platform/java/modules/XRInterface.cpp new file mode 100644 index 0000000..b59f044 --- /dev/null +++ b/cocos/platform/java/modules/XRInterface.cpp @@ -0,0 +1,1252 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "XRInterface.h" +#include +#include +#include +#include +#include "android/AndroidPlatform.h" +#include "base/Log.h" +#include "base/Macros.h" +#include "base/StringUtil.h" +#include "bindings/event/EventDispatcher.h" +#include "cocos-version.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "core/scene-graph/Node.h" +#include "java/jni/JniHelper.h" +#include "platform/interfaces/modules/ISystemWindow.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" +#include "renderer/GFXDeviceManager.h" +#include "scene/Camera.h" +#include "scene/RenderWindow.h" +#ifdef CC_USE_VULKAN + #include "gfx-vulkan/VKDevice.h" +#endif +#ifdef CC_USE_GLES3 + #include "gfx-gles-common/gles3w.h" + #include "gfx-gles3/GLES3Device.h" + #include "renderer/gfx-gles3/GLES3GPUObjects.h" +#endif + +#if CC_USE_XR + #include "Xr.h" +#endif +#include "application/ApplicationManager.h" +#include "base/threading/MessageQueue.h" +#include "platform/Image.h" + +// print log +const bool IS_ENABLE_XR_LOG = false; + +namespace cc { +const static ccstd::unordered_map CLICK_TYPE_TO_KEY_CODE = { + {xr::XRClick::Type::MENU, StickKeyCode::MENU}, + {xr::XRClick::Type::TRIGGER_LEFT, StickKeyCode::TRIGGER_LEFT}, + {xr::XRClick::Type::SHOULDER_LEFT, StickKeyCode::L1}, + {xr::XRClick::Type::THUMBSTICK_LEFT, StickKeyCode::L3}, + {xr::XRClick::Type::X, StickKeyCode::Y}, + {xr::XRClick::Type::Y, StickKeyCode::X}, + {xr::XRClick::Type::TRIGGER_RIGHT, StickKeyCode::TRIGGER_RIGHT}, + {xr::XRClick::Type::SHOULDER_RIGHT, StickKeyCode::R1}, + {xr::XRClick::Type::THUMBSTICK_RIGHT, StickKeyCode::R3}, + {xr::XRClick::Type::A, StickKeyCode::B}, + {xr::XRClick::Type::B, StickKeyCode::A}, + {xr::XRClick::Type::HOME, StickKeyCode::UNDEFINE}, + {xr::XRClick::Type::START, StickKeyCode::START}, + {xr::XRClick::Type::DPAD_DOWN, StickKeyCode::Y}, + {xr::XRClick::Type::DPAD_UP, StickKeyCode::Y}, + {xr::XRClick::Type::DPAD_LEFT, StickKeyCode::X}, + {xr::XRClick::Type::DPAD_RIGHT, StickKeyCode::X}}; + +const static ccstd::unordered_map GRAB_TYPE_TO_AXIS_CODE = { + {xr::XRGrab::Type::TRIGGER_LEFT, StickAxisCode::L2}, + {xr::XRGrab::Type::TRIGGER_RIGHT, StickAxisCode::R2}, + {xr::XRGrab::Type::GRIP_LEFT, StickAxisCode::LEFT_GRIP}, + {xr::XRGrab::Type::GRIP_RIGHT, StickAxisCode::RIGHT_GRIP}, +}; + +const static ccstd::unordered_map TOUCH_TYPE_TO_AXIS_CODE = { + {xr::XRTouch::Type::TOUCH_A, StickTouchCode::A}, + {xr::XRTouch::Type::TOUCH_B, StickTouchCode::B}, + {xr::XRTouch::Type::TOUCH_X, StickTouchCode::X}, + {xr::XRTouch::Type::TOUCH_Y, StickTouchCode::Y}, + {xr::XRTouch::Type::TOUCH_TRIGGER_LEFT, StickTouchCode::LEFT_TRIGGER}, + {xr::XRTouch::Type::TOUCH_TRIGGER_RIGHT, StickTouchCode::RIGHT_TRIGGER}, + {xr::XRTouch::Type::TOUCH_THUMBSTICK_LEFT, StickTouchCode::LEFT_THUMBSTICK}, + {xr::XRTouch::Type::TOUCH_THUMBSTICK_RIGHT, StickTouchCode::RIGHT_THUMBSTICK}, +}; + +void XRInterface::dispatchGamepadEventInternal(const xr::XRControllerEvent &xrControllerEvent) { + if (xrControllerEvent.xrControllerInfos.empty()) { + return; + } + + auto *controllerInfo = ccnew ControllerInfo(); + if (!controllerInfo) { + return; + } + + size_t length = xrControllerEvent.xrControllerInfos.size(); + for (size_t i = 0; i < length; i++) { + switch (xrControllerEvent.xrControllerInfos.at(i)->getXREventType()) { + case xr::XREventType::CLICK: { + auto *xrClick = static_cast(xrControllerEvent.xrControllerInfos.at(i).get()); + if(CLICK_TYPE_TO_KEY_CODE.count(xrClick->type) > 0) { + StickKeyCode stickKeyCode = CLICK_TYPE_TO_KEY_CODE.at(xrClick->type); + + switch (xrClick->type) { + case xr::XRClick::Type::MENU: + case xr::XRClick::Type::TRIGGER_LEFT: + case xr::XRClick::Type::SHOULDER_LEFT: + case xr::XRClick::Type::THUMBSTICK_LEFT: + case xr::XRClick::Type::X: + case xr::XRClick::Type::Y: + case xr::XRClick::Type::TRIGGER_RIGHT: + case xr::XRClick::Type::SHOULDER_RIGHT: + case xr::XRClick::Type::THUMBSTICK_RIGHT: + case xr::XRClick::Type::A: + case xr::XRClick::Type::B: + case xr::XRClick::Type::START: { + controllerInfo->buttonInfos.emplace_back(ControllerInfo::ButtonInfo(stickKeyCode, xrClick->isPress)); + break; + } + case xr::XRClick::Type::HOME: { + CC_LOG_INFO("[XRInterface] dispatchGamepadEventInternal exit when home click in rokid."); +#if CC_USE_XR + xr::XrEntry::getInstance()->destroyXrInstance(); + xr::XrEntry::destroyInstance(); + _isXrEntryInstanceValid = false; +#endif + CC_CURRENT_APPLICATION_SAFE()->close(); + break; + } + case xr::XRClick::Type::DPAD_UP: + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::Y, xrClick->isPress ? 1.F : 0.F)); + break; + case xr::XRClick::Type::DPAD_DOWN: + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::Y, xrClick->isPress ? -1.F : 0.F)); + break; + case xr::XRClick::Type::DPAD_LEFT: + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::X, xrClick->isPress ? -1.F : 0.F)); + break; + case xr::XRClick::Type::DPAD_RIGHT: + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::X, xrClick->isPress ? 1.F : 0.F)); + break; + default: + break; + } + } + } break; + case xr::XREventType::STICK: { + auto *xrStick = static_cast(xrControllerEvent.xrControllerInfos.at(i).get()); + switch (xrStick->type) { + case xr::XRStick::Type::STICK_LEFT: + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::LEFT_STICK_X, xrStick->x)); + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::LEFT_STICK_Y, xrStick->y)); + break; + case xr::XRStick::Type::STICK_RIGHT: + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::RIGHT_STICK_X, xrStick->x)); + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::RIGHT_STICK_Y, xrStick->y)); + break; + default: + break; + } + } break; + case xr::XREventType::GRAB: { + auto *xrGrab = static_cast(xrControllerEvent.xrControllerInfos.at(i).get()); + if(GRAB_TYPE_TO_AXIS_CODE.count(xrGrab->type) > 0) { + StickAxisCode stickAxisCode = GRAB_TYPE_TO_AXIS_CODE.at(xrGrab->type); + switch (xrGrab->type) { + case xr::XRGrab::Type::TRIGGER_LEFT: + case xr::XRGrab::Type::TRIGGER_RIGHT: { + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(stickAxisCode, xrGrab->value)); + break; + } + default: + break; + } + } + } break; + default: + break; + } + } + + controllerInfo->napdId = 0; // xr only one gamepad connection + _controllerEvent.controllerInfos.emplace_back(controllerInfo); + _controllerEvent.type = ControllerEvent::Type::GAMEPAD; +#if CC_USE_XR_REMOTE_PREVIEW + if (_xrRemotePreviewManager) { + if (!controllerInfo->buttonInfos.empty()) { + for (const auto &btnInfo : controllerInfo->buttonInfos) { + _xrRemotePreviewManager->sendControllerKeyInfo(btnInfo); + } + } + + if (!controllerInfo->axisInfos.empty()) { + for (const auto &axisInfo : controllerInfo->axisInfos) { + _xrRemotePreviewManager->sendControllerKeyInfo(axisInfo); + } + } + } +#endif + events::Controller::broadcast(_controllerEvent); + _controllerEvent.type = ControllerEvent::Type::UNKNOWN; + _controllerEvent.controllerInfos.clear(); +} + +void XRInterface::dispatchHandleEventInternal(const xr::XRControllerEvent &xrControllerEvent) { + if (xrControllerEvent.xrControllerInfos.empty()) { + return; + } + + auto *controllerInfo = ccnew ControllerInfo(); + if (!controllerInfo) { + return; + } + + size_t length = xrControllerEvent.xrControllerInfos.size(); + se::AutoHandleScope scope; + uint32_t poseIndex = 0; + for (size_t i = 0; i < length; i++) { + switch (xrControllerEvent.xrControllerInfos.at(i)->getXREventType()) { + case xr::XREventType::CLICK: { + auto *xrClick = static_cast(xrControllerEvent.xrControllerInfos.at(i).get()); + if(CLICK_TYPE_TO_KEY_CODE.count(xrClick->type) > 0) { + StickKeyCode stickKeyCode = CLICK_TYPE_TO_KEY_CODE.at(xrClick->type); + switch (xrClick->type) { + case xr::XRClick::Type::MENU: { +#if !XR_OEM_SEED + controllerInfo->buttonInfos.emplace_back(ControllerInfo::ButtonInfo(StickKeyCode::MENU, xrClick->isPress)); +#else + CC_LOG_INFO("[XRInterface] exit when menu click in seed."); + CC_CURRENT_APPLICATION_SAFE()->close(); +#endif + break; + } + case xr::XRClick::Type::TRIGGER_LEFT: + case xr::XRClick::Type::THUMBSTICK_LEFT: + case xr::XRClick::Type::X: + case xr::XRClick::Type::Y: + case xr::XRClick::Type::TRIGGER_RIGHT: + case xr::XRClick::Type::THUMBSTICK_RIGHT: + case xr::XRClick::Type::A: + case xr::XRClick::Type::B: + case xr::XRClick::Type::START: { + controllerInfo->buttonInfos.emplace_back(ControllerInfo::ButtonInfo(stickKeyCode, xrClick->isPress)); + break; + } + case xr::XRClick::Type::HOME: { + CC_LOG_INFO("[XRInterface] dispatchHandleEventInternal exit when home click in rokid."); +#if CC_USE_XR + xr::XrEntry::getInstance()->destroyXrInstance(); + xr::XrEntry::destroyInstance(); + _isXrEntryInstanceValid = false; +#endif + CC_CURRENT_APPLICATION_SAFE()->close(); + break; + } + default: + break; + } + } + } break; + case xr::XREventType::STICK: { + auto *xrStick = static_cast(xrControllerEvent.xrControllerInfos.at(i).get()); + switch (xrStick->type) { + case xr::XRStick::Type::STICK_LEFT: + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::LEFT_STICK_X, xrStick->x)); + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::LEFT_STICK_Y, xrStick->y)); + break; + case xr::XRStick::Type::STICK_RIGHT: + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::RIGHT_STICK_X, xrStick->x)); + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(StickAxisCode::RIGHT_STICK_Y, xrStick->y)); + break; + default: + break; + } + } break; + case xr::XREventType::GRAB: { + auto *xrGrab = static_cast(xrControllerEvent.xrControllerInfos.at(i).get()); + if(GRAB_TYPE_TO_AXIS_CODE.count(xrGrab->type) > 0) { + StickAxisCode stickAxisCode = GRAB_TYPE_TO_AXIS_CODE.at(xrGrab->type); + controllerInfo->axisInfos.emplace_back(ControllerInfo::AxisInfo(stickAxisCode, xrGrab->value)); + } + } break; + case xr::XREventType::TOUCH: { + auto *xrTouch = static_cast(xrControllerEvent.xrControllerInfos.at(i).get()); + if(TOUCH_TYPE_TO_AXIS_CODE.count(xrTouch->type) > 0) { + StickTouchCode stickTouchCode = TOUCH_TYPE_TO_AXIS_CODE.at(xrTouch->type); + controllerInfo->touchInfos.emplace_back(ControllerInfo::TouchInfo(stickTouchCode, xrTouch->value)); + } + break; + } + case xr::XREventType::POSE: { + auto *xrPose = static_cast(xrControllerEvent.xrControllerInfos.at(i).get()); + switch (xrPose->type) { + case xr::XRPose::Type::HAND_LEFT: + case xr::XRPose::Type::HAND_RIGHT: + case xr::XRPose::Type::AIM_LEFT: + case xr::XRPose::Type::AIM_RIGHT: { + auto *jsPose = se::Object::createPlainObject(); + jsPose->setProperty("code", se::Value(static_cast(xrPose->type))); + jsPose->setProperty("x", se::Value(xrPose->px)); + jsPose->setProperty("y", se::Value(xrPose->py)); + jsPose->setProperty("z", se::Value(xrPose->pz)); + jsPose->setProperty("quaternionX", se::Value(xrPose->qx)); + jsPose->setProperty("quaternionY", se::Value(xrPose->qy)); + jsPose->setProperty("quaternionZ", se::Value(xrPose->qz)); + jsPose->setProperty("quaternionW", se::Value(xrPose->qw)); + if (!_jsPoseEventArray) { + _jsPoseEventArray = se::Object::createArrayObject(0); + _jsPoseEventArray->root(); + } + _jsPoseEventArray->setArrayElement(poseIndex, se::Value(jsPose)); + poseIndex++; + } break; + default: + break; + } + } break; + default: + break; + } + } + + if (poseIndex > 0) { + _jsPoseEventArray->setProperty("length", se::Value(poseIndex)); + se::ValueArray args; + args.emplace_back(se::Value(_jsPoseEventArray)); + EventDispatcher::doDispatchJsEvent("onHandlePoseInput", args); + } + + if (!controllerInfo->buttonInfos.empty() || !controllerInfo->axisInfos.empty() || !controllerInfo->touchInfos.empty()) { + controllerInfo->napdId = 0; // xr only one handle connection + _controllerEvent.controllerInfos.emplace_back(controllerInfo); + _controllerEvent.type = ControllerEvent::Type::HANDLE; +#if CC_USE_XR_REMOTE_PREVIEW + if (_xrRemotePreviewManager) { + if (!controllerInfo->buttonInfos.empty()) { + for (const auto &btnInfo : controllerInfo->buttonInfos) { + _xrRemotePreviewManager->sendControllerKeyInfo(btnInfo); + } + } + + if (!controllerInfo->axisInfos.empty()) { + for (const auto &axisInfo : controllerInfo->axisInfos) { + _xrRemotePreviewManager->sendControllerKeyInfo(axisInfo); + } + } + + if (!controllerInfo->touchInfos.empty()) { + for (const auto &touchInfo : controllerInfo->touchInfos) { + _xrRemotePreviewManager->sendControllerKeyInfo(touchInfo); + } + } + } +#endif + events::Controller::broadcast(_controllerEvent); + _controllerEvent.type = ControllerEvent::Type::UNKNOWN; + _controllerEvent.controllerInfos.clear(); + } else { + CC_SAFE_DELETE(controllerInfo) + } +} + +void XRInterface::dispatchHMDEventInternal(const xr::XRControllerEvent &xrControllerEvent) { + if (xrControllerEvent.xrControllerInfos.empty()) { + return; + } + + size_t length = xrControllerEvent.xrControllerInfos.size(); + se::AutoHandleScope scope; + uint32_t poseIndex = 0; + for (size_t i = 0; i < length; i++) { + if (xrControllerEvent.xrControllerInfos.at(i)->getXREventType() == xr::XREventType::POSE) { + auto *xrPose = static_cast(xrControllerEvent.xrControllerInfos.at(i).get()); + switch (xrPose->type) { + case xr::XRPose::Type::VIEW_LEFT: + case xr::XRPose::Type::VIEW_RIGHT: + case xr::XRPose::Type::HEAD_MIDDLE: { + auto *jsPose = se::Object::createPlainObject(); + jsPose->setProperty("code", se::Value(static_cast(xrPose->type))); + jsPose->setProperty("x", se::Value(xrPose->px)); + jsPose->setProperty("y", se::Value(xrPose->py)); + jsPose->setProperty("z", se::Value(xrPose->pz)); + jsPose->setProperty("quaternionX", se::Value(xrPose->qx)); + jsPose->setProperty("quaternionY", se::Value(xrPose->qy)); + jsPose->setProperty("quaternionZ", se::Value(xrPose->qz)); + jsPose->setProperty("quaternionW", se::Value(xrPose->qw)); + if (!_jsPoseEventArray) { + _jsPoseEventArray = se::Object::createArrayObject(0); + _jsPoseEventArray->root(); + } + _jsPoseEventArray->setArrayElement(poseIndex, se::Value(jsPose)); + poseIndex++; + } break; + default: + break; + } + } + } + + if (poseIndex > 0) { + _jsPoseEventArray->setProperty("length", se::Value(poseIndex)); + se::ValueArray args; + args.emplace_back(se::Value(_jsPoseEventArray)); + EventDispatcher::doDispatchJsEvent("onHMDPoseInput", args); + } +} + +xr::XRVendor XRInterface::getVendor() { +#if CC_USE_XR + return static_cast(xr::XrEntry::getInstance()->getXRConfig(cc::xr::XRConfigKey::DEVICE_VENDOR).getInt()); +#else + return xr::XRVendor::MONADO; +#endif +} + +xr::XRConfigValue XRInterface::getXRConfig(xr::XRConfigKey key) { +#if CC_USE_XR + return xr::XrEntry::getInstance()->getXRConfig(key); +#else + CC_UNUSED_PARAM(key); + cc::xr::XRConfigValue configValue; + return configValue; +#endif +} + +void XRInterface::setXRConfig(xr::XRConfigKey key, xr::XRConfigValue value) { +#if CC_USE_XR + CC_LOG_INFO("[XR] setConfigParameterI %d", key); + xr::XrEntry::getInstance()->setXRConfig(key, value); +#else + CC_UNUSED_PARAM(key); + CC_UNUSED_PARAM(value); +#endif +} + +uint32_t XRInterface::getRuntimeVersion() { +#if CC_USE_XR + return xr::XrEntry::getInstance()->getXRConfig(cc::xr::XRConfigKey::RUNTIME_VERSION).getInt(); +#else + return 1; +#endif +} + +void XRInterface::initialize(void *javaVM, void *activity) { +#if CC_USE_XR + CC_LOG_INFO("[XR] initialize vm.%p,aty.%p | %d", javaVM, activity, (int)gettid()); + _isXrEntryInstanceValid = true; + xr::XrEntry::getInstance()->initPlatformData(javaVM, activity); + xr::XrEntry::getInstance()->setGamepadCallback(std::bind(&XRInterface::dispatchGamepadEventInternal, this, std::placeholders::_1)); + xr::XrEntry::getInstance()->setHandleCallback(std::bind(&XRInterface::dispatchHandleEventInternal, this, std::placeholders::_1)); + xr::XrEntry::getInstance()->setHMDCallback(std::bind(&XRInterface::dispatchHMDEventInternal, this, std::placeholders::_1)); + xr::XrEntry::getInstance()->setXRConfig(xr::XRConfigKey::LOGIC_THREAD_ID, static_cast(gettid())); + xr::XrEntry::getInstance()->setXRConfig(xr::XRConfigKey::ENGINE_VERSION, COCOS_VERSION); + xr::XrEntry::getInstance()->setXRConfigCallback([this](xr::XRConfigKey key, xr::XRConfigValue value) { + if (IS_ENABLE_XR_LOG) CC_LOG_INFO("XRConfigCallback.%d", key); + if (key == xr::XRConfigKey::RENDER_EYE_FRAME_LEFT || key == xr::XRConfigKey::RENDER_EYE_FRAME_RIGHT) { + if (value.getInt() == 0) { + this->beginRenderEyeFrame(key == xr::XRConfigKey::RENDER_EYE_FRAME_LEFT ? 0 : 1); + } + + if (value.getInt() == 1) { + this->endRenderEyeFrame(key == xr::XRConfigKey::RENDER_EYE_FRAME_LEFT ? 0 : 1); + } + } else if (key == xr::XRConfigKey::IMAGE_TRACKING_CANDIDATEIMAGE && value.isString()) { + if (!_gThreadPool) { + _gThreadPool = LegacyThreadPool::newSingleThreadPool(); + } + + std::string imageInfo = value.getString(); + _gThreadPool->pushTask([imageInfo, this](int /*tid*/) { + this->loadImageTrackingData(imageInfo); + }); + } else if (key == xr::XRConfigKey::ASYNC_LOAD_ASSETS_IMAGE && value.isString()) { + std::string imagePath = value.getString(); + if (imagePath.length() == 0) { + return; + } + + if (!_gThreadPool) { + _gThreadPool = LegacyThreadPool::newSingleThreadPool(); + } + _gThreadPool->pushTask([imagePath, this](int /*tid*/) { + this->asyncLoadAssetsImage(imagePath); + }); + } else if (key == xr::XRConfigKey::ASYNC_LOAD_ASSETS_IMAGE && value.isInt()) { + _isFlipPixelY = value.getInt() == static_cast(gfx::API::GLES3); + } else if (key == xr::XRConfigKey::TS_EVENT_CALLBACK) { + se::AutoHandleScope scope; + se::ValueArray args; + args.emplace_back(se::Value(value.getString())); + EventDispatcher::doDispatchJsEvent("onXREvent", args); + } + }); + #if XR_OEM_PICO + std::string graphicsApiName = GraphicsApiOpenglES; + #if CC_USE_VULKAN + graphicsApiName = GraphicsApiVulkan_1_1; + #endif + xr::XrEntry::getInstance()->createXrInstance(graphicsApiName.c_str()); + #endif + + #if CC_USE_XR_REMOTE_PREVIEW + _xrRemotePreviewManager = new XRRemotePreviewManager(); + #endif +#else + CC_UNUSED_PARAM(javaVM); + CC_UNUSED_PARAM(activity); +#endif +} + +// render thread lifecycle +void XRInterface::onRenderPause() { +#if CC_USE_XR + if (!_renderPaused) { + _renderPaused = true; + _renderResumed = false; + #if CC_USE_XR_REMOTE_PREVIEW + if (_xrRemotePreviewManager) { + _xrRemotePreviewManager->pause(); + } + #endif + CC_LOG_INFO("[XR] onRenderPause"); + xr::XrEntry::getInstance()->pauseXrInstance(); + } +#endif +} + +void XRInterface::onRenderResume() { +#if CC_USE_XR + if (!_renderResumed) { + _renderResumed = true; + _renderPaused = false; + CC_LOG_INFO("[XR] onRenderResume"); + xr::XrEntry::getInstance()->resumeXrInstance(); + #if CC_USE_XR_REMOTE_PREVIEW + if (_xrRemotePreviewManager) { + _xrRemotePreviewManager->resume(); + } + #endif + } +#endif +} + +void XRInterface::onRenderDestroy() { +#if CC_USE_XR + CC_LOG_INFO("[XR] onRenderDestroy"); + xr::XrEntry::getInstance()->destroyXrInstance(); + xr::XrEntry::destroyInstance(); + _isXrEntryInstanceValid = false; + if (_jsPoseEventArray != nullptr) { + _jsPoseEventArray->unroot(); + _jsPoseEventArray->decRef(); + _jsPoseEventArray = nullptr; + } + #if CC_USE_XR_REMOTE_PREVIEW + if (_xrRemotePreviewManager) { + _xrRemotePreviewManager->stop(); + } + #endif +#endif +} +// render thread lifecycle + +// gfx +void XRInterface::preGFXDeviceInitialize(gfx::API gfxApi) { +#if CC_USE_XR + CC_LOG_INFO("[XR] preGFXDeviceInitialize.api.%d | Multi Thread.%d", gfxApi, gfx::DeviceAgent::getInstance() ? 1 : 0); + setXRConfig(xr::XRConfigKey::MULTITHREAD_MODE, gfx::DeviceAgent::getInstance() != nullptr); + xr::XrEntry::getInstance()->setXRConfig(xr::XRConfigKey::RENDER_THREAD_ID, static_cast(gettid())); + + if (gfxApi == gfx::API::GLES3 || gfxApi == gfx::API::VULKAN) { + #if !XR_OEM_PICO + std::string graphicsApiName = gfxApi == gfx::API::GLES3 ? GraphicsApiOpenglES : GraphicsApiVulkan_1_1; + xr::XrEntry::getInstance()->createXrInstance(graphicsApiName.c_str()); + #endif + } +#else + CC_UNUSED_PARAM(gfxApi); +#endif +} + +void XRInterface::postGFXDeviceInitialize(gfx::API gfxApi) { +#if CC_USE_XR + CC_LOG_INFO("[XR] postGFXDeviceInitialize.api.%d", gfxApi); + if (gfxApi == gfx::API::GLES3) { + #if CC_USE_GLES3 + xr::XrEntry::getInstance()->initXrSession(_gles3wLoadFuncProc, + _gles3GPUContext->eglDisplay, + _gles3GPUContext->eglConfig, + _gles3GPUContext->eglDefaultContext); + #endif + } else if (gfxApi == gfx::API::VULKAN) { + #if CC_USE_VULKAN + int vkQueueFamilyIndex = xr::XrEntry::getInstance()->getXRConfig(cc::xr::XRConfigKey::VK_QUEUE_FAMILY_INDEX).getInt(); + cc::xr::XrEntry::getInstance()->initXrSession(_vkInstance, _vkPhysicalDevice, _vkDevice, vkQueueFamilyIndex); + #endif + } +#else + CC_UNUSED_PARAM(gfxApi); +#endif +} + +const xr::XRSwapchain &XRInterface::doGFXDeviceAcquire(gfx::API gfxApi) { +#if CC_USE_XR + // CC_LOG_INFO("[XR] doGFXDeviceAcquire.api.%d", gfxApi); + if (gfxApi == gfx::API::GLES3 || gfxApi == gfx::API::VULKAN) { + return xr::XrEntry::getInstance()->acquireXrSwapchain(static_cast(gfxApi)); + } +#else + CC_UNUSED_PARAM(gfxApi); +#endif + return _acquireSwapchain; +} + +bool XRInterface::isGFXDeviceNeedsPresent(gfx::API gfxApi) { + CC_UNUSED_PARAM(gfxApi); +#if CC_USE_XR + // CC_LOG_INFO("[XR] isGFXDeviceNeedsPresent.api.%d", gfxApi); + // if (gfxApi == gfx::API::GLES3 || gfxApi == gfx::API::VULKAN) { + // } + return xr::XrEntry::getInstance()->getXRConfig(cc::xr::XRConfigKey::PRESENT_ENABLE).getBool(); +#else + return true; +#endif +} + +void XRInterface::postGFXDevicePresent(gfx::API gfxApi) { +#if CC_USE_XR + // CC_LOG_INFO("[XR] postGFXDevicePresent.api.%d", gfxApi); + if (gfxApi == gfx::API::GLES3 || gfxApi == gfx::API::VULKAN) { + } +#else + CC_UNUSED_PARAM(gfxApi); +#endif +} + +void XRInterface::createXRSwapchains() { +#if CC_USE_XR + CC_LOG_INFO("[XR] createXRSwapchains"); + if (gfx::DeviceAgent::getInstance()) { + ENQUEUE_MESSAGE_0(gfx::DeviceAgent::getInstance()->getMessageQueue(), + CreateXRSwapchains, + { + CC_LOG_INFO("[XR] [RT] initXrSwapchains"); + xr::XrEntry::getInstance()->setXRConfig(xr::XRConfigKey::RENDER_THREAD_ID, (int)gettid()); + JniHelper::getEnv(); + xr::XrEntry::getInstance()->initXrSwapchains(); + }) + } else { + xr::XrEntry::getInstance()->initXrSwapchains(); + } +#endif +} + +const std::vector &XRInterface::getXRSwapchains() { +#if CC_USE_XR + CC_LOG_INFO("[XR] getXRSwapchains"); + if (_xrSwapchains.empty()) { + _xrSwapchains = xr::XrEntry::getInstance()->getCocosXrSwapchains(); + } +#endif + return _xrSwapchains; +} + +gfx::Format XRInterface::getXRSwapchainFormat() { +#if CC_USE_XR + int swapchainFormat = xr::XrEntry::getInstance()->getXRConfig(xr::XRConfigKey::SWAPCHAIN_FORMAT).getInt(); + #if CC_USE_GLES3 + if (swapchainFormat == GL_SRGB_ALPHA_EXT) { + return gfx::Format::SRGB8_A8; + } + + if (swapchainFormat == GL_RGBA8) { + return gfx::Format::RGBA8; + } + + if (swapchainFormat == GL_BGRA8_EXT) { + return gfx::Format::BGRA8; + } + #endif + + #if CC_USE_VULKAN + if (swapchainFormat == VK_FORMAT_R8G8B8A8_SRGB) { + return gfx::Format::SRGB8_A8; + } + + if (swapchainFormat == VK_FORMAT_R8G8B8A8_UNORM) { + return gfx::Format::RGBA8; + } + + if (swapchainFormat == VK_FORMAT_B8G8R8A8_UNORM) { + return gfx::Format::BGRA8; + } + #endif +#endif + return gfx::Format::BGRA8; +} + +void XRInterface::updateXRSwapchainTypedID(uint32_t typedID) { +#if CC_USE_XR + #if XR_OEM_HUAWEIVR + _eglSurfaceTypeMap[typedID] = EGLSurfaceType::WINDOW; + #else + _eglSurfaceTypeMap[typedID] = EGLSurfaceType::PBUFFER; + #endif +#else + CC_UNUSED_PARAM(typedID); +#endif +} +// gfx + +// vulkan +#ifdef CC_USE_VULKAN +uint32_t XRInterface::getXRVkApiVersion(uint32_t engineVkApiVersion) { + #if CC_USE_XR + return xr::XrEntry::getInstance()->getXrVkApiVersion(engineVkApiVersion); + #else + return engineVkApiVersion; + #endif +} + +void XRInterface::initializeVulkanData(const PFN_vkGetInstanceProcAddr &addr) { + _vkGetInstanceProcAddr = addr; +} + +VkInstance XRInterface::createXRVulkanInstance(const VkInstanceCreateInfo &instInfo) { + #if CC_USE_XR + _vkInstance = xr::XrEntry::getInstance()->xrVkCreateInstance(instInfo, _vkGetInstanceProcAddr); + _vkPhysicalDevice = xr::XrEntry::getInstance()->getXrVkGraphicsDevice(_vkInstance); + return _vkInstance; + #else + CC_UNUSED_PARAM(instInfo); + return nullptr; + #endif +} + +VkDevice XRInterface::createXRVulkanDevice(const VkDeviceCreateInfo *deviceInfo) { + #if CC_USE_XR + VK_CHECK(xr::XrEntry::getInstance()->xrVkCreateDevice(deviceInfo, _vkGetInstanceProcAddr, _vkPhysicalDevice, &_vkDevice)); + #else + CC_UNUSED_PARAM(deviceInfo); + #endif + return _vkDevice; +} + +VkPhysicalDevice XRInterface::getXRVulkanGraphicsDevice() { + return _vkPhysicalDevice; +} + +void XRInterface::getXRSwapchainVkImages(std::vector &vkImages, uint32_t eye) { + #if CC_USE_XR + xr::XrEntry::getInstance()->getSwapchainImages(vkImages, eye); + #else + CC_UNUSED_PARAM(vkImages); + CC_UNUSED_PARAM(eye); + #endif +} +#endif +// vulkan + +// gles +#ifdef CC_USE_GLES3 +void XRInterface::initializeGLESData(PFNGLES3WLOADPROC gles3wLoadFuncProc, gfx::GLES3GPUContext *gpuContext) { + #if CC_USE_XR + _gles3wLoadFuncProc = gles3wLoadFuncProc; + _gles3GPUContext = gpuContext; + void *eglDisplay = gpuContext->eglDisplay; + void *eglConfig = gpuContext->eglConfig; + void *eglDefaultContext = gpuContext->eglDefaultContext; + CC_LOG_INFO("[XR] initializeGLESData.egl.%p/%p/%p", eglDisplay, eglConfig, eglDefaultContext); + #else + CC_UNUSED_PARAM(gles3wLoadFuncProc); + CC_UNUSED_PARAM(gpuContext); + #endif +} + +void XRInterface::attachGLESFramebufferTexture2D() { + #if CC_USE_XR + xr::XrEntry::getInstance()->attachXrFramebufferTexture2D(); + #endif +} + +EGLSurfaceType XRInterface::acquireEGLSurfaceType(uint32_t typedID) { + #if CC_USE_XR + if (_eglSurfaceTypeMap.count(typedID) > 0) { + return _eglSurfaceTypeMap[typedID]; + } + #else + CC_UNUSED_PARAM(typedID); + #endif + return EGLSurfaceType::WINDOW; +} +#endif +// gles + +// stereo render loop +bool XRInterface::platformLoopStart() { +#if CC_USE_XR + // CC_LOG_INFO("[XR] platformLoopStart"); + if (!_isXrEntryInstanceValid) { + return false; + } + #if CC_USE_XR_REMOTE_PREVIEW + if (_xrRemotePreviewManager && !_xrRemotePreviewManager->isStarted()) { + _xrRemotePreviewManager->start(); + } + #endif + return xr::XrEntry::getInstance()->platformLoopStart(); +#else + return false; +#endif +} + +bool XRInterface::beginRenderFrame() { +#if CC_USE_XR + if (IS_ENABLE_XR_LOG) CC_LOG_INFO("[XR] beginRenderFrame.%d", _committedFrame); + _isEnabledEyeRenderJsCallback = xr::XrEntry::getInstance()->getXRConfig(xr::XRConfigKey::EYE_RENDER_JS_CALLBACK).getBool(); + if (gfx::DeviceAgent::getInstance()) { + static uint64_t frameId = 0; + frameId++; + if (_committedFrame) { + gfx::DeviceAgent::getInstance()->presentWait(); + _committedFrame = false; + } + if (IS_ENABLE_XR_LOG) CC_LOG_INFO("[XR] beginRenderFrame waitFrame.%lld", frameId); + xr::XrEntry::getInstance()->waitFrame(); + ENQUEUE_MESSAGE_1(gfx::DeviceAgent::getInstance()->getMessageQueue(), + BeginRenderFrame, frameId, frameId, + { + if (IS_ENABLE_XR_LOG) CC_LOG_INFO("[XR] [RT] beginRenderFrame.%lld", frameId); + xr::XrEntry::getInstance()->frameStart(); + }) + #if CC_USE_XR_REMOTE_PREVIEW + if (_xrRemotePreviewManager) { + _xrRemotePreviewManager->tick(); + } + #endif + return true; + } + + #if CC_USE_XR_REMOTE_PREVIEW + if (_xrRemotePreviewManager) { + _xrRemotePreviewManager->tick(); + } + #endif + return xr::XrEntry::getInstance()->frameStart(); +#else + return false; +#endif +} + +bool XRInterface::isRenderAllowable() { +#if CC_USE_XR + return xr::XrEntry::getInstance()->isRenderAllowable(); +#else + return false; +#endif +} + +bool XRInterface::beginRenderEyeFrame(uint32_t eye) { +#if CC_USE_XR + if (IS_ENABLE_XR_LOG) CC_LOG_INFO("[XR] beginRenderEyeFrame %d", eye); + if (_isEnabledEyeRenderJsCallback) { + se::AutoHandleScope scope; + se::ValueArray args; + args.emplace_back(se::Value(eye)); + EventDispatcher::doDispatchJsEvent("onXREyeRenderBegin", args); + } + if (gfx::DeviceAgent::getInstance()) { + ENQUEUE_MESSAGE_1(gfx::DeviceAgent::getInstance()->getMessageQueue(), + BeginRenderEyeFrame, eye, eye, + { + if (IS_ENABLE_XR_LOG) CC_LOG_INFO("[XR] [RT] beginRenderEyeFrame %d", eye); + xr::XrEntry::getInstance()->renderLoopStart(eye); + }) + } else { + xr::XrEntry::getInstance()->renderLoopStart(static_cast(eye)); + } +#else + CC_UNUSED_PARAM(eye); +#endif + return true; +} + +bool XRInterface::endRenderEyeFrame(uint32_t eye) { +#if CC_USE_XR + if (IS_ENABLE_XR_LOG) CC_LOG_INFO("[XR] endRenderEyeFrame %d", eye); + if (_isEnabledEyeRenderJsCallback) { + se::AutoHandleScope scope; + se::ValueArray args; + args.emplace_back(se::Value(eye)); + EventDispatcher::doDispatchJsEvent("onXREyeRenderEnd", args); + } + if (gfx::DeviceAgent::getInstance()) { + ENQUEUE_MESSAGE_1(gfx::DeviceAgent::getInstance()->getMessageQueue(), + EndRenderEyeFrame, eye, eye, + { + if (IS_ENABLE_XR_LOG) CC_LOG_INFO("[XR] [RT] endRenderEyeFrame %d", eye); + xr::XrEntry::getInstance()->renderLoopEnd(eye); + }) + } else { + xr::XrEntry::getInstance()->renderLoopEnd(static_cast(eye)); + } +#else + CC_UNUSED_PARAM(eye); +#endif + return true; +} + +bool XRInterface::endRenderFrame() { +#if CC_USE_XR + if (IS_ENABLE_XR_LOG) { + CC_LOG_INFO("[XR] endRenderFrame.%d", + cc::ApplicationManager::getInstance()->getCurrentAppSafe()->getEngine()->getTotalFrames()); + } + + if (gfx::DeviceAgent::getInstance()) { + ENQUEUE_MESSAGE_0(gfx::DeviceAgent::getInstance()->getMessageQueue(), + EndRenderFrame, + { + xr::XrEntry::getInstance()->frameEnd(); + gfx::DeviceAgent::getInstance()->presentSignal(); + if (IS_ENABLE_XR_LOG) CC_LOG_INFO("[XR] [RT] presentSignal endRenderFrame.%d", + cc::ApplicationManager::getInstance()->getCurrentAppSafe()->getEngine()->getTotalFrames()); + }) + _committedFrame = true; + // CC_LOG_INFO("[XR] endRenderFrame pass presentWait errno %d", errno); + } else { + xr::XrEntry::getInstance()->frameEnd(); + #if CC_USE_XR_REMOTE_PREVIEW + if (_xrRemotePreviewManager) { + _xrRemotePreviewManager->tick(); + } + #endif + } +#endif + return true; +} + +bool XRInterface::platformLoopEnd() { +#if CC_USE_XR + if (!_isXrEntryInstanceValid) { + return false; + } + return xr::XrEntry::getInstance()->platformLoopEnd(); +#else + return false; +#endif +} +// stereo render loop + +ccstd::vector XRInterface::getHMDViewPosition(uint32_t eye, int trackingType) { +#if CC_USE_XR + return xr::XrEntry::getInstance()->getHMDViewPosition(eye, trackingType); +#else + CC_UNUSED_PARAM(eye); + CC_UNUSED_PARAM(trackingType); + ccstd::vector res; + res.reserve(3); + return res; +#endif +} + +ccstd::vector XRInterface::getXREyeFov(uint32_t eye) { +#if CC_USE_XR + return xr::XrEntry::getInstance()->getEyeFov(eye); +#else + CC_UNUSED_PARAM(eye); + ccstd::vector res; + res.reserve(4); + return res; +#endif +} + +ccstd::vector XRInterface::getXRViewProjectionData(uint32_t eye, float near, float far) { +#if CC_USE_XR + return xr::XrEntry::getInstance()->computeViewProjection(eye, near, far, 1.0F); +#else + CC_UNUSED_PARAM(eye); + CC_UNUSED_PARAM(near); + CC_UNUSED_PARAM(far); + ccstd::vector res; + res.reserve(16); + return res; +#endif +} + +// renderwindow +xr::XREye XRInterface::getXREyeByRenderWindow(void *window) { + if (!window) { + return xr::XREye::NONE; + } + if (_xrWindowMap.count(window) > 0) { + return _xrWindowMap[window]; + } + return xr::XREye::NONE; +} + +void XRInterface::bindXREyeWithRenderWindow(void *window, xr::XREye eye) { + if (_xrWindowMap.count(window) > 0) { + _xrWindowMap[window] = eye; + } else { + _xrWindowMap.emplace(std::make_pair(window, eye)); + } +} + +void XRInterface::loadImageTrackingData(const std::string &imageInfo) { + // name|@assets/TrackingImage_SpacesTown.png|0.18|0.26 + ccstd::vector segments = StringUtil::split(imageInfo, "|"); + std::string imageName = segments.at(0); + std::string imagePath = segments.at(1); + auto physicalSizeX = static_cast(atof(segments.at(2).c_str())); + auto physicalSizeY = static_cast(atof(segments.at(3).c_str())); + auto *spaceTownImage = new Image(); + spaceTownImage->addRef(); + bool res = spaceTownImage->initWithImageFile(imagePath); + if (!res) { + CC_LOG_ERROR("[XRInterface] loadImageTrackingData init failed, %s!!!", imageInfo.c_str()); + return; + } + uint32_t imageWidth = spaceTownImage->getWidth(); + uint32_t imageHeight = spaceTownImage->getHeight(); + const uint32_t bufferSize = imageWidth * imageHeight * 3; + auto *buffer = new uint8_t[bufferSize]; + for (unsigned int j = 0; j < imageHeight; ++j) { + for (unsigned int i = 0; i < imageWidth; ++i) { + const unsigned int pixel = i + j * imageWidth; + const unsigned int pixelFlip = i + (imageHeight - j - 1) * imageWidth; + + const uint8_t *originalPixel = &spaceTownImage->getData()[static_cast(pixel * 4)]; + uint8_t *convertedPixel = &buffer[static_cast(pixelFlip * 3)]; + convertedPixel[0] = originalPixel[0]; + convertedPixel[1] = originalPixel[1]; + convertedPixel[2] = originalPixel[2]; + } + } + spaceTownImage->release(); + + auto app = CC_CURRENT_APPLICATION(); + if (!app) { + CC_LOG_ERROR("[XRInterface] loadImageTrackingData callback failed, application not exist!!!"); + return; + } + auto engine = app->getEngine(); + CC_ASSERT_NOT_NULL(engine); + engine->getScheduler()->performFunctionInCocosThread([=]() { + xr::XRTrackingImageData candidateImage; + candidateImage.friendlyName = imageName; + candidateImage.bufferSize = bufferSize; + candidateImage.buffer = buffer; + candidateImage.pixelSizeWidth = imageWidth; + candidateImage.pixelSizeHeight = imageHeight; + candidateImage.physicalWidth = physicalSizeX; + candidateImage.physicalHeight = physicalSizeY; + setXRConfig(xr::XRConfigKey::IMAGE_TRACKING_CANDIDATEIMAGE, static_cast(&candidateImage)); + }); +} + +void XRInterface::handleAppCommand(int appCmd) { +#if CC_USE_XR + setXRConfig(xr::XRConfigKey::APP_COMMAND, appCmd); +#else + CC_UNUSED_PARAM(appCmd); +#endif +} + +void XRInterface::adaptOrthographicMatrix(cc::scene::Camera *camera, const ccstd::array &preTransform, Mat4 &proj, Mat4 &view) { +#if CC_USE_XR + const float orthoHeight = camera->getOrthoHeight(); + const float near = camera->getNearClip(); + const float far = camera->getFarClip(); + const float aspect = camera->getAspect(); + auto *swapchain = camera->getWindow()->getSwapchain(); + const auto &orientation = swapchain ? swapchain->getSurfaceTransform() : gfx::SurfaceTransform::IDENTITY; + cc::xr::XREye wndXREye = getXREyeByRenderWindow(camera->getWindow()); + const auto &xrFov = getXREyeFov(static_cast(wndXREye)); + const float ipdValue = getXRConfig(xr::XRConfigKey::DEVICE_IPD).getFloat(); + const float projectionSignY = gfx::Device::getInstance()->getCapabilities().clipSpaceSignY; + Mat4 orthoProj; + auto *systemWindow = CC_GET_MAIN_SYSTEM_WINDOW(); + const float deviceAspect = systemWindow ? (systemWindow->getViewSize().width / systemWindow->getViewSize().height) : 1.777F; + // device's fov may be asymmetry + float radioLeft = 1.0F; + float radioRight = 1.0F; + if (wndXREye == xr::XREye::LEFT) { + radioLeft = fabs(tanf(xrFov[0])) / fabs(tanf(xrFov[1])); + } else if (wndXREye == xr::XREye::RIGHT) { + radioRight = fabs(tanf(xrFov[1])) / fabs(tanf(xrFov[0])); + } + + const float x = orthoHeight * deviceAspect; + const float y = orthoHeight; + Mat4::createOrthographicOffCenter(-x * radioLeft, x * radioRight, -y, y, near, far, + gfx::Device::getInstance()->getCapabilities().clipSpaceMinZ, projectionSignY, + static_cast(orientation), &orthoProj); + // keep scale to [-1, 1] only use offset + orthoProj.m[0] = preTransform[0] / x; + orthoProj.m[5] = preTransform[3] * projectionSignY / y; + + Mat4 eyeCameraProj; + const auto &projFloat = getXRViewProjectionData(static_cast(wndXREye), 0.001F, 1000.0F); + std::memcpy(eyeCameraProj.m, projFloat.data(), sizeof(float) * 16); + Vec4 point{ipdValue * 0.5F, 0.0F, -1.0F, 1.0F}; + eyeCameraProj.transformVector(&point); + + Mat4 objectTranslateMat; + // point in [-1,1] convert to [-aspect,aspect] + if (wndXREye == xr::XREye::LEFT) { + objectTranslateMat.translate(point.x * orthoHeight * 0.5F * aspect, 0.0F, 0.0F); + } else if (wndXREye == xr::XREye::RIGHT) { + objectTranslateMat.translate(-point.x * orthoHeight * 0.5F * aspect, 0.0F, 0.0F); + } + // update view matrix + view = camera->getNode()->getWorldMatrix().getInversed(); + Mat4 scaleMat{}; + scaleMat.scale(camera->getNode()->getWorldScale()); + // remove scale + Mat4::multiply(scaleMat, view, &view); + Mat4::multiply(objectTranslateMat, view, &view); + + // fit width, scale deviceAspect to aspect + const float ndcXScale = getXRConfig(xr::XRConfigKey::SPLIT_AR_GLASSES).getBool() ? (1.0F + point.x * 0.5F) : 1.0F; + const float ndcYScale = aspect / deviceAspect; + Mat4 ndcScaleMat; + ndcScaleMat.scale(ndcXScale, ndcYScale, 1.0F); + + proj = ndcScaleMat * orthoProj; +#else + CC_UNUSED_PARAM(camera); + CC_UNUSED_PARAM(preTransform); + CC_UNUSED_PARAM(proj); + CC_UNUSED_PARAM(view); +#endif +} + +void XRInterface::asyncLoadAssetsImage(const std::string &imagePath) { + auto *assetsImage = new Image(); + assetsImage->addRef(); + bool res = assetsImage->initWithImageFile(imagePath); + if (!res) { + CC_LOG_ERROR("[XRInterface] async load assets image init failed, %s!!!", imagePath.c_str()); + assetsImage->release(); + return; + } + uint32_t imageWidth = assetsImage->getWidth(); + uint32_t imageHeight = assetsImage->getHeight(); + uint32_t bufferSize = assetsImage->getDataLen(); + uint8_t *buffer = nullptr; + if (assetsImage->getRenderFormat() == gfx::Format::RGB8) { + // convert to rgba8 + bufferSize = imageWidth * imageHeight * 4; + buffer = new uint8_t[bufferSize]; + for (uint32_t y = 0; y < imageHeight; y++) { + for (uint32_t x = 0; x < imageWidth; x++) { + const unsigned int pixel = x + y * imageWidth; + const unsigned int pixelFlip = x + (imageHeight - y - 1) * imageWidth; + const uint8_t *originalPixel = &assetsImage->getData()[static_cast(pixel * 3)]; + uint8_t *convertedPixel = nullptr; + if (_isFlipPixelY) { + convertedPixel = &buffer[static_cast(pixelFlip * 4)]; + } else { + convertedPixel = &buffer[static_cast(pixel * 4)]; + } + convertedPixel[0] = originalPixel[0]; + convertedPixel[1] = originalPixel[1]; + convertedPixel[2] = originalPixel[2]; + convertedPixel[3] = 255.0F; + } + } + } else { + buffer = new uint8_t[bufferSize]; + if (_isFlipPixelY) { + for (uint32_t y = 0; y < imageHeight; y++) { + for (uint32_t x = 0; x < imageWidth; x++) { + const unsigned int pixel = x + y * imageWidth; + const unsigned int pixelFlip = x + (imageHeight - y - 1) * imageWidth; + const uint8_t *originalPixel = &assetsImage->getData()[static_cast(pixel * 4)]; + uint8_t *convertedPixel = &buffer[static_cast(pixelFlip * 4)]; + convertedPixel[0] = originalPixel[0]; + convertedPixel[1] = originalPixel[1]; + convertedPixel[2] = originalPixel[2]; + convertedPixel[3] = originalPixel[3]; + } + } + } else { + memcpy(buffer, assetsImage->getData(), bufferSize); + } + } + auto app = CC_CURRENT_APPLICATION(); + if (!app) { + CC_LOG_ERROR("[XRInterface] loadAssetsImage callback failed, application not exist!!!"); + return; + } + auto engine = app->getEngine(); + CC_ASSERT_NOT_NULL(engine); + engine->getScheduler()->performFunctionInCocosThread([=]() { + auto *imageData = new xr::XRTrackingImageData(); + imageData->friendlyName = imagePath; + imageData->bufferSize = bufferSize; + imageData->buffer = buffer; + imageData->pixelSizeWidth = imageWidth; + imageData->pixelSizeHeight = imageHeight; + if (!this->getXRConfig(xr::XRConfigKey::ASYNC_LOAD_ASSETS_IMAGE_RESULTS).getPointer()) { + auto *imagesMapPtr = new std::unordered_map(); + (*imagesMapPtr).emplace(std::make_pair(imagePath, static_cast(imageData))); + this->setXRConfig(xr::XRConfigKey::ASYNC_LOAD_ASSETS_IMAGE_RESULTS, static_cast(imagesMapPtr)); + } else { + auto *imagesMapPtr = static_cast *>(getXRConfig(xr::XRConfigKey::ASYNC_LOAD_ASSETS_IMAGE_RESULTS).getPointer()); + (*imagesMapPtr).emplace(std::make_pair(imagePath, static_cast(imageData))); + } + }); + assetsImage->release(); +} + +#if CC_USE_XR + +extern "C" JNIEXPORT void JNICALL Java_com_cocos_lib_xr_CocosXRApi_onAdbCmd(JNIEnv * /*env*/, jobject /*thiz*/, jstring key, jstring value) { + auto cmdKey = cc::JniHelper::jstring2string(key); + auto cmdValue = cc::JniHelper::jstring2string(value); + if (IS_ENABLE_XR_LOG) { + CC_LOG_INFO("CocosXRApi_onAdbCmd_%s_%s", cmdKey.c_str(), cmdValue.c_str()); + } + cc::xr::XrEntry::getInstance()->setXRConfig(cc::xr::XRConfigKey::ADB_COMMAND, cmdKey.append(":").append(cmdValue)); +} + +extern "C" JNIEXPORT void JNICALL Java_com_cocos_lib_xr_CocosXRApi_onActivityLifecycleCallback(JNIEnv * /*env*/, jobject /*thiz*/, jint type, jstring activityClassName) { + auto name = cc::JniHelper::jstring2string(activityClassName); + if (IS_ENABLE_XR_LOG) { + CC_LOG_INFO("CocosXRApi_onActivityLifecycleCallback_%d_%s", type, name.c_str()); + } + cc::xr::XrEntry::getInstance()->setXRConfig(cc::xr::XRConfigKey::ACTIVITY_LIFECYCLE, type); + cc::xr::XrEntry::getInstance()->setXRConfig(cc::xr::XRConfigKey::ACTIVITY_LIFECYCLE, name); +} + +#endif +} // namespace cc diff --git a/cocos/platform/java/modules/XRInterface.h b/cocos/platform/java/modules/XRInterface.h new file mode 100644 index 0000000..f82a7e4 --- /dev/null +++ b/cocos/platform/java/modules/XRInterface.h @@ -0,0 +1,140 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/Ptr.h" +#include "base/ThreadPool.h" +#include "platform/interfaces/modules/IXRInterface.h" +#if CC_USE_XR_REMOTE_PREVIEW + #include "xr/XRRemotePreviewManager.h" +#endif +namespace se { +class Object; +} + +namespace cc { + +class XRInterface : public IXRInterface { +public: + xr::XRVendor getVendor() override; + xr::XRConfigValue getXRConfig(xr::XRConfigKey key) override; + void setXRConfig(xr::XRConfigKey key, xr::XRConfigValue value) override; + + uint32_t getRuntimeVersion() override; + void initialize(void *javaVM, void *activity) override; + + // render thread lifecycle + void onRenderPause() override; + void onRenderResume() override; + void onRenderDestroy() override; + // render thread lifecycle + + // gfx + void preGFXDeviceInitialize(gfx::API gfxApi) override; + void postGFXDeviceInitialize(gfx::API gfxApi) override; + const xr::XRSwapchain &doGFXDeviceAcquire(gfx::API gfxApi) override; + bool isGFXDeviceNeedsPresent(gfx::API gfxApi) override; + void postGFXDevicePresent(gfx::API gfxApi) override; + void createXRSwapchains() override; + const std::vector &getXRSwapchains() override; + gfx::Format getXRSwapchainFormat() override; + void updateXRSwapchainTypedID(uint32_t typedID) override; + // gfx + + // vulkan +#ifdef CC_USE_VULKAN + uint32_t getXRVkApiVersion(uint32_t engineVkApiVersion) override; + void initializeVulkanData(const PFN_vkGetInstanceProcAddr &addr) override; + VkInstance createXRVulkanInstance(const VkInstanceCreateInfo &instInfo) override; + VkDevice createXRVulkanDevice(const VkDeviceCreateInfo *deviceInfo) override; + VkPhysicalDevice getXRVulkanGraphicsDevice() override; + void getXRSwapchainVkImages(std::vector &vkImages, uint32_t eye) override; +#endif + // vulkan + + // gles +#ifdef CC_USE_GLES3 + void initializeGLESData(xr::PFNGLES3WLOADPROC gles3wLoadFuncProc, gfx::GLES3GPUContext *gpuContext) override; + void attachGLESFramebufferTexture2D() override; + EGLSurfaceType acquireEGLSurfaceType(uint32_t typedID) override; +#endif + // gles + + // stereo render loop + bool platformLoopStart() override; + bool beginRenderFrame() override; + bool isRenderAllowable() override; + bool beginRenderEyeFrame(uint32_t eye) override; + bool endRenderEyeFrame(uint32_t eye) override; + bool endRenderFrame() override; + bool platformLoopEnd() override; + // stereo render loop + + ccstd::vector getHMDViewPosition(uint32_t eye, int trackingType) override; + ccstd::vector getXRViewProjectionData(uint32_t eye, float near, float far) override; + ccstd::vector getXREyeFov(uint32_t eye) override; + // renderwindow + xr::XREye getXREyeByRenderWindow(void *window) override; + void bindXREyeWithRenderWindow(void *window, xr::XREye eye) override; + void handleAppCommand(int appCmd) override; + void adaptOrthographicMatrix(cc::scene::Camera *camera, const ccstd::array &preTransform, Mat4 &proj, Mat4 &view) override; + +private: + void loadImageTrackingData(const std::string &imageInfo); + void asyncLoadAssetsImage(const std::string &imagePath); + void dispatchGamepadEventInternal(const xr::XRControllerEvent &xrControllerEvent); + void dispatchHandleEventInternal(const xr::XRControllerEvent &xrControllerEvent); + void dispatchHMDEventInternal(const xr::XRControllerEvent &xrControllerEvent); + ControllerEvent _controllerEvent; + se::Object *_jsPoseEventArray{nullptr}; + +#if CC_USE_VULKAN + PFN_vkGetInstanceProcAddr _vkGetInstanceProcAddr{nullptr}; + VkPhysicalDevice _vkPhysicalDevice{nullptr}; + VkInstance _vkInstance{nullptr}; + VkDevice _vkDevice{nullptr}; +#endif + +#if CC_USE_GLES3 + xr::PFNGLES3WLOADPROC _gles3wLoadFuncProc{nullptr}; + gfx::GLES3GPUContext *_gles3GPUContext{nullptr}; +#endif + xr::XRSwapchain _acquireSwapchain; + std::vector _xrSwapchains; + bool _renderPaused{false}; + bool _renderResumed{false}; + bool _isXrEntryInstanceValid{false}; + std::unordered_map _xrWindowMap; + std::unordered_map _eglSurfaceTypeMap; + bool _committedFrame{false}; +#if CC_USE_XR_REMOTE_PREVIEW + cc::IntrusivePtr _xrRemotePreviewManager{nullptr}; +#endif + LegacyThreadPool *_gThreadPool{nullptr}; + bool _isFlipPixelY{false}; + bool _isEnabledEyeRenderJsCallback{false}; +}; + +} // namespace cc diff --git a/cocos/platform/linux/FileUtils-linux.cpp b/cocos/platform/linux/FileUtils-linux.cpp new file mode 100644 index 0000000..3c0217f --- /dev/null +++ b/cocos/platform/linux/FileUtils-linux.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "FileUtils-linux.h" +#include +#include +#include +#include +#include "base/Log.h" +#include "base/memory/Memory.h" + +#define DECLARE_GUARD std::lock_guard mutexGuard(_mutex) +#ifndef CC_RESOURCE_FOLDER_LINUX + #define CC_RESOURCE_FOLDER_LINUX ("/Resources/") +#endif + +namespace cc { + +FileUtils *createFileUtils() { + return ccnew FileUtilsLinux(); +} + +FileUtilsLinux::FileUtilsLinux() { + init(); +} + +bool FileUtilsLinux::init() { + // get application path + char fullpath[256] = {0}; + ssize_t length = readlink("/proc/self/exe", fullpath, sizeof(fullpath) - 1); + + if (length <= 0) { + return false; + } + + fullpath[length] = '\0'; + ccstd::string appPath = fullpath; + _defaultResRootPath = appPath.substr(0, appPath.find_last_of('/')); + _defaultResRootPath.append(CC_RESOURCE_FOLDER_LINUX); + + // Set writable path to $XDG_CONFIG_HOME or ~/.config// if $XDG_CONFIG_HOME not exists. + const char *xdg_config_path = getenv("XDG_CONFIG_HOME"); + ccstd::string xdgConfigPath; + if (xdg_config_path == nullptr) { + xdgConfigPath = getenv("HOME"); + xdgConfigPath.append("/.config"); + } else { + xdgConfigPath = xdg_config_path; + } + _writablePath = xdgConfigPath; + _writablePath.append(appPath.substr(appPath.find_last_of('/'))); + _writablePath.append("/"); + + return FileUtils::init(); +} + +bool FileUtilsLinux::isFileExistInternal(const ccstd::string &filename) const { + if (filename.empty()) { + return false; + } + + ccstd::string strPath = filename; + if (!isAbsolutePath(strPath)) { // Not absolute path, add the default root path at the beginning. + strPath.insert(0, _defaultResRootPath); + } + + struct stat sts; + return (stat(strPath.c_str(), &sts) == 0) && S_ISREG(sts.st_mode); +} + +ccstd::string FileUtilsLinux::getWritablePath() const { + struct stat st; + stat(_writablePath.c_str(), &st); + if (!S_ISDIR(st.st_mode)) { + mkdir(_writablePath.c_str(), 0744); + } + + return _writablePath; +} + +} // namespace cc diff --git a/cocos/platform/linux/FileUtils-linux.h b/cocos/platform/linux/FileUtils-linux.h new file mode 100644 index 0000000..b8df060 --- /dev/null +++ b/cocos/platform/linux/FileUtils-linux.h @@ -0,0 +1,41 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/FileUtils.h" +namespace cc { +class CC_DLL FileUtilsLinux : public FileUtils { +public: + FileUtilsLinux(); + ~FileUtilsLinux() override = default; + + bool isFileExistInternal(const ccstd::string &filename) const override; + ccstd::string getWritablePath() const override; + bool init() override; + +private: + ccstd::string _writablePath; +}; +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/linux/LinuxPlatform.cpp b/cocos/platform/linux/LinuxPlatform.cpp new file mode 100644 index 0000000..b74a506 --- /dev/null +++ b/cocos/platform/linux/LinuxPlatform.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/linux/LinuxPlatform.h" + +#include +#include + +#include "platform/SDLHelper.h" + +#include "modules/Accelerometer.h" +#include "modules/Battery.h" +#include "modules/Network.h" +#include "modules/System.h" +#include "modules/Vibrator.h" + +#if defined(CC_SERVER_MODE) + #include "platform/empty/modules/Screen.h" + #include "platform/empty/modules/SystemWindow.h" + #include "platform/empty/modules/SystemWindowManager.h" +#else + #include "modules/Screen.h" + #include "modules/SystemWindow.h" + #include "modules/SystemWindowManager.h" +#endif + +#include "base/memory/Memory.h" + +namespace { + +} // namespace + +namespace cc { +LinuxPlatform::LinuxPlatform() { +} + +LinuxPlatform::~LinuxPlatform() { +} + +int32_t LinuxPlatform::init() { + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + _windowManager = std::make_shared(); + registerInterface(_windowManager); + registerInterface(std::make_shared()); + return _windowManager->init(); +} + +ISystemWindow *LinuxPlatform::createNativeWindow(uint32_t windowId, void *externalHandle) { + return ccnew SystemWindow(windowId, externalHandle); +} + +static long getCurrentMillSecond() { + long lLastTime; + struct timeval stCurrentTime; + + gettimeofday(&stCurrentTime, NULL); + lLastTime = stCurrentTime.tv_sec * 1000 + stCurrentTime.tv_usec * 0.001; // milliseconds + return lLastTime; +} + +void LinuxPlatform::exit() { + _quit = true; +} + +int32_t LinuxPlatform::loop() { + long lastTime = 0L; + long curTime = 0L; + long desiredInterval = 0L; + long actualInterval = 0L; + onResume(); + while (!_quit) { + curTime = getCurrentMillSecond(); + desiredInterval = static_cast(1000.0 / getFps()); + _windowManager->processEvent(); + actualInterval = curTime - lastTime; + if (actualInterval >= desiredInterval) { + lastTime = getCurrentMillSecond(); + runTask(); + } else { + usleep((desiredInterval - curTime + lastTime) * 1000); + } + } + + onDestroy(); + return 0; +} + +} // namespace cc diff --git a/cocos/platform/linux/LinuxPlatform.h b/cocos/platform/linux/LinuxPlatform.h new file mode 100644 index 0000000..df71a52 --- /dev/null +++ b/cocos/platform/linux/LinuxPlatform.h @@ -0,0 +1,56 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/UniversalPlatform.h" + +namespace cc { +class SystemWindow; +class SystemWindowManager; + +class CC_DLL LinuxPlatform : public UniversalPlatform { +public: + LinuxPlatform(); + /** + * Destructor of WindowPlatform. + */ + ~LinuxPlatform() override; + /** + * Implementation of Windows platform initialization. + */ + int32_t init() override; + + void exit() override; + int32_t loop() override; + + ISystemWindow *createNativeWindow(uint32_t windowId, void *externalHandle) override; + +private: + bool _quit{false}; + std::shared_ptr _windowManager{nullptr}; + std::shared_ptr _window{nullptr}; +}; + +} // namespace cc diff --git a/cocos/platform/linux/modules/Accelerometer.cpp b/cocos/platform/linux/modules/Accelerometer.cpp new file mode 100644 index 0000000..0e799f7 --- /dev/null +++ b/cocos/platform/linux/modules/Accelerometer.cpp @@ -0,0 +1,39 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/linux/modules/Accelerometer.h" + +namespace cc { +void Accelerometer::setAccelerometerEnabled(bool isEnabled) { +} + +void Accelerometer::setAccelerometerInterval(float interval) { +} + +const Accelerometer::MotionValue &Accelerometer::getDeviceMotionValue() { + static MotionValue motionValue; + return motionValue; +} + +} // namespace cc diff --git a/cocos/platform/linux/modules/Accelerometer.h b/cocos/platform/linux/modules/Accelerometer.h new file mode 100644 index 0000000..03db5e3 --- /dev/null +++ b/cocos/platform/linux/modules/Accelerometer.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IAccelerometer.h" + +namespace cc { + +class CC_DLL Accelerometer : public IAccelerometer { +public: + /** + * To enable or disable accelerometer. + */ + void setAccelerometerEnabled(bool isEnabled) override; + + /** + * Sets the interval of accelerometer. + */ + void setAccelerometerInterval(float interval) override; + + /** + * Gets the motion value of current device. + */ + const MotionValue &getDeviceMotionValue() override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/linux/modules/Battery.cpp b/cocos/platform/linux/modules/Battery.cpp new file mode 100644 index 0000000..0adc59c --- /dev/null +++ b/cocos/platform/linux/modules/Battery.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/linux/modules/Battery.h" + +namespace cc { + +float Battery::getBatteryLevel() const { + return 1.0F; +} + +} // namespace cc diff --git a/cocos/platform/linux/modules/Battery.h b/cocos/platform/linux/modules/Battery.h new file mode 100644 index 0000000..5bac0f6 --- /dev/null +++ b/cocos/platform/linux/modules/Battery.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IBattery.h" + +namespace cc { + +class CC_DLL Battery : public IBattery { +public: + float getBatteryLevel() const override; +}; + +} // namespace cc diff --git a/cocos/platform/linux/modules/CanvasRenderingContext2DDelegate.cpp b/cocos/platform/linux/modules/CanvasRenderingContext2DDelegate.cpp new file mode 100644 index 0000000..bb4518b --- /dev/null +++ b/cocos/platform/linux/modules/CanvasRenderingContext2DDelegate.cpp @@ -0,0 +1,332 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/linux/modules/CanvasRenderingContext2DDelegate.h" +#include "platform/linux/LinuxPlatform.h" +#include "platform/linux/modules/SystemWindow.h" + +namespace { +#define RGB(r, g, b) (int)((int)r | (((int)g) << 8) | (((int)b) << 16)) +#define RGBA(r, g, b, a) (int)((int)r | (((int)g) << 8) | (((int)b) << 16) | (((int)a) << 24)) +} // namespace + +namespace cc { +//static const char gdefaultFontName[] = "-*-helvetica-medium-o-*-*-24-*-*-*-*-*-iso8859-*"; +//static const char gdefaultFontName[] = "lucidasanstypewriter-bold-24"; +static const char gdefaultFontName[] = "lucidasans-24"; + +CanvasRenderingContext2DDelegate::CanvasRenderingContext2DDelegate() { + SystemWindow *window = BasePlatform::getPlatform()->getInterface(); + CC_ASSERT_NOT_NULL(window); + _dis = reinterpret_cast(window->getDisplay()); + _win = reinterpret_cast(window->getWindowHandle()); +} + +CanvasRenderingContext2DDelegate::~CanvasRenderingContext2DDelegate() { + XFreePixmap(_dis, _pixmap); + XFreeGC(_dis, _gc); +} + +void CanvasRenderingContext2DDelegate::recreateBuffer(float w, float h) { + _bufferWidth = w; + _bufferHeight = h; + if (_bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + auto textureSize = static_cast(_bufferWidth * _bufferHeight * 4); + auto *data = static_cast(malloc(sizeof(int8_t) * textureSize)); + memset(data, 0x00, textureSize); + _imageData.fastSet((uint8_t *)data, textureSize); + + if (_pixmap) { + XFreePixmap(_dis, _pixmap); + _pixmap = 0; + } + if (!_win) { + return; + } + //Screen *scr = DefaultScreenOfDisplay(_dis); + _pixmap = XCreatePixmap(_dis, _win, w, h, 32); + _gc = XCreateGC(_dis, _pixmap, 0, 0); +} + +void CanvasRenderingContext2DDelegate::beginPath() { + // called: set_lineWidth() -> beginPath() -> moveTo() -> lineTo() -> stroke(), when draw line + XSetLineAttributes(_dis, _gc, static_cast(_lineWidth), LineSolid, _lineCap, _lineJoin); + XSetForeground(_dis, _gc, RGB(255, 255, 255)); +} + +void CanvasRenderingContext2DDelegate::closePath() { +} + +void CanvasRenderingContext2DDelegate::moveTo(float x, float y) { + //MoveToEx(_DC, static_cast(x), static_cast(-(y - _bufferHeight - _fontSize)), nullptr); + _x = x; + _y = y; +} + +void CanvasRenderingContext2DDelegate::lineTo(float x, float y) { + //LineTo(_DC, static_cast(x), static_cast(-(y - _bufferHeight - _fontSize))); + XDrawLine(_dis, _pixmap, _gc, _x, _y, x, y); +} + +void CanvasRenderingContext2DDelegate::stroke() { +} + +void CanvasRenderingContext2DDelegate::saveContext() { +} + +void CanvasRenderingContext2DDelegate::restoreContext() { +} + +void CanvasRenderingContext2DDelegate::clearRect(float x, float y, float w, float h) { + if (_bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + + if (_imageData.isNull()) { + return; + } + + recreateBuffer(w, h); +} + +void CanvasRenderingContext2DDelegate::fillRect(float x, float y, float w, float h) { + if (_bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + + XSetForeground(_dis, _gc, _fillStyle); + XFillRectangle(_dis, _pixmap, _gc, x, y, w, h); +} + +void CanvasRenderingContext2DDelegate::fillText(const ccstd::string &text, float x, float y, float /*maxWidth*/) { + if (text.empty() || _bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } + + Point offsetPoint = convertDrawPoint(Point{x, y}, text); + XSetForeground(_dis, _gc, 0xff000000 | _fillStyle); + XSetFont(_dis, _gc, _font->fid); + XDrawString(_dis, _pixmap, _gc, offsetPoint[0], offsetPoint[1], text.c_str(), (int)(text.length())); + XImage *image = XGetImage(_dis, _pixmap, 0, 0, _bufferWidth, _bufferHeight, AllPlanes, ZPixmap); + int width = image->width; + int height = image->height; + unsigned char *data = _imageData.getBytes(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; x++) { + *(((int *)data + (y * width) + x)) = static_cast(XGetPixel(image, x, y)); + } + } +} + +void CanvasRenderingContext2DDelegate::strokeText(const ccstd::string &text, float /*x*/, float /*y*/, float /*maxWidth*/) const { + if (text.empty() || _bufferWidth < 1.0F || _bufferHeight < 1.0F) { + return; + } +} + +CanvasRenderingContext2DDelegate::Size CanvasRenderingContext2DDelegate::measureText(const ccstd::string &text) { + if (text.empty()) + return ccstd::array{0.0f, 0.0f}; + int font_ascent = 0; + int font_descent = 0; + int direction = 0; + XCharStruct overall; + XQueryTextExtents(_dis, _font->fid, text.c_str(), text.length(), &direction, &font_ascent, &font_descent, &overall); + return ccstd::array{static_cast(overall.width), + static_cast(overall.ascent + overall.descent)}; +} + +void CanvasRenderingContext2DDelegate::updateFont(const ccstd::string &fontName, + float fontSize, + bool bold, + bool italic, + bool oblique, + bool /* smallCaps */) { + do { + _fontName = fontName; + _fontSize = static_cast(fontSize); + /// TODO(bug):Remove default settings + ccstd::string fontName = "helvetica"; // default + char serv[1024] = {0}; + ccstd::string slant = ""; + if (italic) { + slant = "*I"; + } else if (oblique) { + slant = "*o"; + } + // *name-bold*Italic(Oblique)*size + snprintf(serv, sizeof(serv) - 1, "*%s%s%s*--%d*", fontName.c_str(), + bold ? "*Bold" : "", + slant.c_str(), + _fontSize); + if (_font) { + XFreeFont(_dis, _font); + _font = 0; + } + + _font = XLoadQueryFont(_dis, serv); + if (!_font) { + static int fontSizes[] = {8, 10, 12, 14, 18, 24}; + int i = 0; + int size = sizeof(fontSizes) / sizeof(fontSizes[0]); + for (i = 0; i < size; ++i) { + if (_fontSize < fontSizes[i]) { + break; + } + } + if (i == 0) { + _fontSize = fontSizes[0]; + } else if (i > 1 && i < size) { + _fontSize = fontSizes[i - 1]; + } else { + _fontSize = fontSizes[size - 1]; + } + snprintf(serv, sizeof(serv) - 1, "*%s*%d*", "lucidasans", _fontSize); + _font = XLoadQueryFont(_dis, serv); + if (!_font) { + _font = XLoadQueryFont(_dis, serv); + } + } + } while (false); +} + +void CanvasRenderingContext2DDelegate::setTextAlign(TextAlign align) { + _textAlign = align; +} + +void CanvasRenderingContext2DDelegate::setTextBaseline(TextBaseline baseline) { + _textBaseLine = baseline; +} + +void CanvasRenderingContext2DDelegate::setFillStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _fillStyle = RGBA(r, g, b, a); +} + +void CanvasRenderingContext2DDelegate::setStrokeStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + _strokeStyle = RGBA(r, g, b, a); +} + +void CanvasRenderingContext2DDelegate::setLineWidth(float lineWidth) { + _lineWidth = lineWidth; +} + +const cc::Data &CanvasRenderingContext2DDelegate::getDataRef() const { + return _imageData; +} + +void CanvasRenderingContext2DDelegate::removeCustomFont() { + XFreeFont(_dis, None); +} + +// x, y offset value +int CanvasRenderingContext2DDelegate::drawText(const ccstd::string &text, int x, int y) { + XTextItem item{const_cast(text.c_str()), static_cast(text.length()), 0, None}; + return XDrawText(_dis, _pixmap, _gc, x, y, &item, 1); +} + +CanvasRenderingContext2DDelegate::Size CanvasRenderingContext2DDelegate::sizeWithText(const wchar_t *pszText, int nLen) { + // if (text.empty()) + // return ccstd::array{0.0f, 0.0f}; + // XFontStruct *fs = XLoadQueryFont(dpy, "cursor"); + // CC_ASSERT(fs); + // int font_ascent = 0; + // int font_descent = 0; + // XCharStruct overall; + // XQueryTextExtents(_dis, fs -> fid, text.c_str(), text.length(), nullptr, &font_ascent, &font_descent, &overall); + // return ccstd::array{static_cast(overall.lbearing), + // static_cast(overall.rbearing)}; + return ccstd::array{0.0F, 0.0F}; +} + +void CanvasRenderingContext2DDelegate::prepareBitmap(int nWidth, int nHeight) { +} + +void CanvasRenderingContext2DDelegate::deleteBitmap() { +} + +void CanvasRenderingContext2DDelegate::fillTextureData() { +} + +ccstd::array CanvasRenderingContext2DDelegate::convertDrawPoint(Point point, const ccstd::string &text) { + int font_ascent = 0; + int font_descent = 0; + int direction = 0; + XCharStruct overall; + XQueryTextExtents(_dis, _font->fid, text.c_str(), text.length(), &direction, &font_ascent, &font_descent, &overall); + int width = overall.width; + if (_textAlign == TextAlign::CENTER) { + point[0] -= width / 2.0f; + } else if (_textAlign == TextAlign::RIGHT) { + point[0] -= width; + } + + if (_textBaseLine == TextBaseline::TOP) { + point[1] += overall.ascent; + } else if (_textBaseLine == TextBaseline::MIDDLE) { + point[1] += (overall.descent - overall.ascent) / 2 - overall.descent; + } else if (_textBaseLine == TextBaseline::BOTTOM) { + point[1] += -overall.descent; + } else if (_textBaseLine == TextBaseline::ALPHABETIC) { + //point[1] -= overall.ascent; + // X11 The default way of drawing text + } + + return point; +} + +void CanvasRenderingContext2DDelegate::fill() { +} + +void CanvasRenderingContext2DDelegate::setLineCap(const ccstd::string &lineCap) { + _lineCap = LineSolid; +} + +void CanvasRenderingContext2DDelegate::setLineJoin(const ccstd::string &lineJoin) { + _lineJoin = JoinRound; +} + +void CanvasRenderingContext2DDelegate::fillImageData(const Data & /* imageData */, + float /* imageWidth */, + float /* imageHeight */, + float /* offsetX */, + float /* offsetY */) { + //XCreateImage(display, visual, DefaultDepth(display,DefaultScreen(display)), ZPixmap, 0, image32, width, height, 32, 0); + //XPutImage(dpy, w, gc, image, 0, 0, 50, 60, 40, 30); +} + +void CanvasRenderingContext2DDelegate::strokeText(const ccstd::string & /* text */, + float /* x */, + float /* y */, + float /* maxWidth */) { +} + +void CanvasRenderingContext2DDelegate::rect(float /* x */, + float /* y */, + float /* w */, + float /* h */) { +} + +} // namespace cc diff --git a/cocos/platform/linux/modules/CanvasRenderingContext2DDelegate.h b/cocos/platform/linux/modules/CanvasRenderingContext2DDelegate.h new file mode 100644 index 0000000..f7f9843 --- /dev/null +++ b/cocos/platform/linux/modules/CanvasRenderingContext2DDelegate.h @@ -0,0 +1,127 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/canvas/ICanvasRenderingContext2D.h" + +#include +#include +#include "base/csscolorparser.h" +#include "base/std/container/array.h" +#include "cocos/bindings/manual/jsb_platform.h" +#include "math/Math.h" +#include "platform/FileUtils.h" + +#include +#include +#include + +namespace cc { + +class CC_DLL CanvasRenderingContext2DDelegate : public ICanvasRenderingContext2D::Delegate { +public: + using Point = ccstd::array; + using Vec2 = ccstd::array; + using Size = ccstd::array; + using Color4F = ccstd::array; + using TextAlign = ICanvasRenderingContext2D::TextAlign; + using TextBaseline = ICanvasRenderingContext2D::TextBaseline; + CanvasRenderingContext2DDelegate(); + ~CanvasRenderingContext2DDelegate() override; + + void recreateBuffer(float w, float h) override; + void beginPath() override; + void closePath() override; + void moveTo(float x, float y) override; + void lineTo(float x, float y) override; + void stroke() override; + void saveContext() override; + void restoreContext() override; + void clearRect(float /*x*/, float /*y*/, float w, float h) override; + void fillRect(float x, float y, float w, float h) override; + void fillText(const ccstd::string &text, float x, float y, float /*maxWidth*/) override; + void strokeText(const ccstd::string &text, float /*x*/, float /*y*/, float /*maxWidth*/) const; + Size measureText(const ccstd::string &text) override; + void updateFont(const ccstd::string &fontName, float fontSize, bool bold, bool italic, bool oblique, bool smallCaps) override; + void setTextAlign(TextAlign align) override; + void setTextBaseline(TextBaseline baseline) override; + void setFillStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override; + void setStrokeStyle(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override; + void setLineWidth(float lineWidth) override; + const cc::Data &getDataRef() const override; + void fill() override; + void setLineCap(const ccstd::string &lineCap) override; + void setLineJoin(const ccstd::string &lineCap) override; + void fillImageData(const Data &imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) override; + void strokeText(const ccstd::string &text, float /*x*/, float /*y*/, float /*maxWidth*/) override; + void rect(float x, float y, float w, float h) override; + void updateData() override {} + void setShadowBlur(float blur) override {} + void setShadowColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override {} + void setShadowOffsetX(float offsetX) override {} + void setShadowOffsetY(float offsetY) override {} + +private: + static wchar_t *utf8ToUtf16(const ccstd::string &str, int *pRetLen = nullptr); + void removeCustomFont(); + int drawText(const ccstd::string &text, int x, int y); + Size sizeWithText(const wchar_t *pszText, int nLen); + void prepareBitmap(int nWidth, int nHeight); + void deleteBitmap(); + void fillTextureData(); + ccstd::array convertDrawPoint(Point point, const ccstd::string &text); + +public: + Display *_dis{nullptr}; + int _screen{0}; + Drawable _win{0}; + Drawable _pixmap{0}; + XFontStruct *_font{0}; + GC _gc; + +private: + int32_t _x{0}; + int32_t _y{0}; + int32_t _lineCap{0}; + int32_t _lineJoin{0}; + +private: + cc::Data _imageData; + ccstd::string _curFontPath; + int _savedDC{0}; + float _lineWidth{0.0F}; + float _bufferWidth{0.0F}; + float _bufferHeight{0.0F}; + + ccstd::string _fontName; + int _fontSize{0}; + Size _textSize; + TextAlign _textAlign{TextAlign::CENTER}; + TextBaseline _textBaseLine{TextBaseline::TOP}; + unsigned long _fillStyle{0}; + unsigned long _strokeStyle{0}; +}; + +} // namespace cc diff --git a/cocos/platform/linux/modules/Network.cpp b/cocos/platform/linux/modules/Network.cpp new file mode 100644 index 0000000..6ee9f5a --- /dev/null +++ b/cocos/platform/linux/modules/Network.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/linux/modules/Network.h" + +namespace cc { + +INetwork::NetworkType Network::getNetworkType() const { + return INetwork::NetworkType::LAN; +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/linux/modules/Network.h b/cocos/platform/linux/modules/Network.h new file mode 100644 index 0000000..b0e1268 --- /dev/null +++ b/cocos/platform/linux/modules/Network.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/INetwork.h" + +namespace cc { + +class CC_DLL Network : public INetwork { +public: + NetworkType getNetworkType() const override; +}; + +} // namespace cc diff --git a/cocos/platform/linux/modules/Screen.cpp b/cocos/platform/linux/modules/Screen.cpp new file mode 100644 index 0000000..bd92a89 --- /dev/null +++ b/cocos/platform/linux/modules/Screen.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/linux/modules/Screen.h" +#include "base/Macros.h" +#include "cocos/bindings/jswrapper/SeApi.h" +// clang-format off +// Some macros in xlib.h conflict with v8 headers. +#include +// clang-format on + +namespace cc { + +int Screen::getDPI() const { + static int dpi = -1; + if (dpi == -1) { + Display *dpy; + char *displayname = NULL; + int scr = 0; /* Screen number */ + dpy = XOpenDisplay(displayname); + /* + * there are 2.54 centimeters to an inch; so there are 25.4 millimeters. + * + * dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch)) + * = N pixels / (M inch / 25.4) + * = N * 25.4 pixels / M inch + */ + double xres = ((((double)DisplayWidth(dpy, scr)) * 25.4) / + ((double)DisplayWidthMM(dpy, scr))); + dpi = (int)(xres + 0.5); + //printf("dpi = %d\n", dpi); + XCloseDisplay(dpy); + } + return dpi; +} + +float Screen::getDevicePixelRatio() const { + return 1; +} + +void Screen::setKeepScreenOn(bool value) { + CC_UNUSED_PARAM(value); +} + +Screen::Orientation Screen::getDeviceOrientation() const { + return Orientation::PORTRAIT; +} + +Vec4 Screen::getSafeAreaEdge() const { + return cc::Vec4(); +} + +bool Screen::isDisplayStats() { + se::AutoHandleScope hs; + se::Value ret; + char commandBuf[100] = "cc.profiler.isShowingStats();"; + se::ScriptEngine::getInstance()->evalString(commandBuf, 100, &ret); + return ret.toBoolean(); +} + +void Screen::setDisplayStats(bool isShow) { + se::AutoHandleScope hs; + char commandBuf[100] = {0}; + sprintf(commandBuf, isShow ? "cc.profiler.showStats();" : "cc.profiler.hideStats();"); + se::ScriptEngine::getInstance()->evalString(commandBuf); +} + +} // namespace cc diff --git a/cocos/platform/linux/modules/Screen.h b/cocos/platform/linux/modules/Screen.h new file mode 100644 index 0000000..10071a0 --- /dev/null +++ b/cocos/platform/linux/modules/Screen.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IScreen.h" + +namespace cc { + +class CC_DLL Screen : public IScreen { +public: + int getDPI() const override; + float getDevicePixelRatio() const override; + void setKeepScreenOn(bool value) override; + Orientation getDeviceOrientation() const override; + Vec4 getSafeAreaEdge() const override; + /** + @brief Get current display stats. + @return bool, is displaying stats or not. + */ + bool isDisplayStats() override; + + /** + @brief set display stats information. + */ + void setDisplayStats(bool isShow) override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/linux/modules/System.cpp b/cocos/platform/linux/modules/System.cpp new file mode 100644 index 0000000..59e696a --- /dev/null +++ b/cocos/platform/linux/modules/System.cpp @@ -0,0 +1,132 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/linux/modules/System.h" +#include +#include +#include "SDL2/SDL_clipboard.h" + +namespace cc { + +using OSType = System::OSType; + +OSType System::getOSType() const { + return OSType::LINUX; +} + +ccstd::string System::getDeviceModel() const { + return "Linux"; +} + +System::LanguageType System::getCurrentLanguage() const { + char *pLanguageName = getenv("LANG"); + if (!pLanguageName) { + return LanguageType::ENGLISH; + } + strtok(pLanguageName, "_"); + if (!pLanguageName) { + return LanguageType::ENGLISH; + } + + return getLanguageTypeByISO2(pLanguageName); +} + +ccstd::string System::getCurrentLanguageCode() const { + static char code[3] = {0}; + char *pLanguageName = getenv("LANG"); + if (!pLanguageName) { + return "en"; + } + strtok(pLanguageName, "_"); + if (!pLanguageName) { + return "en"; + } + strncpy(code, pLanguageName, 2); + code[2] = '\0'; + return code; +} + +ccstd::string System::getSystemVersion() const { + struct utsname u; + uname(&u); + return u.version; +} + +bool System::openURL(const ccstd::string &url) { + ccstd::string op = ccstd::string("xdg-open '").append(url).append("'"); + return system(op.c_str()) == 0; +} + +System::LanguageType System::getLanguageTypeByISO2(const char *code) const { + // this function is used by all platforms to get system language + // except windows: cocos/platform/win32/CCApplication-win32.cpp + LanguageType ret = LanguageType::ENGLISH; + + if (strncmp(code, "zh", 2) == 0) { + ret = LanguageType::CHINESE; + } else if (strncmp(code, "ja", 2) == 0) { + ret = LanguageType::JAPANESE; + } else if (strncmp(code, "fr", 2) == 0) { + ret = LanguageType::FRENCH; + } else if (strncmp(code, "it", 2) == 0) { + ret = LanguageType::ITALIAN; + } else if (strncmp(code, "de", 2) == 0) { + ret = LanguageType::GERMAN; + } else if (strncmp(code, "es", 2) == 0) { + ret = LanguageType::SPANISH; + } else if (strncmp(code, "nl", 2) == 0) { + ret = LanguageType::DUTCH; + } else if (strncmp(code, "ru", 2) == 0) { + ret = LanguageType::RUSSIAN; + } else if (strncmp(code, "hu", 2) == 0) { + ret = LanguageType::HUNGARIAN; + } else if (strncmp(code, "pt", 2) == 0) { + ret = LanguageType::PORTUGUESE; + } else if (strncmp(code, "ko", 2) == 0) { + ret = LanguageType::KOREAN; + } else if (strncmp(code, "ar", 2) == 0) { + ret = LanguageType::ARABIC; + } else if (strncmp(code, "nb", 2) == 0) { + ret = LanguageType::NORWEGIAN; + } else if (strncmp(code, "pl", 2) == 0) { + ret = LanguageType::POLISH; + } else if (strncmp(code, "tr", 2) == 0) { + ret = LanguageType::TURKISH; + } else if (strncmp(code, "uk", 2) == 0) { + ret = LanguageType::UKRAINIAN; + } else if (strncmp(code, "ro", 2) == 0) { + ret = LanguageType::ROMANIAN; + } else if (strncmp(code, "bg", 2) == 0) { + ret = LanguageType::BULGARIAN; + } else if (strncmp(code, "hi", 2) == 0) { + ret = LanguageType::HINDI; + } + return ret; +} + +void System::copyTextToClipboard(const ccstd::string &text) { + SDL_SetClipboardText(text.c_str()); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/linux/modules/System.h b/cocos/platform/linux/modules/System.h new file mode 100644 index 0000000..dd20d1c --- /dev/null +++ b/cocos/platform/linux/modules/System.h @@ -0,0 +1,69 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/ISystem.h" + +namespace cc { + +class CC_DLL System : public ISystem { +public: + /** + @brief Get target system type. + */ + OSType getOSType() const override; + /** + @brief Get target device model. + */ + ccstd::string getDeviceModel() const override; + /** + @brief Get current language config. + @return Current language config. + */ + LanguageType getCurrentLanguage() const override; + /** + @brief Get current language iso 639-1 code. + @return Current language iso 639-1 code. + */ + ccstd::string getCurrentLanguageCode() const override; + /** + @brief Get system version. + @return system version. + */ + ccstd::string getSystemVersion() const override; + /** + @brief Open url in default browser. + @param String with url to open. + @return True if the resource located by the URL was successfully opened; otherwise false. + */ + bool openURL(const ccstd::string &url) override; + + void copyTextToClipboard(const std::string &text) override; + +private: + LanguageType getLanguageTypeByISO2(const char *code) const; +}; + +} // namespace cc diff --git a/cocos/platform/linux/modules/SystemWindow.cpp b/cocos/platform/linux/modules/SystemWindow.cpp new file mode 100644 index 0000000..43ffcfd --- /dev/null +++ b/cocos/platform/linux/modules/SystemWindow.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/linux/modules/SystemWindow.h" + +#include "base/Log.h" +#include "base/Macros.h" + +// SDL headers +#include +#include "SDL2/SDL.h" +#include "SDL2/SDL_main.h" +#include "SDL2/SDL_syswm.h" +#include "engine/EngineEvents.h" +#include "platform/SDLHelper.h" + +namespace cc { +SystemWindow::SystemWindow(uint32_t windowId, void *externalHandle) +: _windowId(windowId) { + if (externalHandle) { + _windowHandle = reinterpret_cast(externalHandle); + } +} + +SystemWindow::~SystemWindow() { + _windowHandle = 0; + _windowId = 0; +} + +bool SystemWindow::createWindow(const char *title, + int w, int h, int flags) { + _window = SDLHelper::createWindow(title, w, h, flags); + if (!_window) { + return false; + } + + _width = w; + _height = h; + _windowHandle = SDLHelper::getWindowHandle(_window); + return true; +} + +bool SystemWindow::createWindow(const char *title, + int x, int y, int w, + int h, int flags) { + _window = SDLHelper::createWindow(title, x, y, w, h, flags); + if (!_window) { + return false; + } + + _width = w; + _height = h; + _windowHandle = SDLHelper::getWindowHandle(_window); + + return true; +} + +void SystemWindow::closeWindow() { +#ifndef CC_SERVER_MODE + SDL_Event et; + et.type = SDL_QUIT; + SDL_PushEvent(&et); +#endif +} + +uintptr_t SystemWindow::getWindowHandle() const { + return _windowHandle; +} + +uintptr_t SystemWindow::getDisplay() const { + return SDLHelper::getDisplay(_window); +} + +void SystemWindow::setCursorEnabled(bool value) { + SDLHelper::setCursorEnabled(value); +} + +SystemWindow::Size SystemWindow::getViewSize() const { + return Size{static_cast(_width), static_cast(_height)}; +} + +} // namespace cc diff --git a/cocos/platform/linux/modules/SystemWindow.h b/cocos/platform/linux/modules/SystemWindow.h new file mode 100644 index 0000000..8bf9267 --- /dev/null +++ b/cocos/platform/linux/modules/SystemWindow.h @@ -0,0 +1,73 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +#include "platform/interfaces/modules/ISystemWindow.h" + +struct SDL_Window; +namespace cc { +class SDLHelper; +class CC_DLL SystemWindow : public ISystemWindow { + friend class SystemWindowManager; + +public: + explicit SystemWindow(uint32_t windowId, void* externalHandle); + ~SystemWindow() override; + + bool createWindow(const char* title, + int w, int h, int flags) override; + bool createWindow(const char* title, + int x, int y, int w, + int h, int flags) override; + void closeWindow() override; + + virtual uint32_t getWindowId() const override { return _windowId; } + uintptr_t getWindowHandle() const override; + + uintptr_t getDisplay() const; + Size getViewSize() const override; + void setViewSize(uint32_t w, uint32_t h) override { + _width = w; + _height = h; + } + /* + @brief enable/disable(lock) the cursor, default is enabled + */ + void setCursorEnabled(bool value) override; + +private: + SDL_Window* getSDLWindow() const { return _window; } + + uint32_t _width{0}; + uint32_t _height{0}; + + uint32_t _windowId{0}; + uintptr_t _windowHandle{0}; + SDL_Window* _window{nullptr}; +}; + +} // namespace cc diff --git a/cocos/platform/linux/modules/SystemWindowManager.cpp b/cocos/platform/linux/modules/SystemWindowManager.cpp new file mode 100644 index 0000000..a4f41c5 --- /dev/null +++ b/cocos/platform/linux/modules/SystemWindowManager.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "SystemWindowManager.h" +#include "SDL2/SDL_events.h" +#include "platform/BasePlatform.h" +#include "platform/SDLHelper.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" +#include "platform/linux/modules/SystemWindow.h" + +namespace cc { + +int SystemWindowManager::init() { + return SDLHelper::init(); +} + +void SystemWindowManager::processEvent() { + SDL_Event sdlEvent; + while (SDL_PollEvent(&sdlEvent) != 0) { + SDL_Window *sdlWindow = SDL_GetWindowFromID(sdlEvent.window.windowID); + // SDL_Event like SDL_QUIT does not associate a window + if (!sdlWindow) { + SDLHelper::dispatchSDLEvent(0, sdlEvent); + } else { + ISystemWindow *window = getWindowFromSDLWindow(sdlWindow); + CC_ASSERT(window); + uint32_t windowId = window->getWindowId(); + SDLHelper::dispatchSDLEvent(windowId, sdlEvent); + } + } +} + +ISystemWindow *SystemWindowManager::createWindow(const ISystemWindowInfo &info) { + ISystemWindow *window = BasePlatform::getPlatform()->createNativeWindow(_nextWindowId, info.externalHandle); + if (window) { + if (!info.externalHandle) { + window->createWindow(info.title.c_str(), info.x, info.y, info.width, info.height, info.flags); + } + _windows[_nextWindowId] = std::shared_ptr(window); + _nextWindowId++; + } + return window; +} + +ISystemWindow *SystemWindowManager::getWindow(uint32_t windowId) const { + if (windowId == 0) + return nullptr; + + auto iter = _windows.find(windowId); + if (iter != _windows.end()) + return iter->second.get(); + return nullptr; +} + +cc::ISystemWindow *SystemWindowManager::getWindowFromSDLWindow(SDL_Window *window) const { + for (const auto &iter : _windows) { + SystemWindow *sysWindow = static_cast(iter.second.get()); + SDL_Window *sdlWindow = sysWindow->getSDLWindow(); + if (sdlWindow == window) { + return sysWindow; + } + } + return nullptr; +} + +} // namespace cc diff --git a/cocos/platform/linux/modules/SystemWindowManager.h b/cocos/platform/linux/modules/SystemWindowManager.h new file mode 100644 index 0000000..6154069 --- /dev/null +++ b/cocos/platform/linux/modules/SystemWindowManager.h @@ -0,0 +1,54 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "base/std/container/unordered_map.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" + +struct SDL_Window; + +namespace cc { + +class ISystemWindow; + +class SystemWindowManager : public ISystemWindowManager { +public: + SystemWindowManager() = default; + + int init() override; + void processEvent() override; + + ISystemWindow *createWindow(const ISystemWindowInfo &info) override; + ISystemWindow *getWindow(uint32_t windowId) const override; + const SystemWindowMap &getWindows() const override { return _windows; } + + ISystemWindow *getWindowFromSDLWindow(SDL_Window *window) const; + +private: + uint32_t _nextWindowId{1}; // start from 1, 0 means an invalid ID + + SystemWindowMap _windows; +}; +} // namespace cc diff --git a/cocos/platform/linux/modules/Vibrator.cpp b/cocos/platform/linux/modules/Vibrator.cpp new file mode 100644 index 0000000..12e866f --- /dev/null +++ b/cocos/platform/linux/modules/Vibrator.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/linux/modules/Vibrator.h" + +#include "base/Macros.h" + +namespace cc { + +void Vibrator::vibrate(float duration) { + CC_UNUSED_PARAM(duration); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/linux/modules/Vibrator.h b/cocos/platform/linux/modules/Vibrator.h new file mode 100644 index 0000000..b6d68e8 --- /dev/null +++ b/cocos/platform/linux/modules/Vibrator.h @@ -0,0 +1,43 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IVibrator.h" + +namespace cc { + +class CC_DLL Vibrator : public IVibrator { +public: + /** + * Vibrator for the specified amount of time. + * If Vibrator is not supported, then invoking this method has no effect. + * Some platforms limit to a maximum duration of 5 seconds. + * Duration is ignored on iOS due to API limitations. + * @param duration The duration in seconds. + */ + void vibrate(float duration) override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/mac/AppDelegate.h b/cocos/platform/mac/AppDelegate.h new file mode 100644 index 0000000..afe7149 --- /dev/null +++ b/cocos/platform/mac/AppDelegate.h @@ -0,0 +1,32 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#import + +@interface AppDelegate : NSObject +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification; +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication; +@end diff --git a/cocos/platform/mac/AppDelegate.mm b/cocos/platform/mac/AppDelegate.mm new file mode 100644 index 0000000..8ebdb68 --- /dev/null +++ b/cocos/platform/mac/AppDelegate.mm @@ -0,0 +1,52 @@ +/**************************************************************************** + Copyright (c) 2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#import "AppDelegate.h" +#include "base/std/container/string.h" +#include "engine/EngineEvents.h" +#include "platform/mac/MacPlatform.h" + +@interface AppDelegate () { + NSWindow* _window; + cc::MacPlatform* _platform; +} +@end + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification { + _platform = dynamic_cast(cc::BasePlatform::getPlatform()); +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + return _platform->readyToExit() ? NSTerminateNow : NSTerminateLater; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication { + return YES; +} + +@end diff --git a/cocos/platform/mac/KeyCodeHelper.cpp b/cocos/platform/mac/KeyCodeHelper.cpp new file mode 100644 index 0000000..3bfe113 --- /dev/null +++ b/cocos/platform/mac/KeyCodeHelper.cpp @@ -0,0 +1,454 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "KeyCodeHelper.h" +#include + +// Copy from glfw3.h + +/* The unknown key */ +#define GLFW_KEY_UNKNOWN -1 + +/* Printable keys */ +#define GLFW_KEY_SPACE 32 +#define GLFW_KEY_APOSTROPHE 39 /* ' */ +#define GLFW_KEY_COMMA 44 /* , */ +#define GLFW_KEY_MINUS 45 /* - */ +#define GLFW_KEY_PERIOD 46 /* . */ +#define GLFW_KEY_SLASH 47 /* / */ +#define GLFW_KEY_0 48 +#define GLFW_KEY_1 49 +#define GLFW_KEY_2 50 +#define GLFW_KEY_3 51 +#define GLFW_KEY_4 52 +#define GLFW_KEY_5 53 +#define GLFW_KEY_6 54 +#define GLFW_KEY_7 55 +#define GLFW_KEY_8 56 +#define GLFW_KEY_9 57 +#define GLFW_KEY_SEMICOLON 59 /* ; */ +#define GLFW_KEY_EQUAL 61 /* = */ +#define GLFW_KEY_A 65 +#define GLFW_KEY_B 66 +#define GLFW_KEY_C 67 +#define GLFW_KEY_D 68 +#define GLFW_KEY_E 69 +#define GLFW_KEY_F 70 +#define GLFW_KEY_G 71 +#define GLFW_KEY_H 72 +#define GLFW_KEY_I 73 +#define GLFW_KEY_J 74 +#define GLFW_KEY_K 75 +#define GLFW_KEY_L 76 +#define GLFW_KEY_M 77 +#define GLFW_KEY_N 78 +#define GLFW_KEY_O 79 +#define GLFW_KEY_P 80 +#define GLFW_KEY_Q 81 +#define GLFW_KEY_R 82 +#define GLFW_KEY_S 83 +#define GLFW_KEY_T 84 +#define GLFW_KEY_U 85 +#define GLFW_KEY_V 86 +#define GLFW_KEY_W 87 +#define GLFW_KEY_X 88 +#define GLFW_KEY_Y 89 +#define GLFW_KEY_Z 90 +#define GLFW_KEY_LEFT_BRACKET 91 /* [ */ +#define GLFW_KEY_BACKSLASH 92 /* \ */ +#define GLFW_KEY_RIGHT_BRACKET 93 /* ] */ +#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */ +#define GLFW_KEY_WORLD_1 161 /* non-US #1 */ +#define GLFW_KEY_WORLD_2 162 /* non-US #2 */ + +/* Function keys */ +#define GLFW_KEY_ESCAPE 256 +#define GLFW_KEY_ENTER 257 +#define GLFW_KEY_TAB 258 +#define GLFW_KEY_BACKSPACE 259 +#define GLFW_KEY_INSERT 260 +#define GLFW_KEY_DELETE 261 +#define GLFW_KEY_RIGHT 262 +#define GLFW_KEY_LEFT 263 +#define GLFW_KEY_DOWN 264 +#define GLFW_KEY_UP 265 +#define GLFW_KEY_PAGE_UP 266 +#define GLFW_KEY_PAGE_DOWN 267 +#define GLFW_KEY_HOME 268 +#define GLFW_KEY_END 269 +#define GLFW_KEY_CAPS_LOCK 280 +#define GLFW_KEY_SCROLL_LOCK 281 +#define GLFW_KEY_NUM_LOCK 282 +#define GLFW_KEY_PRINT_SCREEN 283 +#define GLFW_KEY_PAUSE 284 +#define GLFW_KEY_F1 290 +#define GLFW_KEY_F2 291 +#define GLFW_KEY_F3 292 +#define GLFW_KEY_F4 293 +#define GLFW_KEY_F5 294 +#define GLFW_KEY_F6 295 +#define GLFW_KEY_F7 296 +#define GLFW_KEY_F8 297 +#define GLFW_KEY_F9 298 +#define GLFW_KEY_F10 299 +#define GLFW_KEY_F11 300 +#define GLFW_KEY_F12 301 +#define GLFW_KEY_F13 302 +#define GLFW_KEY_F14 303 +#define GLFW_KEY_F15 304 +#define GLFW_KEY_F16 305 +#define GLFW_KEY_F17 306 +#define GLFW_KEY_F18 307 +#define GLFW_KEY_F19 308 +#define GLFW_KEY_F20 309 +#define GLFW_KEY_F21 310 +#define GLFW_KEY_F22 311 +#define GLFW_KEY_F23 312 +#define GLFW_KEY_F24 313 +#define GLFW_KEY_F25 314 +#define GLFW_KEY_KP_0 320 +#define GLFW_KEY_KP_1 321 +#define GLFW_KEY_KP_2 322 +#define GLFW_KEY_KP_3 323 +#define GLFW_KEY_KP_4 324 +#define GLFW_KEY_KP_5 325 +#define GLFW_KEY_KP_6 326 +#define GLFW_KEY_KP_7 327 +#define GLFW_KEY_KP_8 328 +#define GLFW_KEY_KP_9 329 +#define GLFW_KEY_KP_DECIMAL 330 +#define GLFW_KEY_KP_DIVIDE 331 +#define GLFW_KEY_KP_MULTIPLY 332 +#define GLFW_KEY_KP_SUBTRACT 333 +#define GLFW_KEY_KP_ADD 334 +#define GLFW_KEY_KP_ENTER 335 +#define GLFW_KEY_KP_EQUAL 336 +#define GLFW_KEY_LEFT_SHIFT 340 +#define GLFW_KEY_LEFT_CONTROL 341 +#define GLFW_KEY_LEFT_ALT 342 +#define GLFW_KEY_LEFT_SUPER 343 +#define GLFW_KEY_RIGHT_SHIFT 344 +#define GLFW_KEY_RIGHT_CONTROL 345 +#define GLFW_KEY_RIGHT_ALT 346 +#define GLFW_KEY_RIGHT_SUPER 347 +#define GLFW_KEY_MENU 348 + +#define GLFW_KEY_LAST GLFW_KEY_MENU + +namespace { +// Modifier Key State +bool stateShiftLeft = false; +bool stateShiftRight = false; +bool stateControlLeft = false; +bool stateControlRight = false; +bool stateOptionLeft = false; +bool stateOptionRight = false; + +// Refer to https://github.com/glfw/glfw/blob/master/src/cocoa_window.m. +int keyCodes[0xff + 1] = {-1}; + +void init() { + keyCodes[0x1D] = GLFW_KEY_0; + keyCodes[0x12] = GLFW_KEY_1; + keyCodes[0x13] = GLFW_KEY_2; + keyCodes[0x14] = GLFW_KEY_3; + keyCodes[0x15] = GLFW_KEY_4; + keyCodes[0x17] = GLFW_KEY_5; + keyCodes[0x16] = GLFW_KEY_6; + keyCodes[0x1A] = GLFW_KEY_7; + keyCodes[0x1C] = GLFW_KEY_8; + keyCodes[0x19] = GLFW_KEY_9; + keyCodes[0x00] = GLFW_KEY_A; + keyCodes[0x0B] = GLFW_KEY_B; + keyCodes[0x08] = GLFW_KEY_C; + keyCodes[0x02] = GLFW_KEY_D; + keyCodes[0x0E] = GLFW_KEY_E; + keyCodes[0x03] = GLFW_KEY_F; + keyCodes[0x05] = GLFW_KEY_G; + keyCodes[0x04] = GLFW_KEY_H; + keyCodes[0x22] = GLFW_KEY_I; + keyCodes[0x26] = GLFW_KEY_J; + keyCodes[0x28] = GLFW_KEY_K; + keyCodes[0x25] = GLFW_KEY_L; + keyCodes[0x2E] = GLFW_KEY_M; + keyCodes[0x2D] = GLFW_KEY_N; + keyCodes[0x1F] = GLFW_KEY_O; + keyCodes[0x23] = GLFW_KEY_P; + keyCodes[0x0C] = GLFW_KEY_Q; + keyCodes[0x0F] = GLFW_KEY_R; + keyCodes[0x01] = GLFW_KEY_S; + keyCodes[0x11] = GLFW_KEY_T; + keyCodes[0x20] = GLFW_KEY_U; + keyCodes[0x09] = GLFW_KEY_V; + keyCodes[0x0D] = GLFW_KEY_W; + keyCodes[0x07] = GLFW_KEY_X; + keyCodes[0x10] = GLFW_KEY_Y; + keyCodes[0x06] = GLFW_KEY_Z; + + keyCodes[0x27] = GLFW_KEY_APOSTROPHE; + keyCodes[0x2A] = GLFW_KEY_BACKSLASH; + keyCodes[0x2B] = GLFW_KEY_COMMA; + keyCodes[0x18] = GLFW_KEY_EQUAL; + keyCodes[0x32] = GLFW_KEY_GRAVE_ACCENT; + keyCodes[0x21] = GLFW_KEY_LEFT_BRACKET; + keyCodes[0x1B] = GLFW_KEY_MINUS; + keyCodes[0x2F] = GLFW_KEY_PERIOD; + keyCodes[0x1E] = GLFW_KEY_RIGHT_BRACKET; + keyCodes[0x29] = GLFW_KEY_SEMICOLON; + keyCodes[0x2C] = GLFW_KEY_SLASH; + keyCodes[0x0A] = GLFW_KEY_WORLD_1; + + keyCodes[0x33] = GLFW_KEY_BACKSPACE; + keyCodes[0x39] = GLFW_KEY_CAPS_LOCK; + keyCodes[0x75] = GLFW_KEY_DELETE; + keyCodes[0x7D] = GLFW_KEY_DOWN; + keyCodes[0x77] = GLFW_KEY_END; + keyCodes[0x24] = GLFW_KEY_ENTER; + keyCodes[0x35] = GLFW_KEY_ESCAPE; + keyCodes[0x7A] = GLFW_KEY_F1; + keyCodes[0x78] = GLFW_KEY_F2; + keyCodes[0x63] = GLFW_KEY_F3; + keyCodes[0x76] = GLFW_KEY_F4; + keyCodes[0x60] = GLFW_KEY_F5; + keyCodes[0x61] = GLFW_KEY_F6; + keyCodes[0x62] = GLFW_KEY_F7; + keyCodes[0x64] = GLFW_KEY_F8; + keyCodes[0x65] = GLFW_KEY_F9; + keyCodes[0x6D] = GLFW_KEY_F10; + keyCodes[0x67] = GLFW_KEY_F11; + keyCodes[0x6F] = GLFW_KEY_F12; + keyCodes[0x69] = GLFW_KEY_F13; + keyCodes[0x6B] = GLFW_KEY_F14; + keyCodes[0x71] = GLFW_KEY_F15; + keyCodes[0x6A] = GLFW_KEY_F16; + keyCodes[0x40] = GLFW_KEY_F17; + keyCodes[0x4F] = GLFW_KEY_F18; + keyCodes[0x50] = GLFW_KEY_F19; + keyCodes[0x5A] = GLFW_KEY_F20; + keyCodes[0x73] = GLFW_KEY_HOME; + keyCodes[0x72] = GLFW_KEY_INSERT; + keyCodes[0x7B] = GLFW_KEY_LEFT; + keyCodes[0x3A] = GLFW_KEY_LEFT_ALT; + keyCodes[0x3B] = GLFW_KEY_LEFT_CONTROL; + keyCodes[0x38] = GLFW_KEY_LEFT_SHIFT; + keyCodes[0x37] = GLFW_KEY_LEFT_SUPER; + keyCodes[0x6E] = GLFW_KEY_MENU; + keyCodes[0x47] = GLFW_KEY_NUM_LOCK; + keyCodes[0x79] = GLFW_KEY_PAGE_DOWN; + keyCodes[0x74] = GLFW_KEY_PAGE_UP; + keyCodes[0x7C] = GLFW_KEY_RIGHT; + keyCodes[0x3D] = GLFW_KEY_RIGHT_ALT; + keyCodes[0x3E] = GLFW_KEY_RIGHT_CONTROL; + keyCodes[0x3C] = GLFW_KEY_RIGHT_SHIFT; + keyCodes[0x36] = GLFW_KEY_RIGHT_SUPER; + keyCodes[0x31] = GLFW_KEY_SPACE; + keyCodes[0x30] = GLFW_KEY_TAB; + keyCodes[0x7E] = GLFW_KEY_UP; + + keyCodes[0x52] = GLFW_KEY_KP_0; + keyCodes[0x53] = GLFW_KEY_KP_1; + keyCodes[0x54] = GLFW_KEY_KP_2; + keyCodes[0x55] = GLFW_KEY_KP_3; + keyCodes[0x56] = GLFW_KEY_KP_4; + keyCodes[0x57] = GLFW_KEY_KP_5; + keyCodes[0x58] = GLFW_KEY_KP_6; + keyCodes[0x59] = GLFW_KEY_KP_7; + keyCodes[0x5B] = GLFW_KEY_KP_8; + keyCodes[0x5C] = GLFW_KEY_KP_9; + keyCodes[0x45] = GLFW_KEY_KP_ADD; + keyCodes[0x41] = GLFW_KEY_KP_DECIMAL; + keyCodes[0x4B] = GLFW_KEY_KP_DIVIDE; + keyCodes[0x4C] = GLFW_KEY_KP_ENTER; + keyCodes[0x51] = GLFW_KEY_KP_EQUAL; + keyCodes[0x43] = GLFW_KEY_KP_MULTIPLY; + keyCodes[0x4E] = GLFW_KEY_KP_SUBTRACT; +} +} // namespace + +void updateModifierKeyState(int keyCodeInWeb) { + if (keyCodeInWeb == 16) { // shift left + stateShiftLeft = !stateShiftLeft; + } else if (keyCodeInWeb == 20016) { // shift right + stateShiftRight = !stateShiftRight; + } else if (keyCodeInWeb == 17) { // ctrl left + stateControlLeft = !stateControlLeft; + } else if (keyCodeInWeb == 20017) { // ctrl right + stateControlRight = !stateControlRight; + } else if (keyCodeInWeb == 18) { // alt left + stateOptionLeft = !stateOptionLeft; + } else if (keyCodeInWeb == 20018) { // alt right + stateOptionRight = !stateOptionRight; + } +} + +cc::KeyboardEvent::Action getModifierKeyAction(int keyCodeInWeb) { + if (keyCodeInWeb == 16) { // shift left + if (stateShiftLeft) { + return cc::KeyboardEvent::Action::PRESS; + } else { + return cc::KeyboardEvent::Action::RELEASE; + } + } else if (keyCodeInWeb == 20016) { // shift right + if (stateShiftRight) { + return cc::KeyboardEvent::Action::PRESS; + } else { + return cc::KeyboardEvent::Action::RELEASE; + } + } else if (keyCodeInWeb == 17) { // ctrl left + if (stateControlLeft) { + return cc::KeyboardEvent::Action::PRESS; + } else { + return cc::KeyboardEvent::Action::RELEASE; + } + } else if (keyCodeInWeb == 20017) { // ctrl right + if (stateControlRight) { + return cc::KeyboardEvent::Action::PRESS; + } else { + return cc::KeyboardEvent::Action::RELEASE; + } + } else if (keyCodeInWeb == 18) { // alt left + if (stateOptionLeft) { + return cc::KeyboardEvent::Action::PRESS; + } else { + return cc::KeyboardEvent::Action::RELEASE; + } + } else if (keyCodeInWeb == 20018) { // alt right + if (stateOptionRight) { + return cc::KeyboardEvent::Action::PRESS; + } else { + return cc::KeyboardEvent::Action::RELEASE; + } + } + return cc::KeyboardEvent::Action::UNKNOWN; +} + +// Refer to: https://github.com/cocos-creator/cocos2d-x-lite/blob/v2.4.0/cocos/platform/desktop/CCGLView-desktop.cpp. +int translateKeycode(int keyCode) { + std::once_flag flag; + std::call_once(flag, init); + + if (keyCode < 0 || keyCode > 0xff) + return -1; + + int key = keyCodes[keyCode]; + int keyInWeb = -1; + if (key >= GLFW_KEY_0 && key <= GLFW_KEY_9) + keyInWeb = key; + else if (key >= GLFW_KEY_A && key <= GLFW_KEY_Z) + keyInWeb = key; + else if (key >= GLFW_KEY_F1 && key <= GLFW_KEY_F12) + keyInWeb = key - 178; + else if (key >= GLFW_KEY_KP_0 && key <= GLFW_KEY_KP_9) + keyInWeb = key - 272 + 10000; // For indicating number in Numberpad, needs to be converted in JS. + else if (key == GLFW_KEY_ESCAPE) + keyInWeb = 27; + else if (key == GLFW_KEY_MINUS) + keyInWeb = 189; + else if (key == GLFW_KEY_EQUAL) + keyInWeb = 187; + else if (key == GLFW_KEY_BACKSLASH) + keyInWeb = 220; + else if (key == GLFW_KEY_GRAVE_ACCENT) + keyInWeb = 192; + else if (key == GLFW_KEY_BACKSPACE) + keyInWeb = 8; + else if (key == GLFW_KEY_ENTER) + keyInWeb = 13; + else if (key == GLFW_KEY_LEFT_BRACKET) + keyInWeb = 219; + else if (key == GLFW_KEY_RIGHT_BRACKET) + keyInWeb = 221; + else if (key == GLFW_KEY_SEMICOLON) + keyInWeb = 186; + else if (key == GLFW_KEY_APOSTROPHE) + keyInWeb = 222; + else if (key == GLFW_KEY_TAB) + keyInWeb = 9; + else if (key == GLFW_KEY_LEFT_CONTROL) + keyInWeb = 17; + else if (key == GLFW_KEY_RIGHT_CONTROL) + keyInWeb = 17 + 20000; // For indicating Left/Right control, needs to be converted in JS. + else if (key == GLFW_KEY_LEFT_SHIFT) + keyInWeb = 16; + else if (key == GLFW_KEY_RIGHT_SHIFT) + keyInWeb = 16 + 20000; // For indicating Left/Right shift, needs to be converted in JS. + else if (key == GLFW_KEY_LEFT_ALT) + keyInWeb = 18; + else if (key == GLFW_KEY_RIGHT_ALT) + keyInWeb = 18 + 20000; // For indicating Left/Right alt, needs to be converted in JS. + else if (key == GLFW_KEY_LEFT_SUPER) + keyInWeb = 91; + else if (key == GLFW_KEY_RIGHT_SUPER) + keyInWeb = 93; + else if (key == GLFW_KEY_UP) + keyInWeb = 38; + else if (key == GLFW_KEY_DOWN) + keyInWeb = 40; + else if (key == GLFW_KEY_LEFT) + keyInWeb = 37; + else if (key == GLFW_KEY_RIGHT) + keyInWeb = 39; + else if (key == GLFW_KEY_MENU) + keyInWeb = 93 + 20000; + else if (key == GLFW_KEY_KP_ENTER) + keyInWeb = 13 + 20000; // For indicating numpad enter, needs to be converted in JS. + else if (key == GLFW_KEY_KP_ADD) + keyInWeb = 107; + else if (key == GLFW_KEY_KP_SUBTRACT) + keyInWeb = 109; + else if (key == GLFW_KEY_KP_MULTIPLY) + keyInWeb = 106; + else if (key == GLFW_KEY_KP_DIVIDE) + keyInWeb = 111; + else if (key == GLFW_KEY_NUM_LOCK) + keyInWeb = 12; + else if (key == GLFW_KEY_F13) + keyInWeb = 124; + else if (key == GLFW_KEY_BACKSPACE) + keyInWeb = 8; + else if (key == GLFW_KEY_HOME) + keyInWeb = 36; + else if (key == GLFW_KEY_PAGE_UP) + keyInWeb = 33; + else if (key == GLFW_KEY_PAGE_DOWN) + keyInWeb = 34; + else if (key == GLFW_KEY_END) + keyInWeb = 35; + else if (key == GLFW_KEY_COMMA) + keyInWeb = 188; + else if (key == GLFW_KEY_PERIOD) + keyInWeb = 190; + else if (key == GLFW_KEY_SLASH) + keyInWeb = 191; + else if (key == GLFW_KEY_SPACE) + keyInWeb = 32; + else if (key == GLFW_KEY_DELETE) + keyInWeb = 46; + else if (key == GLFW_KEY_KP_DECIMAL) + keyInWeb = 110; + else if (key == GLFW_KEY_CAPS_LOCK) + keyInWeb = 20; + + return keyInWeb; +} diff --git a/cocos/platform/mac/KeyCodeHelper.h b/cocos/platform/mac/KeyCodeHelper.h new file mode 100644 index 0000000..6617228 --- /dev/null +++ b/cocos/platform/mac/KeyCodeHelper.h @@ -0,0 +1,31 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#import "cocos/bindings/event/EventDispatcher.h" + +extern int translateKeycode(int); +extern void updateModifierKeyState(int keyCodeInWeb); +extern cc::KeyboardEvent::Action getModifierKeyAction(int keyCodeInWeb); diff --git a/cocos/platform/mac/MacPlatform.h b/cocos/platform/mac/MacPlatform.h new file mode 100644 index 0000000..3b7da48 --- /dev/null +++ b/cocos/platform/mac/MacPlatform.h @@ -0,0 +1,67 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/UniversalPlatform.h" + +namespace cc { +class SystemWindowManager; +class MacPlatform : public UniversalPlatform { +public: + MacPlatform() = default; + /** + * Destructor of WindowPlatform. + */ + ~MacPlatform() override; + /** + * Implementation of Windows platform initialization. + */ + int32_t init() override; + + /** + * @brief Start base platform initialization. + */ + int32_t run(int argc, const char **argv) override; + + ISystemWindow *createNativeWindow(uint32_t windowId, void *externalHandle) override; + + bool readyToExit(); + void exit() override; + /** + * @brief Implement the main logic of the base platform. + */ + int32_t loop() override; + void setFps(int32_t fps) override; + + void onPause() override; + void onResume() override; + void onClose() override; + void pollEvent() override; +private: + bool _readyToExit{false}; + std::shared_ptr _windowManager{nullptr}; +}; + +} // namespace cc diff --git a/cocos/platform/mac/MacPlatform.mm b/cocos/platform/mac/MacPlatform.mm new file mode 100644 index 0000000..722b358 --- /dev/null +++ b/cocos/platform/mac/MacPlatform.mm @@ -0,0 +1,153 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/mac/MacPlatform.h" +#include "platform/interfaces/OSInterface.h" +#include "platform/mac/AppDelegate.h" + +#include "modules/Accelerometer.h" +#include "modules/Battery.h" +#include "modules/Network.h" +#include "modules/System.h" +#include "modules/Vibrator.h" +#include "platform/SDLHelper.h" +#if defined(CC_SERVER_MODE) + #include "platform/empty/modules/Screen.h" + #include "platform/empty/modules/SystemWindow.h" + #include "platform/empty/modules/SystemWindowManager.h" +#else + #include "modules/Screen.h" + #include "modules/SystemWindow.h" + #include "modules/SystemWindowManager.h" +#endif + +#import + +#include "base/memory/Memory.h" + +extern int cocos_main(int argc, const char **argv); + +namespace cc { + +MacPlatform::~MacPlatform() { +} + +int32_t MacPlatform::init() { + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + _windowManager = std::make_shared(); + registerInterface(_windowManager); + registerInterface(std::make_shared()); + return _windowManager->init(); +} + +bool MacPlatform::readyToExit() { + return _readyToExit; +} + +void MacPlatform::exit() { + if(!_readyToExit) { + [[NSApplication sharedApplication] replyToApplicationShouldTerminate:true]; + _readyToExit = true; + } +} + +int32_t MacPlatform::loop(void) { +#if CC_EDITOR + runTask(); + return 1; +#else + while(!_readyToExit) { + pollEvent(); + runTask(); + } + onDestroy(); + return 0; +#endif +} + +int32_t MacPlatform::run(int argc, const char **argv) { +#if defined(CC_SERVER_MODE) + cocos_main(argc, argv); + while (true) { + runTask(); + } + return 0; +#else + NSArray *arguments = [[NSProcessInfo processInfo] arguments]; + argc = static_cast(arguments.count); + std::vector argVec; + argVec.reserve(argc); + for (id arg in arguments) { + argVec.emplace_back([arg UTF8String]); + } + + id delegate = [[AppDelegate alloc] init]; + [NSApp setDelegate:delegate]; + + if(cocos_main(argc, argVec.data()) != 0) { + return -1; + } + + return loop(); +#endif +} + +void MacPlatform::setFps(int32_t fps) { + if(fps != getFps()) { + UniversalPlatform::setFps(fps); + } +} + +void MacPlatform::onPause() { + cc::WindowEvent ev; + ev.type = cc::WindowEvent::Type::HIDDEN; + cc::events::WindowEvent::broadcast(ev); +} + +void MacPlatform::onResume() { + cc::WindowEvent ev; + ev.type = cc::WindowEvent::Type::SHOW; + cc::events::WindowEvent::broadcast(ev); +} + +void MacPlatform::onClose() { + cc::WindowEvent ev; + ev.type = cc::WindowEvent::Type::CLOSE; + cc::events::WindowEvent::broadcast(ev); +} + +cc::ISystemWindow *MacPlatform::createNativeWindow(uint32_t windowId, void *externalHandle) { + return ccnew SystemWindow(windowId, externalHandle); +} + +void MacPlatform::pollEvent() { + _windowManager->processEvent(); +} + +} // namespace cc diff --git a/cocos/platform/mac/View.h b/cocos/platform/mac/View.h new file mode 100644 index 0000000..adc21ab --- /dev/null +++ b/cocos/platform/mac/View.h @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#import +#import + +@interface View : NSView +- (int)getWindowId; +@property (nonatomic, assign) id device; + +@end diff --git a/cocos/platform/mac/View.mm b/cocos/platform/mac/View.mm new file mode 100644 index 0000000..160d2fa --- /dev/null +++ b/cocos/platform/mac/View.mm @@ -0,0 +1,184 @@ +/**************************************************************************** + Copyright (c) 2020-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#import "View.h" +#import +#import +#import +#import +#import "KeyCodeHelper.h" +#import "application/ApplicationManager.h" +#import "cocos/bindings/event/EventDispatcher.h" +#import "platform/mac/AppDelegate.h" +#import "platform/mac/modules/SystemWindow.h" +#import "platform/mac/modules/SystemWindowManager.h" + +#include "SDL2/SDL.h" + +static int MetalViewEventWatch(void* userData, SDL_Event*event) { + if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + @autoreleasepool { + auto *view = (__bridge View *)userData; + if ([view getWindowId] == event->window.windowID) { + [view viewDidChangeBackingProperties]; + } + } + } + return 0; +} + +@implementation View { + cc::MouseEvent _mouseEvent; + cc::KeyboardEvent _keyboardEvent; +} + +- (CALayer *)makeBackingLayer { + CAMetalLayer *layer = [CAMetalLayer layer]; + layer.delegate = self; + layer.autoresizingMask = true; + layer.needsDisplayOnBoundsChange = true; + return layer; +} + +- (instancetype)initWithFrame:(NSRect)frameRect { + if (self = [super initWithFrame:frameRect]) { + // View is used as a subview, so the resize message needs to be handled manually. + SDL_AddEventWatch(MetalViewEventWatch, (__bridge void*)(self)); + [self.window makeFirstResponder:self]; + int pixelRatio = [[NSScreen mainScreen] backingScaleFactor]; + CGSize size = CGSizeMake(frameRect.size.width * pixelRatio, frameRect.size.height * pixelRatio); + // Create CAMetalLayer + self.wantsLayer = YES; + // Config metal layer + CAMetalLayer *layer = (CAMetalLayer *)self.layer; + layer.drawableSize = size; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + layer.device = self.device = [MTLCreateSystemDefaultDevice() autorelease]; + layer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; + self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize; + self.layerContentsPlacement = NSViewLayerContentsPlacementScaleProportionallyToFill; + + // Add tracking area to receive mouse move events. + NSRect rect = {0, 0, size.width, size.height}; + NSTrackingArea *trackingArea = [[[NSTrackingArea alloc] initWithRect:rect + options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect) + owner:self + userInfo:nil] autorelease]; + [self addTrackingArea:trackingArea]; + } + return self; +} + +- (void)drawInMTKView:(MTKView *)view { + //cc::Application::getInstance()->tick(); +} + +- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { + cc::WindowEvent ev; + ev.windowId = [self getWindowId]; + ev.type = cc::WindowEvent::Type::RESIZED; + ev.width = static_cast(size.width); + ev.height = static_cast(size.height); + cc::events::WindowEvent::broadcast(ev); +} + +- (void)displayLayer:(CALayer *)layer { + //cc::Application::getInstance()->tick(); +} + +- (void)setFrameSize:(NSSize)newSize { + CAMetalLayer *layer = (CAMetalLayer *)self.layer; + + CGSize nativeSize = [self convertSizeToBacking:newSize]; + [super setFrameSize:newSize]; + layer.drawableSize = nativeSize; +} + +- (void)viewDidChangeBackingProperties { + [super viewDidChangeBackingProperties]; + CAMetalLayer *layer = (CAMetalLayer *)self.layer; + layer.contentsScale = self.window.backingScaleFactor; + auto size = [[self.window contentView] frame].size; + auto width = size.width * self.window.backingScaleFactor; + auto height = size.height * self.window.backingScaleFactor; + + if (width > 0 && height > 0) { + [super setFrameSize:size]; + layer.drawableSize = CGSizeMake(width, height); + } +} + +- (void)flagsChanged:(NSEvent *)event { + int keyCode = translateKeycode(event.keyCode); + updateModifierKeyState(keyCode); + auto action = getModifierKeyAction(keyCode); + + // NOTE: in some cases, flagsChanged event may return some wrong keyCodes + // For example: + // - when you long press the capslock key, you may get the keyCode -1 + // - when you press ctrl + space, you may get the keyCode 65 + if (action == cc::KeyboardEvent::Action::UNKNOWN) { + return; + } + _keyboardEvent.windowId = [self getWindowId]; + _keyboardEvent.key = keyCode; + _keyboardEvent.action = action; + [self setModifierFlags:event]; + cc::events::Keyboard::broadcast(_keyboardEvent); +} + +- (void)setModifierFlags:(NSEvent *)event { + NSEventModifierFlags modifierFlags = event.modifierFlags; + if (modifierFlags & NSEventModifierFlagShift) + _keyboardEvent.shiftKeyActive = true; + else + _keyboardEvent.shiftKeyActive = false; + + if (modifierFlags & NSEventModifierFlagControl) + _keyboardEvent.ctrlKeyActive = true; + else + _keyboardEvent.ctrlKeyActive = false; + + if (modifierFlags & NSEventModifierFlagOption) + _keyboardEvent.altKeyActive = true; + else + _keyboardEvent.altKeyActive = false; + + if (modifierFlags & NSEventModifierFlagCommand) + _keyboardEvent.metaKeyActive = true; + else + _keyboardEvent.metaKeyActive = false; +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (int)getWindowId { + auto *windowMgr = CC_GET_PLATFORM_INTERFACE(cc::SystemWindowManager); + auto *window = windowMgr->getWindowFromNSWindow([self window]); + return window->getWindowId(); +} +@end diff --git a/cocos/platform/mac/modules/Accelerometer.h b/cocos/platform/mac/modules/Accelerometer.h new file mode 100644 index 0000000..727914c --- /dev/null +++ b/cocos/platform/mac/modules/Accelerometer.h @@ -0,0 +1,49 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IAccelerometer.h" + +namespace cc { + +class Accelerometer : public IAccelerometer { +public: + /** + * To enable or disable accelerometer. + */ + void setAccelerometerEnabled(bool isEnabled) override; + + /** + * Sets the interval of accelerometer. + */ + void setAccelerometerInterval(float interval) override; + + /** + * Gets the motion value of current device. + */ + const MotionValue &getDeviceMotionValue() override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/mac/modules/Accelerometer.mm b/cocos/platform/mac/modules/Accelerometer.mm new file mode 100644 index 0000000..c728906 --- /dev/null +++ b/cocos/platform/mac/modules/Accelerometer.mm @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/mac/modules/Accelerometer.h" + +namespace cc { +void Accelerometer::setAccelerometerEnabled(bool isEnabled) { +} + +void Accelerometer::setAccelerometerInterval(float interval) { +} + +const Accelerometer::MotionValue& Accelerometer::getDeviceMotionValue() { + static MotionValue motionValue; + return motionValue; +} + +} // namespace cc diff --git a/cocos/platform/mac/modules/Battery.h b/cocos/platform/mac/modules/Battery.h new file mode 100644 index 0000000..802b19f --- /dev/null +++ b/cocos/platform/mac/modules/Battery.h @@ -0,0 +1,37 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IBattery.h" + +namespace cc { + +class Battery : public IBattery { +public: + Battery() = default; + float getBatteryLevel() const override; +}; + +} // namespace cc diff --git a/cocos/platform/mac/modules/Battery.mm b/cocos/platform/mac/modules/Battery.mm new file mode 100644 index 0000000..8256186 --- /dev/null +++ b/cocos/platform/mac/modules/Battery.mm @@ -0,0 +1,34 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/mac/modules/Battery.h" + +namespace cc { + +float Battery::getBatteryLevel() const { + return 1.0F; +} + +} // namespace cc diff --git a/cocos/platform/mac/modules/Network.h b/cocos/platform/mac/modules/Network.h new file mode 100644 index 0000000..dea5b74 --- /dev/null +++ b/cocos/platform/mac/modules/Network.h @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/INetwork.h" + +namespace cc { + +class Network : public INetwork { +public: + NetworkType getNetworkType() const override; +}; + +} // namespace cc diff --git a/cocos/platform/mac/modules/Network.mm b/cocos/platform/mac/modules/Network.mm new file mode 100644 index 0000000..be638fc --- /dev/null +++ b/cocos/platform/mac/modules/Network.mm @@ -0,0 +1,58 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/mac/modules/Network.h" +#import +#include +#include +#include "platform/apple/Reachability.h" + +namespace cc { + +INetwork::NetworkType Network::getNetworkType() const { + static Reachability *__reachability = nullptr; + if (__reachability == nullptr) { + __reachability = Reachability::createForInternetConnection(); + __reachability->addRef(); + } + + NetworkType ret = NetworkType::NONE; + Reachability::NetworkStatus status = __reachability->getCurrentReachabilityStatus(); + switch (status) { + case Reachability::NetworkStatus::REACHABLE_VIA_WIFI: + ret = NetworkType::LAN; + break; + case Reachability::NetworkStatus::REACHABLE_VIA_WWAN: + ret = NetworkType::WWAN; + break; + default: + ret = NetworkType::NONE; + break; + } + + return ret; +} + +} // namespace cc diff --git a/cocos/platform/mac/modules/Screen.h b/cocos/platform/mac/modules/Screen.h new file mode 100644 index 0000000..aad576d --- /dev/null +++ b/cocos/platform/mac/modules/Screen.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IScreen.h" + +namespace cc { + +class Screen : public IScreen { +public: + int getDPI() const override; + float getDevicePixelRatio() const override; + void setKeepScreenOn(bool value) override; + Orientation getDeviceOrientation() const override; + Vec4 getSafeAreaEdge() const override; + /** + @brief Get current display stats. + @return bool, is displaying stats or not. + */ + bool isDisplayStats() override; + + /** + @brief set display stats information. + */ + void setDisplayStats(bool isShow) override; +}; + +} // namespace cc diff --git a/cocos/platform/mac/modules/Screen.mm b/cocos/platform/mac/modules/Screen.mm new file mode 100644 index 0000000..10a3de9 --- /dev/null +++ b/cocos/platform/mac/modules/Screen.mm @@ -0,0 +1,88 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/mac/modules/Screen.h" + +#import +#include +#include + +#include "base/Macros.h" +#include "cocos/bindings/jswrapper/SeApi.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" +#include "platform/mac/modules/SystemWindow.h" +#include "application/ApplicationManager.h" +namespace cc { + +int Screen::getDPI() const { + NSScreen *screen = [NSScreen mainScreen]; + NSDictionary *description = [screen deviceDescription]; + NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; + CGSize displayPhysicalSize = CGDisplayScreenSize([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + + return ((displayPixelSize.width / displayPhysicalSize.width) * 25.4f); +} + +float Screen::getDevicePixelRatio() const { +#if CC_EDITOR + auto* global = se::ScriptEngine::getInstance()->getGlobalObject(); + se::Value devicePixelRatioVal; + global->getProperty("devicePixelRatio", &devicePixelRatioVal); + return devicePixelRatioVal.isNumber() ? devicePixelRatioVal.toFloat() : 1.F; +#else + auto* systemWindow = static_cast(CC_GET_MAIN_SYSTEM_WINDOW()); + auto* nsWindow = systemWindow->getNSWindow(); + return [nsWindow backingScaleFactor]; +#endif +} + +void Screen::setKeepScreenOn(bool value) { + CC_UNUSED_PARAM(value); +} + +Screen::Orientation Screen::getDeviceOrientation() const { + return Orientation::PORTRAIT; +} + +Vec4 Screen::getSafeAreaEdge() const { + return cc::Vec4(); +} + +bool Screen::isDisplayStats() { + se::AutoHandleScope hs; + se::Value ret; + char commandBuf[100] = "cc.debug.isDisplayStats();"; + se::ScriptEngine::getInstance()->evalString(commandBuf, 100, &ret); + return ret.toBoolean(); +} + +void Screen::setDisplayStats(bool isShow) { + se::AutoHandleScope hs; + char commandBuf[100] = {0}; + sprintf(commandBuf, "cc.debug.setDisplayStats(%s);", isShow ? "true" : "false"); + se::ScriptEngine::getInstance()->evalString(commandBuf); +} + +} // namespace cc diff --git a/cocos/platform/mac/modules/System.h b/cocos/platform/mac/modules/System.h new file mode 100644 index 0000000..d86d917 --- /dev/null +++ b/cocos/platform/mac/modules/System.h @@ -0,0 +1,66 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/ISystem.h" + +namespace cc { + +class System : public ISystem { +public: + /** + @brief Get target system type. + */ + OSType getOSType() const override; + /** + @brief Get target device model. + */ + ccstd::string getDeviceModel() const override; + /** + @brief Get current language config. + @return Current language config. + */ + LanguageType getCurrentLanguage() const override; + /** + @brief Get current language iso 639-1 code. + @return Current language iso 639-1 code. + */ + ccstd::string getCurrentLanguageCode() const override; + /** + @brief Get system version. + @return system version. + */ + ccstd::string getSystemVersion() const override; + /** + @brief Open url in default browser. + @param String with url to open. + @return True if the resource located by the URL was successfully opened; otherwise false. + */ + bool openURL(const ccstd::string& url) override; + + void copyTextToClipboard(const std::string& text) override; +}; + +} // namespace cc diff --git a/cocos/platform/mac/modules/System.mm b/cocos/platform/mac/modules/System.mm new file mode 100644 index 0000000..562f494 --- /dev/null +++ b/cocos/platform/mac/modules/System.mm @@ -0,0 +1,107 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/mac/modules/System.h" +#import + +#include +#include +#include +#include + +namespace cc { +using OSType = System::OSType; + +OSType System::getOSType() const { + return OSType::MAC; +} + +ccstd::string System::getDeviceModel() const { + struct utsname systemInfo; + uname(&systemInfo); + return systemInfo.machine; +} + +System::LanguageType System::getCurrentLanguage() const { + // get the current language and country config + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSArray *languages = [defaults objectForKey:@"AppleLanguages"]; + NSString *currentLanguage = [languages objectAtIndex:0]; + + // get the current language code.(such as English is "en", Chinese is "zh" and so on) + NSDictionary *temp = [NSLocale componentsFromLocaleIdentifier:currentLanguage]; + NSString *languageCode = [temp objectForKey:NSLocaleLanguageCode]; + + if ([languageCode isEqualToString:@"zh"]) return LanguageType::CHINESE; + if ([languageCode isEqualToString:@"en"]) return LanguageType::ENGLISH; + if ([languageCode isEqualToString:@"fr"]) return LanguageType::FRENCH; + if ([languageCode isEqualToString:@"it"]) return LanguageType::ITALIAN; + if ([languageCode isEqualToString:@"de"]) return LanguageType::GERMAN; + if ([languageCode isEqualToString:@"es"]) return LanguageType::SPANISH; + if ([languageCode isEqualToString:@"nl"]) return LanguageType::DUTCH; + if ([languageCode isEqualToString:@"ru"]) return LanguageType::RUSSIAN; + if ([languageCode isEqualToString:@"ko"]) return LanguageType::KOREAN; + if ([languageCode isEqualToString:@"ja"]) return LanguageType::JAPANESE; + if ([languageCode isEqualToString:@"hu"]) return LanguageType::HUNGARIAN; + if ([languageCode isEqualToString:@"pt"]) return LanguageType::PORTUGUESE; + if ([languageCode isEqualToString:@"ar"]) return LanguageType::ARABIC; + if ([languageCode isEqualToString:@"nb"]) return LanguageType::NORWEGIAN; + if ([languageCode isEqualToString:@"pl"]) return LanguageType::POLISH; + if ([languageCode isEqualToString:@"tr"]) return LanguageType::TURKISH; + if ([languageCode isEqualToString:@"uk"]) return LanguageType::UKRAINIAN; + if ([languageCode isEqualToString:@"ro"]) return LanguageType::ROMANIAN; + if ([languageCode isEqualToString:@"bg"]) return LanguageType::BULGARIAN; + if ([languageCode isEqualToString:@"hi"]) return LanguageType::HINDI; + return LanguageType::ENGLISH; +} + +ccstd::string System::getCurrentLanguageCode() const { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSArray *languages = [defaults objectForKey:@"AppleLanguages"]; + NSString *currentLanguage = [languages objectAtIndex:0]; + return [currentLanguage UTF8String]; +} + +ccstd::string System::getSystemVersion() const { + NSOperatingSystemVersion v = NSProcessInfo.processInfo.operatingSystemVersion; + char version[50] = {0}; + snprintf(version, sizeof(version), "%d.%d.%d", (int)v.majorVersion, (int)v.minorVersion, (int)v.patchVersion); + return version; +} + +bool System::openURL(const ccstd::string &url) { + NSString *msg = [NSString stringWithCString:url.c_str() encoding:NSUTF8StringEncoding]; + NSURL *nsUrl = [NSURL URLWithString:msg]; + return [[NSWorkspace sharedWorkspace] openURL:nsUrl]; +} + +void System::copyTextToClipboard(const std::string &text) { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + NSString *tmp = [NSString stringWithCString:text.c_str() encoding:NSUTF8StringEncoding]; + [pasteboard setString:tmp forType:NSPasteboardTypeString]; +} + +} // namespace cc diff --git a/cocos/platform/mac/modules/SystemWindow.h b/cocos/platform/mac/modules/SystemWindow.h new file mode 100644 index 0000000..8678c3e --- /dev/null +++ b/cocos/platform/mac/modules/SystemWindow.h @@ -0,0 +1,72 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include "platform/interfaces/modules/ISystemWindow.h" + +struct SDL_Window; +namespace cc { +class SDLHelper; +class CC_DLL SystemWindow : public ISystemWindow { + friend class SystemWindowManager; + +public: + explicit SystemWindow(uint32_t windowId, void* externalHandle); + ~SystemWindow() override; + + bool createWindow(const char* title, + int w, int h, int flags) override; + bool createWindow(const char* title, + int x, int y, int w, + int h, int flags) override; + void closeWindow() override; + + virtual uint32_t getWindowId() const override { return _windowId; } + uintptr_t getWindowHandle() const override; + + Size getViewSize() const override; + void setViewSize(uint32_t w, uint32_t h) override { + _width = w; + _height = h; + } + /* + @brief enable/disable(lock) the cursor, default is enabled + */ + void setCursorEnabled(bool value) override; + NSWindow* getNSWindow() const; +private: + SDL_Window* getSDLWindow() const { return _window; } + void initWindowProperty(SDL_Window* window, const char *title, int x, int y, int w, int h); + uint32_t _width{0}; + uint32_t _height{0}; + + uint32_t _windowId{0}; + uintptr_t _windowHandle{0}; + SDL_Window* _window{nullptr}; +}; + +} // namespace cc diff --git a/cocos/platform/mac/modules/SystemWindow.mm b/cocos/platform/mac/modules/SystemWindow.mm new file mode 100644 index 0000000..4575cf3 --- /dev/null +++ b/cocos/platform/mac/modules/SystemWindow.mm @@ -0,0 +1,138 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/mac/modules/SystemWindow.h" +#include "platform/mac/View.h" + +#include "base/Log.h" +#include "base/Macros.h" + +// SDL headers +#include +#include "SDL2/SDL.h" +#include "SDL2/SDL_main.h" +#include "SDL2/SDL_syswm.h" +#include "engine/EngineEvents.h" +#include "platform/SDLHelper.h" +#import +#import +#import +#import +#include "platform/interfaces/modules/IScreen.h" +#include "platform/BasePlatform.h" + +namespace cc { +SystemWindow::SystemWindow(uint32_t windowId, void *externalHandle) +: _windowId(windowId) { + if (externalHandle) { + _windowHandle = reinterpret_cast(externalHandle); + } +} + +SystemWindow::~SystemWindow() { + _windowHandle = 0; + _windowId = 0; +} + +void SystemWindow::initWindowProperty(SDL_Window* window, const char *title, int x, int y, int w, int h) { + CC_ASSERT(window != nullptr); + auto* nsWindow = reinterpret_cast(SDLHelper::getWindowHandle(window)); + NSString *astring = [NSString stringWithUTF8String:title]; + nsWindow.title = astring; + // contentView is created internally by sdl. + NSView *view = nsWindow.contentView; + auto* newView = [[View alloc] initWithFrame:view.frame]; + [view addSubview:newView]; + [nsWindow.contentView setWantsBestResolutionOpenGLSurface:YES]; + [nsWindow makeKeyAndOrderFront:nil]; + _windowHandle = reinterpret_cast(newView); + + auto dpr = [nsWindow backingScaleFactor]; + _width = w * dpr; + _height = h * dpr; +} + +NSWindow* SystemWindow::getNSWindow() const { + CC_ASSERT(_window != nullptr); + return reinterpret_cast(SDLHelper::getWindowHandle(_window)); +} + +bool SystemWindow::createWindow(const char *title, + int w, int h, int flags) { +#if CC_EDITOR + return createWindow(title, 0, 0, w, h, flags); +#else + _window = SDLHelper::createWindow(title, w, h, flags); + if (!_window) { + return false; + } + Vec2 pos = SDLHelper::getWindowPosition(_window); + initWindowProperty(_window, title, pos.x, pos.y, w, h); + return true; +#endif +} + +bool SystemWindow::createWindow(const char *title, + int x, int y, int w, + int h, int flags) { +#if CC_EDITOR + _width = w; + _height = h; + CAMetalLayer *layer = [[CAMetalLayer layer] retain]; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + layer.frame = CGRectMake(x, y, w, h); + [layer setAnchorPoint:CGPointMake(0.f, 0.f)]; + _windowHandle = reinterpret_cast(layer); + return true; +#else + _window = SDLHelper::createWindow(title, x, y, w, h, flags); + if (!_window) { + return false; + } + initWindowProperty(_window, title, x, y, w, h); + return true; +#endif +} + +void SystemWindow::closeWindow() { +#ifndef CC_SERVER_MODE + SDL_Event et; + et.type = SDL_QUIT; + SDL_PushEvent(&et); +#endif +} + +uintptr_t SystemWindow::getWindowHandle() const { + return _windowHandle; +} + +void SystemWindow::setCursorEnabled(bool value) { + SDLHelper::setCursorEnabled(value); +} + +SystemWindow::Size SystemWindow::getViewSize() const { + return Size{static_cast(_width), static_cast(_height)}; +} + +} // namespace cc diff --git a/cocos/platform/mac/modules/SystemWindowManager.h b/cocos/platform/mac/modules/SystemWindowManager.h new file mode 100644 index 0000000..699832e --- /dev/null +++ b/cocos/platform/mac/modules/SystemWindowManager.h @@ -0,0 +1,53 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +#include +#include "base/std/container/unordered_map.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" + +struct SDL_Window; + +namespace cc { + +class ISystemWindow; + +class SystemWindowManager : public ISystemWindowManager { +public: + SystemWindowManager() = default; + + int init() override; + void processEvent() override; + + ISystemWindow *createWindow(const ISystemWindowInfo &info) override; + ISystemWindow *getWindow(uint32_t windowId) const override; + const SystemWindowMap &getWindows() const override { return _windows; } + + ISystemWindow *getWindowFromSDLWindow(SDL_Window *window) const; + ISystemWindow *getWindowFromNSWindow(NSWindow *window) const; +private: + uint32_t _nextWindowId{1}; // start from 1, 0 means an invalid ID + SystemWindowMap _windows; +}; +} // namespace cc diff --git a/cocos/platform/mac/modules/SystemWindowManager.mm b/cocos/platform/mac/modules/SystemWindowManager.mm new file mode 100644 index 0000000..bcfe9f9 --- /dev/null +++ b/cocos/platform/mac/modules/SystemWindowManager.mm @@ -0,0 +1,98 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "SystemWindowManager.h" +#include "SDL2/SDL_events.h" +#include "platform/BasePlatform.h" +#include "platform/SDLHelper.h" +#include "platform/interfaces/modules/ISystemWindowManager.h" +#include "platform/mac/modules/SystemWindow.h" + +namespace cc { + +int SystemWindowManager::init() { + return SDLHelper::init(); +} + +void SystemWindowManager::processEvent() { + SDL_Event sdlEvent; + while (SDL_PollEvent(&sdlEvent) != 0) { + SDL_Window *sdlWindow = SDL_GetWindowFromID(sdlEvent.window.windowID); + // SDL_Event like SDL_QUIT does not associate a window + if (!sdlWindow) { + SDLHelper::dispatchSDLEvent(0, sdlEvent); + } else { + ISystemWindow *window = getWindowFromSDLWindow(sdlWindow); + CC_ASSERT(window); + uint32_t windowId = window->getWindowId(); + SDLHelper::dispatchSDLEvent(windowId, sdlEvent); + } + } +} + +ISystemWindow *SystemWindowManager::createWindow(const ISystemWindowInfo &info) { + ISystemWindow *window = BasePlatform::getPlatform()->createNativeWindow(_nextWindowId, info.externalHandle); + if (window) { + if (!info.externalHandle) { + window->createWindow(info.title.c_str(), info.x, info.y, info.width, info.height, info.flags); + } + _windows[_nextWindowId] = std::shared_ptr(window); + _nextWindowId++; + } + return window; +} + +ISystemWindow *SystemWindowManager::getWindow(uint32_t windowId) const { + if (windowId == 0) + return nullptr; + + auto iter = _windows.find(windowId); + if (iter != _windows.end()) + return iter->second.get(); + return nullptr; +} + +cc::ISystemWindow *SystemWindowManager::getWindowFromSDLWindow(SDL_Window *window) const { + for (const auto &iter : _windows) { + SystemWindow *sysWindow = static_cast(iter.second.get()); + SDL_Window *sdlWindow = sysWindow->getSDLWindow(); + if (sdlWindow == window) { + return sysWindow; + } + } + return nullptr; +} + +ISystemWindow *SystemWindowManager::getWindowFromNSWindow(NSWindow *window) const { + for (const auto &iter : _windows) { + SystemWindow *sysWindow = static_cast(iter.second.get()); + NSWindow *nsWindow = sysWindow->getNSWindow(); + if (nsWindow == window) { + return sysWindow; + } + } + return nullptr; +} + +} // namespace cc diff --git a/cocos/platform/mac/modules/Vibrator.h b/cocos/platform/mac/modules/Vibrator.h new file mode 100644 index 0000000..6208eb7 --- /dev/null +++ b/cocos/platform/mac/modules/Vibrator.h @@ -0,0 +1,43 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/interfaces/modules/IVibrator.h" + +namespace cc { + +class Vibrator : public IVibrator { +public: + /** + * Vibrate for the specified amount of time. + * If vibrate is not supported, then invoking this method has no effect. + * Some platforms limit to a maximum duration of 5 seconds. + * Duration is ignored on iOS due to API limitations. + * @param duration The duration in seconds. + */ + void vibrate(float duration) override; +}; + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/mac/modules/Vibrator.mm b/cocos/platform/mac/modules/Vibrator.mm new file mode 100644 index 0000000..a634333 --- /dev/null +++ b/cocos/platform/mac/modules/Vibrator.mm @@ -0,0 +1,36 @@ +/**************************************************************************** + Copyright (c) 2021-2022 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/mac/modules/Vibrator.h" + +#include "base/Macros.h" + +namespace cc { + +void Vibrator::vibrate(float duration) { + CC_UNUSED_PARAM(duration); +} + +} // namespace cc diff --git a/cocos/platform/ohos/FileUtils-ohos.cpp b/cocos/platform/ohos/FileUtils-ohos.cpp new file mode 100644 index 0000000..c11b70f --- /dev/null +++ b/cocos/platform/ohos/FileUtils-ohos.cpp @@ -0,0 +1,269 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/ohos/FileUtils-ohos.h" +#include +#include +#include +#include +#include +#include +#include "base/Log.h" +#include "base/memory/Memory.h" +#include "base/std/container/string.h" +#include "platform/java/jni/JniHelper.h" + +#define ASSETS_FOLDER_NAME "@assets/" + +#ifndef JCLS_HELPER + #define JCLS_HELPER "com/cocos/lib/CocosHelper" +#endif + +namespace cc { + +ResourceManager *FileUtilsOHOS::ohosResourceMgr = {}; +ccstd::string FileUtilsOHOS::ohosAssetPath = {}; + +namespace { + +ccstd::string rawfilePrefix = "entry/resources/rawfile/"; + +void printRawfiles(ResourceManager *mgr, const ccstd::string &path) { + auto *file = OpenRawFile(mgr, path.c_str()); + if (file) { + HILOG_DEBUG(LOG_APP, "PrintRawfile %{public}s", path.c_str()); + return; + } + + RawDir *dir = OpenRawDir(mgr, path.c_str()); + if (dir) { + auto fileCnt = GetRawFileCount(dir); + for (auto i = 0; i < fileCnt; i++) { + ccstd::string subFile = GetRawFileName(dir, i); + auto newPath = path + "/" + subFile; // NOLINT + auto debugPtr = newPath.c_str(); + HILOG_ERROR(LOG_APP, " find path %{public}s", newPath.c_str()); + printRawfiles(mgr, newPath); + } + } else { + HILOG_ERROR(LOG_APP, "Invalidate path %{public}s", path.c_str()); + } +} +} // namespace + +FileUtils *createFileUtils() { + return ccnew FileUtilsOHOS(); +} + +bool FileUtilsOHOS::initResourceManager(ResourceManager *mgr, const ccstd::string &assetPath, const ccstd::string &moduleName) { + CC_ASSERT(mgr); + ohosResourceMgr = mgr; + if (!assetPath.empty() && assetPath[assetPath.length() - 1] != '/') { + ohosAssetPath = assetPath + "/"; + } else { + ohosAssetPath = assetPath; + } + if (!moduleName.empty()) { + setRawfilePrefix(moduleName + "/resources/rawfile/"); + } + return true; +} + +void FileUtilsOHOS::setRawfilePrefix(const ccstd::string &prefix) { + rawfilePrefix = prefix; +} + +ResourceManager *FileUtilsOHOS::getResourceManager() { + return ohosResourceMgr; +} + +FileUtilsOHOS::FileUtilsOHOS() { + init(); +} + +bool FileUtilsOHOS::init() { + _defaultResRootPath = ASSETS_FOLDER_NAME; + return FileUtils::init(); +} + +FileUtils::Status FileUtilsOHOS::getContents(const ccstd::string &filename, ResizableBuffer *buffer) { + if (filename.empty()) { + return FileUtils::Status::NOT_EXISTS; + } + + ccstd::string fullPath = fullPathForFilename(filename); + if (fullPath.empty()) { + return FileUtils::Status::NOT_EXISTS; + } + + if (fullPath[0] == '/') { + return FileUtils::getContents(fullPath, buffer); + } + + ccstd::string relativePath; + size_t position = fullPath.find(ASSETS_FOLDER_NAME); + if (0 == position) { + // "@assets/" is at the beginning of the path and we don't want it + relativePath = rawfilePrefix + fullPath.substr(strlen(ASSETS_FOLDER_NAME)); + } else { + relativePath = fullPath; + } + + if (nullptr == ohosResourceMgr) { + HILOG_ERROR(LOG_APP, "... FileUtilsAndroid::assetmanager is nullptr"); + return FileUtils::Status::NOT_INITIALIZED; + } + + RawFile *asset = OpenRawFile(ohosResourceMgr, relativePath.c_str()); + if (nullptr == asset) { + HILOG_DEBUG(LOG_APP, "asset (%{public}s) is nullptr", filename.c_str()); + return FileUtils::Status::OPEN_FAILED; + } + + auto size = GetRawFileSize(asset); + buffer->resize(size); + + assert(buffer->buffer()); + + int readsize = ReadRawFile(asset, buffer->buffer(), size); + CloseRawFile(asset); + + // TODO(unknown): read error + if (readsize < size) { + if (readsize >= 0) { + buffer->resize(readsize); + } + return FileUtils::Status::READ_FAILED; + } + + return FileUtils::Status::OK; +} + +bool FileUtilsOHOS::isAbsolutePath(const ccstd::string &strPath) const { + return !strPath.empty() && (strPath[0] == '/' || strPath.find(ASSETS_FOLDER_NAME) == 0); +} + +ccstd::string FileUtilsOHOS::getWritablePath() const { + auto tmp = cc::JniHelper::callStaticStringMethod(JCLS_HELPER, "getWritablePath"); + if (tmp.empty()) { + return "./"; + } + return tmp.append("/"); +} + +bool FileUtilsOHOS::isFileExistInternal(const ccstd::string &strFilePath) const { + if (strFilePath.empty()) return false; + auto filePath = strFilePath; + auto fileFound = false; + + if (strFilePath[0] == '/') { // absolute path + struct stat info; + return ::stat(filePath.c_str(), &info) == 0; + } + + // relative path + if (strFilePath.find(_defaultResRootPath) == 0) { + filePath = rawfilePrefix + filePath.substr(_defaultResRootPath.length()); + } + + auto rawFile = OpenRawFile(ohosResourceMgr, filePath.c_str()); + if (rawFile != nullptr) { + CloseRawFile(rawFile); + return true; + } + return false; +} + +bool FileUtilsOHOS::isDirectoryExistInternal(const ccstd::string &dirPath) const { + if (dirPath.empty()) return false; + ccstd::string dirPathMf = dirPath[dirPath.length() - 1] == '/' ? dirPath.substr(0, dirPath.length() - 1) : dirPath; + + if (dirPathMf[0] == '/') { + struct stat st; + return stat(dirPathMf.c_str(), &st) == 0 && S_ISDIR(st.st_mode); + } + + if (dirPathMf.find(_defaultResRootPath) == 0) { + dirPathMf = rawfilePrefix + dirPathMf.substr(_defaultResRootPath.length(), dirPathMf.length()); + } + assert(ohosResourceMgr); + auto dir = OpenRawDir(ohosResourceMgr, dirPathMf.c_str()); + if (dir != nullptr) { + CloseRawDir(dir); + return true; + } + return false; +} + +ccstd::string FileUtilsOHOS::expandPath(const ccstd::string &input, bool *isRawFile) const { + if (!input.empty() && input[0] == '/') { + if (isRawFile) *isRawFile = false; + return input; + } + const auto fullpath = fullPathForFilename(input); + + if (fullpath.find(_defaultResRootPath) == 0) { + if (isRawFile) *isRawFile = true; + return rawfilePrefix + fullpath.substr(_defaultResRootPath.length(), fullpath.length()); + } + + if (isRawFile) *isRawFile = false; + + return fullpath; +} + +std::pair> FileUtilsOHOS::getFd(const ccstd::string &path) const { + bool isRawFile = false; + const auto fullpath = expandPath(path, &isRawFile); + if (isRawFile) { + RawFile *rf = OpenRawFile(ohosResourceMgr, fullpath.c_str()); + // FIXME: try reuse file + const auto bufSize = GetRawFileSize(rf); + auto fileCache = ccstd::vector(bufSize); + auto *buf = fileCache.data(); + // Fill buffer + const auto readBytes = ReadRawFile(rf, buf, bufSize); + assert(readBytes == bufSize); // read failure ? + auto fd = syscall(__NR_memfd_create, fullpath.c_str(), 0); + { + auto writeBytes = ::write(fd, buf, bufSize); // Write can fail? + assert(writeBytes == bufSize); + ::lseek(fd, 0, SEEK_SET); + } + if (errno != 0) { + const auto *errMsg = strerror(errno); + CC_LOG_ERROR("failed to open buffer fd %s", errMsg); + } + return std::make_pair(fd, [fd]() { + close(fd); + }); + } + + FILE *fp = fopen(fullpath.c_str(), "rb"); + return std::make_pair(fileno(fp), [fp]() { + fclose(fp); + }); +} + +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/ohos/FileUtils-ohos.h b/cocos/platform/ohos/FileUtils-ohos.h new file mode 100644 index 0000000..a9da981 --- /dev/null +++ b/cocos/platform/ohos/FileUtils-ohos.h @@ -0,0 +1,72 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once +//clang-format off +#include +//clang-format on +#include +#include +#include +#include "base/Macros.h" +#include "cocos/platform/FileUtils.h" + +namespace cc { + +class CC_DLL FileUtilsOHOS : public FileUtils { +public: + static bool initResourceManager(ResourceManager *mgr, const ccstd::string &assetPath, const ccstd::string &moduleName); + + static void setRawfilePrefix(const ccstd::string &prefix); + + static ResourceManager *getResourceManager(); + + FileUtilsOHOS(); + ~FileUtilsOHOS() override = default; + + bool init() override; + + FileUtils::Status getContents(const ccstd::string &filename, ResizableBuffer *buffer) override; + + bool isAbsolutePath(const ccstd::string &strPath) const override; + + ccstd::string getWritablePath() const override; + + ccstd::string expandPath(const ccstd::string &input, bool *isRawFile) const; + + std::pair> getFd(const ccstd::string &path) const; + +private: + bool isFileExistInternal(const ccstd::string &strFilePath) const override; + + bool isDirectoryExistInternal(const ccstd::string &dirPath) const override; + + /* weak ref, do not need release */ + static ResourceManager *ohosResourceMgr; + static ccstd::string ohosAssetPath; + + friend class FileUtils; +}; + +} // namespace cc diff --git a/cocos/platform/ohos/OhosPlatform.cpp b/cocos/platform/ohos/OhosPlatform.cpp new file mode 100644 index 0000000..5741346 --- /dev/null +++ b/cocos/platform/ohos/OhosPlatform.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include + +#include "modules/Screen.h" +#include "modules/System.h" +#include "platform/java/jni/glue/JniNativeGlue.h" +#include "platform/java/modules/Accelerometer.h" +#include "platform/java/modules/Battery.h" +#include "platform/java/modules/Network.h" +#include "platform/java/modules/SystemWindow.h" +#include "platform/java/modules/SystemWindowManager.h" +#include "platform/java/modules/Vibrator.h" +#include "platform/ohos/OhosPlatform.h" + +#include "base/memory/Memory.h" +namespace cc { +OhosPlatform::OhosPlatform() { + _jniNativeGlue = JNI_NATIVE_GLUE(); +} + +int OhosPlatform::init() { + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + registerInterface(std::make_shared()); + return 0; +} + +int OhosPlatform::getSdkVersion() const { + return _jniNativeGlue->getSdkVersion(); +} + +int32_t OhosPlatform::run(int argc, const char **argv) { + std::thread mainLogicThread([this, argc, argv]() { + waitWindowInitialized(); + UniversalPlatform::run(argc, argv); + onDestroy(); + }); + mainLogicThread.detach(); + _jniNativeGlue->waitRunning(); + return 0; +} + +void OhosPlatform::waitWindowInitialized() { + _jniNativeGlue->setRunning(true); + while (_jniNativeGlue->isRunning()) { + pollEvent(); + NativeWindowType *wndHandle = _jniNativeGlue->getWindowHandle(); + if (wndHandle != nullptr) { + break; + } + } +} + +void OhosPlatform::exit() { + +} + +int32_t OhosPlatform::loop() { + while (_jniNativeGlue->isRunning()) { + pollEvent(); + runTask(); + } + return 0; +} + +void OhosPlatform::pollEvent() { + _jniNativeGlue->execCommand(); + if (!_jniNativeGlue->isPause()) { + std::this_thread::yield(); + } + _jniNativeGlue->flushTasksOnGameThread(); +} + +ISystemWindow *OhosPlatform::createNativeWindow(uint32_t windowId, void *externalHandle) { + return ccnew SystemWindow(windowId, externalHandle); +} + +}; // namespace cc diff --git a/cocos/platform/ohos/OhosPlatform.h b/cocos/platform/ohos/OhosPlatform.h new file mode 100644 index 0000000..5a96942 --- /dev/null +++ b/cocos/platform/ohos/OhosPlatform.h @@ -0,0 +1,47 @@ +/**************************************************************************** + Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include "platform/UniversalPlatform.h" + +namespace cc { +class JniNativeGlue; +class CC_DLL OhosPlatform : public UniversalPlatform { +public: + OhosPlatform(); + int init() override; + void pollEvent() override; + int32_t run(int argc, const char **argv) override; + int getSdkVersion() const override; + int32_t loop() override; + void exit() override; + ISystemWindow *createNativeWindow(uint32_t windowId, void *externalHandle) override; + +private: + void waitWindowInitialized(); + + JniNativeGlue *_jniNativeGlue; +}; +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/ohos/jni/AbilityConsts.h b/cocos/platform/ohos/jni/AbilityConsts.h new file mode 100644 index 0000000..e3e7485 --- /dev/null +++ b/cocos/platform/ohos/jni/AbilityConsts.h @@ -0,0 +1,50 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include + +namespace cc { +namespace ohos { +enum { + ABILITY_CMD_INPUT_CHANGED, + ABILITY_CMD_INIT_WINDOW, + ABILITY_CMD_TERM_WINDOW, + ABILITY_CMD_WINDOW_RESIZED, + ABILITY_CMD_WINDOW_REDRAW_NEEDED, + ABILITY_CMD_CONTENT_RECT_CHANGED, + ABILITY_CMD_GAINED_FOCUS, + ABILITY_CMD_LOST_FOCUS, + ABILITY_CMD_CONFIG_CHANGED, + ABILITY_CMD_LOW_MEMORY, + ABILITY_CMD_START, + ABILITY_CMD_RESUME, + ABILITY_CMD_SAVE_STATE, + ABILITY_CMD_PAUSE, + ABILITY_CMD_STOP, + ABILITY_CMD_DESTROY, +}; +} // namespace ohos +} // namespace cc \ No newline at end of file diff --git a/cocos/platform/ohos/jni/JniCocosAbility.cpp b/cocos/platform/ohos/jni/JniCocosAbility.cpp new file mode 100644 index 0000000..a570ea5 --- /dev/null +++ b/cocos/platform/ohos/jni/JniCocosAbility.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include "platform/ohos/jni/JniCocosAbility.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "base/Log.h" + +#include "platform/java/jni/JniHelper.h" +#include "platform/java/jni/glue/JniNativeGlue.h" +#include "platform/ohos/FileUtils-ohos.h" +#include "platform/ohos/jni/AbilityConsts.h" + +#define LOGV(...) HILOG_INFO(LOG_APP, __VA_ARGS__) +//NOLINTNEXTLINE +using namespace cc::ohos; + +extern "C" { +//NOLINTNEXTLINE JNI function name +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosAbilitySlice_onCreateNative(JNIEnv *env, jobject obj, jobject ability, + jstring moduleNameJ, jstring assetPath, jobject resourceManager, + jint sdkVersion) { + if (JNI_NATIVE_GLUE()->isRunning()) { + return; + } + cc::JniHelper::init(env, ability); + JNI_NATIVE_GLUE()->setSdkVersion(sdkVersion); + + ResourceManager *objResourceManager = InitNativeResourceManager(env, resourceManager); + JNI_NATIVE_GLUE()->setResourceManager(objResourceManager); + + jboolean isCopy = false; + ccstd::string assetPathClone; + const char *assetPathStr = env->GetStringUTFChars(assetPath, &isCopy); + assetPathClone = assetPathStr; + if (isCopy) { + env->ReleaseStringUTFChars(assetPath, assetPathStr); + assetPathStr = nullptr; + } + ccstd::string moduleName{"entry"}; + const char *moduleNameStr = env->GetStringUTFChars(moduleNameJ, &isCopy); + moduleName = moduleNameStr; + if (isCopy) { + env->ReleaseStringUTFChars(moduleNameJ, moduleNameStr); + moduleNameStr = nullptr; + } + cc::FileUtilsOHOS::initResourceManager(objResourceManager, assetPathClone, moduleName); + JNI_NATIVE_GLUE()->start(0, nullptr); +} + +JNIEXPORT void JNICALL +Java_com_cocos_lib_CocosAbilitySlice_onSurfaceCreatedNative(JNIEnv *env, jobject obj, jobject surface) { //NOLINT JNI function name + // termAndSetPendingWindow(GetNativeLayer(env, surface)); +} +JNIEXPORT void JNICALL +Java_com_cocos_lib_CocosAbilitySlice_onSurfaceChangedNative(JNIEnv *env, jobject obj, jobject surface, jint width, //NOLINT JNI function name + jint height) { //NOLINT JNI function name + JNI_NATIVE_GLUE()->setWindowHandle(GetNativeLayer(env, surface)); +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosAbilitySlice_onSurfaceDestroyNative(JNIEnv *env, jobject obj) { //NOLINT JNI function name + JNI_NATIVE_GLUE()->setWindowHandle(nullptr); +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosAbilitySlice_onStartNative(JNIEnv *env, jobject obj) { //NOLINT JNI function name +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosAbilitySlice_onPauseNative(JNIEnv *env, jobject obj) { //NOLINT JNI function name + JNI_NATIVE_GLUE()->onPause(); +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosAbilitySlice_onResumeNative(JNIEnv *env, jobject obj) { //NOLINT JNI function name + JNI_NATIVE_GLUE()->onResume(); +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosAbilitySlice_onStopNative(JNIEnv *env, jobject obj) { //NOLINT JNI function name +} + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosAbilitySlice_onLowMemoryNative(JNIEnv *env, jobject obj) { //NOLINT JNI function name + JNI_NATIVE_GLUE()->onLowMemory(); +} + +JNIEXPORT void JNICALL +Java_com_cocos_lib_CocosAbilitySlice_onWindowFocusChangedNative(JNIEnv *env, jobject obj, jboolean has_focus) { //NOLINT JNI function name +} + +JNIEXPORT void JNICALL +Java_com_cocos_lib_CocosAbilitySlice_setRawfilePrefix(JNIEnv *env, jobject obj, jstring prefixJ) { //NOLINT JNI function name + jboolean isCopy = false; + const char *prefix = env->GetStringUTFChars(prefixJ, &isCopy); + cc::FileUtilsOHOS::setRawfilePrefix(prefix); + if (isCopy) { + env->ReleaseStringUTFChars(prefixJ, prefix); + } +} +} diff --git a/cocos/platform/ohos/jni/JniCocosAbility.h b/cocos/platform/ohos/jni/JniCocosAbility.h new file mode 100644 index 0000000..feae256 --- /dev/null +++ b/cocos/platform/ohos/jni/JniCocosAbility.h @@ -0,0 +1,57 @@ +/**************************************************************************** + Copyright (c) 2020-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include "base/std/container/string.h" + +#include + +namespace cc { + +// struct CocosApp { +// ResourceManager *resourceManager = nullptr; +// // NativeLayer *window = nullptr; +// int sdkVersion = 0; + +// std::promise glThreadPromise; + +// NativeLayer *pendingWindow = nullptr; +// bool destroyRequested = false; +// bool animating = true; +// bool running = false; +// bool surfaceInited = false; + +// // Current state of the app's activity. May be either APP_CMD_RESUME, APP_CMD_PAUSE. +// int activityState = 0; +// }; + +// extern CocosApp cocosApp; + +} // namespace cc diff --git a/cocos/platform/ohos/jni/JniCocosSurfaceView.cpp b/cocos/platform/ohos/jni/JniCocosSurfaceView.cpp new file mode 100644 index 0000000..35ab1c9 --- /dev/null +++ b/cocos/platform/ohos/jni/JniCocosSurfaceView.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** + Copyright (c) 2022-2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +****************************************************************************/ + +#include +#include +#include +#include "platform/BasePlatform.h" +#include "platform/java/jni/glue/JniNativeGlue.h" + +JNIEXPORT void JNICALL Java_com_cocos_lib_CocosAbilitySlice_onOrientationChangedNative(JNIEnv *env, jobject obj, jint orientation, jint width, jint height) { //NOLINT JNI function name + static jint pOrientation = 0; + static jint pWidth = 0; + static jint pHeight = 0; + if (pOrientation != orientation || pWidth != width || pHeight != height) { + cc::WindowEvent ev; + ev.type = cc::WindowEvent::Type::SIZE_CHANGED; + ev.width = width; + ev.height = height; + //JNI_NATIVE_GLUE()->dispatchEvent(ev); + cc::events::WindowEvent::broadcast(ev); + pOrientation = orientation; + pHeight = height; + pWidth = width; + } +} \ No newline at end of file diff --git a/cocos/platform/ohos/libcocos/build.gradle b/cocos/platform/ohos/libcocos/build.gradle new file mode 100644 index 0000000..ba3ff0c --- /dev/null +++ b/cocos/platform/ohos/libcocos/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.huawei.ohos.library' +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } +} + +dependencies { +/// implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation("com.squareup.okhttp3:okhttp:3.12.13") + testImplementation 'junit:junit:4.12' +} diff --git a/cocos/platform/ohos/libcocos/libs/okhttp-3.12.7.jar b/cocos/platform/ohos/libcocos/libs/okhttp-3.12.7.jar new file mode 100644 index 0000000..0eb8266 Binary files /dev/null and b/cocos/platform/ohos/libcocos/libs/okhttp-3.12.7.jar differ diff --git a/cocos/platform/ohos/libcocos/libs/okio-1.15.0.jar b/cocos/platform/ohos/libcocos/libs/okio-1.15.0.jar new file mode 100644 index 0000000..4e0e47a Binary files /dev/null and b/cocos/platform/ohos/libcocos/libs/okio-1.15.0.jar differ diff --git a/cocos/platform/ohos/libcocos/proguard-project.txt b/cocos/platform/ohos/libcocos/proguard-project.txt new file mode 100644 index 0000000..165ce03 --- /dev/null +++ b/cocos/platform/ohos/libcocos/proguard-project.txt @@ -0,0 +1,24 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: +# Proguard engine for release +-keep public class com.cocos.** { *; } +-dontwarn com.cocos.** + +# Proguard okhttp for release +-keep class okhttp3.** { *; } +-dontwarn okhttp3.** + +-keep class okio.** { *; } +-dontwarn okio.** + diff --git a/cocos/platform/ohos/libcocos/src/main/config.json b/cocos/platform/ohos/libcocos/src/main/config.json new file mode 100644 index 0000000..b42513e --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/config.json @@ -0,0 +1,29 @@ +{ + "app": { + "bundleName": "com.cocos.libcocos", + "vendor": "cocos", + "version": { + "code": 1000000, + "name": "1.0" + } + }, + "deviceConfig": { + "default": { + "network": { + "cleartextTraffic": true + } + } + }, + "module": { + "package": "com.cocos.lib", + "deviceType": [ + "phone", + "car" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "libcocos", + "moduleType": "har" + } + } +} \ No newline at end of file diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CanvasRenderingContext2DImpl.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CanvasRenderingContext2DImpl.java new file mode 100644 index 0000000..5b2c116 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CanvasRenderingContext2DImpl.java @@ -0,0 +1,480 @@ +/**************************************************************************** + * Copyright (c) 2018 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.render.Path; +import ohos.agp.render.Texture; +import ohos.agp.text.Font; +import ohos.agp.utils.Color; +import ohos.app.Context; +import ohos.global.resource.RawFileEntry; +import ohos.global.resource.Resource; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.media.image.PixelMap; +import ohos.media.image.common.AlphaType; +import ohos.media.image.common.PixelFormat; +import ohos.media.image.common.Rect; + +import java.io.File; +import java.io.FileOutputStream; +import java.lang.ref.WeakReference; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.util.HashMap; + +public class CanvasRenderingContext2DImpl { + + public static final String TAG = "CanvasContext2D"; + public static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, TAG); + + public static final int TEXT_ALIGN_LEFT = 0; + public static final int TEXT_ALIGN_CENTER = 1; + public static final int TEXT_ALIGN_RIGHT = 2; + + public static final int TEXT_BASELINE_TOP = 0; + public static final int TEXT_BASELINE_MIDDLE = 1; + public static final int TEXT_BASELINE_BOTTOM = 2; + public static final int TEXT_BASELINE_ALPHABETIC = 3; + + public static WeakReference sContext; + public Paint mTextPaint; + public Paint mLinePaint; + public Path mLinePath; + public Canvas mCanvas = new Canvas(); + public Texture mTexture; + public int mTextAlign = TEXT_ALIGN_LEFT; + public int mTextBaseline = TEXT_BASELINE_BOTTOM; + public int mFillStyleR = 0; + public int mFillStyleG = 0; + public int mFillStyleB = 0; + public int mFillStyleA = 255; + + public int mStrokeStyleR = 0; + public int mStrokeStyleG = 0; + public int mStrokeStyleB = 0; + public int mStrokeStyleA = 255; + + public String mFontName = "Arial"; + public float mFontSize = 40.0f; + public float mLineWidth = 0.0f; + public static float _sApproximatingOblique = -0.25f;//please check paint api documentation + public boolean mIsBoldFont = false; + public boolean mIsItalicFont = false; + public boolean mIsObliqueFont = false; + public boolean mIsSmallCapsFontVariant = false; + public String mLineCap = "butt"; + public String mLineJoin = "miter"; + private PixelMap mPixelMap; + + + public class Point { + Point(float x, float y) { + this.x = x; + this.y = y; + } + + Point(Point pt) { + this.x = pt.x; + this.y = pt.y; + } + + public float x; + public float y; + } + + static void init(Context context) { + sContext = new WeakReference<>(context); + } + + static void destroy() { + sContext = null; + } + + public static HashMap sTypefaceCache = new HashMap<>(); + + // url is a full path started with '@assets/' + public static void loadTypeface(String familyName, String url) { + Context ctx = sContext.get(); + File fontTmpFile = null; + if (!sTypefaceCache.containsKey(familyName)) { + try { + Font.Builder typeface = null; + if (url.startsWith("/")) { + /// No error reported here, but font render incorrect. + // typeface = new Font.Builder(url); + /// TODO: Opt. + /// After copying to temporary location, we can load font successfully. + /// I don't know why. + fontTmpFile = CocosHelper.copyToTempFile(url, "fontFile"); + typeface = new Font.Builder(fontTmpFile); + } else if (ctx != null) { + final String prefix = "@assets/"; + if (url.startsWith(prefix)) { + url = url.substring(prefix.length()); + } + // TODO: 是否可以直接通过 rawfile 创建 font? + fontTmpFile = CocosHelper.copyOutResFile(ctx, url, "fontFile"); + typeface = new Font.Builder(fontTmpFile); + } + + if (typeface != null) { + sTypefaceCache.put(familyName, typeface); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + // REFINE:: native should clear font cache before exiting game. + public static void clearTypefaceCache() { + sTypefaceCache.clear(); + } + + public static Paint newPaint(String fontName, int fontSize, boolean enableBold, boolean enableItalic, boolean obliqueFont, boolean smallCapsFontVariant) { + Paint paint = new Paint(); + paint.setTextSize(fontSize); + paint.setAntiAlias(true); + paint.setSubpixelAntiAlias(true); + + String key = fontName; + if (enableBold) { + key += "-Bold"; + paint.setFakeBoldText(true); + } + if (enableItalic) { + key += "-Italic"; + } + + Font.Builder typeFace; + if (sTypefaceCache.containsKey(key)) { + typeFace = sTypefaceCache.get(key); + } else { + typeFace = new Font.Builder(fontName); + int style = Font.REGULAR; + typeFace.makeItalic(enableItalic); + typeFace.setWeight(enableBold ? Font.BOLD : Font.REGULAR); + } + paint.setFont(typeFace.build()); + + if (obliqueFont) { + // TODO: skewX 缺少接口 +// paint.setTextSkewX(_sApproximatingOblique); + } + return paint; + } + + public CanvasRenderingContext2DImpl() { + // Log.d(TAG, "constructor"); + } + + public void recreateBuffer(float w, float h) { + // Log.d(TAG, "recreateBuffer:" + w + ", " + h); + PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions(); + initializationOptions.alphaType = AlphaType.UNPREMUL; + initializationOptions.pixelFormat = PixelFormat.ARGB_8888; + initializationOptions.editable = true; // allow writePixels + initializationOptions.size = new ohos.media.image.common.Size((int) Math.ceil(w), (int) Math.ceil(h)); + + mPixelMap = PixelMap.create(initializationOptions); + mTexture = new Texture(mPixelMap); + // NOTE: PixelMap.resetConfig does not change pixel data or nor reallocate memory for pixel data + + mCanvas.setTexture(mTexture); + } + + public void beginPath() { + if (mLinePath == null) { + mLinePath = new Path(); + } + mLinePath.reset(); + } + + public void closePath() { + mLinePath.close(); + } + + public void moveTo(float x, float y) { + mLinePath.moveTo(x, y); + } + + public void lineTo(float x, float y) { + mLinePath.lineTo(x, y); + } + + public void stroke() { + if (mLinePaint == null) { + mLinePaint = new Paint(); + mLinePaint.setAntiAlias(true); + } + + if (mLinePath == null) { + mLinePath = new Path(); + } + + Color strokeColor = new Color(Color.argb(mStrokeStyleA, mStrokeStyleR, mStrokeStyleG, mStrokeStyleB)); + mLinePaint.setColor(strokeColor); + mLinePaint.setStyle(Paint.Style.STROKE_STYLE); + mLinePaint.setStrokeWidth(mLineWidth); + this.setStrokeCap(mLinePaint); + this.setStrokeJoin(mLinePaint); + mCanvas.drawPath(mLinePath, mLinePaint); + } + + public void setStrokeCap(Paint paint) { + switch (mLineCap) { + case "butt": + paint.setStrokeCap(Paint.StrokeCap.BUTT_CAP); + break; + case "round": + paint.setStrokeCap(Paint.StrokeCap.ROUND_CAP); + break; + case "square": + paint.setStrokeCap(Paint.StrokeCap.SQUARE_CAP); + break; + } + } + + public void setStrokeJoin(Paint paint) { + switch (mLineJoin) { + case "bevel": + paint.setStrokeJoin(Paint.Join.BEVEL_JOIN); + break; + case "round": + paint.setStrokeJoin(Paint.Join.ROUND_JOIN); + break; + case "miter": + paint.setStrokeJoin(Paint.Join.MITER_JOIN); + break; + } + } + + public void fill() { + if (mLinePaint == null) { + mLinePaint = new Paint(); + } + + if (mLinePath == null) { + mLinePath = new Path(); + } + + Color fillColor = new Color(Color.argb(mFillStyleA, mFillStyleR, mFillStyleG, mFillStyleB)); + mLinePaint.setColor(fillColor); + mLinePaint.setStyle(Paint.Style.FILL_STYLE); + mCanvas.drawPath(mLinePath, mLinePaint); + // workaround: draw a hairline to cover the border + mLinePaint.setStrokeWidth(0); + this.setStrokeCap(mLinePaint); + this.setStrokeJoin(mLinePaint); + mLinePaint.setStyle(Paint.Style.STROKE_STYLE); + mCanvas.drawPath(mLinePath, mLinePaint); + mLinePaint.setStrokeWidth(mLineWidth); + } + + public void setLineCap(String lineCap) { + mLineCap = lineCap; + } + + public void setLineJoin(String lineJoin) { + mLineJoin = lineJoin; + } + + public void saveContext() { + mCanvas.save(); + } + + public void restoreContext() { + // If there is no saved state, this method should do nothing. + if (mCanvas.getSaveCount() > 1) { + mCanvas.restore(); + } + } + + public void rect(float x, float y, float w, float h) { + beginPath(); + moveTo(x, y); + lineTo(x, y + h); + lineTo(x + w, y + h); + lineTo(x + w, y); + closePath(); + } + + public void clearRect(float x, float y, float w, float h) { +// mTexture.getPixelMap().writePixels(Color.TRANSPARENT.getValue()); + PixelMap pm = mTexture.getPixelMap(); + if (pm.isReleased() || !pm.isEditable()) { + return; + } + Rect region = new Rect((int) x, (int) y, (int) w, (int) h); + int fillSize = (int) (w * h); + IntBuffer buffer = IntBuffer.allocate(fillSize); + for (int i = 0; i < fillSize; i++) { + buffer.put(Color.TRANSPARENT.getValue()); + } + pm.writePixels(buffer.array(), 0, (int) w, region); + } + + public void createTextPaintIfNeeded() { + if (mTextPaint == null) { + mTextPaint = newPaint(mFontName, (int) mFontSize, mIsBoldFont, mIsItalicFont, mIsObliqueFont, mIsSmallCapsFontVariant); + } + } + + public void fillRect(float x, float y, float w, float h) { + PixelMap pm = mTexture.getPixelMap(); + if (pm.isReleased() || !pm.isEditable()) { + return; + } + // Log.d(TAG, "fillRect: " + x + ", " + y + ", " + ", " + w + ", " + h); + int pixelValue = (mFillStyleA & 0xff) << 24 | (mFillStyleR & 0xff) << 16 | (mFillStyleG & 0xff) << 8 | (mFillStyleB & 0xff); + int fillSize = (int) (w * h); + int[] buffer = new int[fillSize]; + IntBuffer fillColors = IntBuffer.wrap(buffer); + for (int i = 0; i < fillSize; ++i) { + buffer[i] = pixelValue; + } + Rect region = new Rect((int) x, (int) y, (int) w, (int) h); + pm.writePixels(buffer, 0, (int) w, region); + } + + public void scaleX(Paint textPaint, String text, float maxWidth) { + if (maxWidth < Float.MIN_VALUE) return; + float measureWidth = this.measureText(text); + if ((measureWidth - maxWidth) < Float.MIN_VALUE) return; + float scaleX = maxWidth / measureWidth; + // TODO: font scale +// textPaint.setTextScaleX(scaleX); + } + + public void fillText(String text, float x, float y, float maxWidth) { + createTextPaintIfNeeded(); + Color fillColor = new Color(Color.argb(mFillStyleA, mFillStyleR, mFillStyleG, mFillStyleB)); + mTextPaint.setColor(fillColor); + mTextPaint.setStyle(Paint.Style.FILL_STYLE); + scaleX(mTextPaint, text, maxWidth); + Point pt = convertDrawPoint(new Point(x, y), text); + mCanvas.drawText(mTextPaint, text, pt.x, pt.y); + } + + public void strokeText(String text, float x, float y, float maxWidth) { + createTextPaintIfNeeded(); + Color strokeColor = new Color(Color.argb(mStrokeStyleA, mStrokeStyleR, mStrokeStyleG, mStrokeStyleB)); + mTextPaint.setColor(strokeColor); + mTextPaint.setStyle(Paint.Style.STROKE_STYLE); + mTextPaint.setStrokeWidth(mLineWidth); + scaleX(mTextPaint, text, maxWidth); + Point pt = convertDrawPoint(new Point(x, y), text); + mCanvas.drawText(mTextPaint, text, pt.x, pt.y); + } + + public float measureText(String text) { + createTextPaintIfNeeded(); + return mTextPaint.measureText(text); + } + + public void updateFont(String fontName, float fontSize, boolean bold, boolean italic, boolean oblique, boolean smallCaps) { + mFontName = fontName; + mFontSize = fontSize; + mIsBoldFont = bold; + mIsItalicFont = italic; + mIsObliqueFont = oblique; + mIsSmallCapsFontVariant = smallCaps; + mTextPaint = null; // Reset paint to re-create paint object in createTextPaintIfNeeded + } + + public void setTextAlign(int align) { + mTextAlign = align; + } + + public void setTextBaseline(int baseline) { + mTextBaseline = baseline; + } + + public void setFillStyle(int r, int g, int b, int a) { + mFillStyleR = r; + mFillStyleG = g; + mFillStyleB = b; + mFillStyleA = a; + } + + public void setStrokeStyle(int r, int g, int b, int a) { + mStrokeStyleR = r; + mStrokeStyleG = g; + mStrokeStyleB = b; + mStrokeStyleA = a; + } + + public void setLineWidth(float lineWidth) { + mLineWidth = lineWidth; + } + + @SuppressWarnings("unused") + public void _fillImageData(int[] imageData, float imageWidth, float imageHeight, float offsetX, float offsetY) { + int fillSize = (int) (imageWidth * imageHeight); + int[] fillColors = new int[fillSize]; + for (int i = 0; i < fillSize; ++i) { + // r g b a -> a r g b + fillColors[i] = Integer.rotateRight(imageData[i], 8); + } + Rect dstRect = new Rect((int) offsetX, (int) offsetY, (int) imageWidth, (int) imageHeight); + mTexture.getPixelMap().writePixels(fillColors, 0, (int) imageWidth, dstRect); + } + + public Point convertDrawPoint(final Point point, String text) { + // The parameter 'point' is located at left-bottom position. + // Need to adjust 'point' according 'text align' & 'text base line'. + Point ret = new Point(point); + createTextPaintIfNeeded(); + Paint.FontMetrics fm = mTextPaint.getFontMetrics(); + float width = measureText(text); + + if (mTextAlign == TEXT_ALIGN_CENTER) { + ret.x -= width / 2; + } else if (mTextAlign == TEXT_ALIGN_RIGHT) { + ret.x -= width; + } + + // Canvas.drawText accepts the y parameter as the baseline position, not the most bottom + if (mTextBaseline == TEXT_BASELINE_TOP) { + ret.y += -fm.ascent; + } else if (mTextBaseline == TEXT_BASELINE_MIDDLE) { + ret.y += (fm.descent - fm.ascent) / 2 - fm.descent; + } else if (mTextBaseline == TEXT_BASELINE_BOTTOM) { + ret.y += -fm.descent; + } + + return ret; + } + + @SuppressWarnings("unused") + private PixelMap getBitmap() { + return mPixelMap; + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosAbilitySlice.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosAbilitySlice.java new file mode 100644 index 0000000..1116d10 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosAbilitySlice.java @@ -0,0 +1,282 @@ +/**************************************************************************** + * Copyright (c) 2020 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.aafwk.content.Intent; +import ohos.agp.components.Component; +import ohos.agp.components.StackLayout; +import ohos.agp.components.surfaceprovider.SurfaceProvider; +import ohos.agp.graphics.Surface; +import ohos.agp.graphics.SurfaceOps; +import ohos.agp.window.service.WindowManager; +import ohos.bundle.AbilityInfo; +import ohos.global.resource.ResourceManager; +import ohos.multimodalinput.event.KeyEvent; +import ohos.multimodalinput.event.TouchEvent; +import ohos.system.version.SystemVersion; + +import java.io.File; +import java.util.Optional; + +public class CocosAbilitySlice extends AbilitySlice implements SurfaceOps.Callback, Component.TouchEventListener , Component.KeyEventListener, Component.FocusChangedListener { + private boolean mDestroyed; + private StackLayout mRootLayout; + private SurfaceProvider mSurfaceProvider; + private Optional mSurface = Optional.empty(); + private CocosTouchHandler mTouchHandler; + private CocosWebViewHelper mWebViewHelper = null; + private CocosVideoHelper mVideoHelper = null; + private CocosOrientationHelper mOrientationHelper = null; + + private boolean engineInit = false; + + private CocosKeyCodeHandler mKeyCodeHandler; + private CocosSensorHandler mSensorHandler; + + + private native void onCreateNative(AbilitySlice activity, String moduleName, String assetPath, ResourceManager resourceManager, int sdkVersion); + + private native void onSurfaceCreatedNative(Surface surface); + + private native void onSurfaceChangedNative(Surface surface, int width, int height); + + private native void onSurfaceDestroyNative(); + + private native void onPauseNative(); + + private native void onResumeNative(); + + private native void onStopNative(); + + private native void onStartNative(); + + private native void onLowMemoryNative(); + + private native void onOrientationChangedNative(int orientation, int width, int height); + + private native void onWindowFocusChangedNative(boolean hasFocus); + + private native void setRawfilePrefix(String prefix); + + @Override + protected void onStart(Intent savedInstanceState) { + super.onStart(savedInstanceState); + GlobalObject.setAbilitySlice(this); + CocosHelper.registerBatteryLevelReceiver(this); + CocosHelper.init(this); + CanvasRenderingContext2DImpl.init(this); + onLoadNativeLibraries(); + + getWindow().setTransparent(true); // required for surface provider + + this.getWindow().addFlags(WindowManager.LayoutConfig.MARK_ALLOW_EXTEND_LAYOUT); + this.getWindow().addFlags(WindowManager.LayoutConfig.MARK_FULL_SCREEN); + +// getContext().setDisplayOrientation(AbilityInfo.DisplayOrientation.UNSPECIFIED); +// getWindow().addFlags(WindowManager.LayoutConfig.INPUT_ADJUST_PAN); +// getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); + + initView(); + + onCreateNative(this, getHapModuleInfo().getModuleName(), getAssetPath(), getResourceManager(), SystemVersion.getApiVersion()); + + mKeyCodeHandler = new CocosKeyCodeHandler(this); + mSensorHandler = new CocosSensorHandler(); + + mTouchHandler = new CocosTouchHandler(); + + mOrientationHelper = new CocosOrientationHelper(this); + + Thread.currentThread().setName("Cocos-UI"); + CocosHelper.runOnGameThread(()->{ + Thread.currentThread().setName("Cocos-JS"); + }); + } + private String getAssetPath() { + return getApplicationContext().getFilesDir().getPath(); + } + + @Override + protected void onOrientationChanged(AbilityInfo.DisplayOrientation displayOrientation) { + super.onOrientationChanged(displayOrientation); +// CocosHelper.runOnGameThread(() -> +// onOrientationChangedNative(displayOrientation.ordinal(), mSurfaceProvider.getWidth(), mSurfaceProvider.getHeight()) +// ); + } + + private static String getAbsolutePath(File file) { + return (file != null) ? file.getAbsolutePath() : null; + } + + protected void initView() { + // gles view + StackLayout.LayoutConfig config = new StackLayout.LayoutConfig(StackLayout.LayoutConfig.MATCH_PARENT, StackLayout.LayoutConfig.MATCH_PARENT); + + mSurfaceProvider = new SurfaceProvider(getContext()); + mSurfaceProvider.getSurfaceOps().get().addCallback(this); + mSurfaceProvider.pinToZTop(false); + + mRootLayout = new StackLayout(getContext()); + mRootLayout.setLayoutConfig(config); + + StackLayout.LayoutConfig layoutConfigSurfaceProvider = new StackLayout.LayoutConfig(StackLayout.LayoutConfig.MATCH_PARENT, StackLayout.LayoutConfig.MATCH_PARENT); + + mRootLayout.addComponent(mSurfaceProvider, layoutConfigSurfaceProvider); + mSurfaceProvider.setKeyEventListener(this); + mSurfaceProvider.setFocusable(Component.FOCUS_ENABLE); + mSurfaceProvider.setTouchFocusable(true); + mSurfaceProvider.setFocusChangedListener(this); + mSurfaceProvider.setTouchEventListener(this); + mSurfaceProvider.setLayoutRefreshedListener(component -> { + // dispatch resize event + CocosHelper.runOnGameThreadAtForeground(()->{ + onOrientationChangedNative(getDisplayOrientation(), component.getWidth(), component.getHeight()); + }); + }); + mSurfaceProvider.requestFocus(); + + if (mWebViewHelper == null) { + mWebViewHelper = new CocosWebViewHelper(mRootLayout); + } + + if (mVideoHelper == null) { + mVideoHelper = new CocosVideoHelper(this, mRootLayout); + } + + setUIContent(mRootLayout); + } + + public SurfaceProvider getSurfaceView() { + return this.mSurfaceProvider; + } + + @Override + protected void onStop() { + mDestroyed = true; + if (mSurfaceProvider != null) { + onSurfaceDestroyNative(); + mSurfaceProvider = null; + } + super.onStop(); + } + + @Override + protected void onInactive() { + super.onInactive(); + mSensorHandler.onPause(); + mOrientationHelper.onPause(); + onPauseNative(); + } + + @Override + protected void onActive() { + super.onActive(); + onStartNative(); + mSensorHandler.onResume(); + mOrientationHelper.onResume(); + onResumeNative(); + + } + + @Override + protected void onBackground() { + super.onBackground(); + onStopNative(); + } + + // TODO: low memory listener +// @Override +// public void onLowMemory() { +// super.onLowMemory(); +// if (!mDestroyed) { +// onLowMemoryNative(); +// } +// } + + @Override + public void onFocusChange(Component component, boolean b) { + //NOTICE: may not be equivalent to onWindowFocusChanged on Android + if (!mDestroyed) { + onWindowFocusChangedNative(b); + } + } + + @Override + public void surfaceCreated(SurfaceOps holder) { + if (!mDestroyed) { + mSurface = Optional.of(holder.getSurface()); + onSurfaceCreatedNative(holder.getSurface()); + engineInit = true; + } + } + + @Override + public void surfaceChanged(SurfaceOps surfaceOps, int format, int width, int height) { + if (!mDestroyed) { + mSurface = Optional.of(surfaceOps.getSurface()); + onSurfaceChangedNative(surfaceOps.getSurface(), width, height); + } + } + + @Override + public void surfaceDestroyed(SurfaceOps surfaceOps) { + mRootLayout = null; + if (!mDestroyed) { + onSurfaceDestroyNative(); + engineInit = false; + } + mSurface = Optional.empty(); + } + + private void onLoadNativeLibraries() { + try { + //TODO: Read library name from configuration + System.loadLibrary("cocos"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return mKeyCodeHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return mKeyCodeHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); + } + + @Override + public boolean onTouchEvent(Component component, TouchEvent touchEvent) { + return mTouchHandler.onTouchEvent(touchEvent); + } + + @Override + public boolean onKeyEvent(Component component, KeyEvent keyEvent) { + return false; + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosDownloader.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosDownloader.java new file mode 100644 index 0000000..a0ea5a8 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosDownloader.java @@ -0,0 +1,340 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import okhttp3.*; + +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public class CocosDownloader { + + private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, "CocosDownloader"); + + private int _id; + private OkHttpClient _httpClient = null; + + private String _tempFileNameSuffix; + private int _countOfMaxProcessingTasks; + private final ConcurrentHashMap _taskMap = new ConcurrentHashMap<>(); + private final Queue _taskQueue = new LinkedList<>(); + private int _runningTaskCount = 0; + private static final ConcurrentHashMap _resumingSupport = new ConcurrentHashMap<>(); + + private void onProgress(final int id, final long downloadBytes, final long downloadNow, final long downloadTotal) { + CocosHelper.runOnGameThread(() -> + nativeOnProgress(_id, id, downloadBytes, downloadNow, downloadTotal) + ); + } + + private void onFinish(final int id, final int errCode, final String errStr, final byte[] data) { + Call task =_taskMap.get(id); + if (null == task) return; + _taskMap.remove(id); + _runningTaskCount -= 1; + CocosHelper.runOnGameThread(() -> + nativeOnFinish(_id, id, errCode, errStr, data) + ); + runNextTaskIfExists(); + } + + @SuppressWarnings("unused") + public static CocosDownloader createDownloader(int id, int timeoutInSeconds, String tempFileSuffix, int maxProcessingTasks) { + CocosDownloader downloader = new CocosDownloader(); + downloader._id = id; + + if (timeoutInSeconds > 0) { + downloader._httpClient = new OkHttpClient().newBuilder() + .followRedirects(true) + .followSslRedirects(true) + .callTimeout(timeoutInSeconds, TimeUnit.SECONDS) + .build(); + } else { + downloader._httpClient = new OkHttpClient().newBuilder() + .followRedirects(true) + .followSslRedirects(true) + .build(); + } + + + downloader._tempFileNameSuffix = tempFileSuffix; + downloader._countOfMaxProcessingTasks = maxProcessingTasks; + return downloader; + } + + @SuppressWarnings("unused") + public static void createTask(final CocosDownloader downloader, int id_, String url_, String path_, String []header_) { + final int id = id_; + final String url = url_; + final String path = path_; + final String[] header = header_; + + Runnable taskRunnable = new Runnable() { + String domain = null; + String host = null; + File tempFile = null; + File finalFile = null; + long downloadStart = 0; + + @Override + public void run() { + Call task = null; + + do { + if (path.length() > 0) { + try { + URI uri = new URI(url); + domain = uri.getHost(); + } catch (URISyntaxException | NullPointerException e) { + e.printStackTrace(); + break; + } + + // file task + tempFile = new File(path + downloader._tempFileNameSuffix); + if (tempFile.isDirectory()) break; + + File parent = tempFile.getParentFile(); + if (!parent.isDirectory() && !parent.mkdirs()) break; + + finalFile = new File(path); + if (finalFile.isDirectory()) break; + long fileLen = tempFile.length(); + + host = domain.startsWith("www.") ? domain.substring(4) : domain; + if (fileLen > 0) { + if (_resumingSupport.containsKey(host) && _resumingSupport.get(host)) { + downloadStart = fileLen; + } else { + // Remove previous downloaded context + try { + PrintWriter writer = new PrintWriter(tempFile); + writer.print(""); + writer.close(); + } + // Not found then nothing to do + catch (FileNotFoundException e) { + } + } + } + } + + final Request.Builder builder = new Request.Builder().url(url); + for (int i = 0; i < header.length / 2; i++) { + builder.addHeader(header[i * 2], header[(i * 2) + 1]); + } + if (downloadStart > 0) { + builder.addHeader("RANGE", "bytes=" + downloadStart + "-"); + } + + final Request request = builder.build(); + task = downloader._httpClient.newCall(request); + if (null == task) { + final String errStr = "Can't create DownloadTask for " + url; + CocosHelper.runOnGameThread(() -> + downloader.nativeOnFinish(downloader._id, id, 0, errStr, null) + ); + } else { + downloader._taskMap.put(id, task); + } + task.enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + downloader.onFinish(id, 0, e.toString(), null); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + InputStream is = null; + byte[] buf = new byte[4096]; + FileOutputStream fos = null; + + try { + + if(!(response.code() >= 200 && response.code() <= 206)) { + // it is encourage to delete the tmp file when requested range not satisfiable. + if (response.code() == 416) { + File file = new File(path + downloader._tempFileNameSuffix); + if (file.exists() && file.isFile()) { + file.delete(); + } + } + downloader.onFinish(id, -2, response.message(), null); + return; + } + + long total = response.body().contentLength(); + if (path.length() > 0 && !_resumingSupport.containsKey(host)) { + if (total > 0) { + _resumingSupport.put(host, true); + } else { + _resumingSupport.put(host, false); + } + } + + long current = downloadStart; + is = response.body().byteStream(); + + if (path.length() > 0) { + if (downloadStart > 0) { + fos = new FileOutputStream(tempFile, true); + } else { + fos = new FileOutputStream(tempFile, false); + } + + int len; + while ((len = is.read(buf)) != -1) { + current += len; + fos.write(buf, 0, len); + downloader.onProgress(id, len, current, total); + } + fos.flush(); + + String errStr = null; + do { + // rename temp file to final file, if final file exist, remove it + if (finalFile.exists()) { + if (finalFile.isDirectory()) { + break; + } + if (!finalFile.delete()) { + errStr = "Can't remove old file:" + finalFile.getAbsolutePath(); + break; + } + } + tempFile.renameTo(finalFile); + } while (false); + + if (errStr == null) { + downloader.onFinish(id, 0, null, null); + downloader.runNextTaskIfExists(); + } + else + downloader.onFinish(id, 0, errStr, null); + } else { + // 非文件 + ByteArrayOutputStream buffer; + if(total > 0) { + buffer = new ByteArrayOutputStream((int) total); + } else { + buffer = new ByteArrayOutputStream(4096); + } + + int len; + while ((len = is.read(buf)) != -1) { + current += len; + buffer.write(buf, 0, len); + downloader.onProgress(id, len, current, total); + } + downloader.onFinish(id, 0, null, buffer.toByteArray()); + downloader.runNextTaskIfExists(); + } + } catch (IOException e) { + e.printStackTrace(); + downloader.onFinish(id, 0, e.toString(), null); + } finally { + try { + if (is != null) { + is.close(); + } + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + HiLog.error(LABEL, e.toString()); + } + } + } + }); + } while (false); + } + }; + downloader.enqueueTask(taskRunnable); + } + + public static void abort(final CocosDownloader downloader, final int id) { + CocosHelper.runOnUIThread(() -> { + for (Map.Entry entry : downloader._taskMap.entrySet()) { + Object key = entry.getKey(); + Call task = entry.getValue(); + if (null != task && Integer.parseInt(key.toString()) == id) { + task.cancel(); + downloader._taskMap.remove(id); + downloader.runNextTaskIfExists(); + break; + } + } + }); + } + + @SuppressWarnings("unused") + public static void cancelAllRequests(final CocosDownloader downloader) { + CocosHelper.runOnUIThread(() -> { + for (Map.Entry entry : downloader._taskMap.entrySet()) { + Call task = entry.getValue(); + if (null != task) { + task.cancel(); + } + } + }); + } + + + private void enqueueTask(Runnable taskRunnable) { + synchronized (_taskQueue) { + if (_runningTaskCount < _countOfMaxProcessingTasks) { + CocosHelper.runOnUIThread(taskRunnable); + _runningTaskCount++; + } else { + _taskQueue.add(taskRunnable); + } + } + } + + private void runNextTaskIfExists() { + synchronized (_taskQueue) { + while (_runningTaskCount < _countOfMaxProcessingTasks && + CocosDownloader.this._taskQueue.size() > 0) { + + Runnable taskRunnable = CocosDownloader.this._taskQueue.poll(); + CocosHelper.runOnUIThread(taskRunnable); + _runningTaskCount += 1; + } + } + } + + native void nativeOnProgress(int id, int taskId, long dl, long dlnow, long dltotal); + native void nativeOnFinish(int id, int taskId, int errCode, String errStr, final byte[] data); +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosEditBoxAbility.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosEditBoxAbility.java new file mode 100644 index 0000000..c7aa591 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosEditBoxAbility.java @@ -0,0 +1,422 @@ +/**************************************************************************** + Copyright (c) 2021 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + + +import ohos.aafwk.ability.AbilitySlice; +import ohos.aafwk.content.Intent; +import ohos.accessibility.ability.AccessibleAbility; +import ohos.accessibility.ability.SoftKeyBoardController; +import ohos.agp.components.*; +import ohos.agp.utils.Color; +import ohos.agp.utils.Rect; +import ohos.agp.window.service.DisplayAttributes; +import ohos.agp.window.service.DisplayManager; +import ohos.agp.window.service.WindowManager; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.multimodalinput.event.TouchEvent; + +import java.lang.ref.WeakReference; + + +public class CocosEditBoxAbility extends AbilitySlice { + + // a color of dark green, was used for confirm button background + private static final Color DARK_GREEN = new Color(Color.getIntColor("#1fa014")); + private static final Color DARK_GREEN_PRESS = new Color(Color.getIntColor("#008e26")); + private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, "CocosEditBoxAbility"); + + private static WeakReference sThis = null; + private CocosTextHelper mTextFieldHelper = null; + private Button mButton = null; + private String mButtonTitle = null; + private boolean mConfirmHold = true; + private boolean mIsMultiLine; + + + /*************************************************************************************** + Inner class. + **************************************************************************************/ + class CocosTextHelper { + private final HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP, 0, "Cocos2dxEditBox"); + private Text.TextObserver mTextWatcher = null; + private TextField mTextFieldLocal; + private int mVisibleHeight = 0; + private ComponentContainer mLayout; + + public CocosTextHelper(TextField tf) { + mTextFieldLocal = tf; + DisplayAttributes displayAttrib = DisplayManager.getInstance().getDefaultDisplay(tf.getContext()).get().getRealAttributes(); + + mTextWatcher = new Text.TextObserver() { + @Override + public void onTextUpdated(String s, int i, int i1, int i2) { + CocosEditBoxAbility.this.onKeyboardInput(s); + } + }; + tf.setAdjustInputPanel(true); + + mTextFieldHelper = this; + + mLayout = (ComponentContainer) findComponentById(ResourceTable.Id_editbox_container); + mLayout.setLayoutRefreshedListener(new Component.LayoutRefreshedListener() { + @Override + public void onRefreshed(Component component) { + HiLog.debug(LABEL, "onRefreshed"); + Rect rect = new Rect(); + boolean result = mTextFieldLocal.getWindowVisibleRect(rect); + if (!result) { + HiLog.debug(LABEL, "getWindowVisibleRect fail"); + return; + } + + int tempVisibleHeight = rect.bottom; + + if (tempVisibleHeight == 0) { + HiLog.debug(LABEL, "invaild rect"); + } else if (mVisibleHeight == 0) { + mVisibleHeight = tempVisibleHeight; + } else { + if (tempVisibleHeight > mVisibleHeight && (Math.abs(tempVisibleHeight - mVisibleHeight) > 200)) { + HiLog.debug(LABEL, "input method down, the height is 0"); + mTextFieldLocal.setText("input method down, the height is 0"); + mVisibleHeight = tempVisibleHeight; + onBoardDown(); + return; + } + if (tempVisibleHeight < mVisibleHeight && (Math.abs(tempVisibleHeight - mVisibleHeight) > 200)) { + HiLog.debug(LABEL, "input method up, the height is" + Math.abs(tempVisibleHeight - mVisibleHeight)); + mTextFieldLocal.setText("input method up, the height is ---" + Math.abs(tempVisibleHeight - mVisibleHeight)); + onBoardUp(Math.abs(tempVisibleHeight - mVisibleHeight)); + mVisibleHeight = tempVisibleHeight; + return; + } + + } + } + }); + } + + + private void onBoardUp(int boardHeight) { + HiLog.debug(LABEL, "onBoardUp, the height is ++++" + boardHeight); + ComponentContainer.LayoutConfig layoutConfig = mTextFieldLocal.getLayoutConfig(); + layoutConfig.setMarginBottom(boardHeight);//835 + mTextFieldLocal.setLayoutConfig(layoutConfig); + } + + private void onBoardDown() { + HiLog.debug(LABEL, "onBoardDown"); + ComponentContainer.LayoutConfig layoutConfig = mTextFieldLocal.getLayoutConfig(); + layoutConfig.setMarginBottom(0); + mTextFieldLocal.setLayoutConfig(layoutConfig); + } + + /*************************************************************************************** + Public functions. + **************************************************************************************/ + + public void show(String defaultValue, int maxLength, boolean isMultiline, boolean confirmHold, String confirmType, String inputType) { + mIsMultiLine = isMultiline; + // TODO: truncate +// this.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)}); +// mTextField.setTruncationMode(Text.TruncationMode.ELLIPSIS_AT_END); + + mTextFieldLocal.setText(defaultValue); + this.setConfirmType(confirmType); +// this.setInputType(inputType, mIsMultiLine); // FIXME: should enable + this.addListeners(); + } + + public void hide() { + mTextFieldLocal.clearFocus(); + this.removeListeners(); + } + + /*************************************************************************************** + Private functions. + **************************************************************************************/ + + private void setConfirmType(final String confirmType) { + if (confirmType.contentEquals("done")) { + mTextFieldLocal.setInputMethodOption(InputAttribute.ENTER_KEY_TYPE_UNSPECIFIED); + mButtonTitle = "Done"; //TODO: read from ResourceTable + } else if (confirmType.contentEquals("next")) { + mTextFieldLocal.setInputMethodOption(InputAttribute.ENTER_KEY_TYPE_UNSPECIFIED); + mButtonTitle = "Done"; //TODO: read from ResourceTable + } else if (confirmType.contentEquals("search")) { + mTextFieldLocal.setInputMethodOption(InputAttribute.ENTER_KEY_TYPE_SEARCH); + mButtonTitle = "Search"; //TODO: read from ResourceTable + } else if (confirmType.contentEquals("go")) { + mTextFieldLocal.setInputMethodOption(InputAttribute.ENTER_KEY_TYPE_GO); + mButtonTitle = "Go"; //TODO: read from ResourceTable + } else if (confirmType.contentEquals("send")) { + mTextFieldLocal.setInputMethodOption(InputAttribute.ENTER_KEY_TYPE_SEND); + mButtonTitle = "Send"; //TODO: read from ResourceTable + } else { + mButtonTitle = null; + HiLog.error(TAG, "unknown confirm type " + confirmType); + } + } + + private void setInputType(final String inputType, boolean isMultiLine) { + if (inputType.contentEquals("text")) { + if (isMultiLine) { +// this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + mTextFieldLocal.setTextInputType(InputAttribute.PATTERN_TEXT); + mTextFieldLocal.setMultipleLine(true); + } else { + mTextFieldLocal.setTextInputType(InputAttribute.PATTERN_TEXT); + mTextFieldLocal.setMultipleLine(false); + } + } else if (inputType.contentEquals("email")) { +// this.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + mTextFieldLocal.setTextInputType(InputAttribute.PATTERN_TEXT); + } else if (inputType.contentEquals("number")) { +// this.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED); + mTextFieldLocal.setTextInputType(InputAttribute.PATTERN_NUMBER); + } else if (inputType.contentEquals("phone")) { +// this.setInputType(InputType.TYPE_CLASS_PHONE); + mTextFieldLocal.setTextInputType(InputAttribute.PATTERN_TEXT); + } else if (inputType.contentEquals("password")) { +// this.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + mTextFieldLocal.setTextInputType(InputAttribute.PATTERN_PASSWORD); + } else { + HiLog.error(TAG, "unknown input type " + inputType); + } + } + + private void addListeners() { + + mTextFieldLocal.setEditorActionListener(new Text.EditorActionListener() { + @Override + public boolean onTextEditorAction(int i) { + //TODO: + return false; + } + }); + + mTextFieldLocal.addTextObserver(mTextWatcher); + } + + private void removeListeners() { +// this.setOnEditorActionListener(null); + mTextFieldLocal.removeTextObserver(mTextWatcher); + } + + } + + @Override + protected void onStart(Intent intent) { + super.onStart(intent); + + getKeyBoard().setShowMode(AccessibleAbility.SHOW_MODE_AUTO); + getAbility().setAbilitySliceAnimator(null); // remove animation + + //FIXME: todo +// getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); // android +// getWindow().setInputPanelDisplayType(Dis); // ohos + + sThis = new WeakReference(this); + getWindow().setInputPanelDisplayType(WindowManager.LayoutConfig.INPUT_ADJUST_PAN); + + setUIContent(ResourceTable.Layout_editbox_layout); + delayShow(intent); + + + } + + private void delayShow(final Intent intent) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosEditBoxAbility.this.addItems(); + CocosEditBoxAbility.this.show(intent.getStringParam("defaultValue"), + intent.getIntParam("maxLength", 60), + intent.getBooleanParam("isMultiline", false), + intent.getBooleanParam("confirmHold", true), + intent.getStringParam("confirmType"), + intent.getStringParam("inputType")); + + } + }); + } + + + + + /*************************************************************************************** + Public functions. + **************************************************************************************/ + + /*************************************************************************************** + Private functions. + **************************************************************************************/ + private void addItems() { + mTextFieldHelper = new CocosTextHelper(getTextField()); + getTextField().setBubbleSize(0, 0); + mButton = (Button) findComponentById(ResourceTable.Id_editbox_enterBtn); + mButton.setTouchEventListener(new Component.TouchEventListener() { + @Override + public boolean onTouchEvent(Component component, TouchEvent touchEvent) { + CocosEditBoxAbility.this.onKeyboardConfirm(CocosEditBoxAbility.this.getTextField().getText()); + if (!CocosEditBoxAbility.this.mConfirmHold && touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) + CocosEditBoxAbility.this.hide(); + return true; + } + }); + // When touch area outside EditText and soft keyboard, then hide. + Component layout = findComponentById(ResourceTable.Id_editbox_container); + layout.setTouchEventListener(new Component.TouchEventListener() { + @Override + public boolean onTouchEvent(Component component, TouchEvent touchEvent) { + if(touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) { + CocosEditBoxAbility.this.hide(); + } + return true; + } + }); +// layout.setLayoutRefreshedListener(new Component.LayoutRefreshedListener() { +// @Override +// public void onRefreshed(Component component) { +// // detect keyboard re-layout? +// HiLog.debug(LABEL, component.getClass().getSimpleName()); +// } +// }); + } + + private TextField getTextField() { + return (TextField)findComponentById(ResourceTable.Id_editbox_textField); + } + + private void hide() { +// Utils.hideVirtualButton(); // TODO: hide virtual button + this.closeKeyboard(); + } + + public void show(String defaultValue, int maxLength, boolean isMultiline, boolean confirmHold, String confirmType, String inputType) { + TextField tf = getTextField(); + mConfirmHold = confirmHold; + mTextFieldHelper.show(defaultValue, maxLength, isMultiline, confirmHold, confirmType, inputType); + int editPaddingBottom = tf.getPaddingBottom(); + int editPadding = tf.getPaddingTop(); + tf.setPadding(editPadding, editPadding, editPadding, editPaddingBottom); + + + mButton.setText(mButtonTitle); +// if (mButtonTitle == null || mButton.length() == 0) { +// mButton.setPadding(0, 0, 0, 0); +// mButtonParams.setMargins(0, 0, 0, 0); +// mButtonLayout.setVisibility(Component.INVISIBLE); +// } else { +// int buttonTextPadding = mEditText.getPaddingBottom() / 2; +// mButton.setPadding(editPadding, buttonTextPadding, editPadding, buttonTextPadding); +// mButtonParams.setMargins(0, buttonTextPadding, 2, 0); +// mButtonLayout.setVisibility(Component.VISIBLE); +// } + + this.openKeyboard(); + } + + private void closeKeyboard() { + TextField tf = getTextField(); + CocosHelper.runOnUIThread(tf::clearFocus); + this.onKeyboardComplete(tf.getText()); + + } + + private void openKeyboard() { + TextField tf = getTextField(); + CocosHelper.runOnUIThread(() -> { + tf.requestFocus(); + tf.simulateClick(); + }); + } + + private SoftKeyBoardController getKeyBoard() { + return ((AccessibleAbility)getAbility()).getSoftKeyBoardController(); + } + + /*************************************************************************************** + Functions invoked by CPP. + **************************************************************************************/ + + @SuppressWarnings("unused") + private static void showNative(String defaultValue, int maxLength, boolean isMultiline, boolean confirmHold, String confirmType, String inputType) { + + CocosHelper.runOnUIThread(() -> { + Intent i = new Intent(); + i.setParam("defaultValue", defaultValue); + i.setParam("maxLength", maxLength); + i.setParam("isMultiline", isMultiline); + i.setParam("confirmHold", confirmHold); + i.setParam("confirmType", confirmType); + i.setParam("inputType", inputType); + CocosEditBoxAbility ability = new CocosEditBoxAbility(); + GlobalObject.getAbilitySlice().present(ability, i); + }); + } + + @SuppressWarnings("unused") + private static void hideNative() { + CocosHelper.runOnUIThread(() -> { + if(null != CocosEditBoxAbility.sThis) { + CocosEditBoxAbility ability = CocosEditBoxAbility.sThis.get(); + if(ability != null) { + ability.hide(); + ability.terminate(); + CocosEditBoxAbility.sThis = null; + } + } + }); + } + + /*************************************************************************************** + Native functions invoked by UI. + **************************************************************************************/ + private void onKeyboardInput(String text) { + CocosHelper.runOnGameThreadAtForeground(() -> + CocosEditBoxAbility.onKeyboardInputNative(text) + ); + } + + private void onKeyboardComplete(String text) { + CocosHelper.runOnGameThreadAtForeground(() -> + CocosEditBoxAbility.onKeyboardCompleteNative(text) + ); + } + + private void onKeyboardConfirm(String text) { + CocosHelper.runOnGameThreadAtForeground(() -> + CocosEditBoxAbility.onKeyboardConfirmNative(text) + ); + } + + private static native void onKeyboardInputNative(String text); + + private static native void onKeyboardCompleteNative(String text); + + private static native void onKeyboardConfirmNative(String text); +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosHandler.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosHandler.java new file mode 100644 index 0000000..17c9044 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosHandler.java @@ -0,0 +1,106 @@ +/**************************************************************************** +Copyright (c) 2010-2013 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import ohos.agp.window.dialog.CommonDialog; +import ohos.agp.window.dialog.IDialog; +import ohos.eventhandler.EventHandler; +import ohos.eventhandler.EventRunner; +import ohos.eventhandler.InnerEvent; + +import java.lang.ref.WeakReference; + +public class CocosHandler extends EventHandler { + // =========================================================== + // Constants + // =========================================================== + public final static int HANDLER_SHOW_DIALOG = 1; + + // =========================================================== + // Fields + // =========================================================== + private WeakReference mActivity; + + // =========================================================== + // Constructors + // =========================================================== + public CocosHandler(CocosAbilitySlice activity) { + super(EventRunner.getMainEventRunner()); + this.mActivity = new WeakReference(activity); + } + + // =========================================================== + // Getter & Setter + // =========================================================== + + // =========================================================== + // Methods for/from SuperClass/Interfaces + // =========================================================== + + // =========================================================== + // Methods + // =========================================================== + + @Override + public void processEvent(InnerEvent msg) { + switch (msg.eventId) { + case CocosHandler.HANDLER_SHOW_DIALOG: + showDialog(msg); + break; + } + } + + private void showDialog(InnerEvent msg) { + CocosAbilitySlice theActivity = this.mActivity.get(); + DialogMessage dialogMessage = (DialogMessage)msg.object; + + CommonDialog dialog = new CommonDialog(theActivity.getContext()); + dialog.setTitleText(dialogMessage.title) + .setContentText(dialogMessage.message) + .setButton(IDialog.BUTTON1, "OK", new IDialog.ClickedListener() { + @Override + public void onClick(IDialog iDialog, int i) { + + } + }); + dialog.show(); + } + + // =========================================================== + // Inner and Anonymous Classes + // =========================================================== + + public static class DialogMessage { + public String title; + public String message; + + public DialogMessage(String title, String message) { + this.title = title; + this.message = message; + } + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosHelper.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosHelper.java new file mode 100644 index 0000000..85dafed --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosHelper.java @@ -0,0 +1,388 @@ +/**************************************************************************** + Copyright (c) 2010-2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.aafwk.content.Intent; +import ohos.aafwk.content.Operation; +import ohos.agp.text.Font; +import ohos.agp.window.service.Display; +import ohos.agp.window.service.DisplayManager; +import ohos.app.Context; +import ohos.app.dispatcher.TaskDispatcher; +import ohos.batterymanager.BatteryInfo; +import ohos.event.commonevent.*; +import ohos.global.resource.RawFileEntry; +import ohos.global.resource.Resource; +import ohos.miscservices.pasteboard.PasteData; +import ohos.miscservices.pasteboard.SystemPasteboard; +import ohos.net.NetManager; +import ohos.rpc.RemoteException; +import ohos.system.DeviceInfo; +import ohos.system.version.SystemVersion; +import ohos.utils.net.Uri; +import ohos.vibrator.agent.VibratorAgent; +import ohos.vibrator.bean.VibrationPattern; +import ohos.wifi.WifiDevice; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.FutureTask; + + +public class CocosHelper { + // =========================================================== + // Constants + // =========================================================== + private static final String TAG = CocosHelper.class.getSimpleName(); + + // =========================================================== + // Fields + // =========================================================== + + private static AbilitySlice sAbilitySlice; + private static VibratorAgent sVibrateService; + private static Optional sBatteryReceiver = Optional.empty(); + private static Thread sUIThread = null; + + public static final int NETWORK_TYPE_NONE = 0; + public static final int NETWORK_TYPE_LAN = 1; + public static final int NETWORK_TYPE_WWAN = 2; + + // The absolute path to the OBB if it exists. + private static String sObbFilePath = ""; + + + static class LockedTaskQ { + private final Object readMtx = new Object(); + private Queue sTaskQ = new LinkedList<>(); + public void addTask(Runnable runnable) { + synchronized (readMtx) { + sTaskQ.add(runnable); + } + } + public void runTasks(){ + Queue tmp; + synchronized (readMtx) { + tmp = sTaskQ; + sTaskQ = new LinkedList<>(); + } + for(Runnable runnable : tmp){ + runnable.run(); + } + } + } + + private static LockedTaskQ sTaskQOnGameThread = new LockedTaskQ(); + private static LockedTaskQ sForegroundTaskQOnGameThread = new LockedTaskQ(); + /** + * Battery receiver to getting battery level. + */ + static class BatteryReceiver extends CommonEventSubscriber { + public float sBatteryLevel = 0.0f; + + public BatteryReceiver(CommonEventSubscribeInfo subscribeInfo) { + super(subscribeInfo); + } + + @Override + public void onReceiveEvent(CommonEventData commonEventData) { + Intent intent = commonEventData.getIntent(); + if (intent != null) { + int capacity = intent.getIntParam(BatteryInfo.OHOS_BATTERY_CAPACITY, 100); + float level = capacity / 100.0f; + sBatteryLevel = Math.min(Math.max(level, 0.0f), 1.0f); + } + + } + } + + static void registerBatteryLevelReceiver(Context context) { + if (sBatteryReceiver.isPresent()) return; + + MatchingSkills ms = new MatchingSkills(); + ms.addEvent(CommonEventSupport.COMMON_EVENT_BATTERY_CHANGED); + CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(ms); + sBatteryReceiver = Optional.of(new BatteryReceiver(subscribeInfo)); + try { + CommonEventManager.subscribeCommonEvent(sBatteryReceiver.get()); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("unused") + static void unregisterBatteryLevelReceiver(Context context) { + if (sBatteryReceiver.isPresent()) { + try { + CommonEventManager.unsubscribeCommonEvent(sBatteryReceiver.get()); + } catch (RemoteException e) { + e.printStackTrace(); + } + sBatteryReceiver = Optional.empty(); + } + } + + //Run on game thread forever, no matter foreground or background + public static void runOnGameThread(final Runnable runnable) { + sTaskQOnGameThread.addTask(runnable); + } + @SuppressWarnings("unused") + static void flushTasksOnGameThread() { + sTaskQOnGameThread.runTasks(); + } + public static void runOnGameThreadAtForeground(final Runnable runnable) { + sForegroundTaskQOnGameThread.addTask(runnable); + } + @SuppressWarnings("unused") + static void flushTasksOnGameThreadAtForeground() { + sForegroundTaskQOnGameThread.runTasks(); + } + + @SuppressWarnings("unused") + public static int getNetworkType() { + + NetManager netManager = NetManager.getInstance(sAbilitySlice.getContext()); + if (!netManager.hasDefaultNet()) return NETWORK_TYPE_NONE; + + WifiDevice wifiDevice = WifiDevice.getInstance(sAbilitySlice.getContext()); + if (null == wifiDevice) return NETWORK_TYPE_NONE; + + if (wifiDevice.isWifiActive() && wifiDevice.isConnected()) { + return NETWORK_TYPE_LAN; + } + return NETWORK_TYPE_WWAN; + } + + // =========================================================== + // Constructors + // =========================================================== + + private static boolean sInited = false; + + public static void init(final AbilitySlice activity) { + sAbilitySlice = activity; + if (!sInited) { + CocosHelper.sVibrateService = new VibratorAgent(); + CocosHelper.sUIThread = Thread.currentThread(); + sInited = true; + } + + } + + @SuppressWarnings("unused") + public static float getBatteryLevel() { + return sBatteryReceiver.map(x -> x.sBatteryLevel).orElse(1.0f); + } + + @SuppressWarnings("unused") + public static String getObbFilePath() { + return CocosHelper.sObbFilePath; + } + + public static String getWritablePath() { + return sAbilitySlice.getApplicationContext().getFilesDir().getAbsolutePath(); + } + + @SuppressWarnings("unused") + public static String getCurrentLanguage() { + return Locale.getDefault().getLanguage(); + } + + @SuppressWarnings("unused") + public static String getCurrentLanguageCode() { + return Locale.getDefault().toString(); + } + + @SuppressWarnings("unused") + public static String getDeviceModel() { + return DeviceInfo.getModel(); + } + + @SuppressWarnings("unused") + public static String getSystemVersion() { + return SystemVersion.getVersion(); + } + + @SuppressWarnings("unused") + public static void vibrate(float durSec) { + List vlist = sVibrateService.getVibratorIdList(); + if (vlist.isEmpty()) return; + int durationMs = (int) (1000 * durSec); + int vibrateId = -1; + for (Integer vId : vlist) { + // TODO: choose preferred vibration effect + if (sVibrateService.isEffectSupport(vId, VibrationPattern.VIBRATOR_TYPE_CAMERA_CLICK)) { + vibrateId = vId; + break; + } + } + if (vibrateId < 0) { + sVibrateService.startOnce(durationMs); + } else { + sVibrateService.startOnce(durationMs, vibrateId); + } + } + + @SuppressWarnings("unused") + public static boolean openURL(String url) { + runOnUIThread(new Runnable() { + @Override + public void run() { + Intent i = new Intent(); + Operation operation = new Intent.OperationBuilder() + .withUri(Uri.parse(url)) + .build(); + i.setOperation(operation); + sAbilitySlice.startAbility(i); + } + }); + return true; + } + + @SuppressWarnings("unused") + public static void copyTextToClipboard(final String text) { + runOnUIThread(new Runnable() { + @Override + public void run() { + PasteData pasteData = PasteData.creatPlainTextData(text); + SystemPasteboard.getSystemPasteboard(sAbilitySlice.getContext()).setPasteData(pasteData); + } + }); + } + + public static void runOnUIThread(final Runnable r, boolean forceDelay) { + if (Thread.currentThread().getId() == sUIThread.getId() && !forceDelay) { + r.run(); + } else { + TaskDispatcher dispatcher = sAbilitySlice.getUITaskDispatcher(); + dispatcher.asyncDispatch(r); + } + } + + public static void runOnUIThread(final Runnable r) { + runOnUIThread(r, false); + } + + public static void ruOnUIThreadSync(final Runnable r) { + CountDownLatch cd = new CountDownLatch(1); + runOnUIThread(() -> { + r.run(); + cd.countDown(); + }); + try { + cd.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public static int getDeviceRotation() { + try { + DisplayManager mgr = DisplayManager.getInstance(); + Optional display = mgr.getDefaultDisplay(sAbilitySlice.getContext()); + return display.map(Display::getRotation).orElse(0); + } catch (NullPointerException e) { + e.printStackTrace(); + } + // 0 indicates no rotation, + // 1 indicates 90 degrees, + // 2 indicates 180 degrees, + // 3 indicates 270 degrees. + return 0; + } + + public static float[] getSafeArea() { + return new float[]{0, 0, 0, 0}; + } + + @SuppressWarnings("unused") + public static int getDPI() { + Optional disp = DisplayManager.getInstance().getDefaultDisplay(getContext()); + if (disp.isPresent()) { + return (int) disp.get().getAttributes().xDpi; + } + return -1; + } + + public static Context getContext() { + return sAbilitySlice.getContext(); + } + + public static File copyOutResFile(Context ctx, String path, String tmpName) throws IOException{ + File fontTmpFile; + FileOutputStream fontOutputStream=null; + Resource resource = null; + if(!path.startsWith("resources/rawfile/")){ + path = "resources/rawfile/" + path; + } + RawFileEntry entry = ctx.getResourceManager().getRawFileEntry(path); + try { + fontTmpFile = File.createTempFile(tmpName, "-tmp"); + fontOutputStream = new FileOutputStream(fontTmpFile); + resource = entry.openRawFile(); + byte[] buf = new byte[4096]; + while (resource.available() > 0) { + int readBytes = resource.read(buf, 0, 4096); + if (readBytes > 0) + fontOutputStream.write(buf, 0, readBytes); + } + } finally { + if(fontOutputStream!=null) + fontOutputStream.close(); + if(resource != null) + resource.close(); + } + return fontTmpFile; + } + + public static File copyToTempFile(String path, String tmpName) throws IOException { + File fontTmpFile; + FileOutputStream fontOutputStream=null; + FileInputStream fis = null; + try { + fontTmpFile = File.createTempFile(tmpName, "-tmp"); + fontOutputStream = new FileOutputStream(fontTmpFile); + fis = new FileInputStream(path); + byte[] buf = new byte[4096]; + while (fis.available() > 0) { + int readBytes = fis.read(buf, 0, 4096); + if (readBytes > 0) + fontOutputStream.write(buf, 0, readBytes); + } + } finally { + if(fontOutputStream!=null) + fontOutputStream.close(); + if(fis != null) + fis.close(); + } + return fontTmpFile; + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosHttpURLConnection.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosHttpURLConnection.java new file mode 100644 index 0000000..fb3d202 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosHttpURLConnection.java @@ -0,0 +1,433 @@ +/**************************************************************************** + Copyright (c) 2010-2014 cocos2d-x.org + Copyright (c) 2014-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + + +import ohos.global.resource.RawFileEntry; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URL; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +public class CocosHttpURLConnection { + private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, "CocosHttpURLConnection"); + private static final String POST_METHOD = "POST"; + private static final String PUT_METHOD = "PUT"; + private static final String PATCH_METHOD = "PATCH"; + + @SuppressWarnings("unused") + static HttpURLConnection createHttpURLConnection(String linkURL) { + URL url; + HttpURLConnection urlConnection; + try { + url = new URL(linkURL); + urlConnection = (HttpURLConnection) url.openConnection(); + //Accept-Encoding + urlConnection.setRequestProperty("Accept-Encoding", "identity"); + urlConnection.setDoInput(true); + } catch (Exception e) { + e.printStackTrace(); + HiLog.error(LABEL, "createHttpURLConnection:" + e.toString()); + return null; + } + + return urlConnection; + } + + @SuppressWarnings("unused") + static void setReadAndConnectTimeout(HttpURLConnection urlConnection, int readMiliseconds, int connectMiliseconds) { + urlConnection.setReadTimeout(readMiliseconds); + urlConnection.setConnectTimeout(connectMiliseconds); + } + + @SuppressWarnings("unused") + static void setRequestMethod(HttpURLConnection urlConnection, String method) { + try { + urlConnection.setRequestMethod(method); + if (method.equalsIgnoreCase(POST_METHOD) || method.equalsIgnoreCase(PUT_METHOD)) { + urlConnection.setDoOutput(true); + } + } catch (ProtocolException e) { + HiLog.error(LABEL, "setRequestMethod:" + e.toString()); + } + + } + + @SuppressWarnings("unused") + static void setVerifySSL(HttpURLConnection urlConnection, String sslFilename) { + if (!(urlConnection instanceof HttpsURLConnection)) + return; + + + HttpsURLConnection httpsURLConnection = (HttpsURLConnection) urlConnection; + + try { + InputStream caInput = null; + FileInputStream fileInputStream = null; + if (sslFilename.startsWith("/")) { + caInput = new BufferedInputStream(new FileInputStream(sslFilename)); + } else { + String assetString = "assets/"; + String assetsfilenameString = sslFilename.substring(assetString.length()); + RawFileEntry fileEntry = CocosHelper.getContext().getResourceManager().getRawFileEntry(assetsfilenameString); + fileInputStream = new FileInputStream(fileEntry.openRawFileDescriptor().getFileDescriptor()); + caInput = new BufferedInputStream(fileInputStream); + } + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate ca; + ca = cf.generateCertificate(caInput); + System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); + caInput.close(); + if (fileInputStream != null) fileInputStream.close(); + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca", ca); + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + + // Create an SSLContext that uses our TrustManager + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + + httpsURLConnection.setSSLSocketFactory(context.getSocketFactory()); + } catch (Exception e) { + e.printStackTrace(); + HiLog.error(LABEL, "setVerifySSL:" + e.toString()); + } + } + + + @SuppressWarnings("unused") + static void addRequestHeader(HttpURLConnection urlConnection, String key, String value) { + urlConnection.setRequestProperty(key, value); + } + + @SuppressWarnings("unused") + static int connect(HttpURLConnection http) { + int suc = 0; + + try { + http.connect(); + } catch (Exception e) { + e.printStackTrace(); + HiLog.error(LABEL, "connect" + e.toString()); + suc = 1; + } + + return suc; + } + + @SuppressWarnings("unused") + static void disconnect(HttpURLConnection http) { + http.disconnect(); + } + + @SuppressWarnings("unused") + static void sendRequest(HttpURLConnection http, byte[] byteArray) { + try { + OutputStream out = http.getOutputStream(); + if (null != byteArray) { + out.write(byteArray); + out.flush(); + } + out.close(); + } catch (Exception e) { + e.printStackTrace(); + HiLog.error(LABEL, "sendRequest:" + e.toString()); + } + } + + @SuppressWarnings("unused") + static String getResponseHeaders(HttpURLConnection http) { + Map> headers = http.getHeaderFields(); + if (null == headers) { + return null; + } + + String header = ""; + + for (Entry> entry : headers.entrySet()) { + String key = entry.getKey(); + if (null == key) { + header += listToString(entry.getValue(), ",") + "\n"; + } else { + header += key + ":" + listToString(entry.getValue(), ",") + "\n"; + } + } + + return header; + } + + @SuppressWarnings("unused") + static String getResponseHeaderByIdx(HttpURLConnection http, int idx) { + Map> headers = http.getHeaderFields(); + if (null == headers) { + return null; + } + + String header = null; + + int counter = 0; + for (Entry> entry : headers.entrySet()) { + if (counter == idx) { + String key = entry.getKey(); + if (null == key) { + header = listToString(entry.getValue(), ",") + "\n"; + } else { + header = key + ":" + listToString(entry.getValue(), ",") + "\n"; + } + break; + } + counter++; + } + + return header; + } + + @SuppressWarnings("unused") + static String getResponseHeaderByKey(HttpURLConnection http, String key) { + if (null == key) { + return null; + } + + Map> headers = http.getHeaderFields(); + if (null == headers) { + return null; + } + + String header = null; + + for (Entry> entry : headers.entrySet()) { + if (key.equalsIgnoreCase(entry.getKey())) { + if ("set-cookie".equalsIgnoreCase(key)) { + header = combinCookies(entry.getValue(), http.getURL().getHost()); + } else { + header = listToString(entry.getValue(), ","); + } + break; + } + } + + return header; + } + + @SuppressWarnings("unused") + static int getResponseHeaderByKeyInt(HttpURLConnection http, String key) { + String value = http.getHeaderField(key); + + if (null == value) { + return 0; + } else { + return Integer.parseInt(value); + } + } + + @SuppressWarnings("unused") + static byte[] getResponseContent(HttpURLConnection http) { + InputStream in; + try { + in = http.getInputStream(); + String contentEncoding = http.getContentEncoding(); + if (contentEncoding != null) { + if (contentEncoding.equalsIgnoreCase("gzip")) { + in = new GZIPInputStream(http.getInputStream()); //reads 2 bytes to determine GZIP stream! + } else if (contentEncoding.equalsIgnoreCase("deflate")) { + in = new InflaterInputStream(http.getInputStream()); + } + } + } catch (IOException e) { + in = http.getErrorStream(); + } catch (Exception e) { + e.printStackTrace(); + HiLog.error(LABEL, "1 getResponseContent: " + e.toString()); + return null; + } + + try { + byte[] buffer = new byte[1024]; + int size = 0; + ByteArrayOutputStream bytestream = new ByteArrayOutputStream(); + while ((size = in.read(buffer, 0, 1024)) != -1) { + bytestream.write(buffer, 0, size); + } + byte retbuffer[] = bytestream.toByteArray(); + bytestream.close(); + return retbuffer; + } catch (Exception e) { + e.printStackTrace(); + HiLog.error(LABEL, "2 getResponseContent:" + e.toString()); + } + + return null; + } + + @SuppressWarnings("unused") + static int getResponseCode(HttpURLConnection http) { + int code = 0; + try { + code = http.getResponseCode(); + } catch (Exception e) { + e.printStackTrace(); + HiLog.error(LABEL, "getResponseCode:" + e.toString()); + } + return code; + } + + @SuppressWarnings("unused") + static String getResponseMessage(HttpURLConnection http) { + String msg; + try { + msg = http.getResponseMessage(); + } catch (Exception e) { + e.printStackTrace(); + msg = e.toString(); + HiLog.error(LABEL, "getResponseMessage: " + msg); + } + + return msg; + } + + public static String listToString(List list, String strInterVal) { + if (list == null) { + return null; + } + StringBuilder result = new StringBuilder(); + boolean flag = false; + for (String str : list) { + if (flag) { + result.append(strInterVal); + } + if (null == str) { + str = ""; + } + result.append(str); + flag = true; + } + return result.toString(); + } + + public static String combinCookies(List list, String hostDomain) { + StringBuilder sbCookies = new StringBuilder(); + String domain = hostDomain; + String tailmatch = "FALSE"; + String path = "/"; + String secure = "FALSE"; + String key = null; + String value = null; + String expires = null; + for (String str : list) { + String[] parts = str.split(";"); + for (String part : parts) { + int firstIndex = part.indexOf("="); + if (-1 == firstIndex) + continue; + + String[] item = {part.substring(0, firstIndex), part.substring(firstIndex + 1)}; + if ("expires".equalsIgnoreCase(item[0].trim())) { + expires = str2Seconds(item[1].trim()); + } else if ("path".equalsIgnoreCase(item[0].trim())) { + path = item[1]; + } else if ("secure".equalsIgnoreCase(item[0].trim())) { + secure = item[1]; + } else if ("domain".equalsIgnoreCase(item[0].trim())) { + domain = item[1]; + } else if ("version".equalsIgnoreCase(item[0].trim()) || "max-age".equalsIgnoreCase(item[0].trim())) { + //do nothing + } else { + key = item[0]; + value = item[1]; + } + } + + if (null == domain) { + domain = "none"; + } + + sbCookies.append(domain); + sbCookies.append('\t'); + sbCookies.append(tailmatch); //access + sbCookies.append('\t'); + sbCookies.append(path); //path + sbCookies.append('\t'); + sbCookies.append(secure); //secure + sbCookies.append('\t'); + sbCookies.append(expires); //expires + sbCookies.append("\t"); + sbCookies.append(key); //key + sbCookies.append("\t"); + sbCookies.append(value); //value + sbCookies.append('\n'); + } + + return sbCookies.toString(); + } + + private static String str2Seconds(String strTime) { + Calendar c = Calendar.getInstance(); + long milliseconds = 0; + + try { + c.setTime(new SimpleDateFormat("EEE, dd-MMM-yy hh:mm:ss zzz", Locale.US).parse(strTime)); + milliseconds = c.getTimeInMillis() / 1000; + } catch (ParseException e) { + HiLog.error(LABEL, "str2Seconds: " + e.toString()); + } + + return Long.toString(milliseconds); + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosJavascriptJavaBridge.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosJavascriptJavaBridge.java new file mode 100644 index 0000000..1153ba1 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosJavascriptJavaBridge.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2013-2016 Chukong Technologies Inc. + * Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.cocos.lib; + +public class CocosJavascriptJavaBridge { + @SuppressWarnings("unused") + public static native int evalString(String value); +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosKeyCodeHandler.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosKeyCodeHandler.java new file mode 100644 index 0000000..26f19e9 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosKeyCodeHandler.java @@ -0,0 +1,78 @@ +/**************************************************************************** + Copyright (c) 2010-2013 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + + +import ohos.multimodalinput.event.KeyEvent; + +public class CocosKeyCodeHandler { + private CocosAbilitySlice mAct; + @SuppressWarnings("unused") + public native void handleKeyDown(final int keyCode); + @SuppressWarnings("unused") + public native void handleKeyUp(final int keyCode); + + public CocosKeyCodeHandler(CocosAbilitySlice act) { + mAct = act; + } + + public boolean onKeyDown(final int keyCode, final KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEY_BACK: +// CocosVideoHelper.mVideoHandler.sendEmptyMessage(CocosVideoHelper.KeyEventBack); + case KeyEvent.KEY_MENU: + case KeyEvent.KEY_DPAD_LEFT: + case KeyEvent.KEY_DPAD_RIGHT: + case KeyEvent.KEY_DPAD_UP: + case KeyEvent.KEY_DPAD_DOWN: + case KeyEvent.KEY_ENTER: + case KeyEvent.KEY_MEDIA_PLAY_PAUSE: + case KeyEvent.KEY_DPAD_CENTER: + CocosHelper.runOnGameThreadAtForeground(() -> handleKeyDown(keyCode)); + return true; + default: + return false; + } + } + + public boolean onKeyUp(final int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEY_BACK: + case KeyEvent.KEY_MENU: + case KeyEvent.KEY_DPAD_LEFT: + case KeyEvent.KEY_DPAD_RIGHT: + case KeyEvent.KEY_DPAD_UP: + case KeyEvent.KEY_DPAD_DOWN: + case KeyEvent.KEY_ENTER: + case KeyEvent.KEY_MEDIA_PLAY_PAUSE: + case KeyEvent.KEY_DPAD_CENTER: + CocosHelper.runOnGameThreadAtForeground(() -> handleKeyDown(keyCode)); + return true; + default: + return false; + } + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosLocalStorage.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosLocalStorage.java new file mode 100644 index 0000000..ee5a6a1 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosLocalStorage.java @@ -0,0 +1,116 @@ +/**************************************************************************** + Copyright (c) 2021 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import ohos.data.DatabaseHelper; +import ohos.data.rdb.*; +import ohos.data.resultset.ResultSet; + +public class CocosLocalStorage { + + private static String DATABASE_NAME = "jsb.storage.db"; + private static String TABLE_NAME = "data"; + private static final int DATABASE_VERSION = 1; + + private static DatabaseHelper mDatabaseOpenHelper = null; + private static RdbStore mDatabase = null; + + private static RdbOpenCallback rdbOpenCallback = new RdbOpenCallback() { + @Override + public void onCreate(RdbStore rdbStore) { + rdbStore.executeSql("CREATE TABLE IF NOT EXISTS "+TABLE_NAME+"(key TEXT PRIMARY KEY,value TEXT);"); + } + + @Override + public void onUpgrade(RdbStore rdbStore, int i, int i1) { + + } + }; + + public static boolean init(String dbName, String tableName) { + if (GlobalObject.getAbilitySlice() != null) { + DATABASE_NAME = dbName; + TABLE_NAME = tableName; + mDatabaseOpenHelper = new DatabaseHelper(GlobalObject.getAbilitySlice()); + StoreConfig cfg = StoreConfig.newDefaultConfig(DATABASE_NAME); + mDatabase = mDatabaseOpenHelper.getRdbStore(cfg, DATABASE_VERSION, rdbOpenCallback, null); + return true; + } + return false; + } + + private static String getTableName() { + return DATABASE_NAME + "." + TABLE_NAME; + } + + public static void destroy() { + if (mDatabase != null) { + mDatabaseOpenHelper.deleteRdbStore(DATABASE_NAME); + } + } + + public static void setItem(String key, String value) { + ValuesBucket valuesBucket = new ValuesBucket(); + valuesBucket.putString("key", key); + valuesBucket.putString("value", value); + mDatabase.insert(TABLE_NAME, valuesBucket); + } + + public static String getItem(String key) { + String[] columes = new String[] {"value"}; + RdbPredicates rdbPredicates = new RdbPredicates(TABLE_NAME).equalTo("key", key); + ResultSet resultSet = mDatabase.query(rdbPredicates, columes); + if(resultSet.goToNextRow()) { + return resultSet.getString(0); + } + return null; + } + + public static void removeItem(String key) { + RdbPredicates rdbPredicates = new RdbPredicates(TABLE_NAME).equalTo("key", key); + mDatabase.delete(rdbPredicates); + } + + public static void clear() { + RdbPredicates rdbPredicates = new RdbPredicates(TABLE_NAME); + mDatabase.delete(rdbPredicates); + } + @SuppressWarnings("unused") + public static String getKey(int nIndex) { + + ResultSet result = mDatabase.querySql("SELECT key from "+TABLE_NAME + " LIMIT 1 OFFSET " + nIndex, null); + if(result.goToNextRow()){ + return result.getString(result.getColumnIndexForName("key")); + } + return null; + } + + public static int getLength() { + ResultSet result = mDatabase.querySql("SELECT count(key) as cnt FROM "+TABLE_NAME, null); + if(result.goToNextRow()) { + return result.getInt(result.getColumnIndexForName("cnt")); + } + return 0; + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosOrientationHelper.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosOrientationHelper.java new file mode 100644 index 0000000..62b14c9 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosOrientationHelper.java @@ -0,0 +1,84 @@ +/**************************************************************************** + * Copyright (c) 2020 Xiamen Yaji Software Co., Ltd. + * + * http://www.cocos.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + + +import ohos.app.Context; +import ohos.sensor.agent.CategoryOrientationAgent; +import ohos.sensor.agent.SensorAgent; +import ohos.sensor.bean.CategoryOrientation; +import ohos.sensor.data.CategoryOrientationData; +import ohos.sensor.listener.ICategoryOrientationDataCallback; + +public class CocosOrientationHelper implements ICategoryOrientationDataCallback { + + private int mCurrentOrientation; + private CategoryOrientationAgent agent; + private CategoryOrientation orientation; + + private final int matrix_length = 9; + private final int rotationVectorLength = 9; + + public CocosOrientationHelper(Context context) { + agent = new CategoryOrientationAgent(); + orientation = agent.getSingleSensor(CategoryOrientation.SENSOR_TYPE_ORIENTATION); + mCurrentOrientation = CocosHelper.getDeviceRotation(); + } + + public void onPause() { + agent.releaseSensorDataCallback(this, orientation); + } + + public void onResume() { + boolean ok = agent.setSensorDataCallback(this, orientation, SensorAgent.SENSOR_SAMPLING_RATE_GAME); + } + + + private static native void nativeOnOrientationChanged(int rotation); + + @Override + public void onSensorDataModified(CategoryOrientationData data) { + final int curOrientation = CocosHelper.getDeviceRotation(); + if (curOrientation != mCurrentOrientation) { + mCurrentOrientation = curOrientation; + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + nativeOnOrientationChanged(mCurrentOrientation); + } + }); + } + } + + @Override + public void onAccuracyDataModified(CategoryOrientation categoryOrientation, int i) { + + } + + @Override + public void onCommandCompleted(CategoryOrientation categoryOrientation) { + + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosReflectionHelper.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosReflectionHelper.java new file mode 100644 index 0000000..ff07335 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosReflectionHelper.java @@ -0,0 +1,77 @@ +/**************************************************************************** +Copyright (c) 2016 cocos2d-x.org +Copyright (c) 2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class CocosReflectionHelper { + private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, "CocosReflection"); + public static T getConstantValue(final Class aClass, final String constantName) { + try { + return (T)aClass.getDeclaredField(constantName).get(null); + } catch (NoSuchFieldException e) { + HiLog.error(LABEL, "can not find " + constantName + " in " + aClass.getName()); + } + catch (IllegalAccessException e) { + HiLog.error(LABEL, constantName + " is not accessable"); + } + catch (IllegalArgumentException e) { + HiLog.error(LABEL, "arguments error when get " + constantName); + } + catch (Exception e) { + HiLog.error(LABEL, "can not get constant" + constantName); + } + + return null; + } + @SuppressWarnings("unused") + public static T invokeInstanceMethod(final Object instance, final String methodName, + final Class[] parameterTypes, final Object[] parameters) { + + final Class aClass = instance.getClass(); + try { + final Method method = aClass.getMethod(methodName, parameterTypes); + return (T)method.invoke(instance, parameters); + } catch (NoSuchMethodException e) { + HiLog.error(LABEL, "can not find " + methodName + " in " + aClass.getName()); + } + catch (IllegalAccessException e) { + HiLog.error(LABEL, methodName + " is not accessible"); + } + catch (IllegalArgumentException e) { + HiLog.error(LABEL, "arguments are error when invoking " + methodName); + } + catch (InvocationTargetException e) { + HiLog.error(LABEL, "an exception was thrown by the invoked method when invoking " + methodName); + } + + return null; + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosSensorHandler.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosSensorHandler.java new file mode 100644 index 0000000..766af8d --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosSensorHandler.java @@ -0,0 +1,156 @@ +/**************************************************************************** + Copyright (c) 2010-2013 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.sensor.agent.CategoryMotionAgent; +import ohos.sensor.agent.SensorAgent; +import ohos.sensor.bean.CategoryMotion; +import ohos.sensor.data.CategoryMotionData; +import ohos.sensor.listener.ICategoryMotionDataCallback; + +public class CocosSensorHandler implements ICategoryMotionDataCallback { + // =========================================================== + // Constants + // =========================================================== + + private static final HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP, 0, "CocosSensorHandler"); + private static CocosSensorHandler mSensorHandler; + + private final CategoryMotionAgent mSensorManager; + private final CategoryMotion mAcceleration; + private final CategoryMotion mAccelerationIncludingGravity; + private final CategoryMotion mGyroscope; + private static float[] sDeviceMotionValues = new float[9]; + private boolean mEnabled = false; + private long mMaxIntervalNanoSeconds = 0; + // =========================================================== + // Constructors + // =========================================================== + + public CocosSensorHandler() { + mSensorManager = new CategoryMotionAgent(); + mAcceleration = mSensorManager.getSingleSensor(CategoryMotion.SENSOR_TYPE_ACCELEROMETER); + mAccelerationIncludingGravity = mSensorManager.getSingleSensor(CategoryMotion.SENSOR_TYPE_LINEAR_ACCELERATION); + mGyroscope = mSensorManager.getSingleSensor(CategoryMotion.SENSOR_TYPE_GYROSCOPE); + mSensorHandler = this; + } + + // =========================================================== + // Getter & Setter + // =========================================================== + public void enable() { + if(mMaxIntervalNanoSeconds == 0) { + mSensorManager.setSensorDataCallback(this, mAcceleration, SensorAgent.SENSOR_SAMPLING_RATE_FASTEST); + mSensorManager.setSensorDataCallback(this, mAccelerationIncludingGravity, SensorAgent.SENSOR_SAMPLING_RATE_FASTEST); + mSensorManager.setSensorDataCallback(this, mGyroscope, SensorAgent.SENSOR_SAMPLING_RATE_FASTEST); + }else { + mSensorManager.setSensorDataCallback(this, mAcceleration, SensorAgent.SENSOR_SAMPLING_RATE_FASTEST, mMaxIntervalNanoSeconds); + mSensorManager.setSensorDataCallback(this, mAccelerationIncludingGravity, SensorAgent.SENSOR_SAMPLING_RATE_FASTEST, mMaxIntervalNanoSeconds); + mSensorManager.setSensorDataCallback(this, mGyroscope, SensorAgent.SENSOR_SAMPLING_RATE_FASTEST, mMaxIntervalNanoSeconds); + } + mEnabled = true; + } + + + public void disable() { + mSensorManager.releaseSensorDataCallback(this, mAcceleration); + mSensorManager.releaseSensorDataCallback(this, mAccelerationIncludingGravity); + mSensorManager.releaseSensorDataCallback(this, mGyroscope); + mEnabled = false; + } + + public void setInterval(float intervalSeconds) { + mMaxIntervalNanoSeconds = (long)(1000_000_000L * intervalSeconds); + if(mEnabled) { + disable(); + enable(); + } + } + + public void onPause() { + disable(); + } + + public void onResume() { + enable(); + } + + public static void setAccelerometerInterval(float interval) { + mSensorHandler.setInterval(interval); + } + + public static void setAccelerometerEnabled(boolean enabled) { + if(enabled == mSensorHandler.mEnabled) { + return; + } + if (enabled) { + mSensorHandler.enable(); + } else { + mSensorHandler.disable(); + } + } + + @SuppressWarnings("unused") + public static float[] getDeviceMotionValue() { + return sDeviceMotionValues; + } + + @Override + public void onSensorDataModified(CategoryMotionData sensorEvent) { + CategoryMotion type = sensorEvent.getSensor(); + if(type == null) return; + final int sensorId = type.getSensorId(); + if (sensorId == mAcceleration.getSensorId()) { + sDeviceMotionValues[0] = sensorEvent.values[0]; + sDeviceMotionValues[1] = sensorEvent.values[1]; + // Issue https://github.com/cocos-creator/2d-tasks/issues/2532 + // use negative event.acceleration.z to match iOS value + sDeviceMotionValues[2] = -sensorEvent.values[2]; + } else if (sensorId == mAccelerationIncludingGravity.getSensorId()) { + sDeviceMotionValues[3] = sensorEvent.values[0]; + sDeviceMotionValues[4] = sensorEvent.values[1]; + sDeviceMotionValues[5] = sensorEvent.values[2]; + } else if (sensorId == mGyroscope.getSensorId()) { + // The unit is rad/s, need to be converted to deg/s + sDeviceMotionValues[6] = (float) Math.toDegrees(sensorEvent.values[0]); + sDeviceMotionValues[7] = (float) Math.toDegrees(sensorEvent.values[1]); + sDeviceMotionValues[8] = (float) Math.toDegrees(sensorEvent.values[2]); + } + } + + @Override + public void onAccuracyDataModified(CategoryMotion categoryMotion, int i) { + + } + + @Override + public void onCommandCompleted(CategoryMotion categoryMotion) { + + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosTouchHandler.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosTouchHandler.java new file mode 100644 index 0000000..4f99b09 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosTouchHandler.java @@ -0,0 +1,135 @@ +/**************************************************************************** + Copyright (c) 2010-2013 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.multimodalinput.event.MmiPoint; +import ohos.multimodalinput.event.TouchEvent; + +public class CocosTouchHandler { + public final static HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP,0, "CocosTouchHandler"); + private boolean mStopHandleTouchAndKeyEvents = false; + + public CocosTouchHandler() { + + } + + boolean onTouchEvent(TouchEvent pMotionEvent) { + // these data are used in ACTION_MOVE and ACTION_CANCEL + final int pointerNumber = pMotionEvent.getPointerCount(); + final int[] ids = new int[pointerNumber]; + final float[] xs = new float[pointerNumber]; + final float[] ys = new float[pointerNumber]; + + for (int i = 0; i < pointerNumber; i++) { + ids[i] = pMotionEvent.getPointerId(i); + MmiPoint pos = pMotionEvent.getPointerPosition(i); + xs[i] = pos.getX(); + ys[i] = pos.getY(); + } + + int action = pMotionEvent.getAction(); + + HiLog.debug(TAG, "Touch event: " + action); + switch (action) { + case TouchEvent.PRIMARY_POINT_DOWN: + case TouchEvent.OTHER_POINT_DOWN: + if (mStopHandleTouchAndKeyEvents) { +// Cocos2dxEditBox.complete(); + return true; + } + + final int indexPointerDown = pMotionEvent.getIndex(); + final int idPointerDown = pMotionEvent.getPointerId(indexPointerDown); + MmiPoint pos = pMotionEvent.getPointerPosition(indexPointerDown); + final float xPointerDown = pos.getX(); + final float yPointerDown = pos.getY(); + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleActionDown(idPointerDown, xPointerDown, yPointerDown); + } + }); + break; + + case TouchEvent.POINT_MOVE: + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleActionMove(ids, xs, ys); + } + }); + + break; + + case TouchEvent.PRIMARY_POINT_UP: + case TouchEvent.OTHER_POINT_UP: + + final int indexPointUp = pMotionEvent.getIndex(); + final int idPointerUp = pMotionEvent.getPointerId(indexPointUp); + MmiPoint posUP = pMotionEvent.getPointerPosition(indexPointUp); + final float xPointerUp = posUP.getX(); + final float yPointerUp = posUP.getY(); + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleActionUp(idPointerUp, xPointerUp, yPointerUp); + } + }); + + break; + + case TouchEvent.CANCEL: + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + handleActionCancel(ids, xs, ys); + } + }); + break; + default: + HiLog.debug(TAG, "Unhandled touch event: " + action); + break; + } + + return true; + } + @SuppressWarnings("unused") + public void setStopHandleTouchAndKeyEvents(boolean value) { + mStopHandleTouchAndKeyEvents = value; + } + + native void handleActionDown(final int id, final float x, final float y); + + native void handleActionMove(final int[] ids, final float[] xPointerList, final float[] yPointerList); + + native void handleActionUp(final int id, final float x, final float y); + + native void handleActionCancel(final int[] ids, final float[] xPointerList, final float[] yPointerList); + +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosVideoHelper.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosVideoHelper.java new file mode 100644 index 0000000..2f787d7 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosVideoHelper.java @@ -0,0 +1,442 @@ +/**************************************************************************** +Copyright (c) 2014-2016 Chukong Technologies Inc. +Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + +import com.cocos.lib.CocosVideoView.OnVideoEventListener; +import ohos.aafwk.ability.AbilitySlice; +import ohos.agp.components.Component; +import ohos.agp.components.StackLayout; +import ohos.eventhandler.EventHandler; +import ohos.eventhandler.EventRunner; +import ohos.eventhandler.InnerEvent; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.media.image.common.Rect; + +import java.lang.ref.WeakReference; +import java.util.WeakHashMap; + +public class CocosVideoHelper { + + private StackLayout mRootLayout = null; + private AbilitySlice mActivity = null; + private static WeakHashMap sVideoViews = null; + static VideoHandler mVideoHandler = null; + + private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, "CocosVideoHelper"); + + CocosVideoHelper(AbilitySlice activity, StackLayout layout) + { + mActivity = activity; + mRootLayout = layout; + + mVideoHandler = new VideoHandler(this); + sVideoViews = new WeakHashMap<>(); + } + + private static int videoTag = 0; + private final static int VideoTaskCreate = 0; + private final static int VideoTaskRemove = 1; + private final static int VideoTaskSetSource = 2; + private final static int VideoTaskSetRect = 3; + private final static int VideoTaskStart = 4; + private final static int VideoTaskPause = 5; + private final static int VideoTaskResume = 6; + private final static int VideoTaskStop = 7; + private final static int VideoTaskSeek = 8; + private final static int VideoTaskSetVisible = 9; + private final static int VideoTaskRestart = 10; + private final static int VideoTaskKeepRatio = 11; + private final static int VideoTaskFullScreen = 12; + private final static int VideoTaskSetVolume = 13; + + final static int KeyEventBack = 1000; + + static class VideoHandler extends EventHandler { + WeakReference mReference; + + VideoHandler(CocosVideoHelper helper){ + super(EventRunner.getMainEventRunner()); + mReference = new WeakReference(helper); + } + + @Override + public void processEvent(InnerEvent event) { + CocosVideoHelper helper = mReference.get(); + Message msg = (Message) event.object; + switch (msg.what) { + case VideoTaskCreate: { + helper._createVideoView(msg.arg1); + break; + } + case VideoTaskRemove: { + CocosHelper.ruOnUIThreadSync(()->helper._removeVideoView(msg.arg1)); + break; + } + case VideoTaskSetSource: { + helper._setVideoURL(msg.arg1, msg.arg2, (String)msg.obj); + break; + } + case VideoTaskStart: { + helper._startVideo(msg.arg1); + break; + } + case VideoTaskSetRect: { + Rect rect = (Rect)msg.obj; + CocosHelper.ruOnUIThreadSync( + ()->helper._setVideoRect(msg.arg1, rect.minX, rect.minY, rect.width , rect.height) + ); + break; + } + case VideoTaskFullScreen:{ + if (msg.arg2 == 1) { + CocosHelper.ruOnUIThreadSync(()->helper._setFullScreenEnabled(msg.arg1, true)); + } else { + CocosHelper.ruOnUIThreadSync(()->helper._setFullScreenEnabled(msg.arg1, false)); + } + break; + } + case VideoTaskPause: { + helper._pauseVideo(msg.arg1); + break; + } + + case VideoTaskStop: { + helper._stopVideo(msg.arg1); + break; + } + case VideoTaskSeek: { + helper._seekVideoTo(msg.arg1, msg.arg2); + break; + } + case VideoTaskSetVisible: { + if (msg.arg2 == 1) { + CocosHelper.ruOnUIThreadSync(()->helper._setVideoVisible(msg.arg1, true)); + } else { + CocosHelper.ruOnUIThreadSync(()->helper._setVideoVisible(msg.arg1, false)); + } + break; + } + case VideoTaskKeepRatio: { + if (msg.arg2 == 1) { + CocosHelper.ruOnUIThreadSync(()->helper._setVideoKeepRatio(msg.arg1, true)); + } else { + CocosHelper.ruOnUIThreadSync(()->helper._setVideoKeepRatio(msg.arg1, false)); + } + break; + } + case KeyEventBack: { + helper.onBackKeyEvent(); + break; + } + case VideoTaskSetVolume: { + float volume = (float) msg.arg2 / 10; + helper._setVolume(msg.arg1, volume); + break; + } + default: + break; + } + + super.processEvent(event); + } + } + + public static native void nativeExecuteVideoCallback(int index,int event); + + OnVideoEventListener videoEventListener = new OnVideoEventListener() { + + @Override + public void onVideoEvent(int tag,int event) { + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + nativeExecuteVideoCallback(tag, event); + } + }); + } + }; + + static class Message { + public int what =0; + public int arg1 = 0; + public int arg2 = 0; + public Object obj = null; + } + + public static int createVideoWidget() { + Message msg = new Message(); + msg.what = VideoTaskCreate; + msg.arg1 = videoTag; + dispatchMessage(msg); + + return videoTag++; + } + + private static void dispatchMessage(Message msg) { + InnerEvent event = InnerEvent.get(msg.what, msg.arg1, msg); + mVideoHandler.distributeEvent(event); + } + + + private void _createVideoView(int index) { + CocosVideoView videoView = new CocosVideoView(mActivity,index); + sVideoViews.put(index, videoView); + StackLayout.LayoutConfig lParams = new StackLayout.LayoutConfig( + StackLayout.LayoutConfig.MATCH_CONTENT, + StackLayout.LayoutConfig.MATCH_CONTENT); + CocosHelper.runOnUIThread(()->{ + mRootLayout.addComponent(videoView, lParams); + }); + videoView.setVideoViewEventListener(videoEventListener); + } + + public static void removeVideoWidget(int index){ + Message msg = new Message(); + msg.what = VideoTaskRemove; + msg.arg1 = index; + dispatchMessage(msg); + } + + private void _removeVideoView(int index) { + CocosVideoView view = sVideoViews.get(index); + if (view != null) { + view.stopPlayback(); + sVideoViews.remove(index); + mRootLayout.removeComponent(view); + } + } + + public static void setVideoUrl(int index, int videoSource, String videoUrl) { + Message msg = new Message(); + msg.what = VideoTaskSetSource; + msg.arg1 = index; + msg.arg2 = videoSource; + msg.obj = videoUrl; + dispatchMessage(msg); + } + + private void _setVideoURL(int index, int videoSource, String videoUrl) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + switch (videoSource) { + case 0: + videoView.setVideoFileName(videoUrl); + break; + case 1: + videoView.setVideoURL(videoUrl); + break; + default: + break; + } + } + } + + public static void setVideoRect(int index, int left, int top, int maxWidth, int maxHeight) { + Message msg = new Message(); + msg.what = VideoTaskSetRect; + msg.arg1 = index; + msg.obj = new Rect(left, top, maxWidth, maxHeight); + dispatchMessage(msg); + } + + private void _setVideoRect(int index, int left, int top, int maxWidth, int maxHeight) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.setVideoRect(left, top, maxWidth, maxHeight); + } + } + + public static void setFullScreenEnabled(int index, boolean enabled) { + Message msg = new Message(); + msg.what = VideoTaskFullScreen; + msg.arg1 = index; + if (enabled) { + msg.arg2 = 1; + } else { + msg.arg2 = 0; + } + dispatchMessage(msg); + } + + private void _setFullScreenEnabled(int index, boolean enabled) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.setFullScreenEnabled(enabled); + } + } + + private void onBackKeyEvent() { + for(Integer key : sVideoViews.keySet()) { + CocosVideoView videoView = sVideoViews.get(key); + if (videoView != null) { + videoView.setFullScreenEnabled(false); + CocosHelper.runOnGameThreadAtForeground(new Runnable() { + @Override + public void run() { + nativeExecuteVideoCallback(key, KeyEventBack); + } + }); + } + } + } + + public static void startVideo(int index) { + Message msg = new Message(); + msg.what = VideoTaskStart; + msg.arg1 = index; + dispatchMessage(msg); + } + + private void _startVideo(int index) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.start(); + } + } + + public static void pauseVideo(int index) { + Message msg = new Message(); + msg.what = VideoTaskPause; + msg.arg1 = index; + dispatchMessage(msg); + } + + private void _pauseVideo(int index) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.pause(); + } + } + + public static void stopVideo(int index) { + Message msg = new Message(); + msg.what = VideoTaskStop; + msg.arg1 = index; + dispatchMessage(msg); + } + + private void _stopVideo(int index) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.stop(); + } + } + + public static void seekVideoTo(int index,int msec) { + Message msg = new Message(); + msg.what = VideoTaskSeek; + msg.arg1 = index; + msg.arg2 = msec; + dispatchMessage(msg); + } + + private void _seekVideoTo(int index,int msec) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.seekTo(msec); + } + } + + public static float getCurrentTime(final int index) { + CocosVideoView video = sVideoViews.get(index); + float currentPosition = -1; + if (video != null) { + currentPosition = video.getCurrentPosition() / 1000.0f; + } + return currentPosition; + } + + public static float getDuration(final int index) { + CocosVideoView video = sVideoViews.get(index); + float duration = -1; + if (video != null) { + duration = video.getDuration() / 1000.0f; + } + if (duration <= 0) { + HiLog.error(LABEL, "Video player's duration is not ready to get now!"); + } + return duration; + } + + public static void setVideoVisible(int index, boolean visible) { + Message msg = new Message(); + msg.what = VideoTaskSetVisible; + msg.arg1 = index; + if (visible) { + msg.arg2 = 1; + } else { + msg.arg2 = 0; + } + dispatchMessage(msg); + } + + private void _setVideoVisible(int index, boolean visible) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + if (visible) { + videoView.fixSizeAsync(); + videoView.setVisibility(Component.VISIBLE); + } else { + videoView.setVisibility(Component.INVISIBLE); + } + } + } + + public static void setVideoKeepRatioEnabled(int index, boolean enable) { + Message msg = new Message(); + msg.what = VideoTaskKeepRatio; + msg.arg1 = index; + if (enable) { + msg.arg2 = 1; + } else { + msg.arg2 = 0; + } + dispatchMessage(msg); + } + + private void _setVideoKeepRatio(int index, boolean enable) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.setKeepRatio(enable); + } + } + + private void _setVolume(final int index, final float volume) { + CocosVideoView videoView = sVideoViews.get(index); + if (videoView != null) { + videoView.setVolume(volume); + } + } + + public static void setVolume(final int index, final float volume) { + Message msg = new Message(); + msg.what = VideoTaskSetVolume; + msg.arg1 = index; + msg.arg2 = (int) (volume * 10); + dispatchMessage(msg); + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosVideoView.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosVideoView.java new file mode 100644 index 0000000..42d1b8b --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosVideoView.java @@ -0,0 +1,634 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * Copyright (c) 2014-2016 Chukong Technologies Inc. + * Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cocos.lib; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.aafwk.ability.Lifecycle; +import ohos.aafwk.ability.LifecycleStateObserver; +import ohos.aafwk.content.Intent; +import ohos.agp.components.Component; +import ohos.agp.components.StackLayout; +import ohos.agp.components.surfaceprovider.SurfaceProvider; +import ohos.agp.graphics.SurfaceOps; +import ohos.agp.utils.Rect; +import ohos.agp.window.dialog.BaseDialog; +import ohos.agp.window.dialog.PopupDialog; +import ohos.agp.window.dialog.ToastDialog; +import ohos.global.resource.RawFileDescriptor; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.media.audio.AudioManager; +import ohos.media.common.Source; +import ohos.media.player.Player; +import ohos.multimodalinput.event.TouchEvent; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class CocosVideoView extends SurfaceProvider implements Component.TouchEventListener, LifecycleStateObserver { + + // =========================================================== + // Internal classes and interfaces. + // =========================================================== + + public interface OnVideoEventListener { + void onVideoEvent(int tag, int event); + } + + private enum State { + IDLE, + ERROR, + INITIALIZED, + PREPARING, + PREPARED, + STARTED, + PAUSED, + STOPPED, + PLAYBACK_COMPLETED, + } + + // =========================================================== + // Constants + // =========================================================== + private static final String AssetResourceRoot = "@assets/"; + private static final String OHOSResourceRoot = "entry/resources/rawfile/"; + + // =========================================================== + // Fields + // =========================================================== + + private HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP, 0, "CocosVideoView"); + + private Source mVideoUri; + private int mDuration; + private int mPosition; + + private State mCurrentState = State.IDLE; + + // All the stuff we need for playing and showing a video + private Player mMediaPlayer = null; + private int mVideoWidth = 0; + private int mVideoHeight = 0; + + private OnVideoEventListener mOnVideoEventListener; + + // recording the seek position while preparing + private int mSeekWhenPrepared = 0; + + protected AbilitySlice mAbility = null; + + protected int mViewLeft = 0; + protected int mViewTop = 0; + protected int mViewWidth = 0; + protected int mViewHeight = 0; + + protected int mVisibleLeft = 0; + protected int mVisibleTop = 0; + protected int mVisibleWidth = 0; + protected int mVisibleHeight = 0; + + protected boolean mFullScreenEnabled = false; + + private boolean mIsAssetRouse = false; + private String mVideoFilePath = null; + + private int mViewTag = 0; + private boolean mKeepRatio = false; + private boolean mMetaUpdated = false; + + // MediaPlayer will be released when surface view is destroyed, so should record the position, + // and use it to play after MedialPlayer is created again. + private int mPositionBeforeRelease = 0; + + // =========================================================== + // Constructors + // =========================================================== + + public CocosVideoView(AbilitySlice activity, int tag) { + super(activity); + + mViewTag = tag; + mAbility = activity; + mMediaPlayer = new Player(getContext()); + mMediaPlayer.setPlayerCallback(mPlayerCallback); + initVideoView(); + + pinToZTop(true); + } + + // =========================================================== + // Getter & Setter + // =========================================================== + + public void setVideoRect(int left, int top, int maxWidth, int maxHeight) { + if (mViewLeft == left && mViewTop == top && mViewWidth == maxWidth && mViewHeight == maxHeight) + return; + + mViewLeft = left; + mViewTop = top; + mViewWidth = maxWidth; + mViewHeight = maxHeight; + + fixSize(mViewLeft, mViewTop, mViewWidth, mViewHeight); + } + + public void setFullScreenEnabled(boolean enabled) { + if (mFullScreenEnabled != enabled) { + mFullScreenEnabled = enabled; + fixSizeAsync(); + } + } + + public void setVolume(float volume) { + if (mMediaPlayer != null) { + mMediaPlayer.setVolume(volume); + } + } + + public void setKeepRatio(boolean enabled) { + mKeepRatio = enabled; + fixSizeAsync(); + } + + public void setVideoURL(String url) { + mIsAssetRouse = false; + setVideoURI(new Source(url), null); + } + + public void setVideoFileName(String path) { + if (path.startsWith(AssetResourceRoot)) { + path = path.replaceFirst(AssetResourceRoot, OHOSResourceRoot); + } + + if (path.startsWith("/")) { + mIsAssetRouse = false; + setVideoURI(new Source(path), null); + } else { + + mVideoFilePath = path; + mIsAssetRouse = true; + setVideoURI(new Source(path), null); + } + } + + public int getCurrentPosition() { + if (!(mCurrentState == State.IDLE || + mCurrentState == State.ERROR || + mCurrentState == State.INITIALIZED || + mCurrentState == State.STOPPED || + mMediaPlayer == null)) { + mPosition = mMediaPlayer.getCurrentTime(); + } + return mPosition; + } + + public int getDuration() { + if (!(mCurrentState == State.IDLE || + mCurrentState == State.ERROR || + mCurrentState == State.INITIALIZED || + mCurrentState == State.STOPPED || + mMediaPlayer == null)) { + mDuration = mMediaPlayer.getDuration(); + } + + return mDuration; + } + + /** + * Register a callback to be invoked when some video event triggered. + * + * @param l The callback that will be run + */ + public void setVideoViewEventListener(OnVideoEventListener l) { + mOnVideoEventListener = l; + } + + // =========================================================== + // Overrides + // =========================================================== + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + } + + @Override + public boolean onTouchEvent(Component component, TouchEvent touchEvent) { + if ((touchEvent.getAction() & TouchEvent.PRIMARY_POINT_UP) == TouchEvent.PRIMARY_POINT_UP) { + this.sendEvent(EVENT_CLICKED); + } + return true; + } + + // =========================================================== + // Public functions + // =========================================================== + + public void stop() { + if (!(mCurrentState == State.IDLE || mCurrentState == State.INITIALIZED || mCurrentState == State.ERROR || mCurrentState == State.STOPPED) + && mMediaPlayer != null) { + mCurrentState = State.STOPPED; + mMediaPlayer.stop(); + this.sendEvent(EVENT_STOPPED); + + // after the video is stop, it shall prepare to be playable again + try { + mMediaPlayer.prepare(); + this.showFirstFrame(); + } catch (Exception ex) { + } + } + } + + public void stopPlayback() { + this.removeFromWindow(); + this.release(); + } + + public void start() { + if ((mCurrentState == State.PREPARED || + mCurrentState == State.PAUSED || + mCurrentState == State.PLAYBACK_COMPLETED) && + mMediaPlayer != null) { + + mCurrentState = State.STARTED; + mMediaPlayer.play(); + this.sendEvent(EVENT_PLAYING); + } + } + + public void pause() { + if ((mCurrentState == State.STARTED || mCurrentState == State.PLAYBACK_COMPLETED) && + mMediaPlayer != null) { + mCurrentState = State.PAUSED; + mMediaPlayer.pause(); + this.sendEvent(EVENT_PAUSED); + } + } + + public void seekTo(int ms) { + if (mCurrentState == State.IDLE || mCurrentState == State.INITIALIZED || + mCurrentState == State.STOPPED || mCurrentState == State.ERROR || + mMediaPlayer == null) { + return; + } + + boolean rewindResult = mMediaPlayer.rewindTo(ms * 1000); + System.out.println("Player seek to " + ms + "ms, " + getCurrentPosition() + "ms/"+getDuration() + "ms, rewind result "+ rewindResult); + } + + public void fixSizeAsync() { + CocosHelper.ruOnUIThreadSync(this::fixSizeUIThread); + } + + private void fixSizeUIThread() { + if (mFullScreenEnabled) { + Rect rect = mAbility.getWindow().getBoundRect(); + fixSize(rect.left, rect.top, rect.getWidth(), rect.getHeight()); + } else { + if(mViewWidth == 0 || mViewHeight == 0) { + CocosHelper.runOnUIThread(this::fixSizeUIThread, true); + return; + } + fixSize(mViewLeft, mViewTop, mViewWidth, mViewHeight); + } + } + + public void fixSize(int left, int top, int width, int height) { + if (mVideoWidth == 0 || mVideoHeight == 0) { + mVisibleLeft = left; + mVisibleTop = top; + mVisibleWidth = width; + mVisibleHeight = height; + } else if (width != 0 && height != 0) { + if (mKeepRatio && !mFullScreenEnabled) { + if (mVideoWidth * height > width * mVideoHeight) { + mVisibleWidth = width; + mVisibleHeight = width * mVideoHeight / mVideoWidth; + } else if (mVideoWidth * height < width * mVideoHeight) { + mVisibleWidth = height * mVideoWidth / mVideoHeight; + mVisibleHeight = height; + } + mVisibleLeft = left + (width - mVisibleWidth) / 2; + mVisibleTop = top + (height - mVisibleHeight) / 2; + } else { + mVisibleLeft = left; + mVisibleTop = top; + mVisibleWidth = width; + mVisibleHeight = height; + } + } else { + mVisibleLeft = left; + mVisibleTop = top; + mVisibleWidth = mVideoWidth; + mVisibleHeight = mVideoHeight; + } + + setComponentSize(mVideoWidth, mVideoHeight); + + StackLayout.LayoutConfig lParams = new StackLayout.LayoutConfig(StackLayout.LayoutConfig.MATCH_CONTENT, + StackLayout.LayoutConfig.MATCH_CONTENT); + lParams.setMarginLeft(mVisibleLeft); + lParams.setMarginTop(mVisibleTop); + lParams.width = mVisibleWidth; + lParams.height = mVisibleHeight; + setLayoutConfig(lParams); + } + + public int resolveAdjustedSize(int desiredSize, int measureSpec) { + int result = desiredSize; + int specMode = EstimateSpec.getMode(measureSpec); + int specSize = EstimateSpec.getSize(measureSpec); + + switch (specMode) { + case EstimateSpec.UNCONSTRAINT: + /* Parent says we can be as big as we want. Just don't be larger + * than max size imposed on ourselves. + */ + result = desiredSize; + break; + + case EstimateSpec.NOT_EXCEED: + /* Parent says we can be as big as we want, up to specSize. + * Don't be larger than specSize, and don't be larger than + * the max size imposed on ourselves. + */ + result = Math.min(desiredSize, specSize); + break; + + case EstimateSpec.PRECISE: + // No choice. Do what we are told. + result = specSize; + break; + } + + return result; + } + + // =========================================================== + // Private functions + // =========================================================== + + private void initVideoView() { + mVideoWidth = 0; + mVideoHeight = 0; + if(this.getSurfaceOps().isPresent()) { + getSurfaceOps().get().addCallback(new SurfaceOps.Callback() { + @Override + public void surfaceCreated(SurfaceOps surfaceOps) { + CocosVideoView.this.mMediaPlayer.setVideoSurface(surfaceOps.getSurface()); + openVideo(); + } + + @Override + public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) { + CocosVideoView.this.mMediaPlayer.setVideoSurface(surfaceOps.getSurface()); + } + + @Override + public void surfaceDestroyed(SurfaceOps surfaceOps) { + // after we return from this we can't use the surface any more + mPositionBeforeRelease = getCurrentPosition(); + CocosVideoView.this.doRelease(); + } + }); + } + mCurrentState = State.IDLE; + setTouchEventListener(this); + setFocusable(FOCUS_ADAPTABLE); + setTouchFocusable(true); + } + + /** + * @hide + */ + private void setVideoURI(Source uri, Map headers) { + mVideoUri = uri; + mVideoWidth = 0; + mVideoHeight = 0; + } + + private void openVideo() { + if (!getSurfaceOps().isPresent() || getSurfaceOps().get().getSurface() == null) { + // not ready for playback just yet, will try again later + return; + } + if (mIsAssetRouse) { + if (mVideoFilePath == null) + return; + } else if (mVideoUri == null) { + return; + } + +// this.pausePlaybackService(); + + try { + mMediaPlayer.setSurfaceOps(getSurfaceOps().get()); +// mMediaPlayer.setAudioStreamType(AudioManager.AudioVolumeType.STREAM_MUSIC.getValue()); + getSurfaceOps().get().setKeepScreenOn(true); + + if (mIsAssetRouse) { + RawFileDescriptor afd = mAbility.getResourceManager().getRawFileEntry(mVideoFilePath).openRawFileDescriptor(); + mMediaPlayer.setSource(afd); + } else { + mMediaPlayer.setSource(mVideoUri); + } + mCurrentState = State.INITIALIZED; + + + // Use Prepare() instead of PrepareAsync to make things easy. +// CompletableFuture.runAsync(()->{ + mMediaPlayer.prepare(); + CocosHelper.ruOnUIThreadSync(this::showFirstFrame); +// }); +// mMediaPlayer.prepareAsync(); + } catch (IOException ex) { + HiLog.error(TAG, "Unable to open content: " + mVideoUri + ex); + mCurrentState = State.ERROR; + mPlayerCallback.onError(Player.PLAYER_ERROR_UNKNOWN, 0); + } catch (IllegalArgumentException ex) { + HiLog.error(TAG, "Unable to open content: " + mVideoUri + ex); + mCurrentState = State.ERROR; + mPlayerCallback.onError(Player.PLAYER_ERROR_UNKNOWN, 0); + } + } + + private final Player.IPlayerCallback mPlayerCallback = new Player.IPlayerCallback() { + @Override + public void onPrepared() { + mVideoWidth = mMediaPlayer.getVideoWidth(); + mVideoHeight = mMediaPlayer.getVideoHeight(); + + if (mVideoWidth != 0 && mVideoHeight != 0) { + fixSizeAsync(); + } + + if (!mMetaUpdated) { + CocosVideoView.this.sendEvent(EVENT_META_LOADED); + CocosVideoView.this.sendEvent(EVENT_READY_TO_PLAY); + mMetaUpdated = true; + } + + mCurrentState = State.PREPARED; + + if (mPositionBeforeRelease > 0) { + CocosVideoView.this.start(); + CocosVideoView.this.seekTo(mPositionBeforeRelease); + mPositionBeforeRelease = 0; + } + } + + @Override + public void onMessage(int i, int i1) { + HiLog.debug(TAG, "Message "+ i + ", "+ i1); + } + + String translateErrorMessage(int errorType, int errorCode) { + String msg = ""; + switch (errorType) { + case Player.PLAYER_ERROR_UNKNOWN: + msg += "type: PLAYER_ERROR_UNKNOWN, "; + break; + case Player.PLAYER_ERROR_SERVER_DIED: + msg += "type: PLAYER_ERROR_SERVER_DIED, "; + break; + default: + msg += "type: " + errorType+ ", "; + break; + } + switch (errorCode) { + case Player.PLAYER_ERROR_IO: + msg += "code: PLAYER_ERROR_IO"; + break; + case Player.PLAYER_ERROR_MALFORMED: + msg += "code: PLAYER_ERROR_MALFORMED"; + break; + case Player.PLAYER_ERROR_UNSUPPORTED: + msg += "code: PLAYER_ERROR_UNSUPPORTED"; + break; + case Player.PLAYER_ERROR_TIMED_OUT: + msg += "code: PLAYER_ERROR_TIMED_OUT"; + break; + case Player.PLAYER_ERROR_SYSTEM: + msg += "code: PLAYER_ERROR_SYSTEM"; + default: + msg += "code: " + errorCode+ ", "; + break; + } + return msg; + } + + @Override + public void onError(int errorType, int errorCode) { + mCurrentState = State.ERROR; + + //TODO: i18n + CocosHelper.runOnUIThread(()->{ + ToastDialog dialog = new ToastDialog(getContext()); + dialog.setText("Video:" + translateErrorMessage(errorType, errorCode)); + dialog.setDuration(5000); + dialog.show(); + }); + } + + @Override + public void onResolutionChanged(int i, int i1) { + + } + + @Override + public void onPlayBackComplete() { + mCurrentState = State.PLAYBACK_COMPLETED; + CocosVideoView.this.sendEvent(EVENT_COMPLETED); + } + + @Override + public void onRewindToComplete() { + System.out.println("rewind complete"); + } + + @Override + public void onBufferingChange(int i) { + + } + + @Override + public void onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) { + + } + + @Override + public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) { + + } + }; + + @Override + public void onStateChanged(Lifecycle.Event event, Intent intent) { + switch (event) { + case ON_START: + break; + case ON_ACTIVE: + mMediaPlayer.play(); + break; + case ON_INACTIVE: + mMediaPlayer.pause(); + break; + case ON_BACKGROUND: + mMediaPlayer.stop(); + break; + case ON_STOP: + mMediaPlayer.release(); + break; + } + } + + private static final int EVENT_PLAYING = 0; + private static final int EVENT_PAUSED = 1; + private static final int EVENT_STOPPED = 2; + private static final int EVENT_COMPLETED = 3; + private static final int EVENT_META_LOADED = 4; + private static final int EVENT_CLICKED = 5; + private static final int EVENT_READY_TO_PLAY = 6; + + /* + * release the media player in any state + */ + private void doRelease() { + if (mMediaPlayer != null) { + mMediaPlayer.release(); + mMediaPlayer = null; + mCurrentState = State.IDLE; + } + } + + private void showFirstFrame() { + mMediaPlayer.rewindTo(1); + } + + private void sendEvent(int event) { + if (this.mOnVideoEventListener != null) { + this.mOnVideoEventListener.onVideoEvent(this.mViewTag, event); + } + } + +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosWebView.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosWebView.java new file mode 100644 index 0000000..d88137a --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosWebView.java @@ -0,0 +1,138 @@ +/**************************************************************************** + Copyright (c) 2017-2021 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +package com.cocos.lib; + + +import ohos.agp.components.PositionLayout; +import ohos.agp.components.webengine.*; +import ohos.app.Context; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +import java.net.URI; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + + +public class CocosWebView extends WebView { + private static final HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP, 0, CocosWebViewHelper.class.getSimpleName()); + + private int mViewTag; + private String mJSScheme; + + public CocosWebView(Context context, int viewTag) { + super(context); + this.mViewTag = viewTag; + this.mJSScheme = ""; + this.setWebAgent(new Cocos2dxWebViewClient()); + + + WebConfig cfg = getWebConfig(); + cfg.setJavaScriptPermit(true); + cfg.setDataAbilityPermit(true); + cfg.setLoadsImagesPermit(true); + cfg.setWebStoragePermit(true); + cfg.setViewPortFitScreen(true); + cfg.setTextAutoSizing(true); + +// setTouchFocusable(true); +// setFocusable(FOCUS_ENABLE); +// requestFocus(); + } + + public void setJavascriptInterfaceScheme(String scheme) { + this.mJSScheme = scheme != null ? scheme : ""; + } + + public void setScalesPageToFit(boolean scalesPageToFit) { + this.getWebConfig().setViewPortFitScreen(scalesPageToFit); + } + + class Cocos2dxWebViewClient extends WebAgent { + @Override + public boolean isNeedLoadUrl(WebView view, ResourceRequest request) { + String urlString = request.getRequestUrl().toString(); + try { + URI uri = new URI(request.getRequestUrl().toString()); + if (uri.getScheme().equals(mJSScheme)) { + CocosHelper.runOnGameThreadAtForeground( + () -> CocosWebViewHelper._onJsCallback(mViewTag, urlString) + ); + return true; + } + } catch (Exception e) { + HiLog.debug(TAG, "Failed to create URI from url"); + } + + FutureTask shouldStartLoadingCB = new FutureTask<>( + ()-> CocosWebViewHelper._shouldStartLoading(mViewTag, urlString) + ); + + // run worker on cocos thread + CocosHelper.runOnGameThreadAtForeground(shouldStartLoadingCB); + // wait for result from cocos thread + + try { + return shouldStartLoadingCB.get(); + } catch (InterruptedException ex) { + HiLog.debug(TAG, "'shouldOverrideUrlLoading' failed"); + } catch (ExecutionException e) { + e.printStackTrace(); + } + return true; + } + + @Override + public void onPageLoaded(WebView view, final String url) { + super.onPageLoaded(view, url); + + CocosHelper.runOnGameThreadAtForeground( + () -> CocosWebViewHelper._didFinishLoading(mViewTag, url) + ); + } + + @Override + public void onError(WebView view, ResourceRequest request, ResourceError error) { + super.onError(view, request, error); + final String failingUrl = request.getRequestUrl().toString(); + CocosHelper.runOnGameThreadAtForeground( + () -> CocosWebViewHelper._didFailLoading(mViewTag, failingUrl) + ); + } + } + + public void setWebViewRect(int left, int top, int maxWidth, int maxHeight) { + PositionLayout.LayoutConfig layoutParams = new PositionLayout.LayoutConfig( + PositionLayout.LayoutConfig.MATCH_CONTENT, + PositionLayout.LayoutConfig.MATCH_CONTENT); + layoutParams.setMarginLeft(left); + layoutParams.setMarginTop(top); + layoutParams.width = maxWidth; + layoutParams.height = maxHeight; + this.setLayoutConfig(layoutParams); + + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosWebViewHelper.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosWebViewHelper.java new file mode 100644 index 0000000..bc3f689 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/CocosWebViewHelper.java @@ -0,0 +1,306 @@ +/**************************************************************************** + Copyright (c) 2010-2011 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + + +import ohos.agp.colors.RgbColor; +import ohos.agp.components.Component; +import ohos.agp.components.DirectionalLayout; +import ohos.agp.components.StackLayout; + +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + + +public class CocosWebViewHelper { + private static final String TAG = CocosWebViewHelper.class.getSimpleName(); + + private static StackLayout sLayout; + + private static ConcurrentHashMap webViews; + private static int viewTag = 0; + + public CocosWebViewHelper(StackLayout layout) { + + CocosWebViewHelper.sLayout = layout; + + CocosWebViewHelper.webViews = new ConcurrentHashMap(); + } + + private static native boolean shouldStartLoading(int index, String message); + private static native void didFinishLoading(int index, String message); + private static native void didFailLoading(int index, String message); + private static native void onJsCallback(int index, String message); + + public static boolean _shouldStartLoading(int index, String message) { return shouldStartLoading(index, message); } + public static void _didFinishLoading(int index, String message) { didFinishLoading(index, message); } + public static void _didFailLoading(int index, String message) { didFailLoading(index, message); } + public static void _onJsCallback(int index, String message) { onJsCallback(index, message); } + + public static int createWebView() { + final int index = viewTag; + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = new CocosWebView(CocosHelper.getContext(), index); + DirectionalLayout.LayoutConfig lParams = new DirectionalLayout.LayoutConfig( + DirectionalLayout.LayoutConfig.MATCH_CONTENT, + DirectionalLayout.LayoutConfig.MATCH_CONTENT); + sLayout.addComponent(webView, lParams); + + webViews.put(index, webView); + } + }); + return viewTag++; + } + + public static void removeWebView(final int index) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webViews.remove(index); + sLayout.removeComponent(webView); + webView.release(); + webView = null; + } + } + }); + } + + public static void setVisible(final int index, final boolean visible) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.setVisibility(visible ? Component.VISIBLE : Component.HIDE); + } + } + }); + } + + public static void setWebViewRect(final int index, final int left, final int top, final int maxWidth, final int maxHeight) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.setWebViewRect(left, top, maxWidth, maxHeight); + } + } + }); + } + + public static void setBackgroundTransparent(final int index, final boolean isTransparent) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + RgbColor color = isTransparent ? new RgbColor(255, 255, 255, 0 ): new RgbColor(255, 255, 255, 255); +// Element bg = webView.getResourceManager().getElement(isTransparent ? ResourceTable.Graphic_bg_webview_transparent : ResourceTable.Graphic_bg_webview_white); + webView.setWebViewBackground(color); + + } + } + }); + } + + public static void setJavascriptInterfaceScheme(final int index, final String scheme) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.setJavascriptInterfaceScheme(scheme); + } + } + }); + } + + public static void loadData(final int index, final String data, final String mimeType, final String encoding, final String baseURL) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.load(data, mimeType, encoding, baseURL, null); + } + } + }); + } + + public static void loadHTMLString(final int index, final String data, final String baseUrl) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.load(data, null, null, baseUrl, null); + } + } + }); + } + + public static void loadUrl(final int index, final String url) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.load(url); + } + } + }); + } + + public static void loadFile(final int index, final String filePath) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.load(filePath); + } + } + }); + } + + public static void stopLoading(final int index) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + // FIXME: stop + // webView.abo(); + } + } + }); + + } + + public static void reload(final int index) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.reload(); + } + } + }); + } + + public static T callInMainThread(Callable call) throws ExecutionException, InterruptedException { + FutureTask task = new FutureTask(call); + CocosHelper.runOnUIThread(task); + return task.get(); + } + + public static boolean canGoBack(final int index) throws InterruptedException { + Callable callable = new Callable() { + @Override + public Boolean call() throws Exception { + CocosWebView webView = webViews.get(index); + return webView != null && webView.getNavigator().canGoBack(); + } + }; + try { + return callInMainThread(callable); + } catch (ExecutionException e) { + return false; + } + } + + public static boolean canGoForward(final int index) throws InterruptedException { + Callable callable = new Callable() { + @Override + public Boolean call() throws Exception { + CocosWebView webView = webViews.get(index); + return webView != null && webView.getNavigator().canGoForward(); + } + }; + try { + return callInMainThread(callable); + } catch (ExecutionException e) { + return false; + } + } + + public static void goBack(final int index) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.getNavigator().goBack(); + } + } + }); + } + + public static void goForward(final int index) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.getNavigator().goForward(); + } + } + }); + } + + public static void evaluateJS(final int index, final String js) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + // webView.load("javascript:" + js); + webView.executeJs(js, null);; + } + } + }); + } + + public static void setScalesPageToFit(final int index, final boolean scalesPageToFit) { + CocosHelper.runOnUIThread(new Runnable() { + @Override + public void run() { + CocosWebView webView = webViews.get(index); + if (webView != null) { + webView.setScalesPageToFit(scalesPageToFit); + } + } + }); + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/GlobalObject.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/GlobalObject.java new file mode 100644 index 0000000..23e5b09 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/GlobalObject.java @@ -0,0 +1,38 @@ +/**************************************************************************** + Copyright (c) 2020 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import ohos.aafwk.ability.AbilitySlice; + +public class GlobalObject { + private static AbilitySlice sActivity = null; + + public static void setAbilitySlice(AbilitySlice activity) { + GlobalObject.sActivity = activity; + } + + public static AbilitySlice getAbilitySlice() { + return GlobalObject.sActivity; + } +} \ No newline at end of file diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/JsbBridge.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/JsbBridge.java new file mode 100644 index 0000000..375ae8e --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/JsbBridge.java @@ -0,0 +1,61 @@ +/**************************************************************************** + Copyright (c) 2018-2021 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +public class JsbBridge { + public interface ICallback{ + /** + * Applies this callback to the given argument. + * + * @param arg0 as input + * @param arg1 as input + */ + void onScript(String arg0, String arg1); + } + private static ICallback callback; + + private static void callByScript(String arg0, String arg1){ + if(JsbBridge.callback != null) + callback.onScript(arg0, arg1); + } + + /**Add a callback which you would like to apply + * @param f ICallback, the method which will be actually applied. multiple calls will override + * */ + public static void setCallback(ICallback f){ + JsbBridge.callback = f; + } + /** + * Java dispatch Js event, use native c++ code + * @param arg0 input values + */ + private static native void nativeSendToScript(String arg0, String arg1); + public static void sendToScript(String arg0, String arg1){ + nativeSendToScript(arg0, arg1); + } + public static void sendToScript(String arg0){ + nativeSendToScript(arg0, null); + } +} \ No newline at end of file diff --git a/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/JsbBridgeWrapper.java b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/JsbBridgeWrapper.java new file mode 100644 index 0000000..6bc71bb --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/java/com/cocos/lib/JsbBridgeWrapper.java @@ -0,0 +1,109 @@ +/**************************************************************************** + Copyright (c) 2018-2021 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in this License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +package com.cocos.lib; + +import java.util.ArrayList; +import java.util.HashMap; + +public class JsbBridgeWrapper { + //Interface for listener, should be implemented and dispatched + public interface OnScriptEventListener { + void onScriptEvent(String arg); + } + /** + * Get the instance of JsbBridgetWrapper + */ + public static JsbBridgeWrapper getInstance() { + if (instance == null) { + instance = new JsbBridgeWrapper(); + } + return instance; + } + /** + * Add a listener to specified event, if the event does not exist, the wrapper will create one. Concurrent listener will be ignored + */ + public void addScriptEventListener(String eventName, OnScriptEventListener listener) { + if (eventMap.get(eventName) == null) { + eventMap.put(eventName, new ArrayList()); + } + eventMap.get(eventName).add(listener); + } + /** + * Remove listener for specified event, concurrent event will be deleted. Return false only if the event does not exist + */ + public boolean removeScriptEventListener(String eventName, OnScriptEventListener listener) { + ArrayList arr = eventMap.get(eventName); + if (arr == null) { + return false; + } + arr.remove(listener); + return true; + } + /** + * Remove all listener for event specified. + */ + public void removeAllListenersForEvent(String eventName) { + this.eventMap.remove(eventName); + } + /** + * Remove all event registered. Use it carefully! + */ + public void removeAllListeners() { + this.eventMap.clear(); + } + /** + * Dispatch the event with argument, the event should be registered in javascript, or other script language in future. + */ + public void dispatchEventToScript(String eventName, String arg) { + JsbBridge.sendToScript(eventName, arg); + } + /** + * Dispatch the event which is registered in javascript, or other script language in future. + */ + public void dispatchEventToScript(String eventName) { + JsbBridge.sendToScript(eventName); + } + + private JsbBridgeWrapper() { + JsbBridge.setCallback(new JsbBridge.ICallback() { + @Override + public void onScript(String arg0, String arg1) { + triggerEvents(arg0, arg1); + } + }); + } + + private final HashMap> eventMap = new HashMap<>(); + private static JsbBridgeWrapper instance; + + private void triggerEvents(String eventName, String arg) { + ArrayList arr = eventMap.get(eventName); + if (arr == null) + return; + for (OnScriptEventListener m : arr) { + m.onScriptEvent(arg); + } + } +} diff --git a/cocos/platform/ohos/libcocos/src/main/res/.keep b/cocos/platform/ohos/libcocos/src/main/res/.keep new file mode 100644 index 0000000..e69de29 diff --git a/cocos/platform/ohos/libcocos/src/main/resources/base/element/strings.json b/cocos/platform/ohos/libcocos/src/main/resources/base/element/strings.json new file mode 100644 index 0000000..d6e1e0b --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/resources/base/element/strings.json @@ -0,0 +1,12 @@ +{ + "string": [ + { + "name": "lib_name", + "value": "cocos" + }, + { + "name": "done", + "value": "Done" + } + ] +} \ No newline at end of file diff --git a/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/bg_common_unpress_btn.xml b/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/bg_common_unpress_btn.xml new file mode 100644 index 0000000..e4cd162 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/bg_common_unpress_btn.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/bg_webview_transparent.xml b/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/bg_webview_transparent.xml new file mode 100644 index 0000000..f500037 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/bg_webview_transparent.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/bg_webview_white.xml b/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/bg_webview_white.xml new file mode 100644 index 0000000..47bd35f --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/bg_webview_white.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/darkgreen_roundrect.xml b/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/darkgreen_roundrect.xml new file mode 100644 index 0000000..a3ae2e1 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/resources/base/graphic/darkgreen_roundrect.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/cocos/platform/ohos/libcocos/src/main/resources/base/layout/editbox_layout.xml b/cocos/platform/ohos/libcocos/src/main/resources/base/layout/editbox_layout.xml new file mode 100644 index 0000000..c55dfa3 --- /dev/null +++ b/cocos/platform/ohos/libcocos/src/main/resources/base/layout/editbox_layout.xml @@ -0,0 +1,44 @@ + + + + + + +